Ver código fonte

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 <dza1@mailbox.org>

Co-authored-by: Daniel Zainzinger <dza1@mailbox.org>
dza1 4 anos atrás
pai
commit
15f169d55b
4 arquivos alterados com 126 adições e 14 exclusões
  1. 1 0
      go.mod
  2. 7 0
      go.sum
  3. 22 14
      server/handle.go
  4. 96 0
      server/sendEthernet.go

+ 1 - 0
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

+ 7 - 0
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=

+ 22 - 14
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")
 	}

+ 96 - 0
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, &eth, &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, &ethAddr)
+	if err != nil {
+		return fmt.Errorf("Cannot send frame via socket: %v", err)
+	}
+	return nil
+}