diff --git a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/google_discovery_protocol_packetin_test.go b/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/google_discovery_protocol_packetin_test.go index 587c8f4e892..f793f73fcf1 100644 --- a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/google_discovery_protocol_packetin_test.go +++ b/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/google_discovery_protocol_packetin_test.go @@ -17,13 +17,12 @@ package google_discovery_protocol_packetin_test import ( "context" "errors" + "flag" "fmt" "strings" "testing" "time" - "flag" - "github.com/cisco-open/go-p4/p4rt_client" "github.com/cisco-open/go-p4/utils" "github.com/google/gopacket" @@ -37,7 +36,8 @@ import ( "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" "github.com/openconfig/ygot/ygot" - p4_v1 "github.com/p4lang/p4runtime/go/p4/v1" + + p4v1pb "github.com/p4lang/p4runtime/go/p4/v1" ) const ( @@ -55,6 +55,7 @@ var ( electionID = uint64(100) metadataIngressPort = uint32(1) metadataEgressPort = uint32(2) + vlanID = uint16(4000) ) var ( @@ -88,7 +89,7 @@ var ( type PacketIO interface { GetTableEntry(delete bool) []*p4rtutils.ACLWbbIngressTableEntryInfo GetPacketTemplate() *PacketIOPacket - GetTrafficFlow(ate *ondatra.ATEDevice, frameSize uint32, frameRate uint64) []gosnappi.Flow + GetTrafficFlows(ate *ondatra.ATEDevice, frameSize uint32, frameRate uint64) []gosnappi.Flow GetEgressPort() []string GetIngressPort() string } @@ -111,13 +112,13 @@ type testArgs struct { // programmTableEntry programs or deletes p4rt table entry based on delete flag. func programmTableEntry(ctx context.Context, t *testing.T, client *p4rt_client.P4RTClient, packetIO PacketIO, delete bool) error { t.Helper() - err := client.Write(&p4_v1.WriteRequest{ + err := client.Write(&p4v1pb.WriteRequest{ DeviceId: deviceID, - ElectionId: &p4_v1.Uint128{High: uint64(0), Low: electionID}, + ElectionId: &p4v1pb.Uint128{High: uint64(0), Low: electionID}, Updates: p4rtutils.ACLWbbIngressTableEntryGet( packetIO.GetTableEntry(delete), ), - Atomicity: p4_v1.WriteRequest_CONTINUE_ON_ERROR, + Atomicity: p4v1pb.WriteRequest_CONTINUE_ON_ERROR, }) if err != nil { return err @@ -125,23 +126,39 @@ func programmTableEntry(ctx context.Context, t *testing.T, client *p4rt_client.P return nil } -// decodePacket decodes L2 header in the packet and returns source and destination MAC and ethernet type. -func decodePacket(t *testing.T, packetData []byte) (string, string, layers.EthernetType) { +// decodePacket decodes L2 header in the packet and returns source and destination MAC +// vlanID, and ethernet type. +func decodePacket(t *testing.T, packetData []byte) (string, string, uint16, layers.EthernetType) { t.Helper() packet := gopacket.NewPacket(packetData, layers.LayerTypeEthernet, gopacket.Default) etherHeader := packet.Layer(layers.LayerTypeEthernet) + d1qHeader := packet.Layer(layers.LayerTypeDot1Q) + + srcMAC, dstMAC := "", "" + vlanID := uint16(0) + etherType := layers.EthernetType(0) if etherHeader != nil { header, decoded := etherHeader.(*layers.Ethernet) if decoded { - return header.SrcMAC.String(), header.DstMAC.String(), header.EthernetType + srcMAC, dstMAC = header.SrcMAC.String(), header.DstMAC.String() + if header.EthernetType != layers.EthernetTypeDot1Q { + return srcMAC, dstMAC, vlanID, header.EthernetType + } } } - return "", "", layers.EthernetType(0) + if d1qHeader != nil { + header, decoded := d1qHeader.(*layers.Dot1Q) + if decoded { + vlanID = header.VLANIdentifier + etherType = header.Type + } + } + return srcMAC, dstMAC, vlanID, etherType } // testTraffic sends traffic flow for duration seconds and returns the // number of packets sent out. -func testTraffic(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, flows []gosnappi.Flow, srcEndPoint gosnappi.Port, duration int) int { +func testTraffic(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, flows []gosnappi.Flow, srcEndPoint gosnappi.Port, duration int) map[string]uint64 { t.Helper() for _, flow := range flows { flow.TxRx().Port().SetTxName(srcEndPoint.Name()).SetRxName(srcEndPoint.Name()) @@ -155,15 +172,25 @@ func testTraffic(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, flow time.Sleep(time.Duration(duration) * time.Second) ate.OTG().StopTraffic(t) - - outPkts := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().FlowAny().Counters().OutPkts().State()) - total := 0 - for _, count := range outPkts { - total += int(count) + fNames := []string{"GDPWithVlan", "GDPWithoutVlan", "NonGDP"} + total := map[string]uint64{} + for _, fName := range fNames { + total[fName] = gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(fName).Counters().OutPkts().State()) } + return total } +func validateTrafficAtATE(t *testing.T, ate *ondatra.ATEDevice, pktOut map[string]uint64) { + wantPkts := map[string]uint64{"GDPWithVlan": 0, "GDPWithoutVlan": 0, "NonGDP": pktOut["NonGDP"]} + for fName, want := range wantPkts { + got := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(fName).Counters().InPkts().State()) + if got != want { + t.Errorf("Number of PacketIN at ATE-Port2 for flow: %s, got: %d, want: %d", fName, got, want) + } + } +} + // testPacketIn programs p4rt table entry and sends traffic related to GDP, // then validates packetin message metadata and payload. func testPacketIn(ctx context.Context, t *testing.T, args *testArgs) { @@ -179,47 +206,56 @@ func testPacketIn(ctx context.Context, t *testing.T, args *testArgs) { // Send GDP traffic from ATE srcEndPoint := ateInterface(t, args.top, "port1") - pktOut := testTraffic(t, args.top, args.ate, args.packetIO.GetTrafficFlow(args.ate, 300, 2), srcEndPoint, 20) + pktOut := testTraffic(t, args.top, args.ate, args.packetIO.GetTrafficFlows(args.ate, 300, 2), srcEndPoint, 20) + validateTrafficAtATE(t, args.ate, pktOut) packetInTests := []struct { desc string client *p4rt_client.P4RTClient - wantPkts int + wantPkts bool }{{ desc: "PacketIn to Primary Controller", client: leader, - wantPkts: pktOut, + wantPkts: true, }, { desc: "PacketIn to Secondary Controller", client: follower, - wantPkts: 0, + wantPkts: false, }} for _, test := range packetInTests { t.Run(test.desc, func(t *testing.T) { + pktCount := pktOut["GDPWithVlan"] + pktOut["GDPWithoutVlan"] // Extract packets from PacketIn message sent to p4rt client - _, packets, err := test.client.StreamChannelGetPackets(&streamName, uint64(test.wantPkts), 30*time.Second) + _, packets, err := test.client.StreamChannelGetPackets(&streamName, pktCount, 30*time.Second) if err != nil { t.Errorf("Unexpected error on StreamChannelGetPackets: %v", err) } - if test.wantPkts == 0 { - return - } - - gotPkts := 0 + gdpWithoutVlanPkts := uint64(0) + gdpWithVlanPkts := uint64(0) + gotVlanID := uint16(0) t.Logf("Start to decode packet and compare with expected packets.") wantPacket := args.packetIO.GetPacketTemplate() for _, packet := range packets { if packet != nil { if wantPacket.DstMAC != nil && wantPacket.EthernetType != nil { - srcMAC, dstMac, etherType := decodePacket(t, packet.Pkt.GetPayload()) + srcMAC, dstMac, vID, etherType := decodePacket(t, packet.Pkt.GetPayload()) if dstMac != *wantPacket.DstMAC || etherType != layers.EthernetType(*wantPacket.EthernetType) { continue } if !strings.EqualFold(srcMAC, *gdpSrcMAC) { continue } + gotVlanID = vID + } + if gotVlanID == 0 { + gdpWithoutVlanPkts++ + } else { + if got, want := gotVlanID, vlanID; got != want { + t.Errorf("VLAN ID mismatch, got: %d, want: %d", got, want) + } + gdpWithVlanPkts++ } metaData := packet.Pkt.GetMetadata() @@ -241,12 +277,14 @@ func testPacketIn(ctx context.Context, t *testing.T, args *testArgs) { } } } - gotPkts++ } } - if got, want := gotPkts, test.wantPkts; got != want { - t.Errorf("Number of PacketIn, got: %d, want: %d", got, want) + if got, want := gdpWithoutVlanPkts, pktOut["GDPWithoutVlan"]; got != want { + t.Errorf("Number of GDP without Vlan PacketIn, got: %d, want: %d", got, want) + } + if got, want := gdpWithVlanPkts, pktOut["GDPWithVlan"]; got != want { + t.Errorf("Number of GDP with Vlan PacketIn, got: %d, want: %d", got, want) } }) } @@ -257,13 +295,12 @@ func TestMain(m *testing.M) { } // configInterfaceDUT configures the interface with the Addrs. -func configInterfaceDUT(i *oc.Interface, a *attrs.Attributes, dut *ondatra.DUTDevice) *oc.Interface { +func configInterfaceDUT(i *oc.Interface, a *attrs.Attributes, dut *ondatra.DUTDevice, hasVlan bool) *oc.Interface { i.Description = ygot.String(a.Desc) i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd if deviations.InterfaceEnabled(dut) { i.Enabled = ygot.Bool(true) } - s := i.GetOrCreateSubinterface(0) s4 := s.GetOrCreateIpv4() if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { @@ -272,6 +309,14 @@ func configInterfaceDUT(i *oc.Interface, a *attrs.Attributes, dut *ondatra.DUTDe s4a := s4.GetOrCreateAddress(a.IPv4) s4a.PrefixLength = ygot.Uint8(ipv4PrefixLen) + if hasVlan && deviations.P4RTGdpRequiresDot1QSubinterface(dut) { + s1 := i.GetOrCreateSubinterface(1) + s1.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().SetVlanId(vlanID) + if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().SetVlanId(10) + i.GetOrCreateAggregation().GetOrCreateSwitchedVlan().SetNativeVlan(10) + } + } return i } @@ -281,14 +326,14 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { p1 := dut.Port(t, "port1") i1 := &oc.Interface{Name: ygot.String(p1.Name()), Id: ygot.Uint32(portID)} - gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(i1, &dutPort1, dut)) + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(i1, &dutPort1, dut, true /*hasVlan*/)) if deviations.ExplicitInterfaceInDefaultVRF(dut) { fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) } p2 := dut.Port(t, "port2") i2 := &oc.Interface{Name: ygot.String(p2.Name()), Id: ygot.Uint32(portID + 1)} - gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(i2, &dutPort2, dut)) + gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(i2, &dutPort2, dut, false)) if deviations.ExplicitInterfaceInDefaultVRF(dut) { fptest.AssignToNetworkInstance(t, dut, p2.Name(), deviations.DefaultNetworkInstance(dut), 0) } @@ -344,11 +389,11 @@ func setupP4RTClient(ctx context.Context, args *testArgs) error { for index, client := range clients { if client != nil { client.StreamChannelCreate(&streamParameter) - if err := client.StreamChannelSendMsg(&streamName, &p4_v1.StreamMessageRequest{ - Update: &p4_v1.StreamMessageRequest_Arbitration{ - Arbitration: &p4_v1.MasterArbitrationUpdate{ + if err := client.StreamChannelSendMsg(&streamName, &p4v1pb.StreamMessageRequest{ + Update: &p4v1pb.StreamMessageRequest_Arbitration{ + Arbitration: &p4v1pb.MasterArbitrationUpdate{ DeviceId: streamParameter.DeviceId, - ElectionId: &p4_v1.Uint128{ + ElectionId: &p4v1pb.Uint128{ High: streamParameter.ElectionIdH, Low: streamParameter.ElectionIdL - uint64(index), }, @@ -363,28 +408,30 @@ func setupP4RTClient(ctx context.Context, args *testArgs) error { } return fmt.Errorf("errors seen in ClientArbitration response: %v", arbErr) } + + client.StreamChannelGet(&streamName).SetPacketQSize(10000) } } // Load p4info file. p4Info, err := utils.P4InfoLoad(p4InfoFile) if err != nil { - return errors.New("Errors seen when loading p4info file.") + return errors.New("errors seen when loading p4info file") } // Send SetForwardingPipelineConfig for p4rt leader client. - if err := args.leader.SetForwardingPipelineConfig(&p4_v1.SetForwardingPipelineConfigRequest{ + if err := args.leader.SetForwardingPipelineConfig(&p4v1pb.SetForwardingPipelineConfigRequest{ DeviceId: deviceID, - ElectionId: &p4_v1.Uint128{High: uint64(0), Low: electionID}, - Action: p4_v1.SetForwardingPipelineConfigRequest_VERIFY_AND_COMMIT, - Config: &p4_v1.ForwardingPipelineConfig{ + ElectionId: &p4v1pb.Uint128{High: uint64(0), Low: electionID}, + Action: p4v1pb.SetForwardingPipelineConfigRequest_VERIFY_AND_COMMIT, + Config: &p4v1pb.ForwardingPipelineConfig{ P4Info: p4Info, - Cookie: &p4_v1.ForwardingPipelineConfig_Cookie{ + Cookie: &p4v1pb.ForwardingPipelineConfig_Cookie{ Cookie: 159, }, }, }); err != nil { - return errors.New("Errors seen when sending SetForwardingPipelineConfig.") + return errors.New("errors seen when sending SetForwardingPipelineConfig") } return nil } @@ -450,9 +497,9 @@ type GDPPacketIO struct { // GetTableEntry creates wbb acl entry related to GDP. func (gdp *GDPPacketIO) GetTableEntry(delete bool) []*p4rtutils.ACLWbbIngressTableEntryInfo { - actionType := p4_v1.Update_INSERT + actionType := p4v1pb.Update_INSERT if delete { - actionType = p4_v1.Update_DELETE + actionType = p4v1pb.Update_DELETE } return []*p4rtutils.ACLWbbIngressTableEntryInfo{{ Type: actionType, @@ -467,19 +514,47 @@ func (gdp *GDPPacketIO) GetPacketTemplate() *PacketIOPacket { return &gdp.PacketIOPacket } -// GetTrafficFlow generates ATE traffic flows for GDP. -func (gdp *GDPPacketIO) GetTrafficFlow(ate *ondatra.ATEDevice, frameSize uint32, frameRate uint64) []gosnappi.Flow { - - flow := gosnappi.NewFlow() - flow.SetName("GDP") - ethHeader := flow.Packet().Add().Ethernet() - ethHeader.Src().SetValue(*gdp.SrcMAC) - ethHeader.Dst().SetValue(*gdp.DstMAC) - ethHeader.EtherType().SetValue(int32(*gdp.EthernetType)) - flow.Size().SetFixed(int32(frameSize)) - flow.Rate().SetPps(int64(frameRate)) - return []gosnappi.Flow{flow} - +// GetTrafficFlows generates OTG traffic flows for GDP. +func (gdp *GDPPacketIO) GetTrafficFlows(ate *ondatra.ATEDevice, frameSize uint32, frameRate uint64) []gosnappi.Flow { + + f1 := gosnappi.NewFlow() + f1.SetName("GDPWithVlan") + eth1 := f1.Packet().Add().Ethernet() + eth1.Src().SetValue(*gdp.SrcMAC) + eth1.Dst().SetValue(*gdp.DstMAC) + eth1.EtherType().SetValue(int32(0x8100)) + + vlan := f1.Packet().Add().Vlan() + vlan.Id().SetValue(int32(vlanID)) + vlan.Tpid().SetValue(int32(*gdp.EthernetType)) + + f1.Size().SetFixed(int32(frameSize)) + f1.Rate().SetPps(int64(frameRate)) + + f2 := gosnappi.NewFlow() + f2.SetName("GDPWithoutVlan") + eth2 := f2.Packet().Add().Ethernet() + eth2.Src().SetValue(*gdp.SrcMAC) + eth2.Dst().SetValue(*gdp.DstMAC) + eth2.EtherType().SetValue(int32(*gdp.EthernetType)) + + f2.Size().SetFixed(int32(frameSize)) + f2.Rate().SetPps(int64(frameRate)) + + f3 := gosnappi.NewFlow() + f3.SetName("NonGDP") + eth3 := f1.Packet().Add().Ethernet() + eth3.Src().SetValue(*gdp.SrcMAC) + eth3.Dst().SetValue(*gdp.DstMAC) + eth3.EtherType().SetValue(int32(0x0800)) + ip3 := f3.Packet().Add().Ipv4() + ip3.Src().SetValue(atePort1.IPv4) + ip3.Dst().SetValue(atePort2.IPv4) + ip3.TimeToLive().SetValue(3) + + f3.Size().SetFixed(int32(frameSize)) + f3.Rate().SetPps(int64(frameRate)) + return []gosnappi.Flow{f1, f2, f3} } // GetEgressPort returns expected egress port info in PacketIn. diff --git a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/metadata.textproto b/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/metadata.textproto index 14e92e70411..f545591d9cf 100644 --- a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/metadata.textproto +++ b/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetin_test/metadata.textproto @@ -11,6 +11,16 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true + p4rt_gdp_requires_dot1q_subinterface: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + p4rt_gdp_requires_dot1q_subinterface: true + no_mix_of_tagged_and_untagged_subinterfaces: true } } platform_exceptions: { diff --git a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/google_discovery_protocol_packetout_test.go b/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/google_discovery_protocol_packetout_test.go index ddfcb2f149f..2bf70ba3eea 100644 --- a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/google_discovery_protocol_packetout_test.go +++ b/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/google_discovery_protocol_packetout_test.go @@ -17,14 +17,13 @@ package google_discovery_protocol_packetout_test import ( "context" "errors" + "flag" "fmt" "net" "sort" "testing" "time" - "flag" - "github.com/cisco-open/go-p4/p4rt_client" "github.com/cisco-open/go-p4/utils" "github.com/google/gopacket" @@ -38,25 +37,23 @@ import ( "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" "github.com/openconfig/ygot/ygot" - p4_v1 "github.com/p4lang/p4runtime/go/p4/v1" + p4v1pb "github.com/p4lang/p4runtime/go/p4/v1" ) const ( ipv4PrefixLen = 30 - packetCount = 100 + packetCount = 300 ) var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") - streamName = "p4rt" - gdpInLayers layers.EthernetType = 0x6007 - deviceID = *ygot.Uint64(1) - portID = *ygot.Uint32(10) - electionID = *ygot.Uint64(100) - METADATA_INGRESS_PORT = *ygot.Uint32(1) - METADATA_EGRESS_PORT = *ygot.Uint32(2) - SUBMIT_TO_INGRESS = *ygot.Uint32(1) - SUBMIT_TO_EGRESS = *ygot.Uint32(0) + p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + streamName = "p4rt" + gdpInLayers layers.EthernetType = 0x6007 + deviceID = uint64(1) + portID = uint32(10) + electionID = uint64(100) + vlanID = uint16(4000) + pktOutDstMAC = "02:F6:65:64:00:08" ) var ( @@ -89,7 +86,7 @@ var ( type PacketIO interface { GetTableEntry(delete bool) []*p4rtutils.ACLWbbIngressTableEntryInfo - GetPacketOut(portID uint32, submitIngress bool) []*p4_v1.PacketOut + GetPacketOut(portID uint32) []*p4v1pb.PacketOut } type testArgs struct { @@ -105,13 +102,13 @@ type testArgs struct { // programmTableEntry programs or deletes p4rt table entry based on delete flag. func programmTableEntry(ctx context.Context, t *testing.T, client *p4rt_client.P4RTClient, packetIO PacketIO, delete bool) error { t.Helper() - err := client.Write(&p4_v1.WriteRequest{ + err := client.Write(&p4v1pb.WriteRequest{ DeviceId: deviceID, - ElectionId: &p4_v1.Uint128{High: uint64(0), Low: electionID}, + ElectionId: &p4v1pb.Uint128{High: uint64(0), Low: electionID}, Updates: p4rtutils.ACLWbbIngressTableEntryGet( packetIO.GetTableEntry(delete), ), - Atomicity: p4_v1.WriteRequest_CONTINUE_ON_ERROR, + Atomicity: p4v1pb.WriteRequest_CONTINUE_ON_ERROR, }) if err != nil { return err @@ -120,13 +117,13 @@ func programmTableEntry(ctx context.Context, t *testing.T, client *p4rt_client.P } // sendPackets sends out packets via PacketOut message in StreamChannel. -func sendPackets(t *testing.T, client *p4rt_client.P4RTClient, packets []*p4_v1.PacketOut, packetCount int) { +func sendPackets(t *testing.T, client *p4rt_client.P4RTClient, packets []*p4v1pb.PacketOut, packetCount int) { count := packetCount / len(packets) for _, packet := range packets { for i := 0; i < count; i++ { if err := client.StreamChannelSendMsg( - &streamName, &p4_v1.StreamMessageRequest{ - Update: &p4_v1.StreamMessageRequest_Packet{ + &streamName, &p4v1pb.StreamMessageRequest{ + Update: &p4v1pb.StreamMessageRequest_Packet{ Packet: packet, }, }); err != nil { @@ -169,11 +166,11 @@ func testPacketOut(ctx context.Context, t *testing.T, args *testArgs) { port := sortPorts(args.ate.Ports())[0].ID() counter0 := gnmi.Get(t, args.ate.OTG(), gnmi.OTG().Port(port).Counters().InFrames().State()) - packets := args.packetIO.GetPacketOut(portID, false) + packets := args.packetIO.GetPacketOut(portID) sendPackets(t, test.client, packets, packetCount) // Wait for ate stats to be populated - time.Sleep(60 * time.Second) + time.Sleep(2 * time.Minute) // Check packet counters after packet out counter1 := gnmi.Get(t, args.ate.OTG(), gnmi.OTG().Port(port).Counters().InFrames().State()) @@ -182,11 +179,11 @@ func testPacketOut(ctx context.Context, t *testing.T, args *testArgs) { t.Logf("Received %v packets on ATE port %s", counter1-counter0, port) if test.expectPass { - if counter1-counter0 < uint64(float64(packetCount)*0.95) { + if counter1-counter0 < uint64(packetCount*0.95) { t.Fatalf("Not all the packets are received.") } } else { - if counter1-counter0 > uint64(float64(packetCount)*0.10) { + if counter1-counter0 > uint64(packetCount*0.10) { t.Fatalf("Unexpected packets are received.") } } @@ -212,7 +209,7 @@ func sortPorts(ports []*ondatra.Port) []*ondatra.Port { } // configInterfaceDUT configures the interface with the Addrs. -func configInterfaceDUT(i *oc.Interface, a *attrs.Attributes, dut *ondatra.DUTDevice) *oc.Interface { +func configInterfaceDUT(i *oc.Interface, a *attrs.Attributes, dut *ondatra.DUTDevice, hasVlan bool) *oc.Interface { i.Description = ygot.String(a.Desc) i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd if deviations.InterfaceEnabled(dut) { @@ -227,6 +224,15 @@ func configInterfaceDUT(i *oc.Interface, a *attrs.Attributes, dut *ondatra.DUTDe s4a := s4.GetOrCreateAddress(a.IPv4) s4a.PrefixLength = ygot.Uint8(ipv4PrefixLen) + if hasVlan && deviations.P4RTGdpRequiresDot1QSubinterface(dut) { + s1 := i.GetOrCreateSubinterface(1) + s1.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().SetVlanId(vlanID) + if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().SetVlanId(10) + i.GetOrCreateAggregation().GetOrCreateSwitchedVlan().SetNativeVlan(10) + } + } + return i } @@ -236,14 +242,14 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { p1 := dut.Port(t, "port1") i1 := &oc.Interface{Name: ygot.String(p1.Name()), Id: ygot.Uint32(portID)} - gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(i1, &dutPort1, dut)) + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(i1, &dutPort1, dut, true)) if deviations.ExplicitInterfaceInDefaultVRF(dut) { fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) } p2 := dut.Port(t, "port2") i2 := &oc.Interface{Name: ygot.String(p2.Name()), Id: ygot.Uint32(portID + 1)} - gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(i2, &dutPort2, dut)) + gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(i2, &dutPort2, dut, false)) if deviations.ExplicitInterfaceInDefaultVRF(dut) { fptest.AssignToNetworkInstance(t, dut, p2.Name(), deviations.DefaultNetworkInstance(dut), 0) } @@ -251,6 +257,7 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { fptest.SetPortSpeed(t, p1) fptest.SetPortSpeed(t, p2) } + gnmi.Replace(t, dut, gnmi.OC().System().MacAddress().RoutingMac().Config(), pktOutDstMAC) } // configureATE configures port1 and port2 on the ATE. @@ -298,11 +305,11 @@ func setupP4RTClient(ctx context.Context, args *testArgs) error { for index, client := range clients { if client != nil { client.StreamChannelCreate(&streamParameter) - if err := client.StreamChannelSendMsg(&streamName, &p4_v1.StreamMessageRequest{ - Update: &p4_v1.StreamMessageRequest_Arbitration{ - Arbitration: &p4_v1.MasterArbitrationUpdate{ + if err := client.StreamChannelSendMsg(&streamName, &p4v1pb.StreamMessageRequest{ + Update: &p4v1pb.StreamMessageRequest_Arbitration{ + Arbitration: &p4v1pb.MasterArbitrationUpdate{ DeviceId: streamParameter.DeviceId, - ElectionId: &p4_v1.Uint128{ + ElectionId: &p4v1pb.Uint128{ High: streamParameter.ElectionIdH, Low: streamParameter.ElectionIdL - uint64(index), }, @@ -323,30 +330,35 @@ func setupP4RTClient(ctx context.Context, args *testArgs) error { // Load p4info file. p4Info, err := utils.P4InfoLoad(p4InfoFile) if err != nil { - return errors.New("Errors seen when loading p4info file.") + return errors.New("errors seen when loading p4info file") } // Send SetForwardingPipelineConfig for p4rt leader client. - if err := args.leader.SetForwardingPipelineConfig(&p4_v1.SetForwardingPipelineConfigRequest{ + if err := args.leader.SetForwardingPipelineConfig(&p4v1pb.SetForwardingPipelineConfigRequest{ DeviceId: deviceID, - ElectionId: &p4_v1.Uint128{High: uint64(0), Low: electionID}, - Action: p4_v1.SetForwardingPipelineConfigRequest_VERIFY_AND_COMMIT, - Config: &p4_v1.ForwardingPipelineConfig{ + ElectionId: &p4v1pb.Uint128{High: uint64(0), Low: electionID}, + Action: p4v1pb.SetForwardingPipelineConfigRequest_VERIFY_AND_COMMIT, + Config: &p4v1pb.ForwardingPipelineConfig{ P4Info: p4Info, - Cookie: &p4_v1.ForwardingPipelineConfig_Cookie{ + Cookie: &p4v1pb.ForwardingPipelineConfig_Cookie{ Cookie: 159, }, }, }); err != nil { - return errors.New("Errors seen when sending SetForwardingPipelineConfig.") + return errors.New("errors seen when sending SetForwardingPipelineConfig") } return nil } // getGDPParameter returns GDP related parameters for testPacketOut testcase. func getGDPParameter(t *testing.T) PacketIO { + mac, err := net.ParseMAC(pktOutDstMAC) + if err != nil { + t.Fatalf("Could not parse MAC: %v", err) + } return &GDPPacketIO{ IngressPort: fmt.Sprint(portID), + DstMAC: mac, } } @@ -395,10 +407,11 @@ func TestPacketOut(t *testing.T) { type GDPPacketIO struct { PacketIO IngressPort string + DstMAC net.HardwareAddr } // packetGDPRequestGet generates PacketOut payload for GDP packets. -func packetGDPRequestGet() []byte { +func packetGDPRequestGet(vlan bool) []byte { buf := gopacket.NewSerializeBuffer() opts := gopacket.SerializeOptions{ FixLengths: true, @@ -410,22 +423,73 @@ func packetGDPRequestGet() []byte { DstMAC: net.HardwareAddr{0x00, 0x0A, 0xDA, 0xF0, 0xF0, 0xF0}, EthernetType: gdpInLayers, } + + payload := []byte{} + payLoadLen := 64 + for i := 0; i < payLoadLen; i++ { + payload = append(payload, byte(i)) + } + if vlan { + pktEth.EthernetType = layers.EthernetTypeDot1Q + d1q := &layers.Dot1Q{ + VLANIdentifier: vlanID, + Type: gdpInLayers, + } + gopacket.SerializeLayers(buf, opts, + pktEth, d1q, gopacket.Payload(payload), + ) + } else { + gopacket.SerializeLayers(buf, opts, + pktEth, gopacket.Payload(payload), + ) + } + return buf.Bytes() +} + +func ipPacketToATEPort1(dstMAC net.HardwareAddr) []byte { + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + eth := &layers.Ethernet{ + SrcMAC: net.HardwareAddr{0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA}, + // GDP MAC is 00:0A:DA:F0:F0:F0 + DstMAC: dstMAC, + EthernetType: layers.EthernetTypeIPv4, + } + ip := &layers.IPv4{ + SrcIP: net.ParseIP(atePort2.IPv4), + DstIP: net.ParseIP(atePort1.IPv4), + TTL: 2, + Version: 4, + Protocol: layers.IPProtocolIPv4, + } + tcp := &layers.TCP{ + SrcPort: 10000, + DstPort: 20000, + Seq: 11050, + } + + // Required for checksum computation. + tcp.SetNetworkLayerForChecksum(ip) + payload := []byte{} payLoadLen := 64 for i := 0; i < payLoadLen; i++ { payload = append(payload, byte(i)) } gopacket.SerializeLayers(buf, opts, - pktEth, gopacket.Payload(payload), + eth, ip, tcp, gopacket.Payload(payload), ) return buf.Bytes() } // GetTableEntry creates wbb acl entry related to GDP. func (gdp *GDPPacketIO) GetTableEntry(delete bool) []*p4rtutils.ACLWbbIngressTableEntryInfo { - actionType := p4_v1.Update_INSERT + actionType := p4v1pb.Update_INSERT if delete { - actionType = p4_v1.Update_DELETE + actionType = p4v1pb.Update_DELETE } return []*p4rtutils.ACLWbbIngressTableEntryInfo{{ Type: actionType, @@ -436,24 +500,35 @@ func (gdp *GDPPacketIO) GetTableEntry(delete bool) []*p4rtutils.ACLWbbIngressTab } // GetPacketOut generates PacketOut message with payload as GDP. -func (gdp *GDPPacketIO) GetPacketOut(portID uint32, submitIngress bool) []*p4_v1.PacketOut { - packets := []*p4_v1.PacketOut{} - packet := &p4_v1.PacketOut{ - Payload: packetGDPRequestGet(), - Metadata: []*p4_v1.PacketMetadata{ +func (gdp *GDPPacketIO) GetPacketOut(portID uint32) []*p4v1pb.PacketOut { + gdpWithVlan := &p4v1pb.PacketOut{ + Payload: packetGDPRequestGet(true), + Metadata: []*p4v1pb.PacketMetadata{ { MetadataId: uint32(1), // "egress_port" Value: []byte(fmt.Sprint(portID)), }, }, } - if submitIngress { - packet.Metadata = append(packet.Metadata, - &p4_v1.PacketMetadata{ + gdpWithoutVlan := &p4v1pb.PacketOut{ + Payload: packetGDPRequestGet(false), + Metadata: []*p4v1pb.PacketMetadata{ + { + MetadataId: uint32(1), // "egress_port" + Value: []byte(fmt.Sprint(portID)), + }, + }, + } + + nonGDP := &p4v1pb.PacketOut{ + Payload: ipPacketToATEPort1(gdp.DstMAC), + Metadata: []*p4v1pb.PacketMetadata{ + { MetadataId: uint32(2), // "submit_to_ingress" Value: []byte{1}, - }) + }, + }, } - packets = append(packets, packet) - return packets + + return []*p4v1pb.PacketOut{gdpWithoutVlan, gdpWithVlan, nonGDP} } diff --git a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/metadata.textproto b/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/metadata.textproto index 15f9dac55a9..09ba96867e2 100644 --- a/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/metadata.textproto +++ b/feature/experimental/p4rt/otg_tests/google_discovery_protocol_packetout_test/metadata.textproto @@ -13,6 +13,15 @@ platform_exceptions: { ipv4_missing_enabled: true } } +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + p4rt_gdp_requires_dot1q_subinterface: true + no_mix_of_tagged_and_untagged_subinterfaces: true + } +} platform_exceptions: { platform: { vendor: NOKIA diff --git a/internal/deviations/deviations.go b/internal/deviations/deviations.go index b400cef8ede..e8ad38ae431 100644 --- a/internal/deviations/deviations.go +++ b/internal/deviations/deviations.go @@ -518,3 +518,9 @@ func ISISRequireSameL1MetricWithL2Metric(dut *ondatra.DUTDevice) bool { func BGPSetMedRequiresEqualOspfSetMetric(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetBgpSetMedRequiresEqualOspfSetMetric() } + +// P4RTGdpRequiresDot1QSubinterface returns true for devices that require configuring +// subinterface with tagged vlan for p4rt packet in. +func P4RTGdpRequiresDot1QSubinterface(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetP4RtGdpRequiresDot1QSubinterface() +} diff --git a/proto/metadata.proto b/proto/metadata.proto index 4ab607babda..6576ea69634 100644 --- a/proto/metadata.proto +++ b/proto/metadata.proto @@ -285,7 +285,10 @@ message Metadata { // Devices require configuring the same OSPF setMetric when BGP // SetMED is configured. bool bgp_set_med_requires_equal_ospf_set_metric = 92; - + // Devices require configuring subinterface with tagged vlan for p4rt + // packet in. + bool p4rt_gdp_requires_dot1q_subinterface = 93; + // Reserved field numbers and identifiers. reserved 84, 9, 28; } diff --git a/proto/metadata_go_proto/metadata.pb.go b/proto/metadata_go_proto/metadata.pb.go index 5705593d2bf..2794d91301f 100644 --- a/proto/metadata_go_proto/metadata.pb.go +++ b/proto/metadata_go_proto/metadata.pb.go @@ -14,7 +14,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.28.0 // protoc v3.21.12 // source: metadata.proto @@ -550,6 +550,9 @@ type Metadata_Deviations struct { // Devices require configuring the same OSPF setMetric when BGP // SetMED is configured. BgpSetMedRequiresEqualOspfSetMetric bool `protobuf:"varint,92,opt,name=bgp_set_med_requires_equal_ospf_set_metric,json=bgpSetMedRequiresEqualOspfSetMetric,proto3" json:"bgp_set_med_requires_equal_ospf_set_metric,omitempty"` + // Devices require configuring subinterface with tagged vlan for p4rt + // packet in. + P4RtGdpRequiresDot1QSubinterface bool `protobuf:"varint,93,opt,name=p4rt_gdp_requires_dot1q_subinterface,json=p4rtGdpRequiresDot1qSubinterface,proto3" json:"p4rt_gdp_requires_dot1q_subinterface,omitempty"` } func (x *Metadata_Deviations) Reset() { @@ -1158,6 +1161,13 @@ func (x *Metadata_Deviations) GetBgpSetMedRequiresEqualOspfSetMetric() bool { return false } +func (x *Metadata_Deviations) GetP4RtGdpRequiresDot1QSubinterface() bool { + if x != nil { + return x.P4RtGdpRequiresDot1QSubinterface + } + return false +} + type Metadata_PlatformExceptions struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1221,7 +1231,7 @@ var file_metadata_proto_rawDesc = []byte{ 0x74, 0x69, 0x6e, 0x67, 0x1a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x6f, 0x6e, 0x64, 0x61, 0x74, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x62, 0x65, - 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x91, 0x33, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe1, 0x33, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, 0x49, @@ -1252,7 +1262,7 @@ var file_metadata_proto_rawDesc = []byte{ 0x72, 0x65, 0x67, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x67, 0x65, 0x78, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x52, 0x0e, 0x68, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, - 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x1a, 0xe5, 0x2b, 0x0a, 0x0a, 0x44, 0x65, 0x76, 0x69, + 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x1a, 0xb5, 0x2c, 0x0a, 0x0a, 0x44, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x70, 0x76, 0x34, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, @@ -1601,7 +1611,12 @@ var file_metadata_proto_rawDesc = []byte{ 0x61, 0x6c, 0x5f, 0x6f, 0x73, 0x70, 0x66, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x5c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, 0x62, 0x67, 0x70, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x45, 0x71, 0x75, 0x61, 0x6c, - 0x4f, 0x73, 0x70, 0x66, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x4a, 0x04, 0x08, + 0x4f, 0x73, 0x70, 0x66, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x4e, 0x0a, + 0x24, 0x70, 0x34, 0x72, 0x74, 0x5f, 0x67, 0x64, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, + 0x65, 0x73, 0x5f, 0x64, 0x6f, 0x74, 0x31, 0x71, 0x5f, 0x73, 0x75, 0x62, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x5d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x20, 0x70, 0x34, 0x72, + 0x74, 0x47, 0x64, 0x70, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x44, 0x6f, 0x74, 0x31, + 0x71, 0x53, 0x75, 0x62, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x4a, 0x04, 0x08, 0x54, 0x10, 0x55, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x4a, 0x04, 0x08, 0x1c, 0x10, 0x1d, 0x1a, 0xa0, 0x01, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f,