package dhcpv6 import ( "errors" "fmt" "net" "time" "github.com/insomniacslk/dhcp/iana" "github.com/u-root/u-root/pkg/rand" "github.com/u-root/u-root/pkg/uio" ) const MessageHeaderSize = 4 // MessageOptions are the options that may appear in a normal DHCPv6 message. // // RFC 3315 Appendix B lists the valid options that can be used. type MessageOptions struct { Options } // ArchTypes returns the architecture type option. func (mo MessageOptions) ArchTypes() iana.Archs { opt := mo.GetOne(OptionClientArchType) if opt == nil { return nil } return opt.(*optClientArchType).Archs } // ClientID returns the client identifier option. func (mo MessageOptions) ClientID() *Duid { opt := mo.GetOne(OptionClientID) if opt == nil { return nil } return &opt.(*optClientID).Duid } // ServerID returns the server identifier option. func (mo MessageOptions) ServerID() *Duid { opt := mo.GetOne(OptionServerID) if opt == nil { return nil } return &opt.(*optServerID).Duid } // IANA returns all Identity Association for Non-temporary Address options. func (mo MessageOptions) IANA() []*OptIANA { opts := mo.Get(OptionIANA) var ianas []*OptIANA for _, o := range opts { ianas = append(ianas, o.(*OptIANA)) } return ianas } // OneIANA returns the first IANA option. func (mo MessageOptions) OneIANA() *OptIANA { ianas := mo.IANA() if len(ianas) == 0 { return nil } return ianas[0] } // Status returns the status code associated with this option. func (mo MessageOptions) Status() *OptStatusCode { opt := mo.Options.GetOne(OptionStatusCode) if opt == nil { return nil } sc, ok := opt.(*OptStatusCode) if !ok { return nil } return sc } // Message represents a DHCPv6 Message as defined by RFC 3315 Section 6. type Message struct { MessageType MessageType TransactionID TransactionID Options MessageOptions } var randomRead = rand.Read // GenerateTransactionID generates a random 3-byte transaction ID. func GenerateTransactionID() (TransactionID, error) { var tid TransactionID n, err := randomRead(tid[:]) if err != nil { return tid, err } if n != len(tid) { return tid, fmt.Errorf("invalid random sequence: shorter than 3 bytes") } return tid, nil } // GetTime returns a time integer suitable for DUID-LLT, i.e. the current time counted // in seconds since January 1st, 2000, midnight UTC, modulo 2^32 func GetTime() uint32 { now := time.Since(time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)) return uint32((now.Nanoseconds() / 1000000000) % 0xffffffff) } // NewSolicit creates a new SOLICIT message, using the given hardware address to // derive the IAID in the IA_NA option. func NewSolicit(hwaddr net.HardwareAddr, modifiers ...Modifier) (*Message, error) { duid := Duid{ Type: DUID_LLT, HwType: iana.HWTypeEthernet, Time: GetTime(), LinkLayerAddr: hwaddr, } m, err := NewMessage() if err != nil { return nil, err } m.MessageType = MessageTypeSolicit m.AddOption(OptClientID(duid)) oro := new(OptRequestedOption) oro.SetRequestedOptions([]OptionCode{ OptionDNSRecursiveNameServer, OptionDomainSearchList, }) m.AddOption(oro) m.AddOption(&OptElapsedTime{}) if len(hwaddr) < 4 { return nil, errors.New("short hardware addrss: less than 4 bytes") } l := len(hwaddr) var iaid [4]byte copy(iaid[:], hwaddr[l-4:l]) modifiers = append([]Modifier{WithIAID(iaid)}, modifiers...) // Apply modifiers for _, mod := range modifiers { mod(m) } return m, nil } // NewAdvertiseFromSolicit creates a new ADVERTISE packet based on an SOLICIT packet. func NewAdvertiseFromSolicit(sol *Message, modifiers ...Modifier) (*Message, error) { if sol == nil { return nil, errors.New("SOLICIT cannot be nil") } if sol.Type() != MessageTypeSolicit { return nil, errors.New("The passed SOLICIT must have SOLICIT type set") } // build ADVERTISE from SOLICIT adv := &Message{ MessageType: MessageTypeAdvertise, TransactionID: sol.TransactionID, } // add Client ID cid := sol.GetOneOption(OptionClientID) if cid == nil { return nil, errors.New("Client ID cannot be nil in SOLICIT when building ADVERTISE") } adv.AddOption(cid) // apply modifiers for _, mod := range modifiers { mod(adv) } return adv, nil } // NewRequestFromAdvertise creates a new REQUEST packet based on an ADVERTISE // packet options. func NewRequestFromAdvertise(adv *Message, modifiers ...Modifier) (*Message, error) { if adv == nil { return nil, errors.New("ADVERTISE cannot be nil") } if adv.MessageType != MessageTypeAdvertise { return nil, fmt.Errorf("The passed ADVERTISE must have ADVERTISE type set") } // build REQUEST from ADVERTISE req := &Message{ MessageType: MessageTypeRequest, TransactionID: adv.TransactionID, } // add Client ID cid := adv.GetOneOption(OptionClientID) if cid == nil { return nil, fmt.Errorf("Client ID cannot be nil in ADVERTISE when building REQUEST") } req.AddOption(cid) // add Server ID sid := adv.GetOneOption(OptionServerID) if sid == nil { return nil, fmt.Errorf("Server ID cannot be nil in ADVERTISE when building REQUEST") } req.AddOption(sid) // add Elapsed Time req.AddOption(&OptElapsedTime{}) // add IA_NA iana := adv.Options.OneIANA() if iana == nil { return nil, fmt.Errorf("IA_NA cannot be nil in ADVERTISE when building REQUEST") } req.AddOption(iana) // add OptRequestedOption oro := OptRequestedOption{} oro.SetRequestedOptions([]OptionCode{ OptionDNSRecursiveNameServer, OptionDomainSearchList, }) req.AddOption(&oro) // add OPTION_VENDOR_CLASS, only if present in the original request // TODO implement OptionVendorClass vClass := adv.GetOneOption(OptionVendorClass) if vClass != nil { req.AddOption(vClass) } // apply modifiers for _, mod := range modifiers { mod(req) } return req, nil } // NewReplyFromMessage creates a new REPLY packet based on a // Message. The function is to be used when generating a reply to a SOLICIT with // rapid-commit, REQUEST, CONFIRM, RENEW, REBIND, RELEASE and INFORMATION-REQUEST // packets. func NewReplyFromMessage(msg *Message, modifiers ...Modifier) (*Message, error) { if msg == nil { return nil, errors.New("message cannot be nil") } switch msg.Type() { case MessageTypeSolicit: if msg.GetOneOption(OptionRapidCommit) == nil { return nil, errors.New("cannot create REPLY from a SOLICIT without rapid-commit option") } modifiers = append([]Modifier{WithRapidCommit}, modifiers...) case MessageTypeRequest, MessageTypeConfirm, MessageTypeRenew, MessageTypeRebind, MessageTypeRelease, MessageTypeInformationRequest: default: return nil, errors.New("cannot create REPLY from the passed message type set") } // build REPLY from MESSAGE rep := &Message{ MessageType: MessageTypeReply, TransactionID: msg.TransactionID, } // add Client ID cid := msg.GetOneOption(OptionClientID) if cid == nil { return nil, errors.New("Client ID cannot be nil when building REPLY") } rep.AddOption(cid) // apply modifiers for _, mod := range modifiers { mod(rep) } return rep, nil } // Type returns this message's message type. func (m Message) Type() MessageType { return m.MessageType } // GetInnerMessage returns the message itself. func (m *Message) GetInnerMessage() (*Message, error) { return m, nil } // AddOption adds an option to this message. func (m *Message) AddOption(option Option) { m.Options.Add(option) } // UpdateOption updates the existing options with the passed option, adding it // at the end if not present already func (m *Message) UpdateOption(option Option) { m.Options.Update(option) } // IsNetboot returns true if the machine is trying to netboot. It checks if // "boot file" is one of the requested options, which is useful for // SOLICIT/REQUEST packet types, it also checks if the "boot file" option is // included in the packet, which is useful for ADVERTISE/REPLY packet. func (m *Message) IsNetboot() bool { if m.IsOptionRequested(OptionBootfileURL) { return true } if optbf := m.GetOneOption(OptionBootfileURL); optbf != nil { return true } return false } // IsOptionRequested takes an OptionCode and returns true if that option is // within the requested options of the DHCPv6 message. func (m *Message) IsOptionRequested(requested OptionCode) bool { for _, optoro := range m.GetOption(OptionORO) { for _, o := range optoro.(*OptRequestedOption).RequestedOptions() { if o == requested { return true } } } return false } // String returns a short human-readable string for this message. func (m *Message) String() string { return fmt.Sprintf("Message(messageType=%s transactionID=%s, %d options)", m.MessageType, m.TransactionID, len(m.Options.Options)) } // Summary prints all options associated with this message. func (m *Message) Summary() string { ret := fmt.Sprintf( "Message\n"+ " messageType=%s\n"+ " transactionid=%s\n", m.MessageType, m.TransactionID, ) ret += " options=[" if len(m.Options.Options) > 0 { ret += "\n" } for _, opt := range m.Options.Options { ret += fmt.Sprintf(" %v\n", opt.String()) } ret += " ]\n" return ret } // ToBytes returns the serialized version of this message as defined by RFC // 3315, Section 5. func (m *Message) ToBytes() []byte { buf := uio.NewBigEndianBuffer(nil) buf.Write8(uint8(m.MessageType)) buf.WriteBytes(m.TransactionID[:]) buf.WriteBytes(m.Options.ToBytes()) return buf.Data() } // GetOption returns the options associated with the code. func (m *Message) GetOption(code OptionCode) []Option { return m.Options.Get(code) } // GetOneOption returns the first associated option with the code from this // message. func (m *Message) GetOneOption(code OptionCode) Option { return m.Options.GetOne(code) } // IsRelay returns whether this is a relay message or not. func (m *Message) IsRelay() bool { return false }