diff --git a/dhcp/dhcp.go b/dhcp/dhcp.go index f1d17d1..cab1ee0 100644 --- a/dhcp/dhcp.go +++ b/dhcp/dhcp.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "net" + "sort" ) var magic = []byte{99, 130, 83, 99} @@ -68,6 +69,40 @@ type Packet struct { Options Options } +func (p *Packet) testString() string { + var b bytes.Buffer + bcast := "Unicast" + if p.Broadcast { + bcast = "Broadcast" + } + fmt.Fprintf(&b, `===== +%s + %#v + %s + MAC: %s + ClientIP: %s + YourIP: %s + RelayIP: %s + + BootServerIP: %s + BootServerName: %s + BootFilename: %s + + Options: +`, p.Type, p.TransactionID, bcast, p.HardwareAddr, p.ClientAddr, p.YourAddr, p.RelayAddr, p.BootServerAddr, p.BootServerName, p.BootFilename) + + var opts []int + for n := range p.Options { + opts = append(opts, n) + } + sort.Ints(opts) + for _, n := range opts { + fmt.Fprintf(&b, " %d: %#v\n", n, p.Options[n]) + } + b.WriteString("=====\n") + return b.String() +} + // Marshal returns the wire encoding of p. func (p *Packet) Marshal() ([]byte, error) { if len(p.TransactionID) != 4 { diff --git a/dhcp/dhcp_test.go b/dhcp/dhcp_test.go new file mode 100644 index 0000000..1e21df5 --- /dev/null +++ b/dhcp/dhcp_test.go @@ -0,0 +1,73 @@ +package dhcp + +import ( + "bytes" + "errors" + "io/ioutil" + "os" + "testing" + + "go.universe.tf/netboot/pcap" +) + +func udpFromPcap(fname string) ([][]byte, error) { + f, err := os.Open(fname) + if err != nil { + return nil, err + } + r, err := pcap.NewReader(f) + if err != nil { + return nil, err + } + + if r.LinkType != pcap.LinkEthernet { + return nil, errors.New("Pcap packets are not ethernet") + } + + ret := [][]byte{} + for r.Next() { + // Assume here that the packets are UDPv4, and just chop off + // the headers in front of the UDP payload + pkt := r.Packet() + hdrLen := 14 // Ethernet header + hdrLen += int(pkt.Bytes[hdrLen]&0xF) * 4 // IP header + hdrLen += 8 // UDP header + ret = append(ret, pkt.Bytes[hdrLen:]) + } + if r.Err() != nil { + return nil, r.Err() + } + + return ret, nil +} + +func TestParse(t *testing.T) { + rawPkts, err := udpFromPcap("testdata/dhcp.pcap") + if err != nil { + t.Fatalf("Getting test packets from pcap: %s", err) + } + + var pkts bytes.Buffer + for i, rawPkt := range rawPkts { + pkt, err := Unmarshal(rawPkt) + if err != nil { + t.Fatalf("Parsing DHCP packet #%d: %s", i+1, err) + } + pkts.WriteString(pkt.testString()) + } + + expectedFile := "testdata/dhcp.parsed" + expected, err := ioutil.ReadFile(expectedFile) + if err != nil { + t.Fatalf("Reading expected file: %s", err) + } + + if pkts.String() != string(expected) { + if os.Getenv("UPDATE_TESTDATA") != "" { + ioutil.WriteFile(expectedFile, pkts.Bytes(), 0644) + t.Errorf("dhcp.pcap didn't decode to dhcp.parsed (updated dhcp.parsed)") + } else { + t.Fatalf("dhcp.pcap didn't decode to dhcp.parsed (rerun with UPDATE_TESTDATA=1 to get diff)") + } + } +} diff --git a/dhcp/testdata/dhcp.parsed b/dhcp/testdata/dhcp.parsed new file mode 100644 index 0000000..014621a --- /dev/null +++ b/dhcp/testdata/dhcp.parsed @@ -0,0 +1,68 @@ +===== +DHCPDISCOVER + "\x9bN\x05W" + Broadcast + MAC: d0:50:99:4e:05:57 + ClientIP: 0.0.0.0 + YourIP: 0.0.0.0 + RelayIP: 0.0.0.0 + + BootServerIP: 0.0.0.0 + BootServerName: + BootFilename: + + Options: + 53: []byte{0x1} + 55: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0xb, 0xc, 0xd, 0xf, 0x10, 0x11, 0x12, 0x16, 0x17, 0x1c, 0x28, 0x29, 0x2a, 0x2b, 0x32, 0x33, 0x36, 0x3a, 0x3b, 0x3c, 0x42, 0x43, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87} + 57: []byte{0x4, 0xec} + 60: []byte{0x50, 0x58, 0x45, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x3a, 0x41, 0x72, 0x63, 0x68, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3a, 0x55, 0x4e, 0x44, 0x49, 0x3a, 0x30, 0x30, 0x32, 0x30, 0x30, 0x31} + 93: []byte{0x0, 0x0} + 94: []byte{0x1, 0x2, 0x1} + 97: []byte{0x0, 0x0, 0x2, 0x0, 0x3, 0x0, 0x4, 0x0, 0x5, 0x0, 0x6, 0x0, 0x7, 0x0, 0x8, 0x0, 0x9} +===== +===== +DHCPOFFER + "\x9bN\x05W" + Broadcast + MAC: d0:50:99:4e:05:57 + ClientIP: 0.0.0.0 + YourIP: 0.0.0.0 + RelayIP: 0.0.0.0 + + BootServerIP: 0.0.0.0 + BootServerName: + BootFilename: + + Options: + 43: []byte{0x6, 0x1, 0x3, 0x8, 0x7, 0x80, 0x0, 0x1, 0xc0, 0xa8, 0x10, 0xa, 0x9, 0xc, 0x80, 0x0, 0x9, 0x50, 0x69, 0x78, 0x69, 0x65, 0x63, 0x6f, 0x72, 0x65, 0xa, 0xa, 0x0, 0x50, 0x69, 0x78, 0x69, 0x65, 0x63, 0x6f, 0x72, 0x65, 0xff} + 53: []byte{0x2} + 54: []byte{0xc0, 0xa8, 0x10, 0xa} + 60: []byte{0x50, 0x58, 0x45, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74} + 97: []byte{0x0, 0x0, 0x2, 0x0, 0x3, 0x0, 0x4, 0x0, 0x5, 0x0, 0x6, 0x0, 0x7, 0x0, 0x8, 0x0, 0x9} +===== +===== +DHCPOFFER + "\x9bN\x05W" + Broadcast + MAC: d0:50:99:4e:05:57 + ClientIP: 0.0.0.0 + YourIP: 192.168.16.12 + RelayIP: 0.0.0.0 + + BootServerIP: 192.168.16.1 + BootServerName: + BootFilename: + + Options: + 1: []byte{0xff, 0xff, 0xff, 0x0} + 3: []byte{0xc0, 0xa8, 0x10, 0x1} + 6: []byte{0xc0, 0xa8, 0x10, 0x1} + 12: []byte{0x63, 0x6f, 0x72, 0x65, 0x30, 0x31} + 15: []byte{0x68, 0x6f, 0x6d, 0x65, 0x2e, 0x75, 0x6e, 0x69, 0x76, 0x65, 0x72, 0x73, 0x65, 0x2e, 0x74, 0x66} + 28: []byte{0xc0, 0xa8, 0x10, 0xff} + 51: []byte{0x0, 0x0, 0xe, 0x10} + 53: []byte{0x2} + 54: []byte{0xc0, 0xa8, 0x10, 0x1} + 58: []byte{0x0, 0x0, 0x7, 0x8} + 59: []byte{0x0, 0x0, 0xc, 0x4e} +===== diff --git a/dhcp/testdata/dhcp.pcap b/dhcp/testdata/dhcp.pcap new file mode 100644 index 0000000..387e87f Binary files /dev/null and b/dhcp/testdata/dhcp.pcap differ