github.com/sagernet/netlink@v0.0.0-20240612041022-b9a21c07ac6a/neigh_test.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package netlink
     5  
     6  import (
     7  	"net"
     8  	"syscall"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/vishvananda/netns"
    13  	"golang.org/x/sys/unix"
    14  )
    15  
    16  type arpEntry struct {
    17  	ip  net.IP
    18  	mac net.HardwareAddr
    19  }
    20  
    21  type proxyEntry struct {
    22  	ip  net.IP
    23  	dev int
    24  }
    25  
    26  func parseMAC(s string) net.HardwareAddr {
    27  	m, err := net.ParseMAC(s)
    28  	if err != nil {
    29  		panic(err)
    30  	}
    31  	return m
    32  }
    33  
    34  func dumpContains(dump []Neigh, e arpEntry) bool {
    35  	for _, n := range dump {
    36  		if n.IP.Equal(e.ip) && (n.State&NUD_INCOMPLETE) == 0 {
    37  			return true
    38  		}
    39  	}
    40  	return false
    41  }
    42  
    43  func dumpContainsNeigh(dump []Neigh, ne Neigh) bool {
    44  	for _, n := range dump {
    45  		if n.IP.Equal(ne.IP) && n.LLIPAddr.Equal(ne.LLIPAddr) {
    46  			return true
    47  		}
    48  	}
    49  	return false
    50  }
    51  
    52  func dumpContainsState(dump []Neigh, e arpEntry, s uint16) bool {
    53  	for _, n := range dump {
    54  		if n.IP.Equal(e.ip) && uint16(n.State) == s {
    55  			return true
    56  		}
    57  	}
    58  	return false
    59  }
    60  
    61  func dumpContainsProxy(dump []Neigh, p proxyEntry) bool {
    62  	for _, n := range dump {
    63  		if n.IP.Equal(p.ip) && (n.LinkIndex == p.dev) && (n.Flags&NTF_PROXY) == NTF_PROXY {
    64  			return true
    65  		}
    66  	}
    67  	return false
    68  }
    69  
    70  func TestNeighAddDelLLIPAddr(t *testing.T) {
    71  	setUpNetlinkTestWithKModule(t, "ip_gre")
    72  
    73  	tearDown := setUpNetlinkTest(t)
    74  	defer tearDown()
    75  
    76  	dummy := Gretun{
    77  		LinkAttrs: LinkAttrs{Name: "neigh0"},
    78  		Local:     net.IPv4(127, 0, 0, 1),
    79  		IKey:      1234,
    80  		OKey:      1234}
    81  	if err := LinkAdd(&dummy); err != nil {
    82  		t.Errorf("Failed to create link: %v", err)
    83  	}
    84  	ensureIndex(dummy.Attrs())
    85  
    86  	entry := Neigh{
    87  		LinkIndex: dummy.Index,
    88  		State:     NUD_PERMANENT,
    89  		IP:        net.IPv4(198, 51, 100, 2),
    90  		LLIPAddr:  net.IPv4(198, 51, 100, 1),
    91  	}
    92  
    93  	err := NeighAdd(&entry)
    94  	if err != nil {
    95  		t.Errorf("Failed to NeighAdd: %v", err)
    96  	}
    97  
    98  	// Dump and see that all added entries are there
    99  	dump, err := NeighList(dummy.Index, 0)
   100  	if err != nil {
   101  		t.Errorf("Failed to NeighList: %v", err)
   102  	}
   103  
   104  	if !dumpContainsNeigh(dump, entry) {
   105  		t.Errorf("Dump does not contain: %v: %v", entry, dump)
   106  	}
   107  
   108  	// Delete the entry
   109  	err = NeighDel(&entry)
   110  	if err != nil {
   111  		t.Errorf("Failed to NeighDel: %v", err)
   112  	}
   113  
   114  	if err := LinkDel(&dummy); err != nil {
   115  		t.Fatal(err)
   116  	}
   117  }
   118  
   119  func TestNeighAddDel(t *testing.T) {
   120  	tearDown := setUpNetlinkTest(t)
   121  	defer tearDown()
   122  
   123  	dummy := Dummy{LinkAttrs{Name: "neigh0"}}
   124  	if err := LinkAdd(&dummy); err != nil {
   125  		t.Fatal(err)
   126  	}
   127  
   128  	ensureIndex(dummy.Attrs())
   129  
   130  	arpTable := []arpEntry{
   131  		{net.ParseIP("10.99.0.1"), parseMAC("aa:bb:cc:dd:00:01")},
   132  		{net.ParseIP("10.99.0.2"), parseMAC("aa:bb:cc:dd:00:02")},
   133  		{net.ParseIP("10.99.0.3"), parseMAC("aa:bb:cc:dd:00:03")},
   134  		{net.ParseIP("10.99.0.4"), parseMAC("aa:bb:cc:dd:00:04")},
   135  		{net.ParseIP("10.99.0.5"), parseMAC("aa:bb:cc:dd:00:05")},
   136  	}
   137  
   138  	// Add the arpTable
   139  	for _, entry := range arpTable {
   140  		err := NeighAdd(&Neigh{
   141  			LinkIndex:    dummy.Index,
   142  			State:        NUD_REACHABLE,
   143  			IP:           entry.ip,
   144  			HardwareAddr: entry.mac,
   145  		})
   146  
   147  		if err != nil {
   148  			t.Errorf("Failed to NeighAdd: %v", err)
   149  		}
   150  	}
   151  
   152  	// Dump and see that all added entries are there
   153  	dump, err := NeighList(dummy.Index, 0)
   154  	if err != nil {
   155  		t.Errorf("Failed to NeighList: %v", err)
   156  	}
   157  
   158  	for _, entry := range arpTable {
   159  		if !dumpContains(dump, entry) {
   160  			t.Errorf("Dump does not contain: %v", entry)
   161  		}
   162  	}
   163  
   164  	// Delete the arpTable
   165  	for _, entry := range arpTable {
   166  		err := NeighDel(&Neigh{
   167  			LinkIndex:    dummy.Index,
   168  			IP:           entry.ip,
   169  			HardwareAddr: entry.mac,
   170  		})
   171  
   172  		if err != nil {
   173  			t.Errorf("Failed to NeighDel: %v", err)
   174  		}
   175  	}
   176  
   177  	// TODO: seems not working because of cache
   178  	//// Dump and see that none of deleted entries are there
   179  	//dump, err = NeighList(dummy.Index, 0)
   180  	//if err != nil {
   181  	//t.Errorf("Failed to NeighList: %v", err)
   182  	//}
   183  
   184  	//for _, entry := range arpTable {
   185  	//if dumpContains(dump, entry) {
   186  	//t.Errorf("Dump contains: %v", entry)
   187  	//}
   188  	//}
   189  
   190  	if err := LinkDel(&dummy); err != nil {
   191  		t.Fatal(err)
   192  	}
   193  }
   194  
   195  func TestNeighAddDelProxy(t *testing.T) {
   196  	tearDown := setUpNetlinkTest(t)
   197  	defer tearDown()
   198  
   199  	dummy := Dummy{LinkAttrs{Name: "neigh0"}}
   200  	if err := LinkAdd(&dummy); err != nil {
   201  		t.Fatal(err)
   202  	}
   203  
   204  	ensureIndex(dummy.Attrs())
   205  
   206  	proxyTable := []proxyEntry{
   207  		{net.ParseIP("10.99.0.1"), dummy.Index},
   208  		{net.ParseIP("10.99.0.2"), dummy.Index},
   209  		{net.ParseIP("10.99.0.3"), dummy.Index},
   210  		{net.ParseIP("10.99.0.4"), dummy.Index},
   211  		{net.ParseIP("10.99.0.5"), dummy.Index},
   212  	}
   213  
   214  	// Add the proxyTable
   215  	for _, entry := range proxyTable {
   216  		err := NeighAdd(&Neigh{
   217  			LinkIndex: dummy.Index,
   218  			Flags:     NTF_PROXY,
   219  			IP:        entry.ip,
   220  		})
   221  
   222  		if err != nil {
   223  			t.Errorf("Failed to NeighAdd: %v", err)
   224  		}
   225  	}
   226  
   227  	// Dump and see that all added entries are there
   228  	dump, err := NeighProxyList(dummy.Index, 0)
   229  	if err != nil {
   230  		t.Errorf("Failed to NeighList: %v", err)
   231  	}
   232  
   233  	for _, entry := range proxyTable {
   234  		if !dumpContainsProxy(dump, entry) {
   235  			t.Errorf("Dump does not contain: %v", entry)
   236  		}
   237  	}
   238  
   239  	// Delete the proxyTable
   240  	for _, entry := range proxyTable {
   241  		err := NeighDel(&Neigh{
   242  			LinkIndex: dummy.Index,
   243  			Flags:     NTF_PROXY,
   244  			IP:        entry.ip,
   245  		})
   246  
   247  		if err != nil {
   248  			t.Errorf("Failed to NeighDel: %v", err)
   249  		}
   250  	}
   251  
   252  	// Dump and see that none of deleted entries are there
   253  	dump, err = NeighProxyList(dummy.Index, 0)
   254  	if err != nil {
   255  		t.Errorf("Failed to NeighList: %v", err)
   256  	}
   257  
   258  	for _, entry := range proxyTable {
   259  		if dumpContainsProxy(dump, entry) {
   260  			t.Errorf("Dump contains: %v", entry)
   261  		}
   262  	}
   263  
   264  	if err := LinkDel(&dummy); err != nil {
   265  		t.Fatal(err)
   266  	}
   267  }
   268  
   269  // expectNeighUpdate returns whether the expected updates are received within one second.
   270  func expectNeighUpdate(ch <-chan NeighUpdate, expected []NeighUpdate) bool {
   271  	for {
   272  		timeout := time.After(time.Second)
   273  		select {
   274  		case update := <-ch:
   275  			var toDelete []int
   276  			for index, elem := range expected {
   277  				if update.Type == elem.Type &&
   278  					update.Neigh.State == elem.Neigh.State &&
   279  					update.Neigh.IP != nil &&
   280  					update.Neigh.IP.Equal(elem.Neigh.IP) {
   281  					toDelete = append(toDelete, index)
   282  				}
   283  			}
   284  			for done, index := range toDelete {
   285  				expected = append(expected[:index-done], expected[index-done+1:]...)
   286  			}
   287  			if len(expected) == 0 {
   288  				return true
   289  			}
   290  		case <-timeout:
   291  			return false
   292  		}
   293  	}
   294  }
   295  
   296  func TestNeighSubscribe(t *testing.T) {
   297  	tearDown := setUpNetlinkTest(t)
   298  	defer tearDown()
   299  
   300  	dummy := &Dummy{LinkAttrs{Name: "neigh0"}}
   301  	if err := LinkAdd(dummy); err != nil {
   302  		t.Errorf("Failed to create link: %v", err)
   303  	}
   304  	ensureIndex(dummy.Attrs())
   305  	defer func() {
   306  		if err := LinkDel(dummy); err != nil {
   307  			t.Fatal(err)
   308  		}
   309  	}()
   310  
   311  	ch := make(chan NeighUpdate)
   312  	done := make(chan struct{})
   313  	defer close(done)
   314  	if err := NeighSubscribe(ch, done); err != nil {
   315  		t.Fatal(err)
   316  	}
   317  
   318  	entry := &Neigh{
   319  		LinkIndex:    dummy.Index,
   320  		State:        NUD_REACHABLE,
   321  		IP:           net.IPv4(10, 99, 0, 1),
   322  		HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"),
   323  	}
   324  
   325  	if err := NeighAdd(entry); err != nil {
   326  		t.Errorf("Failed to NeighAdd: %v", err)
   327  	}
   328  	if !expectNeighUpdate(ch, []NeighUpdate{{
   329  		Type:  unix.RTM_NEWNEIGH,
   330  		Neigh: *entry,
   331  	}}) {
   332  		t.Fatalf("Add update not received as expected")
   333  	}
   334  	if err := NeighDel(entry); err != nil {
   335  		t.Fatal(err)
   336  	}
   337  	if !expectNeighUpdate(ch, []NeighUpdate{{
   338  		Type: unix.RTM_NEWNEIGH,
   339  		Neigh: Neigh{
   340  			State: NUD_FAILED,
   341  			IP:    entry.IP},
   342  	}}) {
   343  		t.Fatalf("Del update not received as expected")
   344  	}
   345  }
   346  
   347  func TestNeighSubscribeWithOptions(t *testing.T) {
   348  	tearDown := setUpNetlinkTest(t)
   349  	defer tearDown()
   350  
   351  	ch := make(chan NeighUpdate)
   352  	done := make(chan struct{})
   353  	defer close(done)
   354  	var lastError error
   355  	defer func() {
   356  		if lastError != nil {
   357  			t.Fatalf("Fatal error received during subscription: %v", lastError)
   358  		}
   359  	}()
   360  	if err := NeighSubscribeWithOptions(ch, done, NeighSubscribeOptions{
   361  		ErrorCallback: func(err error) {
   362  			lastError = err
   363  		},
   364  	}); err != nil {
   365  		t.Fatal(err)
   366  	}
   367  
   368  	dummy := &Dummy{LinkAttrs{Name: "neigh0"}}
   369  	if err := LinkAdd(dummy); err != nil {
   370  		t.Errorf("Failed to create link: %v", err)
   371  	}
   372  	ensureIndex(dummy.Attrs())
   373  	defer func() {
   374  		if err := LinkDel(dummy); err != nil {
   375  			t.Fatal(err)
   376  		}
   377  	}()
   378  
   379  	entry := &Neigh{
   380  		LinkIndex:    dummy.Index,
   381  		State:        NUD_REACHABLE,
   382  		IP:           net.IPv4(10, 99, 0, 1),
   383  		HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"),
   384  	}
   385  
   386  	err := NeighAdd(entry)
   387  	if err != nil {
   388  		t.Errorf("Failed to NeighAdd: %v", err)
   389  	}
   390  	if !expectNeighUpdate(ch, []NeighUpdate{{
   391  		Type:  unix.RTM_NEWNEIGH,
   392  		Neigh: *entry,
   393  	}}) {
   394  		t.Fatalf("Add update not received as expected")
   395  	}
   396  }
   397  
   398  func TestNeighSubscribeAt(t *testing.T) {
   399  	skipUnlessRoot(t)
   400  
   401  	// Create an handle on a custom netns
   402  	newNs, err := netns.New()
   403  	if err != nil {
   404  		t.Fatal(err)
   405  	}
   406  	defer newNs.Close()
   407  
   408  	nh, err := NewHandleAt(newNs)
   409  	if err != nil {
   410  		t.Fatal(err)
   411  	}
   412  	defer nh.Close()
   413  
   414  	// Subscribe for Neigh events on the custom netns
   415  	ch := make(chan NeighUpdate)
   416  	done := make(chan struct{})
   417  	defer close(done)
   418  	if err := NeighSubscribeAt(newNs, ch, done); err != nil {
   419  		t.Fatal(err)
   420  	}
   421  
   422  	dummy := &Dummy{LinkAttrs{Name: "neigh0"}}
   423  	if err := nh.LinkAdd(dummy); err != nil {
   424  		t.Errorf("Failed to create link: %v", err)
   425  	}
   426  	ensureIndex(dummy.Attrs())
   427  	defer func() {
   428  		if err := nh.LinkDel(dummy); err != nil {
   429  			t.Fatal(err)
   430  		}
   431  	}()
   432  
   433  	entry := &Neigh{
   434  		LinkIndex:    dummy.Index,
   435  		State:        NUD_REACHABLE,
   436  		IP:           net.IPv4(198, 51, 100, 1),
   437  		HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"),
   438  	}
   439  
   440  	err = nh.NeighAdd(entry)
   441  	if err != nil {
   442  		t.Errorf("Failed to NeighAdd: %v", err)
   443  	}
   444  	if !expectNeighUpdate(ch, []NeighUpdate{{
   445  		Type:  unix.RTM_NEWNEIGH,
   446  		Neigh: *entry,
   447  	}}) {
   448  		t.Fatalf("Add update not received as expected")
   449  	}
   450  }
   451  
   452  func TestNeighSubscribeListExisting(t *testing.T) {
   453  	skipUnlessRoot(t)
   454  
   455  	// Create an handle on a custom netns
   456  	newNs, err := netns.New()
   457  	if err != nil {
   458  		t.Fatal(err)
   459  	}
   460  	defer newNs.Close()
   461  
   462  	nh, err := NewHandleAt(newNs)
   463  	if err != nil {
   464  		t.Fatal(err)
   465  	}
   466  	defer nh.Close()
   467  
   468  	dummy := &Dummy{LinkAttrs{Name: "neigh0"}}
   469  	if err := nh.LinkAdd(dummy); err != nil {
   470  		t.Errorf("Failed to create link: %v", err)
   471  	}
   472  	ensureIndex(dummy.Attrs())
   473  	defer func() {
   474  		if err := nh.LinkDel(dummy); err != nil {
   475  			t.Fatal(err)
   476  		}
   477  	}()
   478  
   479  	vxlani := &Vxlan{LinkAttrs: LinkAttrs{Name: "neigh1"}, VxlanId: 1}
   480  	if err := nh.LinkAdd(vxlani); err != nil {
   481  		t.Errorf("Failed to create link: %v", err)
   482  	}
   483  	ensureIndex(vxlani.Attrs())
   484  	defer func() {
   485  		if err := nh.LinkDel(vxlani); err != nil {
   486  			t.Fatal(err)
   487  		}
   488  	}()
   489  
   490  	entry1 := &Neigh{
   491  		LinkIndex:    dummy.Index,
   492  		State:        NUD_REACHABLE,
   493  		IP:           net.IPv4(198, 51, 100, 1),
   494  		HardwareAddr: parseMAC("aa:bb:cc:dd:00:01"),
   495  	}
   496  
   497  	entryBr := &Neigh{
   498  		Family:       syscall.AF_BRIDGE,
   499  		LinkIndex:    vxlani.Index,
   500  		State:        NUD_PERMANENT,
   501  		Flags:        NTF_SELF,
   502  		IP:           net.IPv4(198, 51, 100, 3),
   503  		HardwareAddr: parseMAC("aa:bb:cc:dd:00:03"),
   504  	}
   505  
   506  	err = nh.NeighAdd(entry1)
   507  	if err != nil {
   508  		t.Errorf("Failed to NeighAdd: %v", err)
   509  	}
   510  	err = nh.NeighAppend(entryBr)
   511  	if err != nil {
   512  		t.Errorf("Failed to NeighAdd: %v", err)
   513  	}
   514  
   515  	// Subscribe for Neigh events including existing neighbors
   516  	ch := make(chan NeighUpdate)
   517  	done := make(chan struct{})
   518  	defer close(done)
   519  	if err := NeighSubscribeWithOptions(ch, done, NeighSubscribeOptions{
   520  		Namespace:    &newNs,
   521  		ListExisting: true},
   522  	); err != nil {
   523  		t.Fatal(err)
   524  	}
   525  
   526  	if !expectNeighUpdate(ch, []NeighUpdate{
   527  		{
   528  			Type:  unix.RTM_NEWNEIGH,
   529  			Neigh: *entry1,
   530  		},
   531  		{
   532  			Type:  unix.RTM_NEWNEIGH,
   533  			Neigh: *entryBr,
   534  		},
   535  	}) {
   536  		t.Fatalf("Existing add update not received as expected")
   537  	}
   538  
   539  	entry2 := &Neigh{
   540  		LinkIndex:    dummy.Index,
   541  		State:        NUD_PERMANENT,
   542  		IP:           net.IPv4(198, 51, 100, 2),
   543  		HardwareAddr: parseMAC("aa:bb:cc:dd:00:02"),
   544  	}
   545  
   546  	err = nh.NeighAdd(entry2)
   547  	if err != nil {
   548  		t.Errorf("Failed to NeighAdd: %v", err)
   549  	}
   550  
   551  	if !expectNeighUpdate(ch, []NeighUpdate{{
   552  		Type:  unix.RTM_NEWNEIGH,
   553  		Neigh: *entry2,
   554  	}}) {
   555  		t.Fatalf("Existing add update not received as expected")
   556  	}
   557  }
   558  
   559  func TestNeighListExecuteStateFilter(t *testing.T) {
   560  	tearDown := setUpNetlinkTest(t)
   561  	defer tearDown()
   562  
   563  	// Create dummy iface
   564  	dummy := Dummy{LinkAttrs{Name: "neigh0"}}
   565  	if err := LinkAdd(&dummy); err != nil {
   566  		t.Fatal(err)
   567  	}
   568  
   569  	ensureIndex(dummy.Attrs())
   570  
   571  	// Define some entries
   572  	reachArpTable := []arpEntry{
   573  		{net.ParseIP("198.51.100.1"), parseMAC("44:bb:cc:dd:00:01")},
   574  		{net.ParseIP("2001:db8::1"), parseMAC("66:bb:cc:dd:00:02")},
   575  	}
   576  
   577  	staleArpTable := []arpEntry{
   578  		{net.ParseIP("198.51.100.10"), parseMAC("44:bb:cc:dd:00:10")},
   579  		{net.ParseIP("2001:db8::10"), parseMAC("66:bb:cc:dd:00:10")},
   580  	}
   581  
   582  	entries := append(reachArpTable, staleArpTable...)
   583  
   584  	// Add reachable neigh entries
   585  	for _, entry := range reachArpTable {
   586  		err := NeighAdd(&Neigh{
   587  			LinkIndex:    dummy.Index,
   588  			State:        NUD_REACHABLE,
   589  			IP:           entry.ip,
   590  			HardwareAddr: entry.mac,
   591  		})
   592  
   593  		if err != nil {
   594  			t.Errorf("Failed to NeighAdd: %v", err)
   595  		}
   596  	}
   597  	// Add stale neigh entries
   598  	for _, entry := range staleArpTable {
   599  		err := NeighAdd(&Neigh{
   600  			LinkIndex:    dummy.Index,
   601  			State:        NUD_STALE,
   602  			IP:           entry.ip,
   603  			HardwareAddr: entry.mac,
   604  		})
   605  
   606  		if err != nil {
   607  			t.Errorf("Failed to NeighAdd: %v", err)
   608  		}
   609  	}
   610  
   611  	// Dump reachable and see that all added reachable entries are present and there are no stale entries
   612  	dump, err := NeighListExecute(Ndmsg{
   613  		Index: uint32(dummy.Index),
   614  		State: NUD_REACHABLE,
   615  	})
   616  	if err != nil {
   617  		t.Errorf("Failed to NeighListExecute: %v", err)
   618  	}
   619  
   620  	for _, entry := range reachArpTable {
   621  		if !dumpContainsState(dump, entry, NUD_REACHABLE) {
   622  			t.Errorf("Dump does not contains: %v", entry)
   623  		}
   624  	}
   625  	for _, entry := range staleArpTable {
   626  		if dumpContainsState(dump, entry, NUD_STALE) {
   627  			t.Errorf("Dump contains: %v", entry)
   628  		}
   629  	}
   630  
   631  	// Delete all neigh entries
   632  	for _, entry := range entries {
   633  		err := NeighDel(&Neigh{
   634  			LinkIndex:    dummy.Index,
   635  			IP:           entry.ip,
   636  			HardwareAddr: entry.mac,
   637  		})
   638  
   639  		if err != nil {
   640  			t.Errorf("Failed to NeighDel: %v", err)
   641  		}
   642  	}
   643  }