first stab at dhcpv6 support

This commit is contained in:
Dmitri Dolguikh 2017-06-05 13:36:37 +01:00 committed by Dave Anderson
parent 8834cc3e94
commit 3a5808cb30
9 changed files with 1103 additions and 3 deletions

View File

@ -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)
}
}

114
dhcp6/conn.go Normal file
View File

@ -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
}

167
dhcp6/options.go Normal file
View File

@ -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
}

37
dhcp6/options_test.go Normal file
View File

@ -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")
}
}

215
dhcp6/packet.go Normal file
View File

@ -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
}

BIN
dhcp6/testdata/dhcp6.pcap vendored Normal file

Binary file not shown.

427
dhcp6/testdata/dhcp6.txt vendored Normal file
View File

@ -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

34
pixiecore/dhcpv6.go Normal file
View File

@ -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()))
}
}

91
pixiecore/pixicorev6.go Normal file
View File

@ -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
}