diff options
author | Christopher Koch <chrisko@google.com> | 2019-03-11 22:01:27 -0700 |
---|---|---|
committer | Chris K <c@chrisko.ch> | 2019-03-27 16:57:28 -0700 |
commit | 3a7c900b28b944b07cf6606a787f8223947fcd0e (patch) | |
tree | 53dd43b5f9bc2eaf8b2b2fd3be1c7389a42b193a /dhcpv4/nclient4/conn_linux.go | |
parent | f83bae4fe73f44baf884f75775679f0fd7d80026 (diff) |
client4: add a new DHCPv4 client.
- Able to send UDP packets before interface is configured.
- Able to use any net.PacketConn.
- RFC2131-compliant retransmission logic.
- Tests.
- Race-condition-averse.
Previous clients (both mine and the ones here) are prone to race
condition errors.
Having one and only one place that calls receive on the socket
"continuously" without having to coordinate hand-offs makes the logic
way easier to follow, and allows for multiple requests in flux at a
time.
Signed-off-by: Christopher Koch <chrisko@google.com>
Diffstat (limited to 'dhcpv4/nclient4/conn_linux.go')
-rw-r--r-- | dhcpv4/nclient4/conn_linux.go | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/dhcpv4/nclient4/conn_linux.go b/dhcpv4/nclient4/conn_linux.go new file mode 100644 index 0000000..00c8a32 --- /dev/null +++ b/dhcpv4/nclient4/conn_linux.go @@ -0,0 +1,173 @@ +// Copyright 2018 the u-root Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.12 + +package nclient4 + +import ( + "fmt" + "io" + "net" + "os" + + "github.com/insomniacslk/dhcp/dhcpv4" + "github.com/mdlayher/ethernet" + "github.com/mdlayher/raw" + "github.com/u-root/u-root/pkg/uio" + "golang.org/x/sys/unix" +) + +var ( + // BroadcastMac is the broadcast MAC address. + // + // Any UDP packet sent to this address is broadcast on the subnet. + BroadcastMac = net.HardwareAddr([]byte{255, 255, 255, 255, 255, 255}) +) + +// NewIPv4UDPConn returns a UDP connection bound to both the interface and port +// given based on a IPv4 DGRAM socket. The UDP connection allows broadcasting. +// +// The interface must already be configured. +func NewIPv4UDPConn(iface string, port int) (net.PacketConn, error) { + fd, err := unix.Socket(unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_UDP) + if err != nil { + return nil, fmt.Errorf("cannot get a UDP socket: %v", err) + } + f := os.NewFile(uintptr(fd), "") + // net.FilePacketConn dups the FD, so we have to close this in any case. + defer f.Close() + + // Allow broadcasting. + if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_BROADCAST, 1); err != nil { + return nil, fmt.Errorf("cannot set broadcasting on socket: %v", err) + } + // Allow reusing the addr to aid debugging. + if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_REUSEADDR, 1); err != nil { + return nil, fmt.Errorf("cannot set reuseaddr on socket: %v", err) + } + if len(iface) != 0 { + // Bind directly to the interface. + if err := dhcpv4.BindToInterface(fd, iface); err != nil { + return nil, fmt.Errorf("cannot bind to interface %s: %v", iface, err) + } + } + // Bind to the port. + if err := unix.Bind(fd, &unix.SockaddrInet4{Port: port}); err != nil { + return nil, fmt.Errorf("cannot bind to port %d: %v", port, err) + } + + return net.FilePacketConn(f) +} + +// NewRawUDPConn returns a UDP connection bound to the interface and port +// given based on a raw packet socket. All packets are broadcasted. +// +// The interface can be completely unconfigured. +func NewRawUDPConn(iface string, port int) (net.PacketConn, error) { + ifc, err := net.InterfaceByName(iface) + if err != nil { + return nil, err + } + rawConn, err := raw.ListenPacket(ifc, uint16(ethernet.EtherTypeIPv4), &raw.Config{LinuxSockDGRAM: true}) + if err != nil { + return nil, err + } + return NewBroadcastUDPConn(rawConn, &net.UDPAddr{Port: port}), nil +} + +// BroadcastRawUDPConn uses a raw socket to send UDP packets to the broadcast +// MAC address. +type BroadcastRawUDPConn struct { + // PacketConn is a raw DGRAM socket. + net.PacketConn + + // boundAddr is the address this RawUDPConn is "bound" to. + // + // Calls to ReadFrom will only return packets destined to this address. + boundAddr *net.UDPAddr +} + +// NewBroadcastUDPConn returns a PacketConn that marshals and unmarshals UDP +// packets, sending them to the broadcast MAC at on rawPacketConn. +// +// Calls to ReadFrom will only return packets destined to boundAddr. +func NewBroadcastUDPConn(rawPacketConn net.PacketConn, boundAddr *net.UDPAddr) net.PacketConn { + return &BroadcastRawUDPConn{ + PacketConn: rawPacketConn, + boundAddr: boundAddr, + } +} + +func udpMatch(addr *net.UDPAddr, bound *net.UDPAddr) bool { + if bound == nil { + return true + } + if bound.IP != nil && !bound.IP.Equal(addr.IP) { + return false + } + return bound.Port == addr.Port +} + +// ReadFrom implements net.PacketConn.ReadFrom. +// +// ReadFrom reads raw IP packets and will try to match them against +// upc.boundAddr. Any matching packets are returned via the given buffer. +func (upc *BroadcastRawUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { + ipLen := IPv4MaximumHeaderSize + udpLen := UDPMinimumSize + + for { + pkt := make([]byte, ipLen+udpLen+len(b)) + n, _, err := upc.PacketConn.ReadFrom(pkt) + if err != nil { + return 0, nil, err + } + if n == 0 { + return 0, nil, io.EOF + } + pkt = pkt[:n] + buf := uio.NewBigEndianBuffer(pkt) + + // To read the header length, access data directly. + ipHdr := IPv4(buf.Data()) + ipHdr = IPv4(buf.Consume(int(ipHdr.HeaderLength()))) + + if ipHdr.TransportProtocol() != UDPProtocolNumber { + continue + } + udpHdr := UDP(buf.Consume(udpLen)) + + addr := &net.UDPAddr{ + IP: net.IP(ipHdr.DestinationAddress()), + Port: int(udpHdr.DestinationPort()), + } + if !udpMatch(addr, upc.boundAddr) { + continue + } + srcAddr := &net.UDPAddr{ + IP: net.IP(ipHdr.SourceAddress()), + Port: int(udpHdr.SourcePort()), + } + return copy(b, buf.ReadAll()), srcAddr, nil + } +} + +// WriteTo implements net.PacketConn.WriteTo and broadcasts all packets at the +// raw socket level. +// +// WriteTo wraps the given packet in the appropriate UDP and IP header before +// sending it on the packet conn. +func (upc *BroadcastRawUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) { + udpAddr, ok := addr.(*net.UDPAddr) + if !ok { + return 0, fmt.Errorf("must supply UDPAddr") + } + + // Using the boundAddr is not quite right here, but it works. + packet := udp4pkt(b, udpAddr, upc.boundAddr) + + // Broadcasting is not always right, but hell, what the ARP do I know. + return upc.PacketConn.WriteTo(packet, &raw.Addr{HardwareAddr: BroadcastMac}) +} |