Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: nclient4 BSD/macOS support #534

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dhcpv4/nclient4/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build go1.12
//go:build go1.12 && linux
// +build go1.12,linux

package nclient4

Expand Down
100 changes: 100 additions & 0 deletions dhcpv4/nclient4/conn_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// 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 && linux
// +build go1.12,linux

package nclient4

import (
"io"
"net"

"github.com/mdlayher/packet"
"golang.org/x/sys/unix"
)

// 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 := packet.Listen(ifc, packet.Datagram, unix.ETH_P_IP, nil)
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,
}
}

// 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]
dhcpPkt, srcAddr := getUDP4pkt(pkt, upc.boundAddr)
if dhcpPkt == nil {
continue
}

return copy(b, dhcpPkt), 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.
pkt := udp4pkt(b, udpAddr, upc.boundAddr)

// Broadcasting is not always right, but hell, what the ARP do I know.
return upc.PacketConn.WriteTo(pkt, &packet.Addr{HardwareAddr: BroadcastMac})
}
126 changes: 50 additions & 76 deletions dhcpv4/nclient4/conn_unix.go
Original file line number Diff line number Diff line change
@@ -1,145 +1,116 @@
// 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 || dragonfly)
//go:build go1.12 && (darwin || freebsd || netbsd || openbsd || dragonfly)
// +build go1.12
// +build darwin freebsd linux netbsd openbsd dragonfly
// +build darwin freebsd netbsd openbsd dragonfly

package nclient4

import (
"errors"
"io"
"net"

"github.com/mdlayher/packet"
"github.com/u-root/uio/uio"
"golang.org/x/sys/unix"
"github.com/mdlayher/raw"
)

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})
const (
bpfFilterBidirectional int = 1
)

var (
// ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr".
ErrUDPAddrIsRequired = errors.New("must supply UDPAddr")
)
var rawConnectionConfig = &raw.Config{
BPFDirection: bpfFilterBidirectional,
}

// 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) {
func NewRawUDPConn(iface string, port int, vlans ...uint16) (net.PacketConn, error) {
ifc, err := net.InterfaceByName(iface)
if err != nil {
return nil, err
}
rawConn, err := packet.Listen(ifc, packet.Datagram, unix.ETH_P_IP, nil)

var etherType uint16
if len(vlans) > 0 {
etherType = vlanTPID // The VLAN TPID field is located in the same offset as EtherType
} else {
etherType = etherIPv4Proto
}

// Create a bidirectional raw socket on ifc with etherType as the filter
rawConn, err := raw.ListenPacket(ifc, etherType, rawConnectionConfig)
if err != nil {
return nil, err
}
return NewBroadcastUDPConn(rawConn, &net.UDPAddr{Port: port}), nil

return NewBroadcastUDPConn(net.PacketConn(rawConn), &net.UDPAddr{Port: port}, vlans...), nil
}

// BroadcastRawUDPConn uses a raw socket to send UDP packets to the broadcast
// MAC address.
type BroadcastRawUDPConn struct {
// PacketConn is a raw DGRAM socket.
// PacketConn is a raw network 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
// VLAN tags can be configured to make up for the shortcoming of the BSD implementation
VLANs []uint16
}

// NewBroadcastUDPConn returns a PacketConn that marshals and unmarshals UDP
// packets, sending them to the broadcast MAC at on rawPacketConn.
// Supplied VLAN tags are inserted into the Ethernet frame before sending.
//
// Calls to ReadFrom will only return packets destined to boundAddr.
func NewBroadcastUDPConn(rawPacketConn net.PacketConn, boundAddr *net.UDPAddr) net.PacketConn {
func NewBroadcastUDPConn(rawPacketConn net.PacketConn, boundAddr *net.UDPAddr, vlans ...uint16) net.PacketConn {
return &BroadcastRawUDPConn{
PacketConn: rawPacketConn,
boundAddr: boundAddr,
VLANs: vlans,
}
}

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.
// ReadFrom reads raw Ethernet frames, parses and matches the VLAN stack (if configured),
// and will try to match the remaining IP packet against upc.boundAddr.
//
// Any matching packets are returned via the given buffer.
func (upc *BroadcastRawUDPConn) ReadFrom(b []byte) (int, net.Addr, error) {
ethHdrLen := ethHdrBaseLen
if len(upc.VLANs) > 0 {
ethHdrLen += len(upc.VLANs) * vlanTagLen
}
ipHdrMaxLen := ipv4MaximumHeaderSize
udpHdrLen := udpMinimumSize

for {
pkt := make([]byte, ipHdrMaxLen+udpHdrLen+len(b))
pkt := make([]byte, ethHdrLen+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)

ipHdr := ipv4(buf.Data())

if !ipHdr.isValid(n) {
continue
}

ipHdr = ipv4(buf.Consume(int(ipHdr.headerLength())))

if ipHdr.transportProtocol() != udpProtocolNumber {
pkt = getEthernetPayload(pkt[:n], upc.VLANs)
if pkt == nil {
// VLAN stack does not match our configuration
continue
}

if !buf.Has(udpHdrLen) {
dhcpPkt, srcAddr := getUDP4pkt(pkt[:n], upc.boundAddr)
if dhcpPkt == nil {
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
return copy(b, dhcpPkt), 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.
// WriteTo wraps the given packet in the appropriate UDP, IP and Ethernet header
// before sending it on the packet conn. Since the Ethernet encapsulation is done
// on the application's side, VLAN tagging also has to be handled in the application.
func (upc *BroadcastRawUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
udpAddr, ok := addr.(*net.UDPAddr)
if !ok {
Expand All @@ -149,6 +120,9 @@ func (upc *BroadcastRawUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
// Using the boundAddr is not quite right here, but it works.
pkt := udp4pkt(b, udpAddr, upc.boundAddr)

// Broadcasting is not always right, but hell, what the ARP do I know.
return upc.PacketConn.WriteTo(pkt, &packet.Addr{HardwareAddr: BroadcastMac})
srcMac := upc.PacketConn.LocalAddr().(*raw.Addr).HardwareAddr
pkt = addEthernetHdr(pkt, BroadcastMac, srcMac, etherIPv4Proto, upc.VLANs)

// The `raw` packet connection does not take any address as an argument.
return upc.PacketConn.WriteTo(pkt, nil)
}
Loading
Loading