summaryrefslogtreecommitdiffhomepage
path: root/dhcpv4/nclient4/conn_linux.go
diff options
context:
space:
mode:
Diffstat (limited to 'dhcpv4/nclient4/conn_linux.go')
-rw-r--r--dhcpv4/nclient4/conn_linux.go173
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})
+}