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

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