Skip to content

Commit

Permalink
Reject the request to a Service without an Endpoint (#4656)
Browse files Browse the repository at this point in the history
When requesting a Service without an Endpoint, the connection should be rejected,
rather than timeout according to the expectation of Kubernetes sig-network tests.

This PR also relocates variable `NestedServiceRegMark` to a proper place
in pkg/agent/openflow/field.go.

Signed-off-by: Hongliang Liu <lhongliang@vmware.com>
  • Loading branch information
hongliangl committed Mar 22, 2023
1 parent a6f5f0a commit 15a8286
Show file tree
Hide file tree
Showing 19 changed files with 434 additions and 123 deletions.
2 changes: 1 addition & 1 deletion pkg/agent/controller/networkpolicy/audit_logging_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ func TestGetNetworkPolicyInfo(t *testing.T) {
testPriority, testRule := "61800", "test-rule"
allowDispositionData := []byte{0x11, 0x00, 0x00, 0x11}
dropDispositionData := []byte{0x11, 0x00, 0x08, 0x11}
redirectDispositionData := []byte{0x11, 0x08, 0x00, 0x11}
redirectDispositionData := []byte{0x11, 0x10, 0x00, 0x11}
ingressData := []byte{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}
tests := []struct {
name string
Expand Down
4 changes: 2 additions & 2 deletions pkg/agent/controller/networkpolicy/fqdn.go
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,7 @@ func (f *fqdnController) handlePacketIn(pktIn *ofctrl.PacketIn) error {
f.onDNSResponseMsg(&dnsMsg, time.Now(), waitCh)
}
go func() {
ethernetPkt, err := getEthernetPacket(pktIn)
ethernetPkt, err := openflow.GetEthernetPacket(pktIn)
if err != nil {
// Can't parse the packet. Forward it to the Pod.
waitCh <- nil
Expand Down Expand Up @@ -836,7 +836,7 @@ func (f *fqdnController) handlePacketIn(pktIn *ofctrl.PacketIn) error {

// sendDNSPacketout forwards the DNS response packet to the original requesting client.
func (f *fqdnController) sendDNSPacketout(pktIn *ofctrl.PacketIn) error {
ethernetPkt, err := getEthernetPacket(pktIn)
ethernetPkt, err := openflow.GetEthernetPacket(pktIn)
if err != nil {
return err
}
Expand Down
10 changes: 0 additions & 10 deletions pkg/agent/controller/networkpolicy/packetin.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import (
"time"

"antrea.io/libOpenflow/openflow15"
"antrea.io/libOpenflow/protocol"
"antrea.io/libOpenflow/util"
"antrea.io/ofnet/ofctrl"
"github.com/vmware/go-ipfix/pkg/registry"
"k8s.io/klog/v2"
Expand Down Expand Up @@ -210,11 +208,3 @@ func isAntreaPolicyEgressTable(tableID uint8) bool {
}
return false
}

func getEthernetPacket(pktIn *ofctrl.PacketIn) (*protocol.Ethernet, error) {
ethernetPkt := new(protocol.Ethernet)
if err := ethernetPkt.UnmarshalBinary(pktIn.Data.(*util.Buffer).Bytes()); err != nil {
return nil, fmt.Errorf("failed to parse ethernet packet from packet-in message: %v", err)
}
return ethernetPkt, nil
}
50 changes: 4 additions & 46 deletions pkg/agent/controller/networkpolicy/reject.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package networkpolicy

import (
"encoding/binary"
"fmt"
"net"

Expand Down Expand Up @@ -92,7 +91,7 @@ const (
func (c *Controller) rejectRequest(pktIn *ofctrl.PacketIn) error {
// All src/dst mean the source/destination of the reject packet, which are destination/source of the incoming packet.
// Get ethernet data.
ethernetPkt, err := getEthernetPacket(pktIn)
ethernetPkt, err := openflow.GetEthernetPacket(pktIn)
if err != nil {
return err
}
Expand Down Expand Up @@ -191,57 +190,16 @@ func (c *Controller) rejectRequest(pktIn *ofctrl.PacketIn) error {
inPort, outPort := getRejectOFPorts(packetOutType, sIface, dIface, c.gwPort, c.tunPort)
mutateFunc := getRejectPacketOutMutateFunc(packetOutType, c.nodeType, isFlexibleIPAMSrc, isFlexibleIPAMDst, ctZone)

if proto == protocol.Type_TCP {
// Get TCP data.
oriTCPSrcPort, oriTCPDstPort, oriTCPSeqNum, _, _, _, _, err := binding.GetTCPHeaderData(ethernetPkt.Data)
if err != nil {
return err
}
// While sending TCP reject packet-out, switch original src/dst port,
// set the ackNum as original seqNum+1 and set the flag as ack+rst.
return c.ofClient.SendTCPPacketOut(
srcMAC,
dstMAC,
srcIP,
dstIP,
inPort,
outPort,
isIPv6,
oriTCPDstPort,
oriTCPSrcPort,
0,
oriTCPSeqNum+1,
0,
TCPAck|TCPRst,
0,
nil,
mutateFunc)
}
// Use ICMP host administratively prohibited for ICMP, UDP, SCTP reject.
icmpType := ICMPDstUnreachableType
icmpCode := ICMPDstHostAdminProhibitedCode
ipHdrLen := IPv4HdrLen
if isIPv6 {
icmpType = ICMPv6DstUnreachableType
icmpCode = ICMPv6DstAdminProhibitedCode
ipHdrLen = IPv6HdrLen
}
ipHdr, _ := ethernetPkt.Data.MarshalBinary()
icmpData := make([]byte, int(ICMPUnusedHdrLen+ipHdrLen+8))
// Put ICMP unused header in Data prop and set it to zero.
binary.BigEndian.PutUint32(icmpData[:ICMPUnusedHdrLen], 0)
copy(icmpData[ICMPUnusedHdrLen:], ipHdr[:ipHdrLen+8])
return c.ofClient.SendICMPPacketOut(
return openflow.SendRejectPacketOut(c.ofClient,
srcMAC,
dstMAC,
srcIP,
dstIP,
inPort,
outPort,
isIPv6,
icmpType,
icmpCode,
icmpData,
ethernetPkt,
proto,
mutateFunc)
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/agent/controller/traceflow/packetin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ func getTestPacketBytes() []byte {

func TestParsePacketIn(t *testing.T) {
xreg0 := make([]byte, 8)
binary.BigEndian.PutUint32(xreg0[0:4], 262144) // RemoteSNATRegMark in 32bit reg0
binary.BigEndian.PutUint32(xreg0[4:8], 2) // outputPort in 32bit reg1
binary.BigEndian.PutUint32(xreg0[0:4], openflow.RemoteSNATRegMark.GetValue()<<openflow.RemoteSNATRegMark.GetField().GetRange().Offset()) // RemoteSNATRegMark in 32bit reg0
binary.BigEndian.PutUint32(xreg0[4:8], 2) // outputPort in 32bit reg1
matchOutPort := &openflow15.MatchField{
Class: openflow15.OXM_CLASS_PACKET_REGS,
Field: openflow15.NXM_NX_REG0,
Expand Down
24 changes: 4 additions & 20 deletions pkg/agent/multicast/mcast_discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"sync"
"time"

"antrea.io/libOpenflow/openflow15"
"antrea.io/libOpenflow/protocol"
"antrea.io/libOpenflow/util"
"antrea.io/ofnet/ofctrl"
Expand All @@ -33,17 +32,13 @@ import (
"antrea.io/antrea/pkg/agent/types"
"antrea.io/antrea/pkg/apis/controlplane/v1beta2"
"antrea.io/antrea/pkg/apis/crd/v1alpha1"
binding "antrea.io/antrea/pkg/ovs/openflow"
)

const (
IGMPProtocolNumber = 2
)

const (
openflowKeyTunnelSrc = "NXM_NX_TUN_IPV4_SRC"
openflowKeyInPort = "OXM_OF_IN_PORT"
)

var (
// igmpMaxResponseTime is the maximum time allowed before sending a responding report which is used for the
// "Max Resp Code" field in the IGMP query message. It is also the maximum time to wait for the IGMP report message
Expand Down Expand Up @@ -79,7 +74,7 @@ func (s *IGMPSnooper) HandlePacketIn(pktIn *ofctrl.PacketIn) error {
if match == nil {
return fmt.Errorf("error getting match from IGMP marks in CustomField")
}
customReasons, err := getInfoInReg(match, openflow.CustomReasonField.GetRange().ToNXRange())
customReasons, err := openflow.GetInfoInReg(match, openflow.CustomReasonField.GetRange().ToNXRange())
if err != nil {
klog.ErrorS(err, "Received error while unloading customReason from OVS reg")
return err
Expand All @@ -90,20 +85,9 @@ func (s *IGMPSnooper) HandlePacketIn(pktIn *ofctrl.PacketIn) error {
return nil
}

func getInfoInReg(regMatch *ofctrl.MatchField, rng *openflow15.NXRange) (uint32, error) {
regValue, ok := regMatch.GetValue().(*ofctrl.NXRegister)
if !ok {
return 0, errors.New("register value cannot be retrieved")
}
if rng != nil {
return ofctrl.GetUint32ValueWithRange(regValue.Data, rng), nil
}
return regValue.Data, nil
}

func (s *IGMPSnooper) parseSrcInterface(pktIn *ofctrl.PacketIn) (*interfacestore.InterfaceConfig, error) {
matches := pktIn.GetMatches()
ofPortField := matches.GetMatchByName(openflowKeyInPort)
ofPortField := matches.GetMatchByName(binding.OxmFieldInPort)
if ofPortField == nil {
return nil, errors.New("in_port field not found")
}
Expand Down Expand Up @@ -327,7 +311,7 @@ func (s *IGMPSnooper) processPacketIn(pktIn *ofctrl.PacketIn) error {

func (s *IGMPSnooper) parseSrcNode(pktIn *ofctrl.PacketIn) (net.IP, error) {
matches := pktIn.GetMatches()
tunSrcField := matches.GetMatchByName(openflowKeyTunnelSrc)
tunSrcField := matches.GetMatchByName(binding.NxmFieldTunIPv4Src)
if tunSrcField == nil {
return nil, errors.New("in_port field not found")
}
Expand Down
14 changes: 10 additions & 4 deletions pkg/agent/openflow/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,12 @@ func Test_client_InstallServiceGroup(t *testing.T) {
"bucket=bucket_id:1,weight:100,actions=set_field:0xa0a0065->reg3,set_field:0x50/0xffff->reg4,resubmit:EndpointDNAT",
deleteOFEntriesError: fmt.Errorf("error when deleting Openflow entries for Service Endpoints Group 100"),
},
{
name: "No Endpoints",
endpoints: []proxy.Endpoint{},
expectedGroup: "group_id=100,type=select," +
"bucket=bucket_id:0,weight:100,actions=set_field:0x40000/0x7e000->reg0,resubmit:EndpointDNAT",
},
}

for _, tc := range testCases {
Expand Down Expand Up @@ -1290,7 +1296,7 @@ func Test_client_InstallPodSNATFlows(t *testing.T) {
{
name: "SNAT on Remote",
expectedFlows: []string{
"cookie=0x1040000000000, table=EgressMark, priority=200,ip,in_port=100 actions=set_field:0a:00:00:00:00:01->eth_src,set_field:aa:bb:cc:dd:ee:ff->eth_dst,set_field:192.168.77.101->tun_dst,set_field:0x10/0xf0->reg0,set_field:0x40000/0x40000->reg0,goto_table:L2ForwardingCalc",
"cookie=0x1040000000000, table=EgressMark, priority=200,ip,in_port=100 actions=set_field:0a:00:00:00:00:01->eth_src,set_field:aa:bb:cc:dd:ee:ff->eth_dst,set_field:192.168.77.101->tun_dst,set_field:0x10/0xf0->reg0,set_field:0x80000/0x80000->reg0,goto_table:L2ForwardingCalc",
},
},
}
Expand Down Expand Up @@ -1842,7 +1848,7 @@ func Test_client_InstallMulticastRemoteReportFlows(t *testing.T) {
groupID := binding.GroupIDType(102)
expectedFlows := []string{
"cookie=0x1050000000000, table=Classifier, priority=210,ip,in_port=1,nw_dst=224.0.0.0/4 actions=set_field:0x1/0xf->reg0,goto_table:MulticastEgressRule",
"cookie=0x1050000000000, table=MulticastRouting, priority=210,igmp,in_port=4294967293 actions=set_field:0x20000/0x3e000->reg0,group:102",
"cookie=0x1050000000000, table=MulticastRouting, priority=210,igmp,in_port=4294967293 actions=set_field:0x20000/0x7e000->reg0,group:102",
"cookie=0x1050000000000, table=Classifier, priority=200,in_port=4294967293 actions=goto_table:PipelineIPClassifier",
}

Expand Down Expand Up @@ -2116,7 +2122,7 @@ func Test_client_InstallMulticlusterNodeFlows(t *testing.T) {
expectedFlows: []string{
"cookie=0x1060000000000, table=L3Forwarding, priority=200,ip,nw_dst=10.97.0.0/16 actions=set_field:0a:00:00:00:00:01->eth_src,set_field:aa:bb:cc:dd:ee:f0->eth_dst,set_field:192.168.78.101->tun_dst,set_field:0x10/0xf0->reg0,goto_table:L3DecTTL",
"cookie=0x1060000000000, table=L3Forwarding, priority=200,ct_state=+rpl+trk,ip,nw_dst=192.168.78.101 actions=set_field:0a:00:00:00:00:01->eth_src,set_field:aa:bb:cc:dd:ee:f0->eth_dst,set_field:192.168.78.101->tun_dst,set_field:0x10/0xf0->reg0,goto_table:L3DecTTL",
"cookie=0x1060000000000, table=L3Forwarding, priority=199,ip,reg0=0x4000/0x3e000,nw_dst=192.168.78.101 actions=set_field:0a:00:00:00:00:01->eth_src,set_field:aa:bb:cc:dd:ee:f0->eth_dst,set_field:192.168.78.101->tun_dst,set_field:0x10/0xf0->reg0,goto_table:L3DecTTL",
"cookie=0x1060000000000, table=L3Forwarding, priority=199,ip,reg0=0x4000/0x7e000,nw_dst=192.168.78.101 actions=set_field:0a:00:00:00:00:01->eth_src,set_field:aa:bb:cc:dd:ee:f0->eth_dst,set_field:192.168.78.101->tun_dst,set_field:0x10/0xf0->reg0,goto_table:L3DecTTL",
},
},
//TODO: IPv6
Expand Down Expand Up @@ -2168,7 +2174,7 @@ func Test_client_InstallMulticlusterGatewayFlows(t *testing.T) {
"cookie=0x1060000000000, table=UnSNAT, priority=200,ip,nw_dst=192.168.77.100 actions=ct(table=ConntrackZone,zone=65521,nat)",
"cookie=0x1060000000000, table=L3Forwarding, priority=200,ip,nw_dst=10.97.0.0/16 actions=set_field:0a:00:00:00:00:01->eth_src,set_field:aa:bb:cc:dd:ee:f0->eth_dst,set_field:192.168.78.101->tun_dst,set_field:0x10/0xf0->reg0,goto_table:L3DecTTL",
"cookie=0x1060000000000, table=L3Forwarding, priority=200,ct_state=+rpl+trk,ip,nw_dst=192.168.78.101 actions=set_field:0a:00:00:00:00:01->eth_src,set_field:aa:bb:cc:dd:ee:f0->eth_dst,set_field:192.168.78.101->tun_dst,set_field:0x10/0xf0->reg0,goto_table:L3DecTTL",
"cookie=0x1060000000000, table=L3Forwarding, priority=199,ip,reg0=0x4000/0x3e000,nw_dst=192.168.78.101 actions=set_field:0a:00:00:00:00:01->eth_src,set_field:aa:bb:cc:dd:ee:f0->eth_dst,set_field:192.168.78.101->tun_dst,set_field:0x10/0xf0->reg0,goto_table:L3DecTTL",
"cookie=0x1060000000000, table=L3Forwarding, priority=199,ip,reg0=0x4000/0x7e000,nw_dst=192.168.78.101 actions=set_field:0a:00:00:00:00:01->eth_src,set_field:aa:bb:cc:dd:ee:f0->eth_dst,set_field:192.168.78.101->tun_dst,set_field:0x10/0xf0->reg0,goto_table:L3DecTTL",
"cookie=0x1060000000000, table=SNATMark, priority=210,ct_state=+new+trk,ip,nw_dst=10.97.0.0/16 actions=ct(commit,table=SNAT,zone=65520,exec(set_field:0x20/0x20->ct_mark))",
"cookie=0x1060000000000, table=SNAT, priority=200,ct_state=+new+trk,ip,nw_dst=10.97.0.0/16 actions=ct(commit,table=L2ForwardingCalc,zone=65521,nat(src=192.168.77.100))",
},
Expand Down
40 changes: 21 additions & 19 deletions pkg/agent/openflow/fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,24 @@ var (
DispositionAllowRegMark = binding.NewRegMark(APDispositionField, DispositionAllow)
DispositionDropRegMark = binding.NewRegMark(APDispositionField, DispositionDrop)
DispositionPassRegMark = binding.NewRegMark(APDispositionField, DispositionPass)
// reg0[13..17]: Field to indicate the reasons of sending packet to the controller. Marks in this field include:
// - 0b00001: logging
// - 0b00010: reject
// - 0b00100: deny (used by Flow Exporter)
// - 0b01000: DNS packet (used by FQDN)
// - 0b10000: IGMP packet (used by Multicast)
CustomReasonField = binding.NewRegField(0, 13, 17)
CustomReasonLoggingRegMark = binding.NewRegMark(CustomReasonField, CustomReasonLogging)
CustomReasonRejectRegMark = binding.NewRegMark(CustomReasonField, CustomReasonReject)
CustomReasonDenyRegMark = binding.NewRegMark(CustomReasonField, CustomReasonDeny)
CustomReasonDNSRegMark = binding.NewRegMark(CustomReasonField, CustomReasonDNS)
CustomReasonIGMPRegMark = binding.NewRegMark(CustomReasonField, CustomReasonIGMP)
// reg0[18]: Mark to indicate remote SNAT for Egress.
RemoteSNATRegMark = binding.NewOneBitRegMark(0, 18)
// reg0[19]: Field to indicate redirect action of layer 7 NetworkPolicy.
L7NPRegField = binding.NewRegField(0, 19, 19)
// reg0[13..18]: Field to indicate the reasons of sending packet to the controller. Marks in this field include:
// - 0b000001: logging
// - 0b000010: reject
// - 0b000100: deny (used by Flow Exporter)
// - 0b001000: DNS packet (used by FQDN)
// - 0b010000: IGMP packet (used by Multicast)
// - 0b100000: reject packet to a Service without any Endpoints (used by Proxy)
CustomReasonField = binding.NewRegField(0, 13, 18)
CustomReasonLoggingRegMark = binding.NewRegMark(CustomReasonField, CustomReasonLogging)
CustomReasonRejectRegMark = binding.NewRegMark(CustomReasonField, CustomReasonReject)
CustomReasonDenyRegMark = binding.NewRegMark(CustomReasonField, CustomReasonDeny)
CustomReasonDNSRegMark = binding.NewRegMark(CustomReasonField, CustomReasonDNS)
CustomReasonIGMPRegMark = binding.NewRegMark(CustomReasonField, CustomReasonIGMP)
CustomReasonRejectSvcNoEpMark = binding.NewRegMark(CustomReasonField, CustomReasonRejectSvcNoEp)
// reg0[19]: Mark to indicate remote SNAT for Egress.
RemoteSNATRegMark = binding.NewOneBitRegMark(0, 19)
// reg0[20]: Field to indicate redirect action of layer 7 NetworkPolicy.
L7NPRegField = binding.NewRegField(0, 20, 20)
L7NPRedirectRegMark = binding.NewRegMark(L7NPRegField, DispositionL7NPRedirect)

// reg1(NXM_NX_REG1)
Expand Down Expand Up @@ -128,11 +130,11 @@ var (
// externalTrafficPolicy is Cluster.
ToClusterServiceRegMark = binding.NewOneBitRegMark(4, 21)
// reg4[22..23]: Field to store the action of a traffic control rule. Marks in this field include:
TrafficControlActionField = binding.NewRegField(4, 22, 23)
// reg4[24]: Mark to indicate whether the Endpoints of a Service includes another Service's ClusterIP.
NestedServiceRegMark = binding.NewOneBitRegMark(4, 24)
TrafficControlActionField = binding.NewRegField(4, 22, 23)
TrafficControlMirrorRegMark = binding.NewRegMark(TrafficControlActionField, 0b01)
TrafficControlRedirectRegMark = binding.NewRegMark(TrafficControlActionField, 0b10)
// reg4[24]: Mark to indicate whether the Endpoints of a Service includes another Service's ClusterIP.
NestedServiceRegMark = binding.NewOneBitRegMark(4, 24)

// reg5(NXM_NX_REG5)
// Field to cache the Egress conjunction ID hit by TraceFlow packet.
Expand Down
6 changes: 3 additions & 3 deletions pkg/agent/openflow/multicast_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ func Test_featureMulticast_initFlows(t *testing.T) {
expectedFlows: []string{
"cookie=0x1050000000000, table=MulticastEgressRule, priority=64990,igmp,reg0=0x3/0xf actions=goto_table:MulticastRouting",
"cookie=0x1050000000000, table=MulticastEgressPodMetric, priority=210,igmp actions=goto_table:MulticastRouting",
"cookie=0x1050000000000, table=MulticastRouting, priority=210,igmp,reg0=0x3/0xf actions=set_field:0x20000/0x3e000->reg0,controller:(reason=no_match,max_len=128,id=32776)",
"cookie=0x1050000000000, table=MulticastRouting, priority=210,igmp,reg0=0x1/0xf actions=set_field:0x20000/0x3e000->reg0,controller:(reason=no_match,max_len=128,id=32776)",
"cookie=0x1050000000000, table=MulticastRouting, priority=210,igmp,reg0=0x3/0xf actions=set_field:0x20000/0x7e000->reg0,controller:(reason=no_match,max_len=128,id=32776)",
"cookie=0x1050000000000, table=MulticastRouting, priority=210,igmp,reg0=0x1/0xf actions=set_field:0x20000/0x7e000->reg0,controller:(reason=no_match,max_len=128,id=32776)",
"cookie=0x1050000000000, table=MulticastRouting, priority=190,ip actions=set_field:0x100/0x100->reg0,set_field:0x2->reg1,goto_table:MulticastOutput",
"cookie=0x1050000000000, table=MulticastIngressPodMetric, priority=210,igmp actions=goto_table:MulticastOutput",
"cookie=0x1050000000000, table=MulticastOutput, priority=210,reg0=0x101/0x10f,reg1=0x2 actions=drop",
Expand All @@ -55,7 +55,7 @@ func Test_featureMulticast_initFlows(t *testing.T) {
clientOptions: []clientOptionsFn{enableMulticast},
expectedFlows: []string{
"cookie=0x1050000000000, table=MulticastIngressPodMetric, priority=210,igmp actions=goto_table:MulticastOutput",
"cookie=0x1050000000000, table=MulticastRouting, priority=210,igmp,reg0=0x3/0xf actions=set_field:0x20000/0x3e000->reg0,controller:(reason=no_match,max_len=128,id=32776)",
"cookie=0x1050000000000, table=MulticastRouting, priority=210,igmp,reg0=0x3/0xf actions=set_field:0x20000/0x7e000->reg0,controller:(reason=no_match,max_len=128,id=32776)",
"cookie=0x1050000000000, table=MulticastRouting, priority=190,ip actions=set_field:0x100/0x100->reg0,set_field:0x2->reg1,goto_table:MulticastOutput",
"cookie=0x1050000000000, table=MulticastEgressPodMetric, priority=210,igmp actions=goto_table:MulticastRouting",
"cookie=0x1050000000000, table=MulticastEgressRule, priority=64990,igmp,reg0=0x3/0xf actions=goto_table:MulticastRouting",
Expand Down
Loading

0 comments on commit 15a8286

Please sign in to comment.