Skip to content

Commit ebeeb80

Browse files
authored
Merge pull request #41 from digitalocean/nshrader/conjunctions
ovs: add conj_id matcher
2 parents 12e0112 + 44e0cbc commit ebeeb80

File tree

8 files changed

+185
-0
lines changed

8 files changed

+185
-0
lines changed

ovs/action.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ var (
4545
// errResubmitPortInvalid is returned when ResubmitPort is given a port number that is
4646
// invalid per the openflow spec.
4747
errResubmitPortInvalid = errors.New("resubmit port must be between 0 and 65279 inclusive")
48+
49+
// errTooManyDimensions is returned when the specified dimension exceeds the total dimension
50+
// in a conjunction action.
51+
errDimensionTooLarge = errors.New("dimension number exceeds total number of dimensions")
4852
)
4953

5054
// Action strings in lower case, as those are compared to the lower case letters
@@ -145,6 +149,7 @@ func StripVLAN() Action {
145149
// printf-style patterns for marshaling and unmarshaling actions.
146150
const (
147151
patConnectionTracking = "ct(%s)"
152+
patConjunction = "conjunction(%d, %d/%d)"
148153
patModDataLinkDestination = "mod_dl_dst:%s"
149154
patModDataLinkSource = "mod_dl_src:%s"
150155
patModNetworkDestination = "mod_nw_dst:%s"
@@ -372,6 +377,37 @@ func (a *outputAction) GoString() string {
372377
return fmt.Sprintf("ovs.Output(%d)", a.port)
373378
}
374379

380+
// Conjunction associates a flow with a certain conjunction ID to match on more than
381+
// one dimension across multiple set matches.
382+
func Conjunction(id int, dimensionNumber int, dimensionSize int) Action {
383+
return &conjunctionAction{
384+
id: id,
385+
dimensionNumber: dimensionNumber,
386+
dimensionSize: dimensionSize,
387+
}
388+
}
389+
390+
// A conjuctionAction is an Action which is used by Conjunction.
391+
type conjunctionAction struct {
392+
id int
393+
dimensionNumber int
394+
dimensionSize int
395+
}
396+
397+
// MarshalText implements Action.
398+
func (a *conjunctionAction) MarshalText() ([]byte, error) {
399+
if a.dimensionNumber > a.dimensionSize {
400+
return nil, errDimensionTooLarge
401+
}
402+
403+
return bprintf(patConjunction, a.id, a.dimensionNumber, a.dimensionSize), nil
404+
}
405+
406+
// GoString implements Action.
407+
func (a *conjunctionAction) GoString() string {
408+
return fmt.Sprintf("ovs.Conjunction(%d, %d, %d)", a.id, a.dimensionNumber, a.dimensionSize)
409+
}
410+
375411
// Resubmit resubmits a packet for further processing by matching
376412
// flows with the specified port and table. If port or table are zero,
377413
// they are set to empty in the output Action. If both are zero, an

ovs/action_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,50 @@ func TestSetTunnel(t *testing.T) {
531531
}
532532
}
533533

534+
func TestConjunction(t *testing.T) {
535+
var tests = []struct {
536+
desc string
537+
a Action
538+
action string
539+
err error
540+
}{
541+
{
542+
desc: "set conjunction 1/2",
543+
a: Conjunction(123, 1, 2),
544+
action: "conjunction(123, 1/2)",
545+
},
546+
{
547+
desc: "set conjunction 2/2",
548+
a: Conjunction(123, 2, 2),
549+
action: "conjunction(123, 2/2)",
550+
},
551+
{
552+
desc: "set conjunction 3/2",
553+
a: Conjunction(123, 3, 2),
554+
err: errDimensionTooLarge,
555+
},
556+
}
557+
558+
for _, tt := range tests {
559+
t.Run(tt.desc, func(t *testing.T) {
560+
action, err := tt.a.MarshalText()
561+
562+
if want, got := tt.err, err; want != got {
563+
t.Fatalf("unexpected error:\n- want: %v\n- got: %v",
564+
want, got)
565+
}
566+
if err != nil {
567+
return
568+
}
569+
570+
if want, got := tt.action, string(action); want != got {
571+
t.Fatalf("unexpected Action:\n- want: %q\n- got: %q",
572+
want, got)
573+
}
574+
})
575+
}
576+
}
577+
534578
func TestActionGoString(t *testing.T) {
535579
tests := []struct {
536580
a Action
@@ -600,6 +644,10 @@ func TestActionGoString(t *testing.T) {
600644
a: SetTunnel(10),
601645
s: `ovs.SetTunnel(0xa)`,
602646
},
647+
{
648+
a: Conjunction(123, 1, 2),
649+
s: `ovs.Conjunction(123, 1, 2)`,
650+
},
603651
}
604652

605653
for _, tt := range tests {

ovs/actionparser.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,18 @@ func parseAction(s string) (Action, error) {
297297
}
298298
}
299299

300+
// ActionConjunction, with it's id, dimension number, and dimension size
301+
if strings.HasPrefix(s, patConjunction[:len(patConjunction)-10]) {
302+
var id, dimensionNumber, dimensionSize int
303+
n, err := fmt.Sscanf(s, patConjunction, &id, &dimensionNumber, &dimensionSize)
304+
if err != nil {
305+
return nil, err
306+
}
307+
if n > 0 {
308+
return Conjunction(id, dimensionNumber, dimensionSize), nil
309+
}
310+
}
311+
300312
// ActionOutput, with its port number
301313
if strings.HasPrefix(s, patOutput[:len(patOutput)-2]) {
302314
var port int

ovs/actionparser_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,22 @@ func Test_parseAction(t *testing.T) {
281281
s: "set_field:192.168.1.1->arp_spa",
282282
a: SetField("192.168.1.1", "arp_spa"),
283283
},
284+
{
285+
s: "conjunction(123, 1/2)",
286+
a: Conjunction(123, 1, 2),
287+
},
288+
{
289+
s: "conjunction(123, 2/2)",
290+
a: Conjunction(123, 2, 2),
291+
},
292+
{
293+
s: "conjunction(123, 3/2)",
294+
invalid: true,
295+
},
296+
{
297+
s: "conjunxxxxx(123, 3/2)",
298+
invalid: true,
299+
},
284300
}
285301

286302
for _, tt := range tests {

ovs/match.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const (
3939
arpSPA = "arp_spa"
4040
arpTHA = "arp_tha"
4141
arpTPA = "arp_tpa"
42+
conjID = "conj_id"
4243
ctMark = "ct_mark"
4344
ctState = "ct_state"
4445
ctZone = "ct_zone"
@@ -247,6 +248,29 @@ func (m *networkMatch) GoString() string {
247248
return fmt.Sprintf("ovs.NetworkDestination(%q)", m.ip)
248249
}
249250

251+
// ConjunctionID matches flows that have matched all dimension of a conjunction
252+
// inside of the openflow table.
253+
func ConjunctionID(id uint32) Match {
254+
return &conjunctionIDMatch{
255+
id: id,
256+
}
257+
}
258+
259+
// A conjunctionIDMatch is a Match returned by ConjunctionID
260+
type conjunctionIDMatch struct {
261+
id uint32
262+
}
263+
264+
// MarshalText implements Match.
265+
func (m *conjunctionIDMatch) MarshalText() ([]byte, error) {
266+
return bprintf("conj_id=%v", m.id), nil
267+
}
268+
269+
// GoString implements Match.
270+
func (m *conjunctionIDMatch) GoString() string {
271+
return fmt.Sprintf("ovs.ConjunctionID(%v)", m.id)
272+
}
273+
250274
// NetworkProtocol matches packets with the specified IP or IPv6 protocol
251275
// number matching num. For example, specifying 1 when a Flow's Protocol
252276
// is IPv4 matches ICMP packets, or 58 when Protocol is IPv6 matches ICMPv6

ovs/match_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,39 @@ func TestMatchNetworkProtocol(t *testing.T) {
486486
}
487487
}
488488

489+
func TestMatchConjunctionID(t *testing.T) {
490+
var tests = []struct {
491+
desc string
492+
num uint32
493+
out string
494+
}{
495+
{
496+
desc: "ID 1",
497+
num: 1,
498+
out: "conj_id=1",
499+
},
500+
{
501+
desc: "ID 11111",
502+
num: 11111,
503+
out: "conj_id=11111",
504+
},
505+
}
506+
507+
for _, tt := range tests {
508+
t.Run(tt.desc, func(t *testing.T) {
509+
out, err := ConjunctionID(tt.num).MarshalText()
510+
if err != nil {
511+
t.Fatalf("unexpected error: %v", err)
512+
}
513+
514+
if want, got := tt.out, string(out); want != got {
515+
t.Fatalf("unexpected Match output:\n- want: %q\n- got: %q",
516+
want, got)
517+
}
518+
})
519+
}
520+
}
521+
489522
func TestMatchConnectionTrackingState(t *testing.T) {
490523
var tests = []struct {
491524
desc string
@@ -998,6 +1031,10 @@ func TestMatchGoString(t *testing.T) {
9981031
m: TunnelIDWithMask(0xa, 0x0f),
9991032
s: `ovs.TunnelIDWithMask(0xa, 0xf)`,
10001033
},
1034+
{
1035+
m: ConjunctionID(123),
1036+
s: `ovs.ConjunctionID(123)`,
1037+
},
10011038
}
10021039

10031040
for _, tt := range tests {

ovs/matchparser.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ func parseMatch(key string, value string) (Match, error) {
3333
return parseIntMatch(key, value, math.MaxUint8)
3434
case tpSRC, tpDST, ctZone:
3535
return parseIntMatch(key, value, math.MaxUint16)
36+
case conjID:
37+
return parseIntMatch(key, value, math.MaxUint32)
3638
case arpSPA:
3739
return ARPSourceProtocolAddress(value), nil
3840
case arpTPA:
@@ -108,6 +110,8 @@ func parseIntMatch(key string, value string, max int) (Match, error) {
108110
return TransportDestinationPort(uint16(t)), nil
109111
case ctZone:
110112
return ConnectionTrackingZone(uint16(t)), nil
113+
case conjID:
114+
return ConjunctionID(uint32(t)), nil
111115
}
112116

113117
return nil, fmt.Errorf("no action matched for %s=%s", key, value)

ovs/matchparser_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,14 @@ func Test_parseMatch(t *testing.T) {
287287
final: "tun_id=0xa/0x2",
288288
m: TunnelIDWithMask(10, 2),
289289
},
290+
{
291+
s: "conj_id=123",
292+
m: ConjunctionID(123),
293+
},
294+
{
295+
s: "conj_id=nope",
296+
invalid: true,
297+
},
290298
}
291299

292300
for _, tt := range tests {

0 commit comments

Comments
 (0)