github.com/vishvananda/netlink@v1.3.1/rule_test.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  package netlink
     5  
     6  import (
     7  	"net"
     8  	"testing"
     9  	"time"
    10  
    11  	"golang.org/x/sys/unix"
    12  )
    13  
    14  func TestRuleAddDel(t *testing.T) {
    15  	skipUnlessRoot(t)
    16  	defer setUpNetlinkTest(t)()
    17  
    18  	srcNet := &net.IPNet{IP: net.IPv4(172, 16, 0, 1), Mask: net.CIDRMask(16, 32)}
    19  	dstNet := &net.IPNet{IP: net.IPv4(172, 16, 1, 1), Mask: net.CIDRMask(24, 32)}
    20  
    21  	rulesBegin, err := RuleList(FAMILY_V4)
    22  	if err != nil {
    23  		t.Fatal(err)
    24  	}
    25  
    26  	rule := NewRule()
    27  	rule.Family = FAMILY_V4
    28  	rule.Table = unix.RT_TABLE_MAIN
    29  	rule.Src = srcNet
    30  	rule.Dst = dstNet
    31  	rule.Priority = 5
    32  	rule.OifName = "lo"
    33  	rule.IifName = "lo"
    34  	rule.Invert = true
    35  	rule.Tos = 0x10
    36  	rule.Dport = NewRulePortRange(80, 80)
    37  	rule.Sport = NewRulePortRange(1000, 1024)
    38  	rule.IPProto = unix.IPPROTO_UDP
    39  	rule.UIDRange = NewRuleUIDRange(100, 100)
    40  	rule.Protocol = unix.RTPROT_KERNEL
    41  	if err := RuleAdd(rule); err != nil {
    42  		t.Fatal(err)
    43  	}
    44  
    45  	rules, err := RuleList(FAMILY_V4)
    46  	if err != nil {
    47  		t.Fatal(err)
    48  	}
    49  
    50  	if len(rules) != len(rulesBegin)+1 {
    51  		t.Fatal("Rule not added properly")
    52  	}
    53  
    54  	// find this rule
    55  	found := ruleExists(rules, *rule)
    56  	if !found {
    57  		t.Fatal("Rule has diffrent options than one added")
    58  	}
    59  
    60  	if err := RuleDel(rule); err != nil {
    61  		t.Fatal(err)
    62  	}
    63  
    64  	rulesEnd, err := RuleList(FAMILY_V4)
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  
    69  	if len(rulesEnd) != len(rulesBegin) {
    70  		t.Fatal("Rule not removed properly")
    71  	}
    72  }
    73  
    74  func TestRuleListFiltered(t *testing.T) {
    75  	skipUnlessRoot(t)
    76  	defer setUpNetlinkTest(t)()
    77  
    78  	t.Run("IPv4", testRuleListFilteredIPv4)
    79  	t.Run("IPv6", testRuleListFilteredIPv6)
    80  }
    81  
    82  func testRuleListFilteredIPv4(t *testing.T) {
    83  	srcNet := &net.IPNet{IP: net.IPv4(172, 16, 0, 1), Mask: net.CIDRMask(16, 32)}
    84  	dstNet := &net.IPNet{IP: net.IPv4(172, 16, 1, 1), Mask: net.CIDRMask(24, 32)}
    85  	runRuleListFiltered(t, FAMILY_V4, srcNet, dstNet)
    86  }
    87  
    88  func testRuleListFilteredIPv6(t *testing.T) {
    89  	ip1 := net.ParseIP("fd56:6b58:db28:2913::")
    90  	ip2 := net.ParseIP("fde9:379f:3b35:6635::")
    91  
    92  	srcNet := &net.IPNet{IP: ip1, Mask: net.CIDRMask(64, 128)}
    93  	dstNet := &net.IPNet{IP: ip2, Mask: net.CIDRMask(96, 128)}
    94  	runRuleListFiltered(t, FAMILY_V6, srcNet, dstNet)
    95  }
    96  
    97  func runRuleListFiltered(t *testing.T, family int, srcNet, dstNet *net.IPNet) {
    98  	defaultRules, _ := RuleList(family)
    99  
   100  	tests := []struct {
   101  		name       string
   102  		ruleFilter *Rule
   103  		filterMask uint64
   104  		preRun     func() *Rule // Creates sample rule harness
   105  		postRun    func(*Rule)  // Deletes sample rule harness
   106  		setupWant  func(*Rule) ([]Rule, bool)
   107  	}{
   108  		{
   109  			name:       "returns all rules",
   110  			ruleFilter: nil,
   111  			filterMask: 0,
   112  			preRun:     func() *Rule { return nil },
   113  			postRun:    func(r *Rule) {},
   114  			setupWant: func(_ *Rule) ([]Rule, bool) {
   115  				return defaultRules, false
   116  			},
   117  		},
   118  		{
   119  			name:       "returns one rule filtered by Src",
   120  			ruleFilter: &Rule{Src: srcNet},
   121  			filterMask: RT_FILTER_SRC,
   122  			preRun: func() *Rule {
   123  				r := NewRule()
   124  				r.Src = srcNet
   125  				r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
   126  				r.Family = family
   127  				r.Table = 1
   128  				RuleAdd(r)
   129  				return r
   130  			},
   131  			postRun: func(r *Rule) { RuleDel(r) },
   132  			setupWant: func(r *Rule) ([]Rule, bool) {
   133  				return []Rule{*r}, false
   134  			},
   135  		},
   136  		{
   137  			name:       "returns one rule filtered by Dst",
   138  			ruleFilter: &Rule{Dst: dstNet},
   139  			filterMask: RT_FILTER_DST,
   140  			preRun: func() *Rule {
   141  				r := NewRule()
   142  				r.Dst = dstNet
   143  				r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
   144  				r.Family = family
   145  				r.Table = 1
   146  				RuleAdd(r)
   147  				return r
   148  			},
   149  			postRun: func(r *Rule) { RuleDel(r) },
   150  			setupWant: func(r *Rule) ([]Rule, bool) {
   151  				return []Rule{*r}, false
   152  			},
   153  		},
   154  		{
   155  			name:       "returns two rules filtered by Dst",
   156  			ruleFilter: &Rule{Dst: dstNet},
   157  			filterMask: RT_FILTER_DST,
   158  			preRun: func() *Rule {
   159  				r := NewRule()
   160  				r.Dst = dstNet
   161  				r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
   162  				r.Family = family
   163  				r.Table = 1
   164  				RuleAdd(r)
   165  
   166  				rc := *r // Create almost identical copy
   167  				rc.Src = srcNet
   168  				RuleAdd(&rc)
   169  
   170  				return r
   171  			},
   172  			postRun: func(r *Rule) {
   173  				RuleDel(r)
   174  
   175  				rc := *r // Delete the almost identical copy
   176  				rc.Src = srcNet
   177  				RuleDel(&rc)
   178  			},
   179  			setupWant: func(r *Rule) ([]Rule, bool) {
   180  				rs := []Rule{}
   181  				rs = append(rs, *r)
   182  
   183  				rc := *r // Append the almost identical copy
   184  				rc.Src = srcNet
   185  				rs = append(rs, rc)
   186  
   187  				return rs, false
   188  			},
   189  		},
   190  		{
   191  			name:       "returns one rule filtered by Src when two rules exist",
   192  			ruleFilter: &Rule{Src: srcNet},
   193  			filterMask: RT_FILTER_SRC,
   194  			preRun: func() *Rule {
   195  				r := NewRule()
   196  				r.Dst = dstNet
   197  				r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
   198  				r.Family = family
   199  				r.Table = 1
   200  				RuleAdd(r)
   201  
   202  				rc := *r // Create almost identical copy
   203  				rc.Src = srcNet
   204  				RuleAdd(&rc)
   205  
   206  				return r
   207  			},
   208  			postRun: func(r *Rule) {
   209  				RuleDel(r)
   210  
   211  				rc := *r // Delete the almost identical copy
   212  				rc.Src = srcNet
   213  				RuleDel(&rc)
   214  			},
   215  			setupWant: func(r *Rule) ([]Rule, bool) {
   216  				rs := []Rule{}
   217  				// Do not append `r`
   218  
   219  				rc := *r // Append the almost identical copy
   220  				rc.Src = srcNet
   221  				rs = append(rs, rc)
   222  
   223  				return rs, false
   224  			},
   225  		},
   226  		{
   227  			name:       "returns one rule filtered by Priority(0) and Table",
   228  			ruleFilter: &Rule{Priority: 0, Table: 1},
   229  			filterMask: RT_FILTER_PRIORITY | RT_FILTER_TABLE,
   230  			preRun: func() *Rule {
   231  				r := NewRule()
   232  				r.Src = srcNet
   233  				r.Priority = 0
   234  				r.Family = family
   235  				r.Table = 1
   236  				RuleAdd(r)
   237  				return r
   238  			},
   239  			postRun: func(r *Rule) {
   240  				RuleDel(r)
   241  			},
   242  			setupWant: func(r *Rule) ([]Rule, bool) {
   243  				return []Rule{*r}, false
   244  			},
   245  		},
   246  		{
   247  			name:       "returns one rule filtered by Priority preceding main-table rule",
   248  			ruleFilter: &Rule{Priority: 32765},
   249  			filterMask: RT_FILTER_PRIORITY,
   250  			preRun: func() *Rule {
   251  				r := NewRule()
   252  				r.Src = srcNet
   253  				r.Family = family
   254  				r.Table = 1
   255  				RuleAdd(r)
   256  
   257  				r.Priority = 32765 // Set priority for assertion
   258  				return r
   259  			},
   260  			postRun: func(r *Rule) {
   261  				RuleDel(r)
   262  			},
   263  			setupWant: func(r *Rule) ([]Rule, bool) {
   264  				return []Rule{*r}, false
   265  			},
   266  		},
   267  		{
   268  			name:       "returns rules with specific priority",
   269  			ruleFilter: &Rule{Priority: 5},
   270  			filterMask: RT_FILTER_PRIORITY,
   271  			preRun: func() *Rule {
   272  				r := NewRule()
   273  				r.Src = srcNet
   274  				r.Priority = 5
   275  				r.Family = family
   276  				r.Table = 1
   277  				RuleAdd(r)
   278  
   279  				for i := 2; i < 5; i++ {
   280  					rc := *r // Create almost identical copy
   281  					rc.Table = i
   282  					RuleAdd(&rc)
   283  				}
   284  
   285  				return r
   286  			},
   287  			postRun: func(r *Rule) {
   288  				RuleDel(r)
   289  
   290  				for i := 2; i < 5; i++ {
   291  					rc := *r // Delete the almost identical copy
   292  					rc.Table = -1
   293  					RuleDel(&rc)
   294  				}
   295  			},
   296  			setupWant: func(r *Rule) ([]Rule, bool) {
   297  				rs := []Rule{}
   298  				rs = append(rs, *r)
   299  
   300  				for i := 2; i < 5; i++ {
   301  					rc := *r // Append the almost identical copy
   302  					rc.Table = i
   303  					rs = append(rs, rc)
   304  				}
   305  
   306  				return rs, false
   307  			},
   308  		},
   309  		{
   310  			name:       "returns rules filtered by Table",
   311  			ruleFilter: &Rule{Table: 199},
   312  			filterMask: RT_FILTER_TABLE,
   313  			preRun: func() *Rule {
   314  				r := NewRule()
   315  				r.Src = srcNet
   316  				r.Priority = 1 // Must add priority otherwise it's auto-assigned
   317  				r.Family = family
   318  				r.Table = 199
   319  				RuleAdd(r)
   320  				return r
   321  			},
   322  			postRun: func(r *Rule) { RuleDel(r) },
   323  			setupWant: func(r *Rule) ([]Rule, bool) {
   324  				return []Rule{*r}, false
   325  			},
   326  		},
   327  		{
   328  			name:       "returns rules filtered by Mask",
   329  			ruleFilter: &Rule{Mask: &[]uint32{0x5}[0]},
   330  			filterMask: RT_FILTER_MASK,
   331  			preRun: func() *Rule {
   332  				r := NewRule()
   333  				r.Src = srcNet
   334  				r.Priority = 1 // Must add priority and table otherwise it's auto-assigned
   335  				r.Family = family
   336  				r.Table = 1
   337  				r.Mask = &[]uint32{0x5}[0]
   338  				RuleAdd(r)
   339  				return r
   340  			},
   341  			postRun: func(r *Rule) { RuleDel(r) },
   342  			setupWant: func(r *Rule) ([]Rule, bool) {
   343  				return []Rule{*r}, false
   344  			},
   345  		},
   346  		{
   347  			name:       "returns rules filtered by Mark",
   348  			ruleFilter: &Rule{Mark: 0xbb},
   349  			filterMask: RT_FILTER_MARK,
   350  			preRun: func() *Rule {
   351  				r := NewRule()
   352  				r.Src = srcNet
   353  				r.Priority = 1 // Must add priority, table, mask otherwise it's auto-assigned
   354  				r.Family = family
   355  				r.Table = 1
   356  				r.Mask = &[]uint32{0xff}[0]
   357  				r.Mark = 0xbb
   358  				RuleAdd(r)
   359  				return r
   360  			},
   361  			postRun: func(r *Rule) { RuleDel(r) },
   362  			setupWant: func(r *Rule) ([]Rule, bool) {
   363  				return []Rule{*r}, false
   364  			},
   365  		},
   366  		{
   367  			name:       "returns rules filtered by fwmark 0",
   368  			ruleFilter: &Rule{Mark: 0, Mask: nil, Table: 100},
   369  			filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE,
   370  			preRun: func() *Rule {
   371  				r := NewRule()
   372  				r.Src = srcNet
   373  				r.Priority = 1
   374  				r.Family = family
   375  				r.Table = 100
   376  				r.Mark = 0
   377  				r.Mask = nil
   378  				if err := RuleAdd(r); err != nil {
   379  					t.Fatal(err)
   380  				}
   381  				return r
   382  			},
   383  			postRun: func(r *Rule) { RuleDel(r) },
   384  			setupWant: func(r *Rule) ([]Rule, bool) {
   385  				return []Rule{*r}, false
   386  			},
   387  		},
   388  		{
   389  			name:       "returns rules filtered by fwmark 0/0xFFFFFFFF",
   390  			ruleFilter: &Rule{Mark: 0, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100},
   391  			filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE,
   392  			preRun: func() *Rule {
   393  				r := NewRule()
   394  				r.Src = srcNet
   395  				r.Priority = 1
   396  				r.Family = family
   397  				r.Table = 100
   398  				r.Mark = 0
   399  				r.Mask = &[]uint32{0xFFFFFFFF}[0]
   400  				if err := RuleAdd(r); err != nil {
   401  					t.Fatal(err)
   402  				}
   403  				return r
   404  			},
   405  			postRun: func(r *Rule) { RuleDel(r) },
   406  			setupWant: func(r *Rule) ([]Rule, bool) {
   407  				return []Rule{*r}, false
   408  			},
   409  		},
   410  		{
   411  			name:       "returns rules filtered by fwmark 0x1234/0",
   412  			ruleFilter: &Rule{Mark: 0x1234, Mask: &[]uint32{0}[0], Table: 100},
   413  			filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE,
   414  			preRun: func() *Rule {
   415  				r := NewRule()
   416  				r.Src = srcNet
   417  				r.Priority = 1
   418  				r.Family = family
   419  				r.Table = 100
   420  				r.Mark = 0x1234
   421  				r.Mask = &[]uint32{0}[0]
   422  				if err := RuleAdd(r); err != nil {
   423  					t.Fatal(err)
   424  				}
   425  				return r
   426  			},
   427  			postRun: func(r *Rule) { RuleDel(r) },
   428  			setupWant: func(r *Rule) ([]Rule, bool) {
   429  				return []Rule{*r}, false
   430  			},
   431  		},
   432  		{
   433  			name:       "returns rules filtered by fwmark 0/0xFFFFFFFF",
   434  			ruleFilter: &Rule{Mark: 0, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100},
   435  			filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE,
   436  			preRun: func() *Rule {
   437  				r := NewRule()
   438  				r.Src = srcNet
   439  				r.Priority = 1
   440  				r.Family = family
   441  				r.Table = 100
   442  				r.Mark = 0
   443  				r.Mask = &[]uint32{0xFFFFFFFF}[0]
   444  				if err := RuleAdd(r); err != nil {
   445  					t.Fatal(err)
   446  				}
   447  				return r
   448  			},
   449  			postRun: func(r *Rule) { RuleDel(r) },
   450  			setupWant: func(r *Rule) ([]Rule, bool) {
   451  				return []Rule{*r}, false
   452  			},
   453  		},
   454  		{
   455  			name:       "returns rules filtered by fwmark 0xFFFFFFFF",
   456  			ruleFilter: &Rule{Mark: 0xFFFFFFFF, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100},
   457  			filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE,
   458  			preRun: func() *Rule {
   459  				r := NewRule()
   460  				r.Src = srcNet
   461  				r.Priority = 1
   462  				r.Family = family
   463  				r.Table = 100
   464  				r.Mark = 0xFFFFFFFF
   465  				r.Mask = nil
   466  				if err := RuleAdd(r); err != nil {
   467  					t.Fatal(err)
   468  				}
   469  				return r
   470  			},
   471  			postRun: func(r *Rule) { RuleDel(r) },
   472  			setupWant: func(r *Rule) ([]Rule, bool) {
   473  				return []Rule{*r}, false
   474  			},
   475  		},
   476  		{
   477  			name:       "returns rules filtered by fwmark 0x1234",
   478  			ruleFilter: &Rule{Mark: 0x1234, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100},
   479  			filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE,
   480  			preRun: func() *Rule {
   481  				r := NewRule()
   482  				r.Src = srcNet
   483  				r.Priority = 1
   484  				r.Family = family
   485  				r.Table = 100
   486  				r.Mark = 0x1234
   487  				r.Mask = nil
   488  				if err := RuleAdd(r); err != nil {
   489  					t.Fatal(err)
   490  				}
   491  				return r
   492  			},
   493  			postRun: func(r *Rule) { RuleDel(r) },
   494  			setupWant: func(r *Rule) ([]Rule, bool) {
   495  				return []Rule{*r}, false
   496  			},
   497  		},
   498  		{
   499  			name:       "returns rules filtered by fwmark 0x12345678",
   500  			ruleFilter: &Rule{Mark: 0x12345678, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100},
   501  			filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE,
   502  			preRun: func() *Rule {
   503  				r := NewRule()
   504  				r.Src = srcNet
   505  				r.Priority = 1
   506  				r.Family = family
   507  				r.Table = 100
   508  				r.Mark = 0x12345678
   509  				r.Mask = nil
   510  				if err := RuleAdd(r); err != nil {
   511  					t.Fatal(err)
   512  				}
   513  				return r
   514  			},
   515  			postRun: func(r *Rule) { RuleDel(r) },
   516  			setupWant: func(r *Rule) ([]Rule, bool) {
   517  				return []Rule{*r}, false
   518  			},
   519  		},
   520  		{
   521  			name:       "returns rules filtered by fwmark 0xFFFFFFFF/0",
   522  			ruleFilter: &Rule{Mark: 0xFFFFFFFF, Mask: &[]uint32{0}[0], Table: 100},
   523  			filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE,
   524  			preRun: func() *Rule {
   525  				r := NewRule()
   526  				r.Src = srcNet
   527  				r.Priority = 1
   528  				r.Family = family
   529  				r.Table = 100
   530  				r.Mark = 0xFFFFFFFF
   531  				r.Mask = &[]uint32{0}[0]
   532  				if err := RuleAdd(r); err != nil {
   533  					t.Fatal(err)
   534  				}
   535  				return r
   536  			},
   537  			postRun: func(r *Rule) { RuleDel(r) },
   538  			setupWant: func(r *Rule) ([]Rule, bool) {
   539  				return []Rule{*r}, false
   540  			},
   541  		},
   542  		{
   543  			name:       "returns rules filtered by fwmark 0xFFFFFFFF/0xFFFFFFFF",
   544  			ruleFilter: &Rule{Mark: 0xFFFFFFFF, Mask: &[]uint32{0xFFFFFFFF}[0], Table: 100},
   545  			filterMask: RT_FILTER_MARK | RT_FILTER_MASK | RT_FILTER_TABLE,
   546  			preRun: func() *Rule {
   547  				r := NewRule()
   548  				r.Src = srcNet
   549  				r.Priority = 1
   550  				r.Family = family
   551  				r.Table = 100
   552  				r.Mark = 0xFFFFFFFF
   553  				r.Mask = &[]uint32{0xFFFFFFFF}[0]
   554  				if err := RuleAdd(r); err != nil {
   555  					t.Fatal(err)
   556  				}
   557  				return r
   558  			},
   559  			postRun: func(r *Rule) { RuleDel(r) },
   560  			setupWant: func(r *Rule) ([]Rule, bool) {
   561  				return []Rule{*r}, false
   562  			},
   563  		},
   564  		{
   565  			name:       "returns rules filtered by Tos",
   566  			ruleFilter: &Rule{Tos: 12},
   567  			filterMask: RT_FILTER_TOS,
   568  			preRun: func() *Rule {
   569  				r := NewRule()
   570  				r.Src = srcNet
   571  				r.Priority = 1 // Must add priority, table, mask otherwise it's auto-assigned
   572  				r.Family = family
   573  				r.Table = 12
   574  				r.Tos = 12 // Tos must equal table
   575  				RuleAdd(r)
   576  				return r
   577  			},
   578  			postRun: func(r *Rule) { RuleDel(r) },
   579  			setupWant: func(r *Rule) ([]Rule, bool) {
   580  				return []Rule{*r}, false
   581  			},
   582  		},
   583  	}
   584  	for _, tt := range tests {
   585  		t.Run(tt.name, func(t *testing.T) {
   586  			rule := tt.preRun()
   587  			wantRules, wantErr := tt.setupWant(rule)
   588  
   589  			rules, err := RuleListFiltered(family, tt.ruleFilter, tt.filterMask)
   590  			for i := 0; i < len(wantRules); i++ {
   591  				if len(wantRules) == len(rules) {
   592  					break
   593  				}
   594  				time.Sleep(1 * time.Second) // wait rule take effect
   595  				rules, err = RuleListFiltered(family, tt.ruleFilter, tt.filterMask)
   596  			}
   597  			tt.postRun(rule)
   598  
   599  			if len(wantRules) != len(rules) {
   600  				t.Errorf("Expected len: %d, got: %d", len(wantRules), len(rules))
   601  			} else {
   602  				for i := range wantRules {
   603  					if !ruleEquals(wantRules[i], rules[i]) {
   604  						t.Errorf("Rules mismatch, want %v, got %v", wantRules[i], rules[i])
   605  					}
   606  				}
   607  			}
   608  
   609  			if (err != nil) != wantErr {
   610  				t.Errorf("Error expectation not met, want %v, got %v", (err != nil), wantErr)
   611  			}
   612  		})
   613  	}
   614  }
   615  
   616  func TestRuleString(t *testing.T) {
   617  	t.Parallel()
   618  
   619  	testCases := map[string]struct {
   620  		r Rule
   621  		s string
   622  	}{
   623  		"empty rule": {
   624  			s: "ip rule 0: from all to all table 0 ",
   625  		},
   626  		"rule with src and dst equivalent to <nil>": {
   627  			r: Rule{
   628  				Priority: 100,
   629  				Src:      &net.IPNet{IP: net.IPv4(10, 0, 0, 0)},
   630  				Dst:      &net.IPNet{IP: net.IPv4(20, 0, 0, 0)},
   631  				Table:    99,
   632  			},
   633  			s: "ip rule 100: from all to all table 99 ",
   634  		},
   635  		"rule with src and dst": {
   636  			r: Rule{
   637  				Priority: 100,
   638  				Src:      &net.IPNet{IP: net.IPv4(10, 0, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
   639  				Dst:      &net.IPNet{IP: net.IPv4(20, 0, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)},
   640  				Table:    99,
   641  			},
   642  			s: "ip rule 100: from 10.0.0.0/24 to 20.0.0.0/24 table 99 ",
   643  		},
   644  		"rule with type": {
   645  			r: Rule{
   646  				Priority: 101,
   647  				Type:     unix.RTN_UNREACHABLE,
   648  			},
   649  			s: "ip rule 101: from all to all table 0 unreachable",
   650  		},
   651  	}
   652  
   653  	for name, testCase := range testCases {
   654  		testCase := testCase
   655  		t.Run(name, func(t *testing.T) {
   656  			t.Parallel()
   657  
   658  			s := testCase.r.String()
   659  
   660  			if s != testCase.s {
   661  				t.Errorf("expected %q but got %q", testCase.s, s)
   662  			}
   663  		})
   664  	}
   665  }
   666  
   667  func ruleExists(rules []Rule, rule Rule) bool {
   668  	for i := range rules {
   669  		if ruleEquals(rules[i], rule) {
   670  			return true
   671  		}
   672  	}
   673  
   674  	return false
   675  }
   676  
   677  func ruleEquals(a, b Rule) bool {
   678  	return a.Table == b.Table &&
   679  		((a.Src == nil && b.Src == nil) ||
   680  			(a.Src != nil && b.Src != nil && a.Src.String() == b.Src.String())) &&
   681  		((a.Dst == nil && b.Dst == nil) ||
   682  			(a.Dst != nil && b.Dst != nil && a.Dst.String() == b.Dst.String())) &&
   683  		a.OifName == b.OifName &&
   684  		a.Priority == b.Priority &&
   685  		a.Family == b.Family &&
   686  		a.IifName == b.IifName &&
   687  		a.Invert == b.Invert &&
   688  		a.Tos == b.Tos &&
   689  		a.Type == b.Type &&
   690  		a.IPProto == b.IPProto &&
   691  		a.Protocol == b.Protocol &&
   692  		a.Mark == b.Mark &&
   693  		(ptrEqual(a.Mask, b.Mask) || (a.Mark != 0 &&
   694  			(a.Mask == nil && *b.Mask == 0xFFFFFFFF || b.Mask == nil && *a.Mask == 0xFFFFFFFF)))
   695  }