Skip to content

Commit 28832bb

Browse files
committed
Change unicast connection of dhcpv4 client as a raw socket
If the network manager in the operating system uses DHCP to obtain an IP for the network card, it will occupy the DHCP client port. In this case the release function will fail because the unicast connection with UDP socket needs the same port. Use a raw udp socket instead of a datagram udp socket to solve it. Signed-off-by: yaocw2020 <[email protected]>
1 parent 3c283ff commit 28832bb

File tree

6 files changed

+192
-14
lines changed

6 files changed

+192
-14
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
go get -v -t ./...
2222
echo "" > "${GITHUB_WORKSPACE}"/coverage.txt
2323
for d in $(go list ./...); do
24-
go test -v -race -coverprofile=profile.out -covermode=atomic "${d}"
24+
sudo go test -v -race -coverprofile=profile.out -covermode=atomic "${d}"
2525
if [ -f profile.out ]; then
2626
cat profile.out >> "${GITHUB_WORKSPACE}"/coverage.txt
2727
rm profile.out

dhcpv4/nclient4/client.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ func new(iface string, conn net.PacketConn, ifaceHWAddr net.HardwareAddr, opts .
214214
if iface == `` {
215215
return nil, ErrNoConn
216216
}
217-
c.conn, err = NewRawUDPConn(iface, ClientPort) // broadcast
217+
c.conn, err = NewRawUDPConn(iface, &net.UDPAddr{Port: ClientPort}, UDPBroadcast) // broadcast
218218
if err != nil {
219219
return nil, fmt.Errorf("unable to open a broadcasting socket: %w", err)
220220
}
@@ -349,15 +349,9 @@ func WithLogger(newLogger Logger) ClientOpt {
349349
// srcAddr is both:
350350
// * The source address of outgoing frames.
351351
// * The address to be listened for incoming frames.
352-
func WithUnicast(srcAddr *net.UDPAddr) ClientOpt {
352+
func WithUnicast(iface string, srcAddr *net.UDPAddr) ClientOpt {
353353
return func(c *Client) (err error) {
354-
if srcAddr == nil {
355-
srcAddr = &net.UDPAddr{Port: ClientPort}
356-
}
357-
c.conn, err = net.ListenUDP("udp4", srcAddr)
358-
if err != nil {
359-
err = fmt.Errorf("unable to start listening UDP port: %w", err)
360-
}
354+
c.conn, err = NewRawUDPConn(iface, srcAddr, UDPUnicast)
361355
return
362356
}
363357
}

dhcpv4/nclient4/conn_unix.go

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,29 @@ package nclient4
1010

1111
import (
1212
"errors"
13+
"fmt"
1314
"io"
1415
"net"
1516

17+
"github.com/mdlayher/arp"
1618
"github.com/mdlayher/ethernet"
1719
"github.com/mdlayher/raw"
1820
"github.com/u-root/uio/uio"
21+
"github.com/vishvananda/netlink"
22+
)
23+
24+
// UDPConnType indicates the type of the udp conn.
25+
type UDPConnType int
26+
27+
const (
28+
// UDPBroadcast specifies the type of udp conn as broadcast.
29+
//
30+
// All the packets will be broadcasted.
31+
UDPBroadcast UDPConnType = 0
32+
33+
// UDPUnicast specifies the type of udp conn as unicast.
34+
// All the packets will be sent to a unicast MAC address.
35+
UDPUnicast UDPConnType = 1
1936
)
2037

2138
var (
@@ -28,13 +45,16 @@ var (
2845
var (
2946
// ErrUDPAddrIsRequired is an error used when a passed argument is not of type "*net.UDPAddr".
3047
ErrUDPAddrIsRequired = errors.New("must supply UDPAddr")
48+
49+
// ErrHWAddrNotFound is an error used when getting MAC address failed.
50+
ErrHWAddrNotFound = errors.New("hardware address not found")
3151
)
3252

33-
// NewRawUDPConn returns a UDP connection bound to the interface and port
34-
// given based on a raw packet socket. All packets are broadcasted.
53+
// NewRawUDPConn returns a UDP connection bound to the interface and udp address
54+
// given based on a raw packet socket.
3555
//
3656
// The interface can be completely unconfigured.
37-
func NewRawUDPConn(iface string, port int) (net.PacketConn, error) {
57+
func NewRawUDPConn(iface string, addr *net.UDPAddr, typ UDPConnType) (net.PacketConn, error) {
3858
ifc, err := net.InterfaceByName(iface)
3959
if err != nil {
4060
return nil, err
@@ -43,7 +63,12 @@ func NewRawUDPConn(iface string, port int) (net.PacketConn, error) {
4363
if err != nil {
4464
return nil, err
4565
}
46-
return NewBroadcastUDPConn(rawConn, &net.UDPAddr{Port: port}), nil
66+
67+
if typ == UDPUnicast {
68+
return NewUnicastRawUDPConn(rawConn, addr), nil
69+
}
70+
71+
return NewBroadcastUDPConn(rawConn, addr), nil
4772
}
4873

4974
// BroadcastRawUDPConn uses a raw socket to send UDP packets to the broadcast
@@ -157,3 +182,76 @@ func (upc *BroadcastRawUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
157182
// Broadcasting is not always right, but hell, what the ARP do I know.
158183
return upc.PacketConn.WriteTo(packet, &raw.Addr{HardwareAddr: BroadcastMac})
159184
}
185+
186+
// UnicastRawUDPConn inherits from BroadcastRawUDPConn and override the WriteTo method
187+
type UnicastRawUDPConn struct {
188+
*BroadcastRawUDPConn
189+
}
190+
191+
// NewUnicastRawUDPConn returns a PacketConn which sending the packets to a unicast MAC address.
192+
func NewUnicastRawUDPConn(rawPacketConn net.PacketConn, boundAddr *net.UDPAddr) net.PacketConn {
193+
return &UnicastRawUDPConn{
194+
BroadcastRawUDPConn: NewBroadcastUDPConn(rawPacketConn, boundAddr).(*BroadcastRawUDPConn),
195+
}
196+
}
197+
198+
// WriteTo implements net.PacketConn.WriteTo.
199+
//
200+
// WriteTo try to get the MAC address of destination IP address before
201+
// unicast all packets at the raw socket level.
202+
func (upc *UnicastRawUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
203+
udpAddr, ok := addr.(*net.UDPAddr)
204+
if !ok {
205+
return 0, ErrUDPAddrIsRequired
206+
}
207+
208+
// Using the boundAddr is not quite right here, but it works.
209+
packet := udp4pkt(b, udpAddr, upc.boundAddr)
210+
dstMac, err := getHwAddr(udpAddr.IP)
211+
if err != nil {
212+
return 0, ErrHWAddrNotFound
213+
}
214+
215+
return upc.PacketConn.WriteTo(packet, &raw.Addr{HardwareAddr: dstMac})
216+
}
217+
218+
// getHwAddr from local arp cache. If no existing, try to get it by arp protocol.
219+
func getHwAddr(ip net.IP) (net.HardwareAddr, error) {
220+
neighList, err := netlink.NeighListExecute(netlink.Ndmsg{
221+
Family: netlink.FAMILY_V4,
222+
State: netlink.NUD_REACHABLE,
223+
})
224+
if err != nil {
225+
return nil, err
226+
}
227+
228+
for _, neigh := range neighList {
229+
if ip.Equal(neigh.IP) && neigh.HardwareAddr != nil {
230+
return neigh.HardwareAddr, nil
231+
}
232+
}
233+
234+
return arpResolve(ip)
235+
}
236+
237+
func arpResolve(dest net.IP) (net.HardwareAddr, error) {
238+
// auto match the interface based on routes
239+
routes, err := netlink.RouteGet(dest)
240+
if err != nil {
241+
return nil, err
242+
}
243+
if len(routes) == 0 {
244+
return nil, fmt.Errorf("no route to %s found", dest.String())
245+
}
246+
ifc, err := net.InterfaceByIndex(routes[0].LinkIndex)
247+
if err != nil {
248+
return nil, err
249+
}
250+
251+
c, err := arp.Dial(ifc)
252+
if err != nil {
253+
return nil, err
254+
}
255+
256+
return c.Resolve(dest)
257+
}

dhcpv4/nclient4/conn_unix_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package nclient4
2+
3+
import (
4+
"net"
5+
"testing"
6+
7+
"github.com/vishvananda/netlink"
8+
)
9+
10+
const (
11+
linkName = "neigh0"
12+
ipStr = "10.99.0.1"
13+
macStr = "aa:bb:cc:dd:00:01"
14+
)
15+
16+
func TestGetHwAddrFromLocalCache(t *testing.T) {
17+
mac, err := net.ParseMAC(macStr)
18+
if err != nil {
19+
t.Fatal(err)
20+
}
21+
ip := net.ParseIP(ipStr)
22+
23+
if err := addNeigh(ip, mac); err != nil {
24+
t.Fatal(err)
25+
}
26+
defer func() {
27+
if err := delNeigh(ip, mac); err != nil {
28+
t.Fatal(err)
29+
}
30+
}()
31+
32+
_, err = net.InterfaceByName(linkName)
33+
if err != nil {
34+
t.Fatal(err)
35+
}
36+
37+
if hw, err := getHwAddr(ip); err != nil && hw != nil && hw.String() == macStr {
38+
t.Fatal(err)
39+
}
40+
}
41+
42+
func addNeigh(ip net.IP, mac net.HardwareAddr) error {
43+
dummy := netlink.Dummy{LinkAttrs: netlink.LinkAttrs{Name: linkName}}
44+
if err := netlink.LinkAdd(&dummy); err != nil {
45+
return err
46+
}
47+
newlink, err := netlink.LinkByName(dummy.Name)
48+
if err != nil {
49+
return err
50+
}
51+
dummy.Index = newlink.Attrs().Index
52+
53+
return netlink.NeighAdd(&netlink.Neigh{
54+
LinkIndex: dummy.Index,
55+
State: netlink.NUD_REACHABLE,
56+
IP: ip,
57+
HardwareAddr: mac,
58+
})
59+
}
60+
61+
func delNeigh(ip net.IP, mac net.HardwareAddr) error {
62+
dummy, err := netlink.LinkByName(linkName)
63+
if err != nil {
64+
return err
65+
}
66+
67+
return netlink.NeighDel(&netlink.Neigh{
68+
LinkIndex: dummy.Attrs().Index,
69+
State: netlink.NUD_REACHABLE,
70+
IP: ip,
71+
HardwareAddr: mac,
72+
})
73+
}
74+

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ require (
66
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72
77
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714
88
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c
9+
github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc
910
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7
1011
github.com/mdlayher/netlink v1.1.1
1112
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065
1213
github.com/smartystreets/goconvey v1.6.4 // indirect
1314
github.com/stretchr/testify v1.6.1
1415
github.com/u-root/uio v0.0.0-20210528114334-82958018845c
16+
github.com/vishvananda/netlink v1.1.0
1517
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b
1618
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea
1719
)

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c h1:7cpGGTQO6+
1919
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
2020
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
2121
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
22+
github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc h1:m7rJJJeXrYCFpsxXYapkDW53wJCDmf9bsIXUg0HoeQY=
23+
github.com/mdlayher/arp v0.0.0-20191213142603-f72070a231fc/go.mod h1:eOj1DDj3NAZ6yv+WafaKzY37MFZ58TdfIhQ+8nQbiis=
24+
github.com/mdlayher/ethernet v0.0.0-20190313224307-5b5fc417d966/go.mod h1:5s5p/sMJ6sNsFl6uCh85lkFGV8kLuIYJCRJLavVJwvg=
2225
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE=
2326
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
2427
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
2528
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
2629
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
2730
github.com/mdlayher/netlink v1.1.1 h1:VqG+Voq9V4uZ+04vjIrcSCWDpf91B1xxbP4QBUmUJE8=
2831
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
32+
github.com/mdlayher/raw v0.0.0-20190313224157-43dbcdd7739d/go.mod h1:r1fbeITl2xL/zLbVnNHFyOzQJTgr/3fpf1lJX/cjzR8=
2933
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
3034
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w=
3135
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
@@ -41,9 +45,14 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
4145
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
4246
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
4347
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
48+
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
49+
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
50+
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
51+
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
4452
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
4553
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
4654
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
55+
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
4756
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
4857
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
4958
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@@ -59,6 +68,7 @@ golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7w
5968
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6069
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6170
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
71+
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6272
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6373
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6474
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

0 commit comments

Comments
 (0)