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>
This commit is contained in:
dza1 2021-04-16 09:33:54 +02:00 committed by GitHub
parent 00cc6002b6
commit 15f169d55b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 126 additions and 14 deletions

1
go.mod
View File

@ -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
go.sum
View File

@ -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=

View File

@ -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
server/sendEthernet.go Normal file
View File

@ -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
}