From 6ce8f1a5fe09ca3ef35a5adf66c8dd63a323fb87 Mon Sep 17 00:00:00 2001 From: hyung-hwan Date: Fri, 26 Sep 2025 19:58:22 +0900 Subject: [PATCH] added some option handling functions --- Makefile | 1 + haza.go | 22 ++++-- opt.go | 225 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ pkt.go | 208 +++++++++++++++++++++++++++++++++---------------- server.go | 101 +++++++++++++++++++++--- 5 files changed, 473 insertions(+), 84 deletions(-) create mode 100644 opt.go diff --git a/Makefile b/Makefile index 47efb8c..23e7702 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ VERSION=1.0.0 SRCS=\ haza.go \ + opt.go \ pkt.go \ server.go \ sock.go diff --git a/haza.go b/haza.go index 86c6009..c1fd1a7 100644 --- a/haza.go +++ b/haza.go @@ -81,19 +81,19 @@ func Ntoh32(v uint32) uint32 { } // --------------------------------------------------------- -type ByteReader struct { +type NetByteReader struct { r *bytes.Reader } -func NewByteReader(b []byte) *ByteReader { - return &ByteReader{ r: bytes.NewReader(b) } +func NewNetByteReader(b []byte) *NetByteReader { + return &NetByteReader{ r: bytes.NewReader(b) } } -func (br* ByteReader) ReadByte() (byte, error) { +func (br* NetByteReader) ReadByte() (byte, error) { return br.r.ReadByte() } -func (br* ByteReader) ReadUint16() (uint16, error) { +func (br* NetByteReader) ReadUint16() (uint16, error) { var v uint16 var err error err = binary.Read(br.r, binary.BigEndian, &v) @@ -101,7 +101,7 @@ func (br* ByteReader) ReadUint16() (uint16, error) { return v, nil } -func (br* ByteReader) ReadUint32() (uint32, error) { +func (br* NetByteReader) ReadUint32() (uint32, error) { var v uint32 var err error err = binary.Read(br.r, binary.BigEndian, &v) @@ -109,7 +109,7 @@ func (br* ByteReader) ReadUint32() (uint32, error) { return v, nil } -func (br* ByteReader) ReadIp4() (net.IP, error) { +func (br* NetByteReader) ReadIp4() (net.IP, error) { var buf [4]byte var err error _, err = io.ReadFull(br.r, buf[:]) @@ -117,8 +117,14 @@ func (br* ByteReader) ReadIp4() (net.IP, error) { return net.IP(buf[:]), nil } -func (br *ByteReader) ReadAllBytes(buf []byte) error { +func (br *NetByteReader) ReadBytesFull(buf []byte) error { var err error _, err = io.ReadFull(br.r, buf) return err } + +func (br *NetByteReader) ReadBytesAtLeast(buf []byte, len int) error { + var err error + _, err = io.ReadAtLeast(br.r, buf, len) + return err +} diff --git a/opt.go b/opt.go new file mode 100644 index 0000000..e815a91 --- /dev/null +++ b/opt.go @@ -0,0 +1,225 @@ +package haza + +import "encoding/binary" +import "net" + +type Dhcp4OptCode = uint8 +type Dhcp4MsgType = uint8 + +const ( + _ Dhcp4MsgType = iota + + DHCP4_MSG_DISCOVER = 1 + DHCP4_MSG_OFFER = 2 + DHCP4_MSG_REQUEST = 3 + DHCP4_MSG_DECLINE = 4 + DHCP4_MSG_ACK = 5 + DHCP4_MSG_NAK = 6 + DHCP4_MSG_RELEASE = 7 + DHCP4_MSG_INFORM = 8 + + DHCP4_MSG_FORCE_RENEW = 9 + + DHCP4_MSG_LEASE_QUERY = 10 + DHCP4_MSG_LEASE_UNASSIGNED = 11 + DHCP4_MSG_LEASE_UNKNOWN = 12 + DHCP4_MSG_LEASE_ACTIVE = 13 + + DHCP4_MSG_BULK_LEASE_QUERY = 14 + DHCP4_MSG_LEASE_QUERY_DONE = 15 + DHCP4_MSG_ACTIVE_LEASE_QUERY = 16 + DHCP4_MSG_LEASE_QUERY_STATUS = 17 + DHCP4_MSG_TLS = 18 +) + +const ( + DHCP4_OPT_PADDING Dhcp4OptCode = iota // 0 + DHCP4_OPT_SUBNET_MASK = 0x01 + DHCP4_OPT_TIME_OFFSET = 0x02 + DHCP4_OPT_ROUTER = 0x03 + DHCP4_OPT_TIME_SERVER = 0x04 + DHCP4_OPT_NAME_SERVER = 0x05 + DHCP4_OPT_DNS_SERVER = 0x06 + DHCP4_OPT_LOG_SERVER = 0x07 + DHCP4_OPT_QUOTE_SERVER = 0x08 + DHCP4_OPT_LPR_SERVER = 0x09 + + DHCP4_OPT_HOST_NAME = 0x0C + DHCP4_OPT_VENDOR_SPECIFIC = 0x2B + DHCP4_OPT_REQUESTED_IPADDR = 0x32 + DHCP4_OPT_LEASE_TIME = 0x33 + DHCP4_OPT_OVERLOAD = 0x34 + DHCP4_OPT_MSG_TYPE = 0x35 + DHCP4_OPT_SERVER_ID = 0x36 + DHCP4_OPT_PARAM_REQ_LIST = 0x37 + DHCP4_OPT_ERROR_TEXT = 0x38 + DHCP4_OPT_MAX_MSG_SIZE = 0x39 + DHCP4_OPT_RENEWAL_TIME = 0x3A // T1 + DHCP4_OPT_REBINDING_TIME = 0x3B // T2 + DHCP4_OPT_CLIENT_ID = 0x3D + DHCP4_OPT_END = 0xFF +) + +const ( + DHCP4_OPT_VAL_UINT8 = 0 +) + +type Dhcp4Opt interface { + Code() Dhcp4OptCode + Bytes() []byte +} + +type Dhcp4OptBase struct { + code Dhcp4OptCode +} + +type Dhcp4OptIp4Addr struct { + Dhcp4OptBase + value net.IP +} + +type Dhcp4OptIp4Addrs struct { + Dhcp4OptBase + value []net.IP +} + +type Dhcp4OptString struct { + Dhcp4OptBase + value string +} + +type Dhcp4OptBytes struct { + Dhcp4OptBase + value []byte +} + +type Dhcp4OptUint32 struct { + Dhcp4OptBase + value uint32 +} + +type Dhcp4OptUint16 struct { + Dhcp4OptBase + value uint16 +} + +type Dhcp4OptUint8 struct { + Dhcp4OptBase + value uint8 +} + +func (opt *Dhcp4OptBase) Code() Dhcp4OptCode { + return opt.code +} + +func (opt *Dhcp4OptIp4Addr) Bytes() []byte { + // net.IP itself is []byte + var buf []byte + buf = opt.value.To4() + if buf == nil { return []byte{} } + return buf +} + +func (opt *Dhcp4OptIp4Addrs) Bytes() []byte { + var buf []byte + var v net.IP + var v4 []byte + var count int + + count = 0 + for _, ip := range opt.value { + if ip.To4() != nil { count++ } + } + + buf = make([]byte, 0, count * net.IPv4len) + for _, v = range opt.value { + v4 = v.To4() + if v4 == nil { continue } + buf = append(buf, v4...) + } + return buf +} + +func (opt *Dhcp4OptString) Bytes() []byte { + return []byte(opt.value) +} + +func (opt *Dhcp4OptBytes) Bytes() []byte { + return opt.value +} + +func (opt *Dhcp4OptUint32) Bytes() []byte { + var buf [4]byte + binary.BigEndian.PutUint32(buf[:], opt.value) + return buf[:] +} + +func (opt *Dhcp4OptUint16) Bytes() []byte { + var buf [2]byte + binary.BigEndian.PutUint16(buf[:], opt.value) + return buf[:] +} + +func (opt *Dhcp4OptUint8) Bytes() []byte { + return []byte{opt.value} +} + +func Dhcp4OptMsgType(v uint8) Dhcp4Opt { + return &Dhcp4OptUint8{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_MSG_TYPE}, value: v} +} + +func Dhcp4OptSubnetMask(v net.IP) Dhcp4Opt { + return &Dhcp4OptIp4Addr{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_SUBNET_MASK}, value: v} +} + +func Dhcp4OptRouter(v net.IP) Dhcp4Opt { + return &Dhcp4OptIp4Addr{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_ROUTER}, value: v} +} + +func Dhcp4OptTimeServers(v []net.IP) Dhcp4Opt { + return &Dhcp4OptIp4Addrs{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_TIME_SERVER}, value: v} +} + +func Dhcp4OptDnsServers(v []net.IP) Dhcp4Opt { + return &Dhcp4OptIp4Addrs{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_DNS_SERVER}, value: v} +} + +func Dhcp4OptHostName(v string) Dhcp4Opt { + return &Dhcp4OptString{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_DNS_SERVER}, value: v} +} + +func Dhcp4OptReqIpaddr(v net.IP) Dhcp4Opt { + return &Dhcp4OptIp4Addr{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_REQUESTED_IPADDR}, value: v} +} + +func Dhcp4OptLeaseTime(v uint32) Dhcp4Opt { + return &Dhcp4OptUint32{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_LEASE_TIME}, value: v} +} + +func Dhcp4OptServerId(v net.IP) Dhcp4Opt { + return &Dhcp4OptIp4Addr{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_SERVER_ID}, value: v} +} + +func Dhcp4OptParamReqList(v []Dhcp4OptCode) Dhcp4Opt { + return &Dhcp4OptBytes{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_PARAM_REQ_LIST}, value: v} +} + +func Dhcp4OptErrorText(v string) Dhcp4Opt { + return &Dhcp4OptString{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_ERROR_TEXT}, value: v} +} + +func Dhcp4OptMaxMsgSize(v uint16) Dhcp4Opt { + return &Dhcp4OptUint16{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_MAX_MSG_SIZE}, value: v} +} + +func Dhcp4OptRenewalTime(v uint32) Dhcp4Opt { + return &Dhcp4OptUint32{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_RENEWAL_TIME}, value: v} +} + +func Dhcp4OptRebindingTime(v uint32) Dhcp4Opt { + return &Dhcp4OptUint32{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_REBINDING_TIME}, value: v} +} + +func Dhcp4OptClientId(v string) Dhcp4Opt { + return &Dhcp4OptString{Dhcp4OptBase: Dhcp4OptBase{code: DHCP4_OPT_CLIENT_ID}, value: v} +} diff --git a/pkt.go b/pkt.go index 34b603d..2ee72db 100644 --- a/pkt.go +++ b/pkt.go @@ -1,10 +1,12 @@ package haza +import "errors" +import "fmt" +import "io" import "net" type Dhcp4Op = uint8 type Dhcp4Htype = uint8 -type Dhcp4Msg = uint8 const ( _ Dhcp4Op = iota @@ -26,32 +28,6 @@ const ( DHCP4_HTYPE_PUREIP = 35 ) -const ( - _ Dhcp4Msg = iota - - DHCP4_MSG_DISCOVER = 1 - DHCP4_MSG_OFFER = 2 - DHCP4_MSG_REQUEST = 3 - DHCP4_MSG_DECLINE = 4 - DHCP4_MSG_ACK = 5 - DHCP4_MSG_NAK = 6 - DHCP4_MSG_RELEASE = 7 - DHCP4_MSG_INFORM = 8 - - DHCP4_MSG_FORCE_RENEW = 9 - - DHCP4_MSG_LEASE_QUERY = 10 - DHCP4_MSG_LEASE_UNASSIGNED = 11 - DHCP4_MSG_LEASE_UNKNOWN = 12 - DHCP4_MSG_LEASE_ACTIVE = 13 - - DHCP4_MSG_BULK_LEASE_QUERY = 14 - DHCP4_MSG_LEASE_QUERY_DONE = 15 - DHCP4_MSG_ACTIVE_LEASE_QUERY = 16 - DHCP4_MSG_LEASE_QUERY_STATUS = 17 - DHCP4_MSG_TLS = 18 -) - type Dhcp4Pkt struct { Op Dhcp4Op Htype Dhcp4Htype @@ -72,72 +48,170 @@ type Dhcp4Pkt struct { 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 + // 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 r *ByteReader + var nbr *NetByteReader var u8 byte - var u16 uint16 - var u32 uint32 var p Dhcp4Pkt + var cookie [4]byte + var ov []byte + var ok bool var err error - r = NewByteReader(b) - - u8, err = r.ReadByte() + nbr = NewNetByteReader(b) + + u8, err = nbr.ReadByte() if err != nil { return err } p.Op = Dhcp4Op(u8) - u8, err = r.ReadByte() + u8, err = nbr.ReadByte() if err != nil { return err } p.Htype = Dhcp4Htype(u8) - u8, err = r.ReadByte() + p.Hlen, err = nbr.ReadByte() if err != nil { return err } - p.Hlen = u8 + if p.Hlen > 16 { return fmt.Errorf("invalid hlen %d", p.Hlen) } - u8, err = r.ReadByte() - if err != nil { return err } - p.Hops = u8 - - u32, err = r.ReadUint32() - if err != nil { return err } - p.Xid = u32 - - u16, err = r.ReadUint16() - if err != nil { return err } - p.Secs = u16 - - u16, err = r.ReadUint16() - if err != nil { return err } - p.Flags = u16 - - p.Ciaddr, err = r.ReadIp4() + p.Hops, err = nbr.ReadByte() if err != nil { return err } - p.Yiaddr, err = r.ReadIp4() + p.Xid, err = nbr.ReadUint32() if err != nil { return err } - p.Siaddr, err = r.ReadIp4() + p.Secs, err = nbr.ReadUint16() if err != nil { return err } - p.Gwaddr, err = r.ReadIp4() + p.Flags, err = nbr.ReadUint16() if err != nil { return err } - err = r.ReadAllBytes(p.Chaddr[:]) + p.Ciaddr, err = nbr.ReadIp4() if err != nil { return err } - err = r.ReadAllBytes(p.Sname[:]) + p.Yiaddr, err = nbr.ReadIp4() if err != nil { return err } - - err = r.ReadAllBytes(p.File[:]) + + p.Siaddr, err = nbr.ReadIp4() if err != nil { return err } - -// magic -// options.. + + 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 @@ -146,3 +220,7 @@ func (pkt *Dhcp4Pkt) Decode(b []byte) error { func (pkt *Dhcp4Pkt) Encode() []byte { return nil } + +func (pkt *Dhcp4Pkt) MsgType() Dhcp4MsgType { + return pkt.msg_type +} diff --git a/server.go b/server.go index 115569f..6d4005f 100644 --- a/server.go +++ b/server.go @@ -31,6 +31,7 @@ type DhcpServer struct { p int // epoll efd int // eventfd + pkt_chan chan[]byte log Logger } @@ -104,6 +105,7 @@ func NewDhcpServer(ctx context.Context, name string, logger Logger) (*DhcpServer s.p = p s.conns = make(map[string]DhcpConn, 0) s.conns_by_fd = make(map[int]DhcpConn, 0) + s.pkt_chan = make(chan[]byte, 256) runtime.SetFinalizer(s, finalize_dhcp_server) return s, nil @@ -154,9 +156,9 @@ func (s *DhcpServer) RunTask(wg *sync.WaitGroup) { go s.run_handlers(&l_wg) } */ -fmt.Printf ("beginning of recv loop\n") + s.log.Write("", LOG_INFO, "Receiver loop started") s.run_recv_loop() -fmt.Printf ("end of recv loop\n") + s.log.Write("", LOG_INFO, "Receiver loop ended") // l_wg.Wait() finalize_dhcp_server(s) @@ -168,6 +170,7 @@ func (s *DhcpServer) ReqStop() { // eventfd needs an 8-byte integer. v = 1 unix.Write(s.efd, (*[8]byte)(unsafe.Pointer(&v))[:]) + s.CtxCancel() } } @@ -256,13 +259,92 @@ func get_offsets(frame []byte) (int, int, int, int, int, error) { return eth_off, ip_off, udp_off, dhcp_off, dhcp_end, nil } +func (s *DhcpServer) dhcp4_discover(pkt *Dhcp4Pkt) { + fmt.Printf("discover...\n") + + +/* + ans = NewDhcp4Pkt( + Dhcp4OptMsgType(HDCP4_MSG_OFFER), + Dhcp4Ciaddr("192.168.2.3") + ) +*/ + + //Send(ans) +} + +func (s *DhcpServer) dhcp4_request(pkt *Dhcp4Pkt) { + fmt.Printf("request...\n") +} + +func (s *DhcpServer) dhcp4_release(pkt *Dhcp4Pkt) { + fmt.Printf("release...\n") +} + +func (s *DhcpServer) dhcp4_inform(pkt *Dhcp4Pkt) { + fmt.Printf("inform...\n") +} + +func (s *DhcpServer) dhcp4_lease_query(pkt *Dhcp4Pkt) { + fmt.Printf("lease query...\n") +} +// TODO:bulk lease query, etc + +func (s *DhcpServer) dhcp4_packet_proc(wg *sync.WaitGroup, id int) { + var buf []byte + var err error + + defer wg.Done() + + s.log.Write("", LOG_INFO, "dhcp4 packet processor(%d) started", id) + +proc_loop: + for { + select { + case <- s.Ctx.Done(): + break proc_loop + + case buf = <- s.pkt_chan: + var pkt Dhcp4Pkt + //TODO: ethernet part??? + err = pkt.Decode(buf) + if err != nil { continue } + + switch pkt.MsgType() { + case DHCP4_MSG_DISCOVER: + s.dhcp4_discover(&pkt) + case DHCP4_MSG_REQUEST: + s.dhcp4_request(&pkt) + case DHCP4_MSG_RELEASE: + s.dhcp4_release(&pkt) + case DHCP4_MSG_INFORM: + s.dhcp4_inform(&pkt) + case DHCP4_MSG_LEASE_QUERY: + s.dhcp4_lease_query(&pkt) + default: + // do nothing. ignore other types? + // TODO: logging? + } + //fmt.Printf("[%d] PKT: %+v\n", id, pkt) + } + } + + s.log.Write("", LOG_INFO, "dhcp4 packet processor(%d) ended", id) +} + func (s *DhcpServer) run_recv_loop() error { var buf [1500]byte var nevts int var i int var evts [128]unix.EpollEvent + var l_wg sync.WaitGroup var err error + for i = 0; i < 10; i++ { // how many go rootines would be the best? + l_wg.Add(1) + go s.dhcp4_packet_proc(&l_wg, i) + } + epoll_loop: for { nevts, err = unix.EpollWait(s.p, evts[:], -1) @@ -297,15 +379,11 @@ epoll_loop: if err != nil { fmt.Printf ("PACKET -> %s\n", err.Error()) } else { - //fmt.Printf (">>%d [%s]<<\n", epos - spos, string(buf[spos: epos])) - if string(buf[spos:epos]) == "quit\n" { - break epoll_loop - } - - - var pkt Dhcp4Pkt - pkt.Decode(buf[spos:epos]) - fmt.Printf("%+v\n", pkt) + //var pkt Dhcp4Pkt + //TODO: ethernet part??? + //pkt.Decode(buf[spos:epos]) + //fmt.Printf("%+v\n", pkt) + s.pkt_chan <- buf[spos:epos] } } @@ -319,5 +397,6 @@ epoll_loop: } // if there are subtasks wait here for termination and close the dockets + l_wg.Wait() return nil }