Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
go get -v -t ./...
echo "" > "${GITHUB_WORKSPACE}"/coverage.txt
for d in $(go list ./...); do
go test -v -race -coverprofile=profile.out -covermode=atomic "${d}"
sudo go test -v -race -coverprofile=profile.out -covermode=atomic "${d}"
if [ -f profile.out ]; then
cat profile.out >> "${GITHUB_WORKSPACE}"/coverage.txt
rm profile.out
Expand Down
12 changes: 3 additions & 9 deletions dhcpv4/nclient4/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@
if iface == `` {
return nil, ErrNoConn
}
c.conn, err = NewRawUDPConn(iface, ClientPort) // broadcast
c.conn, err = NewRawUDPConn(iface, &net.UDPAddr{Port: ClientPort}, UDPBroadcast) // broadcast

Check warning on line 217 in dhcpv4/nclient4/client.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/client.go#L217

Added line #L217 was not covered by tests
if err != nil {
return nil, fmt.Errorf("unable to open a broadcasting socket: %w", err)
}
Expand Down Expand Up @@ -349,15 +349,9 @@
// srcAddr is both:
// * The source address of outgoing frames.
// * The address to be listened for incoming frames.
func WithUnicast(srcAddr *net.UDPAddr) ClientOpt {
func WithUnicast(iface string, srcAddr *net.UDPAddr) ClientOpt {
return func(c *Client) (err error) {
if srcAddr == nil {
srcAddr = &net.UDPAddr{Port: ClientPort}
}
c.conn, err = net.ListenUDP("udp4", srcAddr)
if err != nil {
err = fmt.Errorf("unable to start listening UDP port: %w", err)
}
c.conn, err = NewRawUDPConn(iface, srcAddr, UDPUnicast)

Check warning on line 354 in dhcpv4/nclient4/client.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/client.go#L354

Added line #L354 was not covered by tests
return
}
}
Expand Down
106 changes: 102 additions & 4 deletions dhcpv4/nclient4/conn_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,29 @@

import (
"errors"
"fmt"
"io"
"net"

"github.com/mdlayher/arp"
"github.com/mdlayher/ethernet"
"github.com/mdlayher/raw"
"github.com/u-root/uio/uio"
"github.com/vishvananda/netlink"
)

// UDPConnType indicates the type of the udp conn.
type UDPConnType int

const (
// UDPBroadcast specifies the type of udp conn as broadcast.
//
// All the packets will be broadcasted.
UDPBroadcast UDPConnType = 0

// UDPUnicast specifies the type of udp conn as unicast.
// All the packets will be sent to a unicast MAC address.
UDPUnicast UDPConnType = 1
)

var (
Expand All @@ -28,13 +45,16 @@
var (
// ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr".
ErrUDPAddrIsRequired = errors.New("must supply UDPAddr")

// ErrHWAddrNotFound is an error used when getting MAC address failed.
ErrHWAddrNotFound = errors.New("hardware address not found")
)

// NewRawUDPConn returns a UDP connection bound to the interface and port
// given based on a raw packet socket. All packets are broadcasted.
// NewRawUDPConn returns a UDP connection bound to the interface and udp address
// given based on a raw packet socket.
//
// The interface can be completely unconfigured.
func NewRawUDPConn(iface string, port int) (net.PacketConn, error) {
func NewRawUDPConn(iface string, addr *net.UDPAddr, typ UDPConnType) (net.PacketConn, error) {
ifc, err := net.InterfaceByName(iface)
if err != nil {
return nil, err
Expand All @@ -43,7 +63,12 @@
if err != nil {
return nil, err
}
return NewBroadcastUDPConn(rawConn, &net.UDPAddr{Port: port}), nil

if typ == UDPUnicast {
return NewUnicastRawUDPConn(rawConn, addr), nil

Check warning on line 68 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L67-L68

Added lines #L67 - L68 were not covered by tests
}

return NewBroadcastUDPConn(rawConn, addr), nil

Check warning on line 71 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L71

Added line #L71 was not covered by tests
}

// BroadcastRawUDPConn uses a raw socket to send UDP packets to the broadcast
Expand Down Expand Up @@ -157,3 +182,76 @@
// Broadcasting is not always right, but hell, what the ARP do I know.
return upc.PacketConn.WriteTo(packet, &raw.Addr{HardwareAddr: BroadcastMac})
}

// UnicastRawUDPConn inherits from BroadcastRawUDPConn and override the WriteTo method
type UnicastRawUDPConn struct {
*BroadcastRawUDPConn
}

// NewUnicastRawUDPConn returns a PacketConn which sending the packets to a unicast MAC address.
func NewUnicastRawUDPConn(rawPacketConn net.PacketConn, boundAddr *net.UDPAddr) net.PacketConn {
return &UnicastRawUDPConn{
BroadcastRawUDPConn: NewBroadcastUDPConn(rawPacketConn, boundAddr).(*BroadcastRawUDPConn),

Check warning on line 194 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L193-L194

Added lines #L193 - L194 were not covered by tests
}
}

// WriteTo implements net.PacketConn.WriteTo.
//
// WriteTo try to get the MAC address of destination IP address before
// unicast all packets at the raw socket level.
func (upc *UnicastRawUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
udpAddr, ok := addr.(*net.UDPAddr)
if !ok {
return 0, ErrUDPAddrIsRequired

Check warning on line 205 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L203-L205

Added lines #L203 - L205 were not covered by tests
}

// Using the boundAddr is not quite right here, but it works.
packet := udp4pkt(b, udpAddr, upc.boundAddr)
dstMac, err := getHwAddr(udpAddr.IP)
if err != nil {
return 0, ErrHWAddrNotFound

Check warning on line 212 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L209-L212

Added lines #L209 - L212 were not covered by tests
}

return upc.PacketConn.WriteTo(packet, &raw.Addr{HardwareAddr: dstMac})

Check warning on line 215 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L215

Added line #L215 was not covered by tests
}

// getHwAddr from local arp cache. If no existing, try to get it by arp protocol.
func getHwAddr(ip net.IP) (net.HardwareAddr, error) {
neighList, err := netlink.NeighListExecute(netlink.Ndmsg{
Family: netlink.FAMILY_V4,
State: netlink.NUD_REACHABLE,
})
if err != nil {
return nil, err

Check warning on line 225 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L225

Added line #L225 was not covered by tests
}

for _, neigh := range neighList {
if ip.Equal(neigh.IP) && neigh.HardwareAddr != nil {
return neigh.HardwareAddr, nil
}
}

return arpResolve(ip)

Check warning on line 234 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L234

Added line #L234 was not covered by tests
}

func arpResolve(dest net.IP) (net.HardwareAddr, error) {
// auto match the interface based on routes
routes, err := netlink.RouteGet(dest)
if err != nil {
return nil, err

Check warning on line 241 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L239-L241

Added lines #L239 - L241 were not covered by tests
}
if len(routes) == 0 {
return nil, fmt.Errorf("no route to %s found", dest.String())

Check warning on line 244 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L243-L244

Added lines #L243 - L244 were not covered by tests
}
ifc, err := net.InterfaceByIndex(routes[0].LinkIndex)
if err != nil {
return nil, err

Check warning on line 248 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L246-L248

Added lines #L246 - L248 were not covered by tests
}

c, err := arp.Dial(ifc)
if err != nil {
return nil, err

Check warning on line 253 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L251-L253

Added lines #L251 - L253 were not covered by tests
}

return c.Resolve(dest)

Check warning on line 256 in dhcpv4/nclient4/conn_unix.go

View check run for this annotation

Codecov / codecov/patch

dhcpv4/nclient4/conn_unix.go#L256

Added line #L256 was not covered by tests
}
74 changes: 74 additions & 0 deletions dhcpv4/nclient4/conn_unix_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package nclient4

import (
"net"
"testing"

"github.com/vishvananda/netlink"
)

const (
linkName = "neigh0"
ipStr = "10.99.0.1"
macStr = "aa:bb:cc:dd:00:01"
)

func TestGetHwAddrFromLocalCache(t *testing.T) {
mac, err := net.ParseMAC(macStr)
if err != nil {
t.Fatal(err)
}
ip := net.ParseIP(ipStr)

if err := addNeigh(ip, mac); err != nil {
t.Fatal(err)
}
defer func() {
if err := delNeigh(ip, mac); err != nil {
t.Fatal(err)
}
}()

_, err = net.InterfaceByName(linkName)
if err != nil {
t.Fatal(err)
}

if hw, err := getHwAddr(ip); err != nil && hw != nil && hw.String() == macStr {
t.Fatal(err)
}
}

func addNeigh(ip net.IP, mac net.HardwareAddr) error {
dummy := netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: linkName}}
if err := netlink.LinkAdd(&dummy); err != nil {
return err
}
newlink, err := netlink.LinkByName(dummy.Name)
if err != nil {
return err
}
dummy.Index = newlink.Attrs().Index

return netlink.NeighAdd(&netlink.Neigh{
LinkIndex: dummy.Index,
State: netlink.NUD_REACHABLE,
IP: ip,
HardwareAddr: mac,
})
}

func delNeigh(ip net.IP, mac net.HardwareAddr) error {
dummy, err := netlink.LinkByName(linkName)
if err != nil {
return err
}

return netlink.NeighDel(&netlink.Neigh{
LinkIndex: dummy.Attrs().Index,
State: netlink.NUD_REACHABLE,
IP: ip,
HardwareAddr: mac,
})
}

2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ require (
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c
github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7
github.com/mdlayher/netlink v1.1.1
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.6.1
github.com/u-root/uio v0.0.0-20210528114334-82958018845c
github.com/vishvananda/netlink v1.1.0
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea
)
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c h1:7cpGGTQO6+
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc h1:m7rJJJeXrYCFpsxXYapkDW53wJCDmf9bsIXUg0HoeQY=
github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc/go.mod h1:eOj1DDj3NAZ6yv+WafaKzY37MFZ58TdfIhQ+8nQbiis=
github.com/mdlayher/ethernet v0.0.0-20190313224307-5b5fc417d966/go.mod h1:5s5p/sMJ6sNsFl6uCh85lkFGV8kLuIYJCRJLavVJwvg=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1 h1:VqG+Voq9V4uZ+04vjIrcSCWDpf91B1xxbP4QBUmUJE8=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/raw v0.0.0-20190313224157-43dbcdd7739d/go.mod h1:r1fbeITl2xL/zLbVnNHFyOzQJTgr/3fpf1lJX/cjzR8=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
Expand All @@ -41,9 +45,14 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
Expand All @@ -59,6 +68,7 @@ golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down