Merge pull request #2701 from rn/vpnkit

Add support for publish port on localhost to the hyperkit backend
This commit is contained in:
Rolf Neugebauer 2017-11-10 13:29:02 +00:00 committed by GitHub
commit 581cbdd1e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
75 changed files with 9036 additions and 847 deletions

View File

@ -62,29 +62,40 @@ this option palatable, and provide alternative options to access the
VMs over the network below.
### Accessing services
### Accessing network services
The simplest way to access networking services exposed by a LinuxKit VM is to use a Docker for Mac container.
`hyperkit` offers a number of ways for accessing network services
running inside the LinuxKit VM from the host. These depend on the
networking mode selected via `-networking`. The default mode is
`docker-for-mac` where the same VPNkit instance is shared between
LinuxKit VMs and the VM running as part of Docker for Mac.
#### Access from the Docker for Mac VM (`-networking docker-for-mac`)
The simplest way to access networking services exposed by a LinuxKit
VM is to use a Docker for Mac container. For example, to access an ssh
server in a LinuxKit VM, create a ssh client container from:
For example, to access an ssh server in a LinuxKit VM, create a ssh client container from:
```
FROM alpine:edge
RUN apk add --no-cache openssh-client
```
and then run
```
docker build -t ssh .
docker run --rm -ti -v ~/.ssh:/root/.ssh ssh ssh <IP address of VM>
```
### Forwarding ports to the host
#### Forwarding ports with `socat` (`-networking docker-for-mac`)
Ports can be forwarded to the host using a container with `socat` or with VPNKit which comes with Docker for Mac.
A `socat` container on Docker for Mac can be used to proxy between the
LinuxKit VM's ports and localhost. For example, to expose the redis
port from the [RedisOS example](../examples/redis-os.yml), use this
Dockerfile:
#### Port forwarding with `socat`
A `socat` container can be used to proxy between the LinuxKit VM's ports and
localhost. For example, to expose the redis port from the [RedisOS
example](../examples/redis-os.yml), use this Dockerfile:
```
FROM alpine:edge
RUN apk add --no-cache socat
@ -96,29 +107,57 @@ docker build -t socat .
docker run --rm -t -d -p 6379:6379 socat tcp-listen:6379,reuseaddr,fork tcp:<IP address of VM>:6379
```
#### Port forwarding with VPNKit
#### Port forwarding with VPNKit (`-networking docker-for-mac`)
VPNKit has the general tooling to expose any guest VM port on the host (just
like it does with containers in Docker for Mac). To enable forwarding, a
`vpnkit-forwarder` container must be running in the VM. The VM also has to be
booted with `linuxkit run hyperkit -networking=vpnkit`.
There is **experimental** support for exposing selected ports of the
guest on `localhost` using the `-publish` command line option. For
example, using `-publish 2222:22/tcp` exposes the guest TCP port 22 on
localhost on port 2222. Multiple `-publish` options can be
specified. For example, the image build from the [`sshd
example`](../examples/sshd.yml) can be started with:
VPNKit uses a 9P mount in `/port` for coordination between the components.
Port forwarding can be manually set up by creating new directories in `/port`
or by using the `vpnkit-expose-port` tool. More details about the forwarding
mechanism is available in the [VPNKit
```
linuxkit run -publish 2222:22/tcp sshd
```
and then you can log into the LinuxKit VM with `ssh -p 2222
root@localhost`.
Note, this mode is **experimental** and may cause the VPNKit instance
shared with Docker for Mac being confused about which ports are
currently in use, in particular if the LinuxKit VM does not exit
gracefully. This can typically be fixed by restarting Docker for Mac.
#### Port forwarding with VPNKit (`-networking vpnkit`)
An alternative to the previous method is to start your own copy of
`vpnkit` (or connect to an already running instance). This can be done
using the `-networking vpnkit` command line option.
VPNKit uses a 9P mount in `/port` for coordination between
components. The first VM on a VPNKit instance currently needs mount
the 9P filesystem and also needs to run the `vpnkit-forwarder` service
to enable port forwarding to localhost. A full example with `vpnkit`
forwarding of `sshd` is available in
[examples/vpnkit-forwarder.yml](/examples/vpnkit-forwarder.yml).
To run this example with its own instance of VPNKit, use:
```
linuxkit run -networking vpnkit -publish 2222:22/tcp vpnkit-forwarder
```
You can then access it via:
```
ssh -p 2222 root@localhost
```
More details about the VPNKit forwarding mechanism is available in the
[VPNKit
documentation](https://github.com/moby/vpnkit/blob/master/docs/ports.md#signalling-from-the-vm-to-the-host).
To get started, the easiest solution at the moment is to use the
`vpnkit-expose-port` command to tell the forwarder and `vpnkit` which ports to
forward. This process requires fewer privileges than `vpnkit-forwarder` and can
be run in a container without networking.
A full example with `vpnkit` forwarding of `sshd` is available in [examples/vpnkit-forwarder.yml](/examples/vpnkit-forwarder.yml).
After building and running the example you should be able to connect to ssh on port 22 on
localhost. The port can also be exposed externally by changing the host IP in
the example to 0.0.0.0.
## Integration services and Metadata

View File

@ -26,14 +26,6 @@ services:
- /var/vpnkit:/port
net: host
command: ["/vpnkit-forwarder"]
- name: vpnkit-expose-port
image: linuxkit/vpnkit-forwarder:c7e61d9250de0b21455dc5c8bb885bd8faa31621
net: none
binds:
- /var/vpnkit:/port
command: ["/vpnkit-expose-port","-i",
"-host-ip","127.0.0.1","-host-port","22",
"-container-ip","127.0.0.1","-container-port","22","-no-local-ip"]
files:
- path: root/.ssh/authorized_keys

View File

@ -1,6 +1,7 @@
package main
import (
"context"
"flag"
"fmt"
"io/ioutil"
@ -11,8 +12,9 @@ import (
"strconv"
"strings"
"github.com/google/uuid"
"github.com/moby/hyperkit/go"
"github.com/satori/go.uuid"
"github.com/moby/vpnkit/go/pkg/vpnkit"
log "github.com/sirupsen/logrus"
)
@ -44,16 +46,18 @@ func runHyperKit(args []string) {
ipStr := flags.String("ip", "", "Preferred IPv4 address for the VM.")
state := flags.String("state", "", "Path to directory to keep VM state in")
vsockports := flags.String("vsock-ports", "", "List of vsock ports to forward from the guest on startup (comma separated). A unix domain socket for each port will be created in the state directory")
networking := flags.String("networking", hyperkitNetworkingDefault, "Networking mode. Valid options are 'default', 'docker-for-mac', 'vpnkit[,socket-path]', 'vmnet' and 'none'. 'docker-for-mac' connects to the network used by Docker for Mac. 'vpnkit' connects to the VPNKit socket specified. If socket-path is omitted a new VPNKit instance will be started and 'vpnkit_eth.sock' will be created in the state directory. 'vmnet' uses the Apple vmnet framework, requires root/sudo. 'none' disables networking.`")
networking := flags.String("networking", hyperkitNetworkingDefault, "Networking mode. Valid options are 'default', 'docker-for-mac', 'vpnkit[,eth-socket-path[,port-socket-path]]', 'vmnet' and 'none'. 'docker-for-mac' connects to the network used by Docker for Mac. 'vpnkit' connects to the VPNKit socket(s) specified. If no socket path is provided a new VPNKit instance will be started and 'vpnkit_eth.sock' and 'vpnkit_port.sock' will be created in the state directory. 'port-socket-path' is only needed if you want to publish ports on localhost using an existing VPNKit instance. 'vmnet' uses the Apple vmnet framework, requires root/sudo. 'none' disables networking.`")
vpnKitUUID := flags.String("vpnkit-uuid", "", "Optional UUID used to identify the VPNKit connection. Overrides 'uuid.vpnkit' in the state directory.")
vpnkitUUID := flags.String("vpnkit-uuid", "", "Optional UUID used to identify the VPNKit connection. Overrides 'vpnkit.uuid' in the state directory.")
publishFlags := multipleFlag{}
flags.Var(&publishFlags, "publish", "Publish a vm's port(s) to the host (default [])")
// Boot type; we try to determine automatically
uefiBoot := flags.Bool("uefi", false, "Use UEFI boot")
isoBoot := flags.Bool("iso", false, "Boot image is an ISO")
kernelBoot := flags.Bool("kernel", false, "Boot image is kernel+initrd+cmdline 'path'-kernel/-initrd/-cmdline")
// Paths and settings for UEFI firware
// Paths and settings for UEFI firmware
// Note, the default uses the firmware shipped with Docker for Mac
fw := flags.String("fw", "/Applications/Docker.app/Contents/Resources/uefi/UEFI.fd", "Path to OVMF firmware for UEFI boot")
@ -154,24 +158,29 @@ func runHyperKit(args []string) {
// Create UUID for VPNKit or reuse an existing one from state dir. IP addresses are
// assigned to the UUID, so to get the same IP we have to store the initial UUID. If
// has specified a VPNKit UUID the file is ignored.
if *vpnKitUUID == "" {
vpnKitUUIDFile := filepath.Join(*state, "uuid.vpnkit")
if _, err := os.Stat(vpnKitUUIDFile); os.IsNotExist(err) {
*vpnKitUUID = uuid.NewV4().String()
if err := ioutil.WriteFile(vpnKitUUIDFile, []byte(*vpnKitUUID), 0600); err != nil {
log.Fatalf("Unable to write to %s: %v", vpnKitUUIDFile, err)
if *vpnkitUUID == "" {
vpnkitUUIDFile := filepath.Join(*state, "vpnkit.uuid")
if _, err := os.Stat(vpnkitUUIDFile); os.IsNotExist(err) {
*vpnkitUUID = uuid.New().String()
if err := ioutil.WriteFile(vpnkitUUIDFile, []byte(*vpnkitUUID), 0600); err != nil {
log.Fatalf("Unable to write to %s: %v", vpnkitUUIDFile, err)
}
} else {
uuid, err := ioutil.ReadFile(vpnKitUUIDFile)
uuidBytes, err := ioutil.ReadFile(vpnkitUUIDFile)
if err != nil {
log.Fatalf("Unable to read VPNKit UUID from %s: %v", vpnKitUUIDFile, err)
log.Fatalf("Unable to read VPNKit UUID from %s: %v", vpnkitUUIDFile, err)
}
*vpnKitUUID = string(uuid)
if tmp, err := uuid.ParseBytes(uuidBytes); err != nil {
log.Fatalf("Unable to parse VPNKit UUID from %s: %v", vpnkitUUIDFile, err)
} else {
*vpnkitUUID = tmp.String()
}
}
}
// Generate new UUID, otherwise /sys/class/dmi/id/product_uuid is identical on all VMs
vmUUID := uuid.NewV4().String()
vmUUID := uuid.New().String()
// Run
var cmdline []byte
@ -208,39 +217,43 @@ func runHyperKit(args []string) {
}
// Select network mode
var vpnKitProcess *os.Process
var vpnkitProcess *os.Process
var vpnkitPortSocket string
if *networking == "" || *networking == "default" {
dflt := hyperkitNetworkingDefault
networking = &dflt
}
netMode := strings.SplitN(*networking, ",", 2)
switch netMode[0] {
case hyperkitNetworkingDockerForMac:
h.VPNKitSock = filepath.Join(os.Getenv("HOME"), "Library/Containers/com.docker.docker/Data/s50")
vpnkitPortSocket = filepath.Join(os.Getenv("HOME"), "Library/Containers/com.docker.docker/Data/s51")
case hyperkitNetworkingVPNKit:
if len(netMode) > 1 {
// Socket path specified, try to use existing VPNKit instance
h.VPNKitSock = netMode[1]
if len(netMode) > 2 {
vpnkitPortSocket = netMode[2]
}
} else {
// Start new VPNKit instance
h.VPNKitSock = filepath.Join(*state, "vpnkit_eth.sock")
vpnKitPortSocket := filepath.Join(*state, "vpnkit_port.sock")
vpnkitPortSocket = filepath.Join(*state, "vpnkit_port.sock")
vsockSocket := filepath.Join(*state, "connect")
vpnKitProcess, err = launchVPNKit(h.VPNKitSock, vsockSocket, vpnKitPortSocket)
vpnkitProcess, err = launchVPNKit(h.VPNKitSock, vsockSocket, vpnkitPortSocket)
if err != nil {
log.Fatalln("Unable to start vpnkit: ", err)
}
defer func() {
if vpnKitProcess != nil {
err := vpnKitProcess.Kill()
if vpnkitProcess != nil {
err := vpnkitProcess.Kill()
if err != nil {
log.Println(err)
}
}
}()
// The guest will use this 9P mount to configure which ports to forward
h.Sockets9P = []hyperkit.Socket9P{{Path: vpnKitPortSocket, Tag: "port"}}
h.Sockets9P = []hyperkit.Socket9P{{Path: vpnkitPortSocket, Tag: "port"}}
// VSOCK port 62373 is used to pass traffic from host->guest
h.VSockPorts = append(h.VSockPorts, 62373)
}
@ -265,7 +278,7 @@ func runHyperKit(args []string) {
h.CPUs = *cpus
h.Memory = *mem
h.VPNKitUUID = *vpnKitUUID
h.VPNKitUUID = *vpnkitUUID
if *ipStr != "" {
if ip := net.ParseIP(*ipStr); len(ip) > 0 && ip.To4() != nil {
h.VPNKitPreferredIPv4 = ip.String()
@ -274,6 +287,23 @@ func runHyperKit(args []string) {
}
}
// Publish ports if requested and VPNKit is used
if len(publishFlags) != 0 {
switch netMode[0] {
case hyperkitNetworkingDockerForMac, hyperkitNetworkingVPNKit:
if vpnkitPortSocket == "" {
log.Fatalf("The VPNKit Port socket path is required to publish ports")
}
f, err := vpnkitPublishPorts(h, publishFlags, vpnkitPortSocket)
if err != nil {
log.Fatalf("Publish ports failed with: %v", err)
}
defer f()
default:
log.Fatalf("Port publishing requires %q or %q networking mode", hyperkitNetworkingDockerForMac, hyperkitNetworkingVPNKit)
}
}
err = h.Run(string(cmdline))
if err != nil {
log.Fatalf("Cannot run hyperkit: %v", err)
@ -300,7 +330,7 @@ func createListenSocket(path string) (*os.File, error) {
func launchVPNKit(etherSock string, vsockSock string, portSock string) (*os.Process, error) {
var err error
vpnKitPath, err := exec.LookPath("vpnkit")
vpnkitPath, err := exec.LookPath("vpnkit")
if err != nil {
return nil, fmt.Errorf("Unable to find vpnkit binary")
}
@ -315,7 +345,7 @@ func launchVPNKit(etherSock string, vsockSock string, portSock string) (*os.Proc
return nil, err
}
cmd := exec.Command(vpnKitPath,
cmd := exec.Command(vpnkitPath,
"--ethernet", "fd:3",
"--vsock-path", vsockSock,
"--port", "fd:4")
@ -329,6 +359,7 @@ func launchVPNKit(etherSock string, vsockSock string, portSock string) (*os.Proc
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Debugf("Starting vpnkit: %v", cmd.Args)
if err := cmd.Start(); err != nil {
return nil, err
}
@ -337,3 +368,78 @@ func launchVPNKit(etherSock string, vsockSock string, portSock string) (*os.Proc
return cmd.Process, nil
}
// vpnkitPublishPorts instructs VPNKit to expose ports from the VM on localhost
// Pre-register the VM with VPNKit using the UUID. This gives the IP
// address (if not specified) allowing us to publish ports. It returns
// a function which should be called to clean up once the VM stops.
func vpnkitPublishPorts(h *hyperkit.HyperKit, publishFlags multipleFlag, portSocket string) (func(), error) {
ctx := context.Background()
vpnkitUUID, err := uuid.Parse(h.VPNKitUUID)
if err != nil {
return nil, fmt.Errorf("Failed to parse VPNKit UUID %s: %v", h.VPNKitUUID, err)
}
localhost := net.ParseIP("127.0.0.1")
if localhost == nil {
return nil, fmt.Errorf("Failed to parse 127.0.0.1")
}
log.Debugf("Creating new VPNKit VMNet on %s", h.VPNKitSock)
vmnet, err := vpnkit.NewVmnet(ctx, h.VPNKitSock)
if err != nil {
return nil, fmt.Errorf("NewVmnet failed: %v", err)
}
defer vmnet.Close()
// Register with VPNKit
var vif *vpnkit.Vif
if h.VPNKitPreferredIPv4 == "" {
log.Debugf("Creating VPNKit VIF for %v", vpnkitUUID)
vif, err = vmnet.ConnectVif(vpnkitUUID)
if err != nil {
return nil, fmt.Errorf("Connection to Vif failed: %v", err)
}
} else {
ip := net.ParseIP(h.VPNKitPreferredIPv4)
if ip == nil {
return nil, fmt.Errorf("Failed to parse IP: %s", h.VPNKitPreferredIPv4)
}
log.Debugf("Creating VPNKit VIF for %v ip=%v", vpnkitUUID, ip)
vif, err = vmnet.ConnectVifIP(vpnkitUUID, ip)
if err != nil {
return nil, fmt.Errorf("Connection to Vif with IP failed: %v", err)
}
}
log.Debugf("VPNKit UUID:%s IP: %v", vpnkitUUID, vif.IP)
log.Debugf("Connecting to VPNKit on %s", portSocket)
c, err := vpnkit.NewConnection(context.Background(), portSocket)
if err != nil {
return nil, fmt.Errorf("Connection to VPNKit failed: %v", err)
}
// Publish ports
var ports []*vpnkit.Port
for _, publish := range publishFlags {
p, err := NewPublishedPort(publish)
if err != nil {
return nil, fmt.Errorf("Failed to parse port publish %s: %v", publish, err)
}
log.Debugf("Publishing %s", publish)
vp := vpnkit.NewPort(c, p.Protocol, localhost, p.Host, vif.IP, p.Guest)
if err = vp.Expose(context.Background()); err != nil {
return nil, fmt.Errorf("Failed to expose port %s: %v", publish, err)
}
ports = append(ports, vp)
}
// Return cleanup function
return func() {
for _, vp := range ports {
vp.Unexpose(context.Background())
}
}, nil
}

View File

@ -13,7 +13,7 @@ import (
"strconv"
"strings"
"github.com/satori/go.uuid"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
)
@ -164,7 +164,7 @@ func runQemu(args []string) {
qemuContainerized := flags.Bool("containerized", false, "Run qemu in a container")
// Generate UUID, so that /sys/class/dmi/id/product_uuid is populated
vmUUID := uuid.NewV4()
vmUUID := uuid.New()
// Networking
networking := flags.String("networking", qemuNetworkingDefault, "Networking mode. Valid options are 'default', 'user', 'bridge[,name]', tap[,name] and 'none'. 'user' uses QEMUs userspace networking. 'bridge' connects to a preexisting bridge. 'tap' uses a prexisting tap device. 'none' disables networking.`")
@ -618,85 +618,24 @@ func discoverBackend(config QemuConfig) QemuConfig {
return config
}
type multipleFlag []string
type publishedPorts struct {
guest int
host int
protocol string
}
func (f *multipleFlag) String() string {
return "A multiple flag is a type of flag that can be repeated any number of times"
}
func (f *multipleFlag) Set(value string) error {
*f = append(*f, value)
return nil
}
func splitPublish(publish string) (publishedPorts, error) {
p := publishedPorts{}
slice := strings.Split(publish, ":")
if len(slice) < 2 {
return p, fmt.Errorf("Unable to parse the ports to be published, should be in format <host>:<guest> or <host>:<guest>/<tcp|udp>")
}
hostPort, err := strconv.Atoi(slice[0])
if err != nil {
return p, fmt.Errorf("The provided hostPort can't be converted to int")
}
right := strings.Split(slice[1], "/")
protocol := "tcp"
if len(right) == 2 {
protocol = strings.TrimSpace(strings.ToLower(right[1]))
}
if protocol != "tcp" && protocol != "udp" {
return p, fmt.Errorf("Provided protocol is not valid, valid options are: udp and tcp")
}
guestPort, err := strconv.Atoi(right[0])
if err != nil {
return p, fmt.Errorf("The provided guestPort can't be converted to int")
}
if hostPort < 1 || hostPort > 65535 {
return p, fmt.Errorf("Invalid hostPort: %d", hostPort)
}
if guestPort < 1 || guestPort > 65535 {
return p, fmt.Errorf("Invalid guestPort: %d", guestPort)
}
p.guest = guestPort
p.host = hostPort
p.protocol = protocol
return p, nil
}
func buildQemuForwardings(publishFlags multipleFlag, containerized bool) (string, error) {
if len(publishFlags) == 0 {
return "", nil
}
var forwardings string
for _, publish := range publishFlags {
p, err := splitPublish(publish)
p, err := NewPublishedPort(publish)
if err != nil {
return "", err
}
hostPort := p.host
guestPort := p.guest
hostPort := p.Host
guestPort := p.Guest
if containerized {
hostPort = guestPort
}
forwardings = fmt.Sprintf("%s,hostfwd=%s::%d-:%d", forwardings, p.protocol, hostPort, guestPort)
forwardings = fmt.Sprintf("%s,hostfwd=%s::%d-:%d", forwardings, p.Protocol, hostPort, guestPort)
}
return forwardings, nil
@ -705,11 +644,11 @@ func buildQemuForwardings(publishFlags multipleFlag, containerized bool) (string
func buildDockerForwardings(publishedPorts []string) ([]string, error) {
pmap := []string{}
for _, port := range publishedPorts {
s, err := splitPublish(port)
s, err := NewPublishedPort(port)
if err != nil {
return nil, err
}
pmap = append(pmap, "-p", fmt.Sprintf("%d:%d/%s", s.host, s.guest, s.protocol))
pmap = append(pmap, "-p", fmt.Sprintf("%d:%d/%s", s.Host, s.Guest, s.Protocol))
}
return pmap, nil
}

View File

@ -8,6 +8,18 @@ import (
"strings"
)
// Handle flags with multiple occurrences
type multipleFlag []string
func (f *multipleFlag) String() string {
return "A multiple flag is a type of flag that can be repeated any number of times"
}
func (f *multipleFlag) Set(value string) error {
*f = append(*f, value)
return nil
}
func getStringValue(envKey string, flagVal string, defaultVal string) string {
var res string
@ -191,3 +203,52 @@ func (l *Disks) Set(value string) error {
*l = append(*l, d)
return nil
}
// PublishedPort is used by some backends to expose a VMs port on the host
type PublishedPort struct {
Guest uint16
Host uint16
Protocol string
}
// NewPublishedPort parses a string of the form <host>:<guest>[/<tcp|udp>] and returns a PublishedPort structure
func NewPublishedPort(publish string) (PublishedPort, error) {
p := PublishedPort{}
slice := strings.Split(publish, ":")
if len(slice) < 2 {
return p, fmt.Errorf("Unable to parse the ports to be published, should be in format <host>:<guest> or <host>:<guest>/<tcp|udp>")
}
hostPort, err := strconv.ParseUint(slice[0], 10, 16)
if err != nil {
return p, fmt.Errorf("The provided hostPort can't be converted to uint16")
}
right := strings.Split(slice[1], "/")
protocol := "tcp"
if len(right) == 2 {
protocol = strings.TrimSpace(strings.ToLower(right[1]))
}
if protocol != "tcp" && protocol != "udp" {
return p, fmt.Errorf("Provided protocol is not valid, valid options are: udp and tcp")
}
guestPort, err := strconv.ParseUint(right[0], 10, 16)
if err != nil {
return p, fmt.Errorf("The provided guestPort can't be converted to uint16")
}
if hostPort < 1 || hostPort > 65535 {
return p, fmt.Errorf("Invalid hostPort: %d", hostPort)
}
if guestPort < 1 || guestPort > 65535 {
return p, fmt.Errorf("Invalid guestPort: %d", guestPort)
}
p.Guest = uint16(guestPort)
p.Host = uint16(hostPort)
p.Protocol = protocol
return p, nil
}

View File

@ -6,13 +6,17 @@ github.com/aws/aws-sdk-go fa107560b5f3528a859a1a1511086646731bb1a8
github.com/davecgh/go-spew v1.1.0
github.com/dgrijalva/jwt-go 6c8dedd55f8a2e41f605de6d5d66e51ed1f299fc
github.com/docker/docker 316b4ba9c2891b9ab4437f1c6a52df2d3d0ca47b
github.com/docker/go-p9p 87ae8514a3a2d9684994a6c319f96ba9e18a062e
github.com/go-ini/ini afbc45e87f3ba324c532d12c71918ef52e0fb194
github.com/golang/protobuf c9c7427a2a70d2eb3bafa0ab2dc163e45f143317
github.com/google/uuid 7e072fc3a7be179aee6d3359e46015aa8c995314
github.com/googleapis/gax-go 8c5154c0fe5bf18cf649634d4c6df50897a32751
github.com/gophercloud/gophercloud 2804b72cf099b41d2e25c8afcca786f9f962ddee
github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
github.com/mitchellh/go-ps 4fdf99ab29366514c69ccccddab5dc58b8d84062
github.com/moby/hyperkit 3e31617ae866c93925e2b3bc5d8006b60985e920
github.com/moby/datakit 97b3d230535397a813323902c23751e176481a86
github.com/moby/hyperkit a12cd7250bcd8d689078e3e42ae4a7cf6a0cbaf3
github.com/moby/vpnkit 0e4293bb1058598c4b0a406ed171f52573ef414c
github.com/packethost/packngo 131798f2804a1b3e895ca98047d56f0d7e094e2a
github.com/pmezard/go-difflib v1.0.0
github.com/radu-matei/azure-sdk-for-go 3b12823551999669c9a325a32472508e0af7978e

View File

@ -1,60 +0,0 @@
## About
This directory contains a collection of scripts used to build and manage this
repository. If there are any issues regarding the intention of a particular
script (or even part of a certain script), please reach out to us.
It may help us either refine our current scripts, or add on new ones
that are appropriate for a given use case.
## DinD (dind.sh)
DinD is a wrapper script which allows Docker to be run inside a Docker
container. DinD requires the container to
be run with privileged mode enabled.
## Generate Authors (generate-authors.sh)
Generates AUTHORS; a file with all the names and corresponding emails of
individual contributors. AUTHORS can be found in the home directory of
this repository.
## Make
There are two make files, each with different extensions. Neither are supposed
to be called directly; only invoke `make`. Both scripts run inside a Docker
container.
### make.ps1
- The Windows native build script that uses PowerShell semantics; it is limited
unlike `hack\make.sh` since it does not provide support for the full set of
operations provided by the Linux counterpart, `make.sh`. However, `make.ps1`
does provide support for local Windows development and Windows to Windows CI.
More information is found within `make.ps1` by the author, @jhowardmsft
### make.sh
- Referenced via `make test` when running tests on a local machine,
or directly referenced when running tests inside a Docker development container.
- When running on a local machine, `make test` to run all tests found in
`test`, `test-unit`, `test-integration-cli`, and `test-docker-py` on
your local machine. The default timeout is set in `make.sh` to 60 minutes
(`${TIMEOUT:=60m}`), since it currently takes up to an hour to run
all of the tests.
- When running inside a Docker development container, `hack/make.sh` does
not have a single target that runs all the tests. You need to provide a
single command line with multiple targets that performs the same thing.
An example referenced from [Run targets inside a development container](https://docs.docker.com/opensource/project/test-and-docs/#run-targets-inside-a-development-container): `root@5f8630b873fe:/go/src/github.com/moby/moby# hack/make.sh dynbinary binary cross test-unit test-integration-cli test-docker-py`
- For more information related to testing outside the scope of this README,
refer to
[Run tests and test documentation](https://docs.docker.com/opensource/project/test-and-docs/)
## Release (release.sh)
Releases any bundles built by `make` on a public AWS S3 bucket.
For information regarding configuration, please view `release.sh`.
## Vendor (vendor.sh)
A shell script that is a wrapper around Vndr. For information on how to use
this, please refer to [vndr's README](https://github.com/LK4D4/vndr/blob/master/README.md)

View File

@ -1,69 +0,0 @@
# Integration Testing on Swarm
IT on Swarm allows you to execute integration test in parallel across a Docker Swarm cluster
## Architecture
### Master service
- Works as a funker caller
- Calls a worker funker (`-worker-service`) with a chunk of `-check.f` filter strings (passed as a file via `-input` flag, typically `/mnt/input`)
### Worker service
- Works as a funker callee
- Executes an equivalent of `TESTFLAGS=-check.f TestFoo|TestBar|TestBaz ... make test-integration-cli` using the bind-mounted API socket (`docker.sock`)
### Client
- Controls master and workers via `docker stack`
- No need to have a local daemon
Typically, the master and workers are supposed to be running on a cloud environment,
while the client is supposed to be running on a laptop, e.g. Docker for Mac/Windows.
## Requirement
- Docker daemon 1.13 or later
- Private registry for distributed execution with multiple nodes
## Usage
### Step 1: Prepare images
$ make build-integration-cli-on-swarm
Following environment variables are known to work in this step:
- `BUILDFLAGS`
- `DOCKER_INCREMENTAL_BINARY`
Note: during the transition into Moby Project, you might need to create a symbolic link `$GOPATH/src/github.com/docker/docker` to `$GOPATH/src/github.com/moby/moby`.
### Step 2: Execute tests
$ ./hack/integration-cli-on-swarm/integration-cli-on-swarm -replicas 40 -push-worker-image YOUR_REGISTRY.EXAMPLE.COM/integration-cli-worker:latest
Following environment variables are known to work in this step:
- `DOCKER_GRAPHDRIVER`
- `DOCKER_EXPERIMENTAL`
#### Flags
Basic flags:
- `-replicas N`: the number of worker service replicas. i.e. degree of parallelism.
- `-chunks N`: the number of chunks. By default, `chunks` == `replicas`.
- `-push-worker-image REGISTRY/IMAGE:TAG`: push the worker image to the registry. Note that if you have only single node and hence you do not need a private registry, you do not need to specify `-push-worker-image`.
Experimental flags for mitigating makespan nonuniformity:
- `-shuffle`: Shuffle the test filter strings
Flags for debugging IT on Swarm itself:
- `-rand-seed N`: the random seed. This flag is useful for deterministic replaying. By default(0), the timestamp is used.
- `-filters-file FILE`: the file contains `-check.f` strings. By default, the file is automatically generated.
- `-dry-run`: skip the actual workload
- `keep-executor`: do not auto-remove executor containers, which is used for running privileged programs on Swarm

View File

@ -1,2 +0,0 @@
# dependencies specific to worker (i.e. github.com/docker/docker/...) are not vendored here
github.com/bfirsh/funker-go eaa0a2e06f30e72c9a0b7f858951e581e26ef773

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,11 @@
# p9p [![GoDoc](https://godoc.org/github.com/docker/go-p9p?status.svg)](https://godoc.org/github.com/docker/go-p9p) [![Apache licensed](https://img.shields.io/badge/license-Apache-blue.svg)](https://raw.githubusercontent.com/docker/go-p9p/master/LICENSE) [![CircleCI](https://circleci.com/gh/docker/go-p9p.svg?style=shield)](https://circleci.com/gh/docker/go-p9p) [![TravisCI](https://travis-ci.org/docker/go-p9p.svg?branch=master)](https://travis-ci.org/docker/go-p9p) [![Go Report Card](https://goreportcard.com/badge/github.com/docker/go-p9p)](https://goreportcard.com/report/github.com/docker/go-p9p) [![Badge Badge](http://doyouevenbadge.com/github.com/docker/go-p9p)](http://doyouevenbadge.com/report/github.com/docker/go-p9p)
A modern, performant 9P library for Go.
For information on usage, please see the [GoDoc](https://godoc.org/github.com/docker/go-p9p).
## Copyright and license
Copyright © 2015 Docker, Inc. go-p9p is licensed under the Apache License,
Version 2.0. See [LICENSE](LICENSE) for the full license text.

View File

@ -0,0 +1,343 @@
package p9p
import (
"bufio"
"context"
"encoding/binary"
"io"
"io/ioutil"
"log"
"net"
"time"
)
const (
// channelMessageHeaderSize is the overhead for sending the size of a
// message on the wire.
channelMessageHeaderSize = 4
)
// Channel defines the operations necessary to implement a 9p message channel
// interface. Typically, message channels do no protocol processing except to
// send and receive message frames.
type Channel interface {
// ReadFcall reads one fcall frame into the provided fcall structure. The
// Fcall may be cleared whether there is an error or not. If the operation
// is successful, the contents of the fcall will be populated in the
// argument. ReadFcall cannot be called concurrently with other calls to
// ReadFcall. This both to preserve message ordering and to allow lockless
// buffer reusage.
ReadFcall(ctx context.Context, fcall *Fcall) error
// WriteFcall writes the provided fcall to the channel. WriteFcall cannot
// be called concurrently with other calls to WriteFcall.
WriteFcall(ctx context.Context, fcall *Fcall) error
// MSize returns the current msize for the channel.
MSize() int
// SetMSize sets the maximum message size for the channel. This must never
// be called currently with ReadFcall or WriteFcall.
SetMSize(msize int)
}
// NewChannel returns a new channel to read and write Fcalls with the provided
// connection and message size.
func NewChannel(conn net.Conn, msize int) Channel {
return newChannel(conn, codec9p{}, msize)
}
const (
defaultRWTimeout = 30 * time.Second // default read/write timeout if not set in context
)
// channel provides bidirectional protocol framing for 9p over net.Conn.
// Operations are not thread-safe but reads and writes may be carried out
// concurrently, supporting separate read and write loops.
//
// Lifecyle
//
// A connection, or message channel abstraction, has a lifecycle delineated by
// Tversion/Rversion request response cycles. For now, this is part of the
// channel itself but doesn't necessarily influence the channels state, except
// the msize. Visually, it might look something like this:
//
// [Established] -> [Version] -> [Session] -> [Version]---+
// ^ |
// |_________________________________|
//
// The connection is established, then we negotiate a version, run a session,
// then negotiate a version and so on. For most purposes, we are likely going
// to terminate the connection after the session but we may want to support
// connection pooling. Pooling may result in possible security leaks if the
// connections are shared among contexts, since the version is negotiated at
// the start of the session. To avoid this, we can actually use a "tombstone"
// version message which clears the server's session state without starting a
// new session. The next version message would then prepare the session
// without leaking any Fid's.
type channel struct {
conn net.Conn
codec Codec
brd *bufio.Reader
bwr *bufio.Writer
closed chan struct{}
msize int
rdbuf []byte
}
func newChannel(conn net.Conn, codec Codec, msize int) *channel {
return &channel{
conn: conn,
codec: codec,
brd: bufio.NewReaderSize(conn, msize), // msize may not be optimal buffer size
bwr: bufio.NewWriterSize(conn, msize),
closed: make(chan struct{}),
msize: msize,
rdbuf: make([]byte, msize),
}
}
func (ch *channel) MSize() int {
return ch.msize
}
// setmsize resizes the buffers for use with a separate msize. This call must
// be protected by a mutex or made before passing to other goroutines.
func (ch *channel) SetMSize(msize int) {
// NOTE(stevvooe): We cannot safely resize the buffered reader and writer.
// Proceed assuming that original size is sufficient.
ch.msize = msize
if msize < len(ch.rdbuf) {
// just change the cap
ch.rdbuf = ch.rdbuf[:msize]
return
}
ch.rdbuf = make([]byte, msize)
}
// ReadFcall reads the next message from the channel into fcall.
//
// If the incoming message overflows the msize, Overflow(err) will return
// nonzero with the number of bytes overflowed.
func (ch *channel) ReadFcall(ctx context.Context, fcall *Fcall) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-ch.closed:
return ErrClosed
default:
}
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(defaultRWTimeout)
}
if err := ch.conn.SetReadDeadline(deadline); err != nil {
log.Printf("transport: error setting read deadline on %v: %v", ch.conn.RemoteAddr(), err)
}
n, err := readmsg(ch.brd, ch.rdbuf)
if err != nil {
// TODO(stevvooe): There may be more we can do here to detect partial
// reads. For now, we just propagate the error untouched.
return err
}
if n > len(ch.rdbuf) {
return overflowErr{size: n - len(ch.rdbuf)}
}
// clear out the fcall
*fcall = Fcall{}
if err := ch.codec.Unmarshal(ch.rdbuf[:n], fcall); err != nil {
return err
}
if err := ch.maybeTruncate(fcall); err != nil {
return err
}
return nil
}
// WriteFcall writes the message to the connection.
//
// If a message destined for the wire will overflow MSize, an Overflow error
// may be returned. For Twrite calls, the buffer will simply be truncated to
// the optimal msize, with the caller detecting this condition with
// Rwrite.Count.
func (ch *channel) WriteFcall(ctx context.Context, fcall *Fcall) error {
select {
case <-ctx.Done():
return ctx.Err()
case <-ch.closed:
return ErrClosed
default:
}
deadline, ok := ctx.Deadline()
if !ok {
deadline = time.Now().Add(defaultRWTimeout)
}
if err := ch.conn.SetWriteDeadline(deadline); err != nil {
log.Printf("transport: error setting read deadline on %v: %v", ch.conn.RemoteAddr(), err)
}
if err := ch.maybeTruncate(fcall); err != nil {
return err
}
p, err := ch.codec.Marshal(fcall)
if err != nil {
return err
}
if err := sendmsg(ch.bwr, p); err != nil {
return err
}
return ch.bwr.Flush()
}
// maybeTruncate will truncate the message to fit into msize on the wire, if
// possible, or modify the message to ensure the response won't overflow.
//
// If the message cannot be truncated, an error will be returned and the
// message should not be sent.
//
// A nil return value means the message can be sent without
func (ch *channel) maybeTruncate(fcall *Fcall) error {
// for certain message types, just remove the extra bytes from the data portion.
switch msg := fcall.Message.(type) {
// TODO(stevvooe): There is one more problematic message type:
//
// Rread: while we can employ the same truncation fix as Twrite, we
// need to make it observable to upstream handlers.
case MessageTread:
// We can rewrite msg.Count so that a return message will be under
// msize. This is more defensive than anything but will ensure that
// calls don't fail on sloppy servers.
// first, craft the shape of the response message
resp := newFcall(fcall.Tag, MessageRread{})
overflow := uint32(ch.msgmsize(resp)) + msg.Count - uint32(ch.msize)
if msg.Count < overflow {
// Let the bad thing happen; msize too small to even support valid
// rewrite. This will result in a Terror from the server-side or
// just work.
return nil
}
msg.Count -= overflow
fcall.Message = msg
return nil
case MessageTwrite:
// If we are going to overflow the msize, we need to truncate the write to
// appropriate size or throw an error in all other conditions.
size := ch.msgmsize(fcall)
if size <= ch.msize {
return nil
}
// overflow the msize, including the channel message size fields.
overflow := size - ch.msize
if len(msg.Data) < overflow {
// paranoid: if msg.Data is not big enough to handle the
// overflow, we should get an overflow error. MSize would have
// to be way too small to be realistic.
return overflowErr{size: overflow}
}
// The truncation is reflected in the return message (Rwrite) by
// the server, so we don't need a return value or error condition
// to communicate it.
msg.Data = msg.Data[:len(msg.Data)-overflow]
fcall.Message = msg // since we have a local copy
return nil
default:
size := ch.msgmsize(fcall)
if size > ch.msize {
// overflow the msize, including the channel message size fields.
return overflowErr{size: size - ch.msize}
}
return nil
}
}
// msgmsize returns the on-wire msize of the Fcall, including the size header.
// Typically, this can be used to detect whether or not the message overflows
// the msize buffer.
func (ch *channel) msgmsize(fcall *Fcall) int {
return channelMessageHeaderSize + ch.codec.Size(fcall)
}
// readmsg reads a 9p message into p from rd, ensuring that all bytes are
// consumed from the size header. If the size header indicates the message is
// larger than p, the entire message will be discarded, leaving a truncated
// portion in p. Any error should be treated as a framing error unless n is
// zero. The caller must check that n is less than or equal to len(p) to
// ensure that a valid message has been read.
func readmsg(rd io.Reader, p []byte) (n int, err error) {
var msize uint32
if err := binary.Read(rd, binary.LittleEndian, &msize); err != nil {
return 0, err
}
n += binary.Size(msize)
mbody := int(msize) - 4
if mbody < len(p) {
p = p[:mbody]
}
np, err := io.ReadFull(rd, p)
if err != nil {
return np + n, err
}
n += np
if mbody > len(p) {
// message has been read up to len(p) but we must consume the entire
// message. This is an error condition but is non-fatal if we can
// consume msize bytes.
nn, err := io.CopyN(ioutil.Discard, rd, int64(mbody-len(p)))
n += int(nn)
if err != nil {
return n, err
}
}
return n, nil
}
// sendmsg writes a message of len(p) to wr with a 9p size header. All errors
// should be considered terminal.
func sendmsg(wr io.Writer, p []byte) error {
size := uint32(len(p) + 4) // message size plus 4-bytes for size.
if err := binary.Write(wr, binary.LittleEndian, size); err != nil {
return err
}
// This assume partial writes to wr aren't possible. Not sure if this
// valid. Matters during timeout retries.
if n, err := wr.Write(p); err != nil {
return err
} else if n < len(p) {
return io.ErrShortWrite
}
return nil
}

View File

@ -0,0 +1,253 @@
package p9p
import (
"io"
"net"
"context"
)
type client struct {
version string
msize int
ctx context.Context
transport roundTripper
}
// NewSession returns a session using the connection. The Context ctx provides
// a context for out of bad messages, such as flushes, that may be sent by the
// session. The session can effectively shutdown with this context.
func NewSession(ctx context.Context, conn net.Conn) (Session, error) {
ch := newChannel(conn, codec9p{}, DefaultMSize) // sets msize, effectively.
// negotiate the protocol version
version, err := clientnegotiate(ctx, ch, DefaultVersion)
if err != nil {
return nil, err
}
return &client{
version: version,
msize: ch.MSize(),
ctx: ctx,
transport: newTransport(ctx, ch),
}, nil
}
var _ Session = &client{}
func (c *client) Version() (int, string) {
return c.msize, c.version
}
func (c *client) Auth(ctx context.Context, afid Fid, uname, aname string) (Qid, error) {
m := MessageTauth{
Afid: afid,
Uname: uname,
Aname: aname,
}
resp, err := c.transport.send(ctx, m)
if err != nil {
return Qid{}, err
}
rauth, ok := resp.(MessageRauth)
if !ok {
return Qid{}, ErrUnexpectedMsg
}
return rauth.Qid, nil
}
func (c *client) Attach(ctx context.Context, fid, afid Fid, uname, aname string) (Qid, error) {
m := MessageTattach{
Fid: fid,
Afid: afid,
Uname: uname,
Aname: aname,
}
resp, err := c.transport.send(ctx, m)
if err != nil {
return Qid{}, err
}
rattach, ok := resp.(MessageRattach)
if !ok {
return Qid{}, ErrUnexpectedMsg
}
return rattach.Qid, nil
}
func (c *client) Clunk(ctx context.Context, fid Fid) error {
resp, err := c.transport.send(ctx, MessageTclunk{
Fid: fid,
})
if err != nil {
return err
}
_, ok := resp.(MessageRclunk)
if !ok {
return ErrUnexpectedMsg
}
return nil
}
func (c *client) Remove(ctx context.Context, fid Fid) error {
resp, err := c.transport.send(ctx, MessageTremove{
Fid: fid,
})
if err != nil {
return err
}
_, ok := resp.(MessageRremove)
if !ok {
return ErrUnexpectedMsg
}
return nil
}
func (c *client) Walk(ctx context.Context, fid Fid, newfid Fid, names ...string) ([]Qid, error) {
if len(names) > 16 {
return nil, ErrWalkLimit
}
resp, err := c.transport.send(ctx, MessageTwalk{
Fid: fid,
Newfid: newfid,
Wnames: names,
})
if err != nil {
return nil, err
}
rwalk, ok := resp.(MessageRwalk)
if !ok {
return nil, ErrUnexpectedMsg
}
return rwalk.Qids, nil
}
func (c *client) Read(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error) {
resp, err := c.transport.send(ctx, MessageTread{
Fid: fid,
Offset: uint64(offset),
Count: uint32(len(p)),
})
if err != nil {
return 0, err
}
rread, ok := resp.(MessageRread)
if !ok {
return 0, ErrUnexpectedMsg
}
n = copy(p, rread.Data)
switch {
case len(rread.Data) == 0:
err = io.EOF
case n < len(p):
// TODO(stevvooe): Technically, we should treat this as an io.EOF.
// However, we cannot tell if the short read was due to EOF or due to
// truncation.
}
return n, err
}
func (c *client) Write(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error) {
resp, err := c.transport.send(ctx, MessageTwrite{
Fid: fid,
Offset: uint64(offset),
Data: p,
})
if err != nil {
return 0, err
}
rwrite, ok := resp.(MessageRwrite)
if !ok {
return 0, ErrUnexpectedMsg
}
if int(rwrite.Count) < len(p) {
err = io.ErrShortWrite
}
return int(rwrite.Count), err
}
func (c *client) Open(ctx context.Context, fid Fid, mode Flag) (Qid, uint32, error) {
resp, err := c.transport.send(ctx, MessageTopen{
Fid: fid,
Mode: mode,
})
if err != nil {
return Qid{}, 0, err
}
ropen, ok := resp.(MessageRopen)
if !ok {
return Qid{}, 0, ErrUnexpectedMsg
}
return ropen.Qid, ropen.IOUnit, nil
}
func (c *client) Create(ctx context.Context, parent Fid, name string, perm uint32, mode Flag) (Qid, uint32, error) {
resp, err := c.transport.send(ctx, MessageTcreate{
Fid: parent,
Name: name,
Perm: perm,
Mode: mode,
})
if err != nil {
return Qid{}, 0, err
}
rcreate, ok := resp.(MessageRcreate)
if !ok {
return Qid{}, 0, ErrUnexpectedMsg
}
return rcreate.Qid, rcreate.IOUnit, nil
}
func (c *client) Stat(ctx context.Context, fid Fid) (Dir, error) {
resp, err := c.transport.send(ctx, MessageTstat{Fid: fid})
if err != nil {
return Dir{}, err
}
rstat, ok := resp.(MessageRstat)
if !ok {
return Dir{}, ErrUnexpectedMsg
}
return rstat.Stat, nil
}
func (c *client) WStat(ctx context.Context, fid Fid, dir Dir) error {
resp, err := c.transport.send(ctx, MessageTwstat{
Fid: fid,
Stat: dir,
})
if err != nil {
return err
}
_, ok := resp.(MessageRwstat)
if !ok {
return ErrUnexpectedMsg
}
return nil
}

View File

@ -0,0 +1,26 @@
package p9p
import (
"context"
)
type contextKey string
const (
versionKey contextKey = "9p.version"
)
func withVersion(ctx context.Context, version string) context.Context {
return context.WithValue(ctx, versionKey, version)
}
// GetVersion returns the protocol version from the context. If the version is
// not known, an empty string is returned. This is typically set on the
// context passed into function calls in a server implementation.
func GetVersion(ctx context.Context) string {
v, ok := ctx.Value(versionKey).(string)
if !ok {
return ""
}
return v
}

View File

@ -0,0 +1,133 @@
package p9p
import "context"
// Handler defines an interface for 9p message handlers. A handler
// implementation could be used to intercept calls of all types before sending
// them to the next handler.
type Handler interface {
Handle(ctx context.Context, msg Message) (Message, error)
// TODO(stevvooe): Right now, this interface is functianally identical to
// roundtripper. If we find that this is sufficient on the server-side, we
// may unify the types. For now, we leave them separated to differentiate
// between them.
}
// HandlerFunc is a convenience type for defining inline handlers.
type HandlerFunc func(ctx context.Context, msg Message) (Message, error)
// Handle implements the requirements for the Handler interface.
func (fn HandlerFunc) Handle(ctx context.Context, msg Message) (Message, error) {
return fn(ctx, msg)
}
// Dispatch returns a handler that dispatches messages to the target session.
// No concurrency is managed by the returned handler. It simply turns messages
// into function calls on the session.
func Dispatch(session Session) Handler {
return HandlerFunc(func(ctx context.Context, msg Message) (Message, error) {
switch msg := msg.(type) {
case MessageTauth:
qid, err := session.Auth(ctx, msg.Afid, msg.Uname, msg.Aname)
if err != nil {
return nil, err
}
return MessageRauth{Qid: qid}, nil
case MessageTattach:
qid, err := session.Attach(ctx, msg.Fid, msg.Afid, msg.Uname, msg.Aname)
if err != nil {
return nil, err
}
return MessageRattach{
Qid: qid,
}, nil
case MessageTwalk:
// TODO(stevvooe): This is one of the places where we need to manage
// fid allocation lifecycle. We need to reserve the fid, then, if this
// call succeeds, we should alloc the fid for future uses. Also need
// to interact correctly with concurrent clunk and the flush of this
// walk message.
qids, err := session.Walk(ctx, msg.Fid, msg.Newfid, msg.Wnames...)
if err != nil {
return nil, err
}
return MessageRwalk{
Qids: qids,
}, nil
case MessageTopen:
qid, iounit, err := session.Open(ctx, msg.Fid, msg.Mode)
if err != nil {
return nil, err
}
return MessageRopen{
Qid: qid,
IOUnit: iounit,
}, nil
case MessageTcreate:
qid, iounit, err := session.Create(ctx, msg.Fid, msg.Name, msg.Perm, msg.Mode)
if err != nil {
return nil, err
}
return MessageRcreate{
Qid: qid,
IOUnit: iounit,
}, nil
case MessageTread:
p := make([]byte, int(msg.Count))
n, err := session.Read(ctx, msg.Fid, p, int64(msg.Offset))
if err != nil {
return nil, err
}
return MessageRread{
Data: p[:n],
}, nil
case MessageTwrite:
n, err := session.Write(ctx, msg.Fid, msg.Data, int64(msg.Offset))
if err != nil {
return nil, err
}
return MessageRwrite{
Count: uint32(n),
}, nil
case MessageTclunk:
// TODO(stevvooe): Manage the clunking of file descriptors based on
// walk and attach call progression.
if err := session.Clunk(ctx, msg.Fid); err != nil {
return nil, err
}
return MessageRclunk{}, nil
case MessageTremove:
if err := session.Remove(ctx, msg.Fid); err != nil {
return nil, err
}
return MessageRremove{}, nil
case MessageTstat:
dir, err := session.Stat(ctx, msg.Fid)
if err != nil {
return nil, err
}
return MessageRstat{
Stat: dir,
}, nil
case MessageTwstat:
if err := session.WStat(ctx, msg.Fid, msg.Stat); err != nil {
return nil, err
}
return MessageRwstat{}, nil
default:
return nil, ErrUnknownMsg
}
})
}

View File

@ -0,0 +1,78 @@
/*
Package p9p implements a compliant 9P2000 client and server library for use
in modern, production Go services. This package differentiates itself in that
is has departed from the plan 9 implementation primitives and better follows
idiomatic Go style.
The package revolves around the session type, which is an enumeration of raw
9p message calls. A few calls, such as flush and version, have been elided,
defering their usage to the server implementation. Sessions can be trivially
proxied through clients and servers.
Getting Started
The best place to get started is with Serve. Serve can be provided a
connection and a handler. A typical implementation will call Serve as part of
a listen/accept loop. As each network connection is created, Serve can be
called with a handler for the specific connection. The handler can be
implemented with a Session via the Dispatch function or can generate sessions
for dispatch in response to client messages. (See cmd/9ps for an example)
On the client side, NewSession provides a 9p session from a connection. After
a version negotiation, methods can be called on the session, in parallel, and
calls will be sent over the connection. Call timeouts can be controlled via
the context provided to each method call.
Framework
This package has the beginning of a nice client-server framework for working
with 9p. Some of the abstractions aren't entirely fleshed out, but most of
this can center around the Handler.
Missing from this are a number of tools for implementing 9p servers. The most
glaring are directory read and walk helpers. Other, more complex additions
might be a system to manage in memory filesystem trees that expose multi-user
sessions.
Differences
The largest difference between this package and other 9p packages is
simplification of the types needed to implement a server. To avoid confusing
bugs and odd behavior, the components are separated by each level of the
protocol. One example is that requests and responses are separated and they no
longer hold mutable state. This means that framing, transport management,
encoding, and dispatching are componentized. Little work will be required to
swap out encodings, transports or connection implementations.
Context Integration
This package has been wired from top to bottom to support context-based
resource management. Everything from startup to shutdown can have timeouts
using contexts. Not all close methods are fully in place, but we are very
close to having controlled, predictable cleanup for both servers and clients.
Timeouts can be very granular or very course, depending on the context of the
timeout. For example, it is very easy to set a short timeout for a stat call
but a long timeout for reading data.
Multiversion Support
Currently, there is not multiversion support. The hooks and functionality are
in place to add multi-version support. Generally, the correct space to do this
is in the codec. Types, such as Dir, simply need to be extended to support the
possibility of extra fields.
The real question to ask here is what is the role of the version number in the
9p protocol. It really comes down to the level of support required. Do we just
need it at the protocol level, or do handlers and sessions need to be have
differently based on negotiated versions?
Caveats
This package has a number of TODOs to make it easier to use. Most of the
existing code provides a solid base to work from. Don't be discouraged by the
sawdust.
In addition, the testing is embarassingly lacking. With time, we can get full
testing going and ensure we have confidence in the implementation.
*/
package p9p

View File

@ -0,0 +1,564 @@
package p9p
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"log"
"reflect"
"strings"
"time"
)
// Codec defines the interface for encoding and decoding of 9p types.
// Unsupported types will throw an error.
type Codec interface {
// Unmarshal from data into the value pointed to by v.
Unmarshal(data []byte, v interface{}) error
// Marshal the value v into a byte slice.
Marshal(v interface{}) ([]byte, error)
// Size returns the encoded size for the target of v.
Size(v interface{}) int
}
// NewCodec returns a new, standard 9P2000 codec, ready for use.
func NewCodec() Codec {
return codec9p{}
}
type codec9p struct{}
func (c codec9p) Unmarshal(data []byte, v interface{}) error {
dec := &decoder{bytes.NewReader(data)}
return dec.decode(v)
}
func (c codec9p) Marshal(v interface{}) ([]byte, error) {
var b bytes.Buffer
enc := &encoder{&b}
if err := enc.encode(v); err != nil {
return nil, err
}
return b.Bytes(), nil
}
func (c codec9p) Size(v interface{}) int {
return int(size9p(v))
}
// DecodeDir decodes a directory entry from rd using the provided codec.
func DecodeDir(codec Codec, rd io.Reader, d *Dir) error {
var ll uint16
// pull the size off the wire
if err := binary.Read(rd, binary.LittleEndian, &ll); err != nil {
return err
}
p := make([]byte, ll+2)
binary.LittleEndian.PutUint16(p, ll) // must have size at start
// read out the rest of the record
if _, err := io.ReadFull(rd, p[2:]); err != nil {
return err
}
return codec.Unmarshal(p, d)
}
// EncodeDir writes the directory to wr.
func EncodeDir(codec Codec, wr io.Writer, d *Dir) error {
p, err := codec.Marshal(d)
if err != nil {
return err
}
_, err = wr.Write(p)
return err
}
type encoder struct {
wr io.Writer
}
func (e *encoder) encode(vs ...interface{}) error {
for _, v := range vs {
switch v := v.(type) {
case uint8, uint16, uint32, uint64, FcallType, Tag, QType, Fid, Flag,
*uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag:
if err := binary.Write(e.wr, binary.LittleEndian, v); err != nil {
return err
}
case []byte:
if err := e.encode(uint32(len(v))); err != nil {
return err
}
if err := binary.Write(e.wr, binary.LittleEndian, v); err != nil {
return err
}
case *[]byte:
if err := e.encode(*v); err != nil {
return err
}
case string:
if err := binary.Write(e.wr, binary.LittleEndian, uint16(len(v))); err != nil {
return err
}
_, err := io.WriteString(e.wr, v)
if err != nil {
return err
}
case *string:
if err := e.encode(*v); err != nil {
return err
}
case []string:
if err := e.encode(uint16(len(v))); err != nil {
return err
}
for _, m := range v {
if err := e.encode(m); err != nil {
return err
}
}
case *[]string:
if err := e.encode(*v); err != nil {
return err
}
case time.Time:
if err := e.encode(uint32(v.Unix())); err != nil {
return err
}
case *time.Time:
if err := e.encode(*v); err != nil {
return err
}
case Qid:
if err := e.encode(v.Type, v.Version, v.Path); err != nil {
return err
}
case *Qid:
if err := e.encode(*v); err != nil {
return err
}
case []Qid:
if err := e.encode(uint16(len(v))); err != nil {
return err
}
elements := make([]interface{}, len(v))
for i := range v {
elements[i] = &v[i]
}
if err := e.encode(elements...); err != nil {
return err
}
case *[]Qid:
if err := e.encode(*v); err != nil {
return err
}
case Dir:
elements, err := fields9p(v)
if err != nil {
return err
}
if err := e.encode(uint16(size9p(elements...))); err != nil {
return err
}
if err := e.encode(elements...); err != nil {
return err
}
case *Dir:
if err := e.encode(*v); err != nil {
return err
}
case []Dir:
elements := make([]interface{}, len(v))
for i := range v {
elements[i] = &v[i]
}
if err := e.encode(elements...); err != nil {
return err
}
case *[]Dir:
if err := e.encode(*v); err != nil {
return err
}
case Fcall:
if err := e.encode(v.Type, v.Tag, v.Message); err != nil {
return err
}
case *Fcall:
if err := e.encode(*v); err != nil {
return err
}
case Message:
elements, err := fields9p(v)
if err != nil {
return err
}
switch v.(type) {
case MessageRstat, *MessageRstat:
// NOTE(stevvooe): Prepend size preceeding Dir. See bugs in
// http://man.cat-v.org/plan_9/5/stat to make sense of this.
// The field has been included here but we need to make sure
// to double emit it for Rstat.
if err := e.encode(uint16(size9p(elements...))); err != nil {
return err
}
}
if err := e.encode(elements...); err != nil {
return err
}
}
}
return nil
}
type decoder struct {
rd io.Reader
}
// read9p extracts values from rd and unmarshals them to the targets of vs.
func (d *decoder) decode(vs ...interface{}) error {
for _, v := range vs {
switch v := v.(type) {
case *uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag:
if err := binary.Read(d.rd, binary.LittleEndian, v); err != nil {
return err
}
case *[]byte:
var ll uint32
if err := d.decode(&ll); err != nil {
return err
}
if ll > 0 {
*v = make([]byte, int(ll))
}
if err := binary.Read(d.rd, binary.LittleEndian, v); err != nil {
return err
}
case *string:
var ll uint16
// implement string[s] encoding
if err := d.decode(&ll); err != nil {
return err
}
b := make([]byte, ll)
n, err := io.ReadFull(d.rd, b)
if err != nil {
return err
}
if n != int(ll) {
return fmt.Errorf("unexpected string length")
}
*v = string(b)
case *[]string:
var ll uint16
if err := d.decode(&ll); err != nil {
return err
}
elements := make([]interface{}, int(ll))
*v = make([]string, int(ll))
for i := range elements {
elements[i] = &(*v)[i]
}
if err := d.decode(elements...); err != nil {
return err
}
case *time.Time:
var epoch uint32
if err := d.decode(&epoch); err != nil {
return err
}
*v = time.Unix(int64(epoch), 0).UTC()
case *Qid:
if err := d.decode(&v.Type, &v.Version, &v.Path); err != nil {
return err
}
case *[]Qid:
var ll uint16
if err := d.decode(&ll); err != nil {
return err
}
elements := make([]interface{}, int(ll))
*v = make([]Qid, int(ll))
for i := range elements {
elements[i] = &(*v)[i]
}
if err := d.decode(elements...); err != nil {
return err
}
case *Dir:
var ll uint16
if err := d.decode(&ll); err != nil {
return err
}
b := make([]byte, ll)
// must consume entire dir entry.
n, err := io.ReadFull(d.rd, b)
if err != nil {
log.Println("dir readfull failed:", err, ll, n)
return err
}
elements, err := fields9p(v)
if err != nil {
return err
}
dec := &decoder{bytes.NewReader(b)}
if err := dec.decode(elements...); err != nil {
return err
}
case *[]Dir:
*v = make([]Dir, 0)
for {
element := Dir{}
if err := d.decode(&element); err != nil {
if err == io.EOF {
return nil
}
return err
}
*v = append(*v, element)
}
case *Fcall:
if err := d.decode(&v.Type, &v.Tag); err != nil {
return err
}
message, err := newMessage(v.Type)
if err != nil {
return err
}
// NOTE(stevvooe): We do a little pointer dance to allocate the
// new type, write to it, then assign it back to the interface as
// a concrete type, avoiding a pointer (the interface) to a
// pointer.
rv := reflect.New(reflect.TypeOf(message))
if err := d.decode(rv.Interface()); err != nil {
return err
}
v.Message = rv.Elem().Interface().(Message)
case Message:
elements, err := fields9p(v)
if err != nil {
return err
}
switch v.(type) {
case *MessageRstat, MessageRstat:
// NOTE(stevvooe): Consume extra size preceeding Dir. See bugs
// in http://man.cat-v.org/plan_9/5/stat to make sense of
// this. The field has been included here but we need to make
// sure to double emit it for Rstat. decode extra size header
// for stat structure.
var ll uint16
if err := d.decode(&ll); err != nil {
return err
}
}
if err := d.decode(elements...); err != nil {
return err
}
}
}
return nil
}
// size9p calculates the projected size of the values in vs when encoded into
// 9p binary protocol. If an element or elements are not valid for 9p encoded,
// the value 0 will be used for the size. The error will be detected when
// encoding.
func size9p(vs ...interface{}) uint32 {
var s uint32
for _, v := range vs {
if v == nil {
continue
}
switch v := v.(type) {
case uint8, uint16, uint32, uint64, FcallType, Tag, QType, Fid, Flag,
*uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag:
s += uint32(binary.Size(v))
case []byte:
s += uint32(binary.Size(uint32(0)) + len(v))
case *[]byte:
s += size9p(uint32(0), *v)
case string:
s += uint32(binary.Size(uint16(0)) + len(v))
case *string:
s += size9p(*v)
case []string:
s += size9p(uint16(0))
for _, sv := range v {
s += size9p(sv)
}
case *[]string:
s += size9p(*v)
case time.Time, *time.Time:
// BUG(stevvooe): Y2038 is coming.
s += size9p(uint32(0))
case Qid:
s += size9p(v.Type, v.Version, v.Path)
case *Qid:
s += size9p(*v)
case []Qid:
s += size9p(uint16(0))
elements := make([]interface{}, len(v))
for i := range elements {
elements[i] = &v[i]
}
s += size9p(elements...)
case *[]Qid:
s += size9p(*v)
case Dir:
// walk the fields of the message to get the total size. we just
// use the field order from the message struct. We may add tag
// ignoring if needed.
elements, err := fields9p(v)
if err != nil {
// BUG(stevvooe): The options here are to return 0, panic or
// make this return an error. Ideally, we make it safe to
// return 0 and have the rest of the package do the right
// thing. For now, we do this, but may want to panic until
// things are stable.
panic(err)
}
s += size9p(elements...) + size9p(uint16(0))
case *Dir:
s += size9p(*v)
case []Dir:
elements := make([]interface{}, len(v))
for i := range elements {
elements[i] = &v[i]
}
s += size9p(elements...)
case *[]Dir:
s += size9p(*v)
case Fcall:
s += size9p(v.Type, v.Tag, v.Message)
case *Fcall:
s += size9p(*v)
case Message:
// special case twstat and rstat for size fields. See bugs in
// http://man.cat-v.org/plan_9/5/stat to make sense of this.
switch v.(type) {
case *MessageRstat, MessageRstat:
s += size9p(uint16(0)) // for extra size field before dir
}
// walk the fields of the message to get the total size. we just
// use the field order from the message struct. We may add tag
// ignoring if needed.
elements, err := fields9p(v)
if err != nil {
// BUG(stevvooe): The options here are to return 0, panic or
// make this return an error. Ideally, we make it safe to
// return 0 and have the rest of the package do the right
// thing. For now, we do this, but may want to panic until
// things are stable.
panic(err)
}
s += size9p(elements...)
}
}
return s
}
// fields9p lists the settable fields from a struct type for reading and
// writing. We are using a lot of reflection here for fairly static
// serialization but we can replace this in the future with generated code if
// performance is an issue.
func fields9p(v interface{}) ([]interface{}, error) {
rv := reflect.Indirect(reflect.ValueOf(v))
if rv.Kind() != reflect.Struct {
return nil, fmt.Errorf("cannot extract fields from non-struct: %v", rv)
}
var elements []interface{}
for i := 0; i < rv.NumField(); i++ {
f := rv.Field(i)
if !f.CanInterface() {
// unexported field, skip it.
continue
}
if f.CanAddr() {
f = f.Addr()
}
elements = append(elements, f.Interface())
}
return elements, nil
}
func string9p(v interface{}) string {
if v == nil {
return "nil"
}
rv := reflect.Indirect(reflect.ValueOf(v))
if rv.Kind() != reflect.Struct {
panic("not a struct")
}
var s string
for i := 0; i < rv.NumField(); i++ {
f := rv.Field(i)
s += fmt.Sprintf(" %v=%v", strings.ToLower(rv.Type().Field(i).Name), f.Interface())
}
return s
}

View File

@ -0,0 +1,58 @@
package p9p
import (
"errors"
"fmt"
)
// MessageRerror provides both a Go error type and message type.
type MessageRerror struct {
Ename string
}
// 9p wire errors returned by Session interface methods
var (
ErrBadattach = new9pError("unknown specifier in attach")
ErrBadoffset = new9pError("bad offset")
ErrBadcount = new9pError("bad count")
ErrBotch = new9pError("9P protocol botch")
ErrCreatenondir = new9pError("create in non-directory")
ErrDupfid = new9pError("duplicate fid")
ErrDuptag = new9pError("duplicate tag")
ErrIsdir = new9pError("is a directory")
ErrNocreate = new9pError("create prohibited")
ErrNomem = new9pError("out of memory")
ErrNoremove = new9pError("remove prohibited")
ErrNostat = new9pError("stat prohibited")
ErrNotfound = new9pError("file not found")
ErrNowrite = new9pError("write prohibited")
ErrNowstat = new9pError("wstat prohibited")
ErrPerm = new9pError("permission denied")
ErrUnknownfid = new9pError("unknown fid")
ErrBaddir = new9pError("bad directory in wstat")
ErrWalknodir = new9pError("walk in non-directory")
// extra errors not part of the normal protocol
ErrTimeout = new9pError("fcall timeout") // returned when timing out on the fcall
ErrUnknownTag = new9pError("unknown tag")
ErrUnknownMsg = new9pError("unknown message") // returned when encountering unknown message type
ErrUnexpectedMsg = new9pError("unexpected message") // returned when an unexpected message is encountered
ErrWalkLimit = new9pError("too many wnames in walk")
ErrClosed = errors.New("closed")
)
// new9pError returns a new 9p error ready for the wire.
func new9pError(s string) error {
return MessageRerror{Ename: s}
}
// Type ensures that 9p errors can be transparently used as a 9p message in an
// Fcall.
func (MessageRerror) Type() FcallType {
return Rerror
}
func (e MessageRerror) Error() string {
return fmt.Sprintf("9p: %v", e.Ename)
}

View File

@ -0,0 +1,142 @@
package p9p
import "fmt"
// FcallType encodes the message type for the target Fcall.
type FcallType uint8
// Definitions for Fcall's used in 9P2000.
const (
Tversion FcallType = iota + 100
Rversion
Tauth
Rauth
Tattach
Rattach
Terror
Rerror
Tflush
Rflush
Twalk
Rwalk
Topen
Ropen
Tcreate
Rcreate
Tread
Rread
Twrite
Rwrite
Tclunk
Rclunk
Tremove
Rremove
Tstat
Rstat
Twstat
Rwstat
Tmax
)
func (fct FcallType) String() string {
switch fct {
case Tversion:
return "Tversion"
case Rversion:
return "Rversion"
case Tauth:
return "Tauth"
case Rauth:
return "Rauth"
case Tattach:
return "Tattach"
case Rattach:
return "Rattach"
case Terror:
// invalid.
return "Terror"
case Rerror:
return "Rerror"
case Tflush:
return "Tflush"
case Rflush:
return "Rflush"
case Twalk:
return "Twalk"
case Rwalk:
return "Rwalk"
case Topen:
return "Topen"
case Ropen:
return "Ropen"
case Tcreate:
return "Tcreate"
case Rcreate:
return "Rcreate"
case Tread:
return "Tread"
case Rread:
return "Rread"
case Twrite:
return "Twrite"
case Rwrite:
return "Rwrite"
case Tclunk:
return "Tclunk"
case Rclunk:
return "Rclunk"
case Tremove:
return "Tremove"
case Rremove:
return "Rremove"
case Tstat:
return "Tstat"
case Rstat:
return "Rstat"
case Twstat:
return "Twstat"
case Rwstat:
return "Rwstat"
default:
return "Tunknown"
}
}
// Fcall defines the fields for sending a 9p formatted message. The type will
// be introspected from the Message implementation.
type Fcall struct {
Type FcallType
Tag Tag
Message Message
}
func newFcall(tag Tag, msg Message) *Fcall {
return &Fcall{
Type: msg.Type(),
Tag: tag,
Message: msg,
}
}
func newErrorFcall(tag Tag, err error) *Fcall {
var msg Message
switch v := err.(type) {
case MessageRerror:
msg = v
case *MessageRerror:
msg = *v
default:
msg = MessageRerror{Ename: v.Error()}
}
return &Fcall{
Type: Rerror,
Tag: tag,
Message: msg,
}
}
func (fc *Fcall) String() string {
return fmt.Sprintf("%v(%v) %v", fc.Type, fc.Tag, string9p(fc.Message))
}

View File

@ -0,0 +1,216 @@
package p9p
import "fmt"
// Message represents the target of an fcall.
type Message interface {
// Type returns the type of call for the target message.
Type() FcallType
}
// newMessage returns a new instance of the message based on the Fcall type.
func newMessage(typ FcallType) (Message, error) {
switch typ {
case Tversion:
return MessageTversion{}, nil
case Rversion:
return MessageRversion{}, nil
case Tauth:
return MessageTauth{}, nil
case Rauth:
return MessageRauth{}, nil
case Tattach:
return MessageTattach{}, nil
case Rattach:
return MessageRattach{}, nil
case Rerror:
return MessageRerror{}, nil
case Tflush:
return MessageTflush{}, nil
case Rflush:
return MessageRflush{}, nil // No message body for this response.
case Twalk:
return MessageTwalk{}, nil
case Rwalk:
return MessageRwalk{}, nil
case Topen:
return MessageTopen{}, nil
case Ropen:
return MessageRopen{}, nil
case Tcreate:
return MessageTcreate{}, nil
case Rcreate:
return MessageRcreate{}, nil
case Tread:
return MessageTread{}, nil
case Rread:
return MessageRread{}, nil
case Twrite:
return MessageTwrite{}, nil
case Rwrite:
return MessageRwrite{}, nil
case Tclunk:
return MessageTclunk{}, nil
case Rclunk:
return MessageRclunk{}, nil // no response body
case Tremove:
return MessageTremove{}, nil
case Rremove:
return MessageRremove{}, nil
case Tstat:
return MessageTstat{}, nil
case Rstat:
return MessageRstat{}, nil
case Twstat:
return MessageTwstat{}, nil
case Rwstat:
return MessageRwstat{}, nil
}
return nil, fmt.Errorf("unknown message type")
}
// MessageVersion encodes the message body for Tversion and Rversion RPC
// calls. The body is identical in both directions.
type MessageTversion struct {
MSize uint32
Version string
}
type MessageRversion struct {
MSize uint32
Version string
}
type MessageTauth struct {
Afid Fid
Uname string
Aname string
}
type MessageRauth struct {
Qid Qid
}
type MessageTflush struct {
Oldtag Tag
}
type MessageRflush struct{}
type MessageTattach struct {
Fid Fid
Afid Fid
Uname string
Aname string
}
type MessageRattach struct {
Qid Qid
}
type MessageTwalk struct {
Fid Fid
Newfid Fid
Wnames []string
}
type MessageRwalk struct {
Qids []Qid
}
type MessageTopen struct {
Fid Fid
Mode Flag
}
type MessageRopen struct {
Qid Qid
IOUnit uint32
}
type MessageTcreate struct {
Fid Fid
Name string
Perm uint32
Mode Flag
}
type MessageRcreate struct {
Qid Qid
IOUnit uint32
}
type MessageTread struct {
Fid Fid
Offset uint64
Count uint32
}
type MessageRread struct {
Data []byte
}
type MessageTwrite struct {
Fid Fid
Offset uint64
Data []byte
}
type MessageRwrite struct {
Count uint32
}
type MessageTclunk struct {
Fid Fid
}
type MessageRclunk struct{}
type MessageTremove struct {
Fid Fid
}
type MessageRremove struct{}
type MessageTstat struct {
Fid Fid
}
type MessageRstat struct {
Stat Dir
}
type MessageTwstat struct {
Fid Fid
Stat Dir
}
type MessageRwstat struct{}
func (MessageTversion) Type() FcallType { return Tversion }
func (MessageRversion) Type() FcallType { return Rversion }
func (MessageTauth) Type() FcallType { return Tauth }
func (MessageRauth) Type() FcallType { return Rauth }
func (MessageTflush) Type() FcallType { return Tflush }
func (MessageRflush) Type() FcallType { return Rflush }
func (MessageTattach) Type() FcallType { return Tattach }
func (MessageRattach) Type() FcallType { return Rattach }
func (MessageTwalk) Type() FcallType { return Twalk }
func (MessageRwalk) Type() FcallType { return Rwalk }
func (MessageTopen) Type() FcallType { return Topen }
func (MessageRopen) Type() FcallType { return Ropen }
func (MessageTcreate) Type() FcallType { return Tcreate }
func (MessageRcreate) Type() FcallType { return Rcreate }
func (MessageTread) Type() FcallType { return Tread }
func (MessageRread) Type() FcallType { return Rread }
func (MessageTwrite) Type() FcallType { return Twrite }
func (MessageRwrite) Type() FcallType { return Rwrite }
func (MessageTclunk) Type() FcallType { return Tclunk }
func (MessageRclunk) Type() FcallType { return Rclunk }
func (MessageTremove) Type() FcallType { return Tremove }
func (MessageRremove) Type() FcallType { return Rremove }
func (MessageTstat) Type() FcallType { return Tstat }
func (MessageRstat) Type() FcallType { return Rstat }
func (MessageTwstat) Type() FcallType { return Twstat }
func (MessageRwstat) Type() FcallType { return Rwstat }

View File

@ -0,0 +1,49 @@
package p9p
import "fmt"
// Overflow will return a positive number, indicating there was an overflow for
// the error.
func Overflow(err error) int {
if of, ok := err.(overflow); ok {
return of.Size()
}
// traverse cause, if above fails.
if causal, ok := err.(interface {
Cause() error
}); ok {
return Overflow(causal.Cause())
}
return 0
}
// overflow is a resolvable error type that can help callers negotiate
// session msize. If this error is encountered, no message was sent.
//
// The return value of `Size()` represents the number of bytes that would have
// been truncated if the message were sent. This IS NOT the optimal buffer size
// for operations like read and write.
//
// In the case of `Twrite`, the caller can Size() from the local size to get an
// optimally size buffer or the write can simply be truncated to `len(buf) -
// err.Size()`.
//
// For the most part, no users of this package should see this error in
// practice. If this escapes the Session interface, it is a bug.
type overflow interface {
Size() int // number of bytes overflowed.
}
type overflowErr struct {
size int // number of bytes overflowed
}
func (o overflowErr) Error() string {
return fmt.Sprintf("message overflowed %d bytes", o.size)
}
func (o overflowErr) Size() int {
return o.size
}

View File

@ -0,0 +1,93 @@
package p9p
import (
"io"
"context"
)
// ReaddirAll reads all the directory entries for the resource fid.
func ReaddirAll(session Session, fid Fid) ([]Dir, error) {
panic("not implemented")
}
// Readdir helps one to implement the server-side of Session.Read on
// directories.
type Readdir struct {
nextfn func() (Dir, error)
buf *Dir // one-item buffer
codec Codec
offset int64
}
// NewReaddir returns a new Readdir to assist implementing server-side Readdir.
// The codec will be used to decode messages with Dir entries. The provided
// function next will be called until io.EOF is returned.
func NewReaddir(codec Codec, next func() (Dir, error)) *Readdir {
return &Readdir{
nextfn: next,
codec: codec,
}
}
// NewFixedReaddir returns a Readdir that will returned a fixed set of
// directory entries.
func NewFixedReaddir(codec Codec, dir []Dir) *Readdir {
dirs := make([]Dir, len(dir))
copy(dirs, dir) // make our own copy!
return NewReaddir(codec,
func() (Dir, error) {
if len(dirs) == 0 {
return Dir{}, io.EOF
}
d := dirs[0]
dirs = dirs[1:]
return d, nil
})
}
func (rd *Readdir) Read(ctx context.Context, p []byte, offset int64) (n int, err error) {
if rd.offset != offset {
return 0, ErrBadoffset
}
p = p[:0:len(p)]
for len(p) < cap(p) {
var d Dir
if rd.buf != nil {
d = *rd.buf
rd.buf = nil
} else {
d, err = rd.nextfn()
if err != nil {
goto done
}
}
var dp []byte
dp, err = rd.codec.Marshal(d)
if err != nil {
goto done
}
if len(p)+len(dp) > cap(p) {
// will over fill buffer. save item and exit.
rd.buf = &d
goto done
}
p = append(p, dp...)
}
done:
if err == io.EOF && len(p) > 0 {
// Don't let io.EOF escape if we've written to p. 9p doesn't handle
// this like Go.
err = nil
}
rd.offset += int64(len(p))
return len(p), err
}

View File

@ -0,0 +1,257 @@
package p9p
import (
"fmt"
"log"
"net"
"sync"
"time"
"context"
)
// TODO(stevvooe): Add net/http.Server-like type here to manage connections.
// Coupled with Handler mux, we can get a very http-like experience for 9p
// servers.
// ServeConn the 9p handler over the provided network connection.
func ServeConn(ctx context.Context, cn net.Conn, handler Handler) error {
// TODO(stevvooe): It would be nice if the handler could declare the
// supported version. Before we had handler, we used the session to get
// the version (msize, version := session.Version()). We must decided if
// we want to proxy version and message size decisions all the back to the
// origin server or make those decisions at each link of a proxy chain.
ch := newChannel(cn, codec9p{}, DefaultMSize)
negctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
// TODO(stevvooe): For now, we negotiate here. It probably makes sense to
// do this outside of this function and then pass in a ready made channel.
// We are not really ready to export the channel type yet.
if err := servernegotiate(negctx, ch, DefaultVersion); err != nil {
// TODO(stevvooe): Need better error handling and retry support here.
return fmt.Errorf("error negotiating version: %s", err)
}
ctx = withVersion(ctx, DefaultVersion)
c := &conn{
ctx: ctx,
ch: ch,
handler: handler,
closed: make(chan struct{}),
}
return c.serve()
}
// conn plays role of session dispatch for handler in a server.
type conn struct {
ctx context.Context
session Session
ch Channel
handler Handler
once sync.Once
closed chan struct{}
err error // terminal error for the conn
}
// activeRequest includes information about the active request.
type activeRequest struct {
ctx context.Context
request *Fcall
cancel context.CancelFunc
}
// serve messages on the connection until an error is encountered.
func (c *conn) serve() error {
tags := map[Tag]*activeRequest{} // active requests
requests := make(chan *Fcall) // sync, read-limited
responses := make(chan *Fcall) // sync, goroutine consumed
completed := make(chan *Fcall) // sync, send in goroutine per request
// read loop
go c.read(requests)
go c.write(responses)
log.Println("server.run()")
for {
select {
case req := <-requests:
if _, ok := tags[req.Tag]; ok {
select {
case responses <- newErrorFcall(req.Tag, ErrDuptag):
// Send to responses, bypass tag management.
case <-c.ctx.Done():
return c.ctx.Err()
case <-c.closed:
return c.err
}
continue
}
switch msg := req.Message.(type) {
case MessageTflush:
log.Println("server: flushing message", msg.Oldtag)
var resp *Fcall
// check if we have actually know about the requested flush
active, ok := tags[msg.Oldtag]
if ok {
active.cancel() // propagate cancellation to callees
delete(tags, msg.Oldtag)
resp = newFcall(req.Tag, MessageRflush{})
} else {
resp = newErrorFcall(req.Tag, ErrUnknownTag)
}
select {
case responses <- resp:
// bypass tag management in completed.
case <-c.ctx.Done():
return c.ctx.Err()
case <-c.closed:
return c.err
}
default:
// Allows us to session handlers to cancel processing of the fcall
// through context.
ctx, cancel := context.WithCancel(c.ctx)
// The contents of these instances are only writable in the main
// server loop. The value of tag will not change.
tags[req.Tag] = &activeRequest{
ctx: ctx,
request: req,
cancel: cancel,
}
go func(ctx context.Context, req *Fcall) {
// TODO(stevvooe): Re-write incoming Treads so that handler
// can always respond with a message of the correct msize.
var resp *Fcall
msg, err := c.handler.Handle(ctx, req.Message)
if err != nil {
// all handler errors are forwarded as protocol errors.
resp = newErrorFcall(req.Tag, err)
} else {
resp = newFcall(req.Tag, msg)
}
select {
case completed <- resp:
case <-ctx.Done():
return
case <-c.closed:
return
}
}(ctx, req)
}
case resp := <-completed:
// only responses that flip the tag state traverse this section.
active, ok := tags[resp.Tag]
if !ok {
// The tag is no longer active. Likely a flushed message.
continue
}
select {
case responses <- resp:
case <-active.ctx.Done():
// the context was canceled for some reason, perhaps timeout or
// due to a flush call. We treat this as a condition where a
// response should not be sent.
log.Println("canceled", resp, active.ctx.Err())
}
delete(tags, resp.Tag)
case <-c.ctx.Done():
return c.ctx.Err()
case <-c.closed:
return c.err
}
}
}
// read takes requests off the channel and sends them on requests.
func (c *conn) read(requests chan *Fcall) {
for {
req := new(Fcall)
if err := c.ch.ReadFcall(c.ctx, req); err != nil {
if err, ok := err.(net.Error); ok {
if err.Timeout() || err.Temporary() {
// TODO(stevvooe): A full idle timeout on the connection
// should be enforced here. No logging because it is quite
// chatty.
continue
}
}
c.CloseWithError(fmt.Errorf("error reading fcall: %v", err))
return
}
select {
case requests <- req:
case <-c.ctx.Done():
c.CloseWithError(c.ctx.Err())
return
case <-c.closed:
return
}
}
}
func (c *conn) write(responses chan *Fcall) {
for {
select {
case resp := <-responses:
// TODO(stevvooe): Correctly protect againt overflowing msize from
// handler. This can be done above, in the main message handler
// loop, by adjusting incoming Tread calls to have a Count that
// won't overflow the msize.
if err := c.ch.WriteFcall(c.ctx, resp); err != nil {
if err, ok := err.(net.Error); ok {
if err.Timeout() || err.Temporary() {
// TODO(stevvooe): A full idle timeout on the
// connection should be enforced here. We log here,
// since this is less common.
log.Printf("9p server: temporary error writing fcall: %v", err)
continue
}
}
c.CloseWithError(fmt.Errorf("error writing fcall: %v", err))
return
}
case <-c.ctx.Done():
c.CloseWithError(c.ctx.Err())
return
case <-c.closed:
return
}
}
}
func (c *conn) Close() error {
return c.CloseWithError(nil)
}
func (c *conn) CloseWithError(err error) error {
c.once.Do(func() {
if err == nil {
err = ErrClosed
}
c.err = err
close(c.closed)
})
return c.err
}

View File

@ -0,0 +1,42 @@
package p9p
import "context"
// Session provides the central abstraction for a 9p connection. Clients
// implement sessions and servers serve sessions. Sessions can be proxied by
// serving up a client session.
//
// The interface is also wired up with full context support to manage timeouts
// and resource clean up.
//
// Session represents the operations covered in section 5 of the plan 9 manual
// (http://man.cat-v.org/plan_9/5/). Requests are managed internally, so the
// Flush method is handled by the internal implementation. Consider preceeding
// these all with context to control request timeout.
type Session interface {
Auth(ctx context.Context, afid Fid, uname, aname string) (Qid, error)
Attach(ctx context.Context, fid, afid Fid, uname, aname string) (Qid, error)
Clunk(ctx context.Context, fid Fid) error
Remove(ctx context.Context, fid Fid) error
Walk(ctx context.Context, fid Fid, newfid Fid, names ...string) ([]Qid, error)
// Read follows the semantics of io.ReaderAt.ReadAtt method except it takes
// a contxt and Fid.
Read(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error)
// Write follows the semantics of io.WriterAt.WriteAt except takes a context and an Fid.
//
// If n == len(p), no error is returned.
// If n < len(p), io.ErrShortWrite will be returned.
Write(ctx context.Context, fid Fid, p []byte, offset int64) (n int, err error)
Open(ctx context.Context, fid Fid, mode Flag) (Qid, uint32, error)
Create(ctx context.Context, parent Fid, name string, perm uint32, mode Flag) (Qid, uint32, error)
Stat(ctx context.Context, fid Fid) (Dir, error)
WStat(ctx context.Context, fid Fid, dir Dir) error
// Version returns the supported version and msize of the session. This
// can be affected by negotiating or the level of support provided by the
// session implementation.
Version() (msize int, version string)
}

View File

@ -0,0 +1,250 @@
package p9p
import (
"errors"
"fmt"
"log"
"net"
"sync"
"context"
)
// roundTripper manages the request and response from the client-side. A
// roundTripper must abide by similar rules to the http.RoundTripper.
// Typically, the roundTripper will manage tag assignment and message
// serialization.
type roundTripper interface {
send(ctx context.Context, msg Message) (Message, error)
}
// transport plays the role of being a client channel manager. It multiplexes
// function calls onto the wire and dispatches responses to blocking calls to
// send. On the whole, transport is thread-safe for calling send
type transport struct {
ctx context.Context
ch Channel
requests chan *fcallRequest
shutdown chan struct{}
once sync.Once // protect closure of shutdown
closed chan struct{}
tags uint16
}
var _ roundTripper = &transport{}
func newTransport(ctx context.Context, ch Channel) roundTripper {
t := &transport{
ctx: ctx,
ch: ch,
requests: make(chan *fcallRequest),
shutdown: make(chan struct{}),
closed: make(chan struct{}),
}
go t.handle()
return t
}
// fcallRequest encompasses the request to send a message via fcall.
type fcallRequest struct {
ctx context.Context
message Message
response chan *Fcall
err chan error
}
func newFcallRequest(ctx context.Context, msg Message) *fcallRequest {
return &fcallRequest{
ctx: ctx,
message: msg,
response: make(chan *Fcall, 1),
err: make(chan error, 1),
}
}
func (t *transport) send(ctx context.Context, msg Message) (Message, error) {
req := newFcallRequest(ctx, msg)
// dispatch the request.
select {
case <-t.closed:
return nil, ErrClosed
case <-ctx.Done():
return nil, ctx.Err()
case t.requests <- req:
}
// wait for the response.
select {
case <-t.closed:
return nil, ErrClosed
case <-ctx.Done():
return nil, ctx.Err()
case err := <-req.err:
return nil, err
case resp := <-req.response:
if resp.Type == Rerror {
// pack the error into something useful
respmesg, ok := resp.Message.(MessageRerror)
if !ok {
return nil, fmt.Errorf("invalid error response: %v", resp)
}
return nil, respmesg
}
return resp.Message, nil
}
}
// allocateTag returns a valid tag given a tag pool map. It receives a hint as
// to where to start the tag search. It returns an error if the allocation is
// not possible. The provided map must not contain NOTAG as a key.
func allocateTag(r *fcallRequest, m map[Tag]*fcallRequest, hint Tag) (Tag, error) {
// The tag pool is depleted if 65535 (0xFFFF) tags are taken.
if len(m) >= 0xFFFF {
return 0, errors.New("tag pool depleted")
}
// Look for the first tag that doesn't exist in the map and return it.
for i := 0; i < 0xFFFF; i++ {
hint++
if hint == NOTAG {
hint = 0
}
if _, exists := m[hint]; !exists {
return hint, nil
}
}
return 0, errors.New("allocateTag: unexpected error")
}
// handle takes messages off the wire and wakes up the waiting tag call.
func (t *transport) handle() {
defer func() {
log.Println("exited handle loop")
close(t.closed)
}()
// the following variable block are protected components owned by this thread.
var (
responses = make(chan *Fcall)
// outstanding provides a map of tags to outstanding requests.
outstanding = map[Tag]*fcallRequest{}
selected Tag
)
// loop to read messages off of the connection
go func() {
defer func() {
log.Println("exited read loop")
t.close() // single main loop
}()
loop:
for {
fcall := new(Fcall)
if err := t.ch.ReadFcall(t.ctx, fcall); err != nil {
switch err := err.(type) {
case net.Error:
if err.Timeout() || err.Temporary() {
// BUG(stevvooe): There may be partial reads under
// timeout errors where this is actually fatal.
// can only retry if we haven't offset the frame.
continue loop
}
}
log.Println("fatal error reading msg:", err)
return
}
select {
case <-t.ctx.Done():
return
case <-t.closed:
log.Println("transport closed")
return
case responses <- fcall:
}
}
}()
for {
select {
case req := <-t.requests:
var err error
selected, err = allocateTag(req, outstanding, selected)
if err != nil {
req.err <- err
continue
}
outstanding[selected] = req
fcall := newFcall(selected, req.message)
// TODO(stevvooe): Consider the case of requests that never
// receive a response. We need to remove the fcall context from
// the tag map and dealloc the tag. We may also want to send a
// flush for the tag.
if err := t.ch.WriteFcall(req.ctx, fcall); err != nil {
delete(outstanding, fcall.Tag)
req.err <- err
}
case b := <-responses:
req, ok := outstanding[b.Tag]
if !ok {
// BUG(stevvooe): The exact handling of an unknown tag is
// unclear at this point. These may not necessarily fatal to
// the session, since they could be messages that the client no
// longer cares for. When we figure this out, replace this
// panic with something more sensible.
panic(fmt.Sprintf("unknown tag received: %v", b))
}
// BUG(stevvooe): Must detect duplicate tag and ensure that we are
// waking up the right caller. If a duplicate is received, the
// entry should not be deleted.
delete(outstanding, b.Tag)
req.response <- b
// TODO(stevvooe): Reclaim tag id.
case <-t.shutdown:
return
case <-t.ctx.Done():
return
}
}
}
func (t *transport) flush(ctx context.Context, tag Tag) error {
// TODO(stevvooe): We need to fire and forget flush messages when a call
// context gets cancelled.
panic("not implemented")
}
func (t *transport) Close() error {
t.close()
select {
case <-t.closed:
return nil
case <-t.ctx.Done():
return t.ctx.Err()
}
}
// close starts the shutdown process.
func (t *transport) close() {
t.once.Do(func() {
close(t.shutdown)
})
}

View File

@ -0,0 +1,149 @@
package p9p
import (
"fmt"
"time"
)
const (
// DefaultMSize messages size used to establish a session.
DefaultMSize = 64 << 10
// DefaultVersion for this package. Currently, the only supported version.
DefaultVersion = "9P2000"
)
// Mode constants for use Dir.Mode.
const (
DMDIR = 0x80000000 // mode bit for directories
DMAPPEND = 0x40000000 // mode bit for append only files
DMEXCL = 0x20000000 // mode bit for exclusive use files
DMMOUNT = 0x10000000 // mode bit for mounted channel
DMAUTH = 0x08000000 // mode bit for authentication file
DMTMP = 0x04000000 // mode bit for non-backed-up files
// 9p2000.u extensions
DMSYMLINK = 0x02000000
DMDEVICE = 0x00800000
DMNAMEDPIPE = 0x00200000
DMSOCKET = 0x00100000
DMSETUID = 0x00080000
DMSETGID = 0x00040000
DMREAD = 0x4 // mode bit for read permission
DMWRITE = 0x2 // mode bit for write permission
DMEXEC = 0x1 // mode bit for execute permission
)
// Flag defines the flag type for use with open and create
type Flag uint8
// Constants to use when opening files.
const (
OREAD Flag = 0x00 // open for read
OWRITE Flag = 0x01 // write
ORDWR Flag = 0x02 // read and write
OEXEC Flag = 0x03 // execute, == read but check execute permission
// PROPOSAL(stevvooe): Possible protocal extension to allow the create of
// symlinks. Initially, the link is created with no value. Read and write
// to read and set the link value.
OSYMLINK Flag = 0x04
OTRUNC Flag = 0x10 // or'ed in (except for exec), truncate file first
OCEXEC Flag = 0x20 // or'ed in, close on exec
ORCLOSE Flag = 0x40 // or'ed in, remove on close
)
// QType indicates the type of a resource within the Qid.
type QType uint8
// Constants for use in Qid to indicate resource type.
const (
QTDIR QType = 0x80 // type bit for directories
QTAPPEND QType = 0x40 // type bit for append only files
QTEXCL QType = 0x20 // type bit for exclusive use files
QTMOUNT QType = 0x10 // type bit for mounted channel
QTAUTH QType = 0x08 // type bit for authentication file
QTTMP QType = 0x04 // type bit for not-backed-up file
QTFILE QType = 0x00 // plain file
)
func (qt QType) String() string {
switch qt {
case QTDIR:
return "dir"
case QTAPPEND:
return "append"
case QTEXCL:
return "excl"
case QTMOUNT:
return "mount"
case QTAUTH:
return "auth"
case QTTMP:
return "tmp"
case QTFILE:
return "file"
}
return "unknown"
}
// Tag uniquely identifies an outstanding fcall in a 9p session.
type Tag uint16
// NOTAG is a reserved values for messages sent before establishing a session,
// such as Tversion.
const NOTAG Tag = ^Tag(0)
// Fid defines a type to hold Fid values.
type Fid uint32
// NOFID indicates the lack of an Fid.
const NOFID Fid = ^Fid(0)
// Qid indicates the type, path and version of the resource returned by a
// server. It is only valid for a session.
//
// Typically, a client maintains a mapping of Fid-Qid as Qids are returned by
// the server.
type Qid struct {
Type QType `9p:"type,1"`
Version uint32
Path uint64
}
func (qid Qid) String() string {
return fmt.Sprintf("qid(%v, v=%x, p=%x)",
qid.Type, qid.Version, qid.Path)
}
// Dir defines the structure used for expressing resources in stat/wstat and
// when reading directories.
type Dir struct {
Type uint16
Dev uint32
Qid Qid
Mode uint32
// BUG(stevvooe): The Year 2038 is coming soon. 9p wire protocol has these
// as 4 byte epoch times. Some possibilities include time dilation fields
// or atemporal files. We can also just not use them and set them to zero.
AccessTime time.Time
ModTime time.Time
Length uint64
Name string
UID string
GID string
MUID string
}
func (d Dir) String() string {
return fmt.Sprintf("dir(%v mode=%v atime=%v mtime=%v length=%v name=%v uid=%v gid=%v muid=%v)",
d.Qid, d.Mode, d.AccessTime, d.ModTime, d.Length, d.Name, d.UID, d.GID, d.MUID)
}

View File

@ -0,0 +1,112 @@
package p9p
import (
"fmt"
"context"
)
// NOTE(stevvooe): This file contains functions for negotiating version on the
// client and server. There are some nasty details to get right for
// downgrading the connection on the server-side that are not present yet.
// Really, these should be refactored into some sort of channel type that can
// support resets through version messages during the protocol exchange.
// clientnegotiate negiotiates the protocol version using channel, blocking
// until a response is received. The received value will be the version
// implemented by the server.
func clientnegotiate(ctx context.Context, ch Channel, version string) (string, error) {
req := newFcall(NOTAG, MessageTversion{
MSize: uint32(ch.MSize()),
Version: version,
})
if err := ch.WriteFcall(ctx, req); err != nil {
return "", err
}
resp := new(Fcall)
if err := ch.ReadFcall(ctx, resp); err != nil {
return "", err
}
switch v := resp.Message.(type) {
case MessageRversion:
if v.Version != version {
// TODO(stevvooe): A stubborn client indeed!
return "", fmt.Errorf("unsupported server version: %v", version)
}
if int(v.MSize) < ch.MSize() {
// upgrade msize if server differs.
ch.SetMSize(int(v.MSize))
}
return v.Version, nil
case error:
return "", v
default:
return "", ErrUnexpectedMsg
}
}
// servernegotiate blocks until a version message is received or a timeout
// occurs. The msize for the tranport will be set from the negotiation. If
// negotiate returns nil, a server may proceed with the connection.
//
// In the future, it might be better to handle the version messages in a
// separate object that manages the session. Each set of version requests
// effectively "reset" a connection, meaning all fids get clunked and all
// outstanding IO is aborted. This is probably slightly racy, in practice with
// a misbehaved client. The main issue is that we cannot tell which session
// messages belong to.
func servernegotiate(ctx context.Context, ch Channel, version string) error {
// wait for the version message over the transport.
req := new(Fcall)
if err := ch.ReadFcall(ctx, req); err != nil {
return err
}
mv, ok := req.Message.(MessageTversion)
if !ok {
return fmt.Errorf("expected version message: %v", mv)
}
respmsg := MessageRversion{
Version: version,
}
if mv.Version != version {
// TODO(stevvooe): Not the best place to do version handling. We need
// to have a way to pass supported versions into this method then have
// it return the actual version. For now, respond with 9P2000 for
// anything that doesn't match the provided version string.
//
// version(9) says "The server may respond with the clients
// version string, or a version string identifying an earlier
// defined protocol version. Currently, the only defined
// version is the 6 characters 9P2000." Therefore, it is always
// OK to respond with this.
respmsg.Version = "9P2000"
}
if int(mv.MSize) < ch.MSize() {
// if the server msize is too large, use the client's suggested msize.
ch.SetMSize(int(mv.MSize))
respmsg.MSize = mv.MSize
} else {
respmsg.MSize = uint32(ch.MSize())
}
resp := newFcall(NOTAG, respmsg)
if err := ch.WriteFcall(ctx, resp); err != nil {
return err
}
if respmsg.Version == "unknown" {
return fmt.Errorf("bad version negotiation")
}
return nil
}

27
src/cmd/linuxkit/vendor/github.com/google/uuid/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2009,2014 Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1,23 @@
**This package is currently in development and the API may not be stable.**
The API will become stable with v1.
# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master)
The uuid package generates and inspects UUIDs based on
[RFC 4122](http://tools.ietf.org/html/rfc4122)
and DCE 1.1: Authentication and Security Services.
This package is based on the github.com/pborman/uuid package (previously named
code.google.com/p/go-uuid). It differs from these earlier packages in that
a UUID is a 16 byte array rather than a byte slice. One loss due to this
change is the ability to represent an invalid UUID (vs a NIL UUID).
###### Install
`go get github.com/google/uuid`
###### Documentation
[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid)
Full `go doc` style documentation for the package can be viewed online without
installing this package by using the GoDoc site here:
http://godoc.org/github.com/google/uuid

80
src/cmd/linuxkit/vendor/github.com/google/uuid/dce.go generated vendored Normal file
View File

@ -0,0 +1,80 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"fmt"
"os"
)
// A Domain represents a Version 2 domain
type Domain byte
// Domain constants for DCE Security (Version 2) UUIDs.
const (
Person = Domain(0)
Group = Domain(1)
Org = Domain(2)
)
// NewDCESecurity returns a DCE Security (Version 2) UUID.
//
// The domain should be one of Person, Group or Org.
// On a POSIX system the id should be the users UID for the Person
// domain and the users GID for the Group. The meaning of id for
// the domain Org or on non-POSIX systems is site defined.
//
// For a given domain/id pair the same token may be returned for up to
// 7 minutes and 10 seconds.
func NewDCESecurity(domain Domain, id uint32) (UUID, error) {
uuid, err := NewUUID()
if err == nil {
uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2
uuid[9] = byte(domain)
binary.BigEndian.PutUint32(uuid[0:], id)
}
return uuid, err
}
// NewDCEPerson returns a DCE Security (Version 2) UUID in the person
// domain with the id returned by os.Getuid.
//
// NewDCESecurity(Person, uint32(os.Getuid()))
func NewDCEPerson() (UUID, error) {
return NewDCESecurity(Person, uint32(os.Getuid()))
}
// NewDCEGroup returns a DCE Security (Version 2) UUID in the group
// domain with the id returned by os.Getgid.
//
// NewDCESecurity(Group, uint32(os.Getgid()))
func NewDCEGroup() (UUID, error) {
return NewDCESecurity(Group, uint32(os.Getgid()))
}
// Domain returns the domain for a Version 2 UUID. Domains are only defined
// for Version 2 UUIDs.
func (uuid UUID) Domain() Domain {
return Domain(uuid[9])
}
// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2
// UUIDs.
func (uuid UUID) ID() uint32 {
return binary.BigEndian.Uint32(uuid[0:4])
}
func (d Domain) String() string {
switch d {
case Person:
return "Person"
case Group:
return "Group"
case Org:
return "Org"
}
return fmt.Sprintf("Domain%d", int(d))
}

12
src/cmd/linuxkit/vendor/github.com/google/uuid/doc.go generated vendored Normal file
View File

@ -0,0 +1,12 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package uuid generates and inspects UUIDs.
//
// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security
// Services.
//
// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to
// maps or compared directly.
package uuid

53
src/cmd/linuxkit/vendor/github.com/google/uuid/hash.go generated vendored Normal file
View File

@ -0,0 +1,53 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"crypto/md5"
"crypto/sha1"
"hash"
)
// Well known namespace IDs and UUIDs
var (
NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
Nil UUID // empty UUID, all zeros
)
// NewHash returns a new UUID derived from the hash of space concatenated with
// data generated by h. The hash should be at least 16 byte in length. The
// first 16 bytes of the hash are used to form the UUID. The version of the
// UUID will be the lower 4 bits of version. NewHash is used to implement
// NewMD5 and NewSHA1.
func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID {
h.Reset()
h.Write(space[:])
h.Write([]byte(data))
s := h.Sum(nil)
var uuid UUID
copy(uuid[:], s)
uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4)
uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant
return uuid
}
// NewMD5 returns a new MD5 (Version 3) UUID based on the
// supplied name space and data. It is the same as calling:
//
// NewHash(md5.New(), space, data, 3)
func NewMD5(space UUID, data []byte) UUID {
return NewHash(md5.New(), space, data, 3)
}
// NewSHA1 returns a new SHA1 (Version 5) UUID based on the
// supplied name space and data. It is the same as calling:
//
// NewHash(sha1.New(), space, data, 5)
func NewSHA1(space UUID, data []byte) UUID {
return NewHash(sha1.New(), space, data, 5)
}

View File

@ -0,0 +1,39 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import "fmt"
// MarshalText implements encoding.TextMarshaler.
func (uuid UUID) MarshalText() ([]byte, error) {
var js [36]byte
encodeHex(js[:], uuid)
return js[:], nil
}
// UnmarshalText implements encoding.TextUnmarshaler.
func (uuid *UUID) UnmarshalText(data []byte) error {
// See comment in ParseBytes why we do this.
// id, err := ParseBytes(data)
id, err := ParseBytes(data)
if err == nil {
*uuid = id
}
return err
}
// MarshalBinary implements encoding.BinaryMarshaler.
func (uuid UUID) MarshalBinary() ([]byte, error) {
return uuid[:], nil
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler.
func (uuid *UUID) UnmarshalBinary(data []byte) error {
if len(data) != 16 {
return fmt.Errorf("invalid UUID (got %d bytes)", len(data))
}
copy(uuid[:], data)
return nil
}

103
src/cmd/linuxkit/vendor/github.com/google/uuid/node.go generated vendored Normal file
View File

@ -0,0 +1,103 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"net"
"sync"
)
var (
nodeMu sync.Mutex
interfaces []net.Interface // cached list of interfaces
ifname string // name of interface being used
nodeID [6]byte // hardware for version 1 UUIDs
zeroID [6]byte // nodeID with only 0's
)
// NodeInterface returns the name of the interface from which the NodeID was
// derived. The interface "user" is returned if the NodeID was set by
// SetNodeID.
func NodeInterface() string {
defer nodeMu.Unlock()
nodeMu.Lock()
return ifname
}
// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs.
// If name is "" then the first usable interface found will be used or a random
// Node ID will be generated. If a named interface cannot be found then false
// is returned.
//
// SetNodeInterface never fails when name is "".
func SetNodeInterface(name string) bool {
defer nodeMu.Unlock()
nodeMu.Lock()
return setNodeInterface(name)
}
func setNodeInterface(name string) bool {
if interfaces == nil {
var err error
interfaces, err = net.Interfaces()
if err != nil && name != "" {
return false
}
}
for _, ifs := range interfaces {
if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) {
copy(nodeID[:], ifs.HardwareAddr)
ifname = ifs.Name
return true
}
}
// We found no interfaces with a valid hardware address. If name
// does not specify a specific interface generate a random Node ID
// (section 4.1.6)
if name == "" {
randomBits(nodeID[:])
return true
}
return false
}
// NodeID returns a slice of a copy of the current Node ID, setting the Node ID
// if not already set.
func NodeID() []byte {
defer nodeMu.Unlock()
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
nid := nodeID
return nid[:]
}
// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes
// of id are used. If id is less than 6 bytes then false is returned and the
// Node ID is not set.
func SetNodeID(id []byte) bool {
if len(id) < 6 {
return false
}
defer nodeMu.Unlock()
nodeMu.Lock()
copy(nodeID[:], id)
ifname = "user"
return true
}
// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is
// not valid. The NodeID is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) NodeID() []byte {
if len(uuid) != 16 {
return nil
}
var node [6]byte
copy(node[:], uuid[10:])
return node[:]
}

59
src/cmd/linuxkit/vendor/github.com/google/uuid/sql.go generated vendored Normal file
View File

@ -0,0 +1,59 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"database/sql/driver"
"fmt"
)
// Scan implements sql.Scanner so UUIDs can be read from databases transparently
// Currently, database types that map to string and []byte are supported. Please
// consult database-specific driver documentation for matching types.
func (uuid *UUID) Scan(src interface{}) error {
switch src := src.(type) {
case nil:
return nil
case string:
// if an empty UUID comes from a table, we return a null UUID
if src == "" {
return nil
}
// see Parse for required string format
u, err := Parse(src)
if err != nil {
return fmt.Errorf("Scan: %v", err)
}
*uuid = u
case []byte:
// if an empty UUID comes from a table, we return a null UUID
if len(src) == 0 {
return nil
}
// assumes a simple slice of bytes if 16 bytes
// otherwise attempts to parse
if len(src) != 16 {
return uuid.Scan(string(src))
}
copy((*uuid)[:], src)
default:
return fmt.Errorf("Scan: unable to scan type %T into UUID", src)
}
return nil
}
// Value implements sql.Valuer so that UUIDs can be written to databases
// transparently. Currently, UUIDs map to strings. Please consult
// database-specific driver documentation for matching types.
func (uuid UUID) Value() (driver.Value, error) {
return uuid.String(), nil
}

123
src/cmd/linuxkit/vendor/github.com/google/uuid/time.go generated vendored Normal file
View File

@ -0,0 +1,123 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
"sync"
"time"
)
// A Time represents a time as the number of 100's of nanoseconds since 15 Oct
// 1582.
type Time int64
const (
lillian = 2299160 // Julian day of 15 Oct 1582
unix = 2440587 // Julian day of 1 Jan 1970
epoch = unix - lillian // Days between epochs
g1582 = epoch * 86400 // seconds between epochs
g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs
)
var (
timeMu sync.Mutex
lasttime uint64 // last time we returned
clockSeq uint16 // clock sequence for this run
timeNow = time.Now // for testing
)
// UnixTime converts t the number of seconds and nanoseconds using the Unix
// epoch of 1 Jan 1970.
func (t Time) UnixTime() (sec, nsec int64) {
sec = int64(t - g1582ns100)
nsec = (sec % 10000000) * 100
sec /= 10000000
return sec, nsec
}
// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and
// clock sequence as well as adjusting the clock sequence as needed. An error
// is returned if the current time cannot be determined.
func GetTime() (Time, uint16, error) {
defer timeMu.Unlock()
timeMu.Lock()
return getTime()
}
func getTime() (Time, uint16, error) {
t := timeNow()
// If we don't have a clock sequence already, set one.
if clockSeq == 0 {
setClockSequence(-1)
}
now := uint64(t.UnixNano()/100) + g1582ns100
// If time has gone backwards with this clock sequence then we
// increment the clock sequence
if now <= lasttime {
clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000
}
lasttime = now
return Time(now), clockSeq, nil
}
// ClockSequence returns the current clock sequence, generating one if not
// already set. The clock sequence is only used for Version 1 UUIDs.
//
// The uuid package does not use global static storage for the clock sequence or
// the last time a UUID was generated. Unless SetClockSequence is used, a new
// random clock sequence is generated the first time a clock sequence is
// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1)
func ClockSequence() int {
defer timeMu.Unlock()
timeMu.Lock()
return clockSequence()
}
func clockSequence() int {
if clockSeq == 0 {
setClockSequence(-1)
}
return int(clockSeq & 0x3fff)
}
// SetClockSeq sets the clock sequence to the lower 14 bits of seq. Setting to
// -1 causes a new sequence to be generated.
func SetClockSequence(seq int) {
defer timeMu.Unlock()
timeMu.Lock()
setClockSequence(seq)
}
func setClockSequence(seq int) {
if seq == -1 {
var b [2]byte
randomBits(b[:]) // clock sequence
seq = int(b[0])<<8 | int(b[1])
}
old_seq := clockSeq
clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant
if old_seq != clockSeq {
lasttime = 0
}
}
// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in
// uuid. The time is only defined for version 1 and 2 UUIDs.
func (uuid UUID) Time() Time {
time := int64(binary.BigEndian.Uint32(uuid[0:4]))
time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32
time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48
return Time(time)
}
// ClockSequence returns the clock sequence encoded in uuid.
// The clock sequence is only well defined for version 1 and 2 UUIDs.
func (uuid UUID) ClockSequence() int {
return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff
}

43
src/cmd/linuxkit/vendor/github.com/google/uuid/util.go generated vendored Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"io"
)
// randomBits completely fills slice b with random data.
func randomBits(b []byte) {
if _, err := io.ReadFull(rander, b); err != nil {
panic(err.Error()) // rand should never fail
}
}
// xvalues returns the value of a byte as a hexadecimal digit or 255.
var xvalues = [256]byte{
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
}
// xtob converts hex characters x1 and x2 into a byte.
func xtob(x1, x2 byte) (byte, bool) {
b1 := xvalues[x1]
b2 := xvalues[x2]
return (b1 << 4) | b2, b1 != 255 && b2 != 255
}

191
src/cmd/linuxkit/vendor/github.com/google/uuid/uuid.go generated vendored Normal file
View File

@ -0,0 +1,191 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"bytes"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"io"
"strings"
)
// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC
// 4122.
type UUID [16]byte
// A Version represents a UUID's version.
type Version byte
// A Variant represents a UUID's variant.
type Variant byte
// Constants returned by Variant.
const (
Invalid = Variant(iota) // Invalid UUID
RFC4122 // The variant specified in RFC4122
Reserved // Reserved, NCS backward compatibility.
Microsoft // Reserved, Microsoft Corporation backward compatibility.
Future // Reserved for future definition.
)
var rander = rand.Reader // random function
// Parse decodes s into a UUID or returns an error. Both the UUID form of
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded.
func Parse(s string) (UUID, error) {
var uuid UUID
if len(s) != 36 {
if len(s) != 36+9 {
return uuid, fmt.Errorf("invalid UUID length: %d", len(s))
}
if strings.ToLower(s[:9]) != "urn:uuid:" {
return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9])
}
s = s[9:]
}
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
return uuid, errors.New("invalid UUID format")
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
if v, ok := xtob(s[x], s[x+1]); !ok {
return uuid, errors.New("invalid UUID format")
} else {
uuid[i] = v
}
}
return uuid, nil
}
// ParseBytes is like Parse, except it parses a byte slice instead of a string.
func ParseBytes(b []byte) (UUID, error) {
var uuid UUID
if len(b) != 36 {
if len(b) != 36+9 {
return uuid, fmt.Errorf("invalid UUID length: %d", len(b))
}
if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) {
return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9])
}
b = b[9:]
}
if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' {
return uuid, errors.New("invalid UUID format")
}
for i, x := range [16]int{
0, 2, 4, 6,
9, 11,
14, 16,
19, 21,
24, 26, 28, 30, 32, 34} {
if v, ok := xtob(b[x], b[x+1]); !ok {
return uuid, errors.New("invalid UUID format")
} else {
uuid[i] = v
}
}
return uuid, nil
}
// Must returns uuid if err is nil and panics otherwise.
func Must(uuid UUID, err error) UUID {
if err != nil {
panic(err)
}
return uuid
}
// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
// , or "" if uuid is invalid.
func (uuid UUID) String() string {
var buf [36]byte
encodeHex(buf[:], uuid)
return string(buf[:])
}
// URN returns the RFC 2141 URN form of uuid,
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid.
func (uuid UUID) URN() string {
var buf [36 + 9]byte
copy(buf[:], "urn:uuid:")
encodeHex(buf[9:], uuid)
return string(buf[:])
}
func encodeHex(dst []byte, uuid UUID) {
hex.Encode(dst[:], uuid[:4])
dst[8] = '-'
hex.Encode(dst[9:13], uuid[4:6])
dst[13] = '-'
hex.Encode(dst[14:18], uuid[6:8])
dst[18] = '-'
hex.Encode(dst[19:23], uuid[8:10])
dst[23] = '-'
hex.Encode(dst[24:], uuid[10:])
}
// Variant returns the variant encoded in uuid.
func (uuid UUID) Variant() Variant {
switch {
case (uuid[8] & 0xc0) == 0x80:
return RFC4122
case (uuid[8] & 0xe0) == 0xc0:
return Microsoft
case (uuid[8] & 0xe0) == 0xe0:
return Future
default:
return Reserved
}
}
// Version returns the version of uuid.
func (uuid UUID) Version() Version {
return Version(uuid[6] >> 4)
}
func (v Version) String() string {
if v > 15 {
return fmt.Sprintf("BAD_VERSION_%d", v)
}
return fmt.Sprintf("VERSION_%d", v)
}
func (v Variant) String() string {
switch v {
case RFC4122:
return "RFC4122"
case Reserved:
return "Reserved"
case Microsoft:
return "Microsoft"
case Future:
return "Future"
case Invalid:
return "Invalid"
}
return fmt.Sprintf("BadVariant%d", int(v))
}
// SetRand sets the random number generator to r, which implements io.Reader.
// If r.Read returns an error when the package requests random data then
// a panic will be issued.
//
// Calling SetRand with nil sets the random number generator to the default
// generator.
func SetRand(r io.Reader) {
if r == nil {
rander = rand.Reader
return
}
rander = r
}

View File

@ -0,0 +1,44 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import (
"encoding/binary"
)
// NewUUID returns a Version 1 UUID based on the current NodeID and clock
// sequence, and the current time. If the NodeID has not been set by SetNodeID
// or SetNodeInterface then it will be set automatically. If the NodeID cannot
// be set NewUUID returns nil. If clock sequence has not been set by
// SetClockSequence then it will be set automatically. If GetTime fails to
// return the current NewUUID returns nil and an error.
//
// In most cases, New should be used.
func NewUUID() (UUID, error) {
nodeMu.Lock()
if nodeID == zeroID {
setNodeInterface("")
}
nodeMu.Unlock()
var uuid UUID
now, seq, err := GetTime()
if err != nil {
return uuid, err
}
timeLow := uint32(now & 0xffffffff)
timeMid := uint16((now >> 32) & 0xffff)
timeHi := uint16((now >> 48) & 0x0fff)
timeHi |= 0x1000 // Version 1
binary.BigEndian.PutUint32(uuid[0:], timeLow)
binary.BigEndian.PutUint16(uuid[4:], timeMid)
binary.BigEndian.PutUint16(uuid[6:], timeHi)
binary.BigEndian.PutUint16(uuid[8:], seq)
copy(uuid[10:], nodeID[:])
return uuid, nil
}

View File

@ -0,0 +1,38 @@
// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package uuid
import "io"
// New creates a new random UUID or panics. New is equivalent to
// the expression
//
// uuid.Must(uuid.NewRandom())
func New() UUID {
return Must(NewRandom())
}
// NewRandom returns a Random (Version 4) UUID or panics.
//
// The strength of the UUIDs is based on the strength of the crypto/rand
// package.
//
// A note about uniqueness derived from the UUID Wikipedia entry:
//
// Randomly generated UUIDs have 122 random bits. One's annual risk of being
// hit by a meteorite is estimated to be one chance in 17 billion, that
// means the probability is about 0.00000000006 (6 × 1011),
// equivalent to the odds of creating a few tens of trillions of UUIDs in a
// year and having one duplicate.
func NewRandom() (UUID, error) {
var uuid UUID
_, err := io.ReadFull(rander, uuid[:])
if err != nil {
return Nil, err
}
uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4
uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10
return uuid, nil
}

View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2013-2016 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,112 @@
## DataKit -- Orchestrate applications using a Git-like dataflow
*DataKit* is a tool to orchestrate applications using a Git-like dataflow. It
revisits the UNIX pipeline concept, with a modern twist: streams of
tree-structured data instead of raw text. DataKit allows you to define
complex build pipelines over version-controlled data.
DataKit is currently used as the coordination
layer for [HyperKit](http://github.com/docker/hyperkit), the
hypervisor component of
[Docker for Mac and Windows](https://blog.docker.com/2016/03/docker-for-mac-windows-beta/), and
for the [DataKitCI][] continuous integration system.
---
[![Build Status (OSX, Linux)](https://travis-ci.org/moby/datakit.svg)](https://travis-ci.org/moby/datakit)
[![Build status (Windows)](https://ci.appveyor.com/api/projects/status/6qrdgiqbhi4sehmy/branch/master?svg=true)](https://ci.appveyor.com/project/moby/datakit/branch/master)
[![docs](https://img.shields.io/badge/doc-online-blue.svg)](https://docker.github.io/datakit/)
There are several components in this repository:
- `src` contains the main DataKit service. This is a Git-like database to which other services can connect.
- `ci` contains [DataKitCI][], a continuous integration system that uses DataKit to monitor repositories and store build results.
- `ci/self-ci` is the CI configuration for DataKitCI that tests DataKit itself.
- `bridge/github` is a service that monitors repositories on GitHub and syncs their metadata with a DataKit database.
e.g. when a pull request is opened or updated, it will commit that information to DataKit. If you commit a status message to DataKit, the bridge will push it to GitHub.
- `bridge/local` is a drop-in replacement for `bridge/github` that just monitors a local Git repository. This is useful for local testing.
### Quick Start
The easiest way to use DataKit is to start both the server and the client in containers.
To expose a Git repository as a 9p endpoint on port 5640 on a private network, run:
```shell
$ docker network create datakit-net # create a private network
$ docker run -it --net datakit-net --name datakit -v <path/to/git/repo>:/data datakit/db
```
*Note*: The `--name datakit` option is mandatory. It will allow the client
to connect to a known name on the private network.
You can then start a DataKit client, which will mount the 9p endpoint and
expose the database as a filesystem API:
```shell
# In an other terminal
$ docker run -it --privileged --net datakit-net datakit/client
$ ls /db
branch remotes snapshots trees
```
*Note*: the `--privileged` option is needed because the container will have
to mount the 9p endpoint into its local filesystem.
Now you can explore, edit and script `/db`. See the
[Filesystem API][]
for more details.
### Building
The easiest way to build the DataKit project is to use [docker](https://docker.com),
(which is what the
[start-datakit.sh](https://github.com/moby/datakit/blob/master/scripts/start-datakit.sh) script
does under the hood):
```shell
docker build -t datakit/db -f Dockerfile .
docker run -p 5640:5640 -it --rm datakit/db --listen-9p=tcp://0.0.0.0:5640
```
These commands will expose the database's 9p endpoint on port 5640.
If you want to build the project from source without Docker, you will need to install
[ocaml](http://ocaml.org/) and [opam](http://opam.ocaml.org/). Then write:
```shell
$ make depends
$ make && make test
```
For information about command-line options:
```shell
$ datakit --help
```
## Prometheus metric reporting
Run with `--listen-prometheus 9090` to expose metrics at `http://*:9090/metrics`.
Note: there is no encryption and no access control. You are expected to run the
database in a container and to not export this port to the outside world. You
can either collect the metrics by running a Prometheus service in a container
on the same Docker network, or front the service with nginx or similar if you
want to collect metrics remotely.
## Language bindings
* **Go** bindings are in the `api/go` directory.
* **OCaml** bindings are in the `api/ocaml` directory. See `examples/ocaml-client` for an example.
## Licensing
DataKit is licensed under the Apache License, Version 2.0. See
[LICENSE](https://github.com/moby/datakit/blob/master/LICENSE.md) for the full
license text.
Contributions are welcome under the terms of this license. You may wish to browse
the [weekly reports](reports) to read about overall activity in the repository.
[DataKitCI]: https://github.com/moby/datakit/tree/master/ci
[Filesystem API]: https://github.com/moby/datakit/tree/master/9p.md

View File

@ -0,0 +1 @@
To run test on windows, launch the a datakit server with --url \\.\pipe\datakit-test

View File

@ -0,0 +1,352 @@
package datakit
import (
"bytes"
"io"
"log"
"net"
"sync"
p9p "github.com/docker/go-p9p"
"context"
)
type Client struct {
conn net.Conn
session p9p.Session
m *sync.Mutex
c *sync.Cond
usedFids map[p9p.Fid]bool
freeFids []p9p.Fid
root p9p.Fid
}
var badFid = p9p.Fid(0)
var rwx = p9p.DMREAD | p9p.DMWRITE | p9p.DMEXEC
var rx = p9p.DMREAD | p9p.DMEXEC
var rw = p9p.DMREAD | p9p.DMWRITE
var r = p9p.DMREAD
var dirperm = uint32(rwx<<6 | rx<<3 | rx | p9p.DMDIR)
var fileperm = uint32(rw<<6 | r<<3 | r)
// Dial opens a connection to a 9P server
func Dial(ctx context.Context, network, address string) (*Client, error) {
log.Println("Dialling", network, address)
conn, err := net.Dial(network, address)
if err != nil {
return nil, err
}
return NewClient(ctx, conn)
}
// NewClient creates opens a connection with the p9p server
func NewClient(ctx context.Context, conn net.Conn) (*Client, error) {
session, err := p9p.NewSession(ctx, conn)
if err != nil {
log.Println("Failed to establish 9P session to", err)
return nil, err
}
root := p9p.Fid(1)
if _, err := session.Attach(ctx, root, p9p.NOFID, "anyone", "/"); err != nil {
log.Println("Failed to Attach to filesystem", err)
return nil, err
}
usedFids := make(map[p9p.Fid]bool, 0)
freeFids := make([]p9p.Fid, 0)
for i := 0; i < 128; i++ {
fid := p9p.Fid(i)
if fid == root {
usedFids[fid] = true
} else {
freeFids = append(freeFids, fid)
usedFids[fid] = false
}
}
var m sync.Mutex
c := sync.NewCond(&m)
return &Client{conn, session, &m, c, usedFids, freeFids, root}, nil
}
func (c *Client) Close(ctx context.Context) {
if err := c.session.Clunk(ctx, c.root); err != nil {
log.Println("Failed to Clunk root fid", err)
} else {
c.usedFids[c.root] = false
}
c.m.Lock()
defer c.m.Unlock()
for fid, inuse := range c.usedFids {
if inuse {
log.Println("I don't know how to flush: leaking", fid)
}
}
c.conn.Close()
}
// allocFid returns a fresh fid, bound to a clone of from
func (c *Client) allocFid(ctx context.Context, from p9p.Fid) (p9p.Fid, error) {
c.m.Lock()
defer c.m.Unlock()
for len(c.freeFids) == 0 {
c.c.Wait()
}
fid := c.freeFids[len(c.freeFids)-1]
c.freeFids = c.freeFids[0 : len(c.freeFids)-1]
c.usedFids[fid] = true
_, err := c.session.Walk(ctx, from, fid)
if err != nil {
log.Println("Failed to clone root fid", err)
return badFid, err
}
return fid, nil
}
// freeFid removes resources associated with the given fid
func (c *Client) freeFid(ctx context.Context, fid p9p.Fid) {
c.m.Lock()
defer c.m.Unlock()
c.freeFids = append(c.freeFids, fid)
c.usedFids[fid] = false
if err := c.session.Clunk(ctx, fid); err != nil {
log.Println("Failed to clunk fid", fid)
}
c.c.Signal()
}
// Mkdir acts like 'mkdir -p'
func (c *Client) Mkdir(ctx context.Context, path ...string) error {
fid, err := c.allocFid(ctx, c.root)
if err != nil {
return nil
}
defer c.freeFid(ctx, fid)
// mkdir -p
for _, dir := range path {
dirfid, err := c.allocFid(ctx, fid)
if err != nil {
return err
}
// dir may or may not exist
_, _, _ = c.session.Create(ctx, dirfid, dir, dirperm, p9p.OREAD)
c.freeFid(ctx, dirfid)
// dir should definitely exist
if _, err := c.session.Walk(ctx, fid, fid, dir); err != nil {
log.Println("Failed to Walk to", dir, err)
return err
}
}
return nil
}
var enoent = p9p.MessageRerror{Ename: "No such file or directory"}
var enotdir = p9p.MessageRerror{Ename: "Can't walk from a file"}
// Remove acts like 'rm -f'
func (c *Client) Remove(ctx context.Context, path ...string) error {
fid, err := c.allocFid(ctx, c.root)
if err != nil {
return err
}
if _, err := c.session.Walk(ctx, fid, fid, path...); err != nil {
if err == enoent || err == enotdir {
c.freeFid(ctx, fid)
return nil
}
log.Println("Failed to walk to", path, err)
c.freeFid(ctx, fid)
return err
}
// Remove will cluck the fid, even if it fails
if err := c.session.Remove(ctx, fid); err != nil {
if err == enoent {
return nil
}
log.Println("Failed to Remove", path, err)
return err
}
return nil
}
type File struct {
fid p9p.Fid
c *Client
m *sync.Mutex
open bool
}
// Create creates a file
func (c *Client) Create(ctx context.Context, path ...string) (*File, error) {
fid, err := c.allocFid(ctx, c.root)
if err != nil {
return nil, err
}
dir := path[0 : len(path)-1]
_, err = c.session.Walk(ctx, fid, fid, dir...)
if err != nil {
if err != enoent {
// This is a common error
log.Println("Failed to Walk to", path, err)
}
c.freeFid(ctx, fid)
return nil, err
}
_, _, err = c.session.Create(ctx, fid, path[len(path)-1], fileperm, p9p.ORDWR)
if err != nil {
log.Println("Failed to Create", path, err)
return nil, err
}
var m sync.Mutex
return &File{fid: fid, c: c, m: &m, open: true}, nil
}
// Open opens a file
func (c *Client) Open(ctx context.Context, mode p9p.Flag, path ...string) (*File, error) {
fid, err := c.allocFid(ctx, c.root)
if err != nil {
return nil, err
}
_, err = c.session.Walk(ctx, fid, fid, path...)
if err != nil {
if err != enoent {
// This is a common error
log.Println("Failed to Walk to", path, err)
}
c.freeFid(ctx, fid)
return nil, err
}
_, _, err = c.session.Open(ctx, fid, mode)
if err != nil {
log.Println("Failed to Open", path, err)
c.freeFid(ctx, fid)
return nil, err
}
var m sync.Mutex
return &File{fid: fid, c: c, m: &m, open: true}, nil
}
// List a directory
func (c *Client) List(ctx context.Context, path []string) ([]string, error) {
file, err := c.Open(ctx, p9p.OREAD, path...)
if err != nil {
return nil, err
}
defer file.Close(ctx)
msize, _ := c.session.Version()
iounit := uint32(msize - 24) // size of message max minus fcall io header (Rread)
p := make([]byte, iounit)
n, err := c.session.Read(ctx, file.fid, p, 0)
if err != nil {
return nil, err
}
files := []string{}
rd := bytes.NewReader(p[:n])
codec := p9p.NewCodec() // TODO(stevvooe): Need way to resolve codec based on session.
for {
var d p9p.Dir
if err := p9p.DecodeDir(codec, rd, &d); err != nil {
if err == io.EOF {
break
}
return files, err
}
files = append(files, d.Name)
}
return files, nil
}
// Close closes a file
func (f *File) Close(ctx context.Context) {
f.m.Lock()
defer f.m.Unlock()
if f.open {
f.c.freeFid(ctx, f.fid)
}
f.open = false
}
// Read reads a value
func (f *File) Read(ctx context.Context, p []byte, offset int64) (int, error) {
f.m.Lock()
defer f.m.Unlock()
if !f.open {
return 0, io.EOF
}
return f.c.session.Read(ctx, f.fid, p, offset)
}
// Write writes a value
func (f *File) Write(ctx context.Context, p []byte, offset int64) (int, error) {
f.m.Lock()
defer f.m.Unlock()
if !f.open {
return 0, io.EOF
}
return f.c.session.Write(ctx, f.fid, p, offset)
}
type FileReader struct {
file *File
offset int64
ctx context.Context
}
func (f *File) NewFileReader(ctx context.Context) *FileReader {
offset := int64(0)
return &FileReader{file: f, offset: offset, ctx: ctx}
}
func (f *FileReader) Read(p []byte) (int, error) {
n, err := f.file.Read(f.ctx, p, f.offset)
f.offset = f.offset + int64(n)
if n == 0 {
return 0, io.EOF
}
return n, err
}
type ioFileReaderWriter struct {
f *File
ctx context.Context
offset int64
}
// NewIOReader creates a standard io.Reader at a given position in the file
func (f *File) NewIOReader(ctx context.Context, offset int64) io.Reader {
return &ioFileReaderWriter{f, ctx, offset}
}
// NewIOWriter creates a standard io.Writer at a given position in the file
func (f *File) NewIOWriter(ctx context.Context, offset int64) io.Writer {
return &ioFileReaderWriter{f, ctx, offset}
}
func (r *ioFileReaderWriter) Read(p []byte) (n int, err error) {
r.f.m.Lock()
defer r.f.m.Unlock()
n, err = r.f.c.session.Read(r.ctx, r.f.fid, p, r.offset)
r.offset += int64(n)
return n, err
}
func (w *ioFileReaderWriter) Write(p []byte) (n int, err error) {
w.f.m.Lock()
defer w.f.m.Unlock()
for err == nil || err == io.ErrShortWrite {
var written int
written, err = w.f.c.session.Write(w.ctx, w.f.fid, p, w.offset)
p = p[written:]
w.offset += int64(written)
n += written
if len(p) == 0 {
break
}
}
return
}

View File

@ -0,0 +1,380 @@
package datakit
import (
"fmt"
"log"
"strconv"
"strings"
"context"
)
type Version int
var InitialVersion = Version(0)
// Record is a typed view on top of a database branch
type Record struct {
client *Client
path []string // directory inside the store
version Version
schemaF *IntField
fields []*StringRefField // registered fields, for schema upgrades
lookupB []string // priority ordered list of branches to look up values in
defaultsB string // name of the branch containing built-in defaults
stateB string // name of the branch containing run-time state
event chan (interface{})
onUpdate [](func([]*Snapshot, Version))
}
func NewRecord(ctx context.Context, client *Client, lookupB []string, defaultsB string, stateB string, path []string) (*Record, error) {
event := make(chan (interface{}), 0)
for _, b := range append(lookupB, stateB) {
// Create the branch if it doesn't exist
t, err := NewTransaction(ctx, client, b)
if err != nil {
log.Fatalf("Failed to open a new transaction: %#v", err)
}
if err = t.Write(ctx, []string{"branch-created"}, ""); err != nil {
log.Fatalf("Failed to write branch-created: %#v", err)
}
if err = t.Commit(ctx, "Creating branch"); err != nil {
log.Fatalf("Failed to commit transaction: %#v", err)
}
}
for _, b := range lookupB {
if err := client.Mkdir(ctx, "branch", b); err != nil {
return nil, err
}
w, err := NewWatch(ctx, client, b, path)
if err != nil {
return nil, err
}
go func() {
for {
_, err := w.Next(ctx)
if err != nil {
return
}
log.Printf("Snapshot has changed\n")
event <- 0
}
}()
}
onUpdate := make([](func([]*Snapshot, Version)), 0)
fields := make([]*StringRefField, 0)
r := &Record{
client: client,
path: path,
version: InitialVersion,
fields: fields,
lookupB: lookupB,
defaultsB: defaultsB,
stateB: stateB,
event: event,
onUpdate: onUpdate,
}
r.schemaF = r.IntField("schema-version", 1)
return r, nil
}
func (r *Record) updateAll(ctx context.Context) error {
snapshots := make([]*Snapshot, 0)
for _, b := range r.lookupB {
head, err := Head(ctx, r.client, b)
if err != nil {
return err
}
snap := NewSnapshot(ctx, r.client, COMMIT, head)
snapshots = append(snapshots, snap)
}
for _, fn := range r.onUpdate {
fn(snapshots, r.version)
}
return nil
}
func (r *Record) Seal(ctx context.Context) error {
return r.updateAll(ctx)
}
func (r *Record) Wait(ctx context.Context) error {
<-r.event
r.version = r.version + 1
return r.updateAll(ctx)
}
func (r *Record) Upgrade(ctx context.Context, schemaVersion int) error {
currentVersion, _ := r.schemaF.Get()
if schemaVersion <= currentVersion {
log.Printf("No schema upgrade necessary because new version (%d) <= current version (%d)\n", schemaVersion, currentVersion)
return nil
}
r.schemaF.defaultInt = schemaVersion
defaultString := fmt.Sprintf("%d", schemaVersion)
r.schemaF.raw.defaultValue = &defaultString
// Create defaults branch
log.Printf("Performing schema upgrade to version %d\n", schemaVersion)
t, err := NewTransaction(ctx, r.client, r.defaultsB)
if err != nil {
return err
}
// For each known field, write default value to branch
for _, f := range r.fields {
p := append(r.path, f.path...)
if f.defaultValue == nil {
err = t.Remove(ctx, p)
} else {
err = t.Write(ctx, p, *f.defaultValue)
}
if err != nil {
return err
}
}
// Merge to the defaults branch
err = t.Commit(ctx, fmt.Sprintf("Upgrade to schema version %d", schemaVersion))
if err != nil {
return err
}
return r.Wait(ctx)
}
// fillInDefault updates the default branch to contain the new value.
func (r *Record) fillInDefault(path []string, valueref *string) error {
ctx := context.Background()
t, err := NewTransaction(ctx, r.client, r.defaultsB)
if err != nil {
return err
}
p := append(r.path, path...)
if valueref == nil {
log.Printf("Removing default value at %s", strings.Join(p, "/"))
if err = t.Remove(ctx, p); err != nil {
log.Printf("Failed to remove key at %s", strings.Join(p, "/"))
return err
}
} else {
log.Printf("Updating default value at %s to %s", strings.Join(p, "/"), *valueref)
if err = t.Write(ctx, p, *valueref); err != nil {
log.Printf("Failed to write key %s = %s", strings.Join(p, "/"), *valueref)
return err
}
}
return t.Commit(ctx, fmt.Sprintf("fill-in default for %s", p))
}
func (r *Record) SetMultiple(description string, fields []*StringField, values []string) error {
if len(fields) != len(values) {
return fmt.Errorf("Length of fields and values is not equal")
}
ctx := context.Background()
t, err := NewTransaction(ctx, r.client, r.lookupB[0])
if err != nil {
return err
}
for i, k := range fields {
p := append(r.path, k.raw.path...)
v := values[i]
log.Printf("Setting value in store: %#v=%s\n", p, v)
err = t.Write(ctx, p, v)
if err != nil {
return err
}
}
return t.Commit(ctx, description)
}
type StringRefField struct {
path []string
value *string
defaultValue *string
version Version // version of last change
record *Record
}
// Set unconditionally sets the value of the key
func (f *StringRefField) Set(description string, value *string) error {
// TODO: maybe this should return Version, too?
ctx := context.Background()
p := append(f.record.path, f.path...)
log.Printf("Setting value in store: %#v=%#v\n", p, value)
t, err := NewTransaction(ctx, f.record.client, f.record.lookupB[0])
if err != nil {
return err
}
if value == nil {
err = t.Remove(ctx, p)
} else {
err = t.Write(ctx, p, *value)
}
if err != nil {
return err
}
return t.Commit(ctx, fmt.Sprintf("Unconditionally set %s: %s", f.path, description))
}
// Get retrieves the current value of the key
func (f *StringRefField) Get() (*string, Version) {
if f.value == nil {
return nil, f.version
}
raw := strings.TrimSpace(*f.value)
return &raw, f.version
}
// HasChanged returns true if the key has changed since the given version
func (f *StringRefField) HasChanged(version Version) bool {
return version < f.version
}
// StringRefField defines a string option which can be nil with a specified
// key and default value
func (f *Record) StringRefField(key string, value *string) *StringRefField {
path := strings.Split(key, "/")
field := &StringRefField{path: path, value: value, defaultValue: value, version: InitialVersion, record: f}
// If the value is not in the database, write the default Value.
err := f.fillInDefault(path, value)
if err != nil {
log.Println("Failed to write default value", key, "=", value)
}
fn := func(snaps []*Snapshot, version Version) {
ctx := context.Background()
var newValue *string
for _, snap := range snaps {
v, err := snap.Read(ctx, append(f.path, path...))
if err != nil {
if err != enoent {
log.Println("Failed to read key", key, "from directory snapshot", snap)
return
}
// if enoent then newValue == nil
} else {
newValue = &v
break
}
}
if (field.value == nil && newValue != nil) || (field.value != nil && newValue == nil) || (field.value != nil && newValue != nil && *field.value != *newValue) {
field.value = newValue
field.version = version
}
// Update the value in memory and in the state branch
t, err := NewTransaction(ctx, f.client, f.stateB)
if err != nil {
log.Fatalf("Failed to create transaction for updating state branch: %#v", err)
}
if newValue != nil {
if err = t.Write(ctx, append(f.path, path...), *newValue); err != nil {
log.Fatalf("Failed to write state %#v = %s: %#v", path, *newValue, err)
}
} else {
if err = t.Remove(ctx, append(f.path, path...)); err != nil {
log.Fatalf("Failed to remove state %#v: %#v", path, err)
}
}
if err = t.Commit(ctx, "Updating state branch"); err != nil {
log.Fatalf("Failed to commit transaction: %#v", err)
}
}
f.onUpdate = append(f.onUpdate, fn)
//fn(f.version)
f.fields = append(f.fields, field)
return field
}
type StringField struct {
raw *StringRefField
defaultString string
}
// Get retrieves the current value of the key
func (f *StringField) Get() (string, Version) {
if f.raw.value == nil {
log.Printf("Failed to find string in database at %s, defaulting to %s", strings.Join(f.raw.path, "/"), f.defaultString)
return f.defaultString, f.raw.version
}
return *f.raw.value, f.raw.version
}
// Set unconditionally sets the value of the key
func (f *StringField) Set(description string, value string) error {
return f.raw.Set(description, &value)
}
// HasChanged returns true if the key has changed since the given version
func (f *StringField) HasChanged(version Version) bool {
return version < f.raw.version
}
// StringField defines a string
func (f *Record) StringField(key string, value string) *StringField {
raw := f.StringRefField(key, &value)
return &StringField{raw: raw, defaultString: value}
}
type IntField struct {
raw *StringRefField
defaultInt int
}
// Get retrieves the current value of the key
func (f *IntField) Get() (int, Version) {
if f.raw.value == nil {
log.Printf("Key %s missing in database, defaulting value to %t", strings.Join(f.raw.path, "/"), f.defaultInt)
return f.defaultInt, f.raw.version
}
value64, err := strconv.ParseInt(strings.TrimSpace(*f.raw.value), 10, 0)
if err != nil {
// revert to default if we can't parse the result
log.Printf("Failed to parse int in database: '%s', defaulting to %d", f.raw.value, f.defaultInt)
return f.defaultInt, f.raw.version
}
return int(value64), f.raw.version
}
// HasChanged returns true if the key has changed since the given version
func (f *IntField) HasChanged(version Version) bool {
return version < f.raw.version
}
// IntField defines an boolean option with a specified key and default value
func (f *Record) IntField(key string, value int) *IntField {
stringValue := fmt.Sprintf("%d", value)
raw := f.StringRefField(key, &stringValue)
return &IntField{raw: raw, defaultInt: value}
}
type BoolField struct {
raw *StringRefField
defaultBool bool
}
// Get retrieves the current value of the key
func (f *BoolField) Get() (bool, Version) {
if f.raw.value == nil {
log.Printf("Key %s missing in database, defaulting value to %t", strings.Join(f.raw.path, "/"), f.defaultBool)
return f.defaultBool, f.raw.version
}
value, err := strconv.ParseBool(strings.TrimSpace(*f.raw.value))
if err != nil {
// revert to default if we can't parse the result
log.Printf("Failed to parse boolean in database: '%s', defaulting to %t", f.raw.value, f.defaultBool)
return f.defaultBool, f.raw.version
}
return value, f.raw.version
}
// HasChanged returns true if the key has changed since the given version
func (f *BoolField) HasChanged(version Version) bool {
return version < f.raw.version
}
// BoolField defines an boolean option with a specified key and default value
func (f *Record) BoolField(key string, value bool) *BoolField {
stringValue := fmt.Sprintf("%t", value)
raw := f.StringRefField(key, &stringValue)
return &BoolField{raw: raw, defaultBool: value}
}

View File

@ -0,0 +1,5 @@
/*
The datakit package contains common patterns over 9P, which avoids the need
for applications to use 9P directly.
*/
package datakit

View File

@ -0,0 +1,87 @@
package datakit
import (
"bytes"
"io"
"log"
"strings"
p9p "github.com/docker/go-p9p"
"context"
)
type SnapshotKind uint8
const (
COMMIT SnapshotKind = 0x01 // from a commit hash
OBJECT SnapshotKind = 0x02 // from an object hash
)
type snapshot struct {
client *Client
kind SnapshotKind
thing string
}
type Snapshot struct {
snapshot
}
// NewSnapshot opens a new snapshot referencing the given object.
func NewSnapshot(ctx context.Context, client *Client, kind SnapshotKind, thing string) *Snapshot {
return &Snapshot{snapshot{client: client, kind: kind, thing: thing}}
}
// Head retrieves the commit sha of the given branch
func Head(ctx context.Context, client *Client, fromBranch string) (string, error) {
// SHA=$(cat branch/<fromBranch>/head)
file, err := client.Open(ctx, p9p.ORDWR, "branch", fromBranch, "head")
if err != nil {
log.Println("Failed to open branch/", fromBranch, "/head")
return "", err
}
defer file.Close(ctx)
buf := make([]byte, 512)
n, err := file.Read(ctx, buf, 0)
if err != nil {
log.Println("Failed to Read branch", fromBranch, "head", err)
return "", err
}
return strings.TrimSpace(string(buf[0:n])), nil
}
func (s *Snapshot) getFullPath(path []string) []string {
var p []string
switch s.kind {
case COMMIT:
p = []string{"snapshots", s.thing, "ro"}
case OBJECT:
p = []string{"trees", s.thing}
}
for _, element := range path {
p = append(p, element)
}
return p
}
// Read reads a value from the snapshot
func (s *Snapshot) Read(ctx context.Context, path []string) (string, error) {
p := s.getFullPath(path)
file, err := s.client.Open(ctx, p9p.OREAD, p...)
if err != nil {
return "", err
}
defer file.Close(ctx)
reader := file.NewIOReader(ctx, 0)
buf := bytes.NewBuffer(nil)
io.Copy(buf, reader)
return string(buf.Bytes()), nil
}
// List returns filenames list in directory
func (s *Snapshot) List(ctx context.Context, path []string) ([]string, error) {
p := s.getFullPath(path)
return s.client.List(ctx, p)
}

View File

@ -0,0 +1,136 @@
package datakit
import (
"bytes"
"io"
"log"
"strconv"
"sync/atomic"
p9p "github.com/docker/go-p9p"
"context"
)
type transaction struct {
client *Client
fromBranch string
newBranch string
}
var nextTransaction = int64(0)
// NewTransaction opens a new transaction originating from fromBranch, named
// newBranch.
func NewTransaction(ctx context.Context, client *Client, fromBranch string) (*transaction, error) {
id := atomic.AddInt64(&nextTransaction, 1)
newBranch := strconv.FormatInt(id, 10)
err := client.Mkdir(ctx, "branch", fromBranch)
if err != nil {
log.Println("Failed to Create branch/", fromBranch, err)
return nil, err
}
err = client.Mkdir(ctx, "branch", fromBranch, "transactions", newBranch)
if err != nil {
log.Println("Failed to Create branch/", fromBranch, "/transactions/", newBranch, err)
return nil, err
}
return &transaction{client: client, fromBranch: fromBranch, newBranch: newBranch}, nil
}
func (t *transaction) close(ctx context.Context) {
// TODO: do we need to clear up unmerged branches?
}
// Abort ensures the update will not be committed.
func (t *transaction) Abort(ctx context.Context) {
t.close(ctx)
}
// Commit merges the newBranch back into the fromBranch, or fails
func (t *transaction) Commit(ctx context.Context, msg string) error {
// msg
msgPath := []string{"branch", t.fromBranch, "transactions", t.newBranch, "msg"}
msgFile, err := t.client.Open(ctx, p9p.ORDWR, msgPath...)
if err != nil {
log.Println("Failed to Open msg", err)
return err
}
defer msgFile.Close(ctx)
_, err = msgFile.Write(ctx, []byte(msg), 0)
if err != nil {
log.Println("Failed to Write msg", err)
}
// ctl
ctlPath := []string{"branch", t.fromBranch, "transactions", t.newBranch, "ctl"}
ctlFile, err := t.client.Open(ctx, p9p.ORDWR, ctlPath...)
if err != nil {
log.Println("Failed to Open ctl", err)
return err
}
defer ctlFile.Close(ctx)
_, err = ctlFile.Write(ctx, []byte("commit"), 0)
if err != nil {
log.Println("Failed to Write ctl", err)
return err
}
return nil
}
// Write updates a key=value pair within the transaction.
func (t *transaction) Write(ctx context.Context, path []string, value string) error {
p := []string{"branch", t.fromBranch, "transactions", t.newBranch, "rw"}
for _, dir := range path[0 : len(path)-1] {
p = append(p, dir)
}
err := t.client.Mkdir(ctx, p...)
if err != nil {
log.Println("Failed to Mkdir", p)
}
p = append(p, path[len(path)-1])
file, err := t.client.Create(ctx, p...)
if err != nil {
log.Println("Failed to Create", p)
return err
}
defer file.Close(ctx)
writer := file.NewIOWriter(ctx, 0)
_, err = writer.Write([]byte(value))
if err != nil {
log.Println("Failed to Write", path, "=", value, ":", err)
return err
}
return nil
}
// Read reads a key within the transaction.
func (t *transaction) Read(ctx context.Context, path []string) (string, error) {
p := []string{"branch", t.fromBranch, "transactions", t.newBranch, "rw"}
for _, dir := range path[0 : len(path)-1] {
p = append(p, dir)
}
file, err := t.client.Open(ctx, p9p.OREAD, p...)
if err != nil {
return "", err
}
defer file.Close(ctx)
reader := file.NewIOReader(ctx, 0)
buf := bytes.NewBuffer(nil)
io.Copy(buf, reader)
return string(buf.Bytes()), nil
}
// Remove deletes a key within the transaction.
func (t *transaction) Remove(ctx context.Context, path []string) error {
p := []string{"branch", t.fromBranch, "transactions", t.newBranch, "rw"}
for _, dir := range path {
p = append(p, dir)
}
err := t.client.Remove(ctx, p...)
if err != nil {
log.Println("Failed to Remove ", p)
}
return nil
}

View File

@ -0,0 +1,77 @@
package datakit
import (
"io"
"log"
"strings"
p9p "github.com/docker/go-p9p"
"context"
)
type watch struct {
client *Client
file *File
offset int64 // current offset within head.live file
}
type Watch struct {
watch
}
// NewWatch starts watching a path within a branch
func NewWatch(ctx context.Context, client *Client, fromBranch string, path []string) (*Watch, error) {
// SHA=$(cat branch/<fromBranch>/watch/<path.node>/tree.live)
p := []string{"branch", fromBranch, "watch"}
for _, dir := range path {
p = append(p, dir+".node")
}
p = append(p, "tree.live")
file, err := client.Open(ctx, p9p.OREAD, p...)
if err != nil {
log.Println("Failed to open", p, err)
return nil, err
}
offset := int64(0)
return &Watch{watch{client: client, file: file, offset: offset}}, nil
}
func (w *Watch) Next(ctx context.Context) (*Snapshot, error) {
buf := make([]byte, 512)
sawFlush := false
for {
// NOTE: irmin9p-direct will never return a fragment;
// we can rely on the buffer containing a whold number
// of lines.
n, err := w.file.Read(ctx, buf, w.offset)
if n == 0 {
// Two reads of "" in a row means end-of-file
if sawFlush {
return nil, io.EOF
} else {
sawFlush = true
continue
}
} else {
sawFlush = false
}
w.offset = w.offset + int64(n)
if err != nil {
log.Println("Failed to Read head.live", err)
return nil, io.EOF
}
lines := strings.Split(string(buf[0:n]), "\n")
// Use the last non-empty line
thing := ""
for _, line := range lines {
if line != "" {
thing = line
}
}
return NewSnapshot(ctx, w.client, OBJECT, thing), nil
}
}
func (w *Watch) Close(ctx context.Context) {
w.file.Close(ctx)
}

View File

@ -395,11 +395,7 @@ func CreateDiskImage(location string, sizeMB int) error {
}
defer f.Close()
buf := make([]byte, 1048676)
for i := 0; i < sizeMB; i++ {
f.Write(buf)
}
return nil
return f.Truncate(int64(sizeMB) * int64(1024) * int64(1024))
}
func intArrayToString(i []int, sep string) string {

View File

@ -4,5 +4,6 @@
#include <stdbool.h>
int bootrom_init(const char *bootrom_path);
const char *bootrom(void);
uint64_t bootrom_load(void);
bool bootrom_contains_gpa(uint64_t gpa);

View File

@ -0,0 +1,54 @@
/*-
* Copyright (c) 2015 Peter Grehan <grehan@freebsd.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#ifndef _FWCTL_H_
#define _FWCTL_H_
#include <xhyve/support/linker_set.h>
/*
* Linker set api for export of information to guest firmware via
* a sysctl-like OID interface
*/
struct ctl {
const char *c_oid;
const void *c_data;
const int c_len;
};
#define CTL_NODE(oid, data, len) \
static struct ctl __CONCAT(__ctl, __LINE__) = { \
oid, \
(data), \
(len), \
}; \
DATA_SET(ctl_set, __CONCAT(__ctl, __LINE__))
void fwctl_init(void);
#endif /* _FWCTL_H_ */

View File

@ -33,6 +33,7 @@
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/disk.h>
#include <sys/mount.h>
#include <assert.h>
#include <fcntl.h>
@ -53,6 +54,14 @@
#define BLOCKIF_SIG 0xb109b109
#ifndef ALIGNUP
#define ALIGNUP(x, a) (((x - 1) & ~(a - 1)) + a)
#endif
#ifndef ALIGNDOWN
#define ALIGNDOWN(x, a) (-(a) & (x))
#endif
/* xhyve: FIXME
*
* // #define BLOCKIF_NUMTHR 8
@ -101,6 +110,8 @@ struct blockif_ctxt {
off_t bc_size;
int bc_sectsz;
int bc_psectsz;
size_t bc_delete_alignment; /* f_bsize always a power of 2 */
void* bc_delete_zero_buf;
int bc_psectoff;
int bc_closing;
pthread_t bc_btid[BLOCKIF_NUMTHR];
@ -211,6 +222,7 @@ static int
block_delete(struct blockif_ctxt *bc, off_t offset, off_t len)
{
int ret = -1;
#ifdef __FreeBSD__
off_t arg[2] = { offset, len };
#endif
@ -221,22 +233,52 @@ block_delete(struct blockif_ctxt *bc, off_t offset, off_t len)
errno = EOPNOTSUPP;
else if (bc->bc_rdonly)
errno = EROFS;
if (bc->bc_fd >= 0) {
if (bc->bc_ischr) {
#ifdef __FreeBSD__
ret = ioctl(bc->bc_fd, DIOCGDELETE, arg);
#else
errno = EOPNOTSUPP;
#endif
} else
errno = EOPNOTSUPP;
#ifdef HAVE_OCAML_QCOW
} else if (bc->bc_mbh >= 0) {
if (bc->bc_mbh >= 0) {
ret = mirage_block_delete(bc->bc_mbh, offset, len);
goto out;
}
#endif
} else
abort();
#ifdef __FreeBSD__
if ((bc->bc_fd >= 0) && (bc->bc_ischr)) {
ret = ioctl(bc->bc_fd, DIOCGDELETE, arg);
errno = EOPNOTSUPP;
goto out;
}
#elif __APPLE__
if (bc->bc_fd >= 0) {
/* PUNCHHOLE lengths and offsets have to be aligned so explicitly write zeroes
into the unaligned parts at the beginning and the end. This wouldn't be necessary
if the host and guest had the same sector size */
assert (offset >= 0);
assert (len >= 0);
size_t fp_offset = (size_t) offset;
size_t fp_length = (size_t) len;
size_t aligned_offset = ALIGNUP(fp_offset, bc->bc_delete_alignment);
if (aligned_offset != fp_offset) {
size_t len_to_zero = aligned_offset - fp_offset;
assert(len_to_zero <= bc->bc_delete_alignment);
ssize_t written = pwrite(bc->bc_fd, bc->bc_delete_zero_buf, len_to_zero, (off_t) fp_offset);
if (written == -1) goto out;
fp_offset += len_to_zero;
fp_length -= len_to_zero;
}
size_t aligned_length = ALIGNDOWN(fp_length, bc->bc_delete_alignment);
if (aligned_length != fp_length) {
size_t len_to_zero = fp_length - aligned_length;
assert(len_to_zero <= bc->bc_delete_alignment);
fp_length -= len_to_zero;
ssize_t written = pwrite(bc->bc_fd, bc->bc_delete_zero_buf, len_to_zero, (off_t) (fp_offset + fp_length));
if (written == -1) goto out;
}
struct fpunchhole arg = { .fp_flags = 0, .reserved = 0, .fp_offset = (off_t) fp_offset, .fp_length = (off_t) fp_length };
ret = fcntl(bc->bc_fd, F_PUNCHHOLE, &arg);
goto out;
}
#endif
errno = EOPNOTSUPP;
out:
HYPERKIT_BLOCK_DELETE_DONE(offset, ret);
return ret;
}
@ -554,9 +596,12 @@ blockif_open(const char *optstr, const char *ident)
char *nopt, *xopts, *cp;
struct blockif_ctxt *bc;
struct stat sbuf;
struct statfs fsbuf;
// struct diocgattr_arg arg;
off_t size, psectsz, psectoff, blocks;
int extra, fd, i, sectsz;
size_t delete_alignment;
void *delete_zero_buf;
int nocache, sync, ro, candelete, geom, ssopt, pssopt;
mirage_block_handle mbh;
int use_mirage = 0;
@ -621,6 +666,8 @@ blockif_open(const char *optstr, const char *ident)
extra |= O_SYNC;
candelete = 0;
sectsz = DEV_BSIZE;
delete_alignment = DEV_BSIZE;
if (use_mirage) {
#ifdef HAVE_OCAML_QCOW
@ -655,7 +702,33 @@ blockif_open(const char *optstr, const char *ident)
perror("Could not stat backing file");
goto err;
}
if (fstatfs(fd, &fsbuf) < 0) {
perror("Could not stat backfile file filesystem");
goto err;
}
delete_alignment = (size_t)fsbuf.f_bsize;
#ifdef __APPLE__
{
/* Check to see whether we can use F_PUNCHHOLE on this file */
struct fpunchhole arg = { .fp_flags = 0, .reserved = 0, .fp_offset = 0, .fp_length = 0 };
if (fcntl(fd, F_PUNCHHOLE, &arg) == 0) {
/* Sparse files are supported: enable TRIM */
candelete = 1;
} else {
perror("fcntl(F_PUNCHHOLE) failed: host filesystem does not support sparse files");
candelete = 0;
}
}
#endif
}
/* delete_alignment is a power of 2 allowing us to use ALIGNDOWN for rounding */
assert((delete_alignment & (delete_alignment - 1)) == 0);
if ((delete_zero_buf = malloc(delete_alignment)) == NULL){
perror("Failed to allocate zeroed buffer for unaligned deletes");
goto err;
}
bzero(delete_zero_buf, delete_alignment);
/* One and only one handle */
assert(mbh >= 0 || fd >= 0);
@ -664,7 +737,6 @@ blockif_open(const char *optstr, const char *ident)
* Deal with raw devices
*/
size = sbuf.st_size;
sectsz = DEV_BSIZE;
psectsz = psectoff = 0;
geom = 0;
if (S_ISCHR(sbuf.st_mode)) {
@ -749,6 +821,8 @@ blockif_open(const char *optstr, const char *ident)
bc->bc_sectsz = sectsz;
bc->bc_psectsz = (int)psectsz;
bc->bc_psectoff = (int)psectoff;
bc->bc_delete_alignment = delete_alignment;
bc->bc_delete_zero_buf = delete_zero_buf;
pthread_mutex_init(&bc->bc_mtx, NULL);
pthread_cond_init(&bc->bc_cond, NULL);
TAILQ_INIT(&bc->bc_freeq);

View File

@ -0,0 +1,560 @@
/*-
* Copyright (c) 2015 Peter Grehan <grehan@freebsd.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
/*
* Guest firmware interface. Uses i/o ports x510/x511 as Qemu does,
* but with a request/response messaging protocol.
*/
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/uio.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "xhyve/inout.h"
#include "xhyve/xhyve.h"
#include "xhyve/support/misc.h"
#include "xhyve/fwctl.h"
/*
* Messaging protocol base operations
*/
#define OP_NULL 1
#define OP_ECHO 2
#define OP_GET 3
#define OP_GET_LEN 4
#define OP_SET 5
#define OP_MAX OP_SET
/* I/O ports */
#define FWCTL_OUT 0x510
#define FWCTL_IN 0x511
/*
* Back-end state-machine
*/
static enum state {
DORMANT,
IDENT_WAIT,
IDENT_SEND,
REQ,
RESP
} be_state = DORMANT;
static uint8_t sig[] = { 'B', 'H', 'Y', 'V' };
static u_int ident_idx;
struct op_info {
int op;
int (*op_start)(size_t len);
void (*op_data)(uint32_t data, size_t len);
int (*op_result)(struct iovec **data);
void (*op_done)(struct iovec *data);
};
static struct op_info *ops[OP_MAX+1];
/* Return 0-padded uint32_t */
static uint32_t
fwctl_send_rest(uint8_t *data, size_t len)
{
union {
uint8_t c[4];
uint32_t w;
} u;
size_t i;
u.w = 0;
for (i = 0, u.w = 0; i < len; i++)
u.c[i] = *data++;
return (u.w);
}
/*
* error op dummy proto - drop all data sent and return an error
*/
static int errop_code;
static void
errop_set(int err)
{
errop_code = err;
}
static int
errop_start(UNUSED size_t len)
{
errop_code = ENOENT;
/* accept any length */
return (errop_code);
}
static void
errop_data(UNUSED uint32_t data, UNUSED size_t len)
{
/* ignore */
}
static int
errop_result(struct iovec **data)
{
/* no data to send back; always successful */
*data = NULL;
return (errop_code);
}
static void
errop_done(UNUSED struct iovec *data)
{
/* assert data is NULL */
}
static struct op_info errop_info = {
.op_start = errop_start,
.op_data = errop_data,
.op_result = errop_result,
.op_done = errop_done
};
/* OID search */
SET_DECLARE(ctl_set, struct ctl);
CTL_NODE("hw.ncpu", &guest_ncpus, sizeof(guest_ncpus));
static struct ctl *
ctl_locate(const char *str, size_t maxlen)
{
struct ctl *cp, **cpp;
SET_FOREACH(cpp, ctl_set) {
cp = *cpp;
if (!strncmp(str, cp->c_oid, maxlen))
return (cp);
}
return (NULL);
}
/* uefi-sysctl get-len */
#define FGET_STRSZ 80
static struct iovec fget_biov[2];
static char fget_str[FGET_STRSZ];
static struct {
size_t f_sz;
uint32_t f_data[1024];
} fget_buf;
static size_t fget_cnt;
static size_t fget_size;
static int
fget_start(size_t len)
{
if (len > FGET_STRSZ)
return(E2BIG);
fget_cnt = 0;
return (0);
}
static void
fget_data(uint32_t data, UNUSED size_t len)
{
memcpy(&fget_str[fget_cnt], &data, sizeof(data));
fget_cnt += sizeof(uint32_t);
}
static int
fget_result(struct iovec **data, int val)
{
struct ctl *cp;
int err;
err = 0;
/* Locate the OID */
cp = ctl_locate(fget_str, fget_cnt);
if (cp == NULL) {
*data = NULL;
err = ENOENT;
} else {
if (val) {
/* For now, copy the len/data into a buffer */
memset(&fget_buf, 0, sizeof(fget_buf));
fget_buf.f_sz = (size_t)cp->c_len;
memcpy(fget_buf.f_data, cp->c_data, cp->c_len);
fget_biov[0].iov_base = (char *)&fget_buf;
fget_biov[0].iov_len = sizeof(fget_buf.f_sz) +
(size_t)cp->c_len;
} else {
fget_size = (size_t)cp->c_len;
fget_biov[0].iov_base = (char *)&fget_size;
fget_biov[0].iov_len = sizeof(fget_size);
}
fget_biov[1].iov_base = NULL;
fget_biov[1].iov_len = 0;
*data = fget_biov;
}
return (err);
}
static void
fget_done(UNUSED struct iovec *data)
{
/* nothing needs to be freed */
}
static int
fget_len_result(struct iovec **data)
{
return (fget_result(data, 0));
}
static int
fget_val_result(struct iovec **data)
{
return (fget_result(data, 1));
}
static struct op_info fgetlen_info = {
.op_start = fget_start,
.op_data = fget_data,
.op_result = fget_len_result,
.op_done = fget_done
};
static struct op_info fgetval_info = {
.op_start = fget_start,
.op_data = fget_data,
.op_result = fget_val_result,
.op_done = fget_done
};
static struct req_info {
int req_error;
u_int req_count;
uint32_t req_size;
uint32_t req_type;
uint32_t req_txid;
struct op_info *req_op;
int resp_error;
int resp_count;
int resp_size;
int resp_off;
struct iovec *resp_biov;
} rinfo;
static void
fwctl_response_done(void)
{
(*rinfo.req_op->op_done)(rinfo.resp_biov);
/* reinit the req data struct */
memset(&rinfo, 0, sizeof(rinfo));
}
static void
fwctl_request_done(void)
{
rinfo.resp_error = (*rinfo.req_op->op_result)(&rinfo.resp_biov);
/* XXX only a single vector supported at the moment */
rinfo.resp_off = 0;
if (rinfo.resp_biov == NULL) {
rinfo.resp_size = 0;
} else {
assert(rinfo.resp_biov[0].iov_len < INT_MAX);
rinfo.resp_size = (int)rinfo.resp_biov[0].iov_len;
}
}
static int
fwctl_request_start(void)
{
int err;
/* Data size doesn't include header */
rinfo.req_size -= 12;
rinfo.req_op = &errop_info;
if (rinfo.req_type <= OP_MAX && ops[rinfo.req_type] != NULL)
rinfo.req_op = ops[rinfo.req_type];
err = (*rinfo.req_op->op_start)(rinfo.req_size);
if (err) {
errop_set(err);
rinfo.req_op = &errop_info;
}
/* Catch case of zero-length message here */
if (rinfo.req_size == 0) {
fwctl_request_done();
return (1);
}
return (0);
}
static int
fwctl_request_data(uint32_t value)
{
size_t remlen;
/* Make sure remaining size is >= 0 */
rinfo.req_size -= sizeof(uint32_t);
remlen = MAX(rinfo.req_size, 0);
(*rinfo.req_op->op_data)(value, remlen);
if (rinfo.req_size < sizeof(uint32_t)) {
fwctl_request_done();
return (1);
}
return (0);
}
static int
fwctl_request(uint32_t value)
{
int ret;
ret = 0;
switch (rinfo.req_count) {
case 0:
/* Verify size */
if (value < 12) {
printf("msg size error");
exit(1);
}
rinfo.req_size = value;
rinfo.req_count = 1;
break;
case 1:
rinfo.req_type = value;
rinfo.req_count++;
break;
case 2:
rinfo.req_txid = value;
rinfo.req_count++;
ret = fwctl_request_start();
break;
default:
ret = fwctl_request_data(value);
break;
}
return (ret);
}
static int
fwctl_response(uint32_t *retval)
{
uint8_t *dp;
size_t remlen;
switch(rinfo.resp_count) {
case 0:
/* 4 x u32 header len + data */
*retval = 4*sizeof(uint32_t) +
roundup((uint32_t)rinfo.resp_size, sizeof(uint32_t));
rinfo.resp_count++;
break;
case 1:
*retval = rinfo.req_type;
rinfo.resp_count++;
break;
case 2:
*retval = rinfo.req_txid;
rinfo.resp_count++;
break;
case 3:
*retval = (uint32_t)rinfo.resp_error;
rinfo.resp_count++;
break;
default:
remlen = (size_t)rinfo.resp_size - (size_t)rinfo.resp_off;
dp = ((uint8_t *)rinfo.resp_biov->iov_base + rinfo.resp_off);
if (remlen >= sizeof(uint32_t)) {
memcpy(retval, dp, sizeof(*retval));
} else if (remlen > 0) {
*retval = fwctl_send_rest(dp, remlen);
}
rinfo.resp_off += sizeof(uint32_t);
break;
}
if (rinfo.resp_count > 3 &&
rinfo.resp_size - rinfo.resp_off <= 0) {
fwctl_response_done();
return (1);
}
return (0);
}
/*
* i/o port handling.
*/
static uint8_t
fwctl_inb(void)
{
uint8_t retval;
retval = 0xff;
switch (be_state) {
case DORMANT:
case IDENT_WAIT:
break;
case IDENT_SEND:
retval = sig[ident_idx++];
if (ident_idx >= sizeof(sig))
be_state = REQ;
break;
case REQ:
case RESP:
break;
}
return (retval);
}
static void
fwctl_outw(uint16_t val)
{
switch (be_state) {
case DORMANT:
break;
case IDENT_WAIT:
if (val == 0) {
be_state = IDENT_SEND;
ident_idx = 0;
}
break;
case IDENT_SEND:
case REQ:
case RESP:
break;
}
}
static uint32_t
fwctl_inl(void)
{
uint32_t retval;
switch (be_state) {
case DORMANT:
case IDENT_WAIT:
case IDENT_SEND:
case REQ:
retval = 0xffffffff;
break;
case RESP:
if (fwctl_response(&retval))
be_state = REQ;
break;
}
return (retval);
}
static void
fwctl_outl(uint32_t val)
{
switch (be_state) {
case DORMANT:
case IDENT_WAIT:
case IDENT_SEND:
break;
case REQ:
if (fwctl_request(val))
be_state = RESP;
case RESP:
break;
}
}
static int
fwctl_handler(UNUSED int vcpu, int in, UNUSED int port, int bytes,
uint32_t *eax, UNUSED void *arg)
{
if (in) {
if (bytes == 1)
*eax = fwctl_inb();
else if (bytes == 4)
*eax = fwctl_inl();
else
*eax = 0xffff;
} else {
if (bytes == 2)
fwctl_outw((uint16_t)*eax);
else if (bytes == 4)
fwctl_outl(*eax);
}
return (0);
}
INOUT_PORT(fwctl_wreg, FWCTL_OUT, IOPORT_F_INOUT, fwctl_handler);
INOUT_PORT(fwctl_rreg, FWCTL_IN, IOPORT_F_IN, fwctl_handler);
void
fwctl_init(void)
{
ops[OP_GET_LEN] = &fgetlen_info;
ops[OP_GET] = &fgetval_info;
be_state = IDENT_WAIT;
}

View File

@ -797,7 +797,15 @@ next:
done += 8;
if (elen == 0) {
if (done >= len) {
ahci_write_fis_d2h(p, slot, cfis, ATA_S_READY | ATA_S_DSC);
if (ncq) {
if (first)
ahci_write_fis_d2h_ncq(p, slot);
ahci_write_fis_sdb(p, slot, cfis,
ATA_S_READY | ATA_S_DSC);
} else {
ahci_write_fis_d2h(p, slot, cfis,
ATA_S_READY | ATA_S_DSC);
}
p->pending &= ~(1 << slot);
ahci_check_stopped(p);
if (!first)
@ -977,6 +985,7 @@ handle_identify(struct ahci_port *p, int slot, uint8_t *cfis)
buf[88] = 0x7f;
if (p->xfermode & ATA_UDMA0)
buf[88] |= (1 << ((p->xfermode & 7) + 8));
buf[93] = (1 | 1 <<14);
buf[100] = (uint16_t) sectors;
buf[101] = (uint16_t) (sectors >> 16);
buf[102] = (uint16_t) (sectors >> 32);
@ -1725,7 +1734,7 @@ ahci_handle_cmd(struct ahci_port *p, int slot, uint8_t *cfis)
case ATA_SEND_FPDMA_QUEUED:
if ((cfis[13] & 0x1f) == ATA_SFPDMA_DSM &&
cfis[17] == 0 && cfis[16] == ATA_DSM_TRIM &&
cfis[11] == 0 && cfis[13] == 1) {
cfis[11] == 0 && cfis[3] == 1) {
ahci_handle_dsm_trim(p, slot, cfis, 0);
break;
}

191
src/cmd/linuxkit/vendor/github.com/moby/vpnkit/LICENSE generated vendored Normal file
View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2013-2016 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,78 @@
VPN-friendly networking devices for [HyperKit](https://github.com/moby/hyperkit)
===============================
[![Build Status (OSX)](https://circleci.com/gh/moby/vpnkit.png)](https://circleci.com/gh/moby/vpnkit)
Binary artefacts are built by CI:
- [MacOS](https://circleci.com/gh/moby/vpnkit)
- [Windows](https://ci.appveyor.com/project/moby/vpnkit/history)
![VPNKit diagram](http://moby.github.io/vpnkit/vpnkit.png)
VPNKit is a set of tools and services for helping [HyperKit](https://github.com/moby/hyperkit)
VMs interoperate with host VPN configurations.
Building on Unix
----------------
First install `wget`, `opam` using your package manager of choice.
Build all the dependencies and the program itself with:
```
cd [path to vpnkit source]
opam remote add vpnkit ./repo/darwin
opam install --deps-only vpnkit
make
```
When the build succeeds the `vpnkit` binary should be available in the current path.
Running with hyperkit
---------------------
First ask `vpnkit` to listen for ethernet connections on a local Unix domain socket:
```
vpnkit --ethernet /tmp/ethernet --debug
```
Next ask [com.docker.hyperkit](https://github.com/moby/hyperkit) to connect a NIC to this
socket by adding a command-line option like `-s 2:0,virtio-vpnkit,path=/tmp/ethernet`. Note:
you may need to change the slot `2:0` to a free slot in your VM configuration.
Why is this needed?
-------------------
Running a VM usually involves modifying the network configuration on the host, for example
by activating Ethernet bridges, new routing table entries, DNS and firewall/NAT configurations.
Activating a VPN involves modifying the same routing tables, DNS and firewall/NAT configurations
and therefore there can be a clash -- this often results in the network connection to the VM
being disconnected.
VPNKit, part of [HyperKit](https://github.com/moby/hyperkit)
attempts to work nicely with VPN software by intercepting the VM traffic at the Ethernet level,
parsing and understanding protocols like NTP, DNS, UDP, TCP and doing the "right thing" with
respect to the host's VPN configuration.
VPNKit operates by reconstructing Ethernet traffic from the VM and translating it into the
relevant socket API calls on OSX or Windows. This allows the host application to generate
traffic without requiring low-level Ethernet bridging support.
Design
------
- [Using vpnkit as a default gateway](docs/ethernet.md): describes the flow of ethernet traffic to/from the VM
- [Port forwarding](docs/ports.md): describes how ports are forwarded from the host into the VM
- [Experimental transparent HTTP proxy](docs/transparent-http-proxy.md): describes the
experimental support for transparent HTTP(S) proxying
Licensing
---------
VPNKit is licensed under the Apache License, Version 2.0. See
[LICENSE](https://github.com/moby/vpnkit/blob/master/LICENSE.md) for the full
license text.
Contributions are welcome under the terms of this license. You may wish to browse
the [weekly reports](reports) to read about overall activity in the repository.

View File

@ -0,0 +1,248 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <syslog.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <sys/wait.h>
#include "hvsock.h"
#define NONE 0
#define LISTEN 1
#define CONNECT 2
int mode = NONE;
char *default_sid = "C378280D-DA14-42C8-A24E-0DE92A1028E2";
char *mount = "/bin/mount";
void fatal(const char *msg)
{
syslog(LOG_CRIT, "%s Error: %d. %s", msg, errno, strerror(errno));
exit(1);
}
static int handle(int fd, char *tag, char *path)
{
char *options = NULL;
int status;
pid_t pid;
int res;
res = asprintf(&options,
"trans=fd,dfltuid=1001,dfltgid=50,version=9p2000,msize=4096,rfdno=%d,wfdno=%d",
fd, fd);
if (res < 0)
fatal("asprintf()");
char *argv[] = {
mount,
"-t", "9p", "-o", options,
tag, path,
NULL
};
pid = fork();
if (pid == 0) {
execv(mount, argv);
fatal("execv()");
}
res = waitpid(pid, &status, 0);
if (res == -1) {
syslog(LOG_CRIT,
"waitpid failed: %d. %s", errno, strerror(errno));
exit(1);
}
return WEXITSTATUS(status);
}
static int create_listening_socket(GUID serviceid)
{
SOCKADDR_HV sa;
int lsock;
int res;
lsock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (lsock == -1)
fatal("socket()");
sa.Family = AF_HYPERV;
sa.Reserved = 0;
sa.VmId = HV_GUID_WILDCARD;
sa.ServiceId = serviceid;
res = bind(lsock, (const struct sockaddr *)&sa, sizeof(sa));
if (res == -1)
fatal("bind()");
res = listen(lsock, 1);
if (res == -1)
fatal("listen()");
return lsock;
}
static int connect_socket(GUID serviceid)
{
SOCKADDR_HV sa;
int sock;
int res;
sock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (sock == -1)
fatal("socket()");
sa.Family = AF_HYPERV;
sa.Reserved = 0;
sa.VmId = HV_GUID_PARENT;
sa.ServiceId = serviceid;
res = connect(sock, (const struct sockaddr *)&sa, sizeof(sa));
if (res == -1)
fatal("connect()");
return sock;
}
static int accept_socket(int lsock)
{
SOCKADDR_HV sac;
socklen_t socklen = sizeof(sac);
int csock;
csock = accept(lsock, (struct sockaddr *)&sac, &socklen);
if (csock == -1)
fatal("accept()");
syslog(LOG_INFO, "Connect from: " GUID_FMT ":" GUID_FMT "\n",
GUID_ARGS(sac.VmId), GUID_ARGS(sac.ServiceId));
return csock;
}
void usage(char *name)
{
printf("%s: mount a 9P filesystem from an hvsock connection\n", name);
printf("usage:\n");
printf("\t[--serviceid <guid>] <listen | connect> <tag> <path>\n");
printf("where\n");
printf("\t--serviceid <guid>: use <guid> as the well-known service GUID\n");
printf("\t (defaults to %s)\n", default_sid);
printf("\t--listen: listen forever for incoming AF_HVSOCK connections\n");
printf("\t--connect: connect to the parent partition\n");
}
int main(int argc, char **argv)
{
int res = 0;
GUID sid;
int c;
/* Defaults to a testing GUID */
char *serviceid = default_sid;
char *tag = NULL;
char *path = NULL;
opterr = 0;
while (1) {
static struct option long_options[] = {
/* These options set a flag. */
{"serviceid", required_argument, NULL, 's'},
{0, 0, 0, 0}
};
int option_index = 0;
c = getopt_long(argc, argv, "s:", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 's':
serviceid = optarg;
break;
case 0:
break;
default:
usage(argv[0]);
exit(1);
}
}
if (optind < argc) {
if (strcmp(argv[optind], "listen") == 0)
mode = LISTEN;
else if (strcmp(argv[optind], "connect") == 0)
mode = CONNECT;
optind++;
}
if (mode == NONE) {
fprintf(stderr, "Please supply either listen or connect\n");
usage(argv[0]);
exit(1);
}
if (optind < argc)
tag = argv[optind++];
if (optind < argc)
path = argv[optind++];
if (!tag) {
fprintf(stderr, "Please supply a tag name\n");
usage(argv[0]);
exit(1);
}
if (!path) {
fprintf(stderr, "Please supply a path\n");
usage(argv[0]);
exit(1);
}
res = parseguid(serviceid, &sid);
if (res) {
fprintf(stderr,
"Failed to parse serviceid as GUID: %s\n", serviceid);
usage(argv[0]);
exit(1);
}
openlog(argv[0], LOG_CONS | LOG_NDELAY | LOG_PERROR, LOG_DAEMON);
for (;;) {
int lsocket;
int sock;
int r;
if (mode == LISTEN) {
syslog(LOG_INFO, "starting in listening mode with serviceid=%s, tag=%s, path=%s", serviceid, tag, path);
lsocket = create_listening_socket(sid);
sock = accept_socket(lsocket);
close(lsocket);
} else {
syslog(LOG_INFO, "starting in connect mode with serviceid=%s, tag=%s, path=%s", serviceid, tag, path);
sock = connect_socket(sid);
}
r = handle(sock, tag, path);
close(sock);
if (r == 0) {
syslog(LOG_INFO, "mount successful for serviceid=%s tag=%s path=%s", serviceid, tag, path);
exit(0);
}
/*
* This can happen if the client times out the connection
* after we accept it
*/
syslog(LOG_CRIT, "mount failed with %d for serviceid=%s tag=%s path=%s", r, serviceid, tag, path);
sleep(1); /* retry */
}
}

View File

@ -0,0 +1,37 @@
#include <stdio.h>
#include "hvsock.h"
int parseguid(const char *s, GUID *g)
{
int res;
int p0, p1, p2, p3, p4, p5, p6, p7;
res = sscanf(s, GUID_FMT,
&g->Data1, &g->Data2, &g->Data3,
&p0, &p1, &p2, &p3, &p4, &p5, &p6, &p7);
if (res != 11)
return 1;
g->Data4[0] = p0;
g->Data4[1] = p1;
g->Data4[2] = p2;
g->Data4[3] = p3;
g->Data4[4] = p4;
g->Data4[5] = p5;
g->Data4[6] = p6;
g->Data4[7] = p7;
return 0;
}
DEFINE_GUID(HV_GUID_ZERO,
0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
DEFINE_GUID(HV_GUID_BROADCAST,
0xFFFFFFFF, 0xFFFF, 0xFFFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
DEFINE_GUID(HV_GUID_WILDCARD,
0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
DEFINE_GUID(HV_GUID_CHILDREN,
0x90db8b89, 0x0d35, 0x4f79, 0x8c, 0xe9, 0x49, 0xea, 0x0a, 0xc8, 0xb7, 0xcd);
DEFINE_GUID(HV_GUID_LOOPBACK,
0xe0e16197, 0xdd56, 0x4a10, 0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38);
DEFINE_GUID(HV_GUID_PARENT,
0xa42e7cda, 0xd03f, 0x480c, 0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78);

View File

@ -0,0 +1,48 @@
/* AF_HYPERV definitions and utilities */
#include <errno.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/socket.h>
/* GUID handling */
typedef struct _GUID {
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} GUID;
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
const GUID name = {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}}
/* Helper macros for parsing/printing GUIDs */
#define GUID_FMT "%08x-%04hx-%04hx-%02x%02x-%02x%02x%02x%02x%02x%02x"
#define GUID_ARGS(_g) \
(_g).Data1, (_g).Data2, (_g).Data3, \
(_g).Data4[0], (_g).Data4[1], (_g).Data4[2], (_g).Data4[3], \
(_g).Data4[4], (_g).Data4[5], (_g).Data4[6], (_g).Data4[7]
#define GUID_SARGS(_g) \
&(_g).Data1, &(_g).Data2, &(_g).Data3, \
&(_g).Data4[0], &(_g).Data4[1], &(_g).Data4[2], &(_g).Data4[3], \
&(_g).Data4[4], &(_g).Data4[5], &(_g).Data4[6], &(_g).Data4[7]
extern int parseguid(const char *s, GUID *g);
/* HV Socket definitions */
#define AF_HYPERV 43
#define HV_PROTOCOL_RAW 1
typedef struct _SOCKADDR_HV {
unsigned short Family;
unsigned short Reserved;
GUID VmId;
GUID ServiceId;
} SOCKADDR_HV;
extern const GUID HV_GUID_ZERO;
extern const GUID HV_GUID_BROADCAST;
extern const GUID HV_GUID_WILDCARD;
extern const GUID HV_GUID_CHILDREN;
extern const GUID HV_GUID_LOOPBACK;
extern const GUID HV_GUID_PARENT;

View File

@ -0,0 +1,40 @@
#include <stdio.h>
#include "hvsock.h"
int parseguid(const char *s, GUID *g)
{
int res;
int p0, p1, p2, p3, p4, p5, p6, p7;
res = sscanf(s, GUID_FMT,
&g->Data1, &g->Data2, &g->Data3,
&p0, &p1, &p2, &p3, &p4, &p5, &p6, &p7);
if (res != 11)
return 1;
g->Data4[0] = p0;
g->Data4[1] = p1;
g->Data4[2] = p2;
g->Data4[3] = p3;
g->Data4[4] = p4;
g->Data4[5] = p5;
g->Data4[6] = p6;
g->Data4[7] = p7;
return 0;
}
DEFINE_GUID(HV_GUID_ZERO,
0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
DEFINE_GUID(HV_GUID_BROADCAST,
0xFFFFFFFF, 0xFFFF, 0xFFFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
DEFINE_GUID(HV_GUID_WILDCARD,
0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
DEFINE_GUID(HV_GUID_CHILDREN,
0x90db8b89, 0x0d35, 0x4f79, 0x8c, 0xe9, 0x49, 0xea, 0x0a, 0xc8, 0xb7, 0xcd);
DEFINE_GUID(HV_GUID_LOOPBACK,
0xe0e16197, 0xdd56, 0x4a10, 0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38);
DEFINE_GUID(HV_GUID_PARENT,
0xa42e7cda, 0xd03f, 0x480c, 0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78);

View File

@ -0,0 +1,48 @@
/* AF_HYPERV definitions and utilities */
#include <errno.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/socket.h>
/* GUID handling */
typedef struct _GUID {
uint32_t Data1;
uint16_t Data2;
uint16_t Data3;
uint8_t Data4[8];
} GUID;
#define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
const GUID name = {l, w1, w2, {b1, b2, b3, b4, b5, b6, b7, b8}}
/* Helper macros for parsing/printing GUIDs */
#define GUID_FMT "%08x-%04hx-%04hx-%02x%02x-%02x%02x%02x%02x%02x%02x"
#define GUID_ARGS(_g) \
(_g).Data1, (_g).Data2, (_g).Data3, \
(_g).Data4[0], (_g).Data4[1], (_g).Data4[2], (_g).Data4[3], \
(_g).Data4[4], (_g).Data4[5], (_g).Data4[6], (_g).Data4[7]
#define GUID_SARGS(_g) \
&(_g).Data1, &(_g).Data2, &(_g).Data3, \
&(_g).Data4[0], &(_g).Data4[1], &(_g).Data4[2], &(_g).Data4[3], \
&(_g).Data4[4], &(_g).Data4[5], &(_g).Data4[6], &(_g).Data4[7]
extern int parseguid(const char *s, GUID *g);
/* HV Socket definitions */
#define AF_HYPERV 43
#define HV_PROTOCOL_RAW 1
typedef struct _SOCKADDR_HV {
unsigned short Family;
unsigned short Reserved;
GUID VmId;
GUID ServiceId;
} SOCKADDR_HV;
extern const GUID HV_GUID_ZERO;
extern const GUID HV_GUID_BROADCAST;
extern const GUID HV_GUID_WILDCARD;
extern const GUID HV_GUID_CHILDREN;
extern const GUID HV_GUID_LOOPBACK;
extern const GUID HV_GUID_PARENT;

View File

@ -0,0 +1,225 @@
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <syslog.h>
#include "protocol.h"
/* Version 0 of the protocol used this */
char expected_hello_old[5] = {'V', 'M', 'N', 'E', 'T'};
/* Version 1 and later of the protocol used this */
char expected_hello[5] = {'V', 'M', 'N', '3', 'T'};
int really_read(int fd, uint8_t *buffer, size_t total)
{
size_t remaining = total;
ssize_t n;
while (remaining > 0) {
n = read(fd, buffer, remaining);
if (n == 0) {
syslog(LOG_CRIT, "EOF reading from socket: closing\n");
goto err;
}
if (n < 0) {
syslog(LOG_CRIT,
"Failure reading from socket: closing: %s",
strerror(errno));
goto err;
}
remaining -= (size_t) n;
buffer = buffer + n;
}
return 0;
err:
/*
* On error: stop reading from the socket and trigger a clean
* shutdown
*/
shutdown(fd, SHUT_RD);
return -1;
}
int really_write(int fd, uint8_t *buffer, size_t total)
{
size_t remaining = total;
ssize_t n;
while (remaining > 0) {
n = write(fd, buffer, remaining);
if (n == 0) {
syslog(LOG_CRIT, "EOF writing to socket: closing");
goto err;
}
if (n < 0) {
syslog(LOG_CRIT,
"Failure writing to socket: closing: %s",
strerror(errno));
goto err;
}
remaining -= (size_t) n;
buffer = buffer + n;
}
return 0;
err:
/* On error: stop listening to the socket */
shutdown(fd, SHUT_WR);
return -1;
}
struct init_message *create_init_message()
{
struct init_message *m;
m = malloc(sizeof(struct init_message));
if (!m)
return NULL;
bzero(m, sizeof(struct init_message));
memcpy(&m->hello[0], &expected_hello[0], sizeof(m->hello));
m->version = CURRENT_VERSION;
memset(&m->commit[0], 0, sizeof(m->commit));
return m;
}
char *print_init_message(struct init_message *m)
{
char tmp[41];
memcpy(&tmp[0], &m->commit[0], 40);
tmp[40] = '\000';
char *buffer;
int n;
buffer = malloc(80);
if (!buffer)
return NULL;
n = snprintf(buffer, 80, "version %d, commit %s", m->version, tmp);
if (n < 0) {
perror("Failed to format init_message");
exit(1);
}
return buffer;
}
int read_init_message(int fd, struct init_message *ci)
{
int res;
bzero(ci, sizeof(struct init_message));
res = really_read(fd, (uint8_t *)&ci->hello[0], sizeof(ci->hello));
if (res == -1) {
syslog(LOG_CRIT, "Failed to read hello from client");
return -1;
}
res = memcmp(&ci->hello[0],
&expected_hello_old[0], sizeof(expected_hello_old));
if (res == 0) {
ci->version = 0;
return 0;
}
res = memcmp(&ci->hello[0],
&expected_hello[0], sizeof(expected_hello));
if (res != 0) {
syslog(LOG_CRIT, "Failed to read header magic from client");
return -1;
}
res = really_read(fd, (uint8_t *)&ci->version, sizeof(ci->version));
if (res == -1) {
syslog(LOG_CRIT, "Failed to read header version from client");
return -1;
}
res = really_read(fd, (uint8_t *)&ci->commit[0], sizeof(ci->commit));
if (res == -1) {
syslog(LOG_CRIT, "Failed to read header hash from client");
return -1;
}
return 0;
}
int write_init_message(int fd, struct init_message *ci)
{
int res;
res = really_write(fd, (uint8_t *)&ci->hello[0], sizeof(ci->hello));
if (res == -1) {
syslog(LOG_CRIT, "Failed to write hello to client");
return -1;
}
if (ci->version > 0) {
res = really_write(fd, (uint8_t *)&ci->version,
sizeof(ci->version));
if (res == -1) {
syslog(LOG_CRIT, "Failed to write version to client");
return -1;
}
res = really_write(fd, (uint8_t *)&ci->commit[0],
sizeof(ci->commit));
if (res == -1) {
syslog(LOG_CRIT,
"Failed to write header hash to client");
return -1;
}
}
return 0;
}
int read_vif_response(int fd, struct vif_info *vif)
{
struct msg_response msg;
if (really_read(fd, (uint8_t*)&msg, sizeof(msg)) == -1) {
syslog(LOG_CRIT, "Client failed to read server response");
return -1;
}
switch (msg.response_type) {
case rt_vif:
memcpy((uint8_t*)vif, (uint8_t*)&msg.vif, sizeof(*vif));
return 0;
case rt_disconnect:
syslog(LOG_CRIT, "Server disconnected: %*s", msg.disconnect.len, msg.disconnect.msg);
return -1;
default:
syslog(LOG_CRIT, "Unknown response type from server");
return -1;
}
}
int write_command(int fd, enum command *c)
{
uint8_t command = *c;
if (really_write(fd, (uint8_t *)&command, sizeof(command)) == -1) {
syslog(LOG_CRIT, "Failed to write command to client");
return -1;
}
return 0;
}
int write_ethernet_args(int fd, struct ethernet_args *args)
{
uint8_t buffer[40];
memset(&buffer[0], 0, sizeof(buffer));
memcpy(&buffer[0], (uint8_t *)&args->uuid_string[0], 36);
if (really_write(fd, (uint8_t *)&buffer, sizeof(buffer)) == -1) {
syslog(LOG_CRIT, "Failed to write ethernet args to client");
return -1;
}
return 0;
}

View File

@ -0,0 +1,77 @@
#ifndef _VMNET_PROTOCOL_H_
#define _VMNET_PROTOCOL_H_
#include <errno.h>
#include <stdint.h>
/* Client -> Server init_message */
/* Server -> Client init_message */
struct init_message {
char hello[5];
uint8_t _padding[3];
uint32_t version;
char commit[40]; /* git sha of the compiled commit */
};
/*
* This should be bumped whenever we add something (like a feature or a
* bugfix) and we wish the UI to be able to detect when to trigger a
* reinstall.
*/
#define CURRENT_VERSION 22
extern struct init_message *create_init_message(void);
extern int read_init_message(int fd, struct init_message *ci);
extern int write_init_message(int fd, struct init_message *ci);
extern char *print_init_message(struct init_message *m);
/* Client -> Server command */
enum command {
ethernet = 1,
};
/* Server -> Client response */
enum response_type {
rt_vif = 1,
rt_disconnect = 2,
};
extern int write_command(int fd, enum command *c);
/* Client -> Server command arguments */
struct ethernet_args {
char uuid_string[36];
};
extern int write_ethernet_args(int fd, struct ethernet_args *args);
/* Server -> Client: details of a vif */
struct vif_info {
uint16_t mtu;
uint16_t max_packet_size;
uint8_t mac[6];
} __attribute__((packed));
/* Server -> Client: disconnect w/reason */
struct disconnect_reason {
uint8_t len;
char msg[256];
} __attribute__((packed));
struct msg_response {
uint8_t response_type;
union {
struct vif_info vif;
struct disconnect_reason disconnect;
};
} __attribute__((packed));
extern int read_vif_response(int fd, struct vif_info *vif);
extern char expected_hello[5];
extern char expected_hello_old[5];
extern int really_read(int fd, uint8_t *buffer, size_t total);
extern int really_write(int fd, uint8_t *buffer, size_t total);
#endif /* _VMNET_PROTOCOL_H_ */

View File

@ -0,0 +1,269 @@
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/uio.h>
#include <errno.h>
#include <assert.h>
#include <stdio.h>
#include "ring.h"
extern void fatal(const char *msg);
/* A fixed-size circular buffer.
The producer and consumer are positive integers from 0 to 2 * size-1.
Adds are modulo 2 * size. This effectively uses one bit to distinguish
the case where the buffer is empty (consumer == producer) from the case
where the buffer is full (consumer + size == producer). */
struct ring {
int producer; /* Next sequence number to be written */
int consumer; /* Next sequence number to be read */
int last; /* Sequence number of end of stream or -1 */
int size; /* Maximum number of buffered bytes */
pthread_cond_t c;
pthread_mutex_t m;
char *data;
};
struct ring *ring_allocate(int size)
{
struct ring *ring = (struct ring*)malloc(sizeof(struct ring));
if (!ring) {
fatal("Failed to allocate ring buffer metadata");
}
ring->data = (char*)malloc(size);
if (!ring->data) {
fatal("Failed to allocate ring buffer data");
}
int err = 0;
if ((err = pthread_cond_init(&ring->c, NULL)) != 0) {
errno = err;
fatal("Failed to create condition variable");
}
if ((err = pthread_mutex_init(&ring->m, NULL)) != 0) {
errno = err;
fatal("Failed to create mutex");
}
ring->size = size;
ring->producer = ring->consumer = 0;
ring->last = -1;
return ring;
}
#define RING_DATA_AVAILABLE(r) \
((r->producer >= r->consumer) ? \
(r->producer - r->consumer) : \
(2 * r->size + r->producer - r->consumer))
#define RING_FREE_REQUESTS(r) (r->size - RING_DATA_AVAILABLE(r))
#define RING_GET(r, seq) (&(r->data[seq % r->size]))
/* Signal that new data is been produced */
void ring_producer_advance(struct ring *ring, int n)
{
int err = 0;
assert(n >= 0);
if ((err = pthread_mutex_lock(&ring->m)) != 0) {
errno = err;
fatal("Failed to lock mutex");
}
ring->producer = (ring->producer + n) % (2 * ring->size);
if ((err = pthread_cond_broadcast(&ring->c)) != 0) {
errno = err;
fatal("Failed to signal condition variable");
}
if ((err = pthread_mutex_unlock(&ring->m)) != 0) {
errno = err;
fatal("Failed to unlock mutex");
}
return;
}
/* Signal that data has been consumed */
void ring_consumer_advance(struct ring *ring, int n)
{
int err = 0;
assert(n >= 0);
if ((err = pthread_mutex_lock(&ring->m)) != 0) {
errno = err;
fatal("Failed to lock mutex");
}
ring->consumer = (ring->consumer + n) % (2 * ring->size);
if ((err = pthread_cond_broadcast(&ring->c)) != 0) {
errno = err;
fatal("Failed to signal condition variable");
}
if ((err = pthread_mutex_unlock(&ring->m)) != 0) {
errno = err;
fatal("Failed to unlock mutex");
}
return;
}
/* The producer sends Eof */
void ring_producer_eof(struct ring *ring)
{
int err = 0;
if ((err = pthread_mutex_lock(&ring->m)) != 0) {
errno = err;
fatal("Failed to lock mutex");
}
ring->last = ring->producer - 1;
if ((err = pthread_cond_broadcast(&ring->c)) != 0) {
errno = err;
fatal("Failed to signal condition variable");
}
if ((err = pthread_mutex_unlock(&ring->m)) != 0) {
errno = err;
fatal("Failed to unlock mutex");
}
return;
}
/* Wait for n bytes to become available. If the ring has shutdown, return
non-zero. If data is available then return zero and fill in the first
iovec_len entries of the iovec. */
int ring_producer_wait_available(
struct ring *ring, size_t n, struct iovec *iovec, int *iovec_len
) {
int ret = 1;
int err = 0;
if ((err = pthread_mutex_lock(&ring->m)) != 0) {
errno = err;
fatal("Failed to lock mutex");
}
while ((RING_FREE_REQUESTS(ring) < n) && (ring->last == -1)) {
if ((err = pthread_cond_wait(&ring->c, &ring->m)) != 0) {
errno = err;
fatal("Failed to wait on condition variable");
}
}
if (ring->last != -1) {
goto out;
}
char *producer = RING_GET(ring, ring->producer);
char *consumer = RING_GET(ring, ring->consumer);
assert (producer >= RING_GET(ring, 0));
assert (producer <= RING_GET(ring, ring->size-1));
assert (consumer >= RING_GET(ring, 0));
assert (consumer <= RING_GET(ring, ring->size-1));
if (*iovec_len <= 0) {
ret = 0;
fprintf(stderr, "no iovecs\n");
goto out;
}
if (consumer > producer) {
/* producer has not wrapped around the buffer yet */
iovec[0].iov_base = producer;
iovec[0].iov_len = consumer - producer;
assert(iovec[0].iov_len > 0);
*iovec_len = 1;
ret = 0;
goto out;
}
/* consumer has wrapped around, so the first chunk is from the producer to
the end of the buffer */
iovec[0].iov_base = producer;
iovec[0].iov_len = ring->size - (int) (producer - RING_GET(ring, 0));
assert(iovec[0].iov_len > 0);
if (*iovec_len == 1) {
ret = 0;
goto out;
}
*iovec_len = 1;
/* also include the chunk from the beginning of the buffer to the consumer */
iovec[1].iov_base = RING_GET(ring, 0);
iovec[1].iov_len = consumer - RING_GET(ring, 0);
if (iovec[1].iov_len > 0) {
/* ... but don't bother if it's zero */
*iovec_len = 2;
}
ret = 0;
out:
if ((err = pthread_mutex_unlock(&ring->m)) != 0) {
errno = err;
fatal("Failed to unlock mutex");
}
if (ret == 0) {
for (int i = 0; i < *iovec_len; i++) {
assert(iovec[i].iov_base >= (void*)RING_GET(ring, 0));
assert(iovec[i].iov_base + iovec[i].iov_len - 1 <= (void*)RING_GET(ring, ring->size - 1));
}
}
return ret;
}
/* Wait for n bytes to become available. If the ring has shutdown, return
non-zero. If data is available then return zero and fill in the first
iovec_len entries of the iovec. */
int ring_consumer_wait_available(
struct ring *ring, size_t n, struct iovec *iovec, int *iovec_len
) {
int ret = 1;
int err = 0;
if ((err = pthread_mutex_lock(&ring->m)) != 0) {
errno = err;
fatal("Failed to lock mutex");
}
while ((RING_DATA_AVAILABLE(ring) < n) && (ring->last == -1)) {
if ((err = pthread_cond_wait(&ring->c, &ring->m)) != 0) {
errno = err;
fatal("Failed to wait on condition variable");
}
}
if (ring->last != -1) {
goto out;
}
char *producer = RING_GET(ring, ring->producer);
char *consumer = RING_GET(ring, ring->consumer);
assert (producer >= RING_GET(ring, 0));
assert (producer <= RING_GET(ring, ring->size-1));
assert (consumer >= RING_GET(ring, 0));
assert (consumer <= RING_GET(ring, ring->size-1));
if (*iovec_len <= 0) {
ret = 0;
goto out;
}
if (producer > consumer) {
/* producer has not wrapped around the buffer yet */
iovec[0].iov_base = consumer;
iovec[0].iov_len = producer - consumer;
assert(iovec[0].iov_len > 0);
*iovec_len = 1;
ret = 0;
goto out;
}
/* producer has wrapped around, so the first chunk is from the consumer to
the end of the buffer */
iovec[0].iov_base = consumer;
iovec[0].iov_len = ring->size - (int) (consumer - RING_GET(ring, 0));
assert(iovec[0].iov_len > 0);
if (*iovec_len == 1) {
ret = 0;
goto out;
}
*iovec_len = 1;
/* also include the chunk from the beginning of the buffer to the producer */
iovec[1].iov_base = RING_GET(ring, 0);
iovec[1].iov_len = producer - RING_GET(ring, 0);
if (iovec[1].iov_len > 0) {
/* ... but don't bother if its zero */
*iovec_len = 2;
}
ret = 0;
out:
if ((err = pthread_mutex_unlock(&ring->m)) != 0) {
errno = err;
fatal("Failed to unlock mutex");
}
if (ret == 0) {
for (int i = 0; i < *iovec_len; i++) {
assert(iovec[i].iov_base >= (void*)RING_GET(ring, 0));
assert(iovec[i].iov_base + iovec[i].iov_len - 1 <= (void*)RING_GET(ring, ring->size - 1));
}
}
return ret;
}

View File

@ -0,0 +1,35 @@
#include <unistd.h>
#include <sys/uio.h>
/* A fixed-size circular buffer */
struct ring;
/* Allocate a circular buffer with the given payload size.
Size must be < INT_MAX / 2. */
extern struct ring *ring_allocate(int size);
/* Signal that new data is been produced */
extern void ring_producer_advance(struct ring *ring, int n);
/* Signal that data has been consumed */
extern void ring_consumer_advance(struct ring *ring, int n);
/* The producer sends Eof. This will cause ring_consumer_wait_available
and ring_producer_wait_available to return an error. */
extern void ring_producer_eof(struct ring *ring);
/* Wait for n bytes of space for new data to become available. If
ring_producer_eof has been called, return non-zero. If space is available
then fill the first *iovec_len entries of the iovec and set *iovec_len to
the number of iovecs used. */
extern int ring_producer_wait_available(
struct ring *ring, size_t n, struct iovec *iovec, int *iovec_len
);
/* Wait for n bytes to become available for reading. If ring_producer_eof has
been called, return non-zero. If data is available then fill the first
*iovec_len entries of the iovec and set *iovec_len to the number of iovecs
used. */
extern int ring_consumer_wait_available(
struct ring *ring, size_t n, struct iovec *iovec, int *iovec_len
);

View File

@ -0,0 +1,732 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <syslog.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <err.h>
#include <sys/uio.h>
#include <stdint.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <linux/if_tun.h>
#include <net/if_arp.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <ifaddrs.h>
#include "hvsock.h"
#include "protocol.h"
#include "ring.h"
int daemon_flag;
int nofork_flag;
int listen_flag;
int connect_flag;
char *default_sid = "30D48B34-7D27-4B0B-AAAF-BBBED334DD59";
/* Support big frames if the server requests it */
const int max_packet_size = 16384;
static int verbose;
#define INFO(...) \
do { \
if (verbose) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
#define DBG(...) \
do { \
if (verbose > 1) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
#define TRC(...) \
do { \
if (verbose > 2) { \
printf(__VA_ARGS__); \
fflush(stdout); \
} \
} while (0)
void fatal(const char *msg)
{
syslog(LOG_CRIT, "%s Error: %d. %s", msg, errno, strerror(errno));
exit(1);
}
int alloc_tap(const char *dev)
{
const char *clonedev = "/dev/net/tun";
struct ifreq ifr;
int persist = 1;
int fd;
fd = open(clonedev, O_RDWR);
if (fd == -1)
fatal("Failed to open /dev/net/tun");
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
if (ioctl(fd, TUNSETIFF, (void *)&ifr) < 0)
fatal("TUNSETIFF failed");
if (ioctl(fd, TUNSETPERSIST, persist) < 0)
fatal("TUNSETPERSIST failed");
syslog(LOG_INFO, "successfully created TAP device %s", dev);
return fd;
}
void set_macaddr(const char *dev, uint8_t *mac)
{
struct ifreq ifq;
int fd;
fd = socket(PF_INET, SOCK_DGRAM, 0);
if (fd == -1)
fatal("Could not get socket to set MAC address");
strcpy(ifq.ifr_name, dev);
memcpy(&ifq.ifr_hwaddr.sa_data[0], mac, 6);
ifq.ifr_hwaddr.sa_family = ARPHRD_ETHER;
if (ioctl(fd, SIOCSIFHWADDR, &ifq) == -1)
fatal("SIOCSIFHWADDR failed");
close(fd);
}
void set_mtu(const char *dev, int mtu)
{
struct ifreq ifq;
int fd;
fd = socket(PF_INET, SOCK_DGRAM, 0);
if (fd == -1)
fatal("Could not get socket to set MTU");
strcpy(ifq.ifr_name, dev);
ifq.ifr_mtu = mtu;
if (ioctl(fd, SIOCSIFMTU, &ifq) == -1)
fatal("SIOCSIFMTU failed");
close(fd);
}
/* Negotiate a vmnet connection, returns 0 on success and 1 on error. */
int negotiate(int fd, struct vif_info *vif)
{
enum command command = ethernet;
struct init_message *me;
struct ethernet_args args;
struct init_message you;
char *txt;
me = create_init_message();
if (!me)
goto err;
if (write_init_message(fd, me) == -1)
goto err;
if (read_init_message(fd, &you) == -1)
goto err;
if (me->version != you.version) {
syslog(LOG_CRIT, "Server did not accept our protocol version (client: %d, server: %d)", me->version, you.version);
goto err;
}
txt = print_init_message(&you);
if (!txt)
goto err;
syslog(LOG_INFO, "Server reports %s", txt);
free(txt);
if (write_command(fd, &command) == -1)
goto err;
/* We don't need a uuid */
memset(&args.uuid_string[0], 0, sizeof(args.uuid_string));
if (write_ethernet_args(fd, &args) == -1)
goto err;
if (read_vif_response(fd, vif) == -1)
goto err;
return 0;
err:
syslog(LOG_CRIT, "Failed to negotiate vmnet connection");
return 1;
}
/* Argument passed to proxy threads */
struct connection {
int fd; /* Hyper-V socket with vmnet protocol */
int tapfd; /* TAP device with ethernet frames */
struct vif_info vif; /* Contains MAC, MTU etc, received from server */
struct ring* from_vmnet_ring;
struct ring* to_vmnet_ring;
int message_size; /* Maximum size of a Hyper-V read or write */
};
/* Trim the iovec so that it contains at most len bytes. */
void trim_iovec(struct iovec *iovec, int *iovec_len, size_t len)
{
for (int i = 0; i < *iovec_len; i++) {
if (iovec[i].iov_len > len) {
iovec[i].iov_len = len;
*iovec_len = i + 1;
return;
}
len -= iovec[i].iov_len;
}
}
size_t len_iovec(struct iovec *iovec, int iovec_len)
{
size_t len = 0;
for (int i = 0; i < iovec_len; i++) {
len += iovec[i].iov_len;
}
return len;
}
/* Read bytes from vmnet into the from_vmnet_ring */
static void* vmnet_to_ring(void *arg)
{
struct connection *c = (struct connection *)arg;
struct ring *ring = c->from_vmnet_ring;
struct iovec iovec[2]; /* We won't need more than 2 for the ring */
int iovec_len;
while (1) {
iovec_len = sizeof(iovec) / sizeof(struct iovec);
TRC("vmnet_to_ring: ring_producer_wait_available n=%d iovec_len=%d\n", 1, iovec_len);
if (ring_producer_wait_available(ring, 1, &iovec[0], &iovec_len) != 0) {
fatal("Failed to read a data from vmnet");
}
trim_iovec(iovec, &iovec_len, c->message_size);
{
int length = 0;
for (int i = 0; i < iovec_len; i ++) {
length += iovec[i].iov_len;
}
TRC("vmnet_to_ring readv len %d\n", length);
}
ssize_t n = readv(c->fd, &iovec[0], iovec_len);
TRC("vmnet_to_ring: read %zd\n", n);
if (n == 0) {
syslog(LOG_CRIT, "EOF reading from socket: closing\n");
ring_producer_eof(ring);
goto err;
}
if (n < 0) {
syslog(LOG_CRIT,
"Failure reading from socket: closing: %s (%d)",
strerror(errno), errno);
ring_producer_eof(ring);
goto err;
}
TRC("vmnet_to_ring: advance producer %zd\n", n);
ring_producer_advance(ring, (size_t) n);
}
err:
/*
* On error: stop reading from the socket and trigger a clean
* shutdown
*/
TRC("vmnet_to_ring: shutdown\n");
shutdown(c->fd, SHUT_RD);
return NULL;
}
/* Decode packets on the from_vmnet_ring and write to the tap device */
static void* ring_to_tap(void *arg)
{
struct connection *c = (struct connection *)arg;
struct iovec iovec[2]; /* We won't need more than 2 for the ring */
int iovec_len;
int length;
struct ring *ring = c->from_vmnet_ring;
while (1) {
/* Read the packet length: this requires 2 bytes */
iovec_len = sizeof(iovec) / sizeof(struct iovec);
TRC("ring_to_tap: ring_consumer_wait_available n=%d iovec_len=%d\n", 2, iovec_len);
if (ring_consumer_wait_available(ring, 2, &iovec[0], &iovec_len) != 0) {
fatal("Failed to read a packet header from host");
}
length = *((uint8_t*)iovec[0].iov_base) & 0xff;
/* The second byte might be in the second iovec array */
if (iovec[0].iov_len >= 2) {
length |= (*((uint8_t*)iovec[0].iov_base + 1) & 0xff) << 8;
} else {
length |= (*((uint8_t*)iovec[1].iov_base) & 0xff) << 8;
}
assert(length > 0);
TRC("ring_to_tap: packet of length %d\n", length);
if (length > max_packet_size) {
syslog(LOG_CRIT,
"Received an over-large packet: %d > %ld",
length, max_packet_size);
exit(1);
}
ring_consumer_advance(ring, 2);
/* Read the variable length packet */
iovec_len = sizeof(iovec) / sizeof(struct iovec);
TRC("ring_to_tap: ring_consumer_wait_available n=%d iovec_len=%d\n", length, iovec_len);
if (ring_consumer_wait_available(ring, length, &iovec[0], &iovec_len) != 0) {
fatal("Failed to read a packet body from host");
}
assert(len_iovec(&iovec[0], iovec_len) >= length);
trim_iovec(iovec, &iovec_len, length);
ssize_t n = writev(c->tapfd, &iovec[0], iovec_len);
if (n != length) {
syslog(LOG_CRIT,
"Failed to write %d bytes to tap device (wrote %d)", length, n);
//exit(1);
}
TRC("ring_to_tap: ring_consumer_advance n=%zd\n", n);
ring_consumer_advance(ring, (size_t) length);
}
return NULL;
}
/* Write packets with header from the tap device onto the to_vmnet_ring */
static void *tap_to_ring(void *arg)
{
struct connection *connection = (struct connection *)arg;
struct ring *ring = connection->to_vmnet_ring;
struct iovec iovec[2]; /* We won't need more than 2 for the ring */
int iovec_len;
struct iovec payload[2]; /* The packet body after the 2 byte header */
int payload_len;
size_t length;
while (1) {
/* Wait for space for a 2 byte header + max_packet_size */
length = 2 + connection->vif.max_packet_size;
iovec_len = sizeof(iovec) / sizeof(struct iovec);
TRC("tap_to_ring: ring_producer_wait_available n=%zd iovec_len=%d\n", length, iovec_len);
if (ring_producer_wait_available(ring, length, &iovec[0], &iovec_len) != 0) {
fatal("Failed to find enough free space for a packet");
}
assert(iovec_len > 0);
assert(iovec[0].iov_len > 0);
memcpy(&payload[0], &iovec[0], sizeof(struct iovec) * iovec_len);
payload_len = iovec_len;
/* take the first 2 bytes of the free space which will contain the header */
char *header1 = payload[0].iov_base;
payload[0].iov_base++;
payload[0].iov_len--;
if (payload[0].iov_len == 0) {
assert(payload_len == 2); /* because `length` > 1 */
payload[0].iov_base = payload[1].iov_base;
payload[0].iov_len = payload[1].iov_len;
payload_len --;
}
char *header2 = payload[0].iov_base;
payload[0].iov_base++;
payload[0].iov_len--;
/* payload is now where the packet should go */
/* limit the message size */
trim_iovec(payload, &payload_len, connection->message_size);
length = readv(connection->tapfd, payload, payload_len);
if (length == -1) {
if (errno == ENXIO)
fatal("tap device has gone down");
syslog(LOG_WARNING, "ignoring error %d", errno);
/*
* This is what mirage-net-unix does. Is it a good
* idea really?
*/
exit(1);
}
*header1 = (length >> 0) & 0xff;
*header2 = (length >> 8) & 0xff;
TRC("tap_to_ring: ring_producer_advance n=%zd\n", length + 2);
ring_producer_advance(ring, (size_t) (length + 2));
}
return NULL;
}
/* Write bytes from the to_vmnet_ring to the vmnet fd */
static void *ring_to_vmnet(void *arg)
{
struct connection *c = (struct connection *)arg;
struct iovec iovec[2]; /* We won't need more than 2 for the ring */
int iovec_len;
int length;
struct ring *ring = c->to_vmnet_ring;
while (1) {
/* Read the packet length: this requires 2 bytes */
iovec_len = sizeof(iovec) / sizeof(struct iovec);
TRC("ring_to_vmnet: ring_producer_wait_available n=%d iovec_len=%d\n", 1, iovec_len);
if (ring_consumer_wait_available(ring, 1, &iovec[0], &iovec_len) != 0) {
fatal("Failed to read data from ring");
}
trim_iovec(iovec, &iovec_len, c->message_size);
length = 0;
for (int i = 0; i < iovec_len; i++ ) {
length += iovec[i].iov_len;
}
TRC("ring_to_vmnet: read %d bytes\n", length);
ssize_t n = writev(c->fd, &iovec[0], iovec_len);
TRC("ring_to_vmnet: advance consumer %zd\n", n);
ring_consumer_advance(ring, (size_t) n);
}
return NULL;
}
/*
* Handle a connection by exchanging ethernet frames forever.
*/
static void handle(struct connection *connection)
{
pthread_t v2r, r2t, t2r, r2v;
if (pthread_create(&t2r, NULL, tap_to_ring, connection) != 0)
fatal("Failed to create the tap_to_ring thread");
if (pthread_create(&v2r, NULL, vmnet_to_ring, connection) != 0)
fatal("Failed to create the vmnet_to_tap thread");
if (pthread_create(&r2t, NULL, ring_to_tap, connection) != 0)
fatal("Failed to create the ring_to_tap thread");
if (pthread_create(&r2v, NULL, ring_to_vmnet, connection) != 0)
fatal("Failed to create the ring_to_vmnet thread");
if (pthread_join(t2r, NULL) != 0)
fatal("Failed to join the tap_to_ring thread");
if (pthread_join(v2r, NULL) != 0)
fatal("Failed to join the vmnet_to_ring thread");
if (pthread_join(t2r, NULL) != 0)
fatal("Failed to join the tap_to_ring thread");
if (pthread_join(r2v, NULL) != 0)
fatal("Failed to join the ring_to_vmnet thread");
}
static int create_listening_socket(GUID serviceid)
{
SOCKADDR_HV sa;
int lsock;
int res;
lsock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (lsock == -1)
fatal("socket()");
sa.Family = AF_HYPERV;
sa.Reserved = 0;
sa.VmId = HV_GUID_WILDCARD;
sa.ServiceId = serviceid;
res = bind(lsock, (const struct sockaddr *)&sa, sizeof(sa));
if (res == -1)
fatal("bind()");
res = listen(lsock, SOMAXCONN);
if (res == -1)
fatal("listen()");
return lsock;
}
static int connect_socket(GUID serviceid)
{
SOCKADDR_HV sa;
int sock;
int res;
sock = socket(AF_HYPERV, SOCK_STREAM, HV_PROTOCOL_RAW);
if (sock == -1)
fatal("socket()");
sa.Family = AF_HYPERV;
sa.Reserved = 0;
sa.VmId = HV_GUID_PARENT;
sa.ServiceId = serviceid;
res = connect(sock, (const struct sockaddr *)&sa, sizeof(sa));
if (res == -1)
fatal("connect()");
return sock;
}
static int accept_socket(int lsock)
{
SOCKADDR_HV sac;
socklen_t socklen = sizeof(sac);
int csock;
csock = accept(lsock, (struct sockaddr *)&sac, &socklen);
if (csock == -1)
fatal("accept()");
syslog(LOG_INFO, "Connect from: " GUID_FMT ":" GUID_FMT "\n",
GUID_ARGS(sac.VmId), GUID_ARGS(sac.ServiceId));
return csock;
}
void write_pidfile(const char *pidfile)
{
pid_t pid = getpid();
char *pid_s;
FILE *file;
int len;
if (asprintf(&pid_s, "%lld", (long long)pid) == -1)
fatal("Failed to allocate pidfile string");
len = strlen(pid_s);
file = fopen(pidfile, "w");
if (file == NULL) {
syslog(LOG_CRIT, "Failed to open pidfile %s", pidfile);
exit(1);
}
if (fwrite(pid_s, 1, len, file) != len)
fatal("Failed to write pid to pidfile");
fclose(file);
free(pid_s);
}
void daemonize(const char *pidfile)
{
pid_t pid;
int null;
pid = fork();
if (pid == -1)
fatal("Failed to fork()");
else if (pid != 0)
exit(0);
if (setsid() == -1)
fatal("Failed to setsid()");
if (chdir("/") == -1)
fatal("Failed to chdir()");
null = open("/dev/null", O_RDWR);
if (null == -1)
fatal("Failed to open /dev/null");
dup2(null, STDIN_FILENO);
dup2(null, STDOUT_FILENO);
dup2(null, STDERR_FILENO);
close(null);
if (pidfile)
write_pidfile(pidfile);
}
void usage(char *name)
{
printf("%s usage:\n", name);
printf("\t[--daemon] [--tap <name>] [--serviceid <guid>] [--pid <file>]\n");
printf("\t[--message-size <bytes>] [--buffer-size <bytes>]\n");
printf("\t[--listen | --connect]\n\n");
printf("where\n");
printf("\t--daemonize: run as a background daemon\n");
printf("\t--nofork: don't run handlers in subprocesses\n");
printf("\t--tap <name>: create a tap device with the given name\n");
printf("\t (defaults to eth1)\n");
printf("\t--serviceid <guid>: use <guid> as the well-known service GUID\n");
printf("\t (defaults to %s)\n", default_sid);
printf("\t--pid <file>: write a pid to the given file\n");
printf("\t--message-size <bytes>: dictates the maximum transfer size for AF_HVSOCK\n");
printf("\t--buffer-size <bytes>: dictates the buffer size for AF_HVSOCK\n");
printf("\t--listen: listen forever for incoming AF_HVSOCK connections\n");
printf("\t--connect: connect to the parent partition\n");
}
int main(int argc, char **argv)
{
char *serviceid = default_sid;
struct connection connection;
char *tap = "eth1";
char *pidfile = NULL;
int lsocket = -1;
int sock = -1;
int res = 0;
int status;
pid_t child;
int tapfd;
int ring_size = 1048576;
int message_size = 8192; /* Well known to work across Hyper-V versions */
GUID sid;
int c;
int option_index;
int log_flags = LOG_CONS | LOG_NDELAY;
static struct option long_options[] = {
/* These options set a flag. */
{"daemon", no_argument, &daemon_flag, 1},
{"nofork", no_argument, &nofork_flag, 1},
{"serviceid", required_argument, NULL, 's'},
{"tap", required_argument, NULL, 't'},
{"pidfile", required_argument, NULL, 'p'},
{"listen", no_argument, &listen_flag, 1},
{"connect", no_argument, &connect_flag, 1},
{"buffer-size", required_argument, NULL, 'b'},
{"message-size", required_argument, NULL, 'm'},
{0, 0, 0, 0}
};
opterr = 0;
while (1) {
option_index = 0;
c = getopt_long(argc, argv, "ds:t:p:r:m:v",
long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 'd':
daemon_flag = 1;
break;
case 'n':
nofork_flag = 1;
break;
case 's':
serviceid = optarg;
break;
case 't':
tap = optarg;
break;
case 'p':
pidfile = optarg;
break;
case 'b':
ring_size = atoi(optarg);
break;
case 'm':
message_size = atoi(optarg);
break;
case 'v':
verbose ++;
break;
case 0:
break;
default:
usage(argv[0]);
exit(1);
}
}
if ((listen_flag && connect_flag) || !(listen_flag || connect_flag)) {
fprintf(stderr, "Please supply either the --listen or --connect flag, but not both.\n");
exit(1);
}
if (daemon_flag && !pidfile) {
fprintf(stderr, "For daemon mode, please supply a --pidfile argument.\n");
exit(1);
}
res = parseguid(serviceid, &sid);
if (res) {
fprintf(stderr, "Failed to parse serviceid as GUID: %s\n", serviceid);
usage(argv[0]);
exit(1);
}
if (!daemon_flag)
log_flags |= LOG_PERROR;
openlog(argv[0], log_flags, LOG_DAEMON);
tapfd = alloc_tap(tap);
connection.tapfd = tapfd;
connection.to_vmnet_ring = ring_allocate(ring_size);
connection.from_vmnet_ring = ring_allocate(ring_size);
connection.message_size = message_size;
if (listen_flag) {
syslog(LOG_INFO, "starting in listening mode with serviceid=%s and tap=%s", serviceid, tap);
lsocket = create_listening_socket(sid);
} else {
syslog(LOG_INFO, "starting in connect mode with serviceid=%s and tap=%s", serviceid, tap);
}
for (;;) {
if (sock != -1) {
close(sock);
sock = -1;
}
if (listen_flag)
sock = accept_socket(lsocket);
else
sock = connect_socket(sid);
connection.fd = sock;
if (negotiate(sock, &connection.vif) != 0) {
sleep(1);
continue;
}
syslog(LOG_INFO, "VMNET VIF has MAC %02x:%02x:%02x:%02x:%02x:%02x",
connection.vif.mac[0], connection.vif.mac[1], connection.vif.mac[2],
connection.vif.mac[3], connection.vif.mac[4], connection.vif.mac[5]
);
set_macaddr(tap, &connection.vif.mac[0]);
set_mtu(tap, connection.vif.mtu);
/* Daemonize after we've made our first reliable connection */
if (daemon_flag) {
daemon_flag = 0;
daemonize(pidfile);
}
if (nofork_flag) {
handle(&connection);
exit(1);
}
/*
* Run the multithreaded part in a subprocess. On error the
* process will exit() which tears down all the threads
*/
child = fork();
if (child == 0) {
handle(&connection);
/*
* should never happen but just in case of a logic
* bug in handle
*/
exit(1);
}
for (;;) {
if (waitpid(child, &status, 0) != -1)
break;
}
}
}

View File

@ -0,0 +1,22 @@
package vpnkit
import (
"context"
datakit "github.com/moby/datakit/api/go-datakit"
)
// Connection represents an open control connection to vpnkit
type Connection struct {
client *datakit.Client
}
// NewConnection connects to a vpnkit Unix domain socket on the given path
// and returns the connection
func NewConnection(ctx context.Context, path string) (*Connection, error) {
client, err := datakit.Dial(ctx, "unix", path)
if err != nil {
return nil, err
}
return &Connection{client}, nil
}

View File

@ -0,0 +1,9 @@
/*
Package vpnkit allows a running VPNKit service to be reconfigured.
Features
- expose/unexpose TCP and UDP ports
*/
package vpnkit

View File

@ -0,0 +1,200 @@
package vpnkit
import (
"context"
"errors"
"fmt"
"log"
"net"
"strconv"
"strings"
p9p "github.com/docker/go-p9p"
datakit "github.com/moby/datakit/api/go-datakit"
)
// Port describes a UDP or TCP port forward
type Port struct {
client *datakit.Client
proto string
outIP net.IP
outPort uint16
inIP net.IP
inPort uint16
handle *datakit.File
}
// NewPort constructs an instance of Port
func NewPort(connection *Connection, proto string, outIP net.IP, outPort uint16, inIP net.IP, inPort uint16) *Port {
return &Port{connection.client, proto, outIP, outPort, inIP, inPort, nil}
}
// ListExposed returns a list of currently exposed ports
func ListExposed(connection *Connection) ([]*Port, error) {
ctx := context.TODO()
dirs, err := connection.client.List(ctx, []string{})
if err != nil {
return nil, err
}
results := make([]*Port, 0)
for _, name := range dirs {
port, err := parse(name)
if err != nil {
// there are some special files like "." and "README" to ignore
continue
}
port.client = connection.client
results = append(results, port)
}
return results, nil
}
// String returns a human-readable string
func (p *Port) String() string {
return fmt.Sprintf("%s forward from %s:%d to %s:%d", p.proto, p.outIP.String(), p.outPort, p.inIP.String(), p.inPort)
}
// spec returns a string of the form proto:outIP:outPort:proto:inIP:inPort as
// understood by vpnkit
func (p *Port) spec() string {
return fmt.Sprintf("%s:%s:%d:%s:%s:%d", p.proto, p.outIP.String(), p.outPort, p.proto, p.inIP.String(), p.inPort)
}
func parse(name string) (*Port, error) {
bits := strings.Split(name, ":")
if len(bits) != 6 {
return nil, errors.New("Failed to parse port spec: " + name)
}
outProto := bits[0]
outIP := net.ParseIP(bits[1])
outPort, err := strconv.ParseUint(bits[2], 10, 16)
if err != nil {
return nil, err
}
inProto := bits[3]
inIP := net.ParseIP(bits[4])
inPort, err := strconv.ParseUint(bits[5], 10, 16)
if err != nil {
return nil, err
}
if outProto != inProto {
return nil, errors.New("Failed to parse port: external proto is " + outProto + " but internal proto is " + inProto)
}
return &Port{nil, outProto, outIP, uint16(outPort), inIP, uint16(inPort), nil}, nil
}
// Expose asks vpnkit to expose the port
func (p *Port) Expose(ctx context.Context) error {
if p.handle != nil {
return errors.New("Port is already exposed")
}
spec := p.spec()
client := p.client
// use the spec also as a name
name := spec
log.Printf("Expose %s\n", spec)
_ = client.Remove(ctx, name)
err := client.Mkdir(ctx, name)
if err != nil {
log.Printf("Expose failed to create %s: %#v\n", name, err)
return err
}
ctl, err := client.Open(ctx, p9p.OREAD, name, "ctl")
if err != nil {
log.Printf("Expose failed to open %s/ctl: %#v\n", name, err)
return err
}
// NB we deliberately leak the fid because we use the clunk as a signal to
// shutdown the forward.
// Read any error from a previous session
bytes := make([]byte, 100)
n, err := ctl.Read(ctx, bytes, 0)
if err != nil {
log.Printf("Expose %s: failed to read response from ctl: %#v\n", spec, err)
return err
}
_, _ = ctl.Read(ctx, bytes, int64(n))
response := string(bytes)
if !strings.HasPrefix(response, "ERROR no request received") {
log.Printf("Expose %s: read error from previous operation: %s\n", spec, response[0:n])
}
request := []byte(spec)
_, err = ctl.Write(ctx, request, 0)
if err != nil {
log.Printf("Expose %s: failed to write to ctl: %#v\n", spec, err)
return err
}
n, err = ctl.Read(ctx, bytes, 0)
if err != nil {
log.Printf("Expose %s: failed to read response from ctl: %#v\n", spec, err)
return err
}
_, _ = ctl.Read(ctx, bytes, int64(n))
response = string(bytes)
if strings.HasPrefix(response, "OK ") {
response = strings.Trim(response[3:n], " \t\r\n")
log.Printf("Expose %s: succeeded with %s\n", spec, response)
p.handle = ctl
return nil
}
log.Printf("Expose %s: failed: %s\n", spec, response[0:n])
if strings.HasPrefix(response, "ERROR ") {
response = strings.Trim(response[6:n], " \t\r\n")
ctl.Close(ctx)
}
return errors.New(response)
}
// Unexpose asks vpnkit to hide the port again
func (p *Port) Unexpose(ctx context.Context) error {
if p.handle == nil {
ctl, err := p.client.Open(ctx, p9p.OREAD, p.spec(), "ctl")
if err != nil {
return errors.New("Port is not exposed")
}
p.handle = ctl
}
ctl := p.handle
p.handle = nil
// Any clunk frees the port
ctl.Close(ctx)
return nil
}
// Proto returns the protocol: either "tcp" or "udp"
func (p *Port) Proto() string {
return p.proto
}
// OutIP returns the public IP
func (p *Port) OutIP() net.IP {
return p.outIP
}
// OutPort returns the public port number
func (p *Port) OutPort() uint16 {
return p.outPort
}
// InIP returns the private IP
func (p *Port) InIP() net.IP {
return p.inIP
}
// InPort returns the private port number
func (p *Port) InPort() uint16 {
return p.inPort
}
var enoent = p9p.MessageRerror{Ename: "file not found"}

View File

@ -0,0 +1,633 @@
package vpnkit
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"io"
"net"
"os"
"time"
"github.com/google/uuid"
)
// Vmnet describes a "vmnet protocol" connection which allows ethernet frames to be
// sent to and received by vpnkit.
type Vmnet struct {
conn net.Conn
remoteVersion *InitMessage
}
// NewVmnet constructs an instance of Vmnet.
func NewVmnet(ctx context.Context, path string) (*Vmnet, error) {
d := &net.Dialer{}
conn, err := d.DialContext(ctx, "unix", path)
if err != nil {
return nil, err
}
var remoteVersion *InitMessage
vmnet := &Vmnet{conn, remoteVersion}
err = vmnet.negotiate()
if err != nil {
return nil, err
}
return vmnet, err
}
// Close closes the connection.
func (v *Vmnet) Close() error {
return v.conn.Close()
}
// InitMessage is used for the initial version exchange
type InitMessage struct {
magic [5]byte
version uint32
commit [40]byte
}
// String returns a human-readable string.
func (m *InitMessage) String() string {
return fmt.Sprintf("magic=%v version=%d commit=%v", m.magic, m.version, m.commit)
}
// defaultInitMessage is the init message we will send to vpnkit
func defaultInitMessage() *InitMessage {
magic := [5]byte{'V', 'M', 'N', '3', 'T'}
version := uint32(22)
var commit [40]byte
copy(commit[:], []byte("0123456789012345678901234567890123456789"))
return &InitMessage{magic, version, commit}
}
// Write marshals an init message to a connection
func (m *InitMessage) Write(c net.Conn) error {
if err := binary.Write(c, binary.LittleEndian, m.magic); err != nil {
return err
}
if err := binary.Write(c, binary.LittleEndian, m.version); err != nil {
return err
}
if err := binary.Write(c, binary.LittleEndian, m.commit); err != nil {
return err
}
return nil
}
// readInitMessage unmarshals an init message from a connection
func (v *Vmnet) readInitMessage() (*InitMessage, error) {
m := defaultInitMessage()
if err := binary.Read(v.conn, binary.LittleEndian, &m.magic); err != nil {
return nil, err
}
if err := binary.Read(v.conn, binary.LittleEndian, &m.version); err != nil {
return nil, err
}
if err := binary.Read(v.conn, binary.LittleEndian, &m.commit); err != nil {
return nil, err
}
return m, nil
}
func (v *Vmnet) negotiate() error {
m := defaultInitMessage()
if err := m.Write(v.conn); err != nil {
return err
}
remoteVersion, err := v.readInitMessage()
if err != nil {
return err
}
v.remoteVersion = remoteVersion
return nil
}
// Ethernet requests the creation of a network connection with a given
// uuid and optional IP
type Ethernet struct {
uuid uuid.UUID
ip net.IP
}
// NewEthernet creates an Ethernet frame
func NewEthernet(uuid uuid.UUID, ip net.IP) *Ethernet {
return &Ethernet{uuid, ip}
}
// Write marshals an Ethernet message
func (m *Ethernet) Write(c net.Conn) error {
ty := uint8(1)
if m.ip != nil {
ty = uint8(8)
}
if err := binary.Write(c, binary.LittleEndian, ty); err != nil {
return err
}
u, err := m.uuid.MarshalText()
if err != nil {
return err
}
if err := binary.Write(c, binary.LittleEndian, u); err != nil {
return err
}
ip := uint32(0)
if m.ip != nil {
ip = binary.BigEndian.Uint32(m.ip.To4())
}
// The protocol uses little endian, not network endian
if err := binary.Write(c, binary.LittleEndian, ip); err != nil {
return err
}
return nil
}
// Vif represents an Ethernet device
type Vif struct {
MTU uint16
MaxPacketSize uint16
ClientMAC net.HardwareAddr
IP net.IP
conn net.Conn
}
func (v *Vmnet) readVif() (*Vif, error) {
var MTU, MaxPacketSize uint16
if err := binary.Read(v.conn, binary.LittleEndian, &MTU); err != nil {
return nil, err
}
if err := binary.Read(v.conn, binary.LittleEndian, &MaxPacketSize); err != nil {
return nil, err
}
var mac [6]byte
if err := binary.Read(v.conn, binary.LittleEndian, &mac); err != nil {
return nil, err
}
padding := make([]byte, 1+256-6-2-2)
if err := binary.Read(v.conn, binary.LittleEndian, &padding); err != nil {
return nil, err
}
ClientMAC := mac[:]
conn := v.conn
var IP net.IP
return &Vif{MTU, MaxPacketSize, ClientMAC, IP, conn}, nil
}
// ConnectVif returns a connected network interface with the given uuid.
func (v *Vmnet) ConnectVif(uuid uuid.UUID) (*Vif, error) {
e := NewEthernet(uuid, nil)
if err := e.Write(v.conn); err != nil {
return nil, err
}
var responseType uint8
if err := binary.Read(v.conn, binary.LittleEndian, &responseType); err != nil {
return nil, err
}
switch responseType {
case 1:
vif, err := v.readVif()
if err != nil {
return nil, err
}
IP, err := vif.dhcp()
if err != nil {
return nil, err
}
vif.IP = IP
return vif, err
default:
var len uint8
if err := binary.Read(v.conn, binary.LittleEndian, &len); err != nil {
return nil, err
}
message := make([]byte, len)
if err := binary.Read(v.conn, binary.LittleEndian, &message); err != nil {
return nil, err
}
return nil, errors.New(string(message))
}
}
// ConnectVifIP returns a connected network interface with the given uuid
// and IP. If the IP is already in use then return an error.
func (v *Vmnet) ConnectVifIP(uuid uuid.UUID, IP net.IP) (*Vif, error) {
e := NewEthernet(uuid, IP)
if err := e.Write(v.conn); err != nil {
return nil, err
}
var responseType uint8
if err := binary.Read(v.conn, binary.LittleEndian, &responseType); err != nil {
return nil, err
}
switch responseType {
case 1:
vif, err := v.readVif()
if err != nil {
return nil, err
}
vif.IP = IP
return vif, err
default:
var len uint8
if err := binary.Read(v.conn, binary.LittleEndian, &len); err != nil {
return nil, err
}
message := make([]byte, len)
if err := binary.Read(v.conn, binary.LittleEndian, &message); err != nil {
return nil, err
}
return nil, errors.New(string(message))
}
}
// Write writes a packet to a Vif
func (v *Vif) Write(packet []byte) error {
len := uint16(len(packet))
if err := binary.Write(v.conn, binary.LittleEndian, len); err != nil {
return err
}
if err := binary.Write(v.conn, binary.LittleEndian, packet); err != nil {
return err
}
return nil
}
// Read reads the next packet from a Vif
func (v *Vif) Read() ([]byte, error) {
var len uint16
if err := binary.Read(v.conn, binary.LittleEndian, &len); err != nil {
return nil, err
}
packet := make([]byte, len)
if err := binary.Read(v.conn, binary.LittleEndian, &packet); err != nil {
return nil, err
}
return packet, nil
}
// PcapWriter writes pcap-formatted packet streams
type PcapWriter struct {
w io.Writer
snaplen uint32
}
// NewPcapWriter creates a PcapWriter and writes the initial header
func NewPcapWriter(w io.Writer) (*PcapWriter, error) {
magic := uint32(0xa1b2c3d4)
major := uint16(2)
minor := uint16(4)
thiszone := uint32(0) // GMT to local correction
sigfigs := uint32(0) // accuracy of local timestamps
snaplen := uint32(1500) // max length of captured packets, in octets
network := uint32(1) // ethernet
if err := binary.Write(w, binary.LittleEndian, magic); err != nil {
return nil, err
}
if err := binary.Write(w, binary.LittleEndian, major); err != nil {
return nil, err
}
if err := binary.Write(w, binary.LittleEndian, minor); err != nil {
return nil, err
}
if err := binary.Write(w, binary.LittleEndian, thiszone); err != nil {
return nil, err
}
if err := binary.Write(w, binary.LittleEndian, sigfigs); err != nil {
return nil, err
}
if err := binary.Write(w, binary.LittleEndian, snaplen); err != nil {
return nil, err
}
if err := binary.Write(w, binary.LittleEndian, network); err != nil {
return nil, err
}
return &PcapWriter{w, snaplen}, nil
}
// Write appends a packet with a pcap-format header
func (p *PcapWriter) Write(packet []byte) error {
stamp := time.Now()
s := uint32(stamp.Second())
us := uint32(stamp.Nanosecond() / 1000)
actualLen := uint32(len(packet))
if err := binary.Write(p.w, binary.LittleEndian, s); err != nil {
return err
}
if err := binary.Write(p.w, binary.LittleEndian, us); err != nil {
return err
}
toWrite := packet[:]
if actualLen > p.snaplen {
toWrite = toWrite[0:p.snaplen]
}
caplen := uint32(len(toWrite))
if err := binary.Write(p.w, binary.LittleEndian, caplen); err != nil {
return err
}
if err := binary.Write(p.w, binary.LittleEndian, actualLen); err != nil {
return err
}
if err := binary.Write(p.w, binary.LittleEndian, toWrite); err != nil {
return err
}
return nil
}
// EthernetFrame is an ethernet frame
type EthernetFrame struct {
Dst net.HardwareAddr
Src net.HardwareAddr
Type uint16
Data []byte
}
// NewEthernetFrame constructs an Ethernet frame
func NewEthernetFrame(Dst, Src net.HardwareAddr, Type uint16) *EthernetFrame {
Data := make([]byte, 0)
return &EthernetFrame{Dst, Src, Type, Data}
}
func (e *EthernetFrame) setData(data []byte) {
e.Data = data
}
// Write marshals an Ethernet frame
func (e *EthernetFrame) Write(w io.Writer) error {
if err := binary.Write(w, binary.BigEndian, e.Dst); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, e.Src); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, e.Type); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, e.Data); err != nil {
return err
}
return nil
}
// ParseEthernetFrame parses the ethernet frame
func ParseEthernetFrame(frame []byte) (*EthernetFrame, error) {
if len(frame) < (6 + 6 + 2) {
return nil, errors.New("Ethernet frame is too small")
}
Dst := frame[0:6]
Src := frame[6:12]
Type := uint16(frame[12])<<8 + uint16(frame[13])
Data := frame[14:]
return &EthernetFrame{Dst, Src, Type, Data}, nil
}
// Bytes returns the marshalled ethernet frame
func (e *EthernetFrame) Bytes() []byte {
buf := bytes.NewBufferString("")
if err := e.Write(buf); err != nil {
panic(err)
}
return buf.Bytes()
}
// Ipv4 is an IPv4 frame
type Ipv4 struct {
Dst net.IP
Src net.IP
Data []byte
Checksum uint16
}
// NewIpv4 constructs a new empty IPv4 packet
func NewIpv4(Dst, Src net.IP) *Ipv4 {
Checksum := uint16(0)
Data := make([]byte, 0)
return &Ipv4{Dst, Src, Data, Checksum}
}
// ParseIpv4 parses an IP packet
func ParseIpv4(packet []byte) (*Ipv4, error) {
if len(packet) < 20 {
return nil, errors.New("IPv4 packet too small")
}
ihl := int((packet[0] & 0xf) * 4) // in octets
if len(packet) < ihl {
return nil, errors.New("IPv4 packet too small")
}
Dst := packet[12:16]
Src := packet[16:20]
Data := packet[ihl:]
Checksum := uint16(0) // assume offload
return &Ipv4{Dst, Src, Data, Checksum}, nil
}
func (i *Ipv4) setData(data []byte) {
i.Data = data
i.Checksum = uint16(0) // as if we were using offload
}
// HeaderBytes returns the marshalled form of the IPv4 header
func (i *Ipv4) HeaderBytes() []byte {
len := len(i.Data) + 20
length := [2]byte{byte(len >> 8), byte(len & 0xff)}
checksum := [2]byte{byte(i.Checksum >> 8), byte(i.Checksum & 0xff)}
return []byte{
0x45, // version + IHL
0x00, // DSCP + ECN
length[0], length[1], // total length
0x7f, 0x61, // Identification
0x00, 0x00, // Flags + Fragment offset
0x40, // TTL
0x11, // Protocol
checksum[0], checksum[1],
0x00, 0x00, 0x00, 0x00, // source
0xff, 0xff, 0xff, 0xff, // destination
}
}
// Bytes returns the marshalled IPv4 packet
func (i *Ipv4) Bytes() []byte {
header := i.HeaderBytes()
return append(header, i.Data...)
}
// Udpv4 is a Udpv4 frame
type Udpv4 struct {
Src uint16
Dst uint16
Data []byte
Checksum uint16
}
// NewUdpv4 constructs a Udpv4 frame
func NewUdpv4(ipv4 *Ipv4, Dst, Src uint16, Data []byte) *Udpv4 {
Checksum := uint16(0)
return &Udpv4{Dst, Src, Data, Checksum}
}
// ParseUdpv4 parses a Udpv4 packet
func ParseUdpv4(packet []byte) (*Udpv4, error) {
if len(packet) < 8 {
return nil, errors.New("UDPv4 is too short")
}
Src := uint16(packet[0])<<8 + uint16(packet[1])
Dst := uint16(packet[2])<<8 + uint16(packet[3])
Checksum := uint16(packet[6])<<8 + uint16(packet[7])
Data := packet[8:]
return &Udpv4{Src, Dst, Data, Checksum}, nil
}
// Write marshalls a Udpv4 frame
func (u *Udpv4) Write(w io.Writer) error {
if err := binary.Write(w, binary.BigEndian, u.Src); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, u.Dst); err != nil {
return err
}
length := uint16(8 + len(u.Data))
if err := binary.Write(w, binary.BigEndian, length); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, u.Checksum); err != nil {
return err
}
if err := binary.Write(w, binary.BigEndian, u.Data); err != nil {
return err
}
return nil
}
// Bytes returns the marshalled Udpv4 frame
func (u *Udpv4) Bytes() []byte {
buf := bytes.NewBufferString("")
if err := u.Write(buf); err != nil {
panic(err)
}
return buf.Bytes()
}
// DhcpRequest is a simple DHCP request
type DhcpRequest struct {
MAC net.HardwareAddr
}
// NewDhcpRequest constructs a DHCP request
func NewDhcpRequest(MAC net.HardwareAddr) *DhcpRequest {
if len(MAC) != 6 {
panic("MAC address must be 6 bytes")
}
return &DhcpRequest{MAC}
}
// Bytes returns the marshalled DHCP request
func (d *DhcpRequest) Bytes() []byte {
bs := []byte{
0x01, // OP
0x01, // HTYPE
0x06, // HLEN
0x00, // HOPS
0x01, 0x00, 0x00, 0x00, // XID
0x00, 0x00, // SECS
0x80, 0x00, // FLAGS
0x00, 0x00, 0x00, 0x00, // CIADDR
0x00, 0x00, 0x00, 0x00, // YIADDR
0x00, 0x00, 0x00, 0x00, // SIADDR
0x00, 0x00, 0x00, 0x00, // GIADDR
d.MAC[0], d.MAC[1], d.MAC[2], d.MAC[3], d.MAC[4], d.MAC[5],
}
bs = append(bs, make([]byte, 202)...)
bs = append(bs, []byte{
0x63, 0x82, 0x53, 0x63, // Magic cookie
0x35, 0x01, 0x01, // DHCP discover
0xff, // Endmark
}...)
return bs
}
// dhcp queries the IP by DHCP
func (v *Vif) dhcp() (net.IP, error) {
broadcastMAC := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
broadcastIP := []byte{0xff, 0xff, 0xff, 0xff}
unknownIP := []byte{0, 0, 0, 0}
dhcpRequest := NewDhcpRequest(v.ClientMAC).Bytes()
ipv4 := NewIpv4(broadcastIP, unknownIP)
udpv4 := NewUdpv4(ipv4, 68, 67, dhcpRequest)
ipv4.setData(udpv4.Bytes())
ethernet := NewEthernetFrame(broadcastMAC, v.ClientMAC, 0x800)
ethernet.setData(ipv4.Bytes())
file, err := os.Create("/tmp/go.pcap")
if err != nil {
panic(err)
}
pcap, err := NewPcapWriter(file)
if err != nil {
panic(err)
}
finished := false
go func() {
for !finished {
if err := v.Write(ethernet.Bytes()); err != nil {
panic(err)
}
if err := pcap.Write(ethernet.Bytes()); err != nil {
panic(err)
}
time.Sleep(time.Second)
}
}()
for {
response, err := v.Read()
if err != nil {
return nil, err
}
if err := pcap.Write(response); err != nil {
panic(err)
}
ethernet, err = ParseEthernetFrame(response)
if err != nil {
continue
}
for i, x := range ethernet.Dst {
if i > len(v.ClientMAC) || v.ClientMAC[i] != x {
// intended for someone else
continue
}
}
ipv4, err = ParseIpv4(ethernet.Data)
if err != nil {
// probably not an IPv4 packet
continue
}
udpv4, err = ParseUdpv4(ipv4.Data)
if err != nil {
// probably not a UDPv4 packet
continue
}
if udpv4.Src != 67 || udpv4.Dst != 68 {
// not a DHCP response
continue
}
if len(udpv4.Data) < 243 {
// truncated
continue
}
if udpv4.Data[240] != 53 || udpv4.Data[241] != 1 || udpv4.Data[242] != 2 {
// not a DHCP offer
continue
}
var ip net.IP
ip = udpv4.Data[16:20]
finished = true // will terminate sending goroutine
return ip, nil
}
}

View File

@ -0,0 +1,5 @@
golang.org/x/sys 9c9d83fe39ed3fd2d9249fcf6b755891fff54b03
github.com/linuxkit/virtsock a381dcc5bcddf1d7f449495c373dbf70f8e501c0
github.com/docker/go-p9p 87ae8514a3a2d9684994a6c319f96ba9e18a062e
github.com/moby/datakit 97b3d230535397a813323902c23751e176481a86
github.com/google/uuid 7e072fc3a7be179aee6d3359e46015aa8c995314

View File

@ -1,20 +0,0 @@
Copyright (C) 2013-2016 by Maxim Bublis <b@codemonkey.ru>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,65 +0,0 @@
# UUID package for Go language
[![Build Status](https://travis-ci.org/satori/go.uuid.png?branch=master)](https://travis-ci.org/satori/go.uuid)
[![Coverage Status](https://coveralls.io/repos/github/satori/go.uuid/badge.svg?branch=master)](https://coveralls.io/github/satori/go.uuid)
[![GoDoc](http://godoc.org/github.com/satori/go.uuid?status.png)](http://godoc.org/github.com/satori/go.uuid)
This package provides pure Go implementation of Universally Unique Identifier (UUID). Supported both creation and parsing of UUIDs.
With 100% test coverage and benchmarks out of box.
Supported versions:
* Version 1, based on timestamp and MAC address (RFC 4122)
* Version 2, based on timestamp, MAC address and POSIX UID/GID (DCE 1.1)
* Version 3, based on MD5 hashing (RFC 4122)
* Version 4, based on random numbers (RFC 4122)
* Version 5, based on SHA-1 hashing (RFC 4122)
## Installation
Use the `go` command:
$ go get github.com/satori/go.uuid
## Requirements
UUID package requires Go >= 1.2.
## Example
```go
package main
import (
"fmt"
"github.com/satori/go.uuid"
)
func main() {
// Creating UUID Version 4
u1 := uuid.NewV4()
fmt.Printf("UUIDv4: %s\n", u1)
// Parsing UUID from string input
u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
if err != nil {
fmt.Printf("Something gone wrong: %s", err)
}
fmt.Printf("Successfully parsed: %s", u2)
}
```
## Documentation
[Documentation](http://godoc.org/github.com/satori/go.uuid) is hosted at GoDoc project.
## Links
* [RFC 4122](http://tools.ietf.org/html/rfc4122)
* [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01)
## Copyright
Copyright (C) 2013-2016 by Maxim Bublis <b@codemonkey.ru>.
UUID package released under MIT License.
See [LICENSE](https://github.com/satori/go.uuid/blob/master/LICENSE) for details.

View File

@ -1,481 +0,0 @@
// Copyright (C) 2013-2015 by Maxim Bublis <b@codemonkey.ru>
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Package uuid provides implementation of Universally Unique Identifier (UUID).
// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and
// version 2 (as specified in DCE 1.1).
package uuid
import (
"bytes"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"database/sql/driver"
"encoding/binary"
"encoding/hex"
"fmt"
"hash"
"net"
"os"
"sync"
"time"
)
// UUID layout variants.
const (
VariantNCS = iota
VariantRFC4122
VariantMicrosoft
VariantFuture
)
// UUID DCE domains.
const (
DomainPerson = iota
DomainGroup
DomainOrg
)
// Difference in 100-nanosecond intervals between
// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970).
const epochStart = 122192928000000000
// Used in string method conversion
const dash byte = '-'
// UUID v1/v2 storage.
var (
storageMutex sync.Mutex
storageOnce sync.Once
epochFunc = unixTimeFunc
clockSequence uint16
lastTime uint64
hardwareAddr [6]byte
posixUID = uint32(os.Getuid())
posixGID = uint32(os.Getgid())
)
// String parse helpers.
var (
urnPrefix = []byte("urn:uuid:")
byteGroups = []int{8, 4, 4, 4, 12}
)
func initClockSequence() {
buf := make([]byte, 2)
safeRandom(buf)
clockSequence = binary.BigEndian.Uint16(buf)
}
func initHardwareAddr() {
interfaces, err := net.Interfaces()
if err == nil {
for _, iface := range interfaces {
if len(iface.HardwareAddr) >= 6 {
copy(hardwareAddr[:], iface.HardwareAddr)
return
}
}
}
// Initialize hardwareAddr randomly in case
// of real network interfaces absence
safeRandom(hardwareAddr[:])
// Set multicast bit as recommended in RFC 4122
hardwareAddr[0] |= 0x01
}
func initStorage() {
initClockSequence()
initHardwareAddr()
}
func safeRandom(dest []byte) {
if _, err := rand.Read(dest); err != nil {
panic(err)
}
}
// Returns difference in 100-nanosecond intervals between
// UUID epoch (October 15, 1582) and current time.
// This is default epoch calculation function.
func unixTimeFunc() uint64 {
return epochStart + uint64(time.Now().UnixNano()/100)
}
// UUID representation compliant with specification
// described in RFC 4122.
type UUID [16]byte
// NullUUID can be used with the standard sql package to represent a
// UUID value that can be NULL in the database
type NullUUID struct {
UUID UUID
Valid bool
}
// The nil UUID is special form of UUID that is specified to have all
// 128 bits set to zero.
var Nil = UUID{}
// Predefined namespace UUIDs.
var (
NamespaceDNS, _ = FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
NamespaceURL, _ = FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
NamespaceOID, _ = FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
NamespaceX500, _ = FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
)
// And returns result of binary AND of two UUIDs.
func And(u1 UUID, u2 UUID) UUID {
u := UUID{}
for i := 0; i < 16; i++ {
u[i] = u1[i] & u2[i]
}
return u
}
// Or returns result of binary OR of two UUIDs.
func Or(u1 UUID, u2 UUID) UUID {
u := UUID{}
for i := 0; i < 16; i++ {
u[i] = u1[i] | u2[i]
}
return u
}
// Equal returns true if u1 and u2 equals, otherwise returns false.
func Equal(u1 UUID, u2 UUID) bool {
return bytes.Equal(u1[:], u2[:])
}
// Version returns algorithm version used to generate UUID.
func (u UUID) Version() uint {
return uint(u[6] >> 4)
}
// Variant returns UUID layout variant.
func (u UUID) Variant() uint {
switch {
case (u[8] & 0x80) == 0x00:
return VariantNCS
case (u[8]&0xc0)|0x80 == 0x80:
return VariantRFC4122
case (u[8]&0xe0)|0xc0 == 0xc0:
return VariantMicrosoft
}
return VariantFuture
}
// Bytes returns bytes slice representation of UUID.
func (u UUID) Bytes() []byte {
return u[:]
}
// Returns canonical string representation of UUID:
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
func (u UUID) String() string {
buf := make([]byte, 36)
hex.Encode(buf[0:8], u[0:4])
buf[8] = dash
hex.Encode(buf[9:13], u[4:6])
buf[13] = dash
hex.Encode(buf[14:18], u[6:8])
buf[18] = dash
hex.Encode(buf[19:23], u[8:10])
buf[23] = dash
hex.Encode(buf[24:], u[10:])
return string(buf)
}
// SetVersion sets version bits.
func (u *UUID) SetVersion(v byte) {
u[6] = (u[6] & 0x0f) | (v << 4)
}
// SetVariant sets variant bits as described in RFC 4122.
func (u *UUID) SetVariant() {
u[8] = (u[8] & 0xbf) | 0x80
}
// MarshalText implements the encoding.TextMarshaler interface.
// The encoding is the same as returned by String.
func (u UUID) MarshalText() (text []byte, err error) {
text = []byte(u.String())
return
}
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// Following formats are supported:
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}",
// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
func (u *UUID) UnmarshalText(text []byte) (err error) {
if len(text) < 32 {
err = fmt.Errorf("uuid: UUID string too short: %s", text)
return
}
t := text[:]
braced := false
if bytes.Equal(t[:9], urnPrefix) {
t = t[9:]
} else if t[0] == '{' {
braced = true
t = t[1:]
}
b := u[:]
for i, byteGroup := range byteGroups {
if i > 0 {
if t[0] != '-' {
err = fmt.Errorf("uuid: invalid string format")
return
}
t = t[1:]
}
if len(t) < byteGroup {
err = fmt.Errorf("uuid: UUID string too short: %s", text)
return
}
if i == 4 && len(t) > byteGroup &&
((braced && t[byteGroup] != '}') || len(t[byteGroup:]) > 1 || !braced) {
err = fmt.Errorf("uuid: UUID string too long: %s", text)
return
}
_, err = hex.Decode(b[:byteGroup/2], t[:byteGroup])
if err != nil {
return
}
t = t[byteGroup:]
b = b[byteGroup/2:]
}
return
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (u UUID) MarshalBinary() (data []byte, err error) {
data = u.Bytes()
return
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
// It will return error if the slice isn't 16 bytes long.
func (u *UUID) UnmarshalBinary(data []byte) (err error) {
if len(data) != 16 {
err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data))
return
}
copy(u[:], data)
return
}
// Value implements the driver.Valuer interface.
func (u UUID) Value() (driver.Value, error) {
return u.String(), nil
}
// Scan implements the sql.Scanner interface.
// A 16-byte slice is handled by UnmarshalBinary, while
// a longer byte slice or a string is handled by UnmarshalText.
func (u *UUID) Scan(src interface{}) error {
switch src := src.(type) {
case []byte:
if len(src) == 16 {
return u.UnmarshalBinary(src)
}
return u.UnmarshalText(src)
case string:
return u.UnmarshalText([]byte(src))
}
return fmt.Errorf("uuid: cannot convert %T to UUID", src)
}
// Value implements the driver.Valuer interface.
func (u NullUUID) Value() (driver.Value, error) {
if !u.Valid {
return nil, nil
}
// Delegate to UUID Value function
return u.UUID.Value()
}
// Scan implements the sql.Scanner interface.
func (u *NullUUID) Scan(src interface{}) error {
if src == nil {
u.UUID, u.Valid = Nil, false
return nil
}
// Delegate to UUID Scan function
u.Valid = true
return u.UUID.Scan(src)
}
// FromBytes returns UUID converted from raw byte slice input.
// It will return error if the slice isn't 16 bytes long.
func FromBytes(input []byte) (u UUID, err error) {
err = u.UnmarshalBinary(input)
return
}
// FromBytesOrNil returns UUID converted from raw byte slice input.
// Same behavior as FromBytes, but returns a Nil UUID on error.
func FromBytesOrNil(input []byte) UUID {
uuid, err := FromBytes(input)
if err != nil {
return Nil
}
return uuid
}
// FromString returns UUID parsed from string input.
// Input is expected in a form accepted by UnmarshalText.
func FromString(input string) (u UUID, err error) {
err = u.UnmarshalText([]byte(input))
return
}
// FromStringOrNil returns UUID parsed from string input.
// Same behavior as FromString, but returns a Nil UUID on error.
func FromStringOrNil(input string) UUID {
uuid, err := FromString(input)
if err != nil {
return Nil
}
return uuid
}
// Returns UUID v1/v2 storage state.
// Returns epoch timestamp, clock sequence, and hardware address.
func getStorage() (uint64, uint16, []byte) {
storageOnce.Do(initStorage)
storageMutex.Lock()
defer storageMutex.Unlock()
timeNow := epochFunc()
// Clock changed backwards since last UUID generation.
// Should increase clock sequence.
if timeNow <= lastTime {
clockSequence++
}
lastTime = timeNow
return timeNow, clockSequence, hardwareAddr[:]
}
// NewV1 returns UUID based on current timestamp and MAC address.
func NewV1() UUID {
u := UUID{}
timeNow, clockSeq, hardwareAddr := getStorage()
binary.BigEndian.PutUint32(u[0:], uint32(timeNow))
binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
binary.BigEndian.PutUint16(u[8:], clockSeq)
copy(u[10:], hardwareAddr)
u.SetVersion(1)
u.SetVariant()
return u
}
// NewV2 returns DCE Security UUID based on POSIX UID/GID.
func NewV2(domain byte) UUID {
u := UUID{}
timeNow, clockSeq, hardwareAddr := getStorage()
switch domain {
case DomainPerson:
binary.BigEndian.PutUint32(u[0:], posixUID)
case DomainGroup:
binary.BigEndian.PutUint32(u[0:], posixGID)
}
binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
binary.BigEndian.PutUint16(u[8:], clockSeq)
u[9] = domain
copy(u[10:], hardwareAddr)
u.SetVersion(2)
u.SetVariant()
return u
}
// NewV3 returns UUID based on MD5 hash of namespace UUID and name.
func NewV3(ns UUID, name string) UUID {
u := newFromHash(md5.New(), ns, name)
u.SetVersion(3)
u.SetVariant()
return u
}
// NewV4 returns random generated UUID.
func NewV4() UUID {
u := UUID{}
safeRandom(u[:])
u.SetVersion(4)
u.SetVariant()
return u
}
// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
func NewV5(ns UUID, name string) UUID {
u := newFromHash(sha1.New(), ns, name)
u.SetVersion(5)
u.SetVariant()
return u
}
// Returns UUID based on hashing of namespace UUID and name.
func newFromHash(h hash.Hash, ns UUID, name string) UUID {
u := UUID{}
h.Write(ns[:])
h.Write([]byte(name))
copy(u[:], h.Sum(nil))
return u
}