diff --git a/cmd/pixiecore/main.go b/cmd/pixiecore/main.go index 0d0bde0..1ebeb26 100644 --- a/cmd/pixiecore/main.go +++ b/cmd/pixiecore/main.go @@ -16,15 +16,30 @@ package main import ( "go.universe.tf/netboot/pixiecore" - "go.universe.tf/netboot/pixiecore/cli" - "go.universe.tf/netboot/third_party/ipxe" +// "go.universe.tf/netboot/pixiecore/cli" +// "go.universe.tf/netboot/third_party/ipxe" + "fmt" ) +// qemu-system-x86_64 -L . --bios /usr/share/edk2-firmware/ipv6/OVMF.fd -netdev bridge,br=br1,id=net0 -device virtio-net-pci,netdev=net0 func main() { - cli.Ipxe[pixiecore.FirmwareX86PC] = ipxe.MustAsset("undionly.kpxe") + /*cli.Ipxe[pixiecore.FirmwareX86PC] = ipxe.MustAsset("undionly.kpxe") cli.Ipxe[pixiecore.FirmwareEFI32] = ipxe.MustAsset("ipxe-i386.efi") cli.Ipxe[pixiecore.FirmwareEFI64] = ipxe.MustAsset("ipxe-x86_64.efi") cli.Ipxe[pixiecore.FirmwareEFIBC] = ipxe.MustAsset("ipxe-x86_64.efi") cli.Ipxe[pixiecore.FirmwareX86Ipxe] = ipxe.MustAsset("ipxe.pxe") cli.CLI() +*/ + + log := func(subsystem, msg string) { fmt.Printf("[%s] %s", subsystem, msg) } + s := pixiecore.ServerV6{ + Address: "2001:db8:f00f:cafe::4/64", + Log: log, + Debug: log, + } + + err := s.Serve() + if err != nil { + fmt.Printf("Error: %s", err) + } } diff --git a/dhcp6/conn.go b/dhcp6/conn.go new file mode 100644 index 0000000..9251d43 --- /dev/null +++ b/dhcp6/conn.go @@ -0,0 +1,114 @@ +package dhcp6 + +import ( + "io" + "net" + "time" + "golang.org/x/net/ipv6" + "fmt" +) + +type conn interface { + io.Closer + Recv([]byte) (b []byte, addr *net.UDPAddr, ifidx int, err error) + Send(b []byte, addr *net.UDPAddr, ifidx int) error + SetReadDeadline(t time.Time) error + SetWriteDeadline(t time.Time) error +} + +type Conn struct { + conn *ipv6.PacketConn + group net.IP + ifi *net.Interface + listenAddress string + listenPort string +} + +func NewConn(addr string) (*Conn, error) { + ifi, err := InterfaceIndexByAddress(addr) + if err != nil { + return nil, err + } + + group := net.ParseIP("ff02::1:2") + c, err := net.ListenPacket("udp6", "[::]:547") + if err != nil { + return nil, err + } + pc := ipv6.NewPacketConn(c) + if err := pc.JoinGroup(ifi, &net.UDPAddr{IP: group}); err != nil { + pc.Close() + return nil, err + } + + if err := pc.SetControlMessage(ipv6.FlagSrc | ipv6.FlagDst, true); err != nil { + pc.Close() + return nil, err + } + + return &Conn{ + conn: pc, + group: group, + ifi: ifi, + listenAddress: addr, + listenPort: "547", + }, nil +} + +func (c *Conn) Close() error { + return c.conn.Close() +} + +func InterfaceIndexByAddress(ifAddr string) (*net.Interface, error) { + allIfis, err := net.Interfaces() + if err != nil { + return nil, fmt.Errorf("Error getting network interface information: %s", err) + } + for _, ifi := range allIfis { + addrs, err := ifi.Addrs() + if err != nil { + return nil, fmt.Errorf("Error getting network interface address information: %s", err) + } + for _, addr := range addrs { + if addr.String() == ifAddr { + return &ifi, nil + } + } + } + return nil, fmt.Errorf("Couldn't find an interface with address %s", ifAddr) +} + +func (c *Conn) RecvDHCP() (*Packet, net.IP, error) { + b := make([]byte, 1500) + for { + packetSize, rcm, _, err := c.conn.ReadFrom(b) + if err != nil { + return nil, nil, err + } + if c.ifi.Index != 0 && rcm.IfIndex != c.ifi.Index { + continue + } + if !rcm.Dst.IsMulticast() || !rcm.Dst.Equal(c.group) { + continue // unknown group, discard + } + pkt := MakePacket(b, packetSize) + + return pkt, rcm.Src, nil + } +} + +func (c *Conn) SendDHCP(dst net.IP, p []byte) error { + dstAddr, err := net.ResolveUDPAddr("udp6", fmt.Sprintf("[%s]:%s", dst.String() + "%en0", "546")) + if err != nil { + return fmt.Errorf("Error resolving ipv6 address %s: %s", dst.String(), err) + } + _, err = c.conn.WriteTo(p, nil, dstAddr) + if err != nil { + return fmt.Errorf("Error sending a reply to %s: %s", dst.String(), err) + } + return nil +} + +func (c *Conn) SourceHardwareAddress() net.HardwareAddr { + return c.ifi.HardwareAddr +} \ No newline at end of file diff --git a/dhcp6/options.go b/dhcp6/options.go new file mode 100644 index 0000000..5e278a3 --- /dev/null +++ b/dhcp6/options.go @@ -0,0 +1,167 @@ +package dhcp6 + +import ( + "encoding/binary" + "fmt" + "bytes" + "net" +) + +const ( + OptClientId uint16 = 1 // IPMask + OptServerId = 2 // int32 + OptIaNa = 3 // IPs + OptIaTa = 4 // IPs + OptIaAddr = 5 // string + OptOro = 6 // uint16 + OptPreference = 7 // string + OptElapsedTime = 8 // IP + OptRelayMessage = 9 // IP + OptAuth = 11 // []byte + OptUnicast = 12 // IP + OptStatusCode = 13 // uint32 + OptRapidCommit = 14 // byte + OptUserClass = 15 // IP + OptVendorClass = 16 // []byte + OptVendorOpts = 17 // string + OptInterfaceId = 18 // uint16 + OptReconfMsg = 19 // uint32 + OptReconfAccept = 20 // uint32 + OptRecursiveDns = 23 // []byte + OptBootfileUrl = 59 + OptBootfileParam = 60 //[][]byte + OptClientArchType = 61 //[][]byte, sent by the client + // 24? Domain search list +) + +type Option struct { + Id uint16 + Length uint16 + Value []byte +} + +type Options map[uint16]*Option + +func MakeOptions(bs []byte) (Options, error) { + to_ret := make(Options) + for len(bs) > 0 { + optionLength := uint16(binary.BigEndian.Uint16(bs[2:4])) + optionId := uint16(binary.BigEndian.Uint16(bs[0:2])) + switch optionId { + // parse client_id + // parse server_id + // parse IaNa # do I need to support IaTa? + //parse ipaddr + case OptOro: + if optionLength% 2 != 0 { + return nil, fmt.Errorf("OptionID request for options (6) length should be even number of bytes: %d", optionLength) + } + default: + if len(bs[4:]) < int(optionLength) { + fmt.Printf("option %d claims to have %d bytes of payload, but only has %d bytes", optionId, optionLength, len(bs[4:])) + return nil, fmt.Errorf("option %d claims to have %d bytes of payload, but only has %d bytes", optionId, optionLength, len(bs[4:])) + } + } + to_ret[optionId] = &Option{ Id: optionId, Length: optionLength, Value: bs[4 : 4+optionLength]} + bs = bs[4+optionLength:] + } + return to_ret, nil +} + +func (o Options) HumanReadable() []string { + to_ret := make([]string, 0, len(o)) + for _, opt := range(o) { + to_ret = append(to_ret, fmt.Sprintf("Option: %d | %d | %d | %s\n", opt.Id, opt.Length, opt.Value, opt.Value)) + } + return to_ret +} + +func (o Options) AddOption(option *Option) { + o[option.Id] = option +} + +func MakeIaNaOption(iaid []byte, t1, t2 uint32, iaAddr *Option) (*Option) { + serializedIaAddr, _ := iaAddr.Marshal() + value := make([]byte, 12 + len(serializedIaAddr)) + copy(value[0:], iaid[0:4]) + binary.BigEndian.PutUint32(value[4:], t1) + binary.BigEndian.PutUint32(value[8:], t2) + copy(value[12:], serializedIaAddr) + return &Option{Id: OptIaNa, Length: uint16(len(value)), Value: value} +} + +func MakeIaAddrOption(addr net.IP, preferredLifetime, validLifetime uint32) (*Option) { + value := make([]byte, 24) + copy(value[0:], addr) + binary.BigEndian.PutUint32(value[16:], preferredLifetime) + binary.BigEndian.PutUint32(value[20:], validLifetime) + return &Option{ Id: OptIaAddr, Length: uint16(len(value)), Value: value} +} + +func (o Options) Marshal() ([]byte, error) { + buffer := bytes.NewBuffer(make([]byte, 0, 1446)) + for _, v := range(o) { + serialized, err := v.Marshal() + if err != nil { + return nil, fmt.Errorf("Error serializing option value: %s", err) + } + if err := binary.Write(buffer, binary.BigEndian, serialized); err != nil { + return nil, fmt.Errorf("Error serializing option value: %s", err) + } + } + return buffer.Bytes(), nil +} + +func (o *Option) Marshal() ([]byte, error) { + buffer := bytes.NewBuffer(make([]byte, 0, o.Length + 2)) + + err := binary.Write(buffer, binary.BigEndian, o.Id) + if err != nil { + return nil, fmt.Errorf("Error serializing option id: %s", err) + } + err = binary.Write(buffer, binary.BigEndian, o.Length) + if err != nil { + return nil, fmt.Errorf("Error serializing option length: %s", err) + } + err = binary.Write(buffer, binary.BigEndian, o.Value) + if err != nil { + return nil, fmt.Errorf("Error serializing option value: %s", err) + } + return buffer.Bytes(), nil +} + +func (o Options) UnmarshalOptionRequestOption() map[uint16]bool { + oro_content := o[OptOro].Value + to_ret := make(map[uint16]bool) + + for i := 0; i < int(o[OptOro].Length)/2; i++ { + to_ret[uint16(binary.BigEndian.Uint16(oro_content[i*2:(i+1)*2]))] = true + } + return to_ret +} + +func (o Options) RequestedBootFileUrlOption() bool { + requested_options := o.UnmarshalOptionRequestOption() + _, present := requested_options[OptBootfileUrl] + return present +} + +func (o Options) HasClientId() bool { + _, present := o[OptClientId] + return present +} + +func (o Options) HasServerId() bool { + _, present := o[OptServerId] + return present +} + +func (o Options) HasIaNa() bool { + _, present := o[OptIaNa] + return present +} + +func (o Options) HasIaTa() bool { + _, present := o[OptIaTa] + return present +} \ No newline at end of file diff --git a/dhcp6/options_test.go b/dhcp6/options_test.go new file mode 100644 index 0000000..297607a --- /dev/null +++ b/dhcp6/options_test.go @@ -0,0 +1,37 @@ +package dhcp6 + +import ( + "testing" +) + + + +func TestUnmarshalFailsIfOROLengthIsOdd(t *testing.T) { + in := []byte{0, 6, 0, 3, 0, 1, 1} + if _, err := MakeOptions(in); err == nil { + t.Fatalf("Parsing options should fail: option request for options has odd length.") + } +} + +func TestUnmarshalORO(t *testing.T) { + options := Options{ + 6: [][]byte{{0, 1, 0, 5, 1, 0}}, + } + parsed_options := options.UnmarshalOptionRequestOption() + + if l := len(parsed_options); l != 3 { + t.Fatalf("Expected 3 options, got: %d", l) + } + + if _, present := parsed_options[1]; !present { + t.Fatalf("Should contain option id 1") + } + + if _, present := parsed_options[5]; !present { + t.Fatalf("Should contain option id 5") + } + + if _, present := parsed_options[256]; !present { + t.Fatalf("Should contain option id 256") + } +} diff --git a/dhcp6/packet.go b/dhcp6/packet.go new file mode 100644 index 0000000..fbedb6e --- /dev/null +++ b/dhcp6/packet.go @@ -0,0 +1,215 @@ +package dhcp6 + +import ( + "fmt" + "net" + "encoding/binary" + "bytes" +) + +type MessageType uint8 + +const ( + MsgSolicit MessageType = iota + 1 + MsgAdvertise + MsgRequest + MsgConfirm + MsgRenew + MsgRebind + MsgReply + MsgRelease + MsgDecline + MsgReconfigure + MsgInformationRequest + MsgRelayForw + MsgRelayRepl +) + +type Packet struct { + Type MessageType + TransactionID [3]byte + Options []byte +} + +func MakePacket(bs []byte, len int) *Packet { + ret := &Packet{Type: MessageType(bs[0]), Options: make([]byte, len - 4)} + copy(ret.TransactionID[:], bs[1:4]) + copy(ret.Options[:], bs[4:len]) + return ret +} + +func (p *Packet) UnmarshalOptions() (Options, error) { + ret, err := MakeOptions(p.Options) + if err != nil { + return nil, fmt.Errorf("packet has malformed options section: %s", err) + } + return ret, nil +} + +func (p *Packet) BuildResponse(serverDuid []byte) ([]byte, error) { + switch p.Type { + case MsgSolicit: + return p.BuildMsgAdvertise(serverDuid) + case MsgRequest: + return p.BuildMsgReply(serverDuid) + case MsgInformationRequest: + return p.BuildMsgInformationRequestReply(serverDuid) + case MsgRelease: + return p.BuildMsgReleaseReply(serverDuid) + default: + return nil, nil + } +} + +func (p *Packet) BuildMsgAdvertise(serverDuid []byte) ([]byte, error) { + in_options, _ := p.UnmarshalOptions() + ret_options := make(Options) + + ret_options.AddOption(&Option{Id: OptClientId, Length: uint16(len(in_options[OptClientId].Value)), Value: in_options[OptClientId].Value}) + ret_options.AddOption(MakeIaNaOption(in_options[OptIaNa].Value[0:4], 0, 0, + MakeIaAddrOption(net.ParseIP("2001:db8:f00f:cafe::99"), 27000, 43200))) + ret_options.AddOption(&Option{Id: OptServerId, Length: uint16(len(serverDuid)), Value: serverDuid}) + + if 0x10 == binary.BigEndian.Uint16(in_options[OptClientArchType].Value) { // HTTPClient + ret_options.AddOption(&Option{Id: OptVendorClass, Length: 16, Value: []byte {0, 0, 0, 0, 0, 10, 72, 84, 84, 80, 67, 108, 105, 101, 110, 116}}) // HTTPClient + ret_options.AddOption(&Option{Id: OptBootfileUrl, Length: 42, Value: []byte("http://[2001:db8:f00f:cafe::4]/bootx64.efi")}) + } else { + ret_options.AddOption(&Option{Id: OptBootfileUrl, Length: 42, Value: []byte("http://[2001:db8:f00f:cafe::4]/script.ipxe")}) + } +// ret_options.AddOption(OptRecursiveDns, net.ParseIP("2001:db8:f00f:cafe::1")) + //ret_options.AddOption(OptBootfileParam, []byte("http://") + //ret.Options[OptPreference] = [][]byte("http://") + + marshalled_ret_options, _ := ret_options.Marshal() + + ret := make([]byte, len(marshalled_ret_options) + 4, len(marshalled_ret_options) + 4) + ret[0] = byte(MsgAdvertise) + copy(ret[1:], p.TransactionID[:]) + copy(ret[4:], marshalled_ret_options) + return ret, nil +} + +// TODO: OptClientArchType may not be present + +func (p *Packet) BuildMsgReply(serverDuid []byte) ([]byte, error) { + in_options, _ := p.UnmarshalOptions() + ret_options := make(Options) + + ret_options.AddOption(&Option{Id: OptClientId, Length: uint16(len(in_options[OptClientId].Value)), Value: in_options[OptClientId].Value}) + ret_options.AddOption(MakeIaNaOption(in_options[OptIaNa].Value[0:4], 0, 0, + MakeIaAddrOption(net.ParseIP("2001:db8:f00f:cafe::99"), 27000, 43200))) + ret_options.AddOption(&Option{Id: OptServerId, Length: uint16(len(serverDuid)), Value: serverDuid}) + // ret_options.AddOption(OptRecursiveDns, net.ParseIP("2001:db8:f00f:cafe::1")) + if 0x10 == binary.BigEndian.Uint16(in_options[OptClientArchType].Value) { // HTTPClient + ret_options.AddOption(&Option{Id: OptVendorClass, Length: 16, Value: []byte {0, 0, 0, 0, 0, 10, 72, 84, 84, 80, 67, 108, 105, 101, 110, 116}}) // HTTPClient + ret_options.AddOption(&Option{Id: OptBootfileUrl, Length: 42, Value: []byte("http://[2001:db8:f00f:cafe::4]/bootx64.efi")}) + } else { + ret_options.AddOption(&Option{Id: OptBootfileUrl, Length: 42, Value: []byte("http://[2001:db8:f00f:cafe::4]/script.ipxe")}) + } + marshalled_ret_options, _ := ret_options.Marshal() + + ret := make([]byte, len(marshalled_ret_options) + 4, len(marshalled_ret_options) + 4) + ret[0] = byte(MsgReply) + copy(ret[1:], p.TransactionID[:]) + copy(ret[4:], marshalled_ret_options) + return ret, nil +} + +func (p *Packet) BuildMsgInformationRequestReply(serverDuid []byte) ([]byte, error) { + in_options, _ := p.UnmarshalOptions() + ret_options := make(Options) + + ret_options.AddOption(&Option{Id: OptClientId, Length: uint16(len(in_options[OptClientId].Value)), Value: in_options[OptClientId].Value}) + ret_options.AddOption(&Option{Id: OptServerId, Length: uint16(len(serverDuid)), Value: serverDuid}) + // ret_options.AddOption(OptRecursiveDns, net.ParseIP("2001:db8:f00f:cafe::1")) + if 0x10 == binary.BigEndian.Uint16(in_options[OptClientArchType].Value) { // HTTPClient + ret_options.AddOption(&Option{Id: OptVendorClass, Length: 16, Value: []byte {0, 0, 0, 0, 0, 10, 72, 84, 84, 80, 67, 108, 105, 101, 110, 116}}) // HTTPClient + ret_options.AddOption(&Option{Id: OptBootfileUrl, Length: 42, Value: []byte("http://[2001:db8:f00f:cafe::4]/bootx64.efi")}) + } else { + ret_options.AddOption(&Option{Id: OptBootfileUrl, Length: 42, Value: []byte("http://[2001:db8:f00f:cafe::4]/script.ipxe")}) + } + marshalled_ret_options, _ := ret_options.Marshal() + + ret := make([]byte, len(marshalled_ret_options) + 4, len(marshalled_ret_options) + 4) + ret[0] = byte(MsgReply) + copy(ret[1:], p.TransactionID[:]) + copy(ret[4:], marshalled_ret_options) + return ret, nil +} + +func (p *Packet) BuildMsgReleaseReply(serverDuid []byte) ([]byte, error){ + in_options, _ := p.UnmarshalOptions() + ret_options := make(Options) + + ret_options.AddOption(&Option{Id: OptClientId, Length: uint16(len(in_options[OptClientId].Value)), Value: in_options[OptClientId].Value}) + ret_options.AddOption(&Option{Id: OptServerId, Length: uint16(len(serverDuid)), Value: serverDuid}) + v := make([]byte, 19, 19) + copy(v[2:], []byte("Release received.")) + ret_options.AddOption(&Option{Id: OptStatusCode, Length: uint16(len(v)), Value: v}) + marshalled_ret_options, _ := ret_options.Marshal() + + ret := make([]byte, len(marshalled_ret_options) + 4, len(marshalled_ret_options) + 4) + ret[0] = byte(MsgReply) + copy(ret[1:], p.TransactionID[:]) + copy(ret[4:], marshalled_ret_options) + //copy(ret.Options, marshalled_ret_options) + return ret, nil +} + +func (p *Packet) ShouldDiscard(serverDuid []byte) error { + switch p.Type { + case MsgSolicit: + return ShouldDiscardSolicit(p) + case MsgRequest: + return ShouldDiscardRequest(p, serverDuid) + case MsgInformationRequest: + return ShouldDiscardInformationRequest(p, serverDuid) + default: + return fmt.Errorf("Unknown packet") + } +} + +func ShouldDiscardSolicit(p *Packet) error { + options, _ := MakeOptions(p.Options) + if !options.RequestedBootFileUrlOption() { + return fmt.Errorf("'Solicit' packet doesn't have file url option") + } + if !options.HasClientId() { + return fmt.Errorf("'Solicit' packet has no client id option") + } + if options.HasServerId() { + return fmt.Errorf("'Solicit' packet has server id option") + } + return nil +} + +func ShouldDiscardRequest(p *Packet, serverDuid []byte) error { + options, _ := MakeOptions(p.Options) + if !options.RequestedBootFileUrlOption() { + return fmt.Errorf("'Request' packet doesn't have file url option") + } + if !options.HasClientId() { + return fmt.Errorf("'Request' packet has no client id option") + } + if !options.HasServerId() { + return fmt.Errorf("'Request' packet has no server id option") + } + if bytes.Compare(options[OptServerId].Value, serverDuid) != 0 { + return fmt.Errorf("'Request' packet's server id option (%d) is different from ours (%d)", options[OptServerId].Value, serverDuid) + } + return nil +} + +func ShouldDiscardInformationRequest(p *Packet, serverDuid []byte) error { + options, _ := MakeOptions(p.Options) + if !options.RequestedBootFileUrlOption() { + return fmt.Errorf("'Information-request' packet doesn't have boot file url option") + } + if options.HasIaNa() || options.HasIaTa() { + return fmt.Errorf("'Information-request' packet has an IA option present") + } + if options.HasServerId() && (bytes.Compare(options[OptServerId].Value, serverDuid) != 0) { + return fmt.Errorf("'Information-request' packet's server id option (%d) is different from ours (%d)", options[OptServerId].Value, serverDuid) + } + return nil +} \ No newline at end of file diff --git a/dhcp6/testdata/dhcp6.pcap b/dhcp6/testdata/dhcp6.pcap new file mode 100644 index 0000000..a870bb6 Binary files /dev/null and b/dhcp6/testdata/dhcp6.pcap differ diff --git a/dhcp6/testdata/dhcp6.txt b/dhcp6/testdata/dhcp6.txt new file mode 100644 index 0000000..2155d22 --- /dev/null +++ b/dhcp6/testdata/dhcp6.txt @@ -0,0 +1,427 @@ +No. Time Source Destination Protocol Length Info + 1 0.000000 fe80::2ad2:44ff:fe85:7b1d ff02::1:2 DHCPv6 161 Solicit XID: 0x958a89 CID: 000474c475b0d742d4ca3681fe4bc6f66c24 + +Frame 1: 161 bytes on wire (1288 bits), 161 bytes captured (1288 bits) + Encapsulation type: Ethernet (1) + Arrival Time: Mar 31, 2017 13:57:48.861433000 BST + [Time shift for this packet: 0.000000000 seconds] + Epoch Time: 1490965068.861433000 seconds + [Time delta from previous captured frame: 0.000000000 seconds] + [Time delta from previous displayed frame: 0.000000000 seconds] + [Time since reference or first frame: 0.000000000 seconds] + Frame Number: 1 + Frame Length: 161 bytes (1288 bits) + Capture Length: 161 bytes (1288 bits) + [Frame is marked: False] + [Frame is ignored: False] + [Protocols in frame: eth:ethertype:ipv6:ipv6.nxt:udp:dhcpv6] + [Coloring Rule Name: UDP] + [Coloring Rule String: udp] +Ethernet II, Src: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d), Dst: IPv6mcast_01:00:02 (33:33:00:01:00:02) + Destination: IPv6mcast_01:00:02 (33:33:00:01:00:02) + Address: IPv6mcast_01:00:02 (33:33:00:01:00:02) + .... ..1. .... .... .... .... = LG bit: Locally administered address (this is NOT the factory default) + .... ...1 .... .... .... .... = IG bit: Group address (multicast/broadcast) + Source: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d) + Address: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d) + .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default) + .... ...0 .... .... .... .... = IG bit: Individual address (unicast) + Type: IPv6 (0x86dd) +Internet Protocol Version 6, Src: fe80::2ad2:44ff:fe85:7b1d (fe80::2ad2:44ff:fe85:7b1d), Dst: ff02::1:2 (ff02::1:2) + 0110 .... = Version: 6 + [0110 .... = This field makes the filter "ip.version == 6" possible: 6] + .... 0000 0000 .... .... .... .... .... = Traffic class: 0x00000000 + .... 0000 00.. .... .... .... .... .... = Differentiated Services Field: Default (0x00000000) + .... .... ..0. .... .... .... .... .... = ECN-Capable Transport (ECT): Not set + .... .... ...0 .... .... .... .... .... = ECN-CE: Not set + .... .... .... 1100 1010 1001 0111 0111 = Flowlabel: 0x000ca977 + Payload length: 107 + Next header: UDP (17) + Hop limit: 1 + Source: fe80::2ad2:44ff:fe85:7b1d (fe80::2ad2:44ff:fe85:7b1d) + [Source SA MAC: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d)] + Destination: ff02::1:2 (ff02::1:2) + [Source GeoIP: Unknown] + [Destination GeoIP: Unknown] +User Datagram Protocol, Src Port: 546 (546), Dst Port: 547 (547) + Source Port: 546 (546) + Destination Port: 547 (547) + Length: 107 + Checksum: 0xdae6 [validation disabled] + [Good Checksum: False] + [Bad Checksum: False] + [Stream index: 0] +DHCPv6 + Message type: Solicit (1) + Transaction ID: 0x958a89 + Client Identifier + Option: Client Identifier (1) + Length: 18 + Value: 000474c475b0d742d4ca3681fe4bc6f66c24 + DUID: 000474c475b0d742d4ca3681fe4bc6f66c24 + DUID Type: link-layer address (old) (4) + Hardware type: Unknown (29892) + Link-layer address: 75b0d742d4ca3681fe4bc6f66c24 + Option Request + Option: Option Request (6) + Length: 10 + Value: 00170018001700180001 + Requested Option code: DNS recursive name server (23) + Requested Option code: Domain Search List (24) + Requested Option code: DNS recursive name server (23) + Requested Option code: Domain Search List (24) + Requested Option code: Client Identifier (1) + Elapsed time + Option: Elapsed time (8) + Length: 2 + Value: 0000 + Elapsed time: 0 ms + Fully Qualified Domain Name + Option: Fully Qualified Domain Name (39) + Length: 33 + Value: 010e6c756369642d6e6f6e73656e73650c6170706c696564... + 0000 0... = Reserved: 0x00 + .... .0.. = N bit: Server should perform DNS updates + .... ..0. = O bit: Server has not overridden client's S bit preference + .... ...1 = S bit: Server should perform forward DNS updates + Client FQDN: lucid-nonsense.appliedlogic.ca + Identity Association for Non-temporary Address + Option: Identity Association for Non-temporary Address (3) + Length: 12 + Value: 44857b1d00000e1000001518 + IAID: 44857b1d + T1: 3600 + T2: 5400 + +No. Time Source Destination Protocol Length Info + 2 0.000371 fe80::8a51:fbff:fe6b:d76d fe80::2ad2:44ff:fe85:7b1d DHCPv6 226 Advertise XID: 0x958a89 IAA: 2001:db8:1:1::99 CID: 000474c475b0d742d4ca3681fe4bc6f66c24 + +Frame 2: 226 bytes on wire (1808 bits), 226 bytes captured (1808 bits) + Encapsulation type: Ethernet (1) + Arrival Time: Mar 31, 2017 13:57:48.861804000 BST + [Time shift for this packet: 0.000000000 seconds] + Epoch Time: 1490965068.861804000 seconds + [Time delta from previous captured frame: 0.000371000 seconds] + [Time delta from previous displayed frame: 0.000371000 seconds] + [Time since reference or first frame: 0.000371000 seconds] + Frame Number: 2 + Frame Length: 226 bytes (1808 bits) + Capture Length: 226 bytes (1808 bits) + [Frame is marked: False] + [Frame is ignored: False] + [Protocols in frame: eth:ethertype:ipv6:ipv6.nxt:udp:dhcpv6] + [Coloring Rule Name: UDP] + [Coloring Rule String: udp] +Ethernet II, Src: HewlettP_6b:d7:6d (88:51:fb:6b:d7:6d), Dst: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d) + Destination: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d) + Address: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d) + .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default) + .... ...0 .... .... .... .... = IG bit: Individual address (unicast) + Source: HewlettP_6b:d7:6d (88:51:fb:6b:d7:6d) + Address: HewlettP_6b:d7:6d (88:51:fb:6b:d7:6d) + .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default) + .... ...0 .... .... .... .... = IG bit: Individual address (unicast) + Type: IPv6 (0x86dd) +Internet Protocol Version 6, Src: fe80::8a51:fbff:fe6b:d76d (fe80::8a51:fbff:fe6b:d76d), Dst: fe80::2ad2:44ff:fe85:7b1d (fe80::2ad2:44ff:fe85:7b1d) + 0110 .... = Version: 6 + [0110 .... = This field makes the filter "ip.version == 6" possible: 6] + .... 0000 0000 .... .... .... .... .... = Traffic class: 0x00000000 + .... 0000 00.. .... .... .... .... .... = Differentiated Services Field: Default (0x00000000) + .... .... ..0. .... .... .... .... .... = ECN-Capable Transport (ECT): Not set + .... .... ...0 .... .... .... .... .... = ECN-CE: Not set + .... .... .... 1001 0100 1000 0000 0001 = Flowlabel: 0x00094801 + Payload length: 172 + Next header: UDP (17) + Hop limit: 64 + Source: fe80::8a51:fbff:fe6b:d76d (fe80::8a51:fbff:fe6b:d76d) + [Source SA MAC: HewlettP_6b:d7:6d (88:51:fb:6b:d7:6d)] + Destination: fe80::2ad2:44ff:fe85:7b1d (fe80::2ad2:44ff:fe85:7b1d) + [Destination SA MAC: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d)] + [Source GeoIP: Unknown] + [Destination GeoIP: Unknown] +User Datagram Protocol, Src Port: 547 (547), Dst Port: 546 (546) + Source Port: 547 (547) + Destination Port: 546 (546) + Length: 172 + Checksum: 0x435f [validation disabled] + [Good Checksum: False] + [Bad Checksum: False] + [Stream index: 1] +DHCPv6 + Message type: Advertise (2) + Transaction ID: 0x958a89 + Identity Association for Non-temporary Address + Option: Identity Association for Non-temporary Address (3) + Length: 40 + Value: 44857b1d00000e1000001c200005001820010db800010001... + IAID: 44857b1d + T1: 3600 + T2: 7200 + IA Address + Option: IA Address (5) + Length: 24 + Value: 20010db800010001000000000000009900093a8000278d00 + IPv6 address: 2001:db8:1:1::99 (2001:db8:1:1::99) + Preferred lifetime: 604800 + Valid lifetime: 2592000 + Client Identifier + Option: Client Identifier (1) + Length: 18 + Value: 000474c475b0d742d4ca3681fe4bc6f66c24 + DUID: 000474c475b0d742d4ca3681fe4bc6f66c24 + DUID Type: link-layer address (old) (4) + Hardware type: Unknown (29892) + Link-layer address: 75b0d742d4ca3681fe4bc6f66c24 + Server Identifier + Option: Server Identifier (2) + Length: 14 + Value: 00010001207106ee8851fb6bd76d + DUID: 00010001207106ee8851fb6bd76d + DUID Type: link-layer address plus time (1) + Hardware type: Ethernet (1) + DUID Time: Mar 31, 2017 13:24:14.000000000 BST + Link-layer address: 88:51:fb:6b:d7:6d + DNS recursive name server + Option: DNS recursive name server (23) + Length: 16 + Value: 20010db8000100010000000000000201 + 1 DNS server address: 2001:db8:1:1::201 (2001:db8:1:1::201) + Domain Search List + Option: Domain Search List (24) + Length: 14 + Value: 08696e7465726e616c036c616e00 + DNS Domain Search List + Domain Search List FQDN: internal.lan + DNS recursive name server + Option: DNS recursive name server (23) + Length: 16 + Value: 20010db8000100010000000000000201 + 1 DNS server address: 2001:db8:1:1::201 (2001:db8:1:1::201) + Domain Search List + Option: Domain Search List (24) + Length: 14 + Value: 08696e7465726e616c036c616e00 + DNS Domain Search List + Domain Search List FQDN: internal.lan + +No. Time Source Destination Protocol Length Info + 3 1.061280 fe80::2ad2:44ff:fe85:7b1d ff02::1:2 DHCPv6 207 Request XID: 0xfa052 CID: 000474c475b0d742d4ca3681fe4bc6f66c24 IAA: 2001:db8:1:1::99 + +Frame 3: 207 bytes on wire (1656 bits), 207 bytes captured (1656 bits) + Encapsulation type: Ethernet (1) + Arrival Time: Mar 31, 2017 13:57:49.922713000 BST + [Time shift for this packet: 0.000000000 seconds] + Epoch Time: 1490965069.922713000 seconds + [Time delta from previous captured frame: 1.060909000 seconds] + [Time delta from previous displayed frame: 1.060909000 seconds] + [Time since reference or first frame: 1.061280000 seconds] + Frame Number: 3 + Frame Length: 207 bytes (1656 bits) + Capture Length: 207 bytes (1656 bits) + [Frame is marked: False] + [Frame is ignored: False] + [Protocols in frame: eth:ethertype:ipv6:ipv6.nxt:udp:dhcpv6] + [Coloring Rule Name: UDP] + [Coloring Rule String: udp] +Ethernet II, Src: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d), Dst: IPv6mcast_01:00:02 (33:33:00:01:00:02) + Destination: IPv6mcast_01:00:02 (33:33:00:01:00:02) + Address: IPv6mcast_01:00:02 (33:33:00:01:00:02) + .... ..1. .... .... .... .... = LG bit: Locally administered address (this is NOT the factory default) + .... ...1 .... .... .... .... = IG bit: Group address (multicast/broadcast) + Source: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d) + Address: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d) + .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default) + .... ...0 .... .... .... .... = IG bit: Individual address (unicast) + Type: IPv6 (0x86dd) +Internet Protocol Version 6, Src: fe80::2ad2:44ff:fe85:7b1d (fe80::2ad2:44ff:fe85:7b1d), Dst: ff02::1:2 (ff02::1:2) + 0110 .... = Version: 6 + [0110 .... = This field makes the filter "ip.version == 6" possible: 6] + .... 0000 0000 .... .... .... .... .... = Traffic class: 0x00000000 + .... 0000 00.. .... .... .... .... .... = Differentiated Services Field: Default (0x00000000) + .... .... ..0. .... .... .... .... .... = ECN-Capable Transport (ECT): Not set + .... .... ...0 .... .... .... .... .... = ECN-CE: Not set + .... .... .... 1100 1010 1001 0111 0111 = Flowlabel: 0x000ca977 + Payload length: 153 + Next header: UDP (17) + Hop limit: 1 + Source: fe80::2ad2:44ff:fe85:7b1d (fe80::2ad2:44ff:fe85:7b1d) + [Source SA MAC: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d)] + Destination: ff02::1:2 (ff02::1:2) + [Source GeoIP: Unknown] + [Destination GeoIP: Unknown] +User Datagram Protocol, Src Port: 546 (546), Dst Port: 547 (547) + Source Port: 546 (546) + Destination Port: 547 (547) + Length: 153 + Checksum: 0x4743 [validation disabled] + [Good Checksum: False] + [Bad Checksum: False] + [Stream index: 0] +DHCPv6 + Message type: Request (3) + Transaction ID: 0x0fa052 + Client Identifier + Option: Client Identifier (1) + Length: 18 + Value: 000474c475b0d742d4ca3681fe4bc6f66c24 + DUID: 000474c475b0d742d4ca3681fe4bc6f66c24 + DUID Type: link-layer address (old) (4) + Hardware type: Unknown (29892) + Link-layer address: 75b0d742d4ca3681fe4bc6f66c24 + Server Identifier + Option: Server Identifier (2) + Length: 14 + Value: 00010001207106ee8851fb6bd76d + DUID: 00010001207106ee8851fb6bd76d + DUID Type: link-layer address plus time (1) + Hardware type: Ethernet (1) + DUID Time: Mar 31, 2017 13:24:14.000000000 BST + Link-layer address: 88:51:fb:6b:d7:6d + Option Request + Option: Option Request (6) + Length: 10 + Value: 00170018001700180001 + Requested Option code: DNS recursive name server (23) + Requested Option code: Domain Search List (24) + Requested Option code: DNS recursive name server (23) + Requested Option code: Domain Search List (24) + Requested Option code: Client Identifier (1) + Elapsed time + Option: Elapsed time (8) + Length: 2 + Value: 0000 + Elapsed time: 0 ms + Fully Qualified Domain Name + Option: Fully Qualified Domain Name (39) + Length: 33 + Value: 010e6c756369642d6e6f6e73656e73650c6170706c696564... + 0000 0... = Reserved: 0x00 + .... .0.. = N bit: Server should perform DNS updates + .... ..0. = O bit: Server has not overridden client's S bit preference + .... ...1 = S bit: Server should perform forward DNS updates + Client FQDN: lucid-nonsense.appliedlogic.ca + Identity Association for Non-temporary Address + Option: Identity Association for Non-temporary Address (3) + Length: 40 + Value: 44857b1d00000e10000015180005001820010db800010001... + IAID: 44857b1d + T1: 3600 + T2: 5400 + IA Address + Option: IA Address (5) + Length: 24 + Value: 20010db800010001000000000000009900001c2000001d4c + IPv6 address: 2001:db8:1:1::99 (2001:db8:1:1::99) + Preferred lifetime: 7200 + Valid lifetime: 7500 + +No. Time Source Destination Protocol Length Info + 4 1.061709 fe80::8a51:fbff:fe6b:d76d fe80::2ad2:44ff:fe85:7b1d DHCPv6 226 Reply XID: 0xfa052 IAA: 2001:db8:1:1::99 CID: 000474c475b0d742d4ca3681fe4bc6f66c24 + +Frame 4: 226 bytes on wire (1808 bits), 226 bytes captured (1808 bits) + Encapsulation type: Ethernet (1) + Arrival Time: Mar 31, 2017 13:57:49.923142000 BST + [Time shift for this packet: 0.000000000 seconds] + Epoch Time: 1490965069.923142000 seconds + [Time delta from previous captured frame: 0.000429000 seconds] + [Time delta from previous displayed frame: 0.000429000 seconds] + [Time since reference or first frame: 1.061709000 seconds] + Frame Number: 4 + Frame Length: 226 bytes (1808 bits) + Capture Length: 226 bytes (1808 bits) + [Frame is marked: False] + [Frame is ignored: False] + [Protocols in frame: eth:ethertype:ipv6:ipv6.nxt:udp:dhcpv6] + [Coloring Rule Name: UDP] + [Coloring Rule String: udp] +Ethernet II, Src: HewlettP_6b:d7:6d (88:51:fb:6b:d7:6d), Dst: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d) + Destination: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d) + Address: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d) + .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default) + .... ...0 .... .... .... .... = IG bit: Individual address (unicast) + Source: HewlettP_6b:d7:6d (88:51:fb:6b:d7:6d) + Address: HewlettP_6b:d7:6d (88:51:fb:6b:d7:6d) + .... ..0. .... .... .... .... = LG bit: Globally unique address (factory default) + .... ...0 .... .... .... .... = IG bit: Individual address (unicast) + Type: IPv6 (0x86dd) +Internet Protocol Version 6, Src: fe80::8a51:fbff:fe6b:d76d (fe80::8a51:fbff:fe6b:d76d), Dst: fe80::2ad2:44ff:fe85:7b1d (fe80::2ad2:44ff:fe85:7b1d) + 0110 .... = Version: 6 + [0110 .... = This field makes the filter "ip.version == 6" possible: 6] + .... 0000 0000 .... .... .... .... .... = Traffic class: 0x00000000 + .... 0000 00.. .... .... .... .... .... = Differentiated Services Field: Default (0x00000000) + .... .... ..0. .... .... .... .... .... = ECN-Capable Transport (ECT): Not set + .... .... ...0 .... .... .... .... .... = ECN-CE: Not set + .... .... .... 1001 0100 1000 0000 0001 = Flowlabel: 0x00094801 + Payload length: 172 + Next header: UDP (17) + Hop limit: 64 + Source: fe80::8a51:fbff:fe6b:d76d (fe80::8a51:fbff:fe6b:d76d) + [Source SA MAC: HewlettP_6b:d7:6d (88:51:fb:6b:d7:6d)] + Destination: fe80::2ad2:44ff:fe85:7b1d (fe80::2ad2:44ff:fe85:7b1d) + [Destination SA MAC: LcfcHefe_85:7b:1d (28:d2:44:85:7b:1d)] + [Source GeoIP: Unknown] + [Destination GeoIP: Unknown] +User Datagram Protocol, Src Port: 547 (547), Dst Port: 546 (546) + Source Port: 547 (547) + Destination Port: 546 (546) + Length: 172 + Checksum: 0x435f [validation disabled] + [Good Checksum: False] + [Bad Checksum: False] + [Stream index: 1] +DHCPv6 + Message type: Reply (7) + Transaction ID: 0x0fa052 + Identity Association for Non-temporary Address + Option: Identity Association for Non-temporary Address (3) + Length: 40 + Value: 44857b1d00000e1000001c200005001820010db800010001... + IAID: 44857b1d + T1: 3600 + T2: 7200 + IA Address + Option: IA Address (5) + Length: 24 + Value: 20010db800010001000000000000009900093a8000278d00 + IPv6 address: 2001:db8:1:1::99 (2001:db8:1:1::99) + Preferred lifetime: 604800 + Valid lifetime: 2592000 + Client Identifier + Option: Client Identifier (1) + Length: 18 + Value: 000474c475b0d742d4ca3681fe4bc6f66c24 + DUID: 000474c475b0d742d4ca3681fe4bc6f66c24 + DUID Type: link-layer address (old) (4) + Hardware type: Unknown (29892) + Link-layer address: 75b0d742d4ca3681fe4bc6f66c24 + Server Identifier + Option: Server Identifier (2) + Length: 14 + Value: 00010001207106ee8851fb6bd76d + DUID: 00010001207106ee8851fb6bd76d + DUID Type: link-layer address plus time (1) + Hardware type: Ethernet (1) + DUID Time: Mar 31, 2017 13:24:14.000000000 BST + Link-layer address: 88:51:fb:6b:d7:6d + DNS recursive name server + Option: DNS recursive name server (23) + Length: 16 + Value: 20010db8000100010000000000000201 + 1 DNS server address: 2001:db8:1:1::201 (2001:db8:1:1::201) + Domain Search List + Option: Domain Search List (24) + Length: 14 + Value: 08696e7465726e616c036c616e00 + DNS Domain Search List + Domain Search List FQDN: internal.lan + DNS recursive name server + Option: DNS recursive name server (23) + Length: 16 + Value: 20010db8000100010000000000000201 + 1 DNS server address: 2001:db8:1:1::201 (2001:db8:1:1::201) + Domain Search List + Option: Domain Search List (24) + Length: 14 + Value: 08696e7465726e616c036c616e00 + DNS Domain Search List + Domain Search List FQDN: internal.lan diff --git a/pixiecore/dhcpv6.go b/pixiecore/dhcpv6.go new file mode 100644 index 0000000..6f5ec5d --- /dev/null +++ b/pixiecore/dhcpv6.go @@ -0,0 +1,34 @@ +package pixiecore + +import ( + "go.universe.tf/netboot/dhcp6" + "fmt" +) + +func (s *ServerV6) serveDHCP(conn *dhcp6.Conn) error { + s.log("dhcpv6", "Waiting for packets...\n") + for { + pkt, src, err := conn.RecvDHCP() + if err != nil { + return fmt.Errorf("Error receiving DHCP packet: %s", err) + } + if err := pkt.ShouldDiscard(s.Duid); err != nil { + s.log("dhcpv6", fmt.Sprintf("Discarding (%d) packet (%d): %s\n", pkt.Type, pkt.TransactionID, err)) + continue + } + + opts, _ := pkt.UnmarshalOptions() + s.log("dhcpv6", fmt.Sprintf("Received (%d) packet (%d): %s\n", pkt.Type, pkt.TransactionID, opts.HumanReadable())) + + response, _ := pkt.BuildResponse(s.Duid) + if err := conn.SendDHCP(src, response); err != nil { + s.log("dhcpv6", fmt.Sprintf("Error sending reply (%d) (%d): %s", pkt.Type, pkt.TransactionID, err)) + continue + } + + reply_packet := dhcp6.MakePacket(response, len(response)) + reply_opts,_ := reply_packet.UnmarshalOptions() + + s.log("dhcpv6", fmt.Sprintf("Sent (%d) packet (%d): %s\n", reply_packet.Type, reply_packet.TransactionID, reply_opts.HumanReadable())) + } +} diff --git a/pixiecore/pixicorev6.go b/pixiecore/pixicorev6.go new file mode 100644 index 0000000..b7bb466 --- /dev/null +++ b/pixiecore/pixicorev6.go @@ -0,0 +1,91 @@ +package pixiecore + +import ( + "go.universe.tf/netboot/dhcp6" + "sync" + "fmt" + "time" + "encoding/binary" + "net" +) + +type ServerV6 struct { + Address string + Port string + Duid []byte + + errs chan error + + eventsMu sync.Mutex + events map[string][]machineEvent + + Log func(subsystem, msg string) + Debug func(subsystem, msg string) +} + +func (s *ServerV6) Serve() error { + s.log("dhcp", "starting...") + + dhcp, err := dhcp6.NewConn(s.Address) + if err != nil { + return err + } + + s.log("dhcp", "new connection...") + + s.events = make(map[string][]machineEvent) + // 5 buffer slots, one for each goroutine, plus one for + // Shutdown(). We only ever pull the first error out, but shutdown + // will likely generate some spurious errors from the other + // goroutines, and we want them to be able to dump them without + // blocking. + s.errs = make(chan error, 6) + + //s.debug("Init", "Starting Pixiecore goroutines") + s.SetDUID(dhcp.SourceHardwareAddress()) + go func() { s.errs <- s.serveDHCP(dhcp) }() + + // Wait for either a fatal error, or Shutdown(). + err = <-s.errs + dhcp.Close() + + s.log("dhcp", "stopped...") + return err +} + +// Shutdown causes Serve() to exit, cleaning up behind itself. +func (s *ServerV6) Shutdown() { + select { + case s.errs <- nil: + default: + } +} + +func (s *ServerV6) log(subsystem, format string, args ...interface{}) { + if s.Log == nil { + return + } + s.Log(subsystem, fmt.Sprintf(format, args...)) +} + +func (s *ServerV6) debug(subsystem, format string, args ...interface{}) { + if s.Debug == nil { + return + } + s.Debug(subsystem, fmt.Sprintf(format, args...)) +} + +func (s *ServerV6) SetDUID(addr net.HardwareAddr) { + duid := make([]byte, len(addr) + 8) // see rfc3315, section 9.2, DUID-LT + + copy(duid[0:], []byte{0, 1}) //fixed, x0001 + copy(duid[2:], []byte{0, 1}) //hw type ethernet, x0001 + + utcLoc, _ := time.LoadLocation("UTC") + sinceJanFirst2000 := time.Since(time.Date(2000, time.January, 1, 0, 0, 0, 0, utcLoc)) + binary.BigEndian.PutUint32(duid[4:], uint32(sinceJanFirst2000.Seconds())) + + copy(duid[8:], addr) + + s.Duid = duid +} \ No newline at end of file