package haza import "errors" import "fmt" import "io" import "net" type Dhcp4Op = uint8 type Dhcp4Htype = uint8 const ( _ Dhcp4Op = iota DHCP4_OP_BOOTREQUEST = 1 DHCP4_OP_BOOTREPLY = 2 ) const ( _ Dhcp4Htype = iota DHCP4_HTYPE_ETHER = 1 DHCP4_HTYPE_IEEE802 = 6 DHCP4_HTYPE_ARCNET = 7 DHCP4_HTYPE_APPLETALK = 8 DHCP4_HTYPE_HDLC = 17 DHCP4_HTYPE_ATM = 19 DHCP4_HTYPE_ARPSEC = 30 DHCP4_HTYPE_IPSEC = 31 DHCP4_HTYPE_INFINIBAND = 32 DHCP4_HTYPE_PUREIP = 35 ) type Dhcp4Pkt struct { Op Dhcp4Op Htype Dhcp4Htype Hlen uint8 // length of Chaddr Hops uint8 Xid uint32 Secs uint16 Flags uint16 Ciaddr net.IP // uint32 Yiaddr net.IP // uint32 Siaddr net.IP // uint32 Gwaddr net.IP // uint32 Chaddr [16]byte Sname [64]byte // server host name File [128]byte // boot file name // options are placed after the header. // the first four bytes of the options compose a magic cookie // 0x63 0x82 0x53 0x63 Options map[Dhcp4OptCode][]byte // internal fields msg_type Dhcp4MsgType } var dhcp4_magic_cookie [4]byte = [4]byte{ 0x63, 0x82, 0x53, 0x63 } func NewDhcp4Pkt(msg_type Dhcp4MsgType) *Dhcp4Pkt { var pkt *Dhcp4Pkt var opt Dhcp4Opt pkt = &Dhcp4Pkt{ Op: DHCP4_OP_BOOTREQUEST, // TODO: choose between request and reply based on the msg type.. Htype: DHCP4_HTYPE_ETHER, Hlen: 6, Xid: 0, // TODO: generate it? Ciaddr: net.IPv4zero, Yiaddr: net.IPv4zero, Siaddr: net.IPv4zero, Gwaddr: net.IPv4zero, //Chaddr: //Sname: //File: // fill the internal fields msg_type: msg_type, } // add the message type option opt = Dhcp4OptMsgType(msg_type) pkt.Options[opt.Code()] = opt.Bytes() return pkt } func (pkt *Dhcp4Pkt) AddOption(opt Dhcp4Opt) { pkt.Options[opt.Code()] = opt.Bytes(); } func (pkt *Dhcp4Pkt) Decode(b []byte) error { // fill the packet with data from the bytes var nbr *NetByteReader var u8 byte var p Dhcp4Pkt var cookie [4]byte var ov []byte var ok bool var err error nbr = NewNetByteReader(b) u8, err = nbr.ReadByte() if err != nil { return err } p.Op = Dhcp4Op(u8) u8, err = nbr.ReadByte() if err != nil { return err } p.Htype = Dhcp4Htype(u8) p.Hlen, err = nbr.ReadByte() if err != nil { return err } if p.Hlen > 16 { return fmt.Errorf("invalid hlen %d", p.Hlen) } p.Hops, err = nbr.ReadByte() if err != nil { return err } p.Xid, err = nbr.ReadUint32() if err != nil { return err } p.Secs, err = nbr.ReadUint16() if err != nil { return err } p.Flags, err = nbr.ReadUint16() if err != nil { return err } p.Ciaddr, err = nbr.ReadIp4() if err != nil { return err } p.Yiaddr, err = nbr.ReadIp4() if err != nil { return err } p.Siaddr, err = nbr.ReadIp4() if err != nil { return err } p.Gwaddr, err = nbr.ReadIp4() if err != nil { return err } err = nbr.ReadBytesFull(p.Chaddr[:]) if err != nil { return err } err = nbr.ReadBytesFull(p.Sname[:]) if err != nil { return err } err = nbr.ReadBytesFull(p.File[:]) if err != nil { return err } err = nbr.ReadBytesFull(cookie[:]) if err != nil { if errors.Is(err, io.EOF) { // TODO: no options } return err } if cookie != dhcp4_magic_cookie { return fmt.Errorf("invalid magic cookie %v", cookie) } // load options p.Options = make(map[Dhcp4OptCode][]byte) for { var oc uint8 var ol uint8 var optval [255]byte oc, err = nbr.ReadByte() // option code if err != nil { if errors.Is(err, io.EOF) { break } return err } if oc == DHCP4_OPT_PADDING { continue } if oc == DHCP4_OPT_END { break } ol, err = nbr.ReadByte() // option length if err != nil { return err } err = nbr.ReadBytesAtLeast(optval[:], int(ol)) if err != nil { return err } // RFC 3396 mandates that when multiple occurrences of a concatenation-requiring // option appear in a DHCPv4 packet, their data MUST be concatenated prior to // processing. For non-concatenation-requiring (e.g. fixed-length) options, concatenation // is not defined. but let's not make an exception and let the processor check // validity. p.Options[oc] = append(p.Options[oc], optval[:ol]...) } //for oc, ov = range p.Optons { // ot = oc_to_type(oc) // switch (ot) { // case OT_UINT8: // case OT_UINT16: // case OT_UINT18: // case OT_UINT32: // case OT_IP4ADDR: // case OT_IP46DDR: // case OT_STRING: // default: // leave it as is // } // //} ov, ok = p.Options[DHCP4_OPT_MSG_TYPE] if !ok { return fmt.Errorf("no message type") } if len(ov) != 1 { return fmt.Errorf("invalid message type option") } p.msg_type = ov[0] *pkt = p return nil } func (pkt *Dhcp4Pkt) Encode() []byte { return nil } func (pkt *Dhcp4Pkt) MsgType() Dhcp4MsgType { return pkt.msg_type }