package dhcp4client import ( "encoding/binary" "math/rand" "net" "time" "golang.org/x/sys/unix" ) const ( minIPHdrLen = 20 maxIPHdrLen = 60 udpHdrLen = 8 ip4Ver = 0x40 ttl = 16 srcPort = 68 dstPort = 67 ) var ( bcastMAC = []byte{255, 255, 255, 255, 255, 255} ) // abstracts AF_PACKET type packetSock struct { fd int ifindex int } func NewPacketSock(ifindex int) (*packetSock, error) { fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_DGRAM, int(swap16(unix.ETH_P_IP))) if err != nil { return nil, err } addr := unix.SockaddrLinklayer{ Ifindex: ifindex, Protocol: swap16(unix.ETH_P_IP), } if err = unix.Bind(fd, &addr); err != nil { return nil, err } return &packetSock{ fd: fd, ifindex: ifindex, }, nil } func (pc *packetSock) Close() error { return unix.Close(pc.fd) } func (pc *packetSock) Write(packet []byte) error { lladdr := unix.SockaddrLinklayer{ Ifindex: pc.ifindex, Protocol: swap16(unix.ETH_P_IP), Halen: uint8(len(bcastMAC)), } copy(lladdr.Addr[:], bcastMAC) pkt := make([]byte, minIPHdrLen+udpHdrLen+len(packet)) fillIPHdr(pkt[0:minIPHdrLen], udpHdrLen+uint16(len(packet))) fillUDPHdr(pkt[minIPHdrLen:minIPHdrLen+udpHdrLen], uint16(len(packet))) // payload copy(pkt[minIPHdrLen+udpHdrLen:len(pkt)], packet) return unix.Sendto(pc.fd, pkt, 0, &lladdr) } func (pc *packetSock) ReadFrom() ([]byte, net.IP, error) { pkt := make([]byte, maxIPHdrLen+udpHdrLen+MaxDHCPLen) n, _, err := unix.Recvfrom(pc.fd, pkt, 0) if err != nil { return nil, nil, err } // IP hdr len ihl := int(pkt[0]&0x0F) * 4 // Source IP address src := net.IP(pkt[12:16]) return pkt[ihl+udpHdrLen : n], src, nil } func (pc *packetSock) SetReadTimeout(t time.Duration) error { tv := unix.NsecToTimeval(t.Nanoseconds()) return unix.SetsockoptTimeval(pc.fd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &tv) } // compute's 1's complement checksum func chksum(p []byte, csum []byte) { cklen := len(p) s := uint32(0) for i := 0; i < (cklen - 1); i += 2 { s += uint32(p[i+1])<<8 | uint32(p[i]) } if cklen&1 == 1 { s += uint32(p[cklen-1]) } s = (s >> 16) + (s & 0xffff) s = s + (s >> 16) s = ^s csum[0] = uint8(s & 0xff) csum[1] = uint8(s >> 8) } func fillIPHdr(hdr []byte, payloadLen uint16) { // version + IHL hdr[0] = ip4Ver | (minIPHdrLen / 4) // total length binary.BigEndian.PutUint16(hdr[2:4], uint16(len(hdr))+payloadLen) // identification if _, err := rand.Read(hdr[4:5]); err != nil { panic(err) } // TTL hdr[8] = 16 // Protocol hdr[9] = unix.IPPROTO_UDP // dst IP copy(hdr[16:20], net.IPv4bcast.To4()) // compute IP hdr checksum chksum(hdr[0:len(hdr)], hdr[10:12]) } func fillUDPHdr(hdr []byte, payloadLen uint16) { // src port binary.BigEndian.PutUint16(hdr[0:2], srcPort) // dest port binary.BigEndian.PutUint16(hdr[2:4], dstPort) // length binary.BigEndian.PutUint16(hdr[4:6], udpHdrLen+payloadLen) } func swap16(x uint16) uint16 { var b [2]byte binary.BigEndian.PutUint16(b[:], x) return binary.LittleEndian.Uint16(b[:]) }