diff --git a/ovs/matchflow.go b/ovs/matchflow.go index 445afe9..4ecf2c2 100644 --- a/ovs/matchflow.go +++ b/ovs/matchflow.go @@ -105,7 +105,7 @@ func (f *MatchFlow) MarshalText() ([]byte, error) { b = append(b, ',') } - if f.Cookie > 0 { + if f.Cookie > 0 || f.CookieMask > 0 { // Hexadecimal cookies and masks are much easier to read. b = append(b, cookie+"="...) b = append(b, paddedHexUint64(f.Cookie)...) diff --git a/ovs/matchflow_test.go b/ovs/matchflow_test.go index d48ba78..d7e17eb 100644 --- a/ovs/matchflow_test.go +++ b/ovs/matchflow_test.go @@ -232,6 +232,24 @@ func TestMatchFlowMarshalText(t *testing.T) { }, s: "udp,in_port=33,nw_dst=192.0.2.1,tp_dst=0xea60/0xffe0,table=55", }, + { + desc: "Test zero cookie match", + f: &MatchFlow{ + Cookie: 0, + CookieMask: 0xffffffffffffffff, + Table: 45, + }, + s: "cookie=0x0000000000000000/0xffffffffffffffff,table=45", + }, + { + desc: "Test match any cookie", + f: &MatchFlow{ + Cookie: 0, + CookieMask: 0, + Table: 45, + }, + s: "table=45", + }, } for _, tt := range tests { diff --git a/ovs/openflow.go b/ovs/openflow.go index a450aba..f431413 100644 --- a/ovs/openflow.go +++ b/ovs/openflow.go @@ -267,13 +267,21 @@ func (o *OpenFlowService) DumpTables(bridge string) ([]*Table, error) { return tables, err } -// DumpFlows retrieves statistics about all flows for the specified bridge. +// DumpFlowsWithFlowArgs retrieves statistics about all flows for the specified bridge, +// filtering on the specified flow(s), if provided. // If a table has no active flows and has not been used for a lookup or matched // by an incoming packet, it is filtered from the output. -func (o *OpenFlowService) DumpFlows(bridge string) ([]*Flow, error) { +// We neeed to add a Matchflow to filter the dumpflow results. For example filter based on table, cookie. +func (o *OpenFlowService) DumpFlowsWithFlowArgs(bridge string, flow *MatchFlow) ([]*Flow, error) { args := []string{"dump-flows", bridge} args = append(args, o.c.ofctlFlags...) - + if flow != nil { + fb, err := flow.MarshalText() + if err != nil { + return nil, err + } + args = append(args, string(fb)) + } out, err := o.exec(args...) if err != nil { return nil, err @@ -298,6 +306,13 @@ func (o *OpenFlowService) DumpFlows(bridge string) ([]*Flow, error) { return flows, err } +// DumpFlows retrieves statistics about all flows for the specified bridge. +// If a table has no active flows and has not been used for a lookup or matched +// by an incoming packet, it is filtered from the output. +func (o *OpenFlowService) DumpFlows(bridge string) ([]*Flow, error) { + return o.DumpFlowsWithFlowArgs(bridge, nil) +} + // DumpAggregate retrieves statistics about the specified flow attached to the // specified bridge. func (o *OpenFlowService) DumpAggregate(bridge string, flow *MatchFlow) (*FlowStats, error) { diff --git a/ovs/openflow_test.go b/ovs/openflow_test.go index 3da04dc..eaa408b 100644 --- a/ovs/openflow_test.go +++ b/ovs/openflow_test.go @@ -1058,6 +1058,116 @@ NXST_FLOW reply (xid=0x4): } } +func TestClientOpenFlowDumpFlowsWithFlowArgs(t *testing.T) { + tests := []struct { + name string + table string + cookie uint64 + cookieMask uint64 + input string + flows string + want []*Flow + err error + }{ + { + name: "test single flow", + input: "br0", + table: "45", + cookie: 0, + cookieMask: 0x0, + flows: `NXST_FLOW reply (xid=0x4): + cookie=0x01, duration=9215.748s, table=45, n_packets=6, n_bytes=480, idle_age=9206, priority=820,in_port=LOCAL actions=mod_vlan_vid:10,output:1 +`, + want: []*Flow{ + { + Priority: 820, + InPort: PortLOCAL, + Matches: []Match{}, + Table: 45, + Cookie: 1, + Actions: []Action{ + ModVLANVID(10), + Output(1), + }, + }, + }, + err: nil, + }, + { + name: "test multiple flows", + input: "br0", + table: "45", + cookie: 0, + cookieMask: 0x1, + flows: `NXST_FLOW reply (xid=0x4): + cookie=0x0, duration=9215.748s, table=45, n_packets=6, n_bytes=480, idle_age=9206, priority=820,in_port=LOCAL actions=mod_vlan_vid:10,output:1 + cookie=0x0, duration=1121991.329s, table=45, n_packets=0, n_bytes=0, priority=110,ip,dl_src=f1:f2:f3:f4:f5:f6 actions=ct(table=51) +`, + want: []*Flow{ + { + Priority: 820, + InPort: PortLOCAL, + Matches: []Match{}, + Table: 45, + Cookie: 0, + Actions: []Action{ + ModVLANVID(10), + Output(1), + }, + }, + { + Priority: 110, + Protocol: ProtocolIPv4, + Matches: []Match{ + DataLinkSource("f1:f2:f3:f4:f5:f6"), + }, + Table: 45, + Actions: []Action{ + ConnectionTracking("table=51"), + }, + }, + }, + err: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, _ := testClient([]OptionFunc{Timeout(1)}, func(cmd string, args ...string) ([]byte, error) { + if want, got := "ovs-ofctl", cmd; want != got { + t.Fatalf("incorrect command:\n- want: %v\n- got: %v", + want, got) + } + filterArg := "cookie=0x0000000000000000/0xffffffffffffffff," + "table=" + tt.table + wantArgs := []string{ + "--timeout=1", + "dump-flows", + string(tt.input), + filterArg, + } + if want, got := wantArgs, args; !reflect.DeepEqual(want, got) { + t.Fatalf("incorrect arguments\n- want: %v\n- got: %v", + want, got) + } + return []byte(tt.flows), tt.err + }).OpenFlow.DumpFlowsWithFlowArgs(tt.input, &MatchFlow{Cookie: 0, + CookieMask: 0xffffffffffffffff, + Table: 45}) + if len(tt.want) != len(got) { + t.Errorf("got %d", len(got)) + t.Errorf("want %d", len(tt.want)) + t.Fatal("expected return value to be equal") + } + for i := range tt.want { + if !flowsEqual(tt.want[i], got[i]) { + t.Errorf("got %+v", got[i]) + t.Errorf("want %+v", tt.want[i]) + t.Fatal("expected return value to be equal") + } + } + }) + } +} + func TestClientOpenFlowDumpFlows15(t *testing.T) { tests := []struct { name string