//go:build linux package dhcpd import ( "net" "testing" "github.com/AdguardTeam/golibs/testutil" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/insomniacslk/dhcp/dhcpv4" "github.com/mdlayher/packet" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDHCPConn_WriteTo_common(t *testing.T) { respData := (&dhcpv4.DHCPv4{}).ToBytes() udpAddr := &net.UDPAddr{ IP: net.IP{1, 2, 3, 4}, Port: dhcpv4.ClientPort, } t.Run("unicast_ip", func(t *testing.T) { writeTo := func(_ []byte, addr net.Addr) (_ int, _ error) { assert.Equal(t, udpAddr, addr) return 0, nil } conn := &dhcpConn{udpConn: &fakePacketConn{writeTo: writeTo}} _, err := conn.WriteTo(respData, udpAddr) assert.NoError(t, err) }) t.Run("unexpected_addr_type", func(t *testing.T) { type unexpectedAddrType struct { net.Addr } conn := &dhcpConn{} n, err := conn.WriteTo(nil, &unexpectedAddrType{}) require.Error(t, err) testutil.AssertErrorMsg(t, "addr has an unexpected type *dhcpd.unexpectedAddrType", err) assert.Zero(t, n) }) } func TestBuildEtherPkt(t *testing.T) { conn := &dhcpConn{ srcMAC: net.HardwareAddr{1, 2, 3, 4, 5, 6}, srcIP: net.IP{1, 2, 3, 4}, } peer := &dhcpUnicastAddr{ Addr: packet.Addr{HardwareAddr: net.HardwareAddr{6, 5, 4, 3, 2, 1}}, yiaddr: net.IP{4, 3, 2, 1}, } payload := (&dhcpv4.DHCPv4{}).ToBytes() t.Run("success", func(t *testing.T) { pkt, err := conn.buildEtherPkt(payload, peer) require.NoError(t, err) assert.NotEmpty(t, pkt) actualPkt := gopacket.NewPacket(pkt, layers.LayerTypeEthernet, gopacket.DecodeOptions{ NoCopy: true, }) require.NotNil(t, actualPkt) wantTypes := []gopacket.LayerType{ layers.LayerTypeEthernet, layers.LayerTypeIPv4, layers.LayerTypeUDP, layers.LayerTypeDHCPv4, } actualLayers := actualPkt.Layers() require.Len(t, actualLayers, len(wantTypes)) for i, wantType := range wantTypes { layer := actualLayers[i] require.NotNil(t, layer) assert.Equal(t, wantType, layer.LayerType()) } }) t.Run("bad_payload", func(t *testing.T) { // Create an invalid DHCP packet. invalidPayload := []byte{1, 2, 3, 4} pkt, err := conn.buildEtherPkt(invalidPayload, peer) require.NoError(t, err) assert.NotEmpty(t, pkt) }) t.Run("serializing_error", func(t *testing.T) { // Create a peer with invalid MAC. badPeer := &dhcpUnicastAddr{ Addr: packet.Addr{HardwareAddr: net.HardwareAddr{5, 4, 3, 2, 1}}, yiaddr: net.IP{4, 3, 2, 1}, } pkt, err := conn.buildEtherPkt(payload, badPeer) require.Error(t, err) assert.Empty(t, pkt) }) } func TestV4Server_Send(t *testing.T) { s := &v4Server{} var ( defaultIP = net.IP{99, 99, 99, 99} knownIP = net.IP{4, 2, 4, 2} knownMAC = net.HardwareAddr{6, 5, 4, 3, 2, 1} ) defaultPeer := &net.UDPAddr{ IP: defaultIP, // Use neither client nor server port to check it actually // changed. Port: dhcpv4.ClientPort + dhcpv4.ServerPort, } defaultResp := &dhcpv4.DHCPv4{} testCases := []struct { want net.Addr req *dhcpv4.DHCPv4 resp *dhcpv4.DHCPv4 name string }{{ name: "giaddr", req: &dhcpv4.DHCPv4{GatewayIPAddr: knownIP}, resp: defaultResp, want: &net.UDPAddr{ IP: knownIP, Port: dhcpv4.ServerPort, }, }, { name: "nak", req: &dhcpv4.DHCPv4{}, resp: &dhcpv4.DHCPv4{ Options: dhcpv4.OptionsFromList( dhcpv4.OptMessageType(dhcpv4.MessageTypeNak), ), }, want: defaultPeer, }, { name: "ciaddr", req: &dhcpv4.DHCPv4{ClientIPAddr: knownIP}, resp: &dhcpv4.DHCPv4{}, want: &net.UDPAddr{ IP: knownIP, Port: dhcpv4.ClientPort, }, }, { name: "chaddr", req: &dhcpv4.DHCPv4{ClientHWAddr: knownMAC}, resp: &dhcpv4.DHCPv4{YourIPAddr: knownIP}, want: &dhcpUnicastAddr{ Addr: packet.Addr{HardwareAddr: knownMAC}, yiaddr: knownIP, }, }, { name: "who_are_you", req: &dhcpv4.DHCPv4{}, resp: &dhcpv4.DHCPv4{}, want: defaultPeer, }} for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { conn := &fakePacketConn{ writeTo: func(_ []byte, addr net.Addr) (_ int, _ error) { assert.Equal(t, tc.want, addr) return 0, nil }, } s.send(cloneUDPAddr(defaultPeer), conn, tc.req, tc.resp) }) } t.Run("giaddr_nak", func(t *testing.T) { req := &dhcpv4.DHCPv4{ GatewayIPAddr: knownIP, } // Ensure the request is for unicast. req.SetUnicast() resp := &dhcpv4.DHCPv4{ Options: dhcpv4.OptionsFromList( dhcpv4.OptMessageType(dhcpv4.MessageTypeNak), ), } want := &net.UDPAddr{ IP: req.GatewayIPAddr, Port: dhcpv4.ServerPort, } conn := &fakePacketConn{ writeTo: func(_ []byte, addr net.Addr) (n int, err error) { assert.Equal(t, want, addr) return 0, nil }, } s.send(cloneUDPAddr(defaultPeer), conn, req, resp) assert.True(t, resp.IsBroadcast()) }) }