summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4/client.go
diff options
context:
space:
mode:
authorAndrea Barberio <insomniac@slackware.it>2017-11-29 21:25:48 +0000
committerAndrea Barberio <insomniac@slackware.it>2017-12-05 23:32:27 +0000
commit1403bbe04ce275148b601c32e9551b2281110347 (patch)
tree6eacd224c2ea6b6e3865cb774970726809363d46 /dhcpv4/client.go
Initial commit
Diffstat (limited to 'dhcpv4/client.go')
-rw-r--r--dhcpv4/client.go146
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
+}