diff --git a/go.mod b/go.mod index 13fcfde3d13..df55590e798 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module antrea.io/antrea go 1.19 require ( - antrea.io/libOpenflow v0.8.0 + antrea.io/libOpenflow v0.8.1 antrea.io/ofnet v0.6.1 github.com/ClickHouse/clickhouse-go v1.5.4 github.com/DATA-DOG/go-sqlmock v1.5.0 @@ -211,3 +211,5 @@ require ( ) replace antrea.io/ofnet v0.6.0 => github.com/wenyingd/ofnet v0.0.0-20220817031400-cb451467adc1 + +replace antrea.io/ofnet v0.6.1 => github.com/hongliangl/ofnet v0.0.0-20220914072523-be2fd8a8929c diff --git a/go.sum b/go.sum index 5cde0cc17b4..e9c0d8e0187 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -antrea.io/libOpenflow v0.8.0 h1:Xm6mlSqdXtDD418nf1lndoDvMi8scqUan8pkEUZ2oas= -antrea.io/libOpenflow v0.8.0/go.mod h1:CzEJZxDNAupiGxeL5VOw92PsxfyvehEAvE3PiC6gr8o= -antrea.io/ofnet v0.6.1 h1:w/FIagCrN7dKt2A2R9grlmcSyGrqlCu+uFYPthtfXeg= -antrea.io/ofnet v0.6.1/go.mod h1:qWqi11pI3kBYcS9SYWm92ZOiOPBx04Jx21cDmJlJhOg= +antrea.io/libOpenflow v0.8.1 h1:uxkXvhlPRXAVw26LW6Pt2jCSEh8NR56vQW70YGvy7aU= +antrea.io/libOpenflow v0.8.1/go.mod h1:CzEJZxDNAupiGxeL5VOw92PsxfyvehEAvE3PiC6gr8o= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -562,6 +560,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/memberlist v0.4.0 h1:k3uda5gZcltmafuFF+UFqNEl5PrH+yPZ4zkjp1f/H/8= github.com/hashicorp/memberlist v0.4.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hongliangl/ofnet v0.0.0-20220914072523-be2fd8a8929c h1:9ta0njXcmlpUqc2dPPzsO9nj9yDrzJOxVmhta/tfB3U= +github.com/hongliangl/ofnet v0.0.0-20220914072523-be2fd8a8929c/go.mod h1:/gjpTqhUpyn8uZnef+ytdCCAeY5oGG1jCr/szPUqVXU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/pkg/ovs/openflow/ofctrl_action_test.go b/pkg/ovs/openflow/ofctrl_action_test.go new file mode 100644 index 00000000000..dfda6dda687 --- /dev/null +++ b/pkg/ovs/openflow/ofctrl_action_test.go @@ -0,0 +1,1425 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openflow + +import ( + "encoding/binary" + "fmt" + "net" + "testing" + + "antrea.io/libOpenflow/openflow15" + "antrea.io/libOpenflow/util" + "antrea.io/ofnet/ofctrl" + "github.com/stretchr/testify/assert" +) + +var ( + tableID1 = uint8(1) + tableID2 = uint8(2) + tableName = "testTable" + stageID = StageID(0) + piplineID = PipelineID(0) + missAction = TableMissActionNext +) + +type actionSetField struct { + class uint16 + field uint8 + + ethSrc net.HardwareAddr + ethDst net.HardwareAddr + arpXHa net.HardwareAddr + arpXPa net.IP + ipv4Src net.IP + ipv4Dst net.IP + ipv6Src net.IP + ipv6Dst net.IP + tunnelIPv4Dst net.IP + vlanId uint16 + arpOper uint16 + + fieldValue util.Message + fieldMask util.Message +} +type actionCopyField struct { + oxmIdSrcClass uint16 + oxmIdDstClass uint16 + oxmIdSrcField uint8 + oxmIdDstField uint8 + nBits uint16 + srcOffset uint16 + dstOffset uint16 +} +type nxActionOutputReg struct { + class uint16 + field uint8 + ofsNbits uint16 + regID uint8 +} +type nxActionConnTrack struct { + flags uint16 + zoneSrc uint32 + zoneOfsNbits uint16 + recircTable uint8 +} + +func newExpectedNXActionOutputReg(class uint16, field uint8, regID uint8, rng *Range) *nxActionOutputReg { + return &nxActionOutputReg{ + class: class, + field: field, + ofsNbits: rng.ToNXRange().ToOfsBits(), + regID: regID, + } +} + +func checkNXActionOutputReg(t *testing.T, expected *nxActionOutputReg, actions []openflow15.Action) { + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.NXActionOutputReg{}, actions[0]) + action := actions[0].(*openflow15.NXActionOutputReg) + assert.Equal(t, expected.class, action.SrcField.Class) + assert.Equal(t, expected.field, action.SrcField.Field) + assert.Equal(t, expected.regID, action.SrcField.Field) + assert.Equal(t, expected.ofsNbits, action.OfsNbits) +} + +func checkActionSetField(t *testing.T, expected []*actionSetField, actions []openflow15.Action) { + assert.Equal(t, len(expected), len(actions)) + for i := 0; i < len(actions); i++ { + assert.IsType(t, &openflow15.ActionSetField{}, actions[i]) + action := actions[i].(*openflow15.ActionSetField) + assert.Equal(t, expected[i].class, action.Field.Class) + assert.Equal(t, expected[i].field, action.Field.Field) + + switch action.Field.Value.(type) { + case *openflow15.EthSrcField: + assert.Equal(t, expected[i].ethSrc, action.Field.Value.(*openflow15.EthSrcField).EthSrc) + case *openflow15.EthDstField: + assert.Equal(t, expected[i].ethDst, action.Field.Value.(*openflow15.EthDstField).EthDst) + case *openflow15.ArpXHaField: + assert.Equal(t, expected[i].arpXHa, action.Field.Value.(*openflow15.ArpXHaField).ArpHa) + case *openflow15.ArpXPaField: + assert.Equal(t, expected[i].arpXPa, action.Field.Value.(*openflow15.ArpXPaField).ArpPa) + case *openflow15.Ipv4SrcField: + assert.Equal(t, expected[i].ipv4Src, action.Field.Value.(*openflow15.Ipv4SrcField).Ipv4Src) + case *openflow15.Ipv4DstField: + assert.Equal(t, expected[i].ipv4Dst, action.Field.Value.(*openflow15.Ipv4DstField).Ipv4Dst) + case *openflow15.Ipv6SrcField: + assert.Equal(t, expected[i].ipv6Src, action.Field.Value.(*openflow15.Ipv6SrcField).Ipv6Src) + case *openflow15.Ipv6DstField: + assert.Equal(t, expected[i].ipv6Dst, action.Field.Value.(*openflow15.Ipv6DstField).Ipv6Dst) + case *openflow15.TunnelIpv4DstField: + assert.Equal(t, expected[i].tunnelIPv4Dst, action.Field.Value.(*openflow15.TunnelIpv4DstField).TunnelIpv4Dst) + case *openflow15.VlanIdField: + assert.Equal(t, expected[i].vlanId, action.Field.Value.(*openflow15.VlanIdField).VlanId) + case *openflow15.ArpOperField: + assert.Equal(t, expected[i].arpOper, action.Field.Value.(*openflow15.ArpOperField).ArpOper) + case *openflow15.MatchField: + assert.Equal(t, expected[i].fieldValue, action.Field.Value.(*openflow15.MatchField).Value) + assert.Equal(t, expected[i].fieldMask, action.Field.Value.(*openflow15.MatchField).Mask) + case *util.Buffer: + assert.Equal(t, expected[i].fieldValue, action.Field.Value.(*util.Buffer)) + if expected[i].fieldMask != nil { + assert.Equal(t, expected[i].fieldMask, action.Field.Mask.(*util.Buffer)) + } + case *openflow15.Uint32Message: + assert.Equal(t, expected[i].fieldValue, action.Field.Value.(*openflow15.Uint32Message)) + assert.Equal(t, expected[i].fieldMask, action.Field.Mask.(*openflow15.Uint32Message)) + case *openflow15.IpDscpField: + assert.Equal(t, expected[i].fieldValue, action.Field.Value.(*openflow15.IpDscpField)) + assert.Equal(t, expected[i].fieldMask, action.Field.Mask.(*openflow15.IpDscpField)) + case *openflow15.CTLabel: + assert.Equal(t, expected[i].fieldValue, action.Field.Value.(*openflow15.CTLabel)) + assert.Equal(t, expected[i].fieldMask, action.Field.Mask.(*openflow15.CTLabel)) + default: + t.Fatalf("Unknown type %v", action.Field.Value) + } + } +} + +func checkActionCopyField(t *testing.T, expected *actionCopyField, actions []openflow15.Action) { + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.ActionCopyField{}, actions[0]) + action := actions[0].(*openflow15.ActionCopyField) + assert.Equal(t, expected.oxmIdSrcClass, action.OxmIdSrc.Class) + assert.Equal(t, expected.oxmIdDstClass, action.OxmIdDst.Class) + assert.Equal(t, expected.oxmIdSrcField, action.OxmIdSrc.Field) + assert.Equal(t, expected.oxmIdDstField, action.OxmIdDst.Field) + assert.Equal(t, expected.nBits, action.NBits) + assert.Equal(t, expected.srcOffset, action.SrcOffset) + assert.Equal(t, expected.dstOffset, action.DstOffset) +} + +func newExpectedNXActionConnTrack(commit bool, tableID uint8, zone int, zoneSrcField *RegField) *nxActionConnTrack { + flags := uint16(0) + if commit { + flags = uint16(1) + } + zoneSrc := uint32(0) + zoneOfsNbits := uint16(zone) + if zoneSrcField != nil { + field, _ := openflow15.FindFieldHeaderByName(fmt.Sprintf("NXM_NX_REG%d", zoneSrcField.regID), true) + zoneSrc = field.MarshalHeader() + zoneOfsNbits = zoneSrcField.rng.ToNXRange().ToOfsBits() + } + return &nxActionConnTrack{ + flags: flags, + zoneSrc: zoneSrc, + zoneOfsNbits: zoneOfsNbits, + recircTable: tableID, + } +} + +func checkNXActionConnTrack(t *testing.T, expected *nxActionConnTrack, actions []openflow15.Action) { + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.NXActionConnTrack{}, actions[0]) + action := actions[0].(*openflow15.NXActionConnTrack) + assert.Equal(t, expected.flags, action.Flags) + assert.Equal(t, expected.zoneSrc, action.ZoneSrc) + assert.Equal(t, expected.zoneOfsNbits, action.ZoneOfsNbits) + assert.Equal(t, expected.recircTable, action.RecircTable) +} + +func TestFlowActions(t *testing.T) { + table := NewOFTable(tableID1, tableName, stageID, piplineID, missAction) + table.SetNext(tableID2) + table.(*ofTable).Table = new(ofctrl.Table) + + mac1, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff") + mac2, _ := net.ParseMAC("aa:bb:cc:dd:ee:fe") + ipv41 := net.ParseIP("1.1.1.1") + ipv42 := net.ParseIP("2.2.2.2") + ipv61 := net.ParseIP("fec0::1111") + ipv62 := net.ParseIP("fec0::2222") + + t.Run("Drop", func(t *testing.T) { + f := table.BuildFlow(1).Action().Drop().Done() + msg, err := f.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + assert.Equal(t, 0, len(flowMod.Instructions)) + }) + t.Run("OutputFieldRange", func(t *testing.T) { + testCases := []struct { + regName string + rng *Range + expected *nxActionOutputReg + }{ + {"NXM_NX_REG4", &Range{16, 31}, newExpectedNXActionOutputReg(openflow15.OXM_CLASS_NXM_1, openflow15.NXM_NX_REG4, uint8(4), &Range{16, 31})}, + {"NXM_NX_REG6", &Range{0, 31}, newExpectedNXActionOutputReg(openflow15.OXM_CLASS_NXM_1, openflow15.NXM_NX_REG6, uint8(6), &Range{0, 31})}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().OutputFieldRange(tc.regName, tc.rng).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkNXActionOutputReg(t, tc.expected, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("OutputToRegField", func(t *testing.T) { + testCases := []struct { + regField *RegField + expected *nxActionOutputReg + }{ + {NewRegField(4, 16, 31), newExpectedNXActionOutputReg(openflow15.OXM_CLASS_NXM_1, openflow15.NXM_NX_REG4, uint8(4), &Range{16, 31})}, + {NewRegField(6, 0, 31), newExpectedNXActionOutputReg(openflow15.OXM_CLASS_NXM_1, openflow15.NXM_NX_REG6, uint8(6), &Range{0, 31})}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().OutputToRegField(tc.regField).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkNXActionOutputReg(t, tc.expected, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("Output,OutputInPort,Normal", func(t *testing.T) { + testCases := []struct { + flow Flow + expected uint32 + }{ + {table.BuildFlow(1).Action().Output(3).Done(), 3}, + {table.BuildFlow(1).Action().Output(5).Done(), 5}, + {table.BuildFlow(1).Action().OutputInPort().Done(), uint32(openflow15.P_IN_PORT)}, + {table.BuildFlow(1).Action().Normal().Done(), uint32(openflow15.P_NORMAL)}, + } + for _, tc := range testCases { + msg, err := tc.flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + actions := msg.GetMessage().(*openflow15.FlowMod).Instructions[0].(*openflow15.InstrActions).Actions + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.ActionOutput{}, actions[0]) + assert.Equal(t, tc.expected, actions[0].(*openflow15.ActionOutput).Port) + } + }) + t.Run("SetSrcMAC,SetDstMAC", func(t *testing.T) { + testCases := []struct { + isSrc bool + mac net.HardwareAddr + expected *actionSetField + }{ + {true, mac1, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ETH_SRC, ethSrc: mac1}}, + {true, mac2, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ETH_SRC, ethSrc: mac2}}, + {false, mac1, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ETH_DST, ethDst: mac1}}, + {false, mac2, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ETH_DST, ethDst: mac2}}, + } + for _, tc := range testCases { + var flow Flow + if tc.isSrc { + flow = table.BuildFlow(1).Action().SetSrcMAC(tc.mac).Done() + } else { + flow = table.BuildFlow(1).Action().SetDstMAC(tc.mac).Done() + } + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionSetField(t, []*actionSetField{tc.expected}, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("SetARPSha,SetARPTha", func(t *testing.T) { + testCases := []struct { + isSrc bool + mac net.HardwareAddr + expected *actionSetField + }{ + {true, mac1, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ARP_SHA, arpXHa: mac1}}, + {true, mac2, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ARP_SHA, arpXHa: mac2}}, + {false, mac1, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ARP_THA, arpXHa: mac1}}, + {false, mac2, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ARP_THA, arpXHa: mac2}}, + } + for _, tc := range testCases { + var flow Flow + if tc.isSrc { + flow = table.BuildFlow(1).Action().SetARPSha(tc.mac).Done() + } else { + flow = table.BuildFlow(1).Action().SetARPTha(tc.mac).Done() + } + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionSetField(t, []*actionSetField{tc.expected}, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("SetARPSpa,SetARPTpa", func(t *testing.T) { + testCases := []struct { + isSrc bool + ip net.IP + expected *actionSetField + }{ + {true, ipv41, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ARP_SPA, arpXPa: ipv41}}, + {true, ipv42, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ARP_SPA, arpXPa: ipv42}}, + {false, ipv41, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ARP_TPA, arpXPa: ipv41}}, + {false, ipv42, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ARP_TPA, arpXPa: ipv42}}, + } + for _, tc := range testCases { + var flow Flow + if tc.isSrc { + flow = table.BuildFlow(1).Action().SetARPSpa(tc.ip).Done() + } else { + flow = table.BuildFlow(1).Action().SetARPTpa(tc.ip).Done() + } + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionSetField(t, []*actionSetField{tc.expected}, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("SetSrcIP,SetDstIP", func(t *testing.T) { + testCases := []struct { + isSrc bool + ip net.IP + expected *actionSetField + }{ + {true, ipv41, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_IPV4_SRC, ipv4Src: ipv41}}, + {true, ipv42, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_IPV4_SRC, ipv4Src: ipv42}}, + {true, ipv61, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_IPV6_SRC, ipv6Src: ipv61}}, + {true, ipv62, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_IPV6_SRC, ipv6Src: ipv62}}, + {false, ipv41, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_IPV4_DST, ipv4Dst: ipv41}}, + {false, ipv42, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_IPV4_DST, ipv4Dst: ipv42}}, + {false, ipv61, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_IPV6_DST, ipv6Dst: ipv61}}, + {false, ipv62, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_IPV6_DST, ipv6Dst: ipv62}}, + } + for _, tc := range testCases { + var flow Flow + if tc.isSrc { + flow = table.BuildFlow(1).Action().SetSrcIP(tc.ip).Done() + } else { + flow = table.BuildFlow(1).Action().SetDstIP(tc.ip).Done() + } + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionSetField(t, []*actionSetField{tc.expected}, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("SetTunnelDst", func(t *testing.T) { + testCases := []struct { + ip net.IP + expected *actionSetField + }{ + {ipv41, &actionSetField{class: openflow15.OXM_CLASS_NXM_1, field: openflow15.NXM_NX_TUN_IPV4_DST, tunnelIPv4Dst: ipv41}}, + {ipv42, &actionSetField{class: openflow15.OXM_CLASS_NXM_1, field: openflow15.NXM_NX_TUN_IPV4_DST, tunnelIPv4Dst: ipv42}}, + {ipv61, &actionSetField{class: openflow15.OXM_CLASS_NXM_1, field: openflow15.NXM_NX_TUN_IPV6_DST, ipv6Dst: ipv61}}, + {ipv62, &actionSetField{class: openflow15.OXM_CLASS_NXM_1, field: openflow15.NXM_NX_TUN_IPV6_DST, ipv6Dst: ipv62}}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().SetTunnelDst(tc.ip).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionSetField(t, []*actionSetField{tc.expected}, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("PopVLAN", func(t *testing.T) { + f := table.BuildFlow(1).Action().PopVLAN().Done() + msg, err := f.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + actions := flowMod.Instructions[0].(*openflow15.InstrActions).Actions + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.ActionPopVlan{}, actions[0]) + }) + t.Run("PushVLAN", func(t *testing.T) { + testCases := []struct { + vlanId uint16 + }{ + {100}, + {200}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().PushVLAN(tc.vlanId).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + actions := msg.GetMessage().(*openflow15.FlowMod).Instructions[0].(*openflow15.InstrActions).Actions + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.ActionPush{}, actions[0]) + assert.Equal(t, tc.vlanId, actions[0].(*openflow15.ActionPush).EtherType) + } + }) + t.Run("SetVLAN", func(t *testing.T) { + testCases := []struct { + vlanId uint16 + expected *actionSetField + }{ + {100, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_VLAN_VID, vlanId: 100 | openflow15.OFPVID_PRESENT}}, + {200, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_VLAN_VID, vlanId: 200 | openflow15.OFPVID_PRESENT}}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().SetVLAN(tc.vlanId).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionSetField(t, []*actionSetField{tc.expected}, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("LoadARPOperation", func(t *testing.T) { + testCases := []struct { + arpOp uint16 + expected *actionSetField + }{ + {1, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ARP_OP, arpOper: 1}}, + {2, &actionSetField{class: openflow15.OXM_CLASS_OPENFLOW_BASIC, field: openflow15.OXM_FIELD_ARP_OP, arpOper: 2}}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().LoadARPOperation(tc.arpOp).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionSetField(t, []*actionSetField{tc.expected}, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("LoadRegMark", func(t *testing.T) { + field1 := NewRegField(11, 1, 17) + field2 := NewRegField(4, 0, 31) + mark1 := NewRegMark(field1, uint32(0xfffe)) + mark2 := NewRegMark(field2, uint32(0xffffeeee)) + + f := func(value uint32, class uint16, field uint8, rng *Range) *actionSetField { + maskData := ^uint32(0) >> (32 - rng.Length()) << rng.Offset() + valueData := value << rng.Offset() + return &actionSetField{ + class: class, + field: field, + fieldValue: &openflow15.Uint32Message{Data: valueData}, + fieldMask: &openflow15.Uint32Message{Data: maskData}, + } + } + expected1 := f(mark1.value, openflow15.OXM_CLASS_NXM_1, openflow15.NXM_NX_REG11, field1.rng) + expected2 := f(mark2.value, openflow15.OXM_CLASS_NXM_1, openflow15.NXM_NX_REG4, field2.rng) + + testCases := []struct { + regMarks []*RegMark + expected []*actionSetField + }{ + {[]*RegMark{mark1}, []*actionSetField{expected1}}, + {[]*RegMark{mark1, mark2}, []*actionSetField{expected1, expected2}}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().LoadRegMark(tc.regMarks...).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionSetField(t, tc.expected, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("LoadPktMarkRange", func(t *testing.T) { + f := func(value uint32, rng *Range) *actionSetField { + valueBytes := make([]byte, 4) + maskBytes := make([]byte, 4) + maskData := ^uint32(0) >> (32 - rng.Length()) << rng.Offset() + valueData := value << rng.Offset() + binary.BigEndian.PutUint32(maskBytes, maskData) + binary.BigEndian.PutUint32(valueBytes, valueData) + return &actionSetField{ + class: openflow15.OXM_CLASS_NXM_1, + field: openflow15.NXM_NX_PKT_MARK, + fieldValue: util.NewBuffer(valueBytes), + fieldMask: util.NewBuffer(maskBytes), + } + } + mark1, mark2 := uint32(0xfeef), uint32(0xfeeffeef) + rng1, rng2 := &Range{2, 18}, &Range{0, 31} + expected1, expected2 := f(mark1, rng1), f(mark2, rng2) + + testCases := []struct { + value uint32 + rng *Range + expected *actionSetField + }{ + {mark1, rng1, expected1}, + {mark2, rng2, expected2}, + } + + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().LoadPktMarkRange(tc.value, tc.rng).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionSetField(t, []*actionSetField{tc.expected}, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("LoadIPDSCP", func(t *testing.T) { + testCases := []struct { + value uint8 + expected *actionSetField + }{ + { + uint8(0xef), + &actionSetField{ + class: openflow15.OXM_CLASS_NXM_0, + field: openflow15.NXM_OF_IP_TOS, + fieldValue: &openflow15.IpDscpField{Dscp: uint8(0xef) << IPDSCPToSRange.Offset()}, + fieldMask: &openflow15.IpDscpField{Dscp: uint8(0xff) >> (8 - IPDSCPToSRange.Length()) << IPDSCPToSRange.Offset()}}, + }, + { + uint8(0xfe), + &actionSetField{ + class: openflow15.OXM_CLASS_NXM_0, + field: openflow15.NXM_OF_IP_TOS, + fieldValue: &openflow15.IpDscpField{Dscp: uint8(0xfe) << IPDSCPToSRange.Offset()}, + fieldMask: &openflow15.IpDscpField{Dscp: uint8(0xff) >> (8 - IPDSCPToSRange.Length()) << IPDSCPToSRange.Offset()}}, + }, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().LoadIPDSCP(tc.value).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionSetField(t, []*actionSetField{tc.expected}, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("Move,MoveRange", func(t *testing.T) { + testCases := []struct { + hasRange bool + srcRegName string + dstRegName string + srcRange Range + dstRange Range + expected *actionCopyField + }{ + { + hasRange: false, + srcRegName: "NXM_NX_REG4", + dstRegName: "NXM_NX_REG5", + expected: &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_NXM_1, + oxmIdDstClass: openflow15.OXM_CLASS_NXM_1, + oxmIdSrcField: openflow15.NXM_NX_REG4, + oxmIdDstField: openflow15.NXM_NX_REG5, + nBits: 32, + }, + }, + { + hasRange: false, + srcRegName: "NXM_NX_XXREG0", + dstRegName: "NXM_NX_XXREG1", + expected: &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_NXM_1, + oxmIdDstClass: openflow15.OXM_CLASS_NXM_1, + oxmIdSrcField: openflow15.NXM_NX_XXREG0, + oxmIdDstField: openflow15.NXM_NX_XXREG1, + nBits: 128, + }, + }, + { + hasRange: false, + srcRegName: "NXM_NX_TUN_METADATA0", + dstRegName: "NXM_NX_TUN_METADATA1", + expected: &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_NXM_1, + oxmIdDstClass: openflow15.OXM_CLASS_NXM_1, + oxmIdSrcField: openflow15.NXM_NX_TUN_METADATA0, + oxmIdDstField: openflow15.NXM_NX_TUN_METADATA1, + nBits: 1024, + }, + }, + { + hasRange: false, + srcRegName: "OXM_OF_ETH_SRC", + dstRegName: "OXM_OF_ETH_DST", + expected: &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_OPENFLOW_BASIC, + oxmIdDstClass: openflow15.OXM_CLASS_OPENFLOW_BASIC, + oxmIdSrcField: openflow15.OXM_FIELD_ETH_SRC, + oxmIdDstField: openflow15.OXM_FIELD_ETH_DST, + nBits: 48, + }, + }, + { + hasRange: true, + srcRegName: "OXM_OF_ETH_SRC", + dstRegName: "NXM_NX_REG6", + srcRange: Range{16, 31}, + dstRange: Range{1, 16}, + expected: &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_OPENFLOW_BASIC, + oxmIdDstClass: openflow15.OXM_CLASS_NXM_1, + oxmIdSrcField: openflow15.OXM_FIELD_ETH_SRC, + oxmIdDstField: openflow15.NXM_NX_REG6, + srcOffset: 16, + dstOffset: 1, + nBits: 16, + }, + }, + { + hasRange: true, + srcRegName: "NXM_NX_REG1", + dstRegName: "OXM_OF_IPV4_SRC", + srcRange: Range{0, 15}, + dstRange: Range{16, 31}, + expected: &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_NXM_1, + oxmIdDstClass: openflow15.OXM_CLASS_OPENFLOW_BASIC, + oxmIdSrcField: openflow15.NXM_NX_REG1, + oxmIdDstField: openflow15.OXM_FIELD_IPV4_SRC, + srcOffset: 0, + dstOffset: 16, + nBits: 16, + }, + }, + } + for _, tc := range testCases { + var flow Flow + if tc.hasRange { + flow = table.BuildFlow(1).Action().MoveRange(tc.srcRegName, tc.dstRegName, tc.srcRange, tc.dstRange).Done() + } else { + flow = table.BuildFlow(1).Action().Move(tc.srcRegName, tc.dstRegName).Done() + } + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionCopyField(t, tc.expected, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("MoveFromTunMetadata", func(t *testing.T) { + testCases := []struct { + srcRegID int + dstRegName string + srcRange Range + dstRange Range + tlvLength uint8 + expected *actionCopyField + }{ + { + 0, + "NXM_NX_REG6", + Range{16, 31}, + Range{1, 16}, + 2, + &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_NXM_1, + oxmIdDstClass: openflow15.OXM_CLASS_NXM_1, + oxmIdSrcField: openflow15.NXM_NX_TUN_METADATA0, + oxmIdDstField: openflow15.NXM_NX_REG6, + srcOffset: 16, + dstOffset: 1, + nBits: 16, + }, + }, + { + 1, + "NXM_NX_REG7", + Range{0, 31}, + Range{0, 31}, + 3, + &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_NXM_1, + oxmIdDstClass: openflow15.OXM_CLASS_NXM_1, + oxmIdSrcField: openflow15.NXM_NX_TUN_METADATA1, + oxmIdDstField: openflow15.NXM_NX_REG7, + srcOffset: 0, + dstOffset: 0, + nBits: 32, + }, + }, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().MoveFromTunMetadata(tc.srcRegID, tc.dstRegName, tc.srcRange, tc.dstRange, tc.tlvLength).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkActionCopyField(t, tc.expected, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("ResubmitToTables", func(t *testing.T) { + testCases := []struct { + tableIDs []uint8 + }{ + {[]uint8{100}}, + {[]uint8{101, 102}}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().ResubmitToTables(tc.tableIDs...).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + actions := msg.GetMessage().(*openflow15.FlowMod).Instructions[0].(*openflow15.InstrActions).Actions + assert.Equal(t, len(tc.tableIDs), len(actions)) + for i := 0; i < len(actions); i++ { + assert.IsType(t, &openflow15.NXActionResubmitTable{}, actions[i]) + action := actions[i].(*openflow15.NXActionResubmitTable) + assert.Equal(t, uint16(openflow15.OFPP_IN_PORT), action.InPort) + assert.Equal(t, tc.tableIDs[i], action.TableID) + } + } + }) + t.Run("DecTTL", func(t *testing.T) { + f := table.BuildFlow(1).Action().DecTTL().Done() + msg, err := f.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + actions := flowMod.Instructions[0].(*openflow15.InstrActions).Actions + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.ActionDecNwTtl{}, actions[0]) + }) + t.Run("Conjunction", func(t *testing.T) { + testCases := []struct { + conjID uint32 + clauseID uint8 + nClause uint8 + }{ + {61, 2, 3}, + {50, 2, 2}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().Conjunction(tc.conjID, tc.clauseID, tc.nClause).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + actions := msg.GetMessage().(*openflow15.FlowMod).Instructions[0].(*openflow15.InstrActions).Actions + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.NXActionConjunction{}, actions[0]) + + action := actions[0].(*openflow15.NXActionConjunction) + assert.Equal(t, tc.conjID, action.ID) + assert.Equal(t, tc.nClause, action.NClause) + assert.Equal(t, tc.clauseID-1, action.Clause) + } + }) + t.Run("Group", func(t *testing.T) { + testCases := []struct { + groupID uint32 + }{ + {100}, + {200}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().Group(GroupIDType(tc.groupID)).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + actions := msg.GetMessage().(*openflow15.FlowMod).Instructions[0].(*openflow15.InstrActions).Actions + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.ActionGroup{}, actions[0]) + assert.Equal(t, tc.groupID, actions[0].(*openflow15.ActionGroup).GroupId) + } + }) + t.Run("Note", func(t *testing.T) { + testCases := []struct { + note string + }{ + {"note1"}, + {"note2"}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().Note(tc.note).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + actions := msg.GetMessage().(*openflow15.FlowMod).Instructions[0].(*openflow15.InstrActions).Actions + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.NXActionNote{}, actions[0]) + assert.Equal(t, []byte(tc.note), actions[0].(*openflow15.NXActionNote).Note) + } + }) + t.Run("Meter", func(t *testing.T) { + testCases := []struct { + meterId uint32 + }{ + {100}, + {200}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().Meter(tc.meterId).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + actions := msg.GetMessage().(*openflow15.FlowMod).Instructions[0].(*openflow15.InstrActions).Actions + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.ActionMeter{}, actions[0]) + assert.Equal(t, tc.meterId, actions[0].(*openflow15.ActionMeter).MeterId) + } + }) + t.Run("GotoTable", func(t *testing.T) { + testCases := []struct { + tableID uint8 + }{ + {100}, + {200}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().GotoTable(tc.tableID).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + assert.Equal(t, tc.tableID, flowMod.Instructions[0].(*openflow15.InstrGotoTable).TableId) + } + }) + t.Run("GotoStage", func(t *testing.T) { + stage1 := StageID(1) + stage2 := StageID(2) + testCases := []struct { + stageID StageID + tables []Table + expectedTableID uint8 + }{ + {stage1, []Table{NewOFTable(100, "table100", stage1, piplineID, missAction)}, 100}, + {stage2, []Table{NewOFTable(200, "table100", stage2, piplineID, missAction)}, 200}, + } + for _, tc := range testCases { + pipelineCache[piplineID] = &ofPipeline{ + pipelineID: piplineID, + tableMap: map[StageID][]Table{stageID: {table}}, + } + + pipelineCache[piplineID].tableMap[tc.stageID] = tc.tables + flow := table.BuildFlow(1).Action().GotoStage(tc.stageID).Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + assert.Equal(t, tc.expectedTableID, flowMod.Instructions[0].(*openflow15.InstrGotoTable).TableId) + } + }) + t.Run("CT", func(t *testing.T) { + regField1 := NewRegField(4, 1, 17) + regField2 := NewRegField(5, 0, 16) + testCases := []struct { + commit bool + tableID uint8 + zone int + zoneSrcField *RegField + expected *nxActionConnTrack + }{ + {true, 200, 100, nil, newExpectedNXActionConnTrack(true, 200, 100, nil)}, + {false, 201, 101, nil, newExpectedNXActionConnTrack(false, 201, 101, nil)}, + {true, 203, 0, regField1, newExpectedNXActionConnTrack(true, 203, 0, regField1)}, + {false, 204, 0, regField2, newExpectedNXActionConnTrack(false, 204, 0, regField2)}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().CT(tc.commit, tc.tableID, tc.zone, tc.zoneSrcField).CTDone().Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + flowMod := msg.GetMessage().(*openflow15.FlowMod) + checkNXActionConnTrack(t, tc.expected, flowMod.Instructions[0].(*openflow15.InstrActions).Actions) + } + }) + t.Run("Learn", func(t *testing.T) { + testCases := []struct { + tableID uint8 + priority uint16 + idleTimeout uint16 + hardTimeout uint16 + cookieID uint64 + }{ + {200, 0, 3600, 3600, uint64(0xffff)}, + {201, 1, 1800, 1800, uint64(0xfffe)}, + } + for _, tc := range testCases { + flow := table.BuildFlow(1).Action().Learn(tc.tableID, tc.priority, tc.idleTimeout, tc.hardTimeout, tc.cookieID).Done().Done() + msg, err := flow.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + actions := msg.GetMessage().(*openflow15.FlowMod).Instructions[0].(*openflow15.InstrActions).Actions + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.NXActionLearn{}, actions[0]) + + action := actions[0].(*openflow15.NXActionLearn) + assert.Equal(t, tc.idleTimeout, action.IdleTimeout) + assert.Equal(t, tc.hardTimeout, action.HardTimeout) + assert.Equal(t, tc.priority, action.Priority) + assert.Equal(t, tc.cookieID, action.Cookie) + assert.Equal(t, tc.tableID, action.TableID) + } + }) +} + +func TestCTActions(t *testing.T) { + table := NewOFTable(tableID1, tableName, stageID, piplineID, missAction) + table.(*ofTable).Table = new(ofctrl.Table) + ipRange := &IPRange{StartIP: net.ParseIP("1.1.1.1"), EndIP: net.ParseIP("1.1.1.2")} + portRange := &PortRange{StartPort: uint16(3333), EndPort: uint16(4444)} + + commit := true + nextTable := uint8(100) + zone := 100 + + t.Run("LoadToCtMark", func(t *testing.T) { + mark1 := NewCTMark(NewCTMarkField(0, 15), uint32(0xffff)) + mark2 := NewCTMark(NewCTMarkField(2, 6), uint32(0xe)) + mark3 := NewCTMark(NewCTMarkField(7, 9), uint32(0b11)) + f := func(value uint32, rng *Range) *actionSetField { + maskData := ^uint32(0) >> (32 - rng.Length()) << rng.Offset() + valueData := value << rng.Offset() + return &actionSetField{ + class: openflow15.OXM_CLASS_NXM_1, + field: openflow15.NXM_NX_CT_MARK, + fieldValue: &openflow15.Uint32Message{Data: valueData}, + fieldMask: &openflow15.Uint32Message{Data: maskData}, + } + } + expected1 := f(mark1.value, mark1.field.rng) + expected2 := f(mark2.value, mark2.field.rng) + expected3 := f(mark3.value, mark3.field.rng) + + testCases := []struct { + marks []*CtMark + expected []*actionSetField + }{ + {[]*CtMark{mark1}, []*actionSetField{expected1}}, + {[]*CtMark{mark2, mark3}, []*actionSetField{expected2, expected3}}, + } + + for _, tc := range testCases { + actions := table.BuildFlow(1).Action().CT(commit, nextTable, zone, nil).LoadToCtMark(tc.marks...).(*ofCTAction).actions + checkActionSetField(t, tc.expected, actions) + } + }) + t.Run("LoadToLabelField", func(t *testing.T) { + value1 := uint64(0xffff_ffff_ffff_ffff) + value2 := uint64(0xffff) + label1 := NewCTLabel(0, 63) + label2 := NewCTLabel(1, 16) + + f := func(value uint64, rng *Range) *actionSetField { + var labelBytes, maskBytes [16]byte + maskData := ^uint64(0) >> (64 - rng.Length()) << (rng.Offset() % 64) + valueData := value << (rng.Offset() % 64) + if rng.Offset() > 63 { + binary.BigEndian.PutUint64(maskBytes[0:8], maskData) + binary.BigEndian.PutUint64(labelBytes[0:8], valueData) + } else { + binary.BigEndian.PutUint64(maskBytes[8:], maskData) + binary.BigEndian.PutUint64(labelBytes[8:], valueData) + } + match := openflow15.NewCTLabelMatchField(labelBytes, &maskBytes) + return &actionSetField{ + class: openflow15.OXM_CLASS_NXM_1, + field: openflow15.NXM_NX_CT_LABEL, + fieldValue: match.Value.(*openflow15.CTLabel), + fieldMask: match.Mask.(*openflow15.CTLabel), + } + } + expected1 := f(value1, label1.rng) + expected2 := f(value2, label2.rng) + + testCases := []struct { + value uint64 + label *CtLabel + expected *actionSetField + }{ + {value1, label1, expected1}, + {value2, label2, expected2}, + } + for _, tc := range testCases { + actions := table.BuildFlow(1).Action().CT(commit, nextTable, zone, nil).LoadToLabelField(tc.value, tc.label).(*ofCTAction).actions + checkActionSetField(t, []*actionSetField{tc.expected}, actions) + } + }) + t.Run("MoveToLabel", func(t *testing.T) { + testCases := []struct { + srcRegName string + srcRng *Range + dstRng *Range + expected *actionCopyField + }{ + { + "NXM_NX_REG4", + &Range{4, 7}, + &Range{0, 3}, + &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_NXM_1, + oxmIdDstClass: openflow15.OXM_CLASS_NXM_1, + oxmIdSrcField: openflow15.NXM_NX_REG4, + oxmIdDstField: openflow15.NXM_NX_CT_LABEL, + srcOffset: 4, + dstOffset: 0, + nBits: 4, + }, + }, + { + "NXM_OF_ETH_DST", + &Range{0, 47}, + &Range{1, 48}, + &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_NXM_0, + oxmIdDstClass: openflow15.OXM_CLASS_NXM_1, + oxmIdSrcField: openflow15.NXM_OF_ETH_DST, + oxmIdDstField: openflow15.NXM_NX_CT_LABEL, + srcOffset: 0, + dstOffset: 1, + nBits: 48, + }, + }, + } + for _, tc := range testCases { + actions := table.BuildFlow(1).Action().CT(commit, nextTable, zone, nil).MoveToLabel(tc.srcRegName, tc.srcRng, tc.dstRng).(*ofCTAction).actions + checkActionCopyField(t, tc.expected, actions) + } + }) + t.Run("MoveToCtMarkField", func(t *testing.T) { + testCases := []struct { + srcRegField *RegField + dstCtMarkField *CtMarkField + expected *actionCopyField + }{ + { + NewRegField(1, 1, 16), + NewCTMarkField(2, 17), + &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_NXM_1, + oxmIdDstClass: openflow15.OXM_CLASS_NXM_1, + oxmIdSrcField: openflow15.NXM_NX_REG1, + oxmIdDstField: openflow15.NXM_NX_CT_MARK, + srcOffset: 1, + dstOffset: 2, + nBits: 16, + }, + }, + { + NewRegField(4, 0, 31), + NewCTMarkField(0, 31), + &actionCopyField{ + oxmIdSrcClass: openflow15.OXM_CLASS_NXM_1, + oxmIdDstClass: openflow15.OXM_CLASS_NXM_1, + oxmIdSrcField: openflow15.NXM_NX_REG4, + oxmIdDstField: openflow15.NXM_NX_CT_MARK, + srcOffset: 0, + dstOffset: 0, + nBits: 32, + }, + }, + } + for _, tc := range testCases { + actions := table.BuildFlow(1).Action().CT(commit, nextTable, zone, nil).MoveToCtMarkField(tc.srcRegField, tc.dstCtMarkField).(*ofCTAction).actions + checkActionCopyField(t, tc.expected, actions) + } + }) + t.Run("SNAT,DNAT,NAT", func(t *testing.T) { + testCases := []struct { + isSNAT bool + isDNAT bool + ipRange *IPRange + portRange *PortRange + expectedFlags uint16 + }{ + { + true, + false, + ipRange, + portRange, + openflow15.NX_NAT_F_SRC, + }, + { + false, + true, + ipRange, + portRange, + openflow15.NX_NAT_F_DST, + }, + { + false, + false, + nil, + nil, + 0, + }, + } + for _, tc := range testCases { + var actions []openflow15.Action + if tc.isSNAT { + actions = table.BuildFlow(1).Action().CT(commit, nextTable, zone, nil).SNAT(tc.ipRange, tc.portRange).(*ofCTAction).actions + } else if tc.isDNAT { + actions = table.BuildFlow(1).Action().CT(commit, nextTable, zone, nil).DNAT(tc.ipRange, tc.portRange).(*ofCTAction).actions + } else { + actions = table.BuildFlow(1).Action().CT(commit, nextTable, zone, nil).NAT().(*ofCTAction).actions + } + assert.Equal(t, 1, len(actions)) + assert.IsType(t, &openflow15.NXActionCTNAT{}, actions[0]) + action := actions[0].(*openflow15.NXActionCTNAT) + assert.Equal(t, tc.expectedFlags, action.Flags) + } + }) +} + +type nxLearSpec struct { + srcClass uint16 + srcField uint8 + srcOffset uint16 + dstClass uint16 + dstField uint8 + dstOffset uint16 + srcValue []uint8 +} + +func newExpectedMatchEthernetProtocolIPAction(isIPv6 bool) *nxLearSpec { + spec := &nxLearSpec{ + dstClass: openflow15.OXM_CLASS_NXM_0, + dstField: openflow15.NXM_OF_ETH_TYPE, + } + ethTypeVal := make([]byte, 2) + var ipProto uint16 = 0x800 + if isIPv6 { + ipProto = 0x86dd + } + binary.BigEndian.PutUint16(ethTypeVal, ipProto) + spec.srcValue = ethTypeVal + return spec +} + +func newExpectedMatchLearnedTransportDstActions(protocol Protocol) []*nxLearSpec { + var ipProtoValue int + var field uint8 + isIPv6 := false + switch protocol { + case ProtocolTCP: + ipProtoValue = ofctrl.IP_PROTO_TCP + field = openflow15.OXM_FIELD_TCP_DST + case ProtocolUDP: + ipProtoValue = ofctrl.IP_PROTO_UDP + field = openflow15.OXM_FIELD_UDP_DST + case ProtocolSCTP: + ipProtoValue = ofctrl.IP_PROTO_SCTP + field = openflow15.OXM_FIELD_SCTP_DST + case ProtocolTCPv6: + ipProtoValue = ofctrl.IP_PROTO_TCP + field = openflow15.OXM_FIELD_TCP_DST + isIPv6 = true + case ProtocolUDPv6: + ipProtoValue = ofctrl.IP_PROTO_UDP + field = openflow15.OXM_FIELD_UDP_DST + isIPv6 = true + case ProtocolSCTPv6: + ipProtoValue = ofctrl.IP_PROTO_SCTP + field = openflow15.OXM_FIELD_SCTP_DST + isIPv6 = true + } + + specs := []*nxLearSpec{newExpectedMatchEthernetProtocolIPAction(isIPv6)} + + ipTypeVal := make([]byte, 2) + ipTypeVal[1] = byte(ipProtoValue) + spec := &nxLearSpec{ + dstClass: openflow15.OXM_CLASS_NXM_0, + dstField: openflow15.NXM_OF_IP_PROTO, + srcValue: ipTypeVal, + } + specs = append(specs, spec) + + spec = &nxLearSpec{ + srcClass: openflow15.OXM_CLASS_OPENFLOW_BASIC, + srcField: field, + dstClass: openflow15.OXM_CLASS_OPENFLOW_BASIC, + dstField: field, + } + specs = append(specs, spec) + + return specs +} + +func checkLearnSpecs(t *testing.T, expected []*nxLearSpec, specs []*openflow15.NXLearnSpec) { + assert.Equal(t, len(expected), len(specs)) + for i := 0; i < len(expected); i++ { + if specs[i].SrcField != nil { + assert.Equal(t, expected[i].srcClass, specs[i].SrcField.Field.Class) + assert.Equal(t, expected[i].srcField, specs[i].SrcField.Field.Field) + assert.Equal(t, expected[i].srcOffset, specs[i].SrcField.Ofs) + } + if specs[i].DstField != nil { + assert.Equal(t, expected[i].dstClass, specs[i].DstField.Field.Class) + assert.Equal(t, expected[i].dstField, specs[i].DstField.Field.Field) + assert.Equal(t, expected[i].dstOffset, specs[i].DstField.Ofs) + } + if specs[i].SrcValue != nil { + assert.Equal(t, expected[i].srcValue, specs[i].SrcValue) + } + } +} + +func TestLearnActions(t *testing.T) { + table := NewOFTable(tableID1, tableName, stageID, piplineID, missAction) + targetTable := uint8(100) + priority := uint16(101) + idleTimeout := uint16(120) + hardTimeout := uint16(3600) + cookieID := uint64(0xffffffff) + + t.Run("MatchEthernetProtocolIP", func(t *testing.T) { + testCases := []struct { + isIPv6 bool + expected []*nxLearSpec + }{ + {false, []*nxLearSpec{newExpectedMatchEthernetProtocolIPAction(false)}}, + {true, []*nxLearSpec{newExpectedMatchEthernetProtocolIPAction(true)}}, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).Action().Learn(targetTable, priority, idleTimeout, hardTimeout, cookieID) + action := fb.MatchEthernetProtocolIP(tc.isIPv6).(*ofLearnAction).nxLearn.GetActionMessage().(*openflow15.NXActionLearn) + checkLearnSpecs(t, tc.expected, action.LearnSpecs) + } + }) + t.Run("MatchTransportDst", func(t *testing.T) { + testCases := []struct { + protocol Protocol + expected []*nxLearSpec + }{ + {ProtocolTCP, newExpectedMatchLearnedTransportDstActions(ProtocolTCP)}, + {ProtocolTCPv6, newExpectedMatchLearnedTransportDstActions(ProtocolTCPv6)}, + {ProtocolUDP, newExpectedMatchLearnedTransportDstActions(ProtocolUDP)}, + {ProtocolUDPv6, newExpectedMatchLearnedTransportDstActions(ProtocolUDPv6)}, + {ProtocolSCTP, newExpectedMatchLearnedTransportDstActions(ProtocolSCTP)}, + {ProtocolSCTPv6, newExpectedMatchLearnedTransportDstActions(ProtocolSCTPv6)}, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).Action().Learn(targetTable, priority, idleTimeout, hardTimeout, cookieID) + action := fb.MatchTransportDst(tc.protocol).(*ofLearnAction).nxLearn.GetActionMessage().(*openflow15.NXActionLearn) + checkLearnSpecs(t, tc.expected, action.LearnSpecs) + } + }) + t.Run("MatchLearnedSrcIP", func(t *testing.T) { + expected := &nxLearSpec{ + srcClass: openflow15.OXM_CLASS_NXM_0, + srcField: openflow15.NXM_OF_IP_SRC, + dstClass: openflow15.OXM_CLASS_NXM_0, + dstField: openflow15.NXM_OF_IP_SRC, + } + fb := table.BuildFlow(1).Action().Learn(targetTable, priority, idleTimeout, hardTimeout, cookieID) + action := fb.MatchLearnedSrcIP().(*ofLearnAction).nxLearn.GetActionMessage().(*openflow15.NXActionLearn) + checkLearnSpecs(t, []*nxLearSpec{expected}, action.LearnSpecs) + }) + t.Run("MatchLearnedDstIP", func(t *testing.T) { + expected := &nxLearSpec{ + srcClass: openflow15.OXM_CLASS_NXM_0, + srcField: openflow15.NXM_OF_IP_DST, + dstClass: openflow15.OXM_CLASS_NXM_0, + dstField: openflow15.NXM_OF_IP_DST, + } + fb := table.BuildFlow(1).Action().Learn(targetTable, priority, idleTimeout, hardTimeout, cookieID) + action := fb.MatchLearnedDstIP().(*ofLearnAction).nxLearn.GetActionMessage().(*openflow15.NXActionLearn) + checkLearnSpecs(t, []*nxLearSpec{expected}, action.LearnSpecs) + }) + t.Run("MatchLearnedSrcIPv6", func(t *testing.T) { + expected := &nxLearSpec{ + srcClass: openflow15.OXM_CLASS_NXM_1, + srcField: openflow15.NXM_NX_IPV6_SRC, + dstClass: openflow15.OXM_CLASS_NXM_1, + dstField: openflow15.NXM_NX_IPV6_SRC, + } + fb := table.BuildFlow(1).Action().Learn(targetTable, priority, idleTimeout, hardTimeout, cookieID) + action := fb.MatchLearnedSrcIPv6().(*ofLearnAction).nxLearn.GetActionMessage().(*openflow15.NXActionLearn) + checkLearnSpecs(t, []*nxLearSpec{expected}, action.LearnSpecs) + }) + t.Run("MatchLearnedDstIPv6", func(t *testing.T) { + expected := &nxLearSpec{ + srcClass: openflow15.OXM_CLASS_NXM_1, + srcField: openflow15.NXM_NX_IPV6_DST, + dstClass: openflow15.OXM_CLASS_NXM_1, + dstField: openflow15.NXM_NX_IPV6_DST, + } + fb := table.BuildFlow(1).Action().Learn(targetTable, priority, idleTimeout, hardTimeout, cookieID) + action := fb.MatchLearnedDstIPv6().(*ofLearnAction).nxLearn.GetActionMessage().(*openflow15.NXActionLearn) + checkLearnSpecs(t, []*nxLearSpec{expected}, action.LearnSpecs) + }) + t.Run("MatchRegMark", func(t *testing.T) { + testCases := []struct { + mark *RegMark + expected *nxLearSpec + }{ + { + NewRegMark(NewRegField(11, 1, 16), 0xffff), + &nxLearSpec{ + dstClass: openflow15.OXM_CLASS_NXM_1, + dstField: openflow15.NXM_NX_REG11, + dstOffset: 1, + srcValue: []uint8{0xff, 0xff}, + }, + }, + { + NewRegMark(NewRegField(1, 0, 31), 0xffff_ffff), + &nxLearSpec{ + dstClass: openflow15.OXM_CLASS_NXM_1, + dstField: openflow15.NXM_NX_REG1, + dstOffset: 0, + srcValue: []uint8{0xff, 0xff, 0xff, 0xff}, + }, + }, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).Action().Learn(targetTable, priority, idleTimeout, hardTimeout, cookieID) + action := fb.MatchRegMark(tc.mark).(*ofLearnAction).nxLearn.GetActionMessage().(*openflow15.NXActionLearn) + checkLearnSpecs(t, []*nxLearSpec{tc.expected}, action.LearnSpecs) + } + }) + t.Run("LoadFieldToField", func(t *testing.T) { + testCases := []struct { + srcField *RegField + dstField *RegField + expected *nxLearSpec + }{ + { + NewRegField(1, 1, 16), + NewRegField(2, 2, 17), + &nxLearSpec{ + srcClass: openflow15.OXM_CLASS_NXM_1, + srcField: openflow15.NXM_NX_REG1, + srcOffset: 1, + dstClass: openflow15.OXM_CLASS_NXM_1, + dstField: openflow15.NXM_NX_REG2, + dstOffset: 2, + }, + }, + { + NewRegField(3, 0, 31), + NewRegField(4, 0, 31), + &nxLearSpec{ + srcClass: openflow15.OXM_CLASS_NXM_1, + srcField: openflow15.NXM_NX_REG3, + srcOffset: 0, + dstClass: openflow15.OXM_CLASS_NXM_1, + dstField: openflow15.NXM_NX_REG4, + dstOffset: 0, + }, + }, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).Action().Learn(targetTable, priority, idleTimeout, hardTimeout, cookieID) + action := fb.LoadFieldToField(tc.srcField, tc.dstField).(*ofLearnAction).nxLearn.GetActionMessage().(*openflow15.NXActionLearn) + checkLearnSpecs(t, []*nxLearSpec{tc.expected}, action.LearnSpecs) + } + }) + t.Run("LoadXXRegToXXReg", func(t *testing.T) { + testCases := []struct { + srcField *XXRegField + dstField *XXRegField + expected *nxLearSpec + }{ + { + NewXXRegField(0, 1, 100), + NewXXRegField(1, 2, 101), + &nxLearSpec{ + srcClass: openflow15.OXM_CLASS_NXM_1, + srcField: openflow15.NXM_NX_XXREG0, + srcOffset: 1, + dstClass: openflow15.OXM_CLASS_NXM_1, + dstField: openflow15.NXM_NX_XXREG1, + dstOffset: 2, + }, + }, + { + NewXXRegField(2, 0, 127), + NewXXRegField(3, 0, 127), + &nxLearSpec{ + srcClass: openflow15.OXM_CLASS_NXM_1, + srcField: openflow15.NXM_NX_XXREG2, + srcOffset: 0, + dstClass: openflow15.OXM_CLASS_NXM_1, + dstField: openflow15.NXM_NX_XXREG3, + dstOffset: 0, + }, + }, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).Action().Learn(targetTable, priority, idleTimeout, hardTimeout, cookieID) + action := fb.LoadXXRegToXXReg(tc.srcField, tc.dstField).(*ofLearnAction).nxLearn.GetActionMessage().(*openflow15.NXActionLearn) + checkLearnSpecs(t, []*nxLearSpec{tc.expected}, action.LearnSpecs) + } + }) + t.Run("LoadRegMark", func(t *testing.T) { + testCases := []struct { + mark *RegMark + expected *nxLearSpec + }{ + { + NewRegMark(NewRegField(11, 1, 16), 0xffff), + &nxLearSpec{ + dstClass: openflow15.OXM_CLASS_NXM_1, + dstField: openflow15.NXM_NX_REG11, + dstOffset: 1, + srcValue: []uint8{0xff, 0xff}, + }, + }, + { + NewRegMark(NewRegField(1, 0, 31), 0xffff_ffff), + &nxLearSpec{ + dstClass: openflow15.OXM_CLASS_NXM_1, + dstField: openflow15.NXM_NX_REG1, + dstOffset: 0, + srcValue: []uint8{0xff, 0xff, 0xff, 0xff}, + }, + }, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).Action().Learn(targetTable, priority, idleTimeout, hardTimeout, cookieID) + action := fb.LoadRegMark(tc.mark).(*ofLearnAction).nxLearn.GetActionMessage().(*openflow15.NXActionLearn) + checkLearnSpecs(t, []*nxLearSpec{tc.expected}, action.LearnSpecs) + } + }) + t.Run("DeleteLearned", func(t *testing.T) { + fb := table.BuildFlow(1).Action().Learn(targetTable, priority, idleTimeout, hardTimeout, cookieID) + action := fb.DeleteLearned().(*ofLearnAction).nxLearn.GetActionMessage().(*openflow15.NXActionLearn) + assert.Equal(t, uint16(openflow15.NX_LEARN_F_DELETE_LEARNED), action.Flags) + }) +} diff --git a/pkg/ovs/openflow/ofctrl_builder_test.go b/pkg/ovs/openflow/ofctrl_builder_test.go index 375507b04c2..04bad402e6e 100644 --- a/pkg/ovs/openflow/ofctrl_builder_test.go +++ b/pkg/ovs/openflow/ofctrl_builder_test.go @@ -16,29 +16,661 @@ package openflow import ( "fmt" + "net" "testing" + "antrea.io/libOpenflow/openflow15" + "antrea.io/libOpenflow/protocol" "antrea.io/ofnet/ofctrl" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" ) -func TestMatchCTLabelRange(t *testing.T) { - for _, tc := range []struct { - rng *Range - expectedLowMask, expectedHighMask uint64 - }{ - {rng: &Range{0, 0}, expectedLowMask: 0x1, expectedHighMask: 0x0}, - {rng: &Range{1, 1}, expectedLowMask: 0b10, expectedHighMask: 0x0}, - {rng: &Range{127, 127}, expectedLowMask: 0x0, expectedHighMask: 0x8000_0000_0000_0000}, - {rng: &Range{126, 127}, expectedLowMask: 0x0, expectedHighMask: 0xc000_0000_0000_0000}, - {rng: &Range{0, 127}, expectedLowMask: 0xffff_ffff_ffff_ffff, expectedHighMask: 0xffff_ffff_ffff_ffff}, - {rng: &Range{0, 64}, expectedLowMask: 0xffff_ffff_ffff_ffff, expectedHighMask: 0x1}, - {rng: &Range{0, 63}, expectedLowMask: 0xffff_ffff_ffff_ffff, expectedHighMask: 0x0}, - {rng: &Range{64, 127}, expectedLowMask: 0x0, expectedHighMask: 0xffff_ffff_ffff_ffff}, - } { - match := new(ofctrl.FlowMatch) - ctLabelRange(0, 0, tc.rng, match) - require.Equal(t, tc.expectedHighMask, match.CtLabelHiMask, fmt.Sprintf("Expected high mask is equal, test case: %+v", tc)) - require.Equal(t, tc.expectedLowMask, match.CtLabelLoMask, fmt.Sprintf("Expected low mask is equal, test case: %+v", tc)) - } +func TestFlowBuilder(t *testing.T) { + table := NewOFTable(tableID1, tableName, stageID, piplineID, missAction) + table.SetNext(tableID2) + table.(*ofTable).Table = new(ofctrl.Table) + + t.Run("MatchTunMetadata", func(t *testing.T) { + fb := table.BuildFlow(1).MatchTunMetadata(1, uint32(0xfeef)).(*ofFlowBuilder) + expected := []*ofctrl.NXTunMetadata{{ID: 1, Data: uint32(0xfeef), Range: openflow15.NewNXRange(0, 31)}} + assert.Equal(t, expected, fb.ofFlow.Match.TunMetadatas) + //TODO: add match str + }) + t.Run("MatchVLAN", func(t *testing.T) { + mask := uint16(0xff) + testCases := []struct { + nonVLAN bool + vlanID uint16 + vlanMask *uint16 + }{ + {false, 0xf1, &mask}, + {true, 0, nil}, + {true, 0xf1, nil}, + } + + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchVLAN(tc.nonVLAN, tc.vlanID, tc.vlanMask).(*ofFlowBuilder) + assert.Equal(t, tc.nonVLAN, fb.Match.NonVlan) + assert.Equal(t, &tc.vlanID, fb.Match.VlanId) + assert.Equal(t, tc.vlanMask, fb.Match.VlanMask) + assert.Equal(t, []string{fmt.Sprintf("dl_vlan=%d", tc.vlanID)}, fb.matchers) + } + }) + t.Run("SetHardTimeout", func(t *testing.T) { + fb := table.BuildFlow(1).SetHardTimeout(uint16(3600)).(*ofFlowBuilder) + assert.Equal(t, uint16(3600), fb.ofFlow.HardTimeout) + }) + t.Run("SetIdleTimeout", func(t *testing.T) { + fb := table.BuildFlow(1).SetIdleTimeout(uint16(3600)).(*ofFlowBuilder) + assert.Equal(t, uint16(3600), fb.ofFlow.IdleTimeout) + }) + t.Run("MatchXXReg", func(t *testing.T) { + testCases := []struct { + regID int + data []byte + expectedMatcher string + }{ + {0, []byte{0x12, 0x34, 0x56}, "xxreg0=0x123456"}, + {1, []byte{0x11, 0x22}, "xxreg1=0x1122"}, + {2, []byte{0xff, 0xff, 0xff, 0xff}, "xxreg2=0xffffffff"}, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchXXReg(tc.regID, tc.data).(*ofFlowBuilder) + expectedXXRegs := []*ofctrl.XXRegister{{ID: tc.regID, Data: tc.data}} + assert.Equal(t, expectedXXRegs, fb.Match.XxRegs) + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchRegMark/MatchRegFieldWithValue", func(t *testing.T) { + mark1 := NewRegMark(NewRegField(1, 0, 31), 0xeeeeffff) + mark2 := NewRegMark(NewRegField(2, 2, 5), 0xf) + mark3 := NewRegMark(NewRegField(3, 3, 10), 0xee) + testCases := []struct { + marks []*RegMark + expectedMatchers []string + }{ + {[]*RegMark{mark1}, []string{"reg1=0xeeeeffff"}}, + {[]*RegMark{mark2, mark3}, []string{"reg2[2..5]=0xf", "reg3[3..10]=0xee"}}, + } + + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchRegMark(tc.marks...).(*ofFlowBuilder) + + var expectedNXRegs []*ofctrl.NXRegister + for _, mark := range tc.marks { + regID := mark.field.regID + data := mark.value + rng := mark.field.rng + if rng.Length() == 32 { + expectedNXRegs = append(expectedNXRegs, &ofctrl.NXRegister{ID: regID, Data: data}) + } else { + expectedNXRegs = append(expectedNXRegs, &ofctrl.NXRegister{ID: regID, Data: data, Range: rng.ToNXRange()}) + } + } + assert.Equal(t, expectedNXRegs, fb.Match.NxRegs) + assert.Equal(t, tc.expectedMatchers, fb.matchers) + } + }) + t.Run("MatchCTState*", func(t *testing.T) { + testCases := []struct { + state string + set bool + expectedMatcher string + }{ + {"new", true, "ct_state=+new"}, + {"new", false, "ct_state=-new"}, + {"rel", true, "ct_state=+rel"}, + {"rel", false, "ct_state=-rel"}, + {"rpl", true, "ct_state=+rpl"}, + {"rpl", false, "ct_state=-rpl"}, + {"est", true, "ct_state=+est"}, + {"est", false, "ct_state=-est"}, + {"trk", true, "ct_state=+trk"}, + {"trk", false, "ct_state=-trk"}, + {"inv", true, "ct_state=+inv"}, + {"inv", false, "ct_state=-inv"}, + {"dnat", true, "ct_state=+dnat"}, + {"dnat", false, "ct_state=-dnat"}, + {"snat", true, "ct_state=+snat"}, + {"snat", false, "ct_state=-snat"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + expectedCtStates := openflow15.NewCTStates() + switch tc.state { + case "new": + fb = table.BuildFlow(1).MatchCTStateNew(tc.set).(*ofFlowBuilder) + if tc.set { + expectedCtStates.SetNew() + } else { + expectedCtStates.UnsetNew() + } + case "rel": + fb = table.BuildFlow(1).MatchCTStateRel(tc.set).(*ofFlowBuilder) + if tc.set { + expectedCtStates.SetRel() + } else { + expectedCtStates.UnsetRel() + } + case "rpl": + fb = table.BuildFlow(1).MatchCTStateRpl(tc.set).(*ofFlowBuilder) + if tc.set { + expectedCtStates.SetRpl() + } else { + expectedCtStates.UnsetRpl() + } + case "est": + fb = table.BuildFlow(1).MatchCTStateEst(tc.set).(*ofFlowBuilder) + if tc.set { + expectedCtStates.SetEst() + } else { + expectedCtStates.UnsetEst() + } + case "trk": + fb = table.BuildFlow(1).MatchCTStateTrk(tc.set).(*ofFlowBuilder) + if tc.set { + expectedCtStates.SetTrk() + } else { + expectedCtStates.UnsetTrk() + } + case "inv": + fb = table.BuildFlow(1).MatchCTStateInv(tc.set).(*ofFlowBuilder) + if tc.set { + expectedCtStates.SetInv() + } else { + expectedCtStates.UnsetInv() + } + case "dnat": + fb = table.BuildFlow(1).MatchCTStateDNAT(tc.set).(*ofFlowBuilder) + if tc.set { + expectedCtStates.SetDNAT() + } else { + expectedCtStates.UnsetDNAT() + } + case "snat": + fb = table.BuildFlow(1).MatchCTStateSNAT(tc.set).(*ofFlowBuilder) + if tc.set { + expectedCtStates.SetSNAT() + } else { + expectedCtStates.UnsetSNAT() + } + } + assert.Equal(t, expectedCtStates, fb.ctStates) + assert.Equal(t, tc.expectedMatcher, fb.ctStateString) + } + }) + t.Run("MatchPktMark", func(t *testing.T) { + mask := uint32(0xf) + testCases := []struct { + value uint32 + mask *uint32 + expectedMatcher string + }{ + {2, &mask, "pkt_mark=2"}, + {8, nil, "pkt_mark=8"}, + } + + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchPktMark(tc.value, tc.mask).(*ofFlowBuilder) + + assert.Equal(t, tc.value, fb.ofFlow.Match.PktMark) + assert.Equal(t, tc.mask, fb.ofFlow.Match.PktMarkMask) + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchTunnelDst", func(t *testing.T) { + testCases := []struct { + ip net.IP + expectedMatcher string + }{ + {net.ParseIP("1.1.1.1"), "tun_dst=1.1.1.1"}, + {net.ParseIP("255.255.255.255"), "tun_dst=255.255.255.255"}, + {net.ParseIP("fec0::1111"), "tun_ipv6_dst=fec0::1111"}, + {net.ParseIP("fe80::ffff"), "tun_ipv6_dst=fe80::ffff"}, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchTunnelDst(tc.ip).(*ofFlowBuilder) + assert.Equal(t, tc.ip, *fb.ofFlow.Match.TunnelDst) + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchCTLabelField", func(t *testing.T) { + testCases := []struct { + field *CtLabel + highLabel uint64 + lowLabel uint64 + expectedHighMask uint64 + expectedLowMask uint64 + expectedMatcher string + }{ + {NewCTLabel(0, 0), 0, 1, 0x0, 0x1, "ct_label[0..0]=0x01"}, + {NewCTLabel(1, 1), 0, 1, 0x0, 0b10, "ct_label[1..1]=0x01"}, + {NewCTLabel(127, 127), 1, 0, 0x8000_0000_0000_0000, 0x0, "ct_label[127..127]=0x10"}, + {NewCTLabel(126, 127), 3, 0, 0xc000_0000_0000_0000, 0x0, "ct_label[126..127]=0x30"}, + {NewCTLabel(0, 127), 0x8000_0000_0000_0001, 0xa000_0000_0000_0001, 0xffff_ffff_ffff_ffff, 0xffff_ffff_ffff_ffff, "ct_label[0..127]=0x8000000000000001a000000000000001"}, + {NewCTLabel(0, 64), 1, 0xa000_0000_0000_0001, 0x1, 0xffff_ffff_ffff_ffff, "ct_label[0..64]=0x1a000000000000001"}, + {NewCTLabel(0, 63), 0, 0xa000_0000_0000_0001, 0x0, 0xffff_ffff_ffff_ffff, "ct_label[0..63]=0x0a000000000000001"}, + {NewCTLabel(64, 127), 0xa000_0000_0000_0001, 0, 0xffff_ffff_ffff_ffff, 0x0, "ct_label[64..127]=0xa0000000000000010"}, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchCTLabelField(tc.highLabel, tc.lowLabel, tc.field).(*ofFlowBuilder) + assert.Equal(t, tc.expectedHighMask, fb.Match.CtLabelHiMask) + assert.Equal(t, tc.expectedLowMask, fb.Match.CtLabelLoMask) + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchInPort", func(t *testing.T) { + testCases := []struct { + inPort uint32 + expectedMatcher string + }{ + {1, "in_port=1"}, + {2, "in_port=2"}, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchInPort(tc.inPort).(*ofFlowBuilder) + assert.Equal(t, tc.inPort, fb.ofFlow.Match.InputPort) + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchSrcIP/MatchDstIP", func(t *testing.T) { + testCases := []struct { + isSrc bool + ip net.IP + expectedMatcher string + }{ + {true, net.ParseIP("1.1.1.1"), "nw_src=1.1.1.1"}, + {false, net.ParseIP("1.1.1.1"), "nw_dst=1.1.1.1"}, + {true, net.ParseIP("fec0::1111"), "ipv6_src=fec0::1111"}, + {false, net.ParseIP("fec0::1111"), "ipv6_dst=fec0::1111"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + if tc.isSrc { + fb = table.BuildFlow(1).MatchSrcIP(tc.ip).(*ofFlowBuilder) + assert.Equal(t, tc.ip, *fb.ofFlow.Match.IpSa) + } else { + fb = table.BuildFlow(1).MatchDstIP(tc.ip).(*ofFlowBuilder) + assert.Equal(t, tc.ip, *fb.ofFlow.Match.IpDa) + } + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchSrcIPNet/MatchDstIPNet", func(t *testing.T) { + _, ipv4Net1, _ := net.ParseCIDR("1.1.1.0/24") + _, ipv4Net2, _ := net.ParseCIDR("1.1.1.1/32") + _, ipv6Net1, _ := net.ParseCIDR("fe80::1111/64") + _, ipv6Net2, _ := net.ParseCIDR("fec0::ffff/128") + testCases := []struct { + isSrc bool + ipNet *net.IPNet + expectedMask net.IP + expectedMatcher string + }{ + {true, ipv4Net1, net.IP(ipv4Net1.Mask), "nw_src=1.1.1.0/24"}, + {true, ipv4Net2, net.IP(ipv4Net2.Mask), "nw_src=1.1.1.1/32"}, + {true, ipv6Net1, net.IP(ipv6Net1.Mask), "ipv6_src=fe80::/64"}, + {true, ipv6Net2, net.IP(ipv6Net2.Mask), "ipv6_src=fec0::ffff/128"}, + {false, ipv4Net1, net.IP(ipv4Net1.Mask), "nw_dst=1.1.1.0/24"}, + {false, ipv4Net2, net.IP(ipv4Net2.Mask), "nw_dst=1.1.1.1/32"}, + {false, ipv6Net1, net.IP(ipv6Net1.Mask), "ipv6_dst=fe80::/64"}, + {false, ipv6Net2, net.IP(ipv6Net2.Mask), "ipv6_dst=fec0::ffff/128"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + if tc.isSrc { + fb = table.BuildFlow(1).MatchSrcIPNet(*tc.ipNet).(*ofFlowBuilder) + assert.Equal(t, tc.ipNet.IP, *fb.ofFlow.Match.IpSa) + assert.Equal(t, tc.expectedMask, *fb.ofFlow.Match.IpSaMask) + } else { + fb = table.BuildFlow(1).MatchDstIPNet(*tc.ipNet).(*ofFlowBuilder) + assert.Equal(t, tc.ipNet.IP, *fb.ofFlow.Match.IpDa) + assert.Equal(t, tc.expectedMask, *fb.ofFlow.Match.IpDaMask) + } + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchICMPType/MatchICMPv6Type", func(t *testing.T) { + testCases := []struct { + isIPv6 bool + icmpType byte + expectedMatcher string + }{ + {true, 0, "icmpv6_type=0"}, + {true, 3, "icmpv6_type=3"}, + {false, 0, "icmp_type=0"}, + {false, 3, "icmp_type=3"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + if tc.isIPv6 { + fb = table.BuildFlow(1).MatchICMPv6Type(tc.icmpType).(*ofFlowBuilder) + assert.Equal(t, tc.icmpType, *fb.ofFlow.Match.Icmp6Type) + } else { + fb = table.BuildFlow(1).MatchICMPType(tc.icmpType).(*ofFlowBuilder) + assert.Equal(t, tc.icmpType, *fb.ofFlow.Match.Icmp4Type) + } + + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchICMPCode/MatchICMPv6Code", func(t *testing.T) { + testCases := []struct { + isIPv6 bool + icmpCode byte + expectedMatcher string + }{ + {true, 10, "icmpv6_code=10"}, + {true, 11, "icmpv6_code=11"}, + {false, 10, "icmp_code=10"}, + {false, 11, "icmp_code=11"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + if tc.isIPv6 { + fb = table.BuildFlow(1).MatchICMPv6Code(tc.icmpCode).(*ofFlowBuilder) + assert.Equal(t, tc.icmpCode, *fb.ofFlow.Match.Icmp6Code) + } else { + fb = table.BuildFlow(1).MatchICMPCode(tc.icmpCode).(*ofFlowBuilder) + assert.Equal(t, tc.icmpCode, *fb.ofFlow.Match.Icmp4Code) + } + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchSrcMAC/MatchDstMAC", func(t *testing.T) { + mac1, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff") + mac2, _ := net.ParseMAC("aa:bb:cc:dd:ee:fe") + + testCases := []struct { + isSrc bool + mac net.HardwareAddr + expectedMatcher string + }{ + {true, mac1, "dl_src=aa:bb:cc:dd:ee:ff"}, + {true, mac2, "dl_src=aa:bb:cc:dd:ee:fe"}, + {false, mac1, "dl_dst=aa:bb:cc:dd:ee:ff"}, + {false, mac2, "dl_dst=aa:bb:cc:dd:ee:fe"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + if tc.isSrc { + fb = table.BuildFlow(1).MatchSrcMAC(tc.mac).(*ofFlowBuilder) + assert.Equal(t, tc.mac, *fb.ofFlow.Match.MacSa) + } else { + fb = table.BuildFlow(1).MatchDstMAC(tc.mac).(*ofFlowBuilder) + assert.Equal(t, tc.mac, *fb.ofFlow.Match.MacDa) + } + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchARPSha/MatchARPTha", func(t *testing.T) { + mac1, _ := net.ParseMAC("aa:bb:cc:dd:ee:ff") + mac2, _ := net.ParseMAC("aa:bb:cc:dd:ee:fe") + + testCases := []struct { + isSrc bool + mac net.HardwareAddr + expectedMatcher string + }{ + {true, mac1, "arp_sha=aa:bb:cc:dd:ee:ff"}, + {true, mac2, "arp_sha=aa:bb:cc:dd:ee:fe"}, + {false, mac1, "arp_tha=aa:bb:cc:dd:ee:ff"}, + {false, mac2, "arp_tha=aa:bb:cc:dd:ee:fe"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + if tc.isSrc { + fb = table.BuildFlow(1).MatchARPSha(tc.mac).(*ofFlowBuilder) + assert.Equal(t, tc.mac, *fb.ofFlow.Match.ArpSha) + } else { + fb = table.BuildFlow(1).MatchARPTha(tc.mac).(*ofFlowBuilder) + assert.Equal(t, tc.mac, *fb.ofFlow.Match.ArpTha) + } + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchARPSpa/MatchARPTpa", func(t *testing.T) { + testCases := []struct { + isSrc bool + ip net.IP + expectedMatcher string + }{ + {true, net.ParseIP("1.1.1.1"), "arp_spa=1.1.1.1"}, + {true, net.ParseIP("2.2.2.2"), "arp_spa=2.2.2.2"}, + {false, net.ParseIP("1.1.1.1"), "arp_tpa=1.1.1.1"}, + {false, net.ParseIP("2.2.2.2"), "arp_tpa=2.2.2.2"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + if tc.isSrc { + fb = table.BuildFlow(1).MatchARPSpa(tc.ip).(*ofFlowBuilder) + assert.Equal(t, tc.ip, *fb.ofFlow.Match.ArpSpa) + } else { + fb = table.BuildFlow(1).MatchARPTpa(tc.ip).(*ofFlowBuilder) + assert.Equal(t, tc.ip, *fb.ofFlow.Match.ArpTpa) + } + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchARPOp", func(t *testing.T) { + testCases := []struct { + dscp uint8 + expectedMatcher string + }{ + {1, "nw_tos=4"}, + {2, "nw_tos=8"}, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchIPDSCP(tc.dscp).(*ofFlowBuilder) + assert.Equal(t, tc.dscp, fb.ofFlow.Match.IpDscp) + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchConjID", func(t *testing.T) { + testCases := []struct { + conjID uint32 + expectedMatcher string + }{ + {1, "conj_id=1"}, + {2, "conj_id=2"}, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchConjID(tc.conjID).(*ofFlowBuilder) + assert.Equal(t, tc.conjID, *fb.ofFlow.Match.ConjunctionID) + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchPriority", func(t *testing.T) { + fb := table.BuildFlow(1).MatchPriority(uint16(100)).(*ofFlowBuilder) + assert.Equal(t, uint16(100), fb.ofFlow.Match.Priority) + }) + t.Run("MatchProtocol", func(t *testing.T) { + testCases := []struct { + protocol Protocol + expectedEthertype uint16 + expectedIpProto uint8 + }{ + {ProtocolIP, 0x0800, 0}, + {ProtocolIPv6, 0x86dd, 0}, + {ProtocolARP, 0x0806, 0}, + {ProtocolTCP, 0x0800, 6}, + {ProtocolTCPv6, 0x86dd, 6}, + {ProtocolUDP, 0x0800, 17}, + {ProtocolUDPv6, 0x86dd, 17}, + {ProtocolSCTP, 0x0800, 132}, + {ProtocolSCTPv6, 0x86dd, 132}, + {ProtocolICMP, 0x0800, 1}, + {ProtocolICMPv6, 0x86dd, 58}, + {ProtocolIGMP, 0x0800, 2}, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchProtocol(tc.protocol).(*ofFlowBuilder) + assert.Equal(t, tc.expectedEthertype, fb.ofFlow.Match.Ethertype) + assert.Equal(t, tc.expectedIpProto, fb.ofFlow.Match.IpProto) + assert.Equal(t, tc.protocol, fb.protocol) + } + }) + t.Run("MatchIPProtocolValue", func(t *testing.T) { + testCases := []struct { + isIPv6 bool + protoValue uint8 + expectedEtherType uint16 + }{ + {false, protocol.Type_TCP, 0x0800}, + {false, protocol.Type_TCP, 0x0800}, + {true, protocol.Type_TCP, 0x86dd}, + {true, protocol.Type_UDP, 0x86dd}, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchIPProtocolValue(tc.isIPv6, tc.protoValue).(*ofFlowBuilder) + assert.Equal(t, tc.expectedEtherType, fb.ofFlow.Match.Ethertype) + assert.Equal(t, tc.protoValue, fb.ofFlow.Match.IpProto) + } + }) + t.Run("MatchSrcPort/MatchDstPort", func(t *testing.T) { + portMask := uint16(0xf000) + testCases := []struct { + isSrc bool + port uint16 + portMask *uint16 + expectedMatcher string + }{ + {true, 0xf001, nil, "tp_src=0xf001"}, + {true, 0xf001, &portMask, "tp_src=0xf001/0xf000"}, + {false, 0xf001, nil, "tp_dst=0xf001"}, + {false, 0xf001, &portMask, "tp_dst=0xf001/0xf000"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + if tc.isSrc { + fb = table.BuildFlow(1).MatchSrcPort(tc.port, tc.portMask).(*ofFlowBuilder) + assert.Equal(t, tc.port, fb.ofFlow.Match.SrcPort) + assert.Equal(t, tc.portMask, fb.ofFlow.Match.SrcPortMask) + } else { + fb = table.BuildFlow(1).MatchDstPort(tc.port, tc.portMask).(*ofFlowBuilder) + assert.Equal(t, tc.port, fb.ofFlow.Match.DstPort) + assert.Equal(t, tc.portMask, fb.ofFlow.Match.DstPortMask) + } + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchCTSrcIP/MatchCTDstIP", func(t *testing.T) { + testCases := []struct { + isSrc bool + ip net.IP + expectedMatcher string + }{ + {true, net.ParseIP("1.1.1.1"), "ct_nw_src=1.1.1.1"}, + {false, net.ParseIP("1.1.1.1"), "ct_nw_dst=1.1.1.1"}, + {true, net.ParseIP("fec0::1111"), "ct_ipv6_src=fec0::1111"}, + {false, net.ParseIP("fec0::1111"), "ct_ipv6_dst=fec0::1111"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + if tc.isSrc { + fb = table.BuildFlow(1).MatchCTSrcIP(tc.ip).(*ofFlowBuilder) + assert.Equal(t, tc.ip, *fb.ofFlow.Match.CtIpSa) + } else { + fb = table.BuildFlow(1).MatchCTDstIP(tc.ip).(*ofFlowBuilder) + assert.Equal(t, tc.ip, *fb.ofFlow.Match.CtIpDa) + } + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchCTSrcIPNet/MatchCTDstIPNet", func(t *testing.T) { + _, ipv4Net1, _ := net.ParseCIDR("1.1.1.0/24") + _, ipv4Net2, _ := net.ParseCIDR("1.1.1.1/32") + _, ipv6Net1, _ := net.ParseCIDR("fe80::1111/64") + _, ipv6Net2, _ := net.ParseCIDR("fec0::ffff/128") + testCases := []struct { + isSrc bool + ipNet *net.IPNet + expectedMask net.IP + expectedMatcher string + }{ + {true, ipv4Net1, net.IP(ipv4Net1.Mask), "ct_nw_src=1.1.1.0/24"}, + {true, ipv4Net2, net.IP(ipv4Net2.Mask), "ct_nw_src=1.1.1.1/32"}, + {true, ipv6Net1, net.IP(ipv6Net1.Mask), "ct_ipv6_src=fe80::/64"}, + {true, ipv6Net2, net.IP(ipv6Net2.Mask), "ct_ipv6_src=fec0::ffff/128"}, + {false, ipv4Net1, net.IP(ipv4Net1.Mask), "ct_nw_dst=1.1.1.0/24"}, + {false, ipv4Net2, net.IP(ipv4Net2.Mask), "ct_nw_dst=1.1.1.1/32"}, + {false, ipv6Net1, net.IP(ipv6Net1.Mask), "ct_ipv6_dst=fe80::/64"}, + {false, ipv6Net2, net.IP(ipv6Net2.Mask), "ct_ipv6_dst=fec0::ffff/128"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + if tc.isSrc { + fb = table.BuildFlow(1).MatchCTSrcIPNet(*tc.ipNet).(*ofFlowBuilder) + assert.Equal(t, tc.ipNet.IP, *fb.ofFlow.Match.CtIpSa) + assert.Equal(t, tc.expectedMask, *fb.ofFlow.Match.CtIpSaMask) + } else { + fb = table.BuildFlow(1).MatchCTDstIPNet(*tc.ipNet).(*ofFlowBuilder) + assert.Equal(t, tc.ipNet.IP, *fb.ofFlow.Match.CtIpDa) + assert.Equal(t, tc.expectedMask, *fb.ofFlow.Match.CtIpDaMask) + } + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchCTSrcPort/MatchCTDstPort", func(t *testing.T) { + testCases := []struct { + isSrc bool + port uint16 + expectedMatcher string + }{ + {true, 100, "ct_tp_src=100"}, + {false, 101, "ct_tp_dst=101"}, + } + for _, tc := range testCases { + var fb *ofFlowBuilder + if tc.isSrc { + fb = table.BuildFlow(1).MatchCTSrcPort(tc.port).(*ofFlowBuilder) + assert.Equal(t, tc.port, fb.ofFlow.Match.CtTpSrcPort) + } else { + fb = table.BuildFlow(1).MatchCTDstPort(tc.port).(*ofFlowBuilder) + assert.Equal(t, tc.port, fb.ofFlow.Match.CtTpDstPort) + } + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("MatchCTProtocol", func(t *testing.T) { + testCases := []struct { + protocol Protocol + expectedIpProto uint8 + expectedMatcher string + }{ + {ProtocolTCP, 6, "ct_nw_proto=6"}, + {ProtocolUDP, 17, "ct_nw_proto=17"}, + {ProtocolSCTP, 132, "ct_nw_proto=132"}, + {ProtocolICMP, 1, "ct_nw_proto=1"}, + } + for _, tc := range testCases { + fb := table.BuildFlow(1).MatchCTProtocol(tc.protocol).(*ofFlowBuilder) + assert.Equal(t, tc.expectedIpProto, fb.ofFlow.Match.CtIpProto) + + assert.Equal(t, 1, len(fb.matchers)) + assert.Equal(t, tc.expectedMatcher, fb.matchers[0]) + } + }) + t.Run("Cookie", func(t *testing.T) { + fb := table.BuildFlow(1).Cookie(uint64(10)).(*ofFlowBuilder) + assert.Equal(t, uint64(10), fb.Flow.CookieID) + }) } diff --git a/pkg/ovs/openflow/ofctrl_group_test.go b/pkg/ovs/openflow/ofctrl_group_test.go new file mode 100644 index 00000000000..3bbf77a0e9c --- /dev/null +++ b/pkg/ovs/openflow/ofctrl_group_test.go @@ -0,0 +1,176 @@ +// Copyright 2022 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openflow + +import ( + "net" + "testing" + + "antrea.io/libOpenflow/openflow15" + "antrea.io/libOpenflow/util" + "antrea.io/ofnet/ofctrl" + "github.com/stretchr/testify/assert" +) + +func TestBucketBuilder(t *testing.T) { + g := &ofGroup{ofctrl: &ofctrl.Group{}} + + t.Run("LoadToRegField", func(t *testing.T) { + testCases := []struct { + regField *RegField + value uint32 + expected *actionSetField + }{ + { + NewRegField(1, 0, 31), + uint32(0xffff_ffff), + &actionSetField{ + class: openflow15.OXM_CLASS_NXM_1, + field: openflow15.NXM_NX_REG1, + fieldValue: &openflow15.Uint32Message{Data: uint32(0xffff_ffff)}, + fieldMask: &openflow15.Uint32Message{Data: uint32(0xffff_ffff)}, + }, + }, + { + NewRegField(1, 4, 15), + uint32(0xf), + &actionSetField{ + class: openflow15.OXM_CLASS_NXM_1, + field: openflow15.NXM_NX_REG1, + fieldValue: &openflow15.Uint32Message{Data: uint32(0xf0)}, + fieldMask: &openflow15.Uint32Message{Data: uint32(0xfff0)}, + }, + }, + } + for _, tc := range testCases { + g.ResetBuckets() + group := g.Bucket().LoadToRegField(tc.regField, tc.value).Done() + msg, err := group.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + groupMod := msg.GetMessage().(*openflow15.GroupMod) + assert.Equal(t, 1, len(groupMod.Buckets)) + checkActionSetField(t, []*actionSetField{tc.expected}, groupMod.Buckets[0].Actions) + } + }) + t.Run("LoadXXReg", func(t *testing.T) { + testCases := []struct { + regID int + data []byte + expected *actionSetField + }{ + { + 0, + []byte{0x11, 0x22, 0x33, 0x44}, + &actionSetField{ + class: openflow15.OXM_CLASS_NXM_1, + field: openflow15.NXM_NX_XXREG0, + fieldValue: util.NewBuffer([]byte{0x11, 0x22, 0x33, 0x44}), + }, + }, + { + 2, + []byte{0x11, 0x22, 0x33, 0x44}, + &actionSetField{ + class: openflow15.OXM_CLASS_NXM_1, + field: openflow15.NXM_NX_XXREG2, + fieldValue: util.NewBuffer([]byte{0x11, 0x22, 0x33, 0x44}), + }, + }, + } + for _, tc := range testCases { + g.ResetBuckets() + group := g.Bucket().LoadXXReg(tc.regID, tc.data).Done() + msg, err := group.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + groupMod := msg.GetMessage().(*openflow15.GroupMod) + assert.Equal(t, 1, len(groupMod.Buckets)) + checkActionSetField(t, []*actionSetField{tc.expected}, groupMod.Buckets[0].Actions) + } + }) + t.Run("SetTunnelDst", func(t *testing.T) { + testCases := []struct { + dstIP net.IP + expected *actionSetField + }{ + { + net.ParseIP("1.1.1.1"), + &actionSetField{ + class: openflow15.OXM_CLASS_NXM_1, + field: openflow15.NXM_NX_TUN_IPV4_DST, + tunnelIPv4Dst: net.ParseIP("1.1.1.1"), + }, + }, + { + net.ParseIP("2.2.2.2"), + &actionSetField{ + class: openflow15.OXM_CLASS_NXM_1, + field: openflow15.NXM_NX_TUN_IPV4_DST, + tunnelIPv4Dst: net.ParseIP("2.2.2.2"), + }, + }, + { + net.ParseIP("fec0::1111"), + &actionSetField{ + class: openflow15.OXM_CLASS_NXM_1, + field: openflow15.NXM_NX_TUN_IPV6_DST, + ipv6Dst: net.ParseIP("fec0::1111"), + }, + }, + { + net.ParseIP("fec0::2222"), + &actionSetField{ + class: openflow15.OXM_CLASS_NXM_1, + field: openflow15.NXM_NX_TUN_IPV6_DST, + ipv6Dst: net.ParseIP("fec0::2222"), + }, + }, + } + for _, tc := range testCases { + g.ResetBuckets() + group := g.Bucket().SetTunnelDst(tc.dstIP).Done() + msg, err := group.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + groupMod := msg.GetMessage().(*openflow15.GroupMod) + assert.Equal(t, 1, len(groupMod.Buckets)) + checkActionSetField(t, []*actionSetField{tc.expected}, groupMod.Buckets[0].Actions) + } + }) + t.Run("ResubmitToTable", func(t *testing.T) { + testCases := []struct { + tableID uint8 + }{ + {uint8(8)}, + {uint8(9)}, + } + for _, tc := range testCases { + g.ResetBuckets() + group := g.Bucket().ResubmitToTable(tc.tableID).Done() + msg, err := group.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + groupMod := msg.GetMessage().(*openflow15.GroupMod) + assert.Equal(t, 1, len(groupMod.Buckets)) + assert.Equal(t, 1, len(groupMod.Buckets[0].Actions)) + assert.IsType(t, &openflow15.NXActionResubmitTable{}, groupMod.Buckets[0].Actions[0]) + + action := groupMod.Buckets[0].Actions[0].(*openflow15.NXActionResubmitTable) + assert.Equal(t, uint16(openflow15.OFPP_IN_PORT), action.InPort) + assert.Equal(t, tc.tableID, action.TableID) + } + }) +} diff --git a/pkg/ovs/openflow/ofctrl_meter_test.go b/pkg/ovs/openflow/ofctrl_meter_test.go new file mode 100644 index 00000000000..93df4577dfc --- /dev/null +++ b/pkg/ovs/openflow/ofctrl_meter_test.go @@ -0,0 +1,121 @@ +// Copyright 2022 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package openflow + +import ( + "testing" + + "antrea.io/libOpenflow/openflow15" + "antrea.io/libOpenflow/util" + "antrea.io/ofnet/ofctrl" + "github.com/stretchr/testify/assert" +) + +func TestMeterBandBuilder(t *testing.T) { + m := &ofMeter{ofctrl: &ofctrl.Meter{}} + + t.Run("MeterType", func(t *testing.T) { + testCases := []struct { + meterType ofctrl.MeterType + expectedBandType util.Message + }{ + {ofctrl.MeterDrop, &openflow15.MeterBandDrop{}}, + {ofctrl.MeterDSCPRemark, &openflow15.MeterBandDSCP{}}, + {ofctrl.MeterExperimenter, &openflow15.MeterBandExperimenter{}}, + } + for _, tc := range testCases { + m.ResetMeterBands() + meter := m.MeterBand().MeterType(tc.meterType).Done() + msg, err := meter.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + meterMod := msg.GetMessage().(*openflow15.MeterMod) + assert.Equal(t, 1, len(meterMod.MeterBands)) + assert.IsType(t, tc.expectedBandType, meterMod.MeterBands[0]) + } + }) + t.Run("Rate", func(t *testing.T) { + testCases := []struct { + rate uint32 + }{ + {100}, + {200}, + } + for _, tc := range testCases { + m.ResetMeterBands() + meter := m.MeterBand().MeterType(ofctrl.MeterDSCPRemark).Rate(tc.rate).Done() + msg, err := meter.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + meterMod := msg.GetMessage().(*openflow15.MeterMod) + assert.Equal(t, 1, len(meterMod.MeterBands)) + assert.Equal(t, tc.rate, meterMod.MeterBands[0].(*openflow15.MeterBandDSCP).MeterBandHeader.Rate) + } + }) + t.Run("Burst", func(t *testing.T) { + testCases := []struct { + burst uint32 + }{ + {100}, + {200}, + } + for _, tc := range testCases { + m.ResetMeterBands() + meter := m.MeterBand().MeterType(ofctrl.MeterDSCPRemark).Burst(tc.burst).Done() + msg, err := meter.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + meterMod := msg.GetMessage().(*openflow15.MeterMod) + assert.Equal(t, 1, len(meterMod.MeterBands)) + assert.Equal(t, tc.burst, meterMod.MeterBands[0].(*openflow15.MeterBandDSCP).MeterBandHeader.BurstSize) + } + }) + t.Run("PrecLevel", func(t *testing.T) { + testCases := []struct { + precLevel uint8 + }{ + {100}, + {200}, + } + for _, tc := range testCases { + m.ResetMeterBands() + meter := m.MeterBand().MeterType(ofctrl.MeterDSCPRemark).PrecLevel(tc.precLevel).Done() + msg, err := meter.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + meterMod := msg.GetMessage().(*openflow15.MeterMod) + assert.Equal(t, 1, len(meterMod.MeterBands)) + assert.Equal(t, tc.precLevel, meterMod.MeterBands[0].(*openflow15.MeterBandDSCP).PrecLevel) + } + }) + t.Run("Experimenter", func(t *testing.T) { + testCases := []struct { + experimenter uint32 + }{ + {100}, + {200}, + } + for _, tc := range testCases { + m.ResetMeterBands() + meter := m.MeterBand().MeterType(ofctrl.MeterExperimenter).Experimenter(tc.experimenter).Done() + msg, err := meter.GetBundleMessage(AddMessage) + assert.NoError(t, err) + + meterMod := msg.GetMessage().(*openflow15.MeterMod) + assert.Equal(t, 1, len(meterMod.MeterBands)) + assert.Equal(t, tc.experimenter, meterMod.MeterBands[0].(*openflow15.MeterBandExperimenter).Experimenter) + } + }) +} diff --git a/pkg/ovs/ovsctl/interface.go b/pkg/ovs/ovsctl/interface.go index c925e7c5db4..980a0746339 100644 --- a/pkg/ovs/ovsctl/interface.go +++ b/pkg/ovs/ovsctl/interface.go @@ -83,7 +83,7 @@ func (e *ExecError) CommandExecuted() bool { return ok && exit.ExitCode() != exitCodeCommandNotFound } -// GetErrorOutput returns the command's output to stderr if it has ben executed +// GetErrorOutput returns the command's output to stderr if it has been executed // and exited with an error. func (e *ExecError) GetErrorOutput() string { if !e.CommandExecuted() {