From 3c283ff8b7dd3a8ea2dbc37d13a35bba7aab00e5 Mon Sep 17 00:00:00 2001 From: Chris Koch Date: Tue, 21 Dec 2021 18:48:10 -0800 Subject: nclient4: support BSDs mdlayher/raw supports them, so why don't we support them, too. Signed-off-by: Chris Koch --- dhcpv4/nclient4/conn_linux.go | 158 ----------------------------------------- dhcpv4/nclient4/conn_unix.go | 159 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+), 158 deletions(-) delete mode 100644 dhcpv4/nclient4/conn_linux.go create mode 100644 dhcpv4/nclient4/conn_unix.go diff --git a/dhcpv4/nclient4/conn_linux.go b/dhcpv4/nclient4/conn_linux.go deleted file mode 100644 index 6cdb071..0000000 --- a/dhcpv4/nclient4/conn_linux.go +++ /dev/null @@ -1,158 +0,0 @@ -// 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. - -//go:build go1.12 -// +build go1.12 - -package nclient4 - -import ( - "errors" - "io" - "net" - - "github.com/mdlayher/ethernet" - "github.com/mdlayher/raw" - "github.com/u-root/uio/uio" -) - -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}) -) - -var ( - // ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr". - ErrUDPAddrIsRequired = errors.New("must supply UDPAddr") -) - -// 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) { - ipHdrMaxLen := ipv4MaximumHeaderSize - udpHdrLen := udpMinimumSize - - for { - pkt := make([]byte, ipHdrMaxLen+udpHdrLen+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. - if !buf.Has(ipv4MinimumSize) { - continue - } - - ipHdr := ipv4(buf.Data()) - - if !buf.Has(int(ipHdr.headerLength())) { - continue - } - - ipHdr = ipv4(buf.Consume(int(ipHdr.headerLength()))) - - if ipHdr.transportProtocol() != udpProtocolNumber { - continue - } - - if !buf.Has(udpHdrLen) { - continue - } - - udpHdr := udp(buf.Consume(udpHdrLen)) - - addr := &net.UDPAddr{ - IP: ipHdr.destinationAddress(), - Port: int(udpHdr.destinationPort()), - } - if !udpMatch(addr, upc.boundAddr) { - continue - } - srcAddr := &net.UDPAddr{ - IP: ipHdr.sourceAddress(), - Port: int(udpHdr.sourcePort()), - } - // Extra padding after end of IP packet should be ignored, - // if not dhcp option parsing will fail. - dhcpLen := int(ipHdr.payloadLength()) - udpHdrLen - return copy(b, buf.Consume(dhcpLen)), 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, ErrUDPAddrIsRequired - } - - // 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}) -} diff --git a/dhcpv4/nclient4/conn_unix.go b/dhcpv4/nclient4/conn_unix.go new file mode 100644 index 0000000..bece752 --- /dev/null +++ b/dhcpv4/nclient4/conn_unix.go @@ -0,0 +1,159 @@ +// 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. + +//go:build go1.12 && (darwin || freebsd || linux || netbsd || openbsd) +// +build go1.12 +// +build darwin freebsd linux netbsd openbsd + +package nclient4 + +import ( + "errors" + "io" + "net" + + "github.com/mdlayher/ethernet" + "github.com/mdlayher/raw" + "github.com/u-root/uio/uio" +) + +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}) +) + +var ( + // ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr". + ErrUDPAddrIsRequired = errors.New("must supply UDPAddr") +) + +// 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) { + ipHdrMaxLen := ipv4MaximumHeaderSize + udpHdrLen := udpMinimumSize + + for { + pkt := make([]byte, ipHdrMaxLen+udpHdrLen+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. + if !buf.Has(ipv4MinimumSize) { + continue + } + + ipHdr := ipv4(buf.Data()) + + if !buf.Has(int(ipHdr.headerLength())) { + continue + } + + ipHdr = ipv4(buf.Consume(int(ipHdr.headerLength()))) + + if ipHdr.transportProtocol() != udpProtocolNumber { + continue + } + + if !buf.Has(udpHdrLen) { + continue + } + + udpHdr := udp(buf.Consume(udpHdrLen)) + + addr := &net.UDPAddr{ + IP: ipHdr.destinationAddress(), + Port: int(udpHdr.destinationPort()), + } + if !udpMatch(addr, upc.boundAddr) { + continue + } + srcAddr := &net.UDPAddr{ + IP: ipHdr.sourceAddress(), + Port: int(udpHdr.sourcePort()), + } + // Extra padding after end of IP packet should be ignored, + // if not dhcp option parsing will fail. + dhcpLen := int(ipHdr.payloadLength()) - udpHdrLen + return copy(b, buf.Consume(dhcpLen)), 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, ErrUDPAddrIsRequired + } + + // 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}) +} -- cgit v1.2.3