Skip to content

Commit 0b6e161

Browse files
committed
Add support for AF_UNIX SOCK_DGRAM
Unfortunately these aren't supported by libuv directly, so we need to implement our own separate system. It doesn't need to be particularly scalable as the expected use-case is to send and receive ethernet frames. The server will listen on a Unix domain socket and receive connected SOCK_DGRAM sockets which will have ethernet frames on them. Signed-off-by: David Scott <[email protected]>
1 parent a7040f6 commit 0b6e161

34 files changed

+2055
-781
lines changed

go/cmd/vmnet-example/main.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
"log"
8+
"os"
9+
10+
"github.com/google/uuid"
11+
"github.com/moby/vpnkit/go/pkg/vmnet"
12+
)
13+
14+
var path string
15+
16+
func main() {
17+
flag.StringVar(&path, "path", "", "path to vmnet socket")
18+
flag.Parse()
19+
if path == "" {
20+
fmt.Fprintf(os.Stderr, "Please supply a --path argument\n")
21+
}
22+
vm, err := vmnet.Connect(context.Background(), vmnet.Config{
23+
Path: path,
24+
})
25+
if err != nil {
26+
log.Fatal(err)
27+
}
28+
defer vm.Close()
29+
log.Println("connected to vmnet service")
30+
u, err := uuid.NewRandom()
31+
if err != nil {
32+
log.Fatal(err)
33+
}
34+
vif, err := vm.ConnectVif(u)
35+
if err != nil {
36+
log.Fatal(err)
37+
}
38+
defer vif.Close()
39+
log.Printf("VIF has IP %s", vif.IP)
40+
log.Printf("SOCK_DGRAM fd: %d", vif.Ethernet.Fd)
41+
}

go/pkg/vmnet/datagram.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package vmnet
2+
3+
/*
4+
// FIXME: Needed because we call C.send. Perhaps we could use syscall instead?
5+
#include <stdlib.h>
6+
#include <sys/socket.h>
7+
8+
*/
9+
import "C"
10+
11+
import (
12+
"syscall"
13+
14+
"github.com/pkg/errors"
15+
)
16+
17+
// Datagram sends and receives ethernet frames via send/recv over a SOCK_DGRAM fd.
18+
type Datagram struct {
19+
Fd int // Underlying SOCK_DGRAM file descriptor.
20+
pcap *PcapWriter
21+
}
22+
23+
func (e Datagram) Recv(buf []byte) (int, error) {
24+
num, _, err := syscall.Recvfrom(e.Fd, buf, 0)
25+
if e.pcap != nil {
26+
if err := e.pcap.Write(buf[0:num]); err != nil {
27+
return 0, errors.Wrap(err, "writing to pcap")
28+
}
29+
}
30+
return num, err
31+
}
32+
33+
func (e Datagram) Send(packet []byte) (int, error) {
34+
if e.pcap != nil {
35+
if err := e.pcap.Write(packet); err != nil {
36+
return 0, errors.Wrap(err, "writing to pcap")
37+
}
38+
}
39+
result, err := C.send(C.int(e.Fd), C.CBytes(packet), C.size_t(len(packet)), 0)
40+
if result == -1 {
41+
return 0, err
42+
}
43+
return len(packet), nil
44+
}
45+
46+
func (e Datagram) Close() error {
47+
return syscall.Close(e.Fd)
48+
}
49+
50+
var _ sendReceiver = Datagram{}

go/pkg/vmnet/dhcp.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package vmnet
2+
3+
import (
4+
"net"
5+
"time"
6+
)
7+
8+
// dhcp queries the IP by DHCP
9+
func dhcpRequest(packet sendReceiver, clientMAC net.HardwareAddr) (net.IP, error) {
10+
broadcastMAC := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
11+
broadcastIP := []byte{0xff, 0xff, 0xff, 0xff}
12+
unknownIP := []byte{0, 0, 0, 0}
13+
14+
dhcpRequest := NewDhcpRequest(clientMAC).Bytes()
15+
ipv4 := NewIpv4(broadcastIP, unknownIP)
16+
17+
udpv4 := NewUdpv4(ipv4, 68, 67, dhcpRequest)
18+
ipv4.setData(udpv4.Bytes())
19+
20+
ethernet := NewEthernetFrame(broadcastMAC, clientMAC, 0x800)
21+
ethernet.setData(ipv4.Bytes())
22+
finished := false
23+
go func() {
24+
for !finished {
25+
if _, err := packet.Send(ethernet.Bytes()); err != nil {
26+
panic(err)
27+
}
28+
time.Sleep(time.Second)
29+
}
30+
}()
31+
32+
buf := make([]byte, 1500)
33+
for {
34+
n, err := packet.Recv(buf)
35+
if err != nil {
36+
return nil, err
37+
}
38+
response := buf[0:n]
39+
ethernet, err = ParseEthernetFrame(response)
40+
if err != nil {
41+
continue
42+
}
43+
for i, x := range ethernet.Dst {
44+
if i > len(clientMAC) || clientMAC[i] != x {
45+
// intended for someone else
46+
continue
47+
}
48+
}
49+
ipv4, err = ParseIpv4(ethernet.Data)
50+
if err != nil {
51+
// probably not an IPv4 packet
52+
continue
53+
}
54+
udpv4, err = ParseUdpv4(ipv4.Data)
55+
if err != nil {
56+
// probably not a UDPv4 packet
57+
continue
58+
}
59+
if udpv4.Src != 67 || udpv4.Dst != 68 {
60+
// not a DHCP response
61+
continue
62+
}
63+
if len(udpv4.Data) < 243 {
64+
// truncated
65+
continue
66+
}
67+
if udpv4.Data[240] != 53 || udpv4.Data[241] != 1 || udpv4.Data[242] != 2 {
68+
// not a DHCP offer
69+
continue
70+
}
71+
var ip net.IP
72+
ip = udpv4.Data[16:20]
73+
finished = true // will terminate sending goroutine
74+
return ip, nil
75+
}
76+
}
77+
78+
// DhcpRequest is a simple DHCP request
79+
type DhcpRequest struct {
80+
MAC net.HardwareAddr
81+
}
82+
83+
// NewDhcpRequest constructs a DHCP request
84+
func NewDhcpRequest(MAC net.HardwareAddr) *DhcpRequest {
85+
if len(MAC) != 6 {
86+
panic("MAC address must be 6 bytes")
87+
}
88+
return &DhcpRequest{MAC}
89+
}
90+
91+
// Bytes returns the marshalled DHCP request
92+
func (d *DhcpRequest) Bytes() []byte {
93+
bs := []byte{
94+
0x01, // OP
95+
0x01, // HTYPE
96+
0x06, // HLEN
97+
0x00, // HOPS
98+
0x01, 0x00, 0x00, 0x00, // XID
99+
0x00, 0x00, // SECS
100+
0x80, 0x00, // FLAGS
101+
0x00, 0x00, 0x00, 0x00, // CIADDR
102+
0x00, 0x00, 0x00, 0x00, // YIADDR
103+
0x00, 0x00, 0x00, 0x00, // SIADDR
104+
0x00, 0x00, 0x00, 0x00, // GIADDR
105+
d.MAC[0], d.MAC[1], d.MAC[2], d.MAC[3], d.MAC[4], d.MAC[5],
106+
}
107+
bs = append(bs, make([]byte, 202)...)
108+
bs = append(bs, []byte{
109+
0x63, 0x82, 0x53, 0x63, // Magic cookie
110+
0x35, 0x01, 0x01, // DHCP discover
111+
0xff, // Endmark
112+
}...)
113+
return bs
114+
}

go/pkg/vmnet/ethernet.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package vmnet
2+
3+
import (
4+
"bytes"
5+
"encoding/binary"
6+
"io"
7+
"net"
8+
9+
"github.com/pkg/errors"
10+
)
11+
12+
// EthernetFrame is an ethernet frame
13+
type EthernetFrame struct {
14+
Dst net.HardwareAddr
15+
Src net.HardwareAddr
16+
Type uint16
17+
Data []byte
18+
}
19+
20+
// NewEthernetFrame constructs an Ethernet frame
21+
func NewEthernetFrame(Dst, Src net.HardwareAddr, Type uint16) *EthernetFrame {
22+
Data := make([]byte, 0)
23+
return &EthernetFrame{Dst, Src, Type, Data}
24+
}
25+
26+
func (e *EthernetFrame) setData(data []byte) {
27+
e.Data = data
28+
}
29+
30+
// Write marshals an Ethernet frame
31+
func (e *EthernetFrame) Write(w io.Writer) error {
32+
if err := binary.Write(w, binary.BigEndian, e.Dst); err != nil {
33+
return err
34+
}
35+
if err := binary.Write(w, binary.BigEndian, e.Src); err != nil {
36+
return err
37+
}
38+
if err := binary.Write(w, binary.BigEndian, e.Type); err != nil {
39+
return err
40+
}
41+
if err := binary.Write(w, binary.BigEndian, e.Data); err != nil {
42+
return err
43+
}
44+
return nil
45+
}
46+
47+
// ParseEthernetFrame parses the ethernet frame
48+
func ParseEthernetFrame(frame []byte) (*EthernetFrame, error) {
49+
if len(frame) < (6 + 6 + 2) {
50+
return nil, errors.New("Ethernet frame is too small")
51+
}
52+
Dst := frame[0:6]
53+
Src := frame[6:12]
54+
Type := uint16(frame[12])<<8 + uint16(frame[13])
55+
Data := frame[14:]
56+
return &EthernetFrame{Dst, Src, Type, Data}, nil
57+
}
58+
59+
// Bytes returns the marshalled ethernet frame
60+
func (e *EthernetFrame) Bytes() []byte {
61+
buf := bytes.NewBufferString("")
62+
if err := e.Write(buf); err != nil {
63+
panic(err)
64+
}
65+
return buf.Bytes()
66+
}

go/pkg/vmnet/framing.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package vmnet
2+
3+
import (
4+
"encoding/binary"
5+
"io"
6+
)
7+
8+
// Messages sent to vpnkit can either be
9+
// - fixed-size, no length prefix
10+
// - variable-length, with a length prefix
11+
12+
// fixedSizeSendReceiver sends and receives fixed-size control messages with no length prefix.
13+
type fixedSizeSendReceiver struct {
14+
rw io.ReadWriter
15+
}
16+
17+
var _ sendReceiver = fixedSizeSendReceiver{}
18+
19+
func (f fixedSizeSendReceiver) Recv(buf []byte) (int, error) {
20+
return io.ReadFull(f.rw, buf)
21+
}
22+
23+
func (f fixedSizeSendReceiver) Send(buf []byte) (int, error) {
24+
return f.rw.Write(buf)
25+
}
26+
27+
// lengthPrefixer sends and receives variable-length control messages with a length prefix.
28+
type lengthPrefixer struct {
29+
rw io.ReadWriter
30+
}
31+
32+
var _ sendReceiver = lengthPrefixer{}
33+
34+
func (e lengthPrefixer) Recv(buf []byte) (int, error) {
35+
var len uint16
36+
if err := binary.Read(e.rw, binary.LittleEndian, &len); err != nil {
37+
return 0, err
38+
}
39+
if err := binary.Read(e.rw, binary.LittleEndian, &buf); err != nil {
40+
return 0, err
41+
}
42+
return int(len), nil
43+
}
44+
45+
func (e lengthPrefixer) Send(packet []byte) (int, error) {
46+
len := uint16(len(packet))
47+
if err := binary.Write(e.rw, binary.LittleEndian, len); err != nil {
48+
return 0, err
49+
}
50+
if err := binary.Write(e.rw, binary.LittleEndian, packet); err != nil {
51+
return 0, err
52+
}
53+
return int(len), nil
54+
}

go/pkg/vmnet/ipv4.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package vmnet
2+
3+
import (
4+
"net"
5+
6+
"github.com/pkg/errors"
7+
)
8+
9+
// Ipv4 is an IPv4 frame
10+
type Ipv4 struct {
11+
Dst net.IP
12+
Src net.IP
13+
Data []byte
14+
Checksum uint16
15+
}
16+
17+
// NewIpv4 constructs a new empty IPv4 packet
18+
func NewIpv4(Dst, Src net.IP) *Ipv4 {
19+
Checksum := uint16(0)
20+
Data := make([]byte, 0)
21+
return &Ipv4{Dst, Src, Data, Checksum}
22+
}
23+
24+
// ParseIpv4 parses an IP packet
25+
func ParseIpv4(packet []byte) (*Ipv4, error) {
26+
if len(packet) < 20 {
27+
return nil, errors.New("IPv4 packet too small")
28+
}
29+
ihl := int((packet[0] & 0xf) * 4) // in octets
30+
if len(packet) < ihl {
31+
return nil, errors.New("IPv4 packet too small")
32+
}
33+
Dst := packet[12:16]
34+
Src := packet[16:20]
35+
Data := packet[ihl:]
36+
Checksum := uint16(0) // assume offload
37+
return &Ipv4{Dst, Src, Data, Checksum}, nil
38+
}
39+
40+
func (i *Ipv4) setData(data []byte) {
41+
i.Data = data
42+
i.Checksum = uint16(0) // as if we were using offload
43+
}
44+
45+
// HeaderBytes returns the marshalled form of the IPv4 header
46+
func (i *Ipv4) HeaderBytes() []byte {
47+
len := len(i.Data) + 20
48+
length := [2]byte{byte(len >> 8), byte(len & 0xff)}
49+
checksum := [2]byte{byte(i.Checksum >> 8), byte(i.Checksum & 0xff)}
50+
return []byte{
51+
0x45, // version + IHL
52+
0x00, // DSCP + ECN
53+
length[0], length[1], // total length
54+
0x7f, 0x61, // Identification
55+
0x00, 0x00, // Flags + Fragment offset
56+
0x40, // TTL
57+
0x11, // Protocol
58+
checksum[0], checksum[1],
59+
0x00, 0x00, 0x00, 0x00, // source
60+
0xff, 0xff, 0xff, 0xff, // destination
61+
}
62+
}
63+
64+
// Bytes returns the marshalled IPv4 packet
65+
func (i *Ipv4) Bytes() []byte {
66+
header := i.HeaderBytes()
67+
return append(header, i.Data...)
68+
}

0 commit comments

Comments
 (0)