From 15f169d55bdce4876bb04811b08a9f29d27f89bb Mon Sep 17 00:00:00 2001 From: dza1 <58394953+dza1@users.noreply.github.com> Date: Fri, 16 Apr 2021 09:33:54 +0200 Subject: [PATCH] server/dhcpv4: Send unicast responses to peers with no IP (#123) When the broadcast bit isn't set in the request, send responses to the L3 address we're assigning, using the peer L2 address, as that's the preferred behavior according to the RFC Signed-off-by: Daniel Zainzinger Co-authored-by: Daniel Zainzinger --- go.mod | 1 + go.sum | 7 +++ server/handle.go | 36 ++++++++++------ server/sendEthernet.go | 96 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 server/sendEthernet.go diff --git a/go.mod b/go.mod index a9f2bbc..c9eb5d4 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( github.com/chappjc/logrus-prefix v0.0.0-20180227015900-3a1d64819adb + github.com/google/gopacket v1.1.19 github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 // indirect github.com/insomniacslk/dhcp v0.0.0-20210120172423-cc9239ac6294 github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect diff --git a/go.sum b/go.sum index d5cdfd8..02a8ab1 100644 --- a/go.sum +++ b/go.sum @@ -74,6 +74,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -281,6 +283,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= @@ -300,11 +303,13 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0 h1:sfUMP1Gu8qASkorDVjnMuvgJzwFbTZSeXFiGBYAVdl4= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -410,7 +415,9 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= diff --git a/server/handle.go b/server/handle.go index 56e3661..f44d952 100644 --- a/server/handle.go +++ b/server/handle.go @@ -108,7 +108,7 @@ func (l *listener4) HandleMsg4(buf []byte, oob *ipv4.ControlMessage, _peer net.A req, err := dhcpv4.FromBytes(buf) bufpool.Put(&buf) if err != nil { - log.Printf("Error parsing DHCPv6 request: %v", err) + log.Printf("Error parsing DHCPv4 request: %v", err) return } @@ -140,6 +140,7 @@ func (l *listener4) HandleMsg4(buf []byte, oob *ipv4.ControlMessage, _peer net.A } if resp != nil { + useEthernet := false var peer *net.UDPAddr if !req.GatewayIPAddr.IsUnspecified() { // TODO: make RFC8357 compliant @@ -151,19 +152,14 @@ func (l *listener4) HandleMsg4(buf []byte, oob *ipv4.ControlMessage, _peer net.A } else if req.IsBroadcast() { peer = &net.UDPAddr{IP: net.IPv4bcast, Port: dhcpv4.ClientPort} } else { - // FIXME: we're supposed to unicast to a specific *L2* address, and an L3 - // address that's not yet assigned. - // I don't know how to do that with this API... - //peer = &net.UDPAddr{IP: resp.YourIPAddr, Port: dhcpv4.ClientPort} - log.Warn("Cannot handle non-broadcast-capable unspecified peers in an RFC-compliant way. " + - "Response will be broadcast") - - peer = &net.UDPAddr{IP: net.IPv4bcast, Port: dhcpv4.ClientPort} + //sends a layer2 frame so that we can define the destination MAC address + peer = &net.UDPAddr{IP: resp.YourIPAddr, Port: dhcpv4.ClientPort} + useEthernet = true } var woob *ipv4.ControlMessage - if peer.IP.Equal(net.IPv4bcast) || peer.IP.IsLinkLocalUnicast() { - // Direct broadcasts and link-local to the interface the request was + if peer.IP.Equal(net.IPv4bcast) || peer.IP.IsLinkLocalUnicast() || useEthernet { + // Direct broadcasts, link-local and layer2 unicasts to the interface the request was // received on. Other packets should use the normal routing table in // case of asymetric routing switch { @@ -175,10 +171,22 @@ func (l *listener4) HandleMsg4(buf []byte, oob *ipv4.ControlMessage, _peer net.A log.Errorf("HandleMsg4: Did not receive interface information") } } - if _, err := l.WriteTo(resp.ToBytes(), woob, peer); err != nil { - log.Printf("MainHandler4: conn.Write to %v failed: %v", peer, err) - } + if useEthernet { + intf, err := net.InterfaceByIndex(woob.IfIndex) + if err != nil { + log.Errorf("MainHandler4: Can not get Interface for index %d %v", woob.IfIndex, err) + return + } + err = sendEthernet(*intf, resp) + if err != nil { + log.Errorf("MainHandler4: Cannot send Ethernet packet: %v", err) + } + } else { + if _, err := l.WriteTo(resp.ToBytes(), woob, peer); err != nil { + log.Errorf("MainHandler4: conn.Write to %v failed: %v", peer, err) + } + } } else { log.Print("MainHandler4: dropping request because response is nil") } diff --git a/server/sendEthernet.go b/server/sendEthernet.go new file mode 100644 index 0000000..35caf86 --- /dev/null +++ b/server/sendEthernet.go @@ -0,0 +1,96 @@ +// Copyright 2018-present the CoreDHCP Authors. All rights reserved +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + +// +build linux + +package server + +import ( + "fmt" + "net" + "syscall" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/insomniacslk/dhcp/dhcpv4" +) + +//this function sends an unicast to the hardware address defined in resp.ClientHWAddr, +//the layer3 destination address is still the broadcast address; +//iface: the interface where the DHCP message should be sent; +//resp: DHCPv4 struct, which should be sent; +func sendEthernet(iface net.Interface, resp *dhcpv4.DHCPv4) error { + + eth := layers.Ethernet{ + EthernetType: layers.EthernetTypeIPv4, + SrcMAC: iface.HardwareAddr, + DstMAC: resp.ClientHWAddr, + } + ip := layers.IPv4{ + Version: 4, + TTL: 64, + SrcIP: resp.ServerIPAddr, + DstIP: resp.YourIPAddr, + Protocol: layers.IPProtocolUDP, + Flags: layers.IPv4DontFragment, + } + udp := layers.UDP{ + SrcPort: dhcpv4.ServerPort, + DstPort: dhcpv4.ClientPort, + } + + err := udp.SetNetworkLayerForChecksum(&ip) + if err != nil { + return fmt.Errorf("Send Ethernet: Couldn't set network layer: %v", err) + } + + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{ + ComputeChecksums: true, + FixLengths: true, + } + + // Decode a packet + packet := gopacket.NewPacket(resp.ToBytes(), layers.LayerTypeDHCPv4, gopacket.NoCopy) + dhcpLayer := packet.Layer(layers.LayerTypeDHCPv4) + dhcp, ok := dhcpLayer.(gopacket.SerializableLayer) + if !ok { + return fmt.Errorf("Layer %s is not serializable", dhcpLayer.LayerType().String()) + } + err = gopacket.SerializeLayers(buf, opts, ð, &ip, &udp, dhcp) + if err != nil { + return fmt.Errorf("Cannot serialize layer: %v", err) + } + data := buf.Bytes() + + fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, 0) + if err != nil { + return fmt.Errorf("Send Ethernet: Cannot open socket: %v", err) + } + defer func() { + err = syscall.Close(fd) + if err != nil { + log.Errorf("Send Ethernet: Cannot close socket: %v", err) + } + }() + + err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) + if err != nil { + log.Errorf("Send Ethernet: Cannot set option for socket: %v", err) + } + + var hwAddr [8]byte + copy(hwAddr[0:6], resp.ClientHWAddr[0:6]) + ethAddr := syscall.SockaddrLinklayer{ + Protocol: 0, + Ifindex: iface.Index, + Halen: 6, + Addr: hwAddr, //not used + } + err = syscall.Sendto(fd, data, 0, ðAddr) + if err != nil { + return fmt.Errorf("Cannot send frame via socket: %v", err) + } + return nil +}