├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── cmd └── dhcp6d │ ├── README.md │ └── main.go ├── const.go ├── dhcp6.go ├── dhcp6opts ├── authentication.go ├── authentication_test.go ├── const.go ├── duid.go ├── duid_test.go ├── iaaddr.go ├── iaaddr_test.go ├── iana.go ├── iana_test.go ├── iapd.go ├── iapd_test.go ├── iaprefix.go ├── iaprefix_test.go ├── iata.go ├── iata_test.go ├── miscoptions.go ├── options.go ├── options_test.go ├── opts.go ├── relaymessage.go ├── relaymessage_test.go ├── remoteid.go ├── remoteid_test.go ├── statuscode.go ├── statuscode_test.go ├── string.go ├── vendorclass.go ├── vendoropts.go └── vendoropts_test.go ├── dhcp6server ├── dhcp6.go ├── mux.go ├── mux_test.go ├── request.go ├── request_test.go ├── server.go └── server_test.go ├── dhcp6test ├── recorder.go └── recorder_test.go ├── go.mod ├── go.sum ├── internal └── buffer │ └── buffer.go ├── options.go ├── options_test.go ├── packet.go ├── packet_test.go └── string.go /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.x 4 | os: 5 | - linux 6 | before_install: 7 | - go get golang.org/x/lint/golint 8 | - go get honnef.co/go/tools/cmd/staticcheck 9 | - go get -d ./... 10 | script: 11 | - go build -tags=gofuzz ./... 12 | - go vet ./... 13 | - staticcheck ./... 14 | - golint -set_exit_status ./... 15 | - go test -v -race ./... -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | =========== 3 | 4 | Copyright (C) 2015-2017 Matt Layher 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dhcp6 [![Build Status](https://travis-ci.org/mdlayher/dhcp6.svg?branch=master)](https://travis-ci.org/mdlayher/dhcp6) [![GoDoc](https://godoc.org/github.com/mdlayher/dhcp6?status.svg)](https://godoc.org/github.com/mdlayher/dhcp6) [![Go Report Card](https://goreportcard.com/badge/github.com/mdlayher/dhcp6)](https://goreportcard.com/report/github.com/mdlayher/dhcp6) 2 | ===== 3 | 4 | Package `dhcp6` implements a DHCPv6 server, as described in IETF RFC 3315. MIT Licensed. 5 | 6 | At this time, the API is not stable, and may change over time. The eventual 7 | goal is to implement a server, client, and testing facilities for consumers 8 | of this package. 9 | 10 | The design of this package is inspired by Go's `net/http` package. The Go 11 | standard library is Copyright (c) 2012 The Go Authors. All rights reserved. 12 | The Go license can be found at https://golang.org/LICENSE. 13 | -------------------------------------------------------------------------------- /cmd/dhcp6d/README.md: -------------------------------------------------------------------------------- 1 | dhcp6d 2 | ====== 3 | 4 | Command `dhcp6d` is an example DHCPv6 server. It can only assign a 5 | single IPv6 address, and is not a complete DHCPv6 server implementation 6 | by any means. It is meant to demonstrate usage of package `dhcp6`. 7 | 8 | Example 9 | ------- 10 | 11 | This example makes use of two machines (a client, "dhcp6c", and server, 12 | "dhcp6d") and the `dhclient(8)` and `dhcp6d` binaries. 13 | 14 | Use server to begin serving a single IPv6 address using DHCPv6: 15 | 16 | ``` 17 | matt@dhcp6d:~$ sudo ./dhcp6d -h 18 | Usage of ./dhcp6d: 19 | -i string 20 | interface to serve DHCPv6 (default "eth0") 21 | -ip string 22 | IPv6 address to serve over DHCPv6 23 | matt@dhcp6d:~$ sudo ./dhcp6d -i eth0 -ip dead:beef:d34d:b33f::10 24 | 2015/09/02 14:50:31 binding DHCPv6 server to interface eth0... 25 | ``` 26 | 27 | Use client to request an IPv6 address using DHCPv6: 28 | 29 | ``` 30 | matt@dhcp6c:~$ ifconfig eth0 | grep ::10 31 | matt@dhcp6c:~$ sudo dhclient -6 eth0 32 | matt@dhcp6c:~$ ifconfig eth0 | grep ::10 33 | inet6 addr: dead:beef:d34d:b33f::10/64 Scope:Global 34 | ``` 35 | -------------------------------------------------------------------------------- /cmd/dhcp6d/main.go: -------------------------------------------------------------------------------- 1 | // Command dhcp6d is an example DHCPv6 dhcp6server. It can only assign a 2 | // single IPv6 address, and is not a complete DHCPv6 server implementation 3 | // by any means. It is meant to demonstrate usage of package dhcp6. 4 | package main 5 | 6 | import ( 7 | "encoding/hex" 8 | "flag" 9 | "log" 10 | "net" 11 | "time" 12 | 13 | "github.com/mdlayher/dhcp6" 14 | "github.com/mdlayher/dhcp6/dhcp6opts" 15 | "github.com/mdlayher/dhcp6/dhcp6server" 16 | ) 17 | 18 | func main() { 19 | iface := flag.String("i", "eth0", "interface to serve DHCPv6") 20 | ipFlag := flag.String("ip", "", "IPv6 address to serve over DHCPv6") 21 | flag.Parse() 22 | 23 | // Only accept a single IPv6 address 24 | ip := net.ParseIP(*ipFlag).To16() 25 | if ip == nil || ip.To4() != nil { 26 | log.Fatal("IP is not an IPv6 address") 27 | } 28 | 29 | // Make Handler to assign ip and use handle for requests 30 | h := &Handler{ 31 | ip: ip, 32 | handler: handle, 33 | } 34 | 35 | // Bind DHCPv6 server to interface and use specified handler 36 | log.Printf("binding DHCPv6 server to interface %s...", *iface) 37 | if err := dhcp6server.ListenAndServe(*iface, h); err != nil { 38 | log.Fatal(err) 39 | } 40 | } 41 | 42 | // A Handler is a basic DHCPv6 handler. 43 | type Handler struct { 44 | ip net.IP 45 | handler handler 46 | } 47 | 48 | // ServeDHCP is a dhcp6.Handler which invokes an internal handler that 49 | // allows errors to be returned and handled in one place. 50 | func (h *Handler) ServeDHCP(w dhcp6server.ResponseSender, r *dhcp6server.Request) { 51 | if err := h.handler(h.ip, w, r); err != nil { 52 | log.Println(err) 53 | } 54 | } 55 | 56 | // A handler is a DHCPv6 handler function which can assign a single IPv6 57 | // address and also return an error. 58 | type handler func(ip net.IP, w dhcp6server.ResponseSender, r *dhcp6server.Request) error 59 | 60 | // handle is a handler which assigns IPv6 addresses using DHCPv6. 61 | func handle(ip net.IP, w dhcp6server.ResponseSender, r *dhcp6server.Request) error { 62 | // Accept only Solicit, Request, or Confirm, since this server 63 | // does not handle Information Request or other message types 64 | valid := map[dhcp6.MessageType]struct{}{ 65 | dhcp6.MessageTypeSolicit: {}, 66 | dhcp6.MessageTypeRequest: {}, 67 | dhcp6.MessageTypeConfirm: {}, 68 | } 69 | if _, ok := valid[r.MessageType]; !ok { 70 | return nil 71 | } 72 | 73 | // Make sure client sent a client ID. 74 | duid, err := r.Options.GetOne(dhcp6.OptionClientID) 75 | if err != nil { 76 | return nil 77 | } 78 | 79 | // Log information about the incoming request. 80 | log.Printf("[%s] id: %s, type: %d, len: %d, tx: %s", 81 | hex.EncodeToString(duid), 82 | r.RemoteAddr, 83 | r.MessageType, 84 | r.Length, 85 | hex.EncodeToString(r.TransactionID[:]), 86 | ) 87 | 88 | // Print out options the client has requested 89 | if opts, err := dhcp6opts.GetOptionRequest(r.Options); err == nil { 90 | log.Println("\t- requested:") 91 | for _, o := range opts { 92 | log.Printf("\t\t - %s", o) 93 | } 94 | } 95 | 96 | // Client must send a IANA to retrieve an IPv6 address 97 | ianas, err := dhcp6opts.GetIANA(r.Options) 98 | if err == dhcp6.ErrOptionNotPresent { 99 | log.Println("no IANAs provided") 100 | return nil 101 | } 102 | if err != nil { 103 | return err 104 | } 105 | 106 | // Only accept one IANA 107 | if len(ianas) > 1 { 108 | log.Println("can only handle one IANA") 109 | return nil 110 | } 111 | ia := ianas[0] 112 | 113 | log.Printf("\tIANA: %s (%s, %s), opts: %v", 114 | hex.EncodeToString(ia.IAID[:]), 115 | ia.T1, 116 | ia.T2, 117 | ia.Options, 118 | ) 119 | 120 | // Instruct client to prefer this server unconditionally 121 | _ = w.Options().Add(dhcp6.OptionPreference, dhcp6opts.Preference(255)) 122 | 123 | // IANA may already have an IAAddr if an address was already assigned. 124 | // If not, assign a new one. 125 | iaaddrs, err := dhcp6opts.GetIAAddr(ia.Options) 126 | switch err { 127 | case dhcp6.ErrOptionNotPresent: 128 | // Client did not indicate a previous address, and is soliciting. 129 | // Advertise a new IPv6 address. 130 | if r.MessageType == dhcp6.MessageTypeSolicit { 131 | return newIAAddr(ia, ip, w, r) 132 | } 133 | // Client did not indicate an address and is not soliciting. Ignore. 134 | return nil 135 | 136 | case nil: 137 | // Fall through below. 138 | 139 | default: 140 | return err 141 | } 142 | 143 | // Confirm or renew an existing IPv6 address 144 | 145 | // Must have an IAAddr, but we ignore if more than one is present 146 | if len(iaaddrs) == 0 { 147 | return nil 148 | } 149 | iaa := iaaddrs[0] 150 | 151 | log.Printf("\t\tIAAddr: %s (%s, %s), opts: %v", 152 | iaa.IP, 153 | iaa.PreferredLifetime, 154 | iaa.ValidLifetime, 155 | iaa.Options, 156 | ) 157 | 158 | // Add IAAddr inside IANA, add IANA to options 159 | _ = ia.Options.Add(dhcp6.OptionIAAddr, iaa) 160 | _ = w.Options().Add(dhcp6.OptionIANA, ia) 161 | 162 | // Send reply to client 163 | _, err = w.Send(dhcp6.MessageTypeReply) 164 | return err 165 | } 166 | 167 | // newIAAddr creates a IAAddr for a IANA using the specified IPv6 address, 168 | // and advertises it to a client. 169 | func newIAAddr(ia *dhcp6opts.IANA, ip net.IP, w dhcp6server.ResponseSender, r *dhcp6server.Request) error { 170 | // Send IPv6 address with 60 second preferred lifetime, 171 | // 90 second valid lifetime, no extra options 172 | iaaddr, err := dhcp6opts.NewIAAddr(ip, 60*time.Second, 90*time.Second, nil) 173 | if err != nil { 174 | return err 175 | } 176 | 177 | // Add IAAddr inside IANA, add IANA to options 178 | _ = ia.Options.Add(dhcp6.OptionIAAddr, iaaddr) 179 | _ = w.Options().Add(dhcp6.OptionIANA, ia) 180 | 181 | // Advertise address to soliciting clients 182 | log.Printf("advertising IP: %s", ip) 183 | _, err = w.Send(dhcp6.MessageTypeAdvertise) 184 | return err 185 | } 186 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | package dhcp6 2 | 3 | // MessageType represents a DHCP message type, as defined in RFC 3315, 4 | // Section 5.3. Different DHCP message types are used to perform different 5 | // actions between a client and server. 6 | type MessageType uint8 7 | 8 | // MessageType constants which indicate the message types described in 9 | // RFCs 3315, 5007, 5460, 6977, and 7341. 10 | // 11 | // These message types are taken from IANA's DHCPv6 parameters registry: 12 | // http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml. 13 | const ( 14 | // RFC 3315 15 | MessageTypeSolicit MessageType = 1 16 | MessageTypeAdvertise MessageType = 2 17 | MessageTypeRequest MessageType = 3 18 | MessageTypeConfirm MessageType = 4 19 | MessageTypeRenew MessageType = 5 20 | MessageTypeRebind MessageType = 6 21 | MessageTypeReply MessageType = 7 22 | MessageTypeRelease MessageType = 8 23 | MessageTypeDecline MessageType = 9 24 | MessageTypeReconfigure MessageType = 10 25 | MessageTypeInformationRequest MessageType = 11 26 | MessageTypeRelayForw MessageType = 12 27 | MessageTypeRelayRepl MessageType = 13 28 | 29 | // RFC 5007 30 | MessageTypeLeasequery MessageType = 14 31 | MessageTypeLeasequeryReply MessageType = 15 32 | 33 | // RFC 5460 34 | MessageTypeLeasequeryDone MessageType = 16 35 | MessageTypeLeasequeryData MessageType = 17 36 | 37 | // RFC 6977 38 | MessageTypeReconfigureRequest MessageType = 18 39 | MessageTypeReconfigureReply MessageType = 19 40 | 41 | // RFC 7341 42 | MessageTypeDHCPv4Query MessageType = 20 43 | MessageTypeDHCPv4Response MessageType = 21 44 | ) 45 | 46 | // Status represesents a DHCP status code, as defined in RFC 3315, 47 | // Section 5.4. Status codes are used to communicate success or failure 48 | // between client and server. 49 | type Status uint16 50 | 51 | // Status constants which indicate the status codes described in 52 | // RFCs 3315, 3633, 5007, and 5460. 53 | // 54 | // These status codes are taken from IANA's DHCPv6 parameters registry: 55 | // http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml. 56 | const ( 57 | // RFC 3315 58 | StatusSuccess Status = 0 59 | StatusUnspecFail Status = 1 60 | StatusNoAddrsAvail Status = 2 61 | StatusNoBinding Status = 3 62 | StatusNotOnLink Status = 4 63 | StatusUseMulticast Status = 5 64 | 65 | // RFC 3633 66 | StatusNoPrefixAvail Status = 6 67 | 68 | // RFC 5007 69 | StatusUnknownQueryType Status = 7 70 | StatusMalformedQuery Status = 8 71 | StatusNotConfigured Status = 9 72 | StatusNotAllowed Status = 10 73 | 74 | // RFC 5460 75 | StatusQueryTerminated Status = 11 76 | ) 77 | 78 | // OptionCode represents a DHCP option, as defined in RFC 3315, 79 | // Section 22. Options are used to carry additional information and 80 | // parameters in DHCP messages between client and server. 81 | type OptionCode uint16 82 | 83 | // OptionCode constants which indicate the option codes described in 84 | // RFC 3315, RFC 3633, and RFC 5970. 85 | // 86 | // These option codes are taken from IANA's DHCPv6 parameters registry: 87 | // http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml. 88 | const ( 89 | // RFC 3315 90 | OptionClientID OptionCode = 1 91 | OptionServerID OptionCode = 2 92 | OptionIANA OptionCode = 3 93 | OptionIATA OptionCode = 4 94 | OptionIAAddr OptionCode = 5 95 | OptionORO OptionCode = 6 96 | OptionPreference OptionCode = 7 97 | OptionElapsedTime OptionCode = 8 98 | OptionRelayMsg OptionCode = 9 99 | _ OptionCode = 10 100 | OptionAuth OptionCode = 11 101 | OptionUnicast OptionCode = 12 102 | OptionStatusCode OptionCode = 13 103 | OptionRapidCommit OptionCode = 14 104 | OptionUserClass OptionCode = 15 105 | OptionVendorClass OptionCode = 16 106 | OptionVendorOpts OptionCode = 17 107 | OptionInterfaceID OptionCode = 18 108 | OptionReconfMsg OptionCode = 19 109 | OptionReconfAccept OptionCode = 20 110 | 111 | // RFC 3646 112 | OptionDNSServers OptionCode = 23 113 | 114 | // RFC 3633 115 | OptionIAPD OptionCode = 25 116 | OptionIAPrefix OptionCode = 26 117 | 118 | // RFC 4649 119 | OptionRemoteIdentifier OptionCode = 37 120 | 121 | // RFC 5970 122 | OptionBootFileURL OptionCode = 59 123 | OptionBootFileParam OptionCode = 60 124 | OptionClientArchType OptionCode = 61 125 | OptionNII OptionCode = 62 126 | 127 | // BUG(mdlayher): add additional option code types defined by IANA 128 | ) 129 | -------------------------------------------------------------------------------- /dhcp6.go: -------------------------------------------------------------------------------- 1 | // Package dhcp6 implements a DHCPv6 server, as described in RFC 3315. 2 | // 3 | // Unless otherwise stated, any reference to "DHCP" in this package refers to 4 | // DHCPv6 only. 5 | package dhcp6 6 | 7 | import ( 8 | "errors" 9 | ) 10 | 11 | //go:generate stringer -output=string.go -type=MessageType,Status,OptionCode 12 | 13 | var ( 14 | // ErrInvalidOptions is returned when invalid options data is encountered 15 | // during parsing. The data could report an incorrect length or have 16 | // trailing bytes which are not part of the option. 17 | ErrInvalidOptions = errors.New("invalid options data") 18 | 19 | // ErrInvalidPacket is returned when a byte slice does not contain enough 20 | // data to create a valid Packet. A Packet must have at least a message type 21 | // and transaction ID. 22 | ErrInvalidPacket = errors.New("not enough bytes for valid packet") 23 | 24 | // ErrOptionNotPresent is returned when a requested opcode is not in 25 | // the packet. 26 | ErrOptionNotPresent = errors.New("option code not present in packet") 27 | ) 28 | -------------------------------------------------------------------------------- /dhcp6opts/authentication.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/mdlayher/dhcp6/internal/buffer" 7 | ) 8 | 9 | // The Authentication option carries authentication information to 10 | // authenticate the identity and contents of DHCP messages. The use of 11 | // the Authentication option is described in section 21. 12 | type Authentication struct { 13 | // The authentication protocol used in this authentication option 14 | Protocol byte 15 | 16 | // The algorithm used in the authentication protocol 17 | Algorithm byte 18 | 19 | // The replay detection method used in this authentication option 20 | RDM byte 21 | 22 | // The replay detection information for the RDM 23 | ReplayDetection uint64 24 | 25 | // The authentication information, as specified by the protocol and 26 | // algorithm used in this authentication option. 27 | AuthenticationInformation []byte 28 | } 29 | 30 | // MarshalBinary allocates a byte slice containing the data from a Authentication. 31 | func (a *Authentication) MarshalBinary() ([]byte, error) { 32 | // 1 byte: Protocol 33 | // 1 byte: Algorithm 34 | // 1 byte: RDM 35 | // 8 bytes: ReplayDetection 36 | // N bytes: AuthenticationInformation (can have 0 len byte) 37 | b := buffer.New(nil) 38 | b.Write8(a.Protocol) 39 | b.Write8(a.Algorithm) 40 | b.Write8(a.RDM) 41 | b.Write64(a.ReplayDetection) 42 | b.WriteBytes(a.AuthenticationInformation) 43 | 44 | return b.Data(), nil 45 | } 46 | 47 | // UnmarshalBinary unmarshals a raw byte slice into a Authentication. 48 | // If the byte slice does not contain enough data to form a valid 49 | // Authentication, io.ErrUnexpectedEOF is returned. 50 | func (a *Authentication) UnmarshalBinary(p []byte) error { 51 | b := buffer.New(p) 52 | // Too short to be valid Authentication 53 | if b.Len() < 11 { 54 | return io.ErrUnexpectedEOF 55 | } 56 | 57 | a.Protocol = b.Read8() 58 | a.Algorithm = b.Read8() 59 | a.RDM = b.Read8() 60 | a.ReplayDetection = b.Read64() 61 | a.AuthenticationInformation = b.Remaining() 62 | return nil 63 | } 64 | -------------------------------------------------------------------------------- /dhcp6opts/authentication_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestAuthenticationMarshalBinary(t *testing.T) { 12 | var tests = []struct { 13 | desc string 14 | buf []byte 15 | authentication *Authentication 16 | }{ 17 | { 18 | desc: "all zero values", 19 | buf: bytes.Repeat([]byte{0}, 11), 20 | authentication: &Authentication{}, 21 | }, 22 | { 23 | desc: "Protocol: 0, Alforithm: 1, RDM: 2, [3, 4, 5, 6, 7, 8, 9, 0xa] ReplayDetection, [0xb, 0xc, 0xd, 0xe, 0xf] AuthenticationInformation", 24 | buf: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}, 25 | authentication: &Authentication{ 26 | Protocol: 0, 27 | Algorithm: 1, 28 | RDM: 2, 29 | ReplayDetection: binary.BigEndian.Uint64([]byte{3, 4, 5, 6, 7, 8, 9, 0xa}), 30 | AuthenticationInformation: []byte{0xb, 0xc, 0xd, 0xe, 0xf}, 31 | }, 32 | }, 33 | } 34 | 35 | for i, tt := range tests { 36 | want := tt.buf 37 | got, err := tt.authentication.MarshalBinary() 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | if !bytes.Equal(want, got) { 42 | t.Fatalf("[%02d] test %q, unexpected Authentication bytes for MarshalBinary(%v)\n- want: %v\n- got: %v", 43 | i, tt.desc, tt.buf, want, got) 44 | } 45 | } 46 | } 47 | 48 | func TestAuthenticationUnmarshalBinary(t *testing.T) { 49 | var tests = []struct { 50 | buf []byte 51 | authentication *Authentication 52 | err error 53 | }{ 54 | { 55 | buf: bytes.Repeat([]byte{0}, 10), 56 | err: io.ErrUnexpectedEOF, 57 | }, 58 | { 59 | buf: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}, 60 | authentication: &Authentication{ 61 | Protocol: 0, 62 | Algorithm: 1, 63 | RDM: 2, 64 | ReplayDetection: binary.BigEndian.Uint64([]byte{3, 4, 5, 6, 7, 8, 9, 0xa}), 65 | AuthenticationInformation: []byte{0xb, 0xc, 0xd, 0xe, 0xf}, 66 | }, 67 | }, 68 | } 69 | 70 | for i, tt := range tests { 71 | authentication := new(Authentication) 72 | if want, got := tt.err, authentication.UnmarshalBinary(tt.buf); want != got { 73 | t.Fatalf("[%02d] unexpected error for parseAuthentication(%v):\n- want: %v\n- got: %v", i, tt.buf, want, got) 74 | } 75 | 76 | if tt.err != nil { 77 | continue 78 | } 79 | 80 | if want, got := tt.authentication, authentication; !reflect.DeepEqual(want, got) { 81 | t.Fatalf("[%02d] unexpected Authentication for parseAuthentication(%v):\n- want: %v\n- got: %v", i, tt.buf, want, got) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /dhcp6opts/const.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | // An ArchType is a client system architecture type, as defined in RFC 4578, 4 | // Section 2.1. Though this RFC indicates these constants are for DHCPv4, 5 | // they are carried over for use in DHCPv6 in RFC 5970, Section 3.3. 6 | type ArchType uint16 7 | 8 | // ArchType constants which indicate the client system architecture types 9 | // described in RFC 4578, Section 2.1. 10 | const ( 11 | // RFC 4578 12 | ArchTypeIntelx86PC ArchType = 0 13 | ArchTypeNECPC98 ArchType = 1 14 | ArchTypeEFIItanium ArchType = 2 15 | ArchTypeDECAlpha ArchType = 3 16 | ArchtypeArcx86 ArchType = 4 17 | ArchTypeIntelLeanClient ArchType = 5 18 | ArchTypeEFIIA32 ArchType = 6 19 | ArchTypeEFIBC ArchType = 7 20 | ArchTypeEFIXscale ArchType = 8 21 | ArchTypeEFIx8664 ArchType = 9 22 | ) 23 | -------------------------------------------------------------------------------- /dhcp6opts/duid.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "encoding" 5 | "errors" 6 | "io" 7 | "net" 8 | "time" 9 | 10 | "github.com/mdlayher/dhcp6/internal/buffer" 11 | ) 12 | 13 | var ( 14 | // errInvalidDUIDLLT is returned when not enough bytes are present 15 | // to parse a valid DUIDLLT from a byte slice, or when the DUID type 16 | // found in the byte slice is incorrect. 17 | errInvalidDUIDLLT = errors.New("invalid DUID-LLT") 18 | 19 | // errInvalidDUIDEN is returned when not enough bytes are present 20 | // to parse a valid DUIDEN from a byte slice, or when the DUID type 21 | // found in the byte slice is incorrect. 22 | errInvalidDUIDEN = errors.New("invalid DUID-EN") 23 | 24 | // errInvalidDUIDLL is returned when not enough bytes are present 25 | // to parse a valid DUIDLL from a byte slice, or when the DUID type 26 | // found in the byte slice is incorrect. 27 | errInvalidDUIDLL = errors.New("invalid DUID-LL") 28 | 29 | // errInvalidDUIDUUID is returned when not enough bytes are present 30 | // to parse a valid DUIDUUID from a byte slice, or when the DUID type 31 | // found in the byte slice is incorrect. 32 | errInvalidDUIDUUID = errors.New("invalid DUID-UUID") 33 | 34 | // errUnknownDUID is returned when an unknown DUID type is 35 | // encountered, and thus, a DUID cannot be parsed. 36 | errUnknownDUID = errors.New("unknown DUID type") 37 | ) 38 | 39 | var ( 40 | // duidLLTTime is the date specified in RFC 3315, Section 9.2, for use 41 | // with DUID-LLT generation. It is used to calculate a duration from an 42 | // input time after this date. Dates before this time are not valid for 43 | // creation of DUIDLLT values. 44 | duidLLTTime = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC) 45 | ) 46 | 47 | // DUIDType is a type of DHCP Unique Identifier, as defined in RFC 48 | // 3315, Section 9. DUIDs are used to uniquely identify a client to a 49 | // server, or vice-versa. 50 | type DUIDType uint16 51 | 52 | // DUIDType constants which indicate DUID types described in RFCs 3315 and 6355. 53 | // 54 | // These DUID types are taken from IANA's DHCPv6 parameters registry: 55 | // http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml. 56 | const ( 57 | // RFC 3315 58 | DUIDTypeLLT DUIDType = 1 59 | DUIDTypeEN DUIDType = 2 60 | DUIDTypeLL DUIDType = 3 61 | 62 | // RFC 6355 63 | DUIDTypeUUID DUIDType = 4 64 | ) 65 | 66 | // DUID represents a DHCP Unique Identifier, as defined in RFC 67 | // 3315, Section 9. A DUID is used by a DHCP server to identify 68 | // unique clients. A DUID can also be used by a DHCP client to identify 69 | // a unique server, when needed. 70 | // 71 | // The DUID interface represents a generic DUID, but DUIDs can be 72 | // type-asserted to one of four specific types outlined in RFC 3315 73 | // and RFC 6355: 74 | // - DUIDLLT - DUID Based on Link-layer Address Plus Time 75 | // - DUIDEN - DUID Assigned by Vendor Based on Enterprise Number 76 | // - DUIDLL - DUID Based on Link-layer Address 77 | // - DUIDUUID - DUID Based on Universally Unique Identifier 78 | // 79 | // If further introspection of the DUID is needed, a type switch is 80 | // recommended: 81 | // switch d := duid.(type) { 82 | // case *dhcp6.DUIDLLT: 83 | // fmt.Println(d.Time) 84 | // case *dhcp6.DUIDEN: 85 | // fmt.Println(d.EnterpriseNumber) 86 | // case *dhcp6.DUIDLL: 87 | // fmt.Println(d.HardwareAddr) 88 | // case *dhcp6.DUIDUUID: 89 | // fmt.Println(d.UUID) 90 | // } 91 | type DUID interface { 92 | encoding.BinaryMarshaler 93 | encoding.BinaryUnmarshaler 94 | } 95 | 96 | // DUIDLLT represents a DUID Based on Link-layer Address Plus Time [DUID-LLT], 97 | // as defined in RFC 3315, Section 9.2. 98 | // 99 | // This DUID type must only be used with clients and servers with stable, 100 | // persistent storage. It is the recommended DUID type for all general 101 | // purpose computing devices. 102 | type DUIDLLT struct { 103 | // Type specifies the DUID type. For a DUIDLLT, this should always be 104 | // DUIDTypeLLT. 105 | Type DUIDType 106 | 107 | // HardwareType specifies an IANA-assigned hardware type, as described 108 | // in RFC 826. 109 | HardwareType uint16 110 | 111 | // Time specifies the duration of the time this DUID was generated, minus 112 | // midnight (UTC), January 1, 2000. 113 | Time time.Duration 114 | 115 | // HardwareAddr specifies the hardware address for an arbitrary link-layer 116 | // interface on a device, used in generating the DUIDLLT. This value 117 | // could represent any arbitrary interface on a system, and should not be 118 | // treated as a client or server's communicating hardware address. 119 | HardwareAddr net.HardwareAddr 120 | } 121 | 122 | // NewDUIDLLT generates a new DUIDLLT from an input IANA-assigned hardware 123 | // type, time value, and a hardware address. 124 | // 125 | // The time value must be greater than midnight (UTC), January 1, 2000. 126 | func NewDUIDLLT(hardwareType uint16, time time.Time, hardwareAddr net.HardwareAddr) (*DUIDLLT, error) { 127 | // Do not accept dates before duidLLTTime. 128 | if time.Before(duidLLTTime) { 129 | return nil, ErrInvalidDUIDLLTTime 130 | } 131 | 132 | return &DUIDLLT{ 133 | Type: DUIDTypeLLT, 134 | HardwareType: hardwareType, 135 | Time: time.Sub(duidLLTTime), 136 | HardwareAddr: hardwareAddr, 137 | }, nil 138 | } 139 | 140 | // MarshalBinary allocates a byte slice containing the data from a DUIDLLT. 141 | func (d *DUIDLLT) MarshalBinary() ([]byte, error) { 142 | // 2 bytes: DUID type 143 | // 2 bytes: hardware type 144 | // 4 bytes: time duration 145 | // N bytes: hardware address 146 | b := buffer.New(nil) 147 | 148 | b.Write16(uint16(d.Type)) 149 | b.Write16(d.HardwareType) 150 | b.Write32(uint32(d.Time / time.Second)) 151 | b.WriteBytes(d.HardwareAddr) 152 | 153 | return b.Data(), nil 154 | } 155 | 156 | // UnmarshalBinary unmarshals a raw byte slice into a DUIDLLT. 157 | // If the byte slice does not contain enough data to form a valid 158 | // DUIDLLT, or another DUID type is indicated, errInvalidDUIDLLT is returned. 159 | func (d *DUIDLLT) UnmarshalBinary(p []byte) error { 160 | b := buffer.New(p) 161 | // Too short to be valid DUIDLLT 162 | if b.Len() < 8 { 163 | return io.ErrUnexpectedEOF 164 | } 165 | 166 | // Verify DUID type 167 | dType := DUIDType(b.Read16()) 168 | if dType != DUIDTypeLLT { 169 | return errInvalidDUIDLLT 170 | } 171 | d.Type = dType 172 | d.HardwareType = b.Read16() 173 | d.Time = time.Duration(b.Read32()) * time.Second 174 | 175 | d.HardwareAddr = b.Remaining() 176 | 177 | return nil 178 | } 179 | 180 | // DUIDEN represents a DUID Assigned by Vendor Based on Enterprise Number 181 | // [DUID-EN], as defined in RFC 3315, Section 9.3. This DUID type 182 | // uses an IANA-assigned Private Enterprise Number for a given vendor. 183 | type DUIDEN struct { 184 | // Type specifies the DUID type. For a DUIDEN, this should always be 185 | // DUIDTypeEN. 186 | Type DUIDType 187 | 188 | // EnterpriseNumber specifies an IANA-assigned vendor Private Enterprise 189 | // Number. 190 | EnterpriseNumber uint32 191 | 192 | // Identifier specifies a unique identifier of arbitrary length. This 193 | // value is typically assigned when a device is manufactured. 194 | Identifier []byte 195 | } 196 | 197 | // NewDUIDEN generates a new DUIDEN from an input IANA-assigned Private 198 | // Enterprise Number and a variable length unique identifier byte slice. 199 | func NewDUIDEN(enterpriseNumber uint32, identifier []byte) *DUIDEN { 200 | return &DUIDEN{ 201 | Type: DUIDTypeEN, 202 | EnterpriseNumber: enterpriseNumber, 203 | Identifier: identifier, 204 | } 205 | } 206 | 207 | // MarshalBinary allocates a byte slice containing the data from a DUIDEN. 208 | func (d *DUIDEN) MarshalBinary() ([]byte, error) { 209 | // 2 bytes: DUID type 210 | // 4 bytes: enterprise number 211 | // N bytes: identifier 212 | b := buffer.New(nil) 213 | 214 | b.Write16(uint16(d.Type)) 215 | b.Write32(d.EnterpriseNumber) 216 | b.WriteBytes(d.Identifier) 217 | 218 | return b.Data(), nil 219 | } 220 | 221 | // UnmarshalBinary unmarshals a raw byte slice into a DUIDEN. 222 | // If the byte slice does not contain enough data to form a valid 223 | // DUIDEN, or another DUID type is indicated, errInvalidDUIDEN is returned. 224 | func (d *DUIDEN) UnmarshalBinary(p []byte) error { 225 | b := buffer.New(p) 226 | // Too short to be valid DUIDEN 227 | if b.Len() < 6 { 228 | return io.ErrUnexpectedEOF 229 | } 230 | 231 | // Verify DUID type 232 | dType := DUIDType(b.Read16()) 233 | if dType != DUIDTypeEN { 234 | return errInvalidDUIDEN 235 | } 236 | d.Type = dType 237 | d.EnterpriseNumber = b.Read32() 238 | d.Identifier = b.Remaining() 239 | return nil 240 | } 241 | 242 | // DUIDLL represents a DUID Based on Link-layer Address [DUID-LL], 243 | // as defined in RFC 3315, Section 9.4. 244 | // 245 | // This DUID type is recommended for devices with a 246 | // permanently-connected network interface, but without stable, 247 | // persistent storage. 248 | // 249 | // DUIDLL values are generated automatically for Servers which are not 250 | // created with a ServerID, using the hardware type found by HardwareType 251 | // and the hardware address of the listening network interface. 252 | type DUIDLL struct { 253 | // Type specifies the DUID type. For a DUIDLL, this should always be 254 | // DUIDTypeLL. 255 | Type DUIDType 256 | 257 | // HardwareType specifies an IANA-assigned hardware type, as described 258 | // in RFC 826. 259 | HardwareType uint16 260 | 261 | // HardwareAddr specifies the hardware address for an arbitrary link-layer 262 | // interface on a device, used in generating the DUIDLL. This value 263 | // could represent any arbitrary interface on a system, and should not be 264 | // treated as a client or server's communicating hardware address. 265 | HardwareAddr net.HardwareAddr 266 | } 267 | 268 | // NewDUIDLL generates a new DUIDLL from an input IANA-assigned hardware 269 | // type and a hardware address. 270 | func NewDUIDLL(hardwareType uint16, hardwareAddr net.HardwareAddr) *DUIDLL { 271 | return &DUIDLL{ 272 | Type: DUIDTypeLL, 273 | HardwareType: hardwareType, 274 | HardwareAddr: hardwareAddr, 275 | } 276 | } 277 | 278 | // MarshalBinary allocates a byte slice containing the data from a DUIDLL. 279 | func (d *DUIDLL) MarshalBinary() ([]byte, error) { 280 | // 2 bytes: DUID type 281 | // 2 bytes: hardware type 282 | // N bytes: hardware address 283 | b := buffer.New(nil) 284 | 285 | b.Write16(uint16(d.Type)) 286 | b.Write16(d.HardwareType) 287 | b.WriteBytes(d.HardwareAddr) 288 | 289 | return b.Data(), nil 290 | } 291 | 292 | // UnmarshalBinary unmarshals a raw byte slice into a DUIDLL. 293 | // If the byte slice does not contain enough data to form a valid 294 | // DUIDLL, or another DUID type is indicated, errInvalidDUIDLL is returned. 295 | func (d *DUIDLL) UnmarshalBinary(p []byte) error { 296 | b := buffer.New(p) 297 | // Too short to be DUIDLL 298 | if b.Len() < 4 { 299 | return io.ErrUnexpectedEOF 300 | } 301 | 302 | // Verify DUID type 303 | dType := DUIDType(b.Read16()) 304 | if dType != DUIDTypeLL { 305 | return errInvalidDUIDLL 306 | } 307 | d.Type = dType 308 | d.HardwareType = b.Read16() 309 | d.HardwareAddr = b.Remaining() 310 | 311 | return nil 312 | } 313 | 314 | // DUIDUUID represents a DUID based on Universally Unique Identifier 315 | // [DUID-UUID], as defined in RFC 6355. This DUID type uses a UUID to 316 | // identify clients or servers. 317 | type DUIDUUID struct { 318 | // Type specifies the DUID type. For a DUIDUUID, this should always be 319 | // DUIDTypeUUID. 320 | Type DUIDType 321 | 322 | // UUID specifies a Universally Unique Identifier, as described in RFC 4578. 323 | UUID [16]byte 324 | } 325 | 326 | // NewDUIDUUID generates a new DUIDUUID using an input UUID. 327 | func NewDUIDUUID(uuid [16]byte) *DUIDUUID { 328 | return &DUIDUUID{ 329 | Type: DUIDTypeUUID, 330 | UUID: uuid, 331 | } 332 | } 333 | 334 | // MarshalBinary allocates a byte slice containing the data from a DUIDUUID. 335 | func (d *DUIDUUID) MarshalBinary() ([]byte, error) { 336 | // 2 bytes: DUID type 337 | // 16 bytes: UUID 338 | b := buffer.New(nil) 339 | 340 | b.Write16(uint16(d.Type)) 341 | b.WriteBytes(d.UUID[:]) 342 | 343 | return b.Data(), nil 344 | } 345 | 346 | // UnmarshalBinary unmarshals a raw byte slice into a DUIDUUID. 347 | // If the byte slice does not contain the exact number of bytes 348 | // needed to form a valid DUIDUUID, or another DUID type is indicated, 349 | // errInvalidDUIDUUID is returned. 350 | func (d *DUIDUUID) UnmarshalBinary(p []byte) error { 351 | b := buffer.New(p) 352 | // DUIDUUIDs are fixed-length structures 353 | if b.Len() != 18 { 354 | return io.ErrUnexpectedEOF 355 | } 356 | 357 | // Verify DUID type 358 | dType := DUIDType(b.Read16()) 359 | if dType != DUIDTypeUUID { 360 | return errInvalidDUIDUUID 361 | } 362 | d.Type = dType 363 | b.ReadBytes(d.UUID[:]) 364 | return nil 365 | } 366 | 367 | // parseDUID returns the correct DUID type of the input byte slice as a 368 | // DUID interface type. 369 | func parseDUID(p []byte) (DUID, error) { 370 | b := buffer.New(p) 371 | // DUID must have enough bytes to determine its type 372 | if b.Len() < 2 { 373 | return nil, io.ErrUnexpectedEOF 374 | } 375 | 376 | var d DUID 377 | switch DUIDType(b.Read16()) { 378 | case DUIDTypeLLT: 379 | d = new(DUIDLLT) 380 | case DUIDTypeEN: 381 | d = new(DUIDEN) 382 | case DUIDTypeLL: 383 | d = new(DUIDLL) 384 | case DUIDTypeUUID: 385 | d = new(DUIDUUID) 386 | default: 387 | return nil, errUnknownDUID 388 | } 389 | 390 | return d, d.UnmarshalBinary(p) 391 | } 392 | -------------------------------------------------------------------------------- /dhcp6opts/duid_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net" 7 | "reflect" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | // TestNewDUIDLLT verifies that NewDUIDLLT generates a proper DUIDLLT or error 13 | // from an input hardware type, time value, and hardware address. 14 | func TestNewDUIDLLT(t *testing.T) { 15 | var tests = []struct { 16 | desc string 17 | hardwareType uint16 18 | time time.Time 19 | hardwareAddr net.HardwareAddr 20 | duid *DUIDLLT 21 | err error 22 | }{ 23 | { 24 | desc: "date too early", 25 | time: duidLLTTime.Add(-1 * time.Minute), 26 | err: ErrInvalidDUIDLLTTime, 27 | }, 28 | { 29 | desc: "OK", 30 | hardwareType: 1, 31 | time: duidLLTTime.Add(1 * time.Minute), 32 | hardwareAddr: net.HardwareAddr([]byte{0, 1, 0, 1, 0, 1}), 33 | duid: &DUIDLLT{ 34 | Type: DUIDTypeLLT, 35 | HardwareType: 1, 36 | Time: duidLLTTime.Add(1 * time.Minute).Sub(duidLLTTime), 37 | HardwareAddr: net.HardwareAddr([]byte{0, 1, 0, 1, 0, 1}), 38 | }, 39 | }, 40 | } 41 | 42 | for i, tt := range tests { 43 | duid, err := NewDUIDLLT(tt.hardwareType, tt.time, tt.hardwareAddr) 44 | if err != nil { 45 | if want, got := tt.err, err; want != got { 46 | t.Fatalf("[%02d] test %q, unexpected error: %v != %v", 47 | i, tt.desc, want, got) 48 | } 49 | 50 | continue 51 | } 52 | 53 | if want, got := tt.duid, duid; !reflect.DeepEqual(want, got) { 54 | t.Fatalf("[%02d] test %q, unexpected DUIDLLT:\n- want %v\n- got %v", 55 | i, tt.desc, want, got) 56 | } 57 | } 58 | } 59 | 60 | // TestDUIDLLTUnmarshalBinary verifies that DUIDLLT.UnmarshalBinary creates 61 | // appropriate DUIDLLTs and errors for various input byte slices. 62 | func TestDUIDLLTUnmarshalBinary(t *testing.T) { 63 | var tests = []struct { 64 | desc string 65 | buf []byte 66 | duid *DUIDLLT 67 | err error 68 | }{ 69 | { 70 | desc: "nil buffer, invalid DUID-LLT", 71 | err: io.ErrUnexpectedEOF, 72 | }, 73 | { 74 | desc: "empty buffer, invalid DUID-LLT", 75 | buf: []byte{}, 76 | err: io.ErrUnexpectedEOF, 77 | }, 78 | { 79 | desc: "length 7 buffer, invalid DUID-LLT", 80 | buf: bytes.Repeat([]byte{0}, 7), 81 | err: io.ErrUnexpectedEOF, 82 | }, 83 | { 84 | desc: "wrong DUID type", 85 | buf: []byte{ 86 | 0, 2, 87 | 0, 0, 88 | 0, 0, 0, 0, 89 | 0, 0, 0, 0, 0, 0, 90 | }, 91 | err: errInvalidDUIDLLT, 92 | }, 93 | { 94 | desc: "OK DUIDLLT", 95 | buf: []byte{ 96 | 0, 1, 97 | 0, 1, 98 | 0, 0, 0, 60, 99 | 0, 1, 0, 1, 0, 1, 100 | }, 101 | duid: &DUIDLLT{ 102 | Type: DUIDTypeLLT, 103 | HardwareType: 1, 104 | Time: 1 * time.Minute, 105 | HardwareAddr: []byte{0, 1, 0, 1, 0, 1}, 106 | }, 107 | }, 108 | } 109 | 110 | for i, tt := range tests { 111 | duid := new(DUIDLLT) 112 | if err := duid.UnmarshalBinary(tt.buf); err != nil { 113 | if want, got := tt.err, err; want != got { 114 | t.Fatalf("[%02d] test %q, unexpected error: %v != %v", 115 | i, tt.desc, want, got) 116 | } 117 | 118 | continue 119 | } 120 | 121 | if want, got := tt.duid, duid; !reflect.DeepEqual(want, got) { 122 | t.Fatalf("[%02d] test %q, unexpected DUID-LLT:\n- want: %v\n- got: %v", 123 | i, tt.desc, want, got) 124 | } 125 | } 126 | } 127 | 128 | // TestNewDUIDEN verifies that NewDUIDEN generates a proper DUIDEN from 129 | // an input enterprise number and identifier. 130 | func TestNewDUIDEN(t *testing.T) { 131 | var tests = []struct { 132 | enterpriseNumber uint32 133 | identifier []byte 134 | duid *DUIDEN 135 | }{ 136 | { 137 | enterpriseNumber: 100, 138 | identifier: []byte{0, 1, 2, 3, 4}, 139 | duid: &DUIDEN{ 140 | Type: DUIDTypeEN, 141 | EnterpriseNumber: 100, 142 | Identifier: []byte{0, 1, 2, 3, 4}, 143 | }, 144 | }, 145 | } 146 | 147 | for i, tt := range tests { 148 | if want, got := tt.duid, NewDUIDEN(tt.enterpriseNumber, tt.identifier); !reflect.DeepEqual(want, got) { 149 | t.Fatalf("[%02d] unexpected DUIDEN:\n- want %v\n- got %v", i, want, got) 150 | } 151 | } 152 | } 153 | 154 | // TestDUIDENUnmarshalBinary verifies that DUIDEN.UnmarshalBinary creates 155 | // appropriate DUIDENs and errors for various input byte slices. 156 | func TestDUIDENUnmarshalBinary(t *testing.T) { 157 | var tests = []struct { 158 | desc string 159 | buf []byte 160 | duid *DUIDEN 161 | err error 162 | }{ 163 | { 164 | desc: "nil buffer, invalid DUID-EN", 165 | err: io.ErrUnexpectedEOF, 166 | }, 167 | { 168 | desc: "empty buffer, invalid DUID-EN", 169 | buf: []byte{}, 170 | err: io.ErrUnexpectedEOF, 171 | }, 172 | { 173 | desc: "length 5 buffer, invalid DUID-EN", 174 | buf: bytes.Repeat([]byte{0}, 5), 175 | err: io.ErrUnexpectedEOF, 176 | }, 177 | { 178 | desc: "wrong DUID type", 179 | buf: []byte{ 180 | 0, 3, 181 | 0, 0, 0, 0, 182 | }, 183 | err: errInvalidDUIDEN, 184 | }, 185 | { 186 | desc: "OK DUIDEN", 187 | buf: []byte{ 188 | 0, 2, 189 | 0, 0, 0, 100, 190 | 0, 1, 2, 3, 4, 5, 191 | }, 192 | duid: &DUIDEN{ 193 | Type: DUIDTypeEN, 194 | EnterpriseNumber: 100, 195 | Identifier: []byte{0, 1, 2, 3, 4, 5}, 196 | }, 197 | }, 198 | } 199 | 200 | for i, tt := range tests { 201 | duid := new(DUIDEN) 202 | if err := duid.UnmarshalBinary(tt.buf); err != nil { 203 | if want, got := tt.err, err; want != got { 204 | t.Fatalf("[%02d] test %q, unexpected error: %v != %v", 205 | i, tt.desc, want, got) 206 | } 207 | 208 | continue 209 | } 210 | 211 | if want, got := tt.duid, duid; !reflect.DeepEqual(want, got) { 212 | t.Fatalf("[%02d] test %q, unexpected DUID-EN:\n- want: %v\n- got: %v", 213 | i, tt.desc, want, got) 214 | } 215 | } 216 | } 217 | 218 | // TestNewDUIDLL verifies that NewDUIDLL generates a proper DUIDLL from 219 | // an input hardware type and hardware address. 220 | func TestNewDUIDLL(t *testing.T) { 221 | var tests = []struct { 222 | hardwareType uint16 223 | hardwareAddr net.HardwareAddr 224 | duid *DUIDLL 225 | }{ 226 | { 227 | hardwareType: 1, 228 | hardwareAddr: net.HardwareAddr([]byte{0, 0, 0, 0, 0, 0}), 229 | duid: &DUIDLL{ 230 | Type: DUIDTypeLL, 231 | HardwareType: 1, 232 | HardwareAddr: net.HardwareAddr([]byte{0, 0, 0, 0, 0, 0}), 233 | }, 234 | }, 235 | } 236 | 237 | for i, tt := range tests { 238 | if want, got := tt.duid, NewDUIDLL(tt.hardwareType, tt.hardwareAddr); !reflect.DeepEqual(want, got) { 239 | t.Fatalf("[%02d] unexpected DUIDLL:\n- want %v\n- got %v", i, want, got) 240 | } 241 | } 242 | } 243 | 244 | // TestDUIDLLUnmarshalBinary verifies that DUIDLL.UnmarshalBinary creates 245 | // appropriate DUIDLLs and errors for various input byte slices. 246 | func TestDUIDLLUnmarshalBinary(t *testing.T) { 247 | var tests = []struct { 248 | desc string 249 | buf []byte 250 | duid *DUIDLL 251 | err error 252 | }{ 253 | { 254 | desc: "nil buffer, invalid DUID-LL", 255 | err: io.ErrUnexpectedEOF, 256 | }, 257 | { 258 | desc: "empty buffer, invalid DUID-LL", 259 | buf: []byte{}, 260 | err: io.ErrUnexpectedEOF, 261 | }, 262 | { 263 | desc: "length 7 buffer, invalid DUID-LL", 264 | buf: bytes.Repeat([]byte{0}, 7), 265 | err: errInvalidDUIDLL, 266 | }, 267 | { 268 | desc: "wrong DUID type", 269 | buf: []byte{ 270 | 0, 1, 271 | 0, 0, 272 | 0, 0, 0, 0, 273 | 0, 0, 0, 0, 0, 0, 274 | }, 275 | err: errInvalidDUIDLL, 276 | }, 277 | { 278 | desc: "OK DUIDLL", 279 | buf: []byte{ 280 | 0, 3, 281 | 0, 1, 282 | 0, 1, 0, 1, 0, 1, 283 | }, 284 | duid: &DUIDLL{ 285 | Type: DUIDTypeLL, 286 | HardwareType: 1, 287 | HardwareAddr: []byte{0, 1, 0, 1, 0, 1}, 288 | }, 289 | }, 290 | } 291 | 292 | for i, tt := range tests { 293 | duid := new(DUIDLL) 294 | if err := duid.UnmarshalBinary(tt.buf); err != nil { 295 | if want, got := tt.err, err; want != got { 296 | t.Fatalf("[%02d] test %q, unexpected error: %v != %v", 297 | i, tt.desc, want, got) 298 | } 299 | 300 | continue 301 | } 302 | 303 | if want, got := tt.duid, duid; !reflect.DeepEqual(want, got) { 304 | t.Fatalf("[%02d] test %q, unexpected DUID-LL:\n- want: %v\n- got: %v", 305 | i, tt.desc, want, got) 306 | } 307 | } 308 | } 309 | 310 | // TestNewDUIDUUID verifies that NewDUIDUUID generates a proper DUIDUUID from 311 | // an input UUID. 312 | func TestNewDUIDUUID(t *testing.T) { 313 | var tests = []struct { 314 | uuid [16]byte 315 | duid *DUIDUUID 316 | }{ 317 | { 318 | uuid: [16]byte{ 319 | 1, 1, 1, 1, 320 | 2, 2, 2, 2, 321 | 3, 3, 3, 3, 322 | 4, 4, 4, 4, 323 | }, 324 | duid: &DUIDUUID{ 325 | Type: DUIDTypeUUID, 326 | UUID: [16]byte{ 327 | 1, 1, 1, 1, 328 | 2, 2, 2, 2, 329 | 3, 3, 3, 3, 330 | 4, 4, 4, 4, 331 | }, 332 | }, 333 | }, 334 | } 335 | 336 | for i, tt := range tests { 337 | if want, got := tt.duid, NewDUIDUUID(tt.uuid); !reflect.DeepEqual(want, got) { 338 | t.Fatalf("[%02d] unexpected DUIDUUID:\n- want %v\n- got %v", i, want, got) 339 | } 340 | } 341 | } 342 | 343 | // TestDUIDUUIDUnmarshalBinary verifies that DUIDUUID.UnmarshalBinary returns 344 | // appropriate DUIDUUIDs and errors for various input byte slices. 345 | func TestDUIDUUIDUnmarshalBinary(t *testing.T) { 346 | var tests = []struct { 347 | desc string 348 | buf []byte 349 | duid *DUIDUUID 350 | err error 351 | }{ 352 | { 353 | desc: "nil buffer, invalid DUID-UUID", 354 | err: io.ErrUnexpectedEOF, 355 | }, 356 | { 357 | desc: "empty buffer, invalid DUID-UUID", 358 | buf: []byte{}, 359 | err: io.ErrUnexpectedEOF, 360 | }, 361 | { 362 | desc: "length 17 buffer, invalid DUID-UUID", 363 | buf: bytes.Repeat([]byte{0}, 17), 364 | err: io.ErrUnexpectedEOF, 365 | }, 366 | { 367 | desc: "length 19 buffer, invalid DUID-UUID", 368 | buf: bytes.Repeat([]byte{0}, 19), 369 | err: io.ErrUnexpectedEOF, 370 | }, 371 | { 372 | desc: "wrong DUID type", 373 | buf: []byte{ 374 | 0, 2, 375 | 0, 0, 0, 0, 376 | 0, 0, 0, 0, 377 | 0, 0, 0, 0, 378 | 0, 0, 0, 0, 379 | }, 380 | err: errInvalidDUIDUUID, 381 | }, 382 | { 383 | desc: "OK DUIDUUID", 384 | buf: []byte{ 385 | 0, 4, 386 | 1, 1, 1, 1, 387 | 2, 2, 2, 2, 388 | 3, 3, 3, 3, 389 | 4, 4, 4, 4, 390 | }, 391 | duid: &DUIDUUID{ 392 | Type: DUIDTypeUUID, 393 | UUID: [16]byte{ 394 | 1, 1, 1, 1, 395 | 2, 2, 2, 2, 396 | 3, 3, 3, 3, 397 | 4, 4, 4, 4, 398 | }, 399 | }, 400 | }, 401 | } 402 | 403 | for i, tt := range tests { 404 | duid := new(DUIDUUID) 405 | if err := duid.UnmarshalBinary(tt.buf); err != nil { 406 | if want, got := tt.err, err; want != got { 407 | t.Fatalf("[%02d] test %q, unexpected error: %v != %v", 408 | i, tt.desc, want, got) 409 | } 410 | 411 | continue 412 | } 413 | 414 | if want, got := tt.duid, duid; !reflect.DeepEqual(want, got) { 415 | t.Fatalf("[%02d] test %q, unexpected DUID-UUID:\n- want: %v\n- got: %v", 416 | i, tt.desc, want, got) 417 | } 418 | } 419 | } 420 | 421 | // Test_parseDUID verifies that parseDUID detects the correct DUID type for a 422 | // variety of input data. 423 | func Test_parseDUID(t *testing.T) { 424 | var tests = []struct { 425 | buf []byte 426 | result reflect.Type 427 | err error 428 | }{ 429 | { 430 | buf: []byte{0}, 431 | err: io.ErrUnexpectedEOF, 432 | }, 433 | { 434 | buf: []byte{0, 0}, 435 | err: errUnknownDUID, 436 | }, 437 | // Known types padded out to be just long enough to not error 438 | { 439 | buf: []byte{0, 1, 0, 0, 0, 0, 0, 0}, 440 | result: reflect.TypeOf(&DUIDLLT{}), 441 | }, 442 | { 443 | buf: []byte{0, 2, 0, 0, 0, 0}, 444 | result: reflect.TypeOf(&DUIDEN{}), 445 | }, 446 | { 447 | buf: []byte{0, 3, 0, 0}, 448 | result: reflect.TypeOf(&DUIDLL{}), 449 | }, 450 | { 451 | buf: append([]byte{0, 4}, bytes.Repeat([]byte{0}, 16)...), 452 | result: reflect.TypeOf(&DUIDUUID{}), 453 | }, 454 | { 455 | buf: []byte{0, 5}, 456 | err: errUnknownDUID, 457 | }, 458 | } 459 | 460 | for i, tt := range tests { 461 | d, err := parseDUID(tt.buf) 462 | if err != nil { 463 | if want, got := tt.err, err; want != got { 464 | t.Fatalf("[%02d] unexpected error for parseDUID(%v): %v != %v", 465 | i, tt.buf, want, got) 466 | } 467 | 468 | continue 469 | } 470 | 471 | if want, got := tt.result, reflect.TypeOf(d); want != got { 472 | t.Fatalf("[%02d] unexpected type for parseDUID(%v): %v != %v", 473 | i, tt.buf, want, got) 474 | } 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /dhcp6opts/iaaddr.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "time" 7 | 8 | "github.com/mdlayher/dhcp6" 9 | "github.com/mdlayher/dhcp6/internal/buffer" 10 | ) 11 | 12 | // IAAddr represents an Identity Association Address, as defined in RFC 3315, 13 | // Section 22.6. 14 | // 15 | // DHCP clients use identity association addresses (IAAddrs) to request IPv6 16 | // addresses from a DHCP server, using the lifetimes specified in the preferred 17 | // lifetime and valid lifetime fields. Multiple IAAddrs may be present in a 18 | // single DHCP request, but only enscapsulated within an IANA or IATA options 19 | // field. 20 | type IAAddr struct { 21 | // IP specifies the IPv6 address to offer to a client. The validity of the 22 | // address is controlled by the PreferredLifetime and ValidLifetime fields. 23 | IP net.IP 24 | 25 | // PreferredLifetime specifies the preferred lifetime of an IPv6 address. 26 | // When the preferred lifetime of an address expires, the address becomes 27 | // deprecated, and should not be used in new communications. 28 | // 29 | // The preferred lifetime of an address must not be greater than its 30 | // valid lifetime. 31 | PreferredLifetime time.Duration 32 | 33 | // ValidLifetime specifies the valid lifetime of an IPv6 address. When the 34 | // valid lifetime of an address expires, the address should not be used for 35 | // any further communication. 36 | // 37 | // The valid lifetime of an address must be greater than its preferred 38 | // lifetime. 39 | ValidLifetime time.Duration 40 | 41 | // Options specifies a map of DHCP options specific to this IAAddr. 42 | // Its methods can be used to retrieve data from an incoming IAAddr, or 43 | // send data with an outgoing IAAddr. 44 | Options dhcp6.Options 45 | } 46 | 47 | // NewIAAddr creates a new IAAddr from an IPv6 address, preferred and valid lifetime 48 | // durations, and an optional Options map. 49 | // 50 | // The IP must be exactly 16 bytes, the correct length for an IPv6 address. 51 | // The preferred lifetime duration must be less than the valid lifetime 52 | // duration. Failure to meet either of these conditions will result in an error. 53 | // If an Options map is not specified, a new one will be allocated. 54 | func NewIAAddr(ip net.IP, preferred time.Duration, valid time.Duration, options dhcp6.Options) (*IAAddr, error) { 55 | // From documentation: If ip is not an IPv4 address, To4 returns nil. 56 | if ip.To4() != nil { 57 | return nil, ErrInvalidIP 58 | } 59 | 60 | // Preferred lifetime must always be less than valid lifetime. 61 | if preferred > valid { 62 | return nil, ErrInvalidLifetimes 63 | } 64 | 65 | // If no options set, make empty map 66 | if options == nil { 67 | options = make(dhcp6.Options) 68 | } 69 | 70 | return &IAAddr{ 71 | IP: ip, 72 | PreferredLifetime: preferred, 73 | ValidLifetime: valid, 74 | Options: options, 75 | }, nil 76 | } 77 | 78 | // MarshalBinary allocates a byte slice containing the data from a IAAddr. 79 | func (i *IAAddr) MarshalBinary() ([]byte, error) { 80 | // 16 bytes: IPv6 address 81 | // 4 bytes: preferred lifetime 82 | // 4 bytes: valid lifetime 83 | // N bytes: options 84 | b := buffer.New(nil) 85 | 86 | copy(b.WriteN(net.IPv6len), i.IP) 87 | b.Write32(uint32(i.PreferredLifetime / time.Second)) 88 | b.Write32(uint32(i.ValidLifetime / time.Second)) 89 | opts, err := i.Options.MarshalBinary() 90 | if err != nil { 91 | return nil, err 92 | } 93 | b.WriteBytes(opts) 94 | 95 | return b.Data(), nil 96 | } 97 | 98 | // UnmarshalBinary unmarshals a raw byte slice into a IAAddr. 99 | // 100 | // If the byte slice does not contain enough data to form a valid IAAddr, 101 | // io.ErrUnexpectedEOF is returned. If the preferred lifetime value in the 102 | // byte slice is less than the valid lifetime, ErrInvalidLifetimes is returned. 103 | func (i *IAAddr) UnmarshalBinary(p []byte) error { 104 | b := buffer.New(p) 105 | if b.Len() < 24 { 106 | return io.ErrUnexpectedEOF 107 | } 108 | 109 | i.IP = make(net.IP, net.IPv6len) 110 | copy(i.IP, b.Consume(net.IPv6len)) 111 | 112 | i.PreferredLifetime = time.Duration(b.Read32()) * time.Second 113 | i.ValidLifetime = time.Duration(b.Read32()) * time.Second 114 | 115 | // Preferred lifetime must always be less than valid lifetime. 116 | if i.PreferredLifetime > i.ValidLifetime { 117 | return ErrInvalidLifetimes 118 | } 119 | 120 | return (&i.Options).UnmarshalBinary(b.Remaining()) 121 | } 122 | -------------------------------------------------------------------------------- /dhcp6opts/iaaddr_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net" 7 | "testing" 8 | "time" 9 | 10 | "github.com/mdlayher/dhcp6" 11 | ) 12 | 13 | // TestNewIAAddr verifies that NewIAAddr creates a proper IAAddr value or returns 14 | // a correct error for input values. 15 | func TestNewIAAddr(t *testing.T) { 16 | var tests = []struct { 17 | desc string 18 | ip net.IP 19 | preferred time.Duration 20 | valid time.Duration 21 | options dhcp6.Options 22 | iaaddr *IAAddr 23 | err error 24 | }{ 25 | { 26 | desc: "all zero values", 27 | iaaddr: &IAAddr{}, 28 | }, 29 | { 30 | desc: "IPv4 address", 31 | ip: net.IP([]byte{192, 168, 1, 1}), 32 | err: ErrInvalidIP, 33 | }, 34 | { 35 | desc: "preferred greater than valid lifetime", 36 | ip: net.IPv6loopback, 37 | preferred: 2 * time.Second, 38 | valid: 1 * time.Second, 39 | err: ErrInvalidLifetimes, 40 | }, 41 | { 42 | desc: "IPv6 localhost, 1s preferred, 2s valid, no options", 43 | ip: net.IPv6loopback, 44 | preferred: 1 * time.Second, 45 | valid: 2 * time.Second, 46 | iaaddr: &IAAddr{ 47 | IP: net.IPv6loopback, 48 | PreferredLifetime: 1 * time.Second, 49 | ValidLifetime: 2 * time.Second, 50 | }, 51 | }, 52 | { 53 | desc: "IPv6 localhost, 1s preferred, 2s valid, option client ID [0 1]", 54 | ip: net.IPv6loopback, 55 | preferred: 1 * time.Second, 56 | valid: 2 * time.Second, 57 | options: dhcp6.Options{ 58 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 59 | }, 60 | iaaddr: &IAAddr{ 61 | IP: net.IPv6loopback, 62 | PreferredLifetime: 1 * time.Second, 63 | ValidLifetime: 2 * time.Second, 64 | Options: dhcp6.Options{ 65 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 66 | }, 67 | }, 68 | }, 69 | } 70 | 71 | for i, tt := range tests { 72 | iaaddr, err := NewIAAddr(tt.ip, tt.preferred, tt.valid, tt.options) 73 | if err != nil { 74 | if want, got := tt.err, err; want != got { 75 | t.Fatalf("[%02d] test %q, unexpected error for NewIAAddr: %v != %v", 76 | i, tt.desc, want, got) 77 | } 78 | 79 | continue 80 | } 81 | 82 | want, err := tt.iaaddr.MarshalBinary() 83 | if err != nil { 84 | t.Fatal(err) 85 | } 86 | got, err := iaaddr.MarshalBinary() 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | 91 | if !bytes.Equal(want, got) { 92 | t.Fatalf("[%02d] test %q, unexpected IAAddr bytes:\n- want: %v\n- got: %v", 93 | i, tt.desc, want, got) 94 | } 95 | } 96 | } 97 | 98 | // TestIAAddrUnmarshalBinary verifies that IAAddr.UnmarshalBinary produces a 99 | // correct IAAddr value or error for an input buffer. 100 | func TestIAAddrUnmarshalBinary(t *testing.T) { 101 | var tests = []struct { 102 | desc string 103 | buf []byte 104 | iaaddr *IAAddr 105 | err error 106 | }{ 107 | { 108 | desc: "one byte IAAddr", 109 | buf: []byte{0}, 110 | err: io.ErrUnexpectedEOF, 111 | }, 112 | { 113 | desc: "23 bytes IAAddr", 114 | buf: bytes.Repeat([]byte{0}, 23), 115 | err: io.ErrUnexpectedEOF, 116 | }, 117 | { 118 | desc: "preferred greater than valid lifetime", 119 | buf: append(net.IPv6zero, []byte{ 120 | 0, 0, 0, 2, 121 | 0, 0, 0, 1, 122 | }...), 123 | err: ErrInvalidLifetimes, 124 | }, 125 | { 126 | desc: "invalid options (length mismatch)", 127 | buf: append(net.IPv6zero, []byte{ 128 | 0, 0, 0, 1, 129 | 0, 0, 0, 2, 130 | 0, 1, 0, 1, 131 | }...), 132 | err: dhcp6.ErrInvalidOptions, 133 | }, 134 | { 135 | desc: "IPv6 loopback, 1s preferred, 2s valid, no options", 136 | buf: append(net.IPv6loopback, []byte{ 137 | 0, 0, 0, 1, 138 | 0, 0, 0, 2, 139 | }...), 140 | iaaddr: &IAAddr{ 141 | IP: net.IPv6loopback, 142 | PreferredLifetime: 1 * time.Second, 143 | ValidLifetime: 2 * time.Second, 144 | }, 145 | }, 146 | { 147 | desc: "IPv6 loopback, 1s preferred, 2s valid, option client ID [0 1]", 148 | buf: append(net.IPv6loopback, []byte{ 149 | 0, 0, 0, 1, 150 | 0, 0, 0, 2, 151 | 0, 1, 0, 2, 0, 1, 152 | }...), 153 | iaaddr: &IAAddr{ 154 | IP: net.IPv6loopback, 155 | PreferredLifetime: 1 * time.Second, 156 | ValidLifetime: 2 * time.Second, 157 | Options: dhcp6.Options{ 158 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 159 | }, 160 | }, 161 | }, 162 | } 163 | 164 | for i, tt := range tests { 165 | iaaddr := new(IAAddr) 166 | if err := iaaddr.UnmarshalBinary(tt.buf); err != nil { 167 | if want, got := tt.err, err; want != got { 168 | t.Fatalf("[%02d] test %q, unexpected error for parseIAAddr: %v != %v", 169 | i, tt.desc, want, got) 170 | } 171 | 172 | continue 173 | } 174 | 175 | want, err := tt.iaaddr.MarshalBinary() 176 | if err != nil { 177 | t.Fatal(err) 178 | } 179 | got, err := iaaddr.MarshalBinary() 180 | if err != nil { 181 | t.Fatal(err) 182 | } 183 | 184 | if !bytes.Equal(want, got) { 185 | t.Fatalf("[%02d] test %q, unexpected IAAddr bytes for parseIAAddr:\n- want: %v\n- got: %v", 186 | i, tt.desc, want, got) 187 | } 188 | 189 | for _, v := range iaaddr.Options { 190 | for ii := range v { 191 | if want, got := cap(v[ii]), cap(v[ii]); want != got { 192 | t.Fatalf("[%02d] test %q, unexpected capacity option data:\n- want: %v\n- got: %v", 193 | i, tt.desc, want, got) 194 | } 195 | } 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /dhcp6opts/iana.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | "time" 6 | 7 | "github.com/mdlayher/dhcp6" 8 | "github.com/mdlayher/dhcp6/internal/buffer" 9 | ) 10 | 11 | // IANA represents an Identity Association for Non-temporary Addresses, as 12 | // defined in RFC 3315, Section 22.4. 13 | // 14 | // Multiple IANAs may be present in a single DHCP request. 15 | type IANA struct { 16 | // IAID specifies a DHCP identity association identifier. The IAID 17 | // is a unique, client-generated identifier. 18 | IAID [4]byte 19 | 20 | // T1 specifies how long a DHCP client will wait to contact this server, 21 | // to extend the lifetimes of the addresses assigned to this IANA 22 | // by this server. 23 | T1 time.Duration 24 | 25 | // T2 specifies how long a DHCP client will wait to contact any server, 26 | // to extend the lifetimes of the addresses assigned to this IANA 27 | // by this server. 28 | T2 time.Duration 29 | 30 | // Options specifies a map of DHCP options specific to this IANA. 31 | // Its methods can be used to retrieve data from an incoming IANA, or send 32 | // data with an outgoing IANA. 33 | Options dhcp6.Options 34 | } 35 | 36 | // NewIANA creates a new IANA from an IAID, T1 and T2 durations, and an 37 | // Options map. If an Options map is not specified, a new one will be 38 | // allocated. 39 | func NewIANA(iaid [4]byte, t1 time.Duration, t2 time.Duration, options dhcp6.Options) *IANA { 40 | if options == nil { 41 | options = make(dhcp6.Options) 42 | } 43 | 44 | return &IANA{ 45 | IAID: iaid, 46 | T1: t1, 47 | T2: t2, 48 | Options: options, 49 | } 50 | } 51 | 52 | // MarshalBinary allocates a byte slice containing the data from a IANA. 53 | func (i IANA) MarshalBinary() ([]byte, error) { 54 | // 4 bytes: IAID 55 | // 4 bytes: T1 56 | // 4 bytes: T2 57 | // N bytes: options slice byte count 58 | b := buffer.New(nil) 59 | 60 | b.WriteBytes(i.IAID[:]) 61 | b.Write32(uint32(i.T1 / time.Second)) 62 | b.Write32(uint32(i.T2 / time.Second)) 63 | opts, err := i.Options.MarshalBinary() 64 | if err != nil { 65 | return nil, err 66 | } 67 | b.WriteBytes(opts) 68 | 69 | return b.Data(), nil 70 | } 71 | 72 | // UnmarshalBinary unmarshals a raw byte slice into a IANA. 73 | // 74 | // If the byte slice does not contain enough data to form a valid IANA, 75 | // io.ErrUnexpectedEOF is returned. 76 | func (i *IANA) UnmarshalBinary(p []byte) error { 77 | // IANA must contain at least an IAID, T1, and T2. 78 | b := buffer.New(p) 79 | if b.Len() < 12 { 80 | return io.ErrUnexpectedEOF 81 | } 82 | 83 | b.ReadBytes(i.IAID[:]) 84 | i.T1 = time.Duration(b.Read32()) * time.Second 85 | i.T2 = time.Duration(b.Read32()) * time.Second 86 | 87 | return (&i.Options).UnmarshalBinary(b.Remaining()) 88 | } 89 | -------------------------------------------------------------------------------- /dhcp6opts/iana_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | "time" 9 | 10 | "github.com/mdlayher/dhcp6" 11 | ) 12 | 13 | // TestNewIANA verifies that NewIANA creates a proper IANA value for 14 | // input values. 15 | func TestNewIANA(t *testing.T) { 16 | var tests = []struct { 17 | desc string 18 | iaid [4]byte 19 | t1 time.Duration 20 | t2 time.Duration 21 | options dhcp6.Options 22 | iana *IANA 23 | }{ 24 | { 25 | desc: "all zero values", 26 | iana: &IANA{}, 27 | }, 28 | { 29 | desc: "[0 1 2 3] IAID, 30s T1, 60s T2, option client ID [0 1]", 30 | iaid: [4]byte{0, 1, 2, 3}, 31 | t1: 30 * time.Second, 32 | t2: 60 * time.Second, 33 | options: dhcp6.Options{ 34 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 35 | }, 36 | iana: &IANA{ 37 | IAID: [4]byte{0, 1, 2, 3}, 38 | T1: 30 * time.Second, 39 | T2: 60 * time.Second, 40 | Options: dhcp6.Options{ 41 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 42 | }, 43 | }, 44 | }, 45 | } 46 | 47 | for i, tt := range tests { 48 | iana := NewIANA(tt.iaid, tt.t1, tt.t2, tt.options) 49 | 50 | want, err := tt.iana.MarshalBinary() 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | got, err := iana.MarshalBinary() 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | 59 | if !bytes.Equal(want, got) { 60 | t.Fatalf("[%02d] test %q, unexpected IANA bytes for NewIANA(%v, %v, %v, %v)\n- want: %v\n- got: %v", 61 | i, tt.desc, tt.iaid, tt.t1, tt.t2, tt.options, want, got) 62 | } 63 | } 64 | } 65 | 66 | // TestIANAMarshalBinary verifies that IANA.MarshalBinary allocates and returns a correct 67 | // byte slice for a variety of input data. 68 | func TestIANAMarshalBinary(t *testing.T) { 69 | var tests = []struct { 70 | desc string 71 | iana *IANA 72 | buf []byte 73 | }{ 74 | { 75 | desc: "empty IANA", 76 | iana: &IANA{}, 77 | buf: []byte{ 78 | 0, 0, 0, 0, 79 | 0, 0, 0, 0, 80 | 0, 0, 0, 0, 81 | }, 82 | }, 83 | { 84 | desc: "[1 2 3 4] IAID only", 85 | iana: &IANA{ 86 | IAID: [4]byte{1, 2, 3, 4}, 87 | }, 88 | buf: []byte{ 89 | 1, 2, 3, 4, 90 | 0, 0, 0, 0, 91 | 0, 0, 0, 0, 92 | }, 93 | }, 94 | { 95 | desc: "[1 2 3 4] IAID, 30s T1, 60s T2", 96 | iana: &IANA{ 97 | IAID: [4]byte{1, 2, 3, 4}, 98 | T1: 30 * time.Second, 99 | T2: 60 * time.Second, 100 | }, 101 | buf: []byte{ 102 | 1, 2, 3, 4, 103 | 0, 0, 0, 30, 104 | 0, 0, 0, 60, 105 | }, 106 | }, 107 | { 108 | desc: "[1 2 3 4] IAID, 30s T1, 60s T2, option client ID [0 1]", 109 | iana: &IANA{ 110 | IAID: [4]byte{1, 2, 3, 4}, 111 | T1: 30 * time.Second, 112 | T2: 60 * time.Second, 113 | Options: dhcp6.Options{ 114 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 115 | }, 116 | }, 117 | buf: []byte{ 118 | 1, 2, 3, 4, 119 | 0, 0, 0, 30, 120 | 0, 0, 0, 60, 121 | 0, 1, 0, 2, 0, 1, 122 | }, 123 | }, 124 | } 125 | 126 | for i, tt := range tests { 127 | got, err := tt.iana.MarshalBinary() 128 | if err != nil { 129 | t.Fatal(err) 130 | } 131 | 132 | if want := tt.buf; !bytes.Equal(want, got) { 133 | t.Fatalf("[%02d] test %q, unexpected IANA bytes:\n- want: %v\n- got: %v", 134 | i, tt.desc, want, got) 135 | } 136 | } 137 | } 138 | 139 | // TestIANAUnmarshalBinary verifies that IANA.UnmarshalBinary produces a correct 140 | // IANA value or error for an input buffer. 141 | func TestIANAUnmarshalBinary(t *testing.T) { 142 | var tests = []struct { 143 | buf []byte 144 | iana *IANA 145 | options dhcp6.Options 146 | err error 147 | }{ 148 | { 149 | buf: []byte{0}, 150 | err: io.ErrUnexpectedEOF, 151 | }, 152 | { 153 | buf: bytes.Repeat([]byte{0}, 11), 154 | err: io.ErrUnexpectedEOF, 155 | }, 156 | { 157 | buf: []byte{ 158 | 1, 2, 3, 4, 159 | 0, 0, 1, 0, 160 | 0, 0, 2, 0, 161 | 0, 1, 0, 1, 162 | }, 163 | err: dhcp6.ErrInvalidOptions, 164 | }, 165 | { 166 | buf: []byte{ 167 | 1, 2, 3, 4, 168 | 0, 0, 1, 0, 169 | 0, 0, 2, 0, 170 | 0, 1, 0, 2, 0, 1, 171 | }, 172 | iana: &IANA{ 173 | IAID: [4]byte{1, 2, 3, 4}, 174 | T1: (4 * time.Minute) + 16*time.Second, 175 | T2: (8 * time.Minute) + 32*time.Second, 176 | Options: dhcp6.Options{ 177 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 178 | }, 179 | }, 180 | }, 181 | } 182 | 183 | for i, tt := range tests { 184 | iana := new(IANA) 185 | if err := iana.UnmarshalBinary(tt.buf); err != nil { 186 | if want, got := tt.err, err; want != got { 187 | t.Fatalf("[%02d] unexpected error for parseIANA(%v): %v != %v", 188 | i, tt.buf, want, got) 189 | } 190 | 191 | continue 192 | } 193 | 194 | if want, got := tt.iana, iana; !reflect.DeepEqual(want, got) { 195 | t.Fatalf("[%02d] unexpected IANA for parseIANA(%v):\n- want: %v\n- got: %v", 196 | i, tt.buf, want, got) 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /dhcp6opts/iapd.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | "time" 6 | 7 | "github.com/mdlayher/dhcp6" 8 | "github.com/mdlayher/dhcp6/internal/buffer" 9 | ) 10 | 11 | // IAPD represents an Identity Association for Prefix Delegation, as 12 | // defined in RFC 3633, Section 9. 13 | // 14 | // Multiple IAPDs may be present in a single DHCP request. 15 | type IAPD struct { 16 | // IAID specifies a DHCP identity association identifier. The IAID 17 | // is a unique, client-generated identifier. 18 | IAID [4]byte 19 | 20 | // T1 specifies how long a requesting router will wait to contact a 21 | // delegating router, to extend the lifetimes of the prefixes delegated 22 | // to this IAPD, by the delegating router. 23 | T1 time.Duration 24 | 25 | // T2 specifies how long a requesting router will wait to contact any 26 | // available delegating router, to extend the lifetimes of the prefixes 27 | // delegated to this IAPD. 28 | T2 time.Duration 29 | 30 | // Options specifies a map of DHCP options specific to this IAPD. 31 | // Its methods can be used to retrieve data from an incoming IAPD, or send 32 | // data with an outgoing IAPD. 33 | Options dhcp6.Options 34 | } 35 | 36 | // NewIAPD creates a new IAPD from an IAID, T1 and T2 durations, and an 37 | // Options map. If an Options map is not specified, a new one will be 38 | // allocated. 39 | func NewIAPD(iaid [4]byte, t1 time.Duration, t2 time.Duration, options dhcp6.Options) *IAPD { 40 | if options == nil { 41 | options = make(dhcp6.Options) 42 | } 43 | 44 | return &IAPD{ 45 | IAID: iaid, 46 | T1: t1, 47 | T2: t2, 48 | Options: options, 49 | } 50 | } 51 | 52 | // MarshalBinary allocates a byte slice containing the data from a IAPD. 53 | func (i *IAPD) MarshalBinary() ([]byte, error) { 54 | // 4 bytes: IAID 55 | // 4 bytes: T1 56 | // 4 bytes: T2 57 | // N bytes: options slice byte count 58 | buf := buffer.New(nil) 59 | 60 | buf.WriteBytes(i.IAID[:]) 61 | buf.Write32(uint32(i.T1 / time.Second)) 62 | buf.Write32(uint32(i.T2 / time.Second)) 63 | opts, err := i.Options.MarshalBinary() 64 | if err != nil { 65 | return nil, err 66 | } 67 | buf.WriteBytes(opts) 68 | 69 | return buf.Data(), nil 70 | } 71 | 72 | // UnmarshalBinary unmarshals a raw byte slice into a IAPD. 73 | // 74 | // If the byte slice does not contain enough data to form a valid IAPD, 75 | // io.ErrUnexpectedEOF is returned. 76 | func (i *IAPD) UnmarshalBinary(b []byte) error { 77 | // IAPD must contain at least an IAID, T1, and T2. 78 | buf := buffer.New(b) 79 | if buf.Len() < 12 { 80 | return io.ErrUnexpectedEOF 81 | } 82 | 83 | copy(i.IAID[:], buf.Consume(4)) 84 | i.T1 = time.Duration(buf.Read32()) * time.Second 85 | i.T2 = time.Duration(buf.Read32()) * time.Second 86 | 87 | return (&i.Options).UnmarshalBinary(buf.Remaining()) 88 | } 89 | -------------------------------------------------------------------------------- /dhcp6opts/iapd_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | "time" 9 | 10 | "github.com/mdlayher/dhcp6" 11 | ) 12 | 13 | // TestNewIAPD verifies that NewIAPD creates a proper IAPD value for 14 | // input values. 15 | func TestNewIAPD(t *testing.T) { 16 | var tests = []struct { 17 | desc string 18 | iaid [4]byte 19 | t1 time.Duration 20 | t2 time.Duration 21 | options dhcp6.Options 22 | iapd *IAPD 23 | }{ 24 | { 25 | desc: "all zero values", 26 | iapd: &IAPD{}, 27 | }, 28 | { 29 | desc: "[0 1 2 3] IAID, 30s T1, 60s T2, option client ID [0 1]", 30 | iaid: [4]byte{0, 1, 2, 3}, 31 | t1: 30 * time.Second, 32 | t2: 60 * time.Second, 33 | options: dhcp6.Options{ 34 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 35 | }, 36 | iapd: &IAPD{ 37 | IAID: [4]byte{0, 1, 2, 3}, 38 | T1: 30 * time.Second, 39 | T2: 60 * time.Second, 40 | Options: dhcp6.Options{ 41 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 42 | }, 43 | }, 44 | }, 45 | } 46 | 47 | for i, tt := range tests { 48 | iapd := NewIAPD(tt.iaid, tt.t1, tt.t2, tt.options) 49 | 50 | want, err := tt.iapd.MarshalBinary() 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | got, err := iapd.MarshalBinary() 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | 59 | if !bytes.Equal(want, got) { 60 | t.Fatalf("[%02d] test %q, unexpected IAPD bytes for NewIAPD(%v, %v, %v, %v)\n- want: %v\n- got: %v", 61 | i, tt.desc, tt.iaid, tt.t1, tt.t2, tt.options, want, got) 62 | } 63 | } 64 | } 65 | 66 | // TestIAPDUnmarshalBinary verifies that IAPD.UnmarshalBinary produces a 67 | // correct IAPD value or error for an input buffer. 68 | func TestIAPDUnmarshalBinary(t *testing.T) { 69 | var tests = []struct { 70 | buf []byte 71 | iapd *IAPD 72 | options dhcp6.Options 73 | err error 74 | }{ 75 | { 76 | buf: []byte{0}, 77 | err: io.ErrUnexpectedEOF, 78 | }, 79 | { 80 | buf: bytes.Repeat([]byte{0}, 11), 81 | err: io.ErrUnexpectedEOF, 82 | }, 83 | { 84 | buf: []byte{ 85 | 1, 2, 3, 4, 86 | 0, 0, 1, 0, 87 | 0, 0, 2, 0, 88 | 0, 1, 0, 1, 89 | }, 90 | err: dhcp6.ErrInvalidOptions, 91 | }, 92 | { 93 | buf: []byte{ 94 | 1, 2, 3, 4, 95 | 0, 0, 1, 0, 96 | 0, 0, 2, 0, 97 | 0, 1, 0, 2, 0, 1, 98 | }, 99 | iapd: &IAPD{ 100 | IAID: [4]byte{1, 2, 3, 4}, 101 | T1: (4 * time.Minute) + 16*time.Second, 102 | T2: (8 * time.Minute) + 32*time.Second, 103 | Options: dhcp6.Options{ 104 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 105 | }, 106 | }, 107 | }, 108 | } 109 | 110 | for i, tt := range tests { 111 | iapd := new(IAPD) 112 | if err := iapd.UnmarshalBinary(tt.buf); err != nil { 113 | if want, got := tt.err, err; want != got { 114 | t.Fatalf("[%02d] unexpected error for parseIAPD(%v): %v != %v", 115 | i, tt.buf, want, got) 116 | } 117 | 118 | continue 119 | } 120 | 121 | if want, got := tt.iapd, iapd; !reflect.DeepEqual(want, got) { 122 | t.Fatalf("[%02d] unexpected IAPD for parseIAPD(%v):\n- want: %v\n- got: %v", 123 | i, tt.buf, want, got) 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /dhcp6opts/iaprefix.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "time" 7 | 8 | "github.com/mdlayher/dhcp6" 9 | "github.com/mdlayher/dhcp6/internal/buffer" 10 | ) 11 | 12 | // IAPrefix represents an Identity Association Prefix, as defined in RFC 3633, 13 | // Section 10. 14 | // 15 | // Routers may use identity association prefixes (IAPrefixes) to request IPv6 16 | // prefixes to assign individual address to IPv6 clients, using the lifetimes 17 | // specified in the preferred lifetime and valid lifetime fields. Multiple 18 | // IAPrefixes may be present in a single DHCP request, but only enscapsulated 19 | // within an IAPD's options. 20 | type IAPrefix struct { 21 | // PreferredLifetime specifies the preferred lifetime of an IPv6 prefix. 22 | // When the preferred lifetime of a prefix expires, the prefix becomes 23 | // deprecated, and addresses from the prefix should not be used in new 24 | // communications. 25 | // 26 | // The preferred lifetime of a prefix must not be greater than its valid 27 | // lifetime. 28 | PreferredLifetime time.Duration 29 | 30 | // ValidLifetime specifies the valid lifetime of an IPv6 prefix. When the 31 | // valid lifetime of a prefix expires, addresses from the prefix the address 32 | // should not be used for any further communication. 33 | // 34 | // The valid lifetime of a prefix must be greater than its preferred 35 | // lifetime. 36 | ValidLifetime time.Duration 37 | 38 | // PrefixLength specifies the length in bits of an IPv6 address prefix, such 39 | // as 32, 64, etc. 40 | PrefixLength uint8 41 | 42 | // Prefix specifies the IPv6 address prefix from which IPv6 addresses can 43 | // be allocated. 44 | Prefix net.IP 45 | 46 | // Options specifies a map of DHCP options specific to this IAPrefix. 47 | // Its methods can be used to retrieve data from an incoming IAPrefix, or 48 | // send data with an outgoing IAPrefix. 49 | Options dhcp6.Options 50 | } 51 | 52 | // NewIAPrefix creates a new IAPrefix from preferred and valid lifetime 53 | // durations, an IPv6 prefix length, an IPv6 prefix, and an optional Options 54 | // map. 55 | // 56 | // The preferred lifetime duration must be less than the valid lifetime 57 | // duration. The IPv6 prefix must be exactly 16 bytes, the correct length 58 | // for an IPv6 address. Failure to meet either of these conditions will result 59 | // in an error. If an Options map is not specified, a new one will be 60 | // allocated. 61 | func NewIAPrefix(preferred time.Duration, valid time.Duration, prefixLength uint8, prefix net.IP, options dhcp6.Options) (*IAPrefix, error) { 62 | // Preferred lifetime must always be less than valid lifetime. 63 | if preferred > valid { 64 | return nil, ErrInvalidLifetimes 65 | } 66 | 67 | // From documentation: If ip is not an IPv4 address, To4 returns nil. 68 | if prefix.To4() != nil { 69 | return nil, ErrInvalidIP 70 | } 71 | 72 | // If no options set, make empty map 73 | if options == nil { 74 | options = make(dhcp6.Options) 75 | } 76 | 77 | return &IAPrefix{ 78 | PreferredLifetime: preferred, 79 | ValidLifetime: valid, 80 | PrefixLength: prefixLength, 81 | Prefix: prefix, 82 | Options: options, 83 | }, nil 84 | } 85 | 86 | // MarshalBinary allocates a byte slice containing the data from a IAPrefix. 87 | func (i *IAPrefix) MarshalBinary() ([]byte, error) { 88 | // 4 bytes: preferred lifetime 89 | // 4 bytes: valid lifetime 90 | // 1 byte : prefix length 91 | // 16 bytes: IPv6 prefix 92 | // N bytes: options 93 | b := buffer.New(nil) 94 | 95 | b.Write32(uint32(i.PreferredLifetime / time.Second)) 96 | b.Write32(uint32(i.ValidLifetime / time.Second)) 97 | b.Write8(i.PrefixLength) 98 | copy(b.WriteN(net.IPv6len), i.Prefix) 99 | opts, err := i.Options.MarshalBinary() 100 | if err != nil { 101 | return nil, err 102 | } 103 | b.WriteBytes(opts) 104 | 105 | return b.Data(), nil 106 | } 107 | 108 | // UnmarshalBinary unmarshals a raw byte slice into a IAPrefix. 109 | // 110 | // If the byte slice does not contain enough data to form a valid IAPrefix, 111 | // io.ErrUnexpectedEOF is returned. If the preferred lifetime value in the 112 | // byte slice is less than the valid lifetime, ErrInvalidLifetimes is 113 | // returned. 114 | func (i *IAPrefix) UnmarshalBinary(p []byte) error { 115 | b := buffer.New(p) 116 | // IAPrefix must at least contain lifetimes, prefix length, and prefix 117 | if b.Len() < 25 { 118 | return io.ErrUnexpectedEOF 119 | } 120 | 121 | i.PreferredLifetime = time.Duration(b.Read32()) * time.Second 122 | i.ValidLifetime = time.Duration(b.Read32()) * time.Second 123 | 124 | // Preferred lifetime must always be less than valid lifetime. 125 | if i.PreferredLifetime > i.ValidLifetime { 126 | return ErrInvalidLifetimes 127 | } 128 | 129 | i.PrefixLength = b.Read8() 130 | i.Prefix = make(net.IP, net.IPv6len) 131 | copy(i.Prefix, b.Consume(net.IPv6len)) 132 | 133 | return (&i.Options).UnmarshalBinary(b.Remaining()) 134 | } 135 | -------------------------------------------------------------------------------- /dhcp6opts/iaprefix_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net" 7 | "testing" 8 | "time" 9 | 10 | "github.com/mdlayher/dhcp6" 11 | ) 12 | 13 | // TestNewIAPrefix verifies that NewIAPrefix creates a proper IAPrefix value 14 | // or returns a correct error for input values. 15 | func TestNewIAPrefix(t *testing.T) { 16 | var tests = []struct { 17 | desc string 18 | preferred time.Duration 19 | valid time.Duration 20 | pLength uint8 21 | prefix net.IP 22 | options dhcp6.Options 23 | iaprefix *IAPrefix 24 | err error 25 | }{ 26 | { 27 | desc: "all zero values", 28 | iaprefix: &IAPrefix{}, 29 | }, 30 | { 31 | desc: "preferred greater than valid lifetime", 32 | preferred: 2 * time.Second, 33 | valid: 1 * time.Second, 34 | err: ErrInvalidLifetimes, 35 | }, 36 | { 37 | desc: "IPv4 address", 38 | prefix: net.IP([]byte{192, 168, 1, 1}), 39 | err: ErrInvalidIP, 40 | }, 41 | { 42 | desc: "1s preferred, 2s valid, '2001:db8::/32', no options", 43 | preferred: 1 * time.Second, 44 | valid: 2 * time.Second, 45 | pLength: 32, 46 | prefix: net.ParseIP("2001:db8::"), 47 | iaprefix: &IAPrefix{ 48 | PreferredLifetime: 1 * time.Second, 49 | ValidLifetime: 2 * time.Second, 50 | PrefixLength: 32, 51 | Prefix: net.ParseIP("2001:db8::"), 52 | }, 53 | }, 54 | { 55 | desc: "1s preferred, 2s valid, '2001:db8::6:1/64', option client ID [0 1]", 56 | preferred: 1 * time.Second, 57 | valid: 2 * time.Second, 58 | pLength: 64, 59 | prefix: net.ParseIP("2001:db8::6:1"), 60 | options: dhcp6.Options{ 61 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 62 | }, 63 | iaprefix: &IAPrefix{ 64 | PreferredLifetime: 1 * time.Second, 65 | ValidLifetime: 2 * time.Second, 66 | PrefixLength: 64, 67 | Prefix: net.ParseIP("2001:db8::6:1"), 68 | Options: dhcp6.Options{ 69 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 70 | }, 71 | }, 72 | }, 73 | } 74 | 75 | for i, tt := range tests { 76 | iaprefix, err := NewIAPrefix(tt.preferred, tt.valid, tt.pLength, tt.prefix, tt.options) 77 | if err != nil { 78 | if want, got := tt.err, err; want != got { 79 | t.Fatalf("[%02d] test %q, unexpected error for NewIAPrefix: %v != %v", 80 | i, tt.desc, want, got) 81 | } 82 | 83 | continue 84 | } 85 | 86 | want, err := tt.iaprefix.MarshalBinary() 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | got, err := iaprefix.MarshalBinary() 91 | if err != nil { 92 | t.Fatal(err) 93 | } 94 | 95 | if !bytes.Equal(want, got) { 96 | t.Fatalf("[%02d] test %q, unexpected IAPrefix bytes:\n- want: %v\n- got: %v", 97 | i, tt.desc, want, got) 98 | } 99 | } 100 | } 101 | 102 | // TestIAPrefixUnmarshalBinary verifies that IAPrefix.UnmarshalBinary produces 103 | // a correct IAPrefix value or error for an input buffer. 104 | func TestIAPrefixUnmarshalBinary(t *testing.T) { 105 | var tests = []struct { 106 | desc string 107 | buf []byte 108 | iaprefix *IAPrefix 109 | err error 110 | }{ 111 | { 112 | desc: "one byte IAPrefix", 113 | buf: []byte{0}, 114 | err: io.ErrUnexpectedEOF, 115 | }, 116 | { 117 | desc: "24 bytes IAPrefix", 118 | buf: bytes.Repeat([]byte{0}, 24), 119 | err: io.ErrUnexpectedEOF, 120 | }, 121 | { 122 | desc: "preferred greater than valid lifetime", 123 | buf: append([]byte{ 124 | 0, 0, 0, 2, 125 | 0, 0, 0, 1, 126 | }, bytes.Repeat([]byte{0}, 17)...), 127 | err: ErrInvalidLifetimes, 128 | }, 129 | { 130 | desc: "invalid options (length mismatch)", 131 | buf: []byte{ 132 | 0, 0, 0, 1, 133 | 0, 0, 0, 2, 134 | 0, 135 | 0, 0, 0, 0, 0, 0, 0, 0, 136 | 0, 0, 0, 0, 0, 0, 0, 0, 137 | 0, 1, 0, 1, 138 | }, 139 | err: dhcp6.ErrInvalidOptions, 140 | }, 141 | { 142 | desc: "1s preferred, 2s valid, '2001:db8::/32', no options", 143 | buf: []byte{ 144 | 0, 0, 0, 1, 145 | 0, 0, 0, 2, 146 | 32, 147 | 32, 1, 13, 184, 0, 0, 0, 0, 148 | 0, 0, 0, 0, 0, 0, 0, 0, 149 | }, 150 | iaprefix: &IAPrefix{ 151 | PreferredLifetime: 1 * time.Second, 152 | ValidLifetime: 2 * time.Second, 153 | PrefixLength: 32, 154 | Prefix: net.ParseIP("2001:db8::"), 155 | }, 156 | }, 157 | { 158 | desc: "1s preferred, 2s valid, '2001:db8::6:1/64', option client ID [0 1]", 159 | buf: []byte{ 160 | 0, 0, 0, 1, 161 | 0, 0, 0, 2, 162 | 64, 163 | 32, 1, 13, 184, 0, 0, 0, 0, 164 | 0, 0, 0, 0, 0, 6, 0, 1, 165 | 0, 1, 0, 2, 0, 1, 166 | }, 167 | iaprefix: &IAPrefix{ 168 | PreferredLifetime: 1 * time.Second, 169 | ValidLifetime: 2 * time.Second, 170 | PrefixLength: 64, 171 | Prefix: net.ParseIP("2001:db8::6:1"), 172 | Options: dhcp6.Options{ 173 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 174 | }, 175 | }, 176 | }, 177 | } 178 | 179 | for i, tt := range tests { 180 | iaprefix := new(IAPrefix) 181 | if err := iaprefix.UnmarshalBinary(tt.buf); err != nil { 182 | if want, got := tt.err, err; want != got { 183 | t.Fatalf("[%02d] test %q, unexpected error for parseIAPrefix: %v != %v", 184 | i, tt.desc, want, got) 185 | } 186 | 187 | continue 188 | } 189 | 190 | want, err := tt.iaprefix.MarshalBinary() 191 | if err != nil { 192 | t.Fatal(err) 193 | } 194 | got, err := iaprefix.MarshalBinary() 195 | if err != nil { 196 | t.Fatal(err) 197 | } 198 | 199 | if !bytes.Equal(want, got) { 200 | t.Fatalf("[%02d] test %q, unexpected IAPrefix bytes for parseIAPrefix:\n- want: %v\n- got: %v", 201 | i, tt.desc, want, got) 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /dhcp6opts/iata.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/mdlayher/dhcp6" 7 | "github.com/mdlayher/dhcp6/internal/buffer" 8 | ) 9 | 10 | // IATA represents an Identity Association for Temporary Addresses, as 11 | // defined in RFC 3315, Section 22.5. 12 | // 13 | // Multiple IATAs may be present in a single DHCP request. 14 | type IATA struct { 15 | // IAID specifies a DHCP identity association identifier. The IAID 16 | // is a unique, client-generated identifier. 17 | IAID [4]byte 18 | 19 | // Options specifies a map of DHCP options specific to this IATA. 20 | // Its methods can be used to retrieve data from an incoming IATA, or send 21 | // data with an outgoing IATA. 22 | Options dhcp6.Options 23 | } 24 | 25 | // NewIATA creates a new IATA from an IAID and an Options map. If an Options 26 | // map is not specified, a new one will be allocated. 27 | func NewIATA(iaid [4]byte, options dhcp6.Options) *IATA { 28 | if options == nil { 29 | options = make(dhcp6.Options) 30 | } 31 | 32 | return &IATA{ 33 | IAID: iaid, 34 | Options: options, 35 | } 36 | } 37 | 38 | // MarshalBinary allocates a byte slice containing the data from a IATA. 39 | func (i *IATA) MarshalBinary() ([]byte, error) { 40 | // 4 bytes: IAID 41 | // N bytes: options slice byte count 42 | b := buffer.New(nil) 43 | 44 | b.WriteBytes(i.IAID[:]) 45 | opts, err := i.Options.MarshalBinary() 46 | if err != nil { 47 | return nil, err 48 | } 49 | b.WriteBytes(opts) 50 | 51 | return b.Data(), nil 52 | } 53 | 54 | // UnmarshalBinary unmarshals a raw byte slice into a IATA. 55 | // 56 | // If the byte slice does not contain enough data to form a valid IATA, 57 | // io.ErrUnexpectedEOF is returned. 58 | func (i *IATA) UnmarshalBinary(p []byte) error { 59 | b := buffer.New(p) 60 | // IATA must contain at least an IAID. 61 | if b.Len() < 4 { 62 | return io.ErrUnexpectedEOF 63 | } 64 | 65 | b.ReadBytes(i.IAID[:]) 66 | return (&i.Options).UnmarshalBinary(b.Remaining()) 67 | } 68 | -------------------------------------------------------------------------------- /dhcp6opts/iata_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/mdlayher/dhcp6" 10 | ) 11 | 12 | // TestNewIATA verifies that NewIATA creates a proper IATA value for 13 | // input values. 14 | func TestNewIATA(t *testing.T) { 15 | var tests = []struct { 16 | desc string 17 | iaid [4]byte 18 | options dhcp6.Options 19 | iata *IATA 20 | }{ 21 | { 22 | desc: "all zero values", 23 | iata: &IATA{}, 24 | }, 25 | { 26 | desc: "[0 1 2 3] IAID, option client ID [0 1]", 27 | iaid: [4]byte{0, 1, 2, 3}, 28 | options: dhcp6.Options{ 29 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 30 | }, 31 | iata: &IATA{ 32 | IAID: [4]byte{0, 1, 2, 3}, 33 | Options: dhcp6.Options{ 34 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 35 | }, 36 | }, 37 | }, 38 | } 39 | 40 | for i, tt := range tests { 41 | iata := NewIATA(tt.iaid, tt.options) 42 | 43 | want, err := tt.iata.MarshalBinary() 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | got, err := iata.MarshalBinary() 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | 52 | if !bytes.Equal(want, got) { 53 | t.Fatalf("[%02d] test %q, unexpected IATA bytes for NewIATA(%v, %v)\n- want: %v\n- got: %v", 54 | i, tt.desc, tt.iaid, tt.options, want, got) 55 | } 56 | } 57 | } 58 | 59 | // TestIATAUnmarshalBinary verifies that IATAUnmarshalBinary produces a 60 | // correct IATA value or error for an input buffer. 61 | func TestIATAUnmarshalBinary(t *testing.T) { 62 | var tests = []struct { 63 | buf []byte 64 | iata *IATA 65 | options dhcp6.Options 66 | err error 67 | }{ 68 | { 69 | buf: []byte{0}, 70 | err: io.ErrUnexpectedEOF, 71 | }, 72 | { 73 | buf: bytes.Repeat([]byte{0}, 3), 74 | err: io.ErrUnexpectedEOF, 75 | }, 76 | { 77 | buf: []byte{ 78 | 1, 2, 3, 4, 79 | 0, 1, 0, 1, 80 | }, 81 | err: dhcp6.ErrInvalidOptions, 82 | }, 83 | { 84 | buf: []byte{ 85 | 1, 2, 3, 4, 86 | 0, 1, 0, 2, 0, 1, 87 | }, 88 | iata: &IATA{ 89 | IAID: [4]byte{1, 2, 3, 4}, 90 | Options: dhcp6.Options{ 91 | dhcp6.OptionClientID: [][]byte{{0, 1}}, 92 | }, 93 | }, 94 | }, 95 | } 96 | 97 | for i, tt := range tests { 98 | iata := new(IATA) 99 | if err := iata.UnmarshalBinary(tt.buf); err != nil { 100 | if want, got := tt.err, err; want != got { 101 | t.Fatalf("[%02d] unexpected error for parseIATA(%v): %v != %v", 102 | i, tt.buf, want, got) 103 | } 104 | 105 | continue 106 | } 107 | 108 | if want, got := tt.iata, iata; !reflect.DeepEqual(want, got) { 109 | t.Fatalf("[%02d] unexpected IATA for parseIATA(%v):\n- want: %v\n- got: %v", 110 | i, tt.buf, want, got) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /dhcp6opts/miscoptions.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | "math" 6 | "net" 7 | "net/url" 8 | "time" 9 | 10 | "github.com/mdlayher/dhcp6" 11 | "github.com/mdlayher/dhcp6/internal/buffer" 12 | ) 13 | 14 | // A OptionRequestOption is a list OptionCode, as defined in RFC 3315, Section 22.7. 15 | // 16 | // The Option Request option is used to identify a list of options in a 17 | // message between a client and a server. 18 | type OptionRequestOption []dhcp6.OptionCode 19 | 20 | // MarshalBinary allocates a byte slice containing the data from a OptionRequestOption. 21 | func (oro OptionRequestOption) MarshalBinary() ([]byte, error) { 22 | b := buffer.New(nil) 23 | for _, opt := range oro { 24 | b.Write16(uint16(opt)) 25 | } 26 | return b.Data(), nil 27 | } 28 | 29 | // UnmarshalBinary unmarshals a raw byte slice into a OptionRequestOption. 30 | // 31 | // If the length of byte slice is not be be divisible by 2, 32 | // errInvalidOptionRequest is returned. 33 | func (oro *OptionRequestOption) UnmarshalBinary(p []byte) error { 34 | b := buffer.New(p) 35 | // Length must be divisible by 2. 36 | if b.Len()%2 != 0 { 37 | return io.ErrUnexpectedEOF 38 | } 39 | 40 | // Fill slice by parsing every two bytes using index i. 41 | *oro = make(OptionRequestOption, 0, b.Len()/2) 42 | for b.Len() > 1 { 43 | *oro = append(*oro, dhcp6.OptionCode(b.Read16())) 44 | } 45 | return nil 46 | } 47 | 48 | // A Preference is a preference value, as defined in RFC 3315, Section 22.8. 49 | // 50 | // A preference value is sent by a server to a client to affect the selection 51 | // of a server by the client. 52 | type Preference uint8 53 | 54 | // MarshalBinary allocates a byte slice containing the data from a Preference. 55 | func (p Preference) MarshalBinary() ([]byte, error) { 56 | return []byte{byte(p)}, nil 57 | } 58 | 59 | // UnmarshalBinary unmarshals a raw byte slice into a Preference. 60 | // 61 | // If the byte slice is not exactly 1 byte in length, io.ErrUnexpectedEOF is 62 | // returned. 63 | func (p *Preference) UnmarshalBinary(b []byte) error { 64 | if len(b) != 1 { 65 | return io.ErrUnexpectedEOF 66 | } 67 | 68 | *p = Preference(b[0]) 69 | return nil 70 | } 71 | 72 | // An ElapsedTime is a client's elapsed request time value, as defined in RFC 73 | // 3315, Section 22.9. 74 | // 75 | // The duration returned reports the time elapsed during a DHCP transaction, 76 | // as reported by a client. 77 | type ElapsedTime time.Duration 78 | 79 | // MarshalBinary allocates a byte slice containing the data from an 80 | // ElapsedTime. 81 | func (t ElapsedTime) MarshalBinary() ([]byte, error) { 82 | b := buffer.New(nil) 83 | 84 | unit := 10 * time.Millisecond 85 | // The elapsed time value is an unsigned, 16 bit integer. 86 | // The client uses the value 0xffff to represent any 87 | // elapsed time values greater than the largest time value 88 | // that can be represented in the Elapsed Time option. 89 | if max := time.Duration(math.MaxUint16) * unit; time.Duration(t) > max { 90 | t = ElapsedTime(max) 91 | } 92 | b.Write16(uint16(time.Duration(t) / unit)) 93 | return b.Data(), nil 94 | } 95 | 96 | // UnmarshalBinary unmarshals a raw byte slice into a ElapsedTime. 97 | // 98 | // If the byte slice is not exactly 2 bytes in length, io.ErrUnexpectedEOF is 99 | // returned. 100 | func (t *ElapsedTime) UnmarshalBinary(p []byte) error { 101 | b := buffer.New(p) 102 | if b.Len() != 2 { 103 | return io.ErrUnexpectedEOF 104 | } 105 | 106 | // Time is reported in hundredths of seconds, so we convert it to a more 107 | // manageable milliseconds 108 | *t = ElapsedTime(time.Duration(b.Read16()) * 10 * time.Millisecond) 109 | return nil 110 | } 111 | 112 | // An IP is an IPv6 address. The IP type is provided for convenience. 113 | // It can be used to easily add IPv6 addresses to an Options map. 114 | type IP net.IP 115 | 116 | // MarshalBinary allocates a byte slice containing the data from a IP. 117 | func (i IP) MarshalBinary() ([]byte, error) { 118 | ip := make([]byte, net.IPv6len) 119 | copy(ip, i) 120 | return ip, nil 121 | } 122 | 123 | // UnmarshalBinary unmarshals a raw byte slice into an IP. 124 | // 125 | // If the byte slice is not an IPv6 address, io.ErrUnexpectedEOF is 126 | // returned. 127 | func (i *IP) UnmarshalBinary(b []byte) error { 128 | if len(b) != net.IPv6len { 129 | return io.ErrUnexpectedEOF 130 | } 131 | 132 | if ip := net.IP(b); ip.To4() != nil { 133 | return io.ErrUnexpectedEOF 134 | } 135 | 136 | *i = make(IP, net.IPv6len) 137 | copy(*i, b) 138 | return nil 139 | } 140 | 141 | // IPs represents a list of IPv6 addresses. 142 | type IPs []net.IP 143 | 144 | // MarshalBinary allocates a byte slice containing the consecutive data of all 145 | // IPs. 146 | func (i IPs) MarshalBinary() ([]byte, error) { 147 | ips := make([]byte, 0, len(i)*net.IPv6len) 148 | for _, ip := range i { 149 | ips = append(ips, ip.To16()...) 150 | } 151 | return ips, nil 152 | } 153 | 154 | // UnmarshalBinary unmarshals a raw byte slice into a list of IPs. 155 | // 156 | // If the byte slice contains any non-IPv6 addresses, io.ErrUnexpectedEOF is 157 | // returned. 158 | func (i *IPs) UnmarshalBinary(p []byte) error { 159 | b := buffer.New(p) 160 | if b.Len()%net.IPv6len != 0 || b.Len() == 0 { 161 | return io.ErrUnexpectedEOF 162 | } 163 | 164 | *i = make(IPs, 0, b.Len()/net.IPv6len) 165 | for b.Len() > 0 { 166 | ip := make(net.IP, net.IPv6len) 167 | b.ReadBytes(ip) 168 | *i = append(*i, ip) 169 | } 170 | return nil 171 | } 172 | 173 | // Data is a raw collection of byte slices, typically carrying user class 174 | // data, vendor class data, or PXE boot file parameters. 175 | type Data [][]byte 176 | 177 | // MarshalBinary allocates a byte slice containing the data from a Data 178 | // structure. 179 | func (d Data) MarshalBinary() ([]byte, error) { 180 | // Count number of bytes needed to allocate at once 181 | var c int 182 | for _, dd := range d { 183 | c += 2 + len(dd) 184 | } 185 | 186 | b := buffer.New(nil) 187 | d.Marshal(b) 188 | return b.Data(), nil 189 | } 190 | 191 | // Marshal marshals to a given buffer from a Data structure. 192 | func (d Data) Marshal(b *buffer.Buffer) { 193 | for _, dd := range d { 194 | // 2 byte: length of data 195 | b.Write16(uint16(len(dd))) 196 | 197 | // N bytes: actual raw data 198 | b.WriteBytes(dd) 199 | } 200 | } 201 | 202 | // UnmarshalBinary unmarshals a raw byte slice into a Data structure. 203 | func (d *Data) UnmarshalBinary(p []byte) error { 204 | b := buffer.New(p) 205 | return d.Unmarshal(b) 206 | } 207 | 208 | // Unmarshal marshals from a given buffer into a Data structure. 209 | // Data is packed in the form: 210 | // - 2 bytes: data length 211 | // - N bytes: raw data 212 | func (d *Data) Unmarshal(b *buffer.Buffer) error { 213 | data := make(Data, 0, b.Len()) 214 | 215 | // Iterate until not enough bytes remain to parse another length value 216 | for b.Len() > 1 { 217 | // 2 bytes: length of data. 218 | length := int(b.Read16()) 219 | 220 | // N bytes: actual data. 221 | data = append(data, b.Consume(length)) 222 | } 223 | 224 | // At least one instance of class data must be present 225 | if len(data) == 0 { 226 | return io.ErrUnexpectedEOF 227 | } 228 | 229 | // If we encounter any trailing bytes, report an error 230 | if b.Len() != 0 { 231 | return io.ErrUnexpectedEOF 232 | } 233 | 234 | *d = data 235 | return nil 236 | } 237 | 238 | // A URL is a uniform resource locater. The URL type is provided for 239 | // convenience. It can be used to easily add URLs to an Options map. 240 | type URL url.URL 241 | 242 | // MarshalBinary allocates a byte slice containing the data from a URL. 243 | func (u URL) MarshalBinary() ([]byte, error) { 244 | uu := url.URL(u) 245 | return []byte(uu.String()), nil 246 | } 247 | 248 | // UnmarshalBinary unmarshals a raw byte slice into an URL. 249 | // 250 | // If the byte slice is not an URLv6 address, io.ErrUnexpectedEOF is 251 | // returned. 252 | func (u *URL) UnmarshalBinary(b []byte) error { 253 | uu, err := url.Parse(string(b)) 254 | if err != nil { 255 | return err 256 | } 257 | 258 | *u = URL(*uu) 259 | return nil 260 | } 261 | 262 | // ArchTypes is a slice of ArchType values. It is provided for convenient 263 | // marshaling and unmarshaling of a slice of ArchType values from an Options 264 | // map. 265 | type ArchTypes []ArchType 266 | 267 | // MarshalBinary allocates a byte slice containing the data from ArchTypes. 268 | func (a ArchTypes) MarshalBinary() ([]byte, error) { 269 | b := buffer.New(nil) 270 | for _, aType := range a { 271 | b.Write16(uint16(aType)) 272 | } 273 | 274 | return b.Data(), nil 275 | } 276 | 277 | // UnmarshalBinary unmarshals a raw byte slice into an ArchTypes slice. 278 | // 279 | // If the byte slice is less than 2 bytes in length, or is not a length that 280 | // is divisible by 2, io.ErrUnexpectedEOF is returned. 281 | func (a *ArchTypes) UnmarshalBinary(p []byte) error { 282 | b := buffer.New(p) 283 | // Length must be at least 2, and divisible by 2. 284 | if b.Len() < 2 || b.Len()%2 != 0 { 285 | return io.ErrUnexpectedEOF 286 | } 287 | 288 | // Allocate ArchTypes at once and unpack every two bytes into an element 289 | arch := make(ArchTypes, 0, b.Len()/2) 290 | for b.Len() > 1 { 291 | arch = append(arch, ArchType(b.Read16())) 292 | } 293 | 294 | *a = arch 295 | return nil 296 | } 297 | 298 | // A NII is a Client Network Interface Identifier, as defined in RFC 5970, 299 | // Section 3.4. 300 | // 301 | // A NII is used to indicate a client's level of Universal Network Device 302 | // Interface (UNDI) support. 303 | type NII struct { 304 | // Type specifies a network interface type. 305 | Type uint8 306 | 307 | // Major specifies the UNDI major revisision which this client supports. 308 | Major uint8 309 | 310 | // Minor specifies the UNDI minor revision which this client supports. 311 | Minor uint8 312 | } 313 | 314 | // MarshalBinary allocates a byte slice containing the data from a NII. 315 | func (n *NII) MarshalBinary() ([]byte, error) { 316 | b := make([]byte, 3) 317 | 318 | b[0] = n.Type 319 | b[1] = n.Major 320 | b[2] = n.Minor 321 | 322 | return b, nil 323 | } 324 | 325 | // UnmarshalBinary unmarshals a raw byte slice into a NII. 326 | // 327 | // If the byte slice is not exactly 3 bytes in length, io.ErrUnexpectedEOF 328 | // is returned. 329 | func (n *NII) UnmarshalBinary(b []byte) error { 330 | // Length must be exactly 3 331 | if len(b) != 3 { 332 | return io.ErrUnexpectedEOF 333 | } 334 | 335 | n.Type = b[0] 336 | n.Major = b[1] 337 | n.Minor = b[2] 338 | 339 | return nil 340 | } 341 | 342 | // A RelayMessageOption is used by a DHCPv6 Relay Agent to relay messages 343 | // between clients and servers or other relay agents through Relay-Forward 344 | // and Relay-Reply message types. The original client DHCP message (i.e., 345 | // the packet payload, excluding UDP and IP headers) is encapsulated in a 346 | // Relay Message option. 347 | type RelayMessageOption []byte 348 | 349 | // MarshalBinary allocates a byte slice containing the data from a RelayMessageOption. 350 | func (r *RelayMessageOption) MarshalBinary() ([]byte, error) { 351 | return *r, nil 352 | } 353 | 354 | // UnmarshalBinary unmarshals a raw byte slice into a RelayMessageOption. 355 | func (r *RelayMessageOption) UnmarshalBinary(b []byte) error { 356 | *r = make([]byte, len(b)) 357 | copy(*r, b) 358 | return nil 359 | } 360 | 361 | // SetClientServerMessage sets a Packet (e.g. Solicit, Advertise ...) into this option. 362 | func (r *RelayMessageOption) SetClientServerMessage(p *dhcp6.Packet) error { 363 | b, err := p.MarshalBinary() 364 | if err != nil { 365 | return err 366 | } 367 | 368 | *r = b 369 | return nil 370 | } 371 | 372 | // SetRelayMessage sets a RelayMessage (e.g. Relay Forward, Relay Reply) into this option. 373 | func (r *RelayMessageOption) SetRelayMessage(p *RelayMessage) error { 374 | b, err := p.MarshalBinary() 375 | if err != nil { 376 | return err 377 | } 378 | 379 | *r = b 380 | return nil 381 | } 382 | 383 | // ClientServerMessage gets the client server message (e.g. Solicit, 384 | // Advertise ...) into this option (when hopcount = 0 of outer RelayMessage). 385 | func (r *RelayMessageOption) ClientServerMessage() (*dhcp6.Packet, error) { 386 | p := new(dhcp6.Packet) 387 | err := p.UnmarshalBinary(*r) 388 | if err != nil { 389 | return nil, err 390 | } 391 | 392 | return p, nil 393 | } 394 | 395 | // RelayMessage gets the relay message (e.g. Relay Forward, Relay Reply) into 396 | // this option (when hopcount > 0 of outer RelayMessage). 397 | func (r *RelayMessageOption) RelayMessage() (*RelayMessage, error) { 398 | rm := new(RelayMessage) 399 | err := rm.UnmarshalBinary(*r) 400 | if err != nil { 401 | return nil, err 402 | } 403 | 404 | return rm, nil 405 | } 406 | 407 | // An InterfaceID is an opaque value of arbitrary length generated 408 | // by the relay agent to identify one of the 409 | // relay agent's interfaces. 410 | type InterfaceID []byte 411 | 412 | // MarshalBinary allocates a byte slice containing the data from a InterfaceID. 413 | func (i *InterfaceID) MarshalBinary() ([]byte, error) { 414 | return *i, nil 415 | } 416 | 417 | // UnmarshalBinary unmarshals a raw byte slice into a InterfaceID. 418 | func (i *InterfaceID) UnmarshalBinary(b []byte) error { 419 | *i = make([]byte, len(b)) 420 | copy(*i, b) 421 | return nil 422 | } 423 | 424 | // A BootFileParam are boot file parameters. 425 | type BootFileParam []string 426 | 427 | // MarshalBinary allocates a byte slice containing the data from a 428 | // BootFileParam. 429 | func (bfp BootFileParam) MarshalBinary() ([]byte, error) { 430 | // Convert []string to [][]byte. 431 | bb := make(Data, 0, len(bfp)) 432 | for _, param := range bfp { 433 | bb = append(bb, []byte(param)) 434 | } 435 | return bb.MarshalBinary() 436 | } 437 | 438 | // UnmarshalBinary unmarshals a raw byte slice into a BootFileParam. 439 | func (bfp *BootFileParam) UnmarshalBinary(b []byte) error { 440 | var d Data 441 | if err := (&d).UnmarshalBinary(b); err != nil { 442 | return err 443 | } 444 | // Convert [][]byte to []string. 445 | *bfp = make([]string, 0, len(d)) 446 | for _, param := range d { 447 | *bfp = append(*bfp, string(param)) 448 | } 449 | return nil 450 | } 451 | -------------------------------------------------------------------------------- /dhcp6opts/options.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "github.com/mdlayher/dhcp6" 5 | ) 6 | 7 | // GetClientID returns the Client Identifier Option value, as described in RFC 8 | // 3315, Section 22.2. 9 | // 10 | // The DUID returned allows unique identification of a client to a server. 11 | func GetClientID(o dhcp6.Options) (DUID, error) { 12 | v, err := o.GetOne(dhcp6.OptionClientID) 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | return parseDUID(v) 18 | } 19 | 20 | // GetServerID returns the Server Identifier Option value, as described in RFC 21 | // 3315, Section 22.3. 22 | // 23 | // The DUID returned allows unique identification of a server to a client. 24 | func GetServerID(o dhcp6.Options) (DUID, error) { 25 | v, err := o.GetOne(dhcp6.OptionServerID) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return parseDUID(v) 31 | } 32 | 33 | // GetIANA returns the Identity Association for Non-temporary Addresses Option 34 | // value, as described in RFC 3315, Section 22.4. 35 | // 36 | // Multiple IANA values may be present in a single DHCP request. 37 | func GetIANA(o dhcp6.Options) ([]*IANA, error) { 38 | vv, err := o.Get(dhcp6.OptionIANA) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | // Parse each IA_NA value 44 | iana := make([]*IANA, len(vv)) 45 | for i := range vv { 46 | iana[i] = &IANA{} 47 | if err := iana[i].UnmarshalBinary(vv[i]); err != nil { 48 | return nil, err 49 | } 50 | } 51 | return iana, nil 52 | } 53 | 54 | // GetIATA returns the Identity Association for Temporary Addresses Option 55 | // value, as described in RFC 3315, Section 22.5. 56 | // 57 | // Multiple IATA values may be present in a single DHCP request. 58 | func GetIATA(o dhcp6.Options) ([]*IATA, error) { 59 | vv, err := o.Get(dhcp6.OptionIATA) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | // Parse each IA_NA value 65 | iata := make([]*IATA, len(vv)) 66 | for i := range vv { 67 | iata[i] = &IATA{} 68 | if err := iata[i].UnmarshalBinary(vv[i]); err != nil { 69 | return nil, err 70 | } 71 | } 72 | return iata, nil 73 | } 74 | 75 | // GetIAAddr returns the Identity Association Address Option value, as described 76 | // in RFC 3315, Section 22.6. 77 | // 78 | // The IAAddr option must always appear encapsulated in the Options map of a 79 | // IANA or IATA option. Multiple IAAddr values may be present in a single DHCP 80 | // request. 81 | func GetIAAddr(o dhcp6.Options) ([]*IAAddr, error) { 82 | vv, err := o.Get(dhcp6.OptionIAAddr) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | iaAddr := make([]*IAAddr, len(vv)) 88 | for i := range vv { 89 | iaAddr[i] = &IAAddr{} 90 | if err := iaAddr[i].UnmarshalBinary(vv[i]); err != nil { 91 | return nil, err 92 | } 93 | } 94 | return iaAddr, nil 95 | } 96 | 97 | // GetOptionRequest returns the Option Request Option value, as described in 98 | // RFC 3315, Section 22.7. 99 | // 100 | // The slice of OptionCode values indicates the options a DHCP client is 101 | // interested in receiving from a server. 102 | func GetOptionRequest(o dhcp6.Options) (OptionRequestOption, error) { 103 | v, err := o.GetOne(dhcp6.OptionORO) 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | var oro OptionRequestOption 109 | err = oro.UnmarshalBinary(v) 110 | return oro, err 111 | } 112 | 113 | // GetPreference returns the Preference Option value, as described in RFC 3315, 114 | // Section 22.8. 115 | // 116 | // The integer preference value is sent by a server to a client to affect the 117 | // selection of a server by the client. 118 | func GetPreference(o dhcp6.Options) (Preference, error) { 119 | v, err := o.GetOne(dhcp6.OptionPreference) 120 | if err != nil { 121 | return 0, err 122 | } 123 | 124 | var p Preference 125 | err = (&p).UnmarshalBinary(v) 126 | return p, err 127 | } 128 | 129 | // GetElapsedTime returns the Elapsed Time Option value, as described in RFC 130 | // 3315, Section 22.9. 131 | // 132 | // The time.Duration returned reports the time elapsed during a DHCP 133 | // transaction, as reported by a client. 134 | func GetElapsedTime(o dhcp6.Options) (ElapsedTime, error) { 135 | v, err := o.GetOne(dhcp6.OptionElapsedTime) 136 | if err != nil { 137 | return 0, err 138 | } 139 | 140 | var t ElapsedTime 141 | err = (&t).UnmarshalBinary(v) 142 | return t, err 143 | } 144 | 145 | // GetRelayMessageOption returns the Relay Message Option value, as described 146 | // in RFC 3315, Section 22.10. 147 | // 148 | // The RelayMessage option carries a DHCP message in a Relay-forward or 149 | // Relay-reply message. 150 | func GetRelayMessageOption(o dhcp6.Options) (RelayMessageOption, error) { 151 | v, err := o.GetOne(dhcp6.OptionRelayMsg) 152 | if err != nil { 153 | return nil, err 154 | } 155 | 156 | var r RelayMessageOption 157 | err = (&r).UnmarshalBinary(v) 158 | return r, err 159 | } 160 | 161 | // GetAuthentication returns the Authentication Option value, as described in 162 | // RFC 3315, Section 22.11. 163 | // 164 | // The Authentication option carries authentication information to 165 | // authenticate the identity and contents of DHCP messages. 166 | func GetAuthentication(o dhcp6.Options) (*Authentication, error) { 167 | v, err := o.GetOne(dhcp6.OptionAuth) 168 | if err != nil { 169 | return nil, err 170 | } 171 | 172 | a := new(Authentication) 173 | err = a.UnmarshalBinary(v) 174 | return a, err 175 | } 176 | 177 | // GetUnicast returns the IP from a Unicast Option value, described in RFC 178 | // 3315, Section 22.12. 179 | // 180 | // The IP return value indicates a server's IPv6 address, which a client may 181 | // use to contact the server via unicast. 182 | func GetUnicast(o dhcp6.Options) (IP, error) { 183 | v, err := o.GetOne(dhcp6.OptionUnicast) 184 | if err != nil { 185 | return nil, err 186 | } 187 | 188 | var ip IP 189 | err = ip.UnmarshalBinary(v) 190 | return ip, err 191 | } 192 | 193 | // GetStatusCode returns the Status Code Option value, described in RFC 3315, 194 | // Section 22.13. 195 | // 196 | // The StatusCode return value may be used to determine a code and an 197 | // explanation for the status. 198 | func GetStatusCode(o dhcp6.Options) (*StatusCode, error) { 199 | v, err := o.GetOne(dhcp6.OptionStatusCode) 200 | if err != nil { 201 | return nil, err 202 | } 203 | 204 | s := new(StatusCode) 205 | err = s.UnmarshalBinary(v) 206 | return s, err 207 | } 208 | 209 | // GetRapidCommit returns the Rapid Commit Option value, described in RFC 3315, 210 | // Section 22.14. 211 | // 212 | // Nil is returned if OptionRapidCommit was present in the Options map. 213 | func GetRapidCommit(o dhcp6.Options) error { 214 | v, err := o.GetOne(dhcp6.OptionRapidCommit) 215 | if err != nil { 216 | return err 217 | } 218 | 219 | // Data must be completely empty; presence of the Rapid Commit option 220 | // indicates it is requested. 221 | if len(v) != 0 { 222 | return dhcp6.ErrInvalidPacket 223 | } 224 | return nil 225 | } 226 | 227 | // GetUserClass returns the User Class Option value, described in RFC 3315, 228 | // Section 22.15. 229 | // 230 | // The Data structure returned contains any raw class data present in 231 | // the option. 232 | func GetUserClass(o dhcp6.Options) (Data, error) { 233 | v, err := o.GetOne(dhcp6.OptionUserClass) 234 | if err != nil { 235 | return nil, err 236 | } 237 | 238 | var d Data 239 | err = d.UnmarshalBinary(v) 240 | return d, err 241 | } 242 | 243 | // GetVendorClass returns the Vendor Class Option value, described in RFC 3315, 244 | // Section 22.16. 245 | // 246 | // The VendorClass structure returned contains VendorClass in 247 | // the option. 248 | func GetVendorClass(o dhcp6.Options) (*VendorClass, error) { 249 | v, err := o.GetOne(dhcp6.OptionVendorClass) 250 | if err != nil { 251 | return nil, err 252 | } 253 | 254 | vc := new(VendorClass) 255 | err = vc.UnmarshalBinary(v) 256 | return vc, err 257 | } 258 | 259 | // GetVendorOpts returns the Vendor-specific Information Option value, 260 | // described in RFC 3315, Section 22.17. 261 | // 262 | // The VendorOpts structure returned contains Vendor-specific Information data 263 | // present in the option. 264 | func GetVendorOpts(o dhcp6.Options) (*VendorOpts, error) { 265 | v, err := o.GetOne(dhcp6.OptionVendorOpts) 266 | if err != nil { 267 | return nil, err 268 | } 269 | 270 | vo := new(VendorOpts) 271 | err = vo.UnmarshalBinary(v) 272 | return vo, err 273 | } 274 | 275 | // GetInterfaceID returns the Interface-Id Option value, described in RFC 3315, 276 | // Section 22.18. 277 | // 278 | // The InterfaceID structure returned contains any raw class data present in 279 | // the option. 280 | func GetInterfaceID(o dhcp6.Options) (InterfaceID, error) { 281 | v, err := o.GetOne(dhcp6.OptionInterfaceID) 282 | if err != nil { 283 | return nil, err 284 | } 285 | 286 | var i InterfaceID 287 | err = i.UnmarshalBinary(v) 288 | return i, err 289 | } 290 | 291 | // GetIAPD returns the Identity Association for Prefix Delegation Option value, 292 | // described in RFC 3633, Section 9. 293 | // 294 | // Multiple IAPD values may be present in a a single DHCP request. 295 | func GetIAPD(o dhcp6.Options) ([]*IAPD, error) { 296 | vv, err := o.Get(dhcp6.OptionIAPD) 297 | if err != nil { 298 | return nil, err 299 | } 300 | 301 | // Parse each IA_PD value 302 | iapd := make([]*IAPD, len(vv)) 303 | for i := range vv { 304 | iapd[i] = &IAPD{} 305 | if err := iapd[i].UnmarshalBinary(vv[i]); err != nil { 306 | return nil, err 307 | } 308 | } 309 | 310 | return iapd, nil 311 | } 312 | 313 | // GetIAPrefix returns the Identity Association Prefix Option value, as 314 | // described in RFC 3633, Section 10. 315 | // 316 | // Multiple IAPrefix values may be present in a a single DHCP request. 317 | func GetIAPrefix(o dhcp6.Options) ([]*IAPrefix, error) { 318 | vv, err := o.Get(dhcp6.OptionIAPrefix) 319 | if err != nil { 320 | return nil, err 321 | } 322 | 323 | // Parse each IAPrefix value 324 | iaPrefix := make([]*IAPrefix, len(vv)) 325 | for i := range vv { 326 | iaPrefix[i] = &IAPrefix{} 327 | if err := iaPrefix[i].UnmarshalBinary(vv[i]); err != nil { 328 | return nil, err 329 | } 330 | } 331 | 332 | return iaPrefix, nil 333 | } 334 | 335 | // GetRemoteIdentifier returns the Remote Identifier, described in RFC 4649. 336 | // 337 | // This option may be added by DHCPv6 relay agents that terminate 338 | // switched or permanent circuits and have mechanisms to identify the 339 | // remote host end of the circuit. 340 | func GetRemoteIdentifier(o dhcp6.Options) (*RemoteIdentifier, error) { 341 | v, err := o.GetOne(dhcp6.OptionRemoteIdentifier) 342 | if err != nil { 343 | return nil, err 344 | } 345 | 346 | r := new(RemoteIdentifier) 347 | err = r.UnmarshalBinary(v) 348 | return r, err 349 | } 350 | 351 | // GetBootFileURL returns the Boot File URL Option value, described in RFC 352 | // 5970, Section 3.1. 353 | // 354 | // The URL return value contains a URL which may be used by clients to obtain 355 | // a boot file for PXE. 356 | func GetBootFileURL(o dhcp6.Options) (*URL, error) { 357 | v, err := o.GetOne(dhcp6.OptionBootFileURL) 358 | if err != nil { 359 | return nil, err 360 | } 361 | 362 | u := new(URL) 363 | err = u.UnmarshalBinary(v) 364 | return u, err 365 | } 366 | 367 | // GetBootFileParam returns the Boot File Parameters Option value, described in 368 | // RFC 5970, Section 3.2. 369 | // 370 | // The Data structure returned contains any parameters needed for a boot 371 | // file, such as a root filesystem label or a path to a configuration file for 372 | // further chainloading. 373 | func GetBootFileParam(o dhcp6.Options) (BootFileParam, error) { 374 | v, err := o.GetOne(dhcp6.OptionBootFileParam) 375 | if err != nil { 376 | return nil, err 377 | } 378 | 379 | var bfp BootFileParam 380 | err = bfp.UnmarshalBinary(v) 381 | return bfp, err 382 | } 383 | 384 | // GetClientArchType returns the Client System Architecture Type Option value, 385 | // described in RFC 5970, Section 3.3. 386 | // 387 | // The ArchTypes slice returned contains a list of one or more ArchType values. 388 | // The first ArchType listed is the client's most preferable value. 389 | func GetClientArchType(o dhcp6.Options) (ArchTypes, error) { 390 | v, err := o.GetOne(dhcp6.OptionClientArchType) 391 | if err != nil { 392 | return nil, err 393 | } 394 | 395 | var a ArchTypes 396 | err = a.UnmarshalBinary(v) 397 | return a, err 398 | } 399 | 400 | // GetNII returns the Client Network Interface Identifier Option value, 401 | // described in RFC 5970, Section 3.4. 402 | // 403 | // The NII value returned indicates a client's level of Universal Network 404 | // Device Interface (UNDI) support. 405 | func GetNII(o dhcp6.Options) (*NII, error) { 406 | v, err := o.GetOne(dhcp6.OptionNII) 407 | if err != nil { 408 | return nil, err 409 | } 410 | 411 | n := new(NII) 412 | err = n.UnmarshalBinary(v) 413 | return n, err 414 | } 415 | 416 | // GetDNSServers returns the DNS Recursive Name Servers Option value, as 417 | // described in RFC 3646, Section 3. 418 | // 419 | // The DNS servers are listed in the order of preference for use by the client 420 | // resolver. 421 | func GetDNSServers(o dhcp6.Options) (IPs, error) { 422 | v, err := o.GetOne(dhcp6.OptionDNSServers) 423 | if err != nil { 424 | return nil, err 425 | } 426 | 427 | var ips IPs 428 | err = ips.UnmarshalBinary(v) 429 | return ips, err 430 | } 431 | -------------------------------------------------------------------------------- /dhcp6opts/opts.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | //go:generate stringer -output=string.go -type=ArchType,DUIDType 8 | 9 | var ( 10 | // ErrHardwareTypeNotImplemented is returned when HardwareType is not 11 | // implemented on the current platform. 12 | ErrHardwareTypeNotImplemented = errors.New("hardware type detection not implemented on this platform") 13 | 14 | // ErrInvalidDUIDLLTTime is returned when a time before midnight (UTC), 15 | // January 1, 2000 is used in NewDUIDLLT. 16 | ErrInvalidDUIDLLTTime = errors.New("DUID-LLT time must be after midnight (UTC), January 1, 2000") 17 | 18 | // ErrInvalidIP is returned when an input net.IP value is not recognized as a 19 | // valid IPv6 address. 20 | ErrInvalidIP = errors.New("IP must be an IPv6 address") 21 | 22 | // ErrInvalidLifetimes is returned when an input preferred lifetime is shorter 23 | // than a valid lifetime parameter. 24 | ErrInvalidLifetimes = errors.New("preferred lifetime must be less than valid lifetime") 25 | 26 | // ErrParseHardwareType is returned when a valid hardware type could 27 | // not be found for a given interface. 28 | ErrParseHardwareType = errors.New("could not parse hardware type for interface") 29 | ) 30 | -------------------------------------------------------------------------------- /dhcp6opts/relaymessage.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | "net" 6 | 7 | "github.com/mdlayher/dhcp6" 8 | "github.com/mdlayher/dhcp6/internal/buffer" 9 | ) 10 | 11 | // RelayMessage represents a raw RelayMessage generated by DHCPv6 relay agent, using RFC 3315, 12 | // Section 7. 13 | type RelayMessage struct { 14 | // RELAY-FORW or RELAY-REPL only 15 | MessageType dhcp6.MessageType 16 | 17 | // Number of relay agents that have relayed this 18 | // message. 19 | HopCount uint8 20 | 21 | // A global or site-local address that will be used by 22 | // the server to identify the link on which the client 23 | // is located. 24 | LinkAddress net.IP 25 | 26 | // The address of the client or relay agent from which 27 | // the message to be relayed was received. 28 | PeerAddress net.IP 29 | 30 | // Options specifies a map of DHCP options. Its methods can be used to 31 | // retrieve data from an incoming RelayMessage, or send data with an outgoing 32 | // RelayMessage. 33 | // MUST include a "Relay Message option" (see 34 | // section 22.10); MAY include other options added by 35 | // the relay agent. 36 | Options dhcp6.Options 37 | } 38 | 39 | // MarshalBinary allocates a byte slice containing the data 40 | // from a RelayMessage. 41 | func (rm *RelayMessage) MarshalBinary() ([]byte, error) { 42 | // 1 byte: message type 43 | // 1 byte: hop-count 44 | // 16 bytes: link-address 45 | // 16 bytes: peer-address 46 | // N bytes: options slice byte count 47 | b := buffer.New(nil) 48 | 49 | b.Write8(uint8(rm.MessageType)) 50 | b.Write8(rm.HopCount) 51 | copy(b.WriteN(net.IPv6len), rm.LinkAddress) 52 | copy(b.WriteN(net.IPv6len), rm.PeerAddress) 53 | opts, err := rm.Options.MarshalBinary() 54 | if err != nil { 55 | return nil, err 56 | } 57 | b.WriteBytes(opts) 58 | 59 | return b.Data(), nil 60 | } 61 | 62 | // UnmarshalBinary unmarshals a raw byte slice into a RelayMessage. 63 | // 64 | // If the byte slice does not contain enough data to form a valid RelayMessage, 65 | // ErrInvalidPacket is returned. 66 | func (rm *RelayMessage) UnmarshalBinary(p []byte) error { 67 | b := buffer.New(p) 68 | // RelayMessage must contain at least message type, hop-count, link-address and peer-address 69 | if b.Len() < 34 { 70 | return io.ErrUnexpectedEOF 71 | } 72 | 73 | rm.MessageType = dhcp6.MessageType(b.Read8()) 74 | rm.HopCount = b.Read8() 75 | 76 | rm.LinkAddress = make(net.IP, net.IPv6len) 77 | copy(rm.LinkAddress, b.Consume(net.IPv6len)) 78 | 79 | rm.PeerAddress = make(net.IP, net.IPv6len) 80 | copy(rm.PeerAddress, b.Consume(net.IPv6len)) 81 | 82 | if err := (&rm.Options).UnmarshalBinary(b.Remaining()); err != nil { 83 | // Invalid options means an invalid RelayMessage 84 | return dhcp6.ErrInvalidPacket 85 | } 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /dhcp6opts/relaymessage_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/mdlayher/dhcp6" 11 | ) 12 | 13 | // TestRelayMessageMarshalBinary verifies that RelayMessage.MarshalBinary allocates and returns a correct 14 | // byte slice for a variety of input data. 15 | func TestRelayMessageMarshalBinary(t *testing.T) { 16 | var tests = []struct { 17 | desc string 18 | relayMsg *RelayMessage 19 | buf []byte 20 | }{ 21 | { 22 | desc: "empty packet", 23 | relayMsg: &RelayMessage{}, 24 | buf: make([]byte, 34), 25 | }, 26 | { 27 | desc: "RelayForw only", 28 | relayMsg: &RelayMessage{ 29 | MessageType: dhcp6.MessageTypeRelayForw, 30 | }, 31 | buf: []byte{12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 32 | }, 33 | { 34 | desc: "RelayReply only", 35 | relayMsg: &RelayMessage{ 36 | MessageType: dhcp6.MessageTypeRelayRepl, 37 | }, 38 | buf: []byte{13, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 39 | }, 40 | { 41 | desc: "RelayForw, 15 Hopcount, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] LinkAddress, [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32] PeerAddress", 42 | relayMsg: &RelayMessage{ 43 | MessageType: dhcp6.MessageTypeRelayForw, 44 | HopCount: 15, 45 | LinkAddress: net.IP([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), 46 | PeerAddress: net.IP([]byte{17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}), 47 | }, 48 | buf: []byte{12, 15, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, 49 | }, 50 | } 51 | 52 | for i, tt := range tests { 53 | buf, err := tt.relayMsg.MarshalBinary() 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | 58 | if want, got := tt.buf, buf; !bytes.Equal(want, got) { 59 | t.Fatalf("[%02d] test %q, unexpected packet bytes:\n- want: %v\n- got: %v", 60 | i, tt.desc, want, got) 61 | } 62 | } 63 | } 64 | 65 | // TestRelayMessageUnmarshalBinary verifies that RelayMessage.UnmarshalBinary returns 66 | // appropriate RelayMessages and errors for various input byte slices. 67 | func TestRelayMessageUnmarshalBinary(t *testing.T) { 68 | var tests = []struct { 69 | desc string 70 | buf []byte 71 | relayMsg *RelayMessage 72 | err error 73 | }{ 74 | { 75 | desc: "nil buffer, malformed packet", 76 | err: io.ErrUnexpectedEOF, 77 | }, 78 | { 79 | desc: "empty buffer, malformed packet", 80 | buf: []byte{}, 81 | err: io.ErrUnexpectedEOF, 82 | }, 83 | { 84 | desc: "length 1 buffer, malformed packet", 85 | buf: []byte{0}, 86 | err: io.ErrUnexpectedEOF, 87 | }, 88 | { 89 | desc: "length 33 buffer, malformed packet", 90 | buf: make([]byte, 33), 91 | err: io.ErrUnexpectedEOF, 92 | }, 93 | { 94 | desc: "invalid options in packet", 95 | buf: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1}, 96 | err: dhcp6.ErrInvalidPacket, 97 | }, 98 | { 99 | desc: "length 34 buffer, OK", 100 | buf: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 101 | relayMsg: &RelayMessage{ 102 | LinkAddress: net.IP(make([]byte, net.IPv6len)), 103 | PeerAddress: net.IP(make([]byte, net.IPv6len)), 104 | Options: make(dhcp6.Options), 105 | }, 106 | }, 107 | { 108 | desc: "RelayForw, 15 Hopcount, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16] LinkAddress, [17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32] PeerAddress", 109 | buf: []byte{12, 15, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}, 110 | relayMsg: &RelayMessage{ 111 | MessageType: dhcp6.MessageTypeRelayForw, 112 | HopCount: 15, 113 | LinkAddress: net.IP([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), 114 | PeerAddress: net.IP([]byte{17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}), 115 | Options: make(dhcp6.Options), 116 | }, 117 | }, 118 | } 119 | 120 | for i, tt := range tests { 121 | p := new(RelayMessage) 122 | if err := p.UnmarshalBinary(tt.buf); err != nil { 123 | if want, got := tt.err, err; want != got { 124 | t.Errorf("[%02d] test %q, unexpected error: %v != %v", 125 | i, tt.desc, want, got) 126 | } 127 | 128 | continue 129 | } 130 | 131 | if want, got := tt.relayMsg, p; !reflect.DeepEqual(want, got) { 132 | t.Errorf("[%02d] test %q, unexpected packet:\n- want: %v\n- got: %v", 133 | i, tt.desc, want, got) 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /dhcp6opts/remoteid.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/mdlayher/dhcp6/internal/buffer" 7 | ) 8 | 9 | // A RemoteIdentifier carries vendor-specific options. 10 | // 11 | // The vendor is indicated in the enterprise-number field. 12 | // The remote-id field may be used to encode, for instance: 13 | // - a "caller ID" telephone number for dial-up connection 14 | // - a "user name" prompted for by a Remote Access Server 15 | // - a remote caller ATM address 16 | // - a "modem ID" of a cable data modem 17 | // - the remote IP address of a point-to-point link 18 | // - a remote X.25 address for X.25 connections 19 | // - an interface or port identifier 20 | type RemoteIdentifier struct { 21 | // EnterpriseNumber specifies an IANA-assigned vendor Private Enterprise 22 | // Number. 23 | EnterpriseNumber uint32 24 | 25 | // The opaque value for the remote-id. 26 | RemoteID []byte 27 | } 28 | 29 | // MarshalBinary allocates a byte slice containing the data 30 | // from a RemoteIdentifier. 31 | func (r *RemoteIdentifier) MarshalBinary() ([]byte, error) { 32 | // 4 bytes: EnterpriseNumber 33 | // N bytes: RemoteId 34 | b := buffer.New(nil) 35 | b.Write32(r.EnterpriseNumber) 36 | b.WriteBytes(r.RemoteID) 37 | return b.Data(), nil 38 | } 39 | 40 | // UnmarshalBinary unmarshals a raw byte slice into a RemoteIdentifier. 41 | // If the byte slice does not contain enough data to form a valid 42 | // RemoteIdentifier, io.ErrUnexpectedEOF is returned. 43 | func (r *RemoteIdentifier) UnmarshalBinary(p []byte) error { 44 | b := buffer.New(p) 45 | // Too short to be valid RemoteIdentifier 46 | if b.Len() < 5 { 47 | return io.ErrUnexpectedEOF 48 | } 49 | 50 | r.EnterpriseNumber = b.Read32() 51 | r.RemoteID = b.Remaining() 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /dhcp6opts/remoteid_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | ) 9 | 10 | func TestRemoteIdentifierMarshalBinary(t *testing.T) { 11 | var tests = []struct { 12 | desc string 13 | buf []byte 14 | remoteIdentifier *RemoteIdentifier 15 | }{ 16 | { 17 | desc: "all zero values", 18 | buf: bytes.Repeat([]byte{0}, 5), 19 | remoteIdentifier: &RemoteIdentifier{ 20 | RemoteID: []byte{0}, 21 | }, 22 | }, 23 | { 24 | desc: "[0, 0, 5, 0x58] EnterpriseNumber, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xe, 0xf] RemoteID", 25 | buf: []byte{0, 0, 5, 0x58, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xe, 0xf}, 26 | remoteIdentifier: &RemoteIdentifier{ 27 | EnterpriseNumber: 1368, 28 | RemoteID: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xe, 0xf}, 29 | }, 30 | }, 31 | } 32 | 33 | for i, tt := range tests { 34 | want := tt.buf 35 | got, err := tt.remoteIdentifier.MarshalBinary() 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | if !bytes.Equal(want, got) { 40 | t.Fatalf("[%02d] test %q, unexpected RemoteIdentifier bytes for MarshalBinary(%v)\n- want: %v\n- got: %v", 41 | i, tt.desc, tt.buf, want, got) 42 | } 43 | } 44 | } 45 | 46 | func TestRemoteIdentifierUnmarshalBinary(t *testing.T) { 47 | var tests = []struct { 48 | buf []byte 49 | remoteIdentifier *RemoteIdentifier 50 | err error 51 | }{ 52 | { 53 | buf: []byte{0, 0, 5, 0x58, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xe, 0xf}, 54 | remoteIdentifier: &RemoteIdentifier{ 55 | EnterpriseNumber: 1368, 56 | RemoteID: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb, 0xc, 0xe, 0xf}, 57 | }, 58 | }, 59 | { 60 | buf: bytes.Repeat([]byte{0}, 4), 61 | err: io.ErrUnexpectedEOF, 62 | }, 63 | } 64 | 65 | for i, tt := range tests { 66 | remoteIdentifier := new(RemoteIdentifier) 67 | if want, got := tt.err, remoteIdentifier.UnmarshalBinary(tt.buf); want != got { 68 | t.Fatalf("[%02d] unexpected error for parseRemoteIdentifier(%v):\n- want: %v\n- got: %v", i, tt.buf, want, got) 69 | } 70 | 71 | if tt.err == nil { 72 | if want, got := tt.remoteIdentifier, remoteIdentifier; !reflect.DeepEqual(want, got) { 73 | t.Fatalf("[%02d] unexpected RemoteIdentifier for parseRemoteIdentifier(%v):\n- want: %v\n- got: %v", i, tt.buf, want, got) 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /dhcp6opts/statuscode.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/mdlayher/dhcp6" 7 | "github.com/mdlayher/dhcp6/internal/buffer" 8 | ) 9 | 10 | // StatusCode represents a Status Code, as defined in RFC 3315, Section 5.4. 11 | // DHCP clients and servers can use status codes to communicate successes 12 | // or failures, and provide additional information using a message to describe 13 | // specific failures. 14 | type StatusCode struct { 15 | // Code specifies the Status value stored within this StatusCode, such as 16 | // StatusSuccess, StatusUnspecFail, etc. 17 | Code dhcp6.Status 18 | 19 | // Message specifies a human-readable message within this StatusCode, useful 20 | // for providing information about successes or failures. 21 | Message string 22 | } 23 | 24 | // NewStatusCode creates a new StatusCode from an input Status value and a 25 | // string message. 26 | func NewStatusCode(code dhcp6.Status, message string) *StatusCode { 27 | return &StatusCode{ 28 | Code: code, 29 | Message: message, 30 | } 31 | } 32 | 33 | // MarshalBinary allocates a byte slice containing the data from a StatusCode. 34 | func (s *StatusCode) MarshalBinary() ([]byte, error) { 35 | // 2 bytes: status code 36 | // N bytes: message 37 | b := buffer.New(nil) 38 | b.Write16(uint16(s.Code)) 39 | b.WriteBytes([]byte(s.Message)) 40 | return b.Data(), nil 41 | } 42 | 43 | // UnmarshalBinary unmarshals a raw byte slice into a StatusCode. 44 | // 45 | // If the byte slice does not contain enough data to form a valid StatusCode, 46 | // errInvalidStatusCode is returned. 47 | func (s *StatusCode) UnmarshalBinary(p []byte) error { 48 | b := buffer.New(p) 49 | // Too short to contain valid StatusCode 50 | if b.Len() < 2 { 51 | return io.ErrUnexpectedEOF 52 | } 53 | 54 | s.Code = dhcp6.Status(b.Read16()) 55 | s.Message = string(b.Remaining()) 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /dhcp6opts/statuscode_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/mdlayher/dhcp6" 10 | ) 11 | 12 | // TestNewStatusCode verifies that NewStatusCode creates a proper StatusCode 13 | // value for the input values. 14 | func TestNewStatusCode(t *testing.T) { 15 | var tests = []struct { 16 | status dhcp6.Status 17 | message string 18 | sc *StatusCode 19 | }{ 20 | { 21 | status: dhcp6.StatusSuccess, 22 | message: "Success", 23 | sc: &StatusCode{ 24 | Code: dhcp6.StatusSuccess, 25 | Message: "Success", 26 | }, 27 | }, 28 | } 29 | 30 | for i, tt := range tests { 31 | if want, got := tt.sc, NewStatusCode(tt.status, tt.message); !reflect.DeepEqual(want, got) { 32 | t.Fatalf("[%02d] unexpected StatusCode for NewStatusCode(%v, %q)\n- want: %v\n- got: %v", 33 | i, tt.status, tt.message, want, got) 34 | } 35 | } 36 | } 37 | 38 | // TestStatusCodeUnmarshalBinary verifies that StatusCode.UnmarshalBinary 39 | // returns correct StatusCode and error values for several input values. 40 | func TestStatusCodeUnmarshalBinary(t *testing.T) { 41 | var tests = []struct { 42 | buf []byte 43 | sc *StatusCode 44 | err error 45 | }{ 46 | { 47 | buf: []byte{0}, 48 | err: io.ErrUnexpectedEOF, 49 | }, 50 | { 51 | buf: []byte{0, 0}, 52 | sc: &StatusCode{ 53 | Code: dhcp6.StatusSuccess, 54 | }, 55 | }, 56 | { 57 | buf: append([]byte{0, 1}, []byte("deadbeef")...), 58 | sc: &StatusCode{ 59 | Code: dhcp6.StatusUnspecFail, 60 | Message: "deadbeef", 61 | }, 62 | }, 63 | } 64 | 65 | for i, tt := range tests { 66 | sc := new(StatusCode) 67 | if err := sc.UnmarshalBinary(tt.buf); err != nil { 68 | if want, got := tt.err, err; want != got { 69 | t.Fatalf("[%02d] unexpected error for parseStatusCode(%v): %v != %v", 70 | i, tt.buf, want, got) 71 | } 72 | 73 | continue 74 | } 75 | 76 | want, err := tt.sc.MarshalBinary() 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | got, err := sc.MarshalBinary() 81 | if err != nil { 82 | t.Fatal(err) 83 | } 84 | 85 | if !bytes.Equal(want, got) { 86 | t.Fatalf("[%02d] unexpected StatusCode for parseStatusCode(%v)\n- want: %v\n- got: %v", 87 | i, tt.buf, want, got) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /dhcp6opts/string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output=string.go -type=ArchType,DUIDType"; DO NOT EDIT. 2 | 3 | package dhcp6opts 4 | 5 | import "fmt" 6 | 7 | const _ArchType_name = "ArchTypeIntelx86PCArchTypeNECPC98ArchTypeEFIItaniumArchTypeDECAlphaArchtypeArcx86ArchTypeIntelLeanClientArchTypeEFIIA32ArchTypeEFIBCArchTypeEFIXscaleArchTypeEFIx8664" 8 | 9 | var _ArchType_index = [...]uint8{0, 18, 33, 51, 67, 81, 104, 119, 132, 149, 165} 10 | 11 | func (i ArchType) String() string { 12 | if i >= ArchType(len(_ArchType_index)-1) { 13 | return fmt.Sprintf("ArchType(%d)", i) 14 | } 15 | return _ArchType_name[_ArchType_index[i]:_ArchType_index[i+1]] 16 | } 17 | 18 | const _DUIDType_name = "DUIDTypeLLTDUIDTypeENDUIDTypeLLDUIDTypeUUID" 19 | 20 | var _DUIDType_index = [...]uint8{0, 11, 21, 31, 43} 21 | 22 | func (i DUIDType) String() string { 23 | i -= 1 24 | if i >= DUIDType(len(_DUIDType_index)-1) { 25 | return fmt.Sprintf("DUIDType(%d)", i+1) 26 | } 27 | return _DUIDType_name[_DUIDType_index[i]:_DUIDType_index[i+1]] 28 | } 29 | -------------------------------------------------------------------------------- /dhcp6opts/vendorclass.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/mdlayher/dhcp6/internal/buffer" 7 | ) 8 | 9 | // VendorClass is used by a client to identify the vendor that 10 | // manufactured the hardware on which the client is running. The 11 | // information contained in the data area of this option is contained in 12 | // one or more opaque fields that identify details of the hardware 13 | // configuration. 14 | type VendorClass struct { 15 | // EnterpriseNumber specifies an IANA-assigned vendor Private Enterprise 16 | // Number. 17 | EnterpriseNumber uint32 18 | 19 | // The vendor-class-data is composed of a series of separate items, each 20 | // of which describes some characteristic of the client's hardware 21 | // configuration. Examples of vendor-class-data instances might include 22 | // the version of the operating system the client is running or the 23 | // amount of memory installed on the client. 24 | VendorClassData Data 25 | } 26 | 27 | // MarshalBinary allocates a byte slice containing the data from a VendorClass. 28 | func (vc *VendorClass) MarshalBinary() ([]byte, error) { 29 | b := buffer.New(nil) 30 | b.Write32(vc.EnterpriseNumber) 31 | vc.VendorClassData.Marshal(b) 32 | return b.Data(), nil 33 | } 34 | 35 | // UnmarshalBinary unmarshals a raw byte slice into a VendorClass. 36 | // 37 | // If the byte slice is less than 4 bytes in length, or if VendorClassData is 38 | // malformed, io.ErrUnexpectedEOF is returned. 39 | func (vc *VendorClass) UnmarshalBinary(p []byte) error { 40 | b := buffer.New(p) 41 | if b.Len() < 4 { 42 | return io.ErrUnexpectedEOF 43 | } 44 | 45 | vc.EnterpriseNumber = b.Read32() 46 | return vc.VendorClassData.Unmarshal(b) 47 | } 48 | -------------------------------------------------------------------------------- /dhcp6opts/vendoropts.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/mdlayher/dhcp6" 7 | "github.com/mdlayher/dhcp6/internal/buffer" 8 | ) 9 | 10 | // A VendorOpts is used by clients and servers to exchange 11 | // VendorOpts information. 12 | type VendorOpts struct { 13 | // EnterpriseNumber specifies an IANA-assigned vendor Private Enterprise 14 | // Number. 15 | EnterpriseNumber uint32 16 | 17 | // An opaque object of option-len octets, 18 | // interpreted by vendor-specific code on the 19 | // clients and servers 20 | Options dhcp6.Options 21 | } 22 | 23 | // MarshalBinary allocates a byte slice containing the data from a VendorOpts. 24 | func (v *VendorOpts) MarshalBinary() ([]byte, error) { 25 | // 4 bytes: EnterpriseNumber 26 | // N bytes: options slice byte count 27 | b := buffer.New(nil) 28 | b.Write32(v.EnterpriseNumber) 29 | opts, err := v.Options.MarshalBinary() 30 | if err != nil { 31 | return nil, err 32 | } 33 | b.WriteBytes(opts) 34 | 35 | return b.Data(), nil 36 | } 37 | 38 | // UnmarshalBinary unmarshals a raw byte slice into a VendorOpts. 39 | // If the byte slice does not contain enough data to form a valid 40 | // VendorOpts, io.ErrUnexpectedEOF is returned. 41 | // If option-data are invalid, then ErrInvalidPacket is returned. 42 | func (v *VendorOpts) UnmarshalBinary(p []byte) error { 43 | b := buffer.New(p) 44 | // Too short to be valid VendorOpts 45 | if b.Len() < 4 { 46 | return io.ErrUnexpectedEOF 47 | } 48 | 49 | v.EnterpriseNumber = b.Read32() 50 | if err := (&v.Options).UnmarshalBinary(b.Remaining()); err != nil { 51 | // Invalid options means an invalid RelayMessage 52 | return dhcp6.ErrInvalidPacket 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /dhcp6opts/vendoropts_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6opts 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/mdlayher/dhcp6" 10 | ) 11 | 12 | // TestVendorOptsMarshalBinary verifies that VendorOpts marshals properly for the input values. 13 | func TestVendorOptsMarshalBinary(t *testing.T) { 14 | var tests = []struct { 15 | desc string 16 | buf []byte 17 | vendorOpts *VendorOpts 18 | }{ 19 | { 20 | desc: "all zero values", 21 | buf: bytes.Repeat([]byte{0}, 4), 22 | vendorOpts: &VendorOpts{}, 23 | }, 24 | { 25 | desc: "[0, 0, 5, 0x58] EnterpriseNumber, [1: []byte{3, 4}, 2: []byte{0x04, 0xa3, 0x9e}] vendorOpts", 26 | buf: []byte{ 27 | 0, 0, 5, 0x58, 28 | 0, 1, 0, 2, 3, 4, 29 | 0, 2, 0, 3, 0x04, 0xa3, 0x9e, 30 | }, 31 | vendorOpts: &VendorOpts{ 32 | EnterpriseNumber: 1368, 33 | Options: dhcp6.Options{ 34 | 1: [][]byte{ 35 | {3, 4}, 36 | }, 37 | 2: [][]byte{ 38 | {0x04, 0xa3, 0x9e}, 39 | }, 40 | }, 41 | }, 42 | }, 43 | } 44 | 45 | for i, tt := range tests { 46 | want := tt.buf 47 | got, err := tt.vendorOpts.MarshalBinary() 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | if !bytes.Equal(want, got) { 52 | t.Fatalf("[%02d] test %q, unexpected VendorOpts bytes for MarshalBinary(%v)\n- want: %v\n- got: %v", 53 | i, tt.desc, tt.buf, want, got) 54 | } 55 | } 56 | } 57 | 58 | // TestVendorOptsUnmarshalBinary verifies that VendorOpts unmarshals properly for the input values. 59 | func TestVendorOptsUnmarshalBinary(t *testing.T) { 60 | var tests = []struct { 61 | buf []byte 62 | vendorOpts *VendorOpts 63 | err error 64 | }{ 65 | { 66 | buf: bytes.Repeat([]byte{0}, 3), 67 | err: io.ErrUnexpectedEOF, 68 | }, 69 | { 70 | buf: []byte{ 71 | 0, 0, 5, 0x58, 72 | 0, 1, 0, 0xa, 73 | }, 74 | err: dhcp6.ErrInvalidPacket, 75 | }, 76 | { 77 | buf: []byte{ 78 | 0, 0, 5, 0x58, 79 | 0, 1, 0, 2, 3, 4, 80 | 0, 2, 0, 3, 0x04, 0xa3, 0x9e, 81 | }, 82 | vendorOpts: &VendorOpts{ 83 | EnterpriseNumber: 1368, 84 | Options: dhcp6.Options{ 85 | 1: [][]byte{ 86 | {3, 4}, 87 | }, 88 | 2: [][]byte{ 89 | {0x04, 0xa3, 0x9e}, 90 | }, 91 | }, 92 | }, 93 | }, 94 | } 95 | 96 | for i, tt := range tests { 97 | vendorOpts := new(VendorOpts) 98 | if want, got := tt.err, vendorOpts.UnmarshalBinary(tt.buf); want != got { 99 | t.Fatalf("[%02d] unexpected error for parseVendorOpts(%v):\n- want: %v\n- got: %v", i, tt.buf, want, got) 100 | } 101 | 102 | if tt.err != nil { 103 | continue 104 | } 105 | 106 | if want, got := tt.vendorOpts, vendorOpts; !reflect.DeepEqual(want, got) { 107 | t.Fatalf("[%02d] unexpected VendorOpts for parseVendorOpts(%v):\n- want: %v\n- got: %v", i, tt.buf, want, got) 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /dhcp6server/dhcp6.go: -------------------------------------------------------------------------------- 1 | package dhcp6server 2 | 3 | import ( 4 | "github.com/mdlayher/dhcp6" 5 | ) 6 | 7 | // Handler provides an interface which allows structs to act as DHCPv6 server 8 | // handlers. ServeDHCP implementations receive a copy of the incoming DHCP 9 | // request via the Request parameter, and allow outgoing communication via 10 | // the ResponseSender. 11 | // 12 | // ServeDHCP implementations can choose to write a response packet using the 13 | // ResponseSender interface, or choose to not write anything at all. If no packet 14 | // is sent back to the client, it may choose to back off and retry, or attempt 15 | // to pursue communication with other DHCP servers. 16 | type Handler interface { 17 | ServeDHCP(ResponseSender, *Request) 18 | } 19 | 20 | // HandlerFunc is an adapter type which allows the use of normal functions as 21 | // DHCP handlers. If f is a function with the appropriate signature, 22 | // HandlerFunc(f) is a Handler struct that calls f. 23 | type HandlerFunc func(ResponseSender, *Request) 24 | 25 | // ServeDHCP calls f(w, r), allowing regular functions to implement Handler. 26 | func (f HandlerFunc) ServeDHCP(w ResponseSender, r *Request) { 27 | f(w, r) 28 | } 29 | 30 | // ResponseSender provides an interface which allows a DHCP handler to construct 31 | // and send a DHCP response packet. In addition, the server automatically handles 32 | // copying certain options from a client Request to a ResponseSender's Options, 33 | // including: 34 | // - Client ID (OptionClientID) 35 | // - Server ID (OptionServerID) 36 | // 37 | // ResponseSender implementations should use the same transaction ID sent in a 38 | // client Request. 39 | type ResponseSender interface { 40 | // Options returns the Options map that will be sent to a client 41 | // after a call to Send. 42 | Options() dhcp6.Options 43 | 44 | // Send generates a DHCP response packet using the input message type 45 | // and any options set by Options. Send returns the number of bytes 46 | // sent and any errors which occurred. 47 | Send(dhcp6.MessageType) (int, error) 48 | } 49 | -------------------------------------------------------------------------------- /dhcp6server/mux.go: -------------------------------------------------------------------------------- 1 | package dhcp6server 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/mdlayher/dhcp6" 7 | ) 8 | 9 | // ServeMux is a DHCP request multiplexer, which implements Handler. ServeMux 10 | // matches handlers based on their MessageType, enabling different handlers 11 | // to be used for different types of DHCP messages. ServeMux can be helpful 12 | // for structuring your application, but may not be needed for very simple 13 | // DHCP servers. 14 | type ServeMux struct { 15 | mu sync.RWMutex 16 | m map[dhcp6.MessageType]Handler 17 | } 18 | 19 | // NewServeMux creates a new ServeMux which is ready to accept Handlers. 20 | func NewServeMux() *ServeMux { 21 | return &ServeMux{ 22 | m: make(map[dhcp6.MessageType]Handler), 23 | } 24 | } 25 | 26 | // ServeDHCP implements Handler for ServeMux, and serves a DHCP request using 27 | // the appropriate handler for an input Request's MessageType. If the 28 | // MessageType does not match a valid Handler, ServeDHCP does not invoke any 29 | // handlers, ignoring a client's request. 30 | func (mux *ServeMux) ServeDHCP(w ResponseSender, r *Request) { 31 | mux.mu.RLock() 32 | defer mux.mu.RUnlock() 33 | h, ok := mux.m[r.MessageType] 34 | if !ok { 35 | return 36 | } 37 | 38 | h.ServeDHCP(w, r) 39 | } 40 | 41 | // Handle registers a MessageType and Handler with a ServeMux, so that 42 | // future requests with that MessageType will invoke the Handler. 43 | func (mux *ServeMux) Handle(mt dhcp6.MessageType, handler Handler) { 44 | mux.mu.Lock() 45 | mux.m[mt] = handler 46 | mux.mu.Unlock() 47 | } 48 | 49 | // HandleFunc registers a MessageType and function as a HandlerFunc with a 50 | // ServeMux, so that future requests with that MessageType will invoke the 51 | // HandlerFunc. 52 | func (mux *ServeMux) HandleFunc(mt dhcp6.MessageType, handler func(ResponseSender, *Request)) { 53 | mux.Handle(mt, HandlerFunc(handler)) 54 | } 55 | -------------------------------------------------------------------------------- /dhcp6server/mux_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6server_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/mdlayher/dhcp6" 7 | "github.com/mdlayher/dhcp6/dhcp6server" 8 | "github.com/mdlayher/dhcp6/dhcp6test" 9 | ) 10 | 11 | // TestServeMuxHandleNoResponse verifies that no Handler is invoked when a 12 | // ServeMux does not have a Handler registered for a given message type. 13 | func TestServeMuxHandleNoResponse(t *testing.T) { 14 | mux := dhcp6server.NewServeMux() 15 | 16 | r, err := dhcp6server.ParseRequest([]byte{1, 1, 2, 3}, nil) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | w := dhcp6test.NewRecorder(r.TransactionID) 22 | mux.ServeDHCP(w, r) 23 | 24 | if mt := w.MessageType; mt != dhcp6.MessageType(0) { 25 | t.Fatalf("reply packet empty, but got message type: %v", mt) 26 | } 27 | if l := len(w.Options()); l > 0 { 28 | t.Fatalf("reply packet empty, but got %d options", l) 29 | } 30 | } 31 | 32 | // TestServeMuxHandleOK verifies that a Handler is invoked when a ServeMux 33 | // has a Handler registered for a given message type. 34 | func TestServeMuxHandleOK(t *testing.T) { 35 | mux := dhcp6server.NewServeMux() 36 | mt := dhcp6.MessageTypeSolicit 37 | 38 | mux.Handle(mt, &solicitHandler{}) 39 | 40 | r, err := dhcp6server.ParseRequest([]byte{byte(mt), 0, 1, 2}, nil) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | w := dhcp6test.NewRecorder(r.TransactionID) 46 | mux.ServeDHCP(w, r) 47 | 48 | if want, got := dhcp6.MessageTypeAdvertise, w.MessageType; want != got { 49 | t.Fatalf("unexpected response message type: %v != %v", want, got) 50 | } 51 | } 52 | 53 | // TestServeMuxHandleFuncOK verifies that a normal function which can be used 54 | // as a Handler is invoked when a ServeMux has a HandlerFunc registered for 55 | // a given message type. 56 | func TestServeMuxHandleFuncOK(t *testing.T) { 57 | mux := dhcp6server.NewServeMux() 58 | mt := dhcp6.MessageTypeSolicit 59 | 60 | mux.HandleFunc(mt, solicit) 61 | 62 | r, err := dhcp6server.ParseRequest([]byte{byte(mt), 0, 1, 2}, nil) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | 67 | w := dhcp6test.NewRecorder(r.TransactionID) 68 | mux.ServeDHCP(w, r) 69 | 70 | if want, got := dhcp6.MessageTypeAdvertise, w.MessageType; want != got { 71 | t.Fatalf("unexpected response message type: %v != %v", want, got) 72 | } 73 | } 74 | 75 | // solicitHandler is a Handler which returns an Advertise in reply 76 | // to a Solicit request. 77 | type solicitHandler struct{} 78 | 79 | func (h *solicitHandler) ServeDHCP(w dhcp6server.ResponseSender, r *dhcp6server.Request) { 80 | solicit(w, r) 81 | } 82 | 83 | // solicit is a function which can be adapted as a HandlerFunc. 84 | func solicit(w dhcp6server.ResponseSender, r *dhcp6server.Request) { 85 | w.Send(dhcp6.MessageTypeAdvertise) 86 | } 87 | -------------------------------------------------------------------------------- /dhcp6server/request.go: -------------------------------------------------------------------------------- 1 | package dhcp6server 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/mdlayher/dhcp6" 7 | ) 8 | 9 | // Request represents a processed DHCP request received by a server. 10 | // Its struct members contain information regarding the request's message 11 | // type, transaction ID, client ID, options, etc. 12 | type Request struct { 13 | // DHCP message type, such as Solicit, Request, or Renew. 14 | MessageType dhcp6.MessageType 15 | 16 | // Unique transaction ID, which should be preserved across 17 | // multiple requests to the same DHCP server. ServeDHCP 18 | // implementations must manually verify that the same 19 | // transaction ID is used. 20 | TransactionID [3]byte 21 | 22 | // Map of options sent by client, carrying additional 23 | // information or requesting additional information from 24 | // the server. Its methods can be used to check for and parse 25 | // additional information relating to a request. 26 | Options dhcp6.Options 27 | 28 | // Length of the DHCP request, in bytes. 29 | Length int64 30 | 31 | // Network address which was used to contact the DHCP server. 32 | RemoteAddr string 33 | } 34 | 35 | // ParseRequest creates a new Request from an input byte slice and UDP address. 36 | // It populates the basic struct members which can be used in a DHCP handler. 37 | // 38 | // If the input byte slice is not a valid DHCP packet, ErrInvalidPacket is 39 | // returned. 40 | func ParseRequest(b []byte, remoteAddr *net.UDPAddr) (*Request, error) { 41 | p := new(dhcp6.Packet) 42 | if err := p.UnmarshalBinary(b); err != nil { 43 | return nil, err 44 | } 45 | 46 | return &Request{ 47 | MessageType: p.MessageType, 48 | TransactionID: p.TransactionID, 49 | Options: p.Options, 50 | Length: int64(len(b)), 51 | RemoteAddr: remoteAddr.String(), 52 | }, nil 53 | } 54 | -------------------------------------------------------------------------------- /dhcp6server/request_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6server 2 | 3 | import ( 4 | "net" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/mdlayher/dhcp6" 9 | "github.com/mdlayher/dhcp6/dhcp6opts" 10 | ) 11 | 12 | // TestParseRequest verifies that ParseRequest returns a consistent 13 | // Request struct for use in Handler types. 14 | func TestParseRequest(t *testing.T) { 15 | p := &dhcp6.Packet{ 16 | MessageType: dhcp6.MessageTypeSolicit, 17 | TransactionID: [3]byte{1, 2, 3}, 18 | Options: make(dhcp6.Options), 19 | } 20 | var uuid [16]byte 21 | p.Options.Add(dhcp6.OptionClientID, dhcp6opts.NewDUIDUUID(uuid)) 22 | 23 | addr := &net.UDPAddr{ 24 | IP: net.ParseIP("::1"), 25 | Port: 546, 26 | } 27 | 28 | buf, err := p.MarshalBinary() 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | r := &Request{ 34 | MessageType: p.MessageType, 35 | TransactionID: p.TransactionID, 36 | Options: make(dhcp6.Options), 37 | Length: int64(len(buf)), 38 | RemoteAddr: "[::1]:546", 39 | } 40 | r.Options.Add(dhcp6.OptionClientID, dhcp6opts.NewDUIDUUID(uuid)) 41 | 42 | gotR, err := ParseRequest(buf, addr) 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | 47 | if want, got := r, gotR; !reflect.DeepEqual(want, got) { 48 | t.Fatalf("unexpected Request for ParseRequest(%v, %v)\n- want: %v\n- got: %v", 49 | p, addr, want, got) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /dhcp6server/server.go: -------------------------------------------------------------------------------- 1 | package dhcp6server 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "net" 7 | 8 | "github.com/mdlayher/dhcp6" 9 | "github.com/mdlayher/dhcp6/dhcp6opts" 10 | "golang.org/x/net/ipv6" 11 | ) 12 | 13 | var ( 14 | // AllRelayAgentsAndServersAddr is the multicast address group which is 15 | // used to communicate with neighboring (on-link) DHCP servers and relay 16 | // agents, as defined in RFC 3315, Section 5.1. All DHCP servers 17 | // and relay agents are members of this multicast group. 18 | AllRelayAgentsAndServersAddr = &net.IPAddr{ 19 | IP: net.ParseIP("ff02::1:2"), 20 | } 21 | 22 | // AllServersAddr is the multicast address group which is used by a 23 | // DHCP relay agent to communicate with DHCP servers, if the relay agent 24 | // wishes to send messages to all servers, or does not know the unicast 25 | // address of a server. All DHCP servers are members of this multicast 26 | // group. 27 | AllServersAddr = &net.IPAddr{ 28 | IP: net.ParseIP("ff05::1:3"), 29 | } 30 | 31 | // errClosing is a special value used to stop the server's read loop 32 | // when a connection is closing. 33 | errClosing = errors.New("use of closed network connection") 34 | ) 35 | 36 | // PacketConn is an interface which types must implement in order to serve 37 | // DHCP connections using Server.Serve. 38 | type PacketConn interface { 39 | ReadFrom(b []byte) (n int, cm *ipv6.ControlMessage, src net.Addr, err error) 40 | WriteTo(b []byte, cm *ipv6.ControlMessage, dst net.Addr) (n int, err error) 41 | 42 | Close() error 43 | 44 | JoinGroup(ifi *net.Interface, group net.Addr) error 45 | LeaveGroup(ifi *net.Interface, group net.Addr) error 46 | 47 | SetControlMessage(cf ipv6.ControlFlags, on bool) error 48 | } 49 | 50 | // Server represents a DHCP server, and is used to configure a DHCP server's 51 | // behavior. 52 | type Server struct { 53 | // Iface is the the network interface on which this server should 54 | // listen. Traffic from any other network interface will be filtered out 55 | // and ignored by the server. 56 | Iface *net.Interface 57 | 58 | // Addr is the network address which this server should bind to. The 59 | // default value is [::]:547, as specified in RFC 3315, Section 5.2. 60 | Addr string 61 | 62 | // Handler is the handler to use while serving DHCP requests. If this 63 | // value is nil, the Server will panic. 64 | Handler Handler 65 | 66 | // MulticastGroups designates which IPv6 multicast groups this server 67 | // will join on start-up. Because the default configuration acts as a 68 | // DHCP server, most servers will typically join both 69 | // AllRelayAgentsAndServersAddr, and AllServersAddr. If configuring a 70 | // DHCP relay agent, only the former value should be used. 71 | MulticastGroups []*net.IPAddr 72 | 73 | // ServerID is the the server's DUID, which uniquely identifies this 74 | // server to clients. If no DUID is specified, a DUID-LL will be 75 | // generated using Iface's hardware type and address. If possible, 76 | // servers with persistent storage available should generate a DUID-LLT 77 | // and store it for future use. 78 | ServerID dhcp6opts.DUID 79 | 80 | // ErrorLog is an optional logger which can be used to report errors and 81 | // erroneous behavior while the server is accepting client requests. 82 | // If ErrorLog is nil, logging goes to os.Stderr via the log package's 83 | // standard logger. 84 | ErrorLog *log.Logger 85 | } 86 | 87 | // logf logs a message using the server's ErrorLog logger, or the log package 88 | // standard logger, if ErrorLog is nil. 89 | func (s *Server) logf(format string, args ...interface{}) { 90 | if s.ErrorLog != nil { 91 | s.ErrorLog.Printf(format, args...) 92 | } else { 93 | log.Printf(format, args...) 94 | } 95 | } 96 | 97 | // ListenAndServe listens for UDP6 connections on the specified address of the 98 | // specified interface, using the default Server configuration and specified 99 | // handler to handle DHCPv6 connections. The Handler must not be nil. 100 | // 101 | // Any traffic which reaches the Server, and is not bound for the specified 102 | // network interface, will be filtered out and ignored. 103 | // 104 | // In this configuration, the server acts as a DHCP server, but NOT as a 105 | // DHCP relay agent. For more information on DHCP relay agents, see RFC 3315, 106 | // Section 20. 107 | func ListenAndServe(iface string, handler Handler) error { 108 | // Verify network interface exists 109 | ifi, err := net.InterfaceByName(iface) 110 | if err != nil { 111 | return err 112 | } 113 | 114 | return (&Server{ 115 | Iface: ifi, 116 | Addr: "[::]:547", 117 | Handler: handler, 118 | MulticastGroups: []*net.IPAddr{ 119 | AllRelayAgentsAndServersAddr, 120 | AllServersAddr, 121 | }, 122 | }).ListenAndServe() 123 | } 124 | 125 | // ListenAndServe listens on the address specified by s.Addr using the network 126 | // interface defined in s.Iface. Traffic from any other interface will be 127 | // filtered out and ignored. Serve is called to handle serving DHCP traffic 128 | // once ListenAndServe opens a UDP6 packet connection. 129 | func (s *Server) ListenAndServe() error { 130 | // Open UDP6 packet connection listener on specified address 131 | conn, err := net.ListenPacket("udp6", s.Addr) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | defer conn.Close() 137 | return s.Serve(ipv6.NewPacketConn(conn)) 138 | } 139 | 140 | // Serve configures and accepts incoming connections on PacketConn p, creating a 141 | // new goroutine for each. Serve configures IPv6 control message settings, joins 142 | // the appropriate multicast groups, and begins listening for incoming connections. 143 | // 144 | // The service goroutine reads requests, generate the appropriate Request and 145 | // ResponseSender values, then calls s.Handler to handle the request. 146 | func (s *Server) Serve(p PacketConn) error { 147 | // If no DUID was set for server previously, generate a DUID-LL 148 | // now using the interface's hardware address, and just assume the 149 | // "Ethernet 10Mb" hardware type since the caller probably doesn't care. 150 | if s.ServerID == nil { 151 | const ethernet10Mb uint16 = 1 152 | s.ServerID = dhcp6opts.NewDUIDLL(ethernet10Mb, s.Iface.HardwareAddr) 153 | } 154 | 155 | // Filter any traffic which does not indicate the interface 156 | // defined by s.Iface. 157 | if err := p.SetControlMessage(ipv6.FlagInterface, true); err != nil { 158 | return err 159 | } 160 | 161 | // Join appropriate multicast groups 162 | for _, g := range s.MulticastGroups { 163 | if err := p.JoinGroup(s.Iface, g); err != nil { 164 | return err 165 | } 166 | } 167 | 168 | // Set up IPv6 packet connection, and on return, handle leaving multicast 169 | // groups and closing connection 170 | defer func() { 171 | for _, g := range s.MulticastGroups { 172 | _ = p.LeaveGroup(s.Iface, g) 173 | } 174 | 175 | _ = p.Close() 176 | }() 177 | 178 | // Loop and read requests until exit 179 | buf := make([]byte, 1500) 180 | for { 181 | n, cm, addr, err := p.ReadFrom(buf) 182 | if err != nil { 183 | // Stop serve loop gracefully when closing 184 | if err == errClosing { 185 | return nil 186 | } 187 | 188 | // BUG(mdlayher): determine if error can be temporary 189 | return err 190 | } 191 | 192 | // Filter any traffic with a control message indicating an incorrect 193 | // interface index 194 | if cm != nil && cm.IfIndex != s.Iface.Index { 195 | continue 196 | } 197 | 198 | // Create conn struct with data specific to this connection 199 | uc, err := s.newConn(p, addr.(*net.UDPAddr), n, buf) 200 | if err != nil { 201 | continue 202 | } 203 | 204 | // Serve conn and continue looping for more connections 205 | go uc.serve() 206 | } 207 | } 208 | 209 | // conn represents an in-flight DHCP connection, and contains information about 210 | // the connection and server. 211 | type conn struct { 212 | conn PacketConn 213 | remoteAddr *net.UDPAddr 214 | server *Server 215 | buf []byte 216 | } 217 | 218 | // newConn creates a new conn using information received in a single DHCP 219 | // connection. newConn makes a copy of the input buffer for use in handling 220 | // a single connection. 221 | // BUG(mdlayher): consider using a sync.Pool with many buffers available to avoid 222 | // allocating a new one on each connection 223 | func (s *Server) newConn(p PacketConn, addr *net.UDPAddr, n int, buf []byte) (*conn, error) { 224 | c := &conn{ 225 | conn: p, 226 | remoteAddr: addr, 227 | server: s, 228 | buf: make([]byte, n), 229 | } 230 | copy(c.buf, buf[:n]) 231 | 232 | return c, nil 233 | } 234 | 235 | // response represents a DHCP response, and implements ResponseSender so that 236 | // outbound Packets can be appropriately created and sent to a client. 237 | type response struct { 238 | conn PacketConn 239 | remoteAddr *net.UDPAddr 240 | req *Request 241 | 242 | options dhcp6.Options 243 | } 244 | 245 | // Options returns the Options map, which can be modified before a call 246 | // to Write. When Write is called, the Options map is enumerated into an 247 | // ordered slice of option codes and values. 248 | func (r *response) Options() dhcp6.Options { 249 | return r.options 250 | } 251 | 252 | // Send uses the input message typ, the transaction ID sent by a client, 253 | // and the options set by Options, to create and send a Packet to the 254 | // client's address. 255 | func (r *response) Send(mt dhcp6.MessageType) (int, error) { 256 | p := &dhcp6.Packet{ 257 | MessageType: mt, 258 | TransactionID: r.req.TransactionID, 259 | Options: r.options, 260 | } 261 | 262 | b, err := p.MarshalBinary() 263 | if err != nil { 264 | return 0, err 265 | } 266 | 267 | return r.conn.WriteTo(b, nil, r.remoteAddr) 268 | } 269 | 270 | // serve handles serving an individual DHCP connection, and is invoked in a 271 | // goroutine. 272 | func (c *conn) serve() { 273 | // Attempt to parse a Request from a raw packet, providing a nicer 274 | // API for callers to implement their own DHCP request handlers. 275 | r, err := ParseRequest(c.buf, c.remoteAddr) 276 | if err != nil { 277 | // Malformed packets get no response 278 | if err == dhcp6.ErrInvalidPacket { 279 | return 280 | } 281 | 282 | // BUG(mdlayher): decide to log or handle other request errors 283 | c.server.logf("%s: error parsing request: %s", c.remoteAddr.String(), err.Error()) 284 | return 285 | } 286 | 287 | // Filter out unknown/invalid message types, using the lowest and highest 288 | // numbered types 289 | if r.MessageType < dhcp6.MessageTypeSolicit || r.MessageType > dhcp6.MessageTypeDHCPv4Response { 290 | c.server.logf("%s: unrecognized message type: %d", c.remoteAddr.String(), r.MessageType) 291 | return 292 | } 293 | 294 | // Set up response to send responses back to the original requester 295 | w := &response{ 296 | remoteAddr: c.remoteAddr, 297 | conn: c.conn, 298 | req: r, 299 | options: make(dhcp6.Options), 300 | } 301 | 302 | // Add server ID to response 303 | if sID := c.server.ServerID; sID != nil { 304 | _ = w.options.Add(dhcp6.OptionServerID, sID) 305 | } 306 | 307 | // If available in request, add client ID to response 308 | if cID, err := dhcp6opts.GetClientID(r.Options); err == nil { 309 | w.options.Add(dhcp6.OptionClientID, cID) 310 | } 311 | 312 | // Enforce a valid Handler. 313 | handler := c.server.Handler 314 | if handler == nil { 315 | panic("nil DHCPv6 handler for server") 316 | } 317 | 318 | handler.ServeDHCP(w, r) 319 | } 320 | -------------------------------------------------------------------------------- /dhcp6server/server_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6server 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "log" 7 | "net" 8 | "reflect" 9 | "testing" 10 | "time" 11 | 12 | "github.com/mdlayher/dhcp6" 13 | "github.com/mdlayher/dhcp6/dhcp6opts" 14 | "golang.org/x/net/ipv6" 15 | ) 16 | 17 | func init() { 18 | // Discard all output from Server.logf 19 | log.SetOutput(ioutil.Discard) 20 | } 21 | 22 | // TestServeIPv6ControlParameters verifies that a PacketConn successfully 23 | // joins and leaves the appropriate multicast groups designated by a Server, 24 | // and that the appropriate IPv6 control flags are set. 25 | func TestServeIPv6ControlParameters(t *testing.T) { 26 | s := &Server{ 27 | MulticastGroups: []*net.IPAddr{ 28 | AllRelayAgentsAndServersAddr, 29 | AllServersAddr, 30 | }, 31 | } 32 | 33 | // Send pseudo-Packet to avoid EOF, even though it does not matter 34 | // for this test 35 | r := &testMessage{} 36 | r.b.Write([]byte{0, 0, 0, 0}) 37 | 38 | // Don't expect a reply, don't handle a request 39 | _, ip6, err := testServe(r, s, false, func(w ResponseSender, r *Request) {}) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | for _, m := range s.MulticastGroups { 45 | var foundJ bool 46 | var foundL bool 47 | 48 | for _, j := range ip6.joined { 49 | if m.String() == j.String() { 50 | foundJ = true 51 | break 52 | } 53 | } 54 | for _, l := range ip6.left { 55 | if m.String() == l.String() { 56 | foundL = true 57 | break 58 | } 59 | } 60 | 61 | if !foundJ { 62 | t.Fatalf("did not find joined IPv6 multicast group: %v", m) 63 | } 64 | if !foundL { 65 | t.Fatalf("did not find left IPv6 multicast group: %v", m) 66 | } 67 | } 68 | 69 | if b, ok := ip6.flags[ipv6.FlagInterface]; !ok || !b { 70 | t.Fatalf("FlagInterface not found or not set to true:\n- found: %v\n- bool: %v", ok, b) 71 | } 72 | 73 | if !ip6.closed { 74 | t.Fatal("IPv6 connection not closed after Serve") 75 | } 76 | } 77 | 78 | // TestServeWithSetServerID verifies that Serve uses the server ID provided 79 | // instead of generating its own, when a server ID is set. 80 | func TestServeWithSetServerID(t *testing.T) { 81 | p := &dhcp6.Packet{ 82 | MessageType: dhcp6.MessageTypeSolicit, 83 | TransactionID: [3]byte{0, 1, 2}, 84 | } 85 | 86 | pb, err := p.MarshalBinary() 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | 91 | r := &testMessage{} 92 | r.b.Write(pb) 93 | 94 | duid, err := dhcp6opts.NewDUIDLLT(1, time.Now(), []byte{0, 1, 0, 1, 0, 1}) 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | s := &Server{ 100 | ServerID: duid, 101 | } 102 | 103 | // Expect a reply with type advertise 104 | mt := dhcp6.MessageTypeAdvertise 105 | w, _, err := testServe(r, s, true, func(w ResponseSender, r *Request) { 106 | w.Send(mt) 107 | }) 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | 112 | wp := new(dhcp6.Packet) 113 | if err := wp.UnmarshalBinary(w.b.Bytes()); err != nil { 114 | t.Fatal(err) 115 | } 116 | 117 | if want, got := mt, wp.MessageType; want != got { 118 | t.Fatalf("unexpected message type: %v != %v", want, got) 119 | } 120 | 121 | want, err := duid.MarshalBinary() 122 | if err != nil { 123 | t.Fatal(err) 124 | } 125 | 126 | got, err := wp.Options.GetOne(dhcp6.OptionServerID) 127 | if err != nil { 128 | t.Fatal("server ID not found in reply") 129 | } 130 | 131 | if !bytes.Equal(want, got) { 132 | t.Fatalf("unexpected server ID bytes:\n- want: %v\n- got: %v", want, got) 133 | } 134 | } 135 | 136 | // TestServeCreateResponseSenderWithCorrectParameters verifies that a new ResponseSender 137 | // gets appropriate transaction ID, client ID, and server ID values copied into 138 | // it before a Handler is invoked. 139 | func TestServeCreateResponseSenderWithCorrectParameters(t *testing.T) { 140 | txID := [3]byte{0, 1, 2} 141 | duid := dhcp6opts.NewDUIDLL(1, []byte{0, 1, 0, 1, 0, 1}) 142 | 143 | p := &dhcp6.Packet{ 144 | MessageType: dhcp6.MessageTypeSolicit, 145 | TransactionID: txID, 146 | Options: make(dhcp6.Options), 147 | } 148 | p.Options.Add(dhcp6.OptionClientID, duid) 149 | 150 | pb, err := p.MarshalBinary() 151 | if err != nil { 152 | t.Fatal(err) 153 | } 154 | 155 | r := &testMessage{} 156 | r.b.Write(pb) 157 | 158 | // Do not expect a reply, but do some validation to ensure that Serve 159 | // sets up appropriate Request and ResponseSender values from an input request 160 | _, _, err = testServe(r, nil, false, func(w ResponseSender, r *Request) { 161 | if want, got := txID[:], r.TransactionID[:]; !bytes.Equal(want, got) { 162 | t.Fatalf("unexpected transaction ID:\n- want: %v\n- got: %v", want, got) 163 | } 164 | 165 | cID, err := dhcp6opts.GetClientID(w.Options()) 166 | if err != nil { 167 | t.Fatal("ResponseSender options did not contain client ID") 168 | } 169 | if want, got := duid, cID; !reflect.DeepEqual(want, got) { 170 | t.Fatalf("unexpected client ID bytes:\n- want: %v\n- got: %v", want, got) 171 | } 172 | 173 | if sID, err := dhcp6opts.GetServerID(w.Options()); err != nil || sID == nil { 174 | t.Fatal("ResponseSender options did not contain server ID") 175 | } 176 | }) 177 | if err != nil { 178 | t.Fatal(err) 179 | } 180 | } 181 | 182 | // TestServeIgnoreWrongCMIfIndex verifies that Serve will ignore incoming 183 | // connections with an incorrect IPv6 control message interface index. 184 | func TestServeIgnoreWrongCMIfIndex(t *testing.T) { 185 | // Wrong interface index in control message 186 | r := &testMessage{ 187 | cm: &ipv6.ControlMessage{ 188 | IfIndex: -1, 189 | }, 190 | } 191 | r.b.Write([]byte{0, 0, 0, 0}) 192 | 193 | s := &Server{ 194 | Iface: &net.Interface{ 195 | Index: 0, 196 | }, 197 | } 198 | 199 | // Expect no reply at all 200 | w, _, err := testServe(r, s, false, func(w ResponseSender, r *Request) {}) 201 | if err != nil { 202 | t.Fatal(err) 203 | } 204 | 205 | if l := w.b.Len(); l > 0 { 206 | t.Fatalf("reply should be empty, but got length: %d", l) 207 | } 208 | if cm := w.cm; cm != nil { 209 | t.Fatalf("control message should be nil, but got: %v", cm) 210 | } 211 | if addr := w.addr; addr != nil { 212 | t.Fatalf("address should be nil, but got: %v", addr) 213 | } 214 | } 215 | 216 | // TestServeIgnoreInvalidPacket verifies that Serve will ignore invalid 217 | // request packets. 218 | func TestServeIgnoreInvalidPacket(t *testing.T) { 219 | // Packet too short to be valid 220 | r := &testMessage{} 221 | r.b.Write([]byte{0, 0, 0}) 222 | 223 | // Expect no reply at all 224 | w, _, err := testServe(r, nil, false, func(w ResponseSender, r *Request) {}) 225 | if err != nil { 226 | t.Fatal(err) 227 | } 228 | 229 | if l := w.b.Len(); l > 0 { 230 | t.Fatalf("reply should be empty, but got length: %d", l) 231 | } 232 | if cm := w.cm; cm != nil { 233 | t.Fatalf("control message should be nil, but got: %v", cm) 234 | } 235 | if addr := w.addr; addr != nil { 236 | t.Fatalf("address should be nil, but got: %v", addr) 237 | } 238 | } 239 | 240 | // TestServeIgnoreBadMessageType verifies that Serve will ignore request 241 | // packets with invalid message types. 242 | func TestServeIgnoreBadMessageType(t *testing.T) { 243 | // Message types not known 244 | badMT := []byte{0, 22} 245 | for _, mt := range badMT { 246 | r := &testMessage{} 247 | r.b.Write([]byte{mt, 0, 0, 0}) 248 | 249 | // Expect no reply at all 250 | w, _, err := testServe(r, nil, false, func(w ResponseSender, r *Request) {}) 251 | if err != nil { 252 | t.Fatal(err) 253 | } 254 | 255 | if l := w.b.Len(); l > 0 { 256 | t.Fatalf("reply should be empty, but got length: %d", l) 257 | } 258 | if cm := w.cm; cm != nil { 259 | t.Fatalf("control message should be nil, but got: %v", cm) 260 | } 261 | if addr := w.addr; addr != nil { 262 | t.Fatalf("address should be nil, but got: %v", addr) 263 | } 264 | } 265 | } 266 | 267 | // TestServeOK verifies that Serve correctly handles an incoming request and 268 | // all of its options, and replies with expected values. 269 | func TestServeOK(t *testing.T) { 270 | txID := [3]byte{0, 1, 2} 271 | duid := dhcp6opts.NewDUIDLL(1, []byte{0, 1, 0, 1, 0, 1}) 272 | 273 | // Perform an entire Solicit transaction 274 | p := &dhcp6.Packet{ 275 | MessageType: dhcp6.MessageTypeSolicit, 276 | TransactionID: txID, 277 | Options: make(dhcp6.Options), 278 | } 279 | p.Options.Add(dhcp6.OptionClientID, duid) 280 | 281 | pb, err := p.MarshalBinary() 282 | if err != nil { 283 | t.Fatal(err) 284 | } 285 | 286 | // Send from a different mock IP 287 | r := &testMessage{ 288 | addr: &net.UDPAddr{ 289 | IP: net.ParseIP("::2"), 290 | }, 291 | } 292 | r.b.Write(pb) 293 | 294 | // Expect these option values set by server 295 | var preference dhcp6opts.Preference = 255 296 | sCode := dhcp6.StatusSuccess 297 | sMsg := "success" 298 | 299 | // Expect Advertise reply with several options added by server 300 | mt := dhcp6.MessageTypeAdvertise 301 | w, _, err := testServe(r, nil, true, func(w ResponseSender, r *Request) { 302 | w.Options().Add(dhcp6.OptionPreference, preference) 303 | w.Options().Add(dhcp6.OptionStatusCode, dhcp6opts.NewStatusCode(sCode, sMsg)) 304 | 305 | w.Send(mt) 306 | }) 307 | if err != nil { 308 | t.Fatal(err) 309 | } 310 | 311 | // For now, outgoing control message is always nil 312 | if w.cm != nil { 313 | t.Fatal("control message is never set on outgoing reply") 314 | } 315 | if want, got := r.addr, w.addr; want != got { 316 | t.Fatalf("unexpected client address: %v != %v", want, got) 317 | } 318 | 319 | wp := new(dhcp6.Packet) 320 | if err := wp.UnmarshalBinary(w.b.Bytes()); err != nil { 321 | t.Fatal(err) 322 | } 323 | 324 | if want, got := mt, wp.MessageType; want != got { 325 | t.Fatalf("unexpected message type: %v != %v", want, got) 326 | } 327 | 328 | if want, got := txID[:], wp.TransactionID[:]; !bytes.Equal(want, got) { 329 | t.Fatalf("unexpected transaction ID:\n- want: %v\n- got: %v", want, got) 330 | } 331 | 332 | cID, err := dhcp6opts.GetClientID(wp.Options) 333 | if err != nil { 334 | t.Fatalf("response options did not contain client ID: %v", err) 335 | } 336 | if want, got := duid, cID; !reflect.DeepEqual(want, got) { 337 | t.Fatalf("unexpected client ID bytes:\n- want: %v\n- got: %v", want, got) 338 | } 339 | if sID, err := dhcp6opts.GetServerID(wp.Options); err != nil || sID == nil { 340 | t.Fatal("ResponseSender options did not contain server ID") 341 | } 342 | 343 | pr, err := dhcp6opts.GetPreference(wp.Options) 344 | if err != nil { 345 | t.Fatal("response Options did not contain preference") 346 | } 347 | if want, got := preference, pr; want != got { 348 | t.Fatalf("unexpected preference value: %v != %v", want, got) 349 | } 350 | 351 | st, err := dhcp6opts.GetStatusCode(wp.Options) 352 | if err != nil { 353 | t.Fatal("response Options did not contain status code") 354 | } 355 | if want, got := sCode, st.Code; want != got { 356 | t.Fatalf("unexpected status code value: %v != %v", want, got) 357 | } 358 | if want, got := sMsg, st.Message; want != got { 359 | t.Fatalf("unexpected status code meesage: %q != %q", want, got) 360 | } 361 | } 362 | 363 | // testServe performs a single transaction using the input message, server 364 | // configuration, whether or not a reply is expected, and a closure which 365 | // acts as a HandlerFunc. 366 | func testServe(r *testMessage, s *Server, expectReply bool, fn func(w ResponseSender, r *Request)) (*testMessage, *recordIPv6PacketConn, error) { 367 | // If caller doesn't specify a testMessage or client address 368 | // for it, configure it for them 369 | if r == nil { 370 | r = &testMessage{} 371 | } 372 | if r.addr == nil { 373 | r.addr = &net.UDPAddr{ 374 | IP: net.ParseIP("::1"), 375 | } 376 | } 377 | 378 | // If caller doesn't specify Server value, configure it for 379 | // them using input function as HandlerFunc. 380 | if s == nil { 381 | s = &Server{} 382 | } 383 | if s.Iface == nil { 384 | s.Iface = &net.Interface{ 385 | Name: "foo0", 386 | Index: 0, 387 | } 388 | } 389 | s.Handler = HandlerFunc(fn) 390 | 391 | // Implements PacketConn to capture request/response 392 | tc := &testPacketConn{ 393 | r: r, 394 | w: &testMessage{}, 395 | 396 | // Record IPv6 control parameters 397 | recordIPv6PacketConn: &recordIPv6PacketConn{ 398 | joined: make([]net.Addr, 0), 399 | left: make([]net.Addr, 0), 400 | flags: make(map[ipv6.ControlFlags]bool), 401 | }, 402 | } 403 | 404 | // Perform a single read and possibly write before returning 405 | // an error on second read to close server 406 | c := &oneReadPacketConn{ 407 | PacketConn: tc, 408 | 409 | readDoneC: make(chan struct{}), 410 | writeDoneC: make(chan struct{}), 411 | } 412 | 413 | // If no reply is expected, this channel will never be closed, 414 | // and should be closed immediately 415 | if !expectReply { 416 | close(c.writeDoneC) 417 | } 418 | 419 | // Handle request 420 | err := s.Serve(c) 421 | 422 | // Wait for read and write to complete 423 | <-c.readDoneC 424 | <-c.writeDoneC 425 | 426 | // Return written values, IPv6 control parameters 427 | return tc.w, tc.recordIPv6PacketConn, err 428 | } 429 | 430 | // oneReadPacketConn allows a single read and possibly write transaction using 431 | // the embedded PacketConn before issuing errClosing to close the server. 432 | type oneReadPacketConn struct { 433 | PacketConn 434 | 435 | txDone bool 436 | 437 | readDoneC chan struct{} 438 | writeDoneC chan struct{} 439 | } 440 | 441 | // ReadFrom reads input bytes using the underlying PacketConn only once. Once 442 | // the read is completed, readDoneC will close and stop blocking. Any 443 | // further ReadFrom calls result in errClosing. 444 | func (c *oneReadPacketConn) ReadFrom(b []byte) (int, *ipv6.ControlMessage, net.Addr, error) { 445 | if c.txDone { 446 | return 0, nil, nil, errClosing 447 | } 448 | c.txDone = true 449 | 450 | n, cm, addr, err := c.PacketConn.ReadFrom(b) 451 | close(c.readDoneC) 452 | return n, cm, addr, err 453 | } 454 | 455 | // WriteTo writes input bytes and IPv6 control parameters to the underlying 456 | // PacketConn. Once the write is completed, writeDoneC will close and 457 | // stop blocking. 458 | func (c *oneReadPacketConn) WriteTo(b []byte, cm *ipv6.ControlMessage, dst net.Addr) (int, error) { 459 | n, err := c.PacketConn.WriteTo(b, cm, dst) 460 | close(c.writeDoneC) 461 | return n, err 462 | } 463 | 464 | // testPacketConn captures client requests, server responses, and IPv6 465 | // control parameters set by the server. 466 | type testPacketConn struct { 467 | r *testMessage 468 | w *testMessage 469 | 470 | *recordIPv6PacketConn 471 | } 472 | 473 | // testMessage captures data from a request or response. 474 | type testMessage struct { 475 | b bytes.Buffer 476 | cm *ipv6.ControlMessage 477 | addr net.Addr 478 | } 479 | 480 | // ReadFrom returns data from a preconfigured testMessage containing bytes, 481 | // an IPv6 control message, and a client address. 482 | func (c *testPacketConn) ReadFrom(b []byte) (int, *ipv6.ControlMessage, net.Addr, error) { 483 | n, err := c.r.b.Read(b) 484 | return n, c.r.cm, c.r.addr, err 485 | } 486 | 487 | // WriteTo writes data to a testMessage containing bytes, an IPv6 control 488 | // message, and a client address. 489 | func (c *testPacketConn) WriteTo(b []byte, cm *ipv6.ControlMessage, dst net.Addr) (int, error) { 490 | n, err := c.w.b.Write(b) 491 | c.w.cm = cm 492 | c.w.addr = dst 493 | 494 | return n, err 495 | } 496 | 497 | // recordIPv6PacketConn tracks IPv6 control parameters, such as joined and 498 | // left multicast groups, control flags, and whether or not the connection 499 | // was closed. 500 | type recordIPv6PacketConn struct { 501 | closed bool 502 | joined []net.Addr 503 | left []net.Addr 504 | flags map[ipv6.ControlFlags]bool 505 | } 506 | 507 | // Close records that an IPv6 connection was closed. 508 | func (c *recordIPv6PacketConn) Close() error { 509 | c.closed = true 510 | return nil 511 | } 512 | 513 | // JoinGroup records that a multicast group was joined. 514 | func (c *recordIPv6PacketConn) JoinGroup(ifi *net.Interface, group net.Addr) error { 515 | c.joined = append(c.joined, group) 516 | return nil 517 | } 518 | 519 | // LeaveGroup records that a multicast group was left. 520 | func (c *recordIPv6PacketConn) LeaveGroup(ifi *net.Interface, group net.Addr) error { 521 | c.left = append(c.left, group) 522 | return nil 523 | } 524 | 525 | // SetControlMessage records that an IPv6 control message was set. 526 | func (c *recordIPv6PacketConn) SetControlMessage(cf ipv6.ControlFlags, on bool) error { 527 | c.flags[cf] = on 528 | return nil 529 | } 530 | -------------------------------------------------------------------------------- /dhcp6test/recorder.go: -------------------------------------------------------------------------------- 1 | // Package dhcp6test provides utilities for testing DHCPv6 clients and servers. 2 | package dhcp6test 3 | 4 | import ( 5 | "github.com/mdlayher/dhcp6" 6 | ) 7 | 8 | // Recorder is a dhcp6.ResponseSender which captures a response's message type and 9 | // options, for inspection during tests. 10 | type Recorder struct { 11 | MessageType dhcp6.MessageType 12 | TransactionID [3]byte 13 | OptionsMap dhcp6.Options 14 | Packet *dhcp6.Packet 15 | } 16 | 17 | // NewRecorder creates a new Recorder which uses the input transaction ID. 18 | func NewRecorder(txID [3]byte) *Recorder { 19 | return &Recorder{ 20 | TransactionID: txID, 21 | OptionsMap: make(dhcp6.Options), 22 | } 23 | } 24 | 25 | // Options returns the Options map of a Recorder. 26 | func (r *Recorder) Options() dhcp6.Options { 27 | return r.OptionsMap 28 | } 29 | 30 | // Send creates a new DHCPv6 packet using the input message type, and stores 31 | // it for later inspection. 32 | func (r *Recorder) Send(mt dhcp6.MessageType) (int, error) { 33 | r.MessageType = mt 34 | p := &dhcp6.Packet{ 35 | MessageType: mt, 36 | TransactionID: r.TransactionID, 37 | Options: r.OptionsMap, 38 | } 39 | r.Packet = p 40 | 41 | b, err := p.MarshalBinary() 42 | return len(b), err 43 | } 44 | -------------------------------------------------------------------------------- /dhcp6test/recorder_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/mdlayher/dhcp6" 9 | "github.com/mdlayher/dhcp6/dhcp6opts" 10 | ) 11 | 12 | // TestRecorder verifies that a Recorder properly captures information 13 | // when a message is sent. 14 | func TestRecorder(t *testing.T) { 15 | mt := dhcp6.MessageTypeAdvertise 16 | txID := [3]byte{0, 1, 2} 17 | clientID := dhcp6opts.NewDUIDLL(1, []byte{0, 1, 0, 1, 0, 1}) 18 | 19 | r := NewRecorder(txID) 20 | if err := r.Options().Add(dhcp6.OptionClientID, clientID); err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | if _, err := r.Send(mt); err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | if want, got := mt, r.MessageType; want != got { 29 | t.Fatalf("unexpected message type: %v != %v", want, got) 30 | } 31 | if want, got := txID[:], r.TransactionID[:]; !bytes.Equal(want, got) { 32 | t.Fatalf("unexpected transaction ID: %v != %v", want, got) 33 | } 34 | 35 | duid, err := dhcp6opts.GetClientID(r.Options()) 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | if want, got := clientID, duid; !reflect.DeepEqual(want, got) { 40 | t.Fatalf("unexpected client ID: %v != %v", want, got) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mdlayher/dhcp6 2 | 3 | go 1.12 4 | 5 | require golang.org/x/net v0.0.0-20190310074541-c10a0554eabf 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/net v0.0.0-20190310074541-c10a0554eabf h1:J7RqX9u0J9ZB37CGaFc2VC+QZZT6E6jnDbrboEFVo0U= 2 | golang.org/x/net v0.0.0-20190310074541-c10a0554eabf/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 3 | -------------------------------------------------------------------------------- /internal/buffer/buffer.go: -------------------------------------------------------------------------------- 1 | package buffer 2 | 3 | import ( 4 | "encoding/binary" 5 | ) 6 | 7 | var order = binary.BigEndian 8 | 9 | // Buffer encapsulates marshaling unsigned integer and byte slice values. 10 | type Buffer struct { 11 | // data is the underlying data. 12 | data []byte 13 | } 14 | 15 | // New consumes b for marshaling or unmarshaling. 16 | func New(b []byte) *Buffer { 17 | return &Buffer{b} 18 | } 19 | 20 | // append appends n bytes to the Buffer and returns a slice pointing to the 21 | // newly appended bytes. 22 | func (b *Buffer) append(n int) []byte { 23 | b.data = append(b.data, make([]byte, n)...) 24 | return b.data[len(b.data)-n:] 25 | } 26 | 27 | // Data is unconsumed data remaining in the Buffer. 28 | func (b *Buffer) Data() []byte { 29 | return b.data 30 | } 31 | 32 | // Remaining consumes and returns a copy of all remaining bytes in the Buffer. 33 | func (b *Buffer) Remaining() []byte { 34 | p := b.Consume(len(b.Data())) 35 | cp := make([]byte, len(p)) 36 | copy(cp, p) 37 | return cp 38 | } 39 | 40 | // consume consumes n bytes from the Buffer. It returns nil, false if there 41 | // aren't enough bytes left. 42 | func (b *Buffer) consume(n int) ([]byte, bool) { 43 | if !b.Has(n) { 44 | return nil, false 45 | } 46 | rval := b.data[:n] 47 | b.data = b.data[n:] 48 | return rval, true 49 | } 50 | 51 | // Consume consumes n bytes from the Buffer. It returns nil if there aren't 52 | // enough bytes left. 53 | func (b *Buffer) Consume(n int) []byte { 54 | v, ok := b.consume(n) 55 | if !ok { 56 | return nil 57 | } 58 | return v 59 | } 60 | 61 | // Has returns true if n bytes are available. 62 | func (b *Buffer) Has(n int) bool { 63 | return len(b.data) >= n 64 | } 65 | 66 | // Len returns the length of the remaining bytes. 67 | func (b *Buffer) Len() int { 68 | return len(b.data) 69 | } 70 | 71 | // Read8 reads a byte from the Buffer. 72 | func (b *Buffer) Read8() uint8 { 73 | v, ok := b.consume(1) 74 | if !ok { 75 | return 0 76 | } 77 | return uint8(v[0]) 78 | } 79 | 80 | // Read16 reads a 16-bit value from the Buffer. 81 | func (b *Buffer) Read16() uint16 { 82 | v, ok := b.consume(2) 83 | if !ok { 84 | return 0 85 | } 86 | return order.Uint16(v) 87 | } 88 | 89 | // Read32 reads a 32-bit value from the Buffer. 90 | func (b *Buffer) Read32() uint32 { 91 | v, ok := b.consume(4) 92 | if !ok { 93 | return 0 94 | } 95 | return order.Uint32(v) 96 | } 97 | 98 | // Read64 reads a 64-bit value from the Buffer. 99 | func (b *Buffer) Read64() uint64 { 100 | v, ok := b.consume(8) 101 | if !ok { 102 | return 0 103 | } 104 | return order.Uint64(v) 105 | } 106 | 107 | // ReadBytes reads exactly len(p) values from the Buffer. 108 | func (b *Buffer) ReadBytes(p []byte) { 109 | copy(p, b.Consume(len(p))) 110 | } 111 | 112 | // Write8 writes a byte to the Buffer. 113 | func (b *Buffer) Write8(v uint8) { 114 | b.append(1)[0] = byte(v) 115 | } 116 | 117 | // Write16 writes a 16-bit value to the Buffer. 118 | func (b *Buffer) Write16(v uint16) { 119 | order.PutUint16(b.append(2), v) 120 | } 121 | 122 | // Write32 writes a 32-bit value to the Buffer. 123 | func (b *Buffer) Write32(v uint32) { 124 | order.PutUint32(b.append(4), v) 125 | } 126 | 127 | // Write64 writes a 64-bit value to the Buffer. 128 | func (b *Buffer) Write64(v uint64) { 129 | order.PutUint64(b.append(8), v) 130 | } 131 | 132 | // WriteN returns a newly appended n-size Buffer to write to. 133 | func (b *Buffer) WriteN(n int) []byte { 134 | return b.append(n) 135 | } 136 | 137 | // WriteBytes writes p to the Buffer. 138 | func (b *Buffer) WriteBytes(p []byte) { 139 | copy(b.append(len(p)), p) 140 | } 141 | -------------------------------------------------------------------------------- /options.go: -------------------------------------------------------------------------------- 1 | package dhcp6 2 | 3 | import ( 4 | "encoding" 5 | "sort" 6 | 7 | "github.com/mdlayher/dhcp6/internal/buffer" 8 | ) 9 | 10 | // Options is a map of OptionCode keys with a slice of byte slice values. 11 | // 12 | // Its methods can be used to easily check for additional information from a 13 | // packet. Get and GetOne should be used to access data from Options. 14 | type Options map[OptionCode][][]byte 15 | 16 | // Add adds a new OptionCode key and BinaryMarshaler struct's bytes to the 17 | // Options map. 18 | func (o Options) Add(key OptionCode, value encoding.BinaryMarshaler) error { 19 | // Special case: since OptionRapidCommit actually has zero length, it is 20 | // possible for an option key to appear with no value. 21 | if value == nil { 22 | o.AddRaw(key, nil) 23 | return nil 24 | } 25 | 26 | b, err := value.MarshalBinary() 27 | if err != nil { 28 | return err 29 | } 30 | 31 | o.AddRaw(key, b) 32 | return nil 33 | } 34 | 35 | // AddRaw adds a new OptionCode key and raw value byte slice to the 36 | // Options map. 37 | func (o Options) AddRaw(key OptionCode, value []byte) { 38 | o[key] = append(o[key], value) 39 | } 40 | 41 | // Get attempts to retrieve all values specified by an OptionCode key. 42 | // 43 | // If a value is found, get returns a non-nil [][]byte and nil. If it is not 44 | // found, Get returns nil and ErrOptionNotPresent. 45 | func (o Options) Get(key OptionCode) ([][]byte, error) { 46 | // Check for value by key. 47 | v, ok := o[key] 48 | if !ok { 49 | return nil, ErrOptionNotPresent 50 | } 51 | 52 | // Some options can actually have zero length (OptionRapidCommit), so 53 | // just return an empty byte slice if this is the case. 54 | if len(v) == 0 { 55 | return [][]byte{{}}, nil 56 | } 57 | return v, nil 58 | } 59 | 60 | // GetOne attempts to retrieve the first and only value specified by an 61 | // OptionCode key. GetOne should only be used for OptionCode keys that must 62 | // have at most one value. 63 | // 64 | // GetOne works just like Get, but if there is more than one value for the 65 | // OptionCode key, ErrInvalidPacket will be returned. 66 | func (o Options) GetOne(key OptionCode) ([]byte, error) { 67 | vv, err := o.Get(key) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | if len(vv) != 1 { 73 | return nil, ErrInvalidPacket 74 | } 75 | return vv[0], nil 76 | } 77 | 78 | // MarshalBinary allocates a buffer and writes options in their DHCPv6 binary 79 | // format into the buffer. 80 | func (o Options) MarshalBinary() ([]byte, error) { 81 | b := buffer.New(nil) 82 | for _, code := range o.sortedCodes() { 83 | for _, data := range o[code] { 84 | // 2 bytes: option code 85 | b.Write16(uint16(code)) 86 | 87 | // 2 bytes: option length 88 | b.Write16(uint16(len(data))) 89 | 90 | // N bytes: option data 91 | b.WriteBytes(data) 92 | } 93 | } 94 | return b.Data(), nil 95 | } 96 | 97 | // UnmarshalBinary fills opts with option codes and corresponding values from 98 | // an input byte slice. 99 | // 100 | // It is used with various different types to enable parsing of both top-level 101 | // options, and options embedded within other options. If options data is 102 | // malformed, it returns ErrInvalidOptions. 103 | func (o *Options) UnmarshalBinary(p []byte) error { 104 | buf := buffer.New(p) 105 | *o = make(Options) 106 | 107 | for buf.Len() >= 4 { 108 | // 2 bytes: option code 109 | // 2 bytes: option length n 110 | // n bytes: data 111 | code := OptionCode(buf.Read16()) 112 | length := buf.Read16() 113 | 114 | // N bytes: option data 115 | data := buf.Consume(int(length)) 116 | if data == nil { 117 | return ErrInvalidOptions 118 | } 119 | data = data[:int(length):int(length)] 120 | 121 | o.AddRaw(code, data) 122 | } 123 | 124 | // Report error for any trailing bytes 125 | if buf.Len() != 0 { 126 | return ErrInvalidOptions 127 | } 128 | return nil 129 | } 130 | 131 | // optionCodes implements sort.Interface. 132 | type optionCodes []OptionCode 133 | 134 | func (b optionCodes) Len() int { return len(b) } 135 | func (b optionCodes) Less(i int, j int) bool { return b[i] < b[j] } 136 | func (b optionCodes) Swap(i int, j int) { b[i], b[j] = b[j], b[i] } 137 | 138 | func (o Options) sortedCodes() optionCodes { 139 | var codes optionCodes 140 | for code := range o { 141 | codes = append(codes, code) 142 | } 143 | 144 | sort.Sort(codes) 145 | return codes 146 | } 147 | -------------------------------------------------------------------------------- /options_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | type option struct { 10 | code OptionCode 11 | data []byte 12 | } 13 | 14 | // TestOptionsAddRaw verifies that Options.AddRaw correctly creates or appends 15 | // key/value Option pairs to an Options map. 16 | func TestOptionsAddRaw(t *testing.T) { 17 | var tests = []struct { 18 | desc string 19 | kv []option 20 | options Options 21 | }{ 22 | { 23 | desc: "one key/value pair", 24 | kv: []option{ 25 | { 26 | code: 1, 27 | data: []byte("foo"), 28 | }, 29 | }, 30 | options: Options{ 31 | 1: [][]byte{[]byte("foo")}, 32 | }, 33 | }, 34 | { 35 | desc: "two key/value pairs", 36 | kv: []option{ 37 | { 38 | code: 1, 39 | data: []byte("foo"), 40 | }, 41 | { 42 | code: 2, 43 | data: []byte("bar"), 44 | }, 45 | }, 46 | options: Options{ 47 | 1: [][]byte{[]byte("foo")}, 48 | 2: [][]byte{[]byte("bar")}, 49 | }, 50 | }, 51 | { 52 | desc: "three key/value pairs, two with same key", 53 | kv: []option{ 54 | { 55 | code: 1, 56 | data: []byte("foo"), 57 | }, 58 | { 59 | code: 1, 60 | data: []byte("baz"), 61 | }, 62 | { 63 | code: 2, 64 | data: []byte("bar"), 65 | }, 66 | }, 67 | options: Options{ 68 | 1: [][]byte{[]byte("foo"), []byte("baz")}, 69 | 2: [][]byte{[]byte("bar")}, 70 | }, 71 | }, 72 | } 73 | 74 | for i, tt := range tests { 75 | o := make(Options) 76 | for _, p := range tt.kv { 77 | o.AddRaw(p.code, p.data) 78 | } 79 | 80 | if want, got := tt.options, o; !reflect.DeepEqual(want, got) { 81 | t.Errorf("[%02d] test %q, unexpected Options map:\n- want: %v\n- got: %v", 82 | i, tt.desc, want, got) 83 | } 84 | } 85 | } 86 | 87 | // TestOptionsGet verifies that Options.Get correctly selects the first value 88 | // for a given key, if the value is not empty in an Options map. 89 | func TestOptionsGet(t *testing.T) { 90 | var tests = []struct { 91 | desc string 92 | options Options 93 | key OptionCode 94 | value []byte 95 | err error 96 | }{ 97 | { 98 | desc: "nil Options map", 99 | err: ErrOptionNotPresent, 100 | }, 101 | { 102 | desc: "empty Options map", 103 | options: Options{}, 104 | err: ErrOptionNotPresent, 105 | }, 106 | { 107 | desc: "value not present in Options map", 108 | options: Options{ 109 | 2: [][]byte{[]byte("foo")}, 110 | }, 111 | key: 1, 112 | err: ErrOptionNotPresent, 113 | }, 114 | { 115 | desc: "value present in Options map, but zero length value for key", 116 | options: Options{ 117 | 1: [][]byte{}, 118 | }, 119 | key: 1, 120 | }, 121 | { 122 | desc: "value present in Options map", 123 | options: Options{ 124 | 1: [][]byte{[]byte("foo")}, 125 | }, 126 | key: 1, 127 | value: []byte("foo"), 128 | }, 129 | { 130 | desc: "value present in Options map, with multiple values", 131 | options: Options{ 132 | 1: [][]byte{[]byte("foo"), []byte("bar")}, 133 | }, 134 | key: 1, 135 | err: ErrInvalidPacket, 136 | }, 137 | } 138 | 139 | for i, tt := range tests { 140 | value, err := tt.options.GetOne(tt.key) 141 | if err != nil { 142 | if want, got := tt.err, err; want != got { 143 | t.Errorf("[%02d] test %q, unexpected err for Options.GetOne(%v): %v != %v", 144 | i, tt.desc, tt.key, want, got) 145 | continue 146 | } 147 | } 148 | 149 | if want, got := tt.value, value; !bytes.Equal(want, got) { 150 | t.Errorf("[%02d] test %q, unexpected value for Options.GetOne(%v):\n- want: %v\n- got: %v", 151 | i, tt.desc, tt.key, want, got) 152 | } 153 | } 154 | } 155 | 156 | // Test_parseOptions verifies that parseOptions parses correct option values 157 | // from a slice of bytes, and that it returns an empty Options map if the byte 158 | // slice cannot contain options. 159 | func Test_parseOptions(t *testing.T) { 160 | var tests = []struct { 161 | desc string 162 | buf []byte 163 | options Options 164 | err error 165 | }{ 166 | { 167 | desc: "nil options bytes", 168 | options: Options{}, 169 | }, 170 | { 171 | desc: "empty options bytes", 172 | buf: []byte{}, 173 | options: Options{}, 174 | }, 175 | { 176 | desc: "too short options bytes", 177 | buf: []byte{0}, 178 | err: ErrInvalidOptions, 179 | }, 180 | { 181 | desc: "zero code, zero length option bytes", 182 | buf: []byte{0, 0, 0, 0}, 183 | options: Options{ 184 | 0: [][]byte{{}}, 185 | }, 186 | }, 187 | { 188 | desc: "zero code, zero length option bytes with trailing byte", 189 | buf: []byte{0, 0, 0, 0, 1}, 190 | err: ErrInvalidOptions, 191 | }, 192 | { 193 | desc: "zero code, length 3, incorrect length for data", 194 | buf: []byte{0, 0, 0, 3, 1, 2}, 195 | err: ErrInvalidOptions, 196 | }, 197 | { 198 | desc: "Rapid Commit, length 0", 199 | buf: []byte{0, 14, 0, 0}, 200 | options: Options{ 201 | OptionRapidCommit: [][]byte{{}}, 202 | }, 203 | }, 204 | { 205 | desc: "client ID, length 1, value [1]", 206 | buf: []byte{0, 1, 0, 1, 1}, 207 | options: Options{ 208 | OptionClientID: [][]byte{{1}}, 209 | }, 210 | }, 211 | { 212 | desc: "client ID, length 2, value [1 1] + server ID, length 3, value [1 2 3]", 213 | buf: []byte{ 214 | 0, 1, 0, 2, 1, 1, 215 | 0, 2, 0, 3, 1, 2, 3, 216 | }, 217 | options: Options{ 218 | OptionClientID: [][]byte{{1, 1}}, 219 | OptionServerID: [][]byte{{1, 2, 3}}, 220 | }, 221 | }, 222 | } 223 | 224 | for i, tt := range tests { 225 | var options Options 226 | err := (&options).UnmarshalBinary(tt.buf) 227 | if err != nil { 228 | if want, got := tt.err, err; want != got { 229 | t.Errorf("[%02d] test %q, unexpected error for parseOptions(%v): %v != %v", 230 | i, tt.desc, tt.buf, want, got) 231 | } 232 | continue 233 | } 234 | 235 | if want, got := tt.options, options; !reflect.DeepEqual(want, got) { 236 | t.Errorf("[%02d] test %q, unexpected Options map for parseOptions(%v):\n- want: %v\n- got: %v", 237 | i, tt.desc, tt.buf, want, got) 238 | } 239 | 240 | for k, v := range tt.options { 241 | for ii := range v { 242 | if want, got := cap(v[ii]), cap(options[k][ii]); want != got { 243 | t.Errorf("[%02d] test %q, option %d, unexpected capacity option data:\n- want: %v\n- got: %v", 244 | i, tt.desc, ii, want, got) 245 | } 246 | } 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /packet.go: -------------------------------------------------------------------------------- 1 | package dhcp6 2 | 3 | import ( 4 | "github.com/mdlayher/dhcp6/internal/buffer" 5 | ) 6 | 7 | // Packet represents a raw DHCPv6 packet, using the format described in RFC 3315, 8 | // Section 6. 9 | // 10 | // The Packet type is typically only needed for low-level operations within the 11 | // client, server, or in tests. 12 | type Packet struct { 13 | // MessageType specifies the DHCP message type constant, such as 14 | // MessageTypeSolicit, MessageTypeAdvertise, etc. 15 | MessageType MessageType 16 | 17 | // TransactionID specifies the DHCP transaction ID. The transaction ID must 18 | // be the same for all message exchanges in one DHCP transaction. 19 | TransactionID [3]byte 20 | 21 | // Options specifies a map of DHCP options. Its methods can be used to 22 | // retrieve data from an incoming packet, or send data with an outgoing 23 | // packet. 24 | Options Options 25 | } 26 | 27 | // MarshalBinary allocates a byte slice containing the data 28 | // from a Packet. 29 | func (p *Packet) MarshalBinary() ([]byte, error) { 30 | // 1 byte: message type 31 | // 3 bytes: transaction ID 32 | // N bytes: options slice byte count 33 | b := buffer.New(nil) 34 | 35 | b.Write8(uint8(p.MessageType)) 36 | b.WriteBytes(p.TransactionID[:]) 37 | 38 | opts, err := p.Options.MarshalBinary() 39 | if err != nil { 40 | return nil, err 41 | } 42 | b.WriteBytes(opts) 43 | return b.Data(), nil 44 | } 45 | 46 | // UnmarshalBinary unmarshals a raw byte slice into a Packet. 47 | // 48 | // If the byte slice does not contain enough data to form a valid Packet, 49 | // ErrInvalidPacket is returned. 50 | func (p *Packet) UnmarshalBinary(q []byte) error { 51 | b := buffer.New(q) 52 | // Packet must contain at least a message type and transaction ID 53 | if b.Len() < 4 { 54 | return ErrInvalidPacket 55 | } 56 | 57 | p.MessageType = MessageType(b.Read8()) 58 | b.ReadBytes(p.TransactionID[:]) 59 | 60 | if err := (&p.Options).UnmarshalBinary(b.Remaining()); err != nil { 61 | return ErrInvalidPacket 62 | } 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /packet_test.go: -------------------------------------------------------------------------------- 1 | package dhcp6 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | // TestPacketMarshalBinary verifies that Packet.MarshalBinary allocates and returns a correct 10 | // byte slice for a variety of input data. 11 | func TestPacketMarshalBinary(t *testing.T) { 12 | var tests = []struct { 13 | desc string 14 | packet *Packet 15 | buf []byte 16 | }{ 17 | { 18 | desc: "empty packet", 19 | packet: &Packet{}, 20 | buf: []byte{0, 0, 0, 0}, 21 | }, 22 | { 23 | desc: "Solicit only", 24 | packet: &Packet{ 25 | MessageType: MessageTypeSolicit, 26 | }, 27 | buf: []byte{1, 0, 0, 0}, 28 | }, 29 | { 30 | desc: "Solicit, [1 2 3] transaction ID", 31 | packet: &Packet{ 32 | MessageType: MessageTypeSolicit, 33 | TransactionID: [3]byte{1, 2, 3}, 34 | }, 35 | buf: []byte{1, 1, 2, 3}, 36 | }, 37 | { 38 | desc: "Solicit, [2, 3, 4] transaction ID, option client ID [0 1]", 39 | packet: &Packet{ 40 | MessageType: MessageTypeSolicit, 41 | TransactionID: [3]byte{1, 2, 3}, 42 | Options: Options{ 43 | OptionClientID: [][]byte{{0, 1}}, 44 | }, 45 | }, 46 | buf: []byte{1, 1, 2, 3, 0, 1, 0, 2, 0, 1}, 47 | }, 48 | } 49 | 50 | for i, tt := range tests { 51 | buf, err := tt.packet.MarshalBinary() 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | if want, got := tt.buf, buf; !bytes.Equal(want, got) { 57 | t.Fatalf("[%02d] test %q, unexpected packet bytes:\n- want: %v\n- got: %v", 58 | i, tt.desc, want, got) 59 | } 60 | } 61 | } 62 | 63 | // TestPacketUnmarshalBinary verifies that Packet.UnmarshalBinary returns 64 | // appropriate Packets and errors for various input byte slices. 65 | func TestPacketUnmarshalBinary(t *testing.T) { 66 | var tests = []struct { 67 | desc string 68 | buf []byte 69 | packet *Packet 70 | err error 71 | }{ 72 | { 73 | desc: "nil buffer, malformed packet", 74 | err: ErrInvalidPacket, 75 | }, 76 | { 77 | desc: "empty buffer, malformed packet", 78 | buf: []byte{}, 79 | err: ErrInvalidPacket, 80 | }, 81 | { 82 | desc: "length 1 buffer, malformed packet", 83 | buf: []byte{0}, 84 | err: ErrInvalidPacket, 85 | }, 86 | { 87 | desc: "length 3 buffer, malformed packet", 88 | buf: []byte{0, 0, 0}, 89 | err: ErrInvalidPacket, 90 | }, 91 | { 92 | desc: "invalid options in packet", 93 | buf: []byte{0, 0, 0, 0, 0, 1, 0, 1}, 94 | err: ErrInvalidPacket, 95 | }, 96 | { 97 | desc: "length 4 buffer, OK", 98 | buf: []byte{0, 0, 0, 0}, 99 | packet: &Packet{ 100 | MessageType: 0, 101 | TransactionID: [3]byte{0, 0, 0}, 102 | Options: make(Options), 103 | }, 104 | }, 105 | { 106 | desc: "Solicit, [1 2 3] transaction ID, OK", 107 | buf: []byte{1, 1, 2, 3}, 108 | packet: &Packet{ 109 | MessageType: MessageTypeSolicit, 110 | TransactionID: [3]byte{1, 2, 3}, 111 | Options: make(Options), 112 | }, 113 | }, 114 | { 115 | desc: "Solicit, [2 3 4] transaction ID, option client ID [0 1], OK", 116 | buf: []byte{1, 2, 3, 4, 0, 1, 0, 2, 0, 1}, 117 | packet: &Packet{ 118 | MessageType: MessageTypeSolicit, 119 | TransactionID: [3]byte{2, 3, 4}, 120 | Options: Options{ 121 | OptionClientID: [][]byte{{0, 1}}, 122 | }, 123 | }, 124 | }, 125 | } 126 | 127 | for i, tt := range tests { 128 | p := new(Packet) 129 | if err := p.UnmarshalBinary(tt.buf); err != nil { 130 | if want, got := tt.err, err; want != got { 131 | t.Fatalf("[%02d] test %q, unexpected error: %v != %v", 132 | i, tt.desc, want, got) 133 | } 134 | 135 | continue 136 | } 137 | 138 | if want, got := tt.packet, p; !reflect.DeepEqual(want, got) { 139 | t.Fatalf("[%02d] test %q, unexpected packet:\n- want: %v\n- got: %v", 140 | i, tt.desc, want, got) 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -output=string.go -type=MessageType,Status,OptionCode"; DO NOT EDIT. 2 | 3 | package dhcp6 4 | 5 | import "fmt" 6 | 7 | const _MessageType_name = "MessageTypeSolicitMessageTypeAdvertiseMessageTypeRequestMessageTypeConfirmMessageTypeRenewMessageTypeRebindMessageTypeReplyMessageTypeReleaseMessageTypeDeclineMessageTypeReconfigureMessageTypeInformationRequestMessageTypeRelayForwMessageTypeRelayReplMessageTypeLeasequeryMessageTypeLeasequeryReplyMessageTypeLeasequeryDoneMessageTypeLeasequeryDataMessageTypeReconfigureRequestMessageTypeReconfigureReplyMessageTypeDHCPv4QueryMessageTypeDHCPv4Response" 8 | 9 | var _MessageType_index = [...]uint16{0, 18, 38, 56, 74, 90, 107, 123, 141, 159, 181, 210, 230, 250, 271, 297, 322, 347, 376, 403, 425, 450} 10 | 11 | func (i MessageType) String() string { 12 | i -= 1 13 | if i >= MessageType(len(_MessageType_index)-1) { 14 | return fmt.Sprintf("MessageType(%d)", i+1) 15 | } 16 | return _MessageType_name[_MessageType_index[i]:_MessageType_index[i+1]] 17 | } 18 | 19 | const _Status_name = "StatusSuccessStatusUnspecFailStatusNoAddrsAvailStatusNoBindingStatusNotOnLinkStatusUseMulticastStatusNoPrefixAvailStatusUnknownQueryTypeStatusMalformedQueryStatusNotConfiguredStatusNotAllowedStatusQueryTerminated" 20 | 21 | var _Status_index = [...]uint8{0, 13, 29, 47, 62, 77, 95, 114, 136, 156, 175, 191, 212} 22 | 23 | func (i Status) String() string { 24 | if i >= Status(len(_Status_index)-1) { 25 | return fmt.Sprintf("Status(%d)", i) 26 | } 27 | return _Status_name[_Status_index[i]:_Status_index[i+1]] 28 | } 29 | 30 | const ( 31 | _OptionCode_name_0 = "OptionClientIDOptionServerIDOptionIANAOptionIATAOptionIAAddrOptionOROOptionPreferenceOptionElapsedTimeOptionRelayMsg" 32 | _OptionCode_name_1 = "OptionAuthOptionUnicastOptionStatusCodeOptionRapidCommitOptionUserClassOptionVendorClassOptionVendorOptsOptionInterfaceIDOptionReconfMsgOptionReconfAccept" 33 | _OptionCode_name_2 = "OptionIAPDOptionIAPrefix" 34 | _OptionCode_name_3 = "OptionRemoteIdentifier" 35 | _OptionCode_name_4 = "OptionBootFileURLOptionBootFileParamOptionClientArchTypeOptionNII" 36 | ) 37 | 38 | var ( 39 | _OptionCode_index_0 = [...]uint8{0, 14, 28, 38, 48, 60, 69, 85, 102, 116} 40 | _OptionCode_index_1 = [...]uint8{0, 10, 23, 39, 56, 71, 88, 104, 121, 136, 154} 41 | _OptionCode_index_2 = [...]uint8{0, 10, 24} 42 | _OptionCode_index_3 = [...]uint8{0, 22} 43 | _OptionCode_index_4 = [...]uint8{0, 17, 36, 56, 65} 44 | ) 45 | 46 | func (i OptionCode) String() string { 47 | switch { 48 | case 1 <= i && i <= 9: 49 | i -= 1 50 | return _OptionCode_name_0[_OptionCode_index_0[i]:_OptionCode_index_0[i+1]] 51 | case 11 <= i && i <= 20: 52 | i -= 11 53 | return _OptionCode_name_1[_OptionCode_index_1[i]:_OptionCode_index_1[i+1]] 54 | case 25 <= i && i <= 26: 55 | i -= 25 56 | return _OptionCode_name_2[_OptionCode_index_2[i]:_OptionCode_index_2[i+1]] 57 | case i == 37: 58 | return _OptionCode_name_3 59 | case 59 <= i && i <= 62: 60 | i -= 59 61 | return _OptionCode_name_4[_OptionCode_index_4[i]:_OptionCode_index_4[i+1]] 62 | default: 63 | return fmt.Sprintf("OptionCode(%d)", i) 64 | } 65 | } 66 | --------------------------------------------------------------------------------