Skip to content

Commit

Permalink
P4RT GDP requires vlan in EthernetHeader. (openconfig#1987)
Browse files Browse the repository at this point in the history
* gdp add vlan id

* change deviation for Nokia
  • Loading branch information
Ankur19 authored Aug 16, 2023
1 parent 98014f3 commit e84f4ca
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 (
Expand All @@ -55,6 +55,7 @@ var (
electionID = uint64(100)
metadataIngressPort = uint32(1)
metadataEgressPort = uint32(2)
vlanID = uint16(4000)
)

var (
Expand Down Expand Up @@ -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
}
Expand All @@ -111,37 +112,53 @@ 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
}
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())
Expand All @@ -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) {
Expand All @@ -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()
Expand All @@ -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)
}
})
}
Expand All @@ -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) {
Expand All @@ -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
}

Expand All @@ -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)
}
Expand Down Expand Up @@ -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),
},
Expand All @@ -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
}
Expand Down Expand Up @@ -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,
Expand All @@ -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.
Expand Down
Loading

0 comments on commit e84f4ca

Please sign in to comment.