-
Notifications
You must be signed in to change notification settings - Fork 189
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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]>
- Loading branch information
Showing
36 changed files
with
2,270 additions
and
787 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"fmt" | ||
"log" | ||
"os" | ||
|
||
"github.com/google/uuid" | ||
"github.com/moby/vpnkit/go/pkg/vmnet" | ||
) | ||
|
||
var path string | ||
|
||
func main() { | ||
flag.StringVar(&path, "path", "", "path to vmnet socket") | ||
flag.Parse() | ||
if path == "" { | ||
fmt.Fprintf(os.Stderr, "Please supply a --path argument\n") | ||
} | ||
vm, err := vmnet.Connect(context.Background(), vmnet.Config{ | ||
Path: path, | ||
}) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer vm.Close() | ||
log.Println("connected to vmnet service") | ||
u, err := uuid.NewRandom() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
vif, err := vm.ConnectVif(u) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer vif.Close() | ||
log.Printf("VIF has IP %s", vif.IP) | ||
log.Printf("SOCK_DGRAM fd: %d", vif.Ethernet.Fd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package vmnet | ||
|
||
/* | ||
// FIXME: Needed because we call C.send. Perhaps we could use syscall instead? | ||
#include <stdlib.h> | ||
#include <sys/socket.h> | ||
*/ | ||
import "C" | ||
|
||
import ( | ||
"syscall" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
// Datagram sends and receives ethernet frames via send/recv over a SOCK_DGRAM fd. | ||
type Datagram struct { | ||
Fd int // Underlying SOCK_DGRAM file descriptor. | ||
pcap *PcapWriter | ||
} | ||
|
||
func (e Datagram) Recv(buf []byte) (int, error) { | ||
num, _, err := syscall.Recvfrom(e.Fd, buf, 0) | ||
if e.pcap != nil { | ||
if err := e.pcap.Write(buf[0:num]); err != nil { | ||
return 0, errors.Wrap(err, "writing to pcap") | ||
} | ||
} | ||
return num, err | ||
} | ||
|
||
func (e Datagram) Send(packet []byte) (int, error) { | ||
if e.pcap != nil { | ||
if err := e.pcap.Write(packet); err != nil { | ||
return 0, errors.Wrap(err, "writing to pcap") | ||
} | ||
} | ||
result, err := C.send(C.int(e.Fd), C.CBytes(packet), C.size_t(len(packet)), 0) | ||
if result == -1 { | ||
return 0, err | ||
} | ||
return len(packet), nil | ||
} | ||
|
||
func (e Datagram) Close() error { | ||
return syscall.Close(e.Fd) | ||
} | ||
|
||
var _ sendReceiver = Datagram{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package vmnet | ||
|
||
import ( | ||
"net" | ||
"time" | ||
) | ||
|
||
// dhcp queries the IP by DHCP | ||
func dhcpRequest(packet sendReceiver, clientMAC net.HardwareAddr) (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(clientMAC).Bytes() | ||
ipv4 := NewIpv4(broadcastIP, unknownIP) | ||
|
||
udpv4 := NewUdpv4(ipv4, 68, 67, dhcpRequest) | ||
ipv4.setData(udpv4.Bytes()) | ||
|
||
ethernet := NewEthernetFrame(broadcastMAC, clientMAC, 0x800) | ||
ethernet.setData(ipv4.Bytes()) | ||
finished := false | ||
go func() { | ||
for !finished { | ||
if _, err := packet.Send(ethernet.Bytes()); err != nil { | ||
panic(err) | ||
} | ||
time.Sleep(time.Second) | ||
} | ||
}() | ||
|
||
buf := make([]byte, 1500) | ||
for { | ||
n, err := packet.Recv(buf) | ||
if err != nil { | ||
return nil, err | ||
} | ||
response := buf[0:n] | ||
ethernet, err = ParseEthernetFrame(response) | ||
if err != nil { | ||
continue | ||
} | ||
for i, x := range ethernet.Dst { | ||
if i > len(clientMAC) || 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 | ||
} | ||
} | ||
|
||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package vmnet | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"io" | ||
"net" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
// 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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package vmnet | ||
|
||
import ( | ||
"encoding/binary" | ||
"io" | ||
) | ||
|
||
// Messages sent to vpnkit can either be | ||
// - fixed-size, no length prefix | ||
// - variable-length, with a length prefix | ||
|
||
// fixedSizeSendReceiver sends and receives fixed-size control messages with no length prefix. | ||
type fixedSizeSendReceiver struct { | ||
rw io.ReadWriter | ||
} | ||
|
||
var _ sendReceiver = fixedSizeSendReceiver{} | ||
|
||
func (f fixedSizeSendReceiver) Recv(buf []byte) (int, error) { | ||
return io.ReadFull(f.rw, buf) | ||
} | ||
|
||
func (f fixedSizeSendReceiver) Send(buf []byte) (int, error) { | ||
return f.rw.Write(buf) | ||
} | ||
|
||
// lengthPrefixer sends and receives variable-length control messages with a length prefix. | ||
type lengthPrefixer struct { | ||
rw io.ReadWriter | ||
} | ||
|
||
var _ sendReceiver = lengthPrefixer{} | ||
|
||
func (e lengthPrefixer) Recv(buf []byte) (int, error) { | ||
var len uint16 | ||
if err := binary.Read(e.rw, binary.LittleEndian, &len); err != nil { | ||
return 0, err | ||
} | ||
if err := binary.Read(e.rw, binary.LittleEndian, &buf); err != nil { | ||
return 0, err | ||
} | ||
return int(len), nil | ||
} | ||
|
||
func (e lengthPrefixer) Send(packet []byte) (int, error) { | ||
len := uint16(len(packet)) | ||
if err := binary.Write(e.rw, binary.LittleEndian, len); err != nil { | ||
return 0, err | ||
} | ||
if err := binary.Write(e.rw, binary.LittleEndian, packet); err != nil { | ||
return 0, err | ||
} | ||
return int(len), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package vmnet | ||
|
||
import ( | ||
"net" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
// 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...) | ||
} |
Oops, something went wrong.