k8s.io/kubernetes@v1.29.3/pkg/proxy/ipvs/ipset/ipset_test.go (about)

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