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