diff options
author | Andrea Barberio <insomniac@slackware.it> | 2017-11-29 21:25:48 +0000 |
---|---|---|
committer | Andrea Barberio <insomniac@slackware.it> | 2017-12-05 23:32:27 +0000 |
commit | 1403bbe04ce275148b601c32e9551b2281110347 (patch) | |
tree | 6eacd224c2ea6b6e3865cb774970726809363d46 /dhcpv4/client.go |
Initial commit
Diffstat (limited to 'dhcpv4/client.go')
-rw-r--r-- | dhcpv4/client.go | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/dhcpv4/client.go b/dhcpv4/client.go new file mode 100644 index 0000000..34724e4 --- /dev/null +++ b/dhcpv4/client.go @@ -0,0 +1,146 @@ +package dhcpv4 + +import ( + "encoding/binary" + "golang.org/x/net/ipv4" + "net" + "syscall" + "time" +) + +const ( + maxUDPReceivedPacketSize = 8192 // arbitrary size. Theoretically could be up to 65kb +) + +type Client struct { + Network string + Dialer *net.Dialer + Timeout time.Duration +} + +func makeRawBroadcastPacket(payload []byte) ([]byte, error) { + udp := make([]byte, 8) + binary.BigEndian.PutUint16(udp[:2], ClientPort) + binary.BigEndian.PutUint16(udp[2:4], ServerPort) + binary.BigEndian.PutUint16(udp[4:6], uint16(8+len(payload))) + binary.BigEndian.PutUint16(udp[6:8], 0) // try to offload the checksum + + h := ipv4.Header{ + Version: 4, + Len: 20, + TotalLen: 20 + len(udp) + len(payload), + TTL: 64, + Protocol: 17, // UDP + Dst: net.IPv4bcast, + Src: net.IPv4zero, + } + ret, err := h.Marshal() + if err != nil { + return nil, err + } + ret = append(ret, udp...) + ret = append(ret, payload...) + return ret, nil +} + +// Run a full DORA transaction: Discovery, Offer, Request, Acknowledge, over +// UDP. Does not retry in case of failures. +// Returns a list of DHCPv4 structures representing the exchange. It can contain +// up to four elements, ordered as Discovery, Offer, Request and Acknowledge. +// In case of errors, an error is returned, and the list of DHCPv4 objects will +// be shorted than 4, containing all the sent and received DHCPv4 messages. +func (c *Client) Exchange(d *DHCPv4, ifname string) ([]DHCPv4, error) { + conversation := make([]DHCPv4, 1) + var err error + + // Discovery + if d == nil { + d, err = NewDiscovery() + if err != nil { + return conversation, err + } + iface, err := net.InterfaceByName(ifname) + if err != nil { + return conversation, err + } + d.SetClientHwAddr(iface.HardwareAddr) + } + conversation[0] = *d + + fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW) + if err != nil { + return conversation, err + } + err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + if err != nil { + return conversation, err + } + err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_HDRINCL, 1) + if err != nil { + return conversation, err + } + err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1) + if err != nil { + return conversation, err + } + err = syscall.BindToDevice(fd, ifname) + if err != nil { + return conversation, err + } + + daddr := syscall.SockaddrInet4{Port: ClientPort, Addr: [4]byte{255, 255, 255, 255}} + packet, err := makeRawBroadcastPacket(d.ToBytes()) + if err != nil { + return conversation, err + } + err = syscall.Sendto(fd, packet, 0, &daddr) + if err != nil { + return conversation, err + } + + // Offer + conn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: ClientPort}) + if err != nil { + return conversation, err + } + defer conn.Close() + + buf := make([]byte, maxUDPReceivedPacketSize) + oobdata := []byte{} // ignoring oob data + n, _, _, _, err := conn.ReadMsgUDP(buf, oobdata) + offer, err := FromBytes(buf[:n]) + if err != nil { + return conversation, err + } + // TODO match the packet content + // TODO check that the peer address matches the declared server IP and port + conversation = append(conversation, *offer) + + // Request + request, err := RequestFromOffer(*offer) + if err != nil { + return conversation, err + } + conversation = append(conversation, *request) + packet, err = makeRawBroadcastPacket(request.ToBytes()) + if err != nil { + return conversation, err + } + err = syscall.Sendto(fd, packet, 0, &daddr) + if err != nil { + return conversation, err + } + + // Acknowledge + buf = make([]byte, maxUDPReceivedPacketSize) + n, _, _, _, err = conn.ReadMsgUDP(buf, oobdata) + acknowledge, err := FromBytes(buf[:n]) + if err != nil { + return conversation, err + } + // TODO match the packet content + // TODO check that the peer address matches the declared server IP and port + conversation = append(conversation, *acknowledge) + + return conversation, nil +} |