Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for AF_UNIX SOCK_DGRAM #602

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions go/cmd/vmnet-example/main.go
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)
}
50 changes: 50 additions & 0 deletions go/pkg/vmnet/datagram.go
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{}
114 changes: 114 additions & 0 deletions go/pkg/vmnet/dhcp.go
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
}
66 changes: 66 additions & 0 deletions go/pkg/vmnet/ethernet.go
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()
}
54 changes: 54 additions & 0 deletions go/pkg/vmnet/framing.go
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
}
68 changes: 68 additions & 0 deletions go/pkg/vmnet/ipv4.go
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...)
}