k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/proxy/ipvs/ipset/ipset_test.go (about)

     1  //go:build linux
     2  // +build linux
     3  
     4  /*
     5  Copyright 2017 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package ipset
    21  
    22  import (
    23  	"reflect"
    24  	"testing"
    25  
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	"k8s.io/utils/exec"
    28  	fakeexec "k8s.io/utils/exec/testing"
    29  )
    30  
    31  func TestCheckIPSetVersion(t *testing.T) {
    32  	testCases := []struct {
    33  		vstring string
    34  		Expect  string
    35  		Err     bool
    36  	}{
    37  		{"ipset v4.0, protocol version: 4", "v4.0", false},
    38  		{"ipset v5.1, protocol version: 5", "v5.1", false},
    39  		{"ipset v6.0, protocol version: 6", "v6.0", false},
    40  		{"ipset v6.1, protocol version: 6", "v6.1", false},
    41  		{"ipset v6.19, protocol version: 6", "v6.19", false},
    42  		{"total junk", "", true},
    43  	}
    44  
    45  	for i := range testCases {
    46  		fcmd := fakeexec.FakeCmd{
    47  			CombinedOutputScript: []fakeexec.FakeAction{
    48  				// ipset version response
    49  				func() ([]byte, []byte, error) { return []byte(testCases[i].vstring), nil, nil },
    50  			},
    51  		}
    52  
    53  		fexec := &fakeexec.FakeExec{
    54  			CommandScript: []fakeexec.FakeCommandAction{
    55  				func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
    56  			},
    57  		}
    58  
    59  		gotVersion, err := getIPSetVersionString(fexec)
    60  		if (err != nil) != testCases[i].Err {
    61  			t.Errorf("Expected error: %v, Got error: %v", testCases[i].Err, err)
    62  		}
    63  		if err == nil {
    64  			if testCases[i].Expect != gotVersion {
    65  				t.Errorf("Expected result: %v, Got result: %v", testCases[i].Expect, gotVersion)
    66  			}
    67  		}
    68  	}
    69  }
    70  
    71  func TestFlushSet(t *testing.T) {
    72  	fcmd := fakeexec.FakeCmd{
    73  		CombinedOutputScript: []fakeexec.FakeAction{
    74  			// Success
    75  			func() ([]byte, []byte, error) { return []byte{}, nil, nil },
    76  			// Success
    77  			func() ([]byte, []byte, error) { return []byte{}, nil, nil },
    78  		},
    79  	}
    80  	fexec := &fakeexec.FakeExec{
    81  		CommandScript: []fakeexec.FakeCommandAction{
    82  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
    83  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
    84  		},
    85  	}
    86  	runner := New(fexec)
    87  	// Success.
    88  	err := runner.FlushSet("FOOBAR")
    89  	if err != nil {
    90  		t.Errorf("expected success, got %v", err)
    91  	}
    92  	if fcmd.CombinedOutputCalls != 1 {
    93  		t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
    94  	}
    95  	if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "flush", "FOOBAR") {
    96  		t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
    97  	}
    98  	// Flush again
    99  	err = runner.FlushSet("FOOBAR")
   100  	if err != nil {
   101  		t.Errorf("expected success, got %v", err)
   102  	}
   103  }
   104  
   105  func TestDestroySet(t *testing.T) {
   106  	fcmd := fakeexec.FakeCmd{
   107  		CombinedOutputScript: []fakeexec.FakeAction{
   108  			// Success
   109  			func() ([]byte, []byte, error) { return []byte{}, nil, nil },
   110  			// Failure
   111  			func() ([]byte, []byte, error) {
   112  				return []byte("ipset v6.19: The set with the given name does not exist"), nil, &fakeexec.FakeExitError{Status: 1}
   113  			},
   114  		},
   115  	}
   116  	fexec := &fakeexec.FakeExec{
   117  		CommandScript: []fakeexec.FakeCommandAction{
   118  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   119  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   120  		},
   121  	}
   122  	runner := New(fexec)
   123  	// Success
   124  	err := runner.DestroySet("FOOBAR")
   125  	if err != nil {
   126  		t.Errorf("expected success, got %v", err)
   127  	}
   128  	if fcmd.CombinedOutputCalls != 1 {
   129  		t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
   130  	}
   131  	if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "destroy", "FOOBAR") {
   132  		t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
   133  	}
   134  	// Failure
   135  	err = runner.DestroySet("FOOBAR")
   136  	if err == nil {
   137  		t.Errorf("expected failure, got nil")
   138  	}
   139  }
   140  
   141  func TestDestroyAllSets(t *testing.T) {
   142  	fcmd := fakeexec.FakeCmd{
   143  		CombinedOutputScript: []fakeexec.FakeAction{
   144  			// Success
   145  			func() ([]byte, []byte, error) { return []byte{}, nil, nil },
   146  			// Success
   147  			func() ([]byte, []byte, error) { return []byte{}, nil, nil },
   148  		},
   149  	}
   150  	fexec := &fakeexec.FakeExec{
   151  		CommandScript: []fakeexec.FakeCommandAction{
   152  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   153  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   154  		},
   155  	}
   156  	runner := New(fexec)
   157  	// Success
   158  	err := runner.DestroyAllSets()
   159  	if err != nil {
   160  		t.Errorf("expected success, got %v", err)
   161  	}
   162  	if fcmd.CombinedOutputCalls != 1 {
   163  		t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
   164  	}
   165  	if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "destroy") {
   166  		t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
   167  	}
   168  	// Success
   169  	err = runner.DestroyAllSets()
   170  	if err != nil {
   171  		t.Errorf("Unexpected failure: %v", err)
   172  	}
   173  }
   174  
   175  func TestCreateSet(t *testing.T) {
   176  	testSet := IPSet{
   177  		Name:       "FOOBAR",
   178  		SetType:    HashIPPort,
   179  		HashFamily: ProtocolFamilyIPV4,
   180  	}
   181  
   182  	fcmd := fakeexec.FakeCmd{
   183  		CombinedOutputScript: []fakeexec.FakeAction{
   184  			// Success
   185  			func() ([]byte, []byte, error) { return []byte{}, nil, nil },
   186  			// Success
   187  			func() ([]byte, []byte, error) { return []byte{}, nil, nil },
   188  			// Failure
   189  			func() ([]byte, []byte, error) {
   190  				return []byte("ipset v6.19: Set cannot be created: set with the same name already exists"), nil, &fakeexec.FakeExitError{Status: 1}
   191  			},
   192  		},
   193  	}
   194  	fexec := &fakeexec.FakeExec{
   195  		CommandScript: []fakeexec.FakeCommandAction{
   196  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   197  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   198  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   199  		},
   200  	}
   201  	runner := New(fexec)
   202  	// Create with ignoreExistErr = false, expect success
   203  	err := runner.CreateSet(&testSet, false)
   204  	if err != nil {
   205  		t.Errorf("expected success, got %v", err)
   206  	}
   207  	if fcmd.CombinedOutputCalls != 1 {
   208  		t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
   209  	}
   210  	if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "create", "FOOBAR", "hash:ip,port", "family", "inet", "hashsize", "1024", "maxelem", "65536") {
   211  		t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
   212  	}
   213  	// Create with ignoreExistErr = true, expect success
   214  	err = runner.CreateSet(&testSet, true)
   215  	if err != nil {
   216  		t.Errorf("expected success, got %v", err)
   217  	}
   218  	if fcmd.CombinedOutputCalls != 2 {
   219  		t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
   220  	}
   221  	if !sets.NewString(fcmd.CombinedOutputLog[1]...).HasAll("ipset", "create", "FOOBAR", "hash:ip,port", "family", "inet", "hashsize", "1024", "maxelem", "65536", "-exist") {
   222  		t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[1])
   223  	}
   224  	// Create with ignoreExistErr = false, expect failure
   225  	err = runner.CreateSet(&testSet, false)
   226  	if err == nil {
   227  		t.Errorf("expected failure, got nil")
   228  	}
   229  }
   230  
   231  var testCases = []struct {
   232  	entry                *Entry
   233  	set                  *IPSet
   234  	addCombinedOutputLog [][]string
   235  	delCombinedOutputLog []string
   236  }{
   237  	{ // case 0
   238  		entry: &Entry{
   239  			IP:       "192.168.1.1",
   240  			Port:     53,
   241  			Protocol: ProtocolUDP,
   242  			SetType:  HashIPPort,
   243  		},
   244  		set: &IPSet{
   245  			Name: "ZERO",
   246  		},
   247  		addCombinedOutputLog: [][]string{
   248  			{"ipset", "add", "ZERO", "192.168.1.1,udp:53"},
   249  			{"ipset", "add", "ZERO", "192.168.1.1,udp:53", "-exist"},
   250  		},
   251  		delCombinedOutputLog: []string{"ipset", "del", "ZERO", "192.168.1.1,udp:53"},
   252  	},
   253  	{ // case 1
   254  		entry: &Entry{
   255  			IP:       "192.168.1.2",
   256  			Port:     80,
   257  			Protocol: ProtocolTCP,
   258  			SetType:  HashIPPort,
   259  		},
   260  		set: &IPSet{
   261  			Name: "UN",
   262  		},
   263  		addCombinedOutputLog: [][]string{
   264  			{"ipset", "add", "UN", "192.168.1.2,tcp:80"},
   265  			{"ipset", "add", "UN", "192.168.1.2,tcp:80", "-exist"},
   266  		},
   267  		delCombinedOutputLog: []string{"ipset", "del", "UN", "192.168.1.2,tcp:80"},
   268  	},
   269  	{ // case 2
   270  		entry: &Entry{
   271  			IP:       "192.168.1.3",
   272  			Port:     53,
   273  			Protocol: ProtocolUDP,
   274  			SetType:  HashIPPortIP,
   275  			IP2:      "10.20.30.1",
   276  		},
   277  		set: &IPSet{
   278  			Name: "DEUX",
   279  		},
   280  		addCombinedOutputLog: [][]string{
   281  			{"ipset", "add", "DEUX", "192.168.1.3,udp:53,10.20.30.1"},
   282  			{"ipset", "add", "DEUX", "192.168.1.3,udp:53,10.20.30.1", "-exist"},
   283  		},
   284  		delCombinedOutputLog: []string{"ipset", "del", "DEUX", "192.168.1.3,udp:53,10.20.30.1"},
   285  	},
   286  	{ // case 3
   287  		entry: &Entry{
   288  			IP:       "192.168.1.4",
   289  			Port:     80,
   290  			Protocol: ProtocolTCP,
   291  			SetType:  HashIPPortIP,
   292  			IP2:      "10.20.30.2",
   293  		},
   294  		set: &IPSet{
   295  			Name: "TROIS",
   296  		},
   297  		addCombinedOutputLog: [][]string{
   298  			{"ipset", "add", "TROIS", "192.168.1.4,tcp:80,10.20.30.2"},
   299  			{"ipset", "add", "TROIS", "192.168.1.4,tcp:80,10.20.30.2", "-exist"},
   300  		},
   301  		delCombinedOutputLog: []string{"ipset", "del", "TROIS", "192.168.1.4,tcp:80,10.20.30.2"},
   302  	},
   303  	{ // case 4
   304  		entry: &Entry{
   305  			IP:       "192.168.1.5",
   306  			Port:     53,
   307  			Protocol: ProtocolUDP,
   308  			SetType:  HashIPPortNet,
   309  			Net:      "10.20.30.0/24",
   310  		},
   311  		set: &IPSet{
   312  			Name: "QUATRE",
   313  		},
   314  		addCombinedOutputLog: [][]string{
   315  			{"ipset", "add", "QUATRE", "192.168.1.5,udp:53,10.20.30.0/24"},
   316  			{"ipset", "add", "QUATRE", "192.168.1.5,udp:53,10.20.30.0/24", "-exist"},
   317  		},
   318  		delCombinedOutputLog: []string{"ipset", "del", "QUATRE", "192.168.1.5,udp:53,10.20.30.0/24"},
   319  	},
   320  	{ // case 5
   321  		entry: &Entry{
   322  			IP:       "192.168.1.6",
   323  			Port:     80,
   324  			Protocol: ProtocolTCP,
   325  			SetType:  HashIPPortNet,
   326  			Net:      "10.20.40.0/24",
   327  		},
   328  		set: &IPSet{
   329  			Name: "CINQ",
   330  		},
   331  		addCombinedOutputLog: [][]string{
   332  			{"ipset", "add", "CINQ", "192.168.1.6,tcp:80,10.20.40.0/24"},
   333  			{"ipset", "add", "CINQ", "192.168.1.6,tcp:80,10.20.40.0/24", "-exist"},
   334  		},
   335  		delCombinedOutputLog: []string{"ipset", "del", "CINQ", "192.168.1.6,tcp:80,10.20.40.0/24"},
   336  	},
   337  	{ // case 6
   338  		entry: &Entry{
   339  			Port:     80,
   340  			Protocol: ProtocolTCP,
   341  			SetType:  BitmapPort,
   342  		},
   343  		set: &IPSet{
   344  			Name: "SIX",
   345  		},
   346  		addCombinedOutputLog: [][]string{
   347  			{"ipset", "add", "SIX", "80"},
   348  			{"ipset", "add", "SIX", "80", "-exist"},
   349  		},
   350  		delCombinedOutputLog: []string{"ipset", "del", "SIX", "80"},
   351  	},
   352  	{ // case 7
   353  		entry: &Entry{
   354  			IP:       "192.168.1.2",
   355  			Port:     80,
   356  			Protocol: ProtocolSCTP,
   357  			SetType:  HashIPPort,
   358  		},
   359  		set: &IPSet{
   360  			Name: "SETTE",
   361  		},
   362  		addCombinedOutputLog: [][]string{
   363  			{"ipset", "add", "SETTE", "192.168.1.2,sctp:80"},
   364  			{"ipset", "add", "SETTE", "192.168.1.2,sctp:80", "-exist"},
   365  		},
   366  		delCombinedOutputLog: []string{"ipset", "del", "SETTE", "192.168.1.2,sctp:80"},
   367  	},
   368  }
   369  
   370  func TestAddEntry(t *testing.T) {
   371  	for i := range testCases {
   372  		fcmd := fakeexec.FakeCmd{
   373  			CombinedOutputScript: []fakeexec.FakeAction{
   374  				// Success
   375  				func() ([]byte, []byte, error) { return []byte{}, nil, nil },
   376  				// Success
   377  				func() ([]byte, []byte, error) { return []byte{}, nil, nil },
   378  				// Failure
   379  				func() ([]byte, []byte, error) {
   380  					return []byte("ipset v6.19: Set cannot be created: set with the same name already exists"), nil, &fakeexec.FakeExitError{Status: 1}
   381  				},
   382  			},
   383  		}
   384  		fexec := &fakeexec.FakeExec{
   385  			CommandScript: []fakeexec.FakeCommandAction{
   386  				func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   387  				func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   388  				func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   389  			},
   390  		}
   391  		runner := New(fexec)
   392  		// Create with ignoreExistErr = false, expect success
   393  		err := runner.AddEntry(testCases[i].entry.String(), testCases[i].set, false)
   394  		if err != nil {
   395  			t.Errorf("expected success, got %v", err)
   396  		}
   397  		if fcmd.CombinedOutputCalls != 1 {
   398  			t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
   399  		}
   400  		if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll(testCases[i].addCombinedOutputLog[0]...) {
   401  			t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
   402  		}
   403  		// Create with ignoreExistErr = true, expect success
   404  		err = runner.AddEntry(testCases[i].entry.String(), testCases[i].set, true)
   405  		if err != nil {
   406  			t.Errorf("expected success, got %v", err)
   407  		}
   408  		if fcmd.CombinedOutputCalls != 2 {
   409  			t.Errorf("expected 3 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
   410  		}
   411  		if !sets.NewString(fcmd.CombinedOutputLog[1]...).HasAll(testCases[i].addCombinedOutputLog[1]...) {
   412  			t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[1])
   413  		}
   414  		// Create with ignoreExistErr = false, expect failure
   415  		err = runner.AddEntry(testCases[i].entry.String(), testCases[i].set, false)
   416  		if err == nil {
   417  			t.Errorf("expected failure, got nil")
   418  		}
   419  	}
   420  }
   421  
   422  func TestDelEntry(t *testing.T) {
   423  	for i := range testCases {
   424  		fcmd := fakeexec.FakeCmd{
   425  			CombinedOutputScript: []fakeexec.FakeAction{
   426  				// Success
   427  				func() ([]byte, []byte, error) { return []byte{}, nil, nil },
   428  				// Failure
   429  				func() ([]byte, []byte, error) {
   430  					return []byte("ipset v6.19: Element cannot be deleted from the set: it's not added"), nil, &fakeexec.FakeExitError{Status: 1}
   431  				},
   432  			},
   433  		}
   434  		fexec := &fakeexec.FakeExec{
   435  			CommandScript: []fakeexec.FakeCommandAction{
   436  				func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   437  				func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   438  			},
   439  		}
   440  		runner := New(fexec)
   441  
   442  		err := runner.DelEntry(testCases[i].entry.String(), testCases[i].set.Name)
   443  		if err != nil {
   444  			t.Errorf("expected success, got %v", err)
   445  		}
   446  		if fcmd.CombinedOutputCalls != 1 {
   447  			t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
   448  		}
   449  		if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll(testCases[i].delCombinedOutputLog...) {
   450  			t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
   451  		}
   452  		err = runner.DelEntry(testCases[i].entry.String(), testCases[i].set.Name)
   453  		if err == nil {
   454  			t.Errorf("expected failure, got nil")
   455  		}
   456  	}
   457  }
   458  
   459  func TestTestEntry(t *testing.T) {
   460  	testEntry := &Entry{
   461  		IP:       "10.120.7.100",
   462  		Port:     8080,
   463  		Protocol: ProtocolTCP,
   464  		SetType:  HashIPPort,
   465  	}
   466  	setName := "NOT"
   467  	fcmd := fakeexec.FakeCmd{
   468  		CombinedOutputScript: []fakeexec.FakeAction{
   469  			// Success
   470  			func() ([]byte, []byte, error) {
   471  				return []byte("10.120.7.100,tcp:8080 is in set " + setName + "."), nil, nil
   472  			},
   473  			// Failure
   474  			func() ([]byte, []byte, error) {
   475  				return []byte("192.168.1.3,tcp:8080 is NOT in set " + setName + "."), nil, &fakeexec.FakeExitError{Status: 1}
   476  			},
   477  		},
   478  	}
   479  	fexec := &fakeexec.FakeExec{
   480  		CommandScript: []fakeexec.FakeCommandAction{
   481  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   482  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   483  		},
   484  	}
   485  	runner := New(fexec)
   486  	// Success
   487  	ok, err := runner.TestEntry(testEntry.String(), setName)
   488  	if err != nil {
   489  		t.Errorf("expected success, got %v", err)
   490  	}
   491  	if fcmd.CombinedOutputCalls != 1 {
   492  		t.Errorf("expected 2 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
   493  	}
   494  	if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "test", setName, "10.120.7.100,tcp:8080") {
   495  		t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
   496  	}
   497  	if !ok {
   498  		t.Errorf("expect entry exists in test set, got not")
   499  	}
   500  	// Failure
   501  	ok, err = runner.TestEntry(testEntry.String(), "FOOBAR")
   502  	if err == nil || ok {
   503  		t.Errorf("expect entry doesn't exist in test set")
   504  	}
   505  }
   506  
   507  func TestTestEntryIPv6(t *testing.T) {
   508  	testEntry := &Entry{
   509  		IP:       "fd00:1234:5678:dead:beaf::1",
   510  		Port:     8080,
   511  		Protocol: ProtocolTCP,
   512  		SetType:  HashIPPort,
   513  	}
   514  	setName := "NOT"
   515  	fcmd := fakeexec.FakeCmd{
   516  		CombinedOutputScript: []fakeexec.FakeAction{
   517  			// Success
   518  			func() ([]byte, []byte, error) {
   519  				return []byte("fd00:1234:5678:dead:beaf::1,tcp:8080 is in set " + setName + "."), nil, nil
   520  			},
   521  			// Failure
   522  			func() ([]byte, []byte, error) {
   523  				return []byte("fd00::2,tcp:8080 is NOT in set FOOBAR."), nil, &fakeexec.FakeExitError{Status: 1}
   524  			},
   525  		},
   526  	}
   527  	fexec := &fakeexec.FakeExec{
   528  		CommandScript: []fakeexec.FakeCommandAction{
   529  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   530  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   531  		},
   532  	}
   533  	runner := New(fexec)
   534  	// Success
   535  	ok, err := runner.TestEntry(testEntry.String(), setName)
   536  	if err != nil {
   537  		t.Errorf("expected success, got %v", err)
   538  	}
   539  	if fcmd.CombinedOutputCalls != 1 {
   540  		t.Errorf("expected 1 CombinedOutput() calls, got %d", fcmd.CombinedOutputCalls)
   541  	}
   542  	if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "test", setName, "fd00:1234:5678:dead:beaf::1,tcp:8080") {
   543  		t.Errorf("wrong CombinedOutput() log, got %s", fcmd.CombinedOutputLog[0])
   544  	}
   545  	if !ok {
   546  		t.Errorf("expect entry exists in test set, got not")
   547  	}
   548  	// Failure
   549  	ok, err = runner.TestEntry(testEntry.String(), "FOOBAR")
   550  	if err == nil || ok {
   551  		t.Errorf("expect entry doesn't exist in test set")
   552  	}
   553  }
   554  
   555  func TestListEntries(t *testing.T) {
   556  
   557  	output := `Name: foobar
   558  Type: hash:ip,port
   559  Revision: 2
   560  Header: family inet hashsize 1024 maxelem 65536
   561  Size in memory: 16592
   562  References: 0
   563  Members:
   564  192.168.1.2,tcp:8080
   565  192.168.1.1,udp:53`
   566  
   567  	emptyOutput := `Name: KUBE-NODE-PORT
   568  Type: bitmap:port
   569  Revision: 1
   570  Header: range 0-65535
   571  Size in memory: 524432
   572  References: 1
   573  Members:
   574  
   575  `
   576  
   577  	testCases := []struct {
   578  		output   string
   579  		expected []string
   580  	}{
   581  		{
   582  			output:   output,
   583  			expected: []string{"192.168.1.2,tcp:8080", "192.168.1.1,udp:53"},
   584  		},
   585  		{
   586  			output:   emptyOutput,
   587  			expected: []string{},
   588  		},
   589  	}
   590  
   591  	for i := range testCases {
   592  		fcmd := fakeexec.FakeCmd{
   593  			CombinedOutputScript: []fakeexec.FakeAction{
   594  				// Success
   595  				func() ([]byte, []byte, error) {
   596  					return []byte(testCases[i].output), nil, nil
   597  				},
   598  			},
   599  		}
   600  		fexec := &fakeexec.FakeExec{
   601  			CommandScript: []fakeexec.FakeCommandAction{
   602  				func(cmd string, args ...string) exec.Cmd {
   603  					return fakeexec.InitFakeCmd(&fcmd, cmd, args...)
   604  				},
   605  			},
   606  		}
   607  		runner := New(fexec)
   608  		// Success
   609  		entries, err := runner.ListEntries("foobar")
   610  		if err != nil {
   611  			t.Errorf("expected success, got: %v", err)
   612  		}
   613  		if fcmd.CombinedOutputCalls != 1 {
   614  			t.Errorf("expected 1 CombinedOutput() calls, got: %d", fcmd.CombinedOutputCalls)
   615  		}
   616  		if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "list", "foobar") {
   617  			t.Errorf("wrong CombinedOutput() log, got: %s", fcmd.CombinedOutputLog[0])
   618  		}
   619  		if len(entries) != len(testCases[i].expected) {
   620  			t.Errorf("expected %d ipset entries, got: %d", len(testCases[i].expected), len(entries))
   621  		}
   622  		if !reflect.DeepEqual(entries, testCases[i].expected) {
   623  			t.Errorf("expected entries: %v, got: %v", testCases[i].expected, entries)
   624  		}
   625  	}
   626  }
   627  
   628  func TestListSets(t *testing.T) {
   629  	output := `foo
   630  bar
   631  baz`
   632  
   633  	expected := []string{"foo", "bar", "baz"}
   634  
   635  	fcmd := fakeexec.FakeCmd{
   636  		CombinedOutputScript: []fakeexec.FakeAction{
   637  			// Success
   638  			func() ([]byte, []byte, error) { return []byte(output), nil, nil },
   639  		},
   640  	}
   641  	fexec := &fakeexec.FakeExec{
   642  		CommandScript: []fakeexec.FakeCommandAction{
   643  			func(cmd string, args ...string) exec.Cmd { return fakeexec.InitFakeCmd(&fcmd, cmd, args...) },
   644  		},
   645  	}
   646  	runner := New(fexec)
   647  	// Success
   648  	list, err := runner.ListSets()
   649  	if err != nil {
   650  		t.Errorf("expected success, got: %v", err)
   651  	}
   652  	if fcmd.CombinedOutputCalls != 1 {
   653  		t.Errorf("expected 1 CombinedOutput() calls, got: %d", fcmd.CombinedOutputCalls)
   654  	}
   655  	if !sets.NewString(fcmd.CombinedOutputLog[0]...).HasAll("ipset", "list", "-n") {
   656  		t.Errorf("wrong CombinedOutput() log, got: %s", fcmd.CombinedOutputLog[0])
   657  	}
   658  	if len(list) != len(expected) {
   659  		t.Errorf("expected %d sets, got: %d", len(expected), len(list))
   660  	}
   661  	if !reflect.DeepEqual(list, expected) {
   662  		t.Errorf("expected sets: %v, got: %v", expected, list)
   663  	}
   664  }
   665  
   666  func Test_validIPSetType(t *testing.T) {
   667  	testCases := []struct {
   668  		setType   Type
   669  		expectErr bool
   670  	}{
   671  		{ // case[0]
   672  			setType:   Type("foo"),
   673  			expectErr: true,
   674  		},
   675  		{ // case[1]
   676  			setType:   HashIPPortNet,
   677  			expectErr: false,
   678  		},
   679  		{ // case[2]
   680  			setType:   HashIPPort,
   681  			expectErr: false,
   682  		},
   683  		{ // case[3]
   684  			setType:   HashIPPortIP,
   685  			expectErr: false,
   686  		},
   687  		{ // case[4]
   688  			setType:   BitmapPort,
   689  			expectErr: false,
   690  		},
   691  		{ // case[5]
   692  			setType:   Type(""),
   693  			expectErr: true,
   694  		},
   695  	}
   696  	for i := range testCases {
   697  		err := validateIPSetType(testCases[i].setType)
   698  		if err != nil {
   699  			if !testCases[i].expectErr {
   700  				t.Errorf("case [%d]: unexpected mismatch, expect error[%v], got error[%v]", i, testCases[i].expectErr, err)
   701  			}
   702  			continue
   703  		}
   704  	}
   705  }
   706  
   707  func Test_validatePortRange(t *testing.T) {
   708  	testCases := []struct {
   709  		portRange string
   710  		expectErr bool
   711  		desc      string
   712  	}{
   713  		{ // case[0]
   714  			portRange: "a-b",
   715  			expectErr: true,
   716  			desc:      "invalid port number",
   717  		},
   718  		{ // case[1]
   719  			portRange: "1-2",
   720  			expectErr: false,
   721  			desc:      "valid",
   722  		},
   723  		{ // case[2]
   724  			portRange: "90-1",
   725  			expectErr: false,
   726  			desc:      "ipset util can accept the input of begin port number can be less than end port number",
   727  		},
   728  		{ // case[3]
   729  			portRange: DefaultPortRange,
   730  			expectErr: false,
   731  			desc:      "default port range is valid, of course",
   732  		},
   733  		{ // case[4]
   734  			portRange: "12",
   735  			expectErr: true,
   736  			desc:      "a single number is invalid",
   737  		},
   738  		{ // case[5]
   739  			portRange: "1-",
   740  			expectErr: true,
   741  			desc:      "should specify end port",
   742  		},
   743  		{ // case[6]
   744  			portRange: "-100",
   745  			expectErr: true,
   746  			desc:      "should specify begin port",
   747  		},
   748  		{ // case[7]
   749  			portRange: "1:100",
   750  			expectErr: true,
   751  			desc:      "delimiter should be -",
   752  		},
   753  		{ // case[8]
   754  			portRange: "1~100",
   755  			expectErr: true,
   756  			desc:      "delimiter should be -",
   757  		},
   758  		{ // case[9]
   759  			portRange: "1,100",
   760  			expectErr: true,
   761  			desc:      "delimiter should be -",
   762  		},
   763  		{ // case[10]
   764  			portRange: "100-100",
   765  			expectErr: false,
   766  			desc:      "begin port number can be equal to end port number",
   767  		},
   768  		{ // case[11]
   769  			portRange: "",
   770  			expectErr: true,
   771  			desc:      "empty string is invalid",
   772  		},
   773  		{ // case[12]
   774  			portRange: "-1-12",
   775  			expectErr: true,
   776  			desc:      "port number can not be negative value",
   777  		},
   778  		{ // case[13]
   779  			portRange: "-1--8",
   780  			expectErr: true,
   781  			desc:      "port number can not be negative value",
   782  		},
   783  	}
   784  	for i := range testCases {
   785  		err := validatePortRange(testCases[i].portRange)
   786  		if err != nil {
   787  			if !testCases[i].expectErr {
   788  				t.Errorf("case [%d]: unexpected mismatch, expect error[%v], got error[%v], desc: %s", i, testCases[i].expectErr, err, testCases[i].desc)
   789  			}
   790  			continue
   791  		}
   792  	}
   793  }
   794  
   795  func Test_validateFamily(t *testing.T) {
   796  	testCases := []struct {
   797  		family    string
   798  		expectErr bool
   799  	}{
   800  		{ // case[0]
   801  			family:    "foo",
   802  			expectErr: true,
   803  		},
   804  		{ // case[1]
   805  			family:    ProtocolFamilyIPV4,
   806  			expectErr: false,
   807  		},
   808  		{ // case[2]
   809  			family:    ProtocolFamilyIPV6,
   810  			expectErr: false,
   811  		},
   812  		{ // case[3]
   813  			family:    "ipv4",
   814  			expectErr: true,
   815  		},
   816  		{ // case[4]
   817  			family:    "ipv6",
   818  			expectErr: true,
   819  		},
   820  		{ // case[5]
   821  			family:    "tcp",
   822  			expectErr: true,
   823  		},
   824  		{ // case[6]
   825  			family:    "udp",
   826  			expectErr: true,
   827  		},
   828  		{ // case[7]
   829  			family:    "",
   830  			expectErr: true,
   831  		},
   832  		{ // case[8]
   833  			family:    "sctp",
   834  			expectErr: true,
   835  		},
   836  	}
   837  	for i := range testCases {
   838  		err := validateHashFamily(testCases[i].family)
   839  		if err != nil {
   840  			if !testCases[i].expectErr {
   841  				t.Errorf("case [%d]: unexpected err: %v, desc: %s", i, err, testCases[i].family)
   842  			}
   843  			continue
   844  		}
   845  	}
   846  }
   847  
   848  func Test_validateProtocol(t *testing.T) {
   849  	testCases := []struct {
   850  		protocol string
   851  		valid    bool
   852  		desc     string
   853  	}{
   854  		{ // case[0]
   855  			protocol: "foo",
   856  			valid:    false,
   857  		},
   858  		{ // case[1]
   859  			protocol: ProtocolTCP,
   860  			valid:    true,
   861  		},
   862  		{ // case[2]
   863  			protocol: ProtocolUDP,
   864  			valid:    true,
   865  		},
   866  		{ // case[3]
   867  			protocol: "ipv4",
   868  			valid:    false,
   869  		},
   870  		{ // case[4]
   871  			protocol: "ipv6",
   872  			valid:    false,
   873  		},
   874  		{ // case[5]
   875  			protocol: "TCP",
   876  			valid:    false,
   877  			desc:     "should be low case",
   878  		},
   879  		{ // case[6]
   880  			protocol: "UDP",
   881  			valid:    false,
   882  			desc:     "should be low case",
   883  		},
   884  		{ // case[7]
   885  			protocol: "",
   886  			valid:    false,
   887  		},
   888  		{ // case[8]
   889  			protocol: ProtocolSCTP,
   890  			valid:    true,
   891  		},
   892  	}
   893  	for i := range testCases {
   894  		valid := validateProtocol(testCases[i].protocol)
   895  		if valid != testCases[i].valid {
   896  			t.Errorf("case [%d]: unexpected mismatch, expect valid[%v], got valid[%v], desc: %s", i, testCases[i].valid, valid, testCases[i].desc)
   897  		}
   898  	}
   899  }
   900  
   901  func TestValidateIPSet(t *testing.T) {
   902  	testCases := []struct {
   903  		ipset     *IPSet
   904  		expectErr bool
   905  		desc      string
   906  	}{
   907  		{ // case[0]
   908  			ipset: &IPSet{
   909  				Name:       "test",
   910  				SetType:    HashIPPort,
   911  				HashFamily: ProtocolFamilyIPV4,
   912  				HashSize:   1024,
   913  				MaxElem:    1024,
   914  			},
   915  			expectErr: false,
   916  			desc:      "No Port range",
   917  		},
   918  		{ // case[1]
   919  			ipset: &IPSet{
   920  				Name:       "SET",
   921  				SetType:    BitmapPort,
   922  				HashFamily: ProtocolFamilyIPV6,
   923  				HashSize:   65535,
   924  				MaxElem:    2048,
   925  				PortRange:  DefaultPortRange,
   926  			},
   927  			expectErr: false,
   928  			desc:      "control case",
   929  		},
   930  		{ // case[2]
   931  			ipset: &IPSet{
   932  				Name:       "foo",
   933  				SetType:    BitmapPort,
   934  				HashFamily: ProtocolFamilyIPV6,
   935  				HashSize:   65535,
   936  				MaxElem:    2048,
   937  			},
   938  			expectErr: true,
   939  			desc:      "should specify right port range for bitmap type set",
   940  		},
   941  		{ // case[3]
   942  			ipset: &IPSet{
   943  				Name:       "bar",
   944  				SetType:    HashIPPort,
   945  				HashFamily: ProtocolFamilyIPV6,
   946  				HashSize:   0,
   947  				MaxElem:    2048,
   948  			},
   949  			expectErr: true,
   950  			desc:      "wrong hash size number",
   951  		},
   952  		{ // case[4]
   953  			ipset: &IPSet{
   954  				Name:       "baz",
   955  				SetType:    HashIPPort,
   956  				HashFamily: ProtocolFamilyIPV6,
   957  				HashSize:   1024,
   958  				MaxElem:    -1,
   959  			},
   960  			expectErr: true,
   961  			desc:      "wrong hash max elem number",
   962  		},
   963  		{ // case[5]
   964  			ipset: &IPSet{
   965  				Name:       "baz",
   966  				SetType:    HashIPPortNet,
   967  				HashFamily: "ip",
   968  				HashSize:   1024,
   969  				MaxElem:    1024,
   970  			},
   971  			expectErr: true,
   972  			desc:      "wrong protocol",
   973  		},
   974  		{ // case[6]
   975  			ipset: &IPSet{
   976  				Name:       "foo-bar",
   977  				SetType:    "xxx",
   978  				HashFamily: ProtocolFamilyIPV4,
   979  				HashSize:   1024,
   980  				MaxElem:    1024,
   981  			},
   982  			expectErr: true,
   983  			desc:      "wrong set type",
   984  		},
   985  	}
   986  	for i := range testCases {
   987  		err := testCases[i].ipset.Validate()
   988  		if err != nil {
   989  			if !testCases[i].expectErr {
   990  				t.Errorf("case [%d]: unexpected mismatch, expect error[%v], got error[%v], desc: %s", i, testCases[i].expectErr, err, testCases[i].desc)
   991  			}
   992  			continue
   993  		}
   994  	}
   995  }
   996  
   997  func Test_setIPSetDefaults(t *testing.T) {
   998  	testCases := []struct {
   999  		name   string
  1000  		set    *IPSet
  1001  		expect *IPSet
  1002  	}{
  1003  		{
  1004  			name: "test all the IPSet fields not present",
  1005  			set: &IPSet{
  1006  				Name: "test1",
  1007  			},
  1008  			expect: &IPSet{
  1009  				Name:       "test1",
  1010  				SetType:    HashIPPort,
  1011  				HashFamily: ProtocolFamilyIPV4,
  1012  				HashSize:   1024,
  1013  				MaxElem:    65536,
  1014  				PortRange:  DefaultPortRange,
  1015  			},
  1016  		},
  1017  		{
  1018  			name: "test all the IPSet fields present",
  1019  			set: &IPSet{
  1020  				Name:       "test2",
  1021  				SetType:    BitmapPort,
  1022  				HashFamily: ProtocolFamilyIPV6,
  1023  				HashSize:   65535,
  1024  				MaxElem:    2048,
  1025  				PortRange:  DefaultPortRange,
  1026  			},
  1027  			expect: &IPSet{
  1028  				Name:       "test2",
  1029  				SetType:    BitmapPort,
  1030  				HashFamily: ProtocolFamilyIPV6,
  1031  				HashSize:   65535,
  1032  				MaxElem:    2048,
  1033  				PortRange:  DefaultPortRange,
  1034  			},
  1035  		},
  1036  		{
  1037  			name: "test part of the IPSet fields present",
  1038  			set: &IPSet{
  1039  				Name:       "test3",
  1040  				SetType:    BitmapPort,
  1041  				HashFamily: ProtocolFamilyIPV6,
  1042  				HashSize:   65535,
  1043  			},
  1044  			expect: &IPSet{
  1045  				Name:       "test3",
  1046  				SetType:    BitmapPort,
  1047  				HashFamily: ProtocolFamilyIPV6,
  1048  				HashSize:   65535,
  1049  				MaxElem:    65536,
  1050  				PortRange:  DefaultPortRange,
  1051  			},
  1052  		},
  1053  	}
  1054  
  1055  	for _, test := range testCases {
  1056  		t.Run(test.name, func(t *testing.T) {
  1057  			test.set.setIPSetDefaults()
  1058  			if !reflect.DeepEqual(test.set, test.expect) {
  1059  				t.Errorf("expected ipset struct: %v, got ipset struct: %v", test.expect, test.set)
  1060  			}
  1061  		})
  1062  	}
  1063  }
  1064  
  1065  func Test_checkIPandProtocol(t *testing.T) {
  1066  	testset := &IPSet{
  1067  		Name:       "test1",
  1068  		SetType:    HashIPPort,
  1069  		HashFamily: ProtocolFamilyIPV4,
  1070  		HashSize:   1024,
  1071  		MaxElem:    65536,
  1072  		PortRange:  DefaultPortRange,
  1073  	}
  1074  
  1075  	testCases := []struct {
  1076  		name  string
  1077  		entry *Entry
  1078  		valid bool
  1079  	}{
  1080  		{
  1081  			name: "valid IP with ProtocolTCP",
  1082  			entry: &Entry{
  1083  				SetType:  HashIPPort,
  1084  				IP:       "1.2.3.4",
  1085  				Protocol: ProtocolTCP,
  1086  				Port:     8080,
  1087  			},
  1088  			valid: true,
  1089  		},
  1090  		{
  1091  			name: "valid IP with ProtocolUDP",
  1092  			entry: &Entry{
  1093  				SetType:  HashIPPort,
  1094  				IP:       "1.2.3.4",
  1095  				Protocol: ProtocolUDP,
  1096  				Port:     8080,
  1097  			},
  1098  			valid: true,
  1099  		},
  1100  		{
  1101  			name: "valid IP with nil Protocol",
  1102  			entry: &Entry{
  1103  				SetType: HashIPPort,
  1104  				IP:      "1.2.3.4",
  1105  				Port:    8080,
  1106  			},
  1107  			valid: true,
  1108  		},
  1109  		{
  1110  			name: "valid IP with invalid Protocol",
  1111  			entry: &Entry{
  1112  				SetType:  HashIPPort,
  1113  				IP:       "1.2.3.4",
  1114  				Protocol: "invalidProtocol",
  1115  				Port:     8080,
  1116  			},
  1117  			valid: false,
  1118  		},
  1119  		{
  1120  			name: "invalid IP with ProtocolTCP",
  1121  			entry: &Entry{
  1122  				SetType:  HashIPPort,
  1123  				IP:       "1.2.3.423",
  1124  				Protocol: ProtocolTCP,
  1125  				Port:     8080,
  1126  			},
  1127  			valid: false,
  1128  		},
  1129  	}
  1130  
  1131  	for _, test := range testCases {
  1132  		t.Run(test.name, func(t *testing.T) {
  1133  			result := test.entry.checkIPandProtocol(testset)
  1134  			if result != test.valid {
  1135  				t.Errorf("expected valid: %v, got valid: %v", test.valid, result)
  1136  			}
  1137  		})
  1138  	}
  1139  }
  1140  
  1141  func Test_parsePortRange(t *testing.T) {
  1142  	testCases := []struct {
  1143  		portRange string
  1144  		expectErr bool
  1145  		beginPort int
  1146  		endPort   int
  1147  		desc      string
  1148  	}{
  1149  		{ // case[0]
  1150  			portRange: "1-100",
  1151  			expectErr: false,
  1152  			beginPort: 1,
  1153  			endPort:   100,
  1154  		},
  1155  		{ // case[1]
  1156  			portRange: "0-0",
  1157  			expectErr: false,
  1158  			beginPort: 0,
  1159  			endPort:   0,
  1160  		},
  1161  		{ // case[2]
  1162  			portRange: "100-10",
  1163  			expectErr: false,
  1164  			beginPort: 10,
  1165  			endPort:   100,
  1166  		},
  1167  		{ // case[3]
  1168  			portRange: "1024",
  1169  			expectErr: true,
  1170  			desc:      "single port number is not allowed",
  1171  		},
  1172  		{ // case[4]
  1173  			portRange: DefaultPortRange,
  1174  			expectErr: false,
  1175  			beginPort: 0,
  1176  			endPort:   65535,
  1177  		},
  1178  		{ // case[5]
  1179  			portRange: "1-",
  1180  			expectErr: true,
  1181  			desc:      "should specify end port",
  1182  		},
  1183  		{ // case[6]
  1184  			portRange: "-100",
  1185  			expectErr: true,
  1186  			desc:      "should specify begin port",
  1187  		},
  1188  		{ // case[7]
  1189  			portRange: "1:100",
  1190  			expectErr: true,
  1191  			desc:      "delimiter should be -",
  1192  		},
  1193  		{ // case[8]
  1194  			portRange: "1~100",
  1195  			expectErr: true,
  1196  			desc:      "delimiter should be -",
  1197  		},
  1198  		{ // case[9]
  1199  			portRange: "1,100",
  1200  			expectErr: true,
  1201  			desc:      "delimiter should be -",
  1202  		},
  1203  		{ // case[10]
  1204  			portRange: "100-100",
  1205  			expectErr: false,
  1206  			desc:      "begin port number can be equal to end port number",
  1207  			beginPort: 100,
  1208  			endPort:   100,
  1209  		},
  1210  		{ // case[11]
  1211  			portRange: "",
  1212  			expectErr: false,
  1213  			desc:      "empty string indicates default port range",
  1214  			beginPort: 0,
  1215  			endPort:   65535,
  1216  		},
  1217  		{ // case[12]
  1218  			portRange: "-1-12",
  1219  			expectErr: true,
  1220  			desc:      "port number can not be negative value",
  1221  		},
  1222  		{ // case[13]
  1223  			portRange: "-1--8",
  1224  			expectErr: true,
  1225  			desc:      "port number can not be negative value",
  1226  		},
  1227  	}
  1228  	for i := range testCases {
  1229  		begin, end, err := parsePortRange(testCases[i].portRange)
  1230  		if err != nil {
  1231  			if !testCases[i].expectErr {
  1232  				t.Errorf("case [%d]: unexpected err: %v, desc: %s", i, err, testCases[i].desc)
  1233  			}
  1234  			continue
  1235  		}
  1236  		if begin != testCases[i].beginPort || end != testCases[i].endPort {
  1237  			t.Errorf("case [%d]: unexpected mismatch [beginPort, endPort] pair, expect [%d, %d], got [%d, %d], desc: %s", i, testCases[i].beginPort, testCases[i].endPort, begin, end, testCases[i].desc)
  1238  		}
  1239  	}
  1240  }
  1241  
  1242  // This is a coarse test, but it offers some modicum of confidence as the code is evolved.
  1243  func TestValidateEntry(t *testing.T) {
  1244  	testCases := []struct {
  1245  		entry *Entry
  1246  		set   *IPSet
  1247  		valid bool
  1248  		desc  string
  1249  	}{
  1250  		{ // case[0]
  1251  			entry: &Entry{
  1252  				SetType: BitmapPort,
  1253  			},
  1254  			set: &IPSet{
  1255  				PortRange: DefaultPortRange,
  1256  			},
  1257  			valid: true,
  1258  			desc:  "port number can be empty, default is 0. And port number is in the range of its ipset's port range",
  1259  		},
  1260  		{ // case[1]
  1261  			entry: &Entry{
  1262  				SetType: BitmapPort,
  1263  				Port:    0,
  1264  			},
  1265  			set: &IPSet{
  1266  				PortRange: DefaultPortRange,
  1267  			},
  1268  			valid: true,
  1269  			desc:  "port number can be 0. And port number is in the range of its ipset's port range",
  1270  		},
  1271  		{ // case[2]
  1272  			entry: &Entry{
  1273  				SetType: BitmapPort,
  1274  				Port:    -1,
  1275  			},
  1276  			valid: false,
  1277  			desc:  "port number can not be negative value",
  1278  		},
  1279  		{ // case[3]
  1280  			entry: &Entry{
  1281  				SetType: BitmapPort,
  1282  				Port:    1080,
  1283  			},
  1284  			set: &IPSet{
  1285  				Name:      "baz",
  1286  				PortRange: DefaultPortRange,
  1287  			},
  1288  			desc:  "port number is in the range of its ipset's port range",
  1289  			valid: true,
  1290  		},
  1291  		{ // case[4]
  1292  			entry: &Entry{
  1293  				SetType: BitmapPort,
  1294  				Port:    1080,
  1295  			},
  1296  			set: &IPSet{
  1297  				Name:      "foo",
  1298  				PortRange: "0-1079",
  1299  			},
  1300  			desc:  "port number is NOT in the range of its ipset's port range",
  1301  			valid: false,
  1302  		},
  1303  		{ // case[5]
  1304  			entry: &Entry{
  1305  				SetType:  HashIPPort,
  1306  				IP:       "1.2.3.4",
  1307  				Protocol: ProtocolTCP,
  1308  				Port:     8080,
  1309  			},
  1310  			set: &IPSet{
  1311  				Name: "bar",
  1312  			},
  1313  			valid: true,
  1314  		},
  1315  		{ // case[6]
  1316  			entry: &Entry{
  1317  				SetType:  HashIPPort,
  1318  				IP:       "1.2.3.4",
  1319  				Protocol: ProtocolUDP,
  1320  				Port:     0,
  1321  			},
  1322  			set: &IPSet{
  1323  				Name: "bar",
  1324  			},
  1325  			valid: true,
  1326  		},
  1327  		{ // case[7]
  1328  			entry: &Entry{
  1329  				SetType:  HashIPPort,
  1330  				IP:       "FE80:0000:0000:0000:0202:B3FF:FE1E:8329",
  1331  				Protocol: ProtocolTCP,
  1332  				Port:     1111,
  1333  			},
  1334  			set: &IPSet{
  1335  				Name: "ipv6",
  1336  			},
  1337  			valid: true,
  1338  		},
  1339  		{ // case[8]
  1340  			entry: &Entry{
  1341  				SetType:  HashIPPort,
  1342  				IP:       "",
  1343  				Protocol: ProtocolTCP,
  1344  				Port:     1234,
  1345  			},
  1346  			set: &IPSet{
  1347  				Name: "empty-ip",
  1348  			},
  1349  			valid: false,
  1350  		},
  1351  		{ // case[9]
  1352  			entry: &Entry{
  1353  				SetType:  HashIPPort,
  1354  				IP:       "1-2-3-4",
  1355  				Protocol: ProtocolTCP,
  1356  				Port:     8900,
  1357  			},
  1358  			set: &IPSet{
  1359  				Name: "bad-ip",
  1360  			},
  1361  			valid: false,
  1362  		},
  1363  		{ // case[10]
  1364  			entry: &Entry{
  1365  				SetType:  HashIPPort,
  1366  				IP:       "10.20.30.40",
  1367  				Protocol: "",
  1368  				Port:     8090,
  1369  			},
  1370  			set: &IPSet{
  1371  				Name: "empty-protocol",
  1372  			},
  1373  			valid: true,
  1374  		},
  1375  		{ // case[11]
  1376  			entry: &Entry{
  1377  				SetType:  HashIPPort,
  1378  				IP:       "10.20.30.40",
  1379  				Protocol: "ICMP",
  1380  				Port:     8090,
  1381  			},
  1382  			set: &IPSet{
  1383  				Name: "unsupported-protocol",
  1384  			},
  1385  			valid: false,
  1386  		},
  1387  		{ // case[11]
  1388  			entry: &Entry{
  1389  				SetType:  HashIPPort,
  1390  				IP:       "10.20.30.40",
  1391  				Protocol: "ICMP",
  1392  				Port:     -1,
  1393  			},
  1394  			set: &IPSet{
  1395  				// TODO: set name string with white space?
  1396  				Name: "negative-port-number",
  1397  			},
  1398  			valid: false,
  1399  		},
  1400  		{ // case[12]
  1401  			entry: &Entry{
  1402  				SetType:  HashIPPortIP,
  1403  				IP:       "10.20.30.40",
  1404  				Protocol: ProtocolUDP,
  1405  				Port:     53,
  1406  				IP2:      "10.20.30.40",
  1407  			},
  1408  			set: &IPSet{
  1409  				Name: "LOOP-BACK",
  1410  			},
  1411  			valid: true,
  1412  		},
  1413  		{ // case[13]
  1414  			entry: &Entry{
  1415  				SetType:  HashIPPortIP,
  1416  				IP:       "10.20.30.40",
  1417  				Protocol: ProtocolUDP,
  1418  				Port:     53,
  1419  				IP2:      "",
  1420  			},
  1421  			set: &IPSet{
  1422  				Name: "empty IP2",
  1423  			},
  1424  			valid: false,
  1425  		},
  1426  		{ // case[14]
  1427  			entry: &Entry{
  1428  				SetType:  HashIPPortIP,
  1429  				IP:       "10.20.30.40",
  1430  				Protocol: ProtocolUDP,
  1431  				Port:     53,
  1432  				IP2:      "foo",
  1433  			},
  1434  			set: &IPSet{
  1435  				Name: "invalid IP2",
  1436  			},
  1437  			valid: false,
  1438  		},
  1439  		{ // case[15]
  1440  			entry: &Entry{
  1441  				SetType:  HashIPPortIP,
  1442  				IP:       "10.20.30.40",
  1443  				Protocol: ProtocolTCP,
  1444  				Port:     0,
  1445  				IP2:      "1.2.3.4",
  1446  			},
  1447  			set: &IPSet{
  1448  				Name: "zero port",
  1449  			},
  1450  			valid: true,
  1451  		},
  1452  		{ // case[16]
  1453  			entry: &Entry{
  1454  				SetType:  HashIPPortIP,
  1455  				IP:       "10::40",
  1456  				Protocol: ProtocolTCP,
  1457  				Port:     10000,
  1458  				IP2:      "1::4",
  1459  			},
  1460  			set: &IPSet{
  1461  				Name: "IPV6",
  1462  				// TODO: check set's hash family
  1463  			},
  1464  			valid: true,
  1465  		},
  1466  		{ // case[17]
  1467  			entry: &Entry{
  1468  				SetType:  HashIPPortIP,
  1469  				IP:       "",
  1470  				Protocol: ProtocolTCP,
  1471  				Port:     1234,
  1472  				IP2:      "1.2.3.4",
  1473  			},
  1474  			set: &IPSet{
  1475  				Name: "empty-ip",
  1476  			},
  1477  			valid: false,
  1478  		},
  1479  		{ // case[18]
  1480  			entry: &Entry{
  1481  				SetType:  HashIPPortIP,
  1482  				IP:       "1-2-3-4",
  1483  				Protocol: ProtocolTCP,
  1484  				Port:     8900,
  1485  				IP2:      "10.20.30.41",
  1486  			},
  1487  			set: &IPSet{
  1488  				Name: "bad-ip",
  1489  			},
  1490  			valid: false,
  1491  		},
  1492  		{ // case[19]
  1493  			entry: &Entry{
  1494  				SetType:  HashIPPortIP,
  1495  				IP:       "10.20.30.40",
  1496  				Protocol: ProtocolSCTP,
  1497  				Port:     8090,
  1498  				IP2:      "10.20.30.41",
  1499  			},
  1500  			set: &IPSet{
  1501  				Name: "sctp",
  1502  			},
  1503  			valid: true,
  1504  		},
  1505  		{ // case[20]
  1506  			entry: &Entry{
  1507  				SetType:  HashIPPortIP,
  1508  				IP:       "10.20.30.40",
  1509  				Protocol: "ICMP",
  1510  				Port:     -1,
  1511  				IP2:      "100.200.30.41",
  1512  			},
  1513  			set: &IPSet{
  1514  				Name: "negative-port-number",
  1515  			},
  1516  			valid: false,
  1517  		},
  1518  		{ // case[21]
  1519  			entry: &Entry{
  1520  				SetType:  HashIPPortNet,
  1521  				IP:       "10.20.30.40",
  1522  				Protocol: ProtocolTCP,
  1523  				Port:     53,
  1524  				Net:      "10.20.30.0/24",
  1525  			},
  1526  			set: &IPSet{
  1527  				Name: "abc",
  1528  			},
  1529  			valid: true,
  1530  		},
  1531  		{ // case[22]
  1532  			entry: &Entry{
  1533  				SetType:  HashIPPortNet,
  1534  				IP:       "11.21.31.41",
  1535  				Protocol: ProtocolUDP,
  1536  				Port:     1122,
  1537  				Net:      "",
  1538  			},
  1539  			set: &IPSet{
  1540  				Name: "empty Net",
  1541  			},
  1542  			valid: false,
  1543  		},
  1544  		{ // case[23]
  1545  			entry: &Entry{
  1546  				SetType:  HashIPPortNet,
  1547  				IP:       "10.20.30.40",
  1548  				Protocol: ProtocolUDP,
  1549  				Port:     8080,
  1550  				Net:      "x-y-z-w",
  1551  			},
  1552  			set: &IPSet{
  1553  				Name: "invalid Net",
  1554  			},
  1555  			valid: false,
  1556  		},
  1557  		{ // case[24]
  1558  			entry: &Entry{
  1559  				SetType:  HashIPPortNet,
  1560  				IP:       "10.20.30.40",
  1561  				Protocol: ProtocolTCP,
  1562  				Port:     0,
  1563  				Net:      "10.1.0.0/16",
  1564  			},
  1565  			set: &IPSet{
  1566  				Name: "zero port",
  1567  			},
  1568  			valid: true,
  1569  		},
  1570  		{ // case[25]
  1571  			entry: &Entry{
  1572  				SetType:  HashIPPortNet,
  1573  				IP:       "10::40",
  1574  				Protocol: ProtocolTCP,
  1575  				Port:     80,
  1576  				Net:      "2001:db8::/32",
  1577  			},
  1578  			set: &IPSet{
  1579  				Name: "IPV6",
  1580  				// TODO: check set's hash family
  1581  			},
  1582  			valid: true,
  1583  		},
  1584  		{ // case[26]
  1585  			entry: &Entry{
  1586  				SetType:  HashIPPortNet,
  1587  				IP:       "",
  1588  				Protocol: ProtocolTCP,
  1589  				Port:     1234,
  1590  				Net:      "1.2.3.4/22",
  1591  			},
  1592  			set: &IPSet{
  1593  				Name: "empty-ip",
  1594  			},
  1595  			valid: false,
  1596  		},
  1597  		{ // case[27]
  1598  			entry: &Entry{
  1599  				SetType:  HashIPPortNet,
  1600  				IP:       "1-2-3-4",
  1601  				Protocol: ProtocolTCP,
  1602  				Port:     8900,
  1603  				Net:      "10.20.30.41/31",
  1604  			},
  1605  			set: &IPSet{
  1606  				Name: "bad-ip",
  1607  			},
  1608  			valid: false,
  1609  		},
  1610  		{ // case[28]
  1611  			entry: &Entry{
  1612  				SetType:  HashIPPortIP,
  1613  				IP:       "10.20.30.40",
  1614  				Protocol: "FOO",
  1615  				Port:     8090,
  1616  				IP2:      "10.20.30.0/10",
  1617  			},
  1618  			set: &IPSet{
  1619  				Name: "unsupported-protocol",
  1620  			},
  1621  			valid: false,
  1622  		},
  1623  		{ // case[29]
  1624  			entry: &Entry{
  1625  				SetType:  HashIPPortIP,
  1626  				IP:       "10.20.30.40",
  1627  				Protocol: ProtocolUDP,
  1628  				Port:     -1,
  1629  				IP2:      "100.200.30.0/12",
  1630  			},
  1631  			set: &IPSet{
  1632  				Name: "negative-port-number",
  1633  			},
  1634  			valid: false,
  1635  		},
  1636  		{ // case[30]
  1637  			entry: &Entry{
  1638  				SetType:  HashIPPortNet,
  1639  				IP:       "10.20.30.40",
  1640  				Protocol: ProtocolTCP,
  1641  				Port:     53,
  1642  				Net:      "192.168.3.0/0",
  1643  			},
  1644  			set: &IPSet{
  1645  				Name: "net mask boundary 0",
  1646  			},
  1647  			valid: true,
  1648  		},
  1649  		{ // case[31]
  1650  			entry: &Entry{
  1651  				SetType:  HashIPPortNet,
  1652  				IP:       "10.20.30.40",
  1653  				Protocol: ProtocolTCP,
  1654  				Port:     53,
  1655  				Net:      "192.168.3.0/32",
  1656  			},
  1657  			set: &IPSet{
  1658  				Name: "net mask boundary 32",
  1659  			},
  1660  			valid: true,
  1661  		},
  1662  		{ // case[32]
  1663  			entry: &Entry{
  1664  				SetType:  HashIPPortNet,
  1665  				IP:       "10.20.30.40",
  1666  				Protocol: ProtocolTCP,
  1667  				Port:     53,
  1668  				Net:      "192.168.3.1/33",
  1669  			},
  1670  			set: &IPSet{
  1671  				Name: "invalid net mask",
  1672  			},
  1673  			valid: false,
  1674  		},
  1675  		{ // case[33]
  1676  			entry: &Entry{
  1677  				SetType:  HashIPPortNet,
  1678  				IP:       "10.20.30.40",
  1679  				Protocol: ProtocolTCP,
  1680  				Port:     53,
  1681  				Net:      "192.168.3.1/-1",
  1682  			},
  1683  			set: &IPSet{
  1684  				Name: "invalid net mask",
  1685  			},
  1686  			valid: false,
  1687  		},
  1688  	}
  1689  	for i := range testCases {
  1690  		valid := testCases[i].entry.Validate(testCases[i].set)
  1691  		if valid != testCases[i].valid {
  1692  			t.Errorf("case [%d]: unexpected mismatch, expect valid[%v], got valid[%v], desc: %s", i, testCases[i].valid, valid, testCases[i].entry)
  1693  		}
  1694  	}
  1695  }
  1696  
  1697  func TestEntryString(t *testing.T) {
  1698  	testCases := []struct {
  1699  		name   string
  1700  		entry  *Entry
  1701  		expect string
  1702  	}{
  1703  		{
  1704  			name: "test when SetType is HashIPPort",
  1705  			entry: &Entry{
  1706  				SetType:  HashIPPort,
  1707  				IP:       "1.2.3.4",
  1708  				Protocol: ProtocolTCP,
  1709  				Port:     8080,
  1710  			},
  1711  			expect: "1.2.3.4,tcp:8080",
  1712  		},
  1713  		{
  1714  			name: "test when SetType is HashIPPortIP",
  1715  			entry: &Entry{
  1716  				SetType:  HashIPPortIP,
  1717  				IP:       "1.2.3.8",
  1718  				Protocol: ProtocolUDP,
  1719  				Port:     8081,
  1720  				IP2:      "1.2.3.8",
  1721  			},
  1722  			expect: "1.2.3.8,udp:8081,1.2.3.8",
  1723  		},
  1724  		{
  1725  			name: "test when SetType is HashIPPortNet",
  1726  			entry: &Entry{
  1727  				SetType:  HashIPPortNet,
  1728  				IP:       "192.168.1.2",
  1729  				Protocol: ProtocolUDP,
  1730  				Port:     80,
  1731  				Net:      "10.0.1.0/24",
  1732  			},
  1733  			expect: "192.168.1.2,udp:80,10.0.1.0/24",
  1734  		},
  1735  		{
  1736  			name: "test when SetType is BitmapPort",
  1737  			entry: &Entry{
  1738  				SetType: BitmapPort,
  1739  				Port:    80,
  1740  			},
  1741  			expect: "80",
  1742  		},
  1743  		{
  1744  			name: "test when SetType is unknown",
  1745  			entry: &Entry{
  1746  				SetType:  "unknown",
  1747  				IP:       "192.168.1.2",
  1748  				Protocol: ProtocolUDP,
  1749  				Port:     80,
  1750  				Net:      "10.0.1.0/24",
  1751  			},
  1752  			expect: "",
  1753  		},
  1754  	}
  1755  
  1756  	for _, test := range testCases {
  1757  		t.Run(test.name, func(t *testing.T) {
  1758  			result := test.entry.String()
  1759  			if result != test.expect {
  1760  				t.Errorf("Unexpected mismatch, expected: %s, got: %s", test.expect, result)
  1761  			}
  1762  		})
  1763  	}
  1764  }