Skip to content

Commit 63aa367

Browse files
author
Nicolas Bouliane
committed
Update the ProtoTrace function to accept extra parameters.
When tracing a packet that goes through the connection tracker, --ct-next can be specified to set the states expected. Update parseCTState() with updated format. Change the parser to extract the protocol in a more flexible way. Change the parser to skip unused keywords. Add more tests with connection tracking trace
1 parent b433cb8 commit 63aa367

File tree

6 files changed

+262
-31
lines changed

6 files changed

+262
-31
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
language: go
22
go:
3-
- 1.11
3+
- 1.12
44
os:
55
- linux
66
sudo: required
77
before_install:
88
- sudo apt update
99
- sudo apt install openvswitch-switch
1010
- sudo ovs-vsctl add-br ovsbr0
11-
- go get github.com/golang/lint/golint
11+
- go get -u golang.org/x/lint/golint
1212
- go get -d ./...
1313
script:
1414
- ./scripts/licensecheck.sh

ovs/app.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ type AppService struct {
2525
}
2626

2727
// ProtoTrace runs ovs-appctl ofproto/trace on the given bridge and match flow
28-
// and returns a *ProtoTrace. Also returns err if there is any error parsing the
29-
// output from ovs-appctl ofproto/trace.
30-
func (a *AppService) ProtoTrace(bridge string, protocol Protocol, matches []Match) (*ProtoTrace, error) {
28+
// with the possibility to pass extra parameters like `--ct-next` and returns a *ProtoTrace.
29+
// Also returns err if there is any error parsing the output from ovs-appctl ofproto/trace.
30+
func (a *AppService) ProtoTrace(bridge string, protocol Protocol, matches []Match, params ...string) (*ProtoTrace, error) {
3131
matchFlows := []string{}
3232
if protocol != "" {
3333
matchFlows = append(matchFlows, string(protocol))
@@ -44,6 +44,7 @@ func (a *AppService) ProtoTrace(bridge string, protocol Protocol, matches []Matc
4444

4545
matchArg := strings.Join(matchFlows, ",")
4646
args := []string{"ofproto/trace", bridge, matchArg}
47+
args = append(args, params...)
4748
out, err := a.exec(args...)
4849
if err != nil {
4950
return nil, err

ovs/matchparser.go

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -243,23 +243,26 @@ func parseMACMatch(key string, value string) (Match, error) {
243243

244244
// parseCTState parses a series of connection tracking values into a Match.
245245
func parseCTState(value string) (Match, error) {
246-
if len(value)%4 != 0 {
247-
return nil, errors.New("ct_state length must be divisible by 4")
248-
}
249-
250-
var buf bytes.Buffer
251-
var states []string
252-
253-
for i, r := range value {
254-
if i != 0 && i%4 == 0 {
255-
states = append(states, buf.String())
256-
buf.Reset()
257-
}
258-
259-
_, _ = buf.WriteRune(r)
260-
}
261-
states = append(states, buf.String())
262-
246+
// If the format use bar:
247+
// "est|trk|dnat" => "+est+trk+dnat"
248+
if strings.Contains(value, "|") {
249+
value = strings.ReplaceAll(value, "|", "+")
250+
value = "+" + value
251+
}
252+
253+
// Add space between flags
254+
// "+est+trk+dnat-snat" => "+est +trk +dnat -snat"
255+
if strings.Contains(value, "+") || strings.Contains(value, "-") {
256+
value = strings.ReplaceAll(value, "+", " +")
257+
value = strings.ReplaceAll(value, "-", " -")
258+
value = strings.Trim(value, " ")
259+
} else {
260+
// There are only two valid format for ct_state:
261+
// `ct_state=+est+trk` and `ct_state=est|trk`
262+
return nil, errors.New("ct_state format is invalid")
263+
}
264+
265+
states := strings.Fields(value)
263266
return ConnectionTrackingState(states...), nil
264267
}
265268

ovs/matchparser_test.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ import (
2323

2424
func Test_parseMatch(t *testing.T) {
2525
var tests = []struct {
26-
desc string
27-
s string
28-
final string
29-
m Match
30-
invalid bool
26+
desc string
27+
s string
28+
final string
29+
m Match
30+
invalid bool
31+
skipMarshal bool
3132
}{
3233
{
3334
s: "foo=bar",
@@ -68,6 +69,23 @@ func Test_parseMatch(t *testing.T) {
6869
UnsetState(CTStateNew),
6970
),
7071
},
72+
{
73+
s: "ct_state=est|trk",
74+
m: ConnectionTrackingState("+est", "+trk"),
75+
skipMarshal: true,
76+
},
77+
{
78+
s: "ct_state=esttrk",
79+
invalid: true,
80+
},
81+
{
82+
s: "ct_state=est trk",
83+
invalid: true,
84+
},
85+
{
86+
s: "ct_state=est$trk",
87+
invalid: true,
88+
},
7189
{
7290
s: "tcp_flags=+omg",
7391
invalid: true,
@@ -476,6 +494,10 @@ func Test_parseMatch(t *testing.T) {
476494
tt.m, m)
477495
}
478496

497+
if tt.skipMarshal {
498+
return
499+
}
500+
479501
s, err := m.MarshalText()
480502
if err != nil {
481503
t.Fatalf("unexpected error: %v", err)

ovs/proto_trace.go

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ var (
3434
traceStartRegexp = regexp.MustCompile(`bridge\("(.*)"\)`)
3535
traceFlowRegexp = regexp.MustCompile(` *[[:digit:]]+[.] ([[:alpha:]].*)`)
3636
traceActionRegexp = regexp.MustCompile(` +([[:alpha:]].*)`)
37+
recircIDRegexp = regexp.MustCompile(`recirc_id=`)
38+
recircRegexp = regexp.MustCompile(`recirc\(`)
39+
ctCommentRegexp = regexp.MustCompile(`^\s+->`)
40+
ctThawRegexp = regexp.MustCompile(`thaw`)
41+
ctResumeFromRegexp = regexp.MustCompile(`Resuming from table`)
42+
ctResumeWithRegexp = regexp.MustCompile(`resume conntrack with`)
43+
tunNative = regexp.MustCompile(`native tunnel`)
3744

3845
pushVLANPattern = `push_vlan(vid=[0-9]+,pcp=[0-9]+)`
3946
)
@@ -80,12 +87,25 @@ func (df *DataPathFlows) UnmarshalText(b []byte) error {
8087
return errors.New("error unmarshalling text, no comma delimiter found")
8188
}
8289

83-
// first string is always the protocol
84-
df.Protocol = Protocol(matches[0])
90+
for _, match := range matches {
91+
switch Protocol(match) {
92+
case ProtocolARP, ProtocolICMPv4, ProtocolICMPv6,
93+
ProtocolIPv4, ProtocolIPv6, ProtocolTCPv4,
94+
ProtocolTCPv6, ProtocolUDPv4, ProtocolUDPv6:
95+
df.Protocol = Protocol(match)
96+
continue
97+
}
8598

86-
matches = matches[1:]
99+
// We can safely skip these keywords
100+
switch {
101+
case match == "eth":
102+
continue
103+
case match == "unchanged":
104+
continue
105+
case recircIDRegexp.MatchString(match):
106+
continue
107+
}
87108

88-
for _, match := range matches {
89109
kv := strings.Split(match, "=")
90110
if len(kv) != 2 {
91111
return fmt.Errorf("unexpected match format for match %q", match)
@@ -126,6 +146,11 @@ func (pt *ProtoTrace) UnmarshalText(b []byte) error {
126146
lines := strings.Split(string(b), "\n")
127147
for _, line := range lines {
128148
if matches, matched := checkMatch(datapathActionsRegexp, line); matched {
149+
150+
if recircRegexp.MatchString(line) {
151+
pt.FlowActions = append(pt.FlowActions, "recirc")
152+
}
153+
129154
// first index is always the left most match, following
130155
// are the actual matches
131156
pt.DataPathActions = &dataPathActions{
@@ -169,6 +194,20 @@ func (pt *ProtoTrace) UnmarshalText(b []byte) error {
169194
continue
170195
}
171196

197+
// We can safely skip these keywords
198+
switch {
199+
case ctCommentRegexp.MatchString(line):
200+
continue
201+
case ctThawRegexp.MatchString(line):
202+
continue
203+
case ctResumeFromRegexp.MatchString(line):
204+
continue
205+
case ctResumeWithRegexp.MatchString(line):
206+
continue
207+
case tunNative.MatchString(line):
208+
continue
209+
}
210+
172211
if matches, matched := checkMatch(traceActionRegexp, line); matched {
173212
pt.FlowActions = append(pt.FlowActions, matches[1])
174213
continue

ovs/proto_trace_test.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,172 @@ Datapath actions: drop`,
126126
"output:1",
127127
},
128128
},
129+
130+
{
131+
name: "connection tracker trace with 3 legs",
132+
output: ` Flow: icmp,in_port=4,dl_vlan=2,dl_vlan_pcp=0,vlan_tci1=0x0000,dl_src=10:0e:7e:be:fc:40,dl_dst=3c:fd:fe:b6:fb:50,nw_src=10.126.86.66,nw_dst=10.39.144.8,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=8,icmp_code=0
133+
134+
bridge("br0")
135+
-------------
136+
0. ip,in_port=4,dl_vlan=2,nw_dst=10.39.144.8, priority 900, cookie 0x1dfd9000410000
137+
resubmit(,25)
138+
25. ip,in_port=4,dl_vlan=2,nw_dst=10.39.144.8, priority 2020, cookie 0x1dfd9000410000
139+
pop_vlan
140+
set_field:fe:00:00:00:01:01->eth_src
141+
set_field:a6:c1:a7:15:a4:3d->eth_dst
142+
resubmit(,28)
143+
28. priority 100
144+
resubmit(,35)
145+
35. priority 100
146+
resubmit(,45)
147+
45. priority 100
148+
resubmit(,50)
149+
50. ip,dl_dst=a6:c1:a7:15:a4:3d, priority 110, cookie 0x1dfd9000500000
150+
ct(table=51)
151+
drop
152+
-> A clone of the packet is forked to recirculate. The forked pipeline will be resumed at table 51.
153+
-> Sets the packet to an untracked state, and clears all the conntrack fields.
154+
Final flow: icmp,in_port=4,vlan_tci=0x0000,dl_src=fe:00:00:00:01:01,dl_dst=a6:c1:a7:15:a4:3d,nw_src=10.126.86.66,nw_dst=10.39.144.8,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=8,icmp_code=0
155+
Megaflow: recirc_id=0,eth,ip,tun_id=0,in_port=4,dl_vlan=2,dl_vlan_pcp=0,dl_src=10:0e:7e:be:fc:40,dl_dst=3c:fd:fe:b6:fb:50,nw_src=10.64.0.0/10,nw_dst=10.39.144.8,nw_frag=no
156+
Datapath actions: set(eth(src=fe:00:00:00:01:01,dst=a6:c1:a7:15:a4:3d)),pop_vlan,ct,recirc(0x908)
157+
===============================================================================
158+
recirc(0x908) - resume conntrack with default ct_state=trk|new (use --ct-next to customize)
159+
===============================================================================
160+
Flow: recirc_id=0x908,ct_state=new|trk,eth,icmp,in_port=4,vlan_tci=0x0000,dl_src=fe:00:00:00:01:01,dl_dst=a6:c1:a7:15:a4:3d,nw_src=10.126.86.66,nw_dst=10.39.144.8,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=8,icmp_code=0
161+
bridge("br0")
162+
-------------
163+
thaw
164+
Resuming from table 51
165+
51. priority 200
166+
resubmit(,55)
167+
55. ct_state=+new+trk,icmp,dl_dst=a6:c1:a7:15:a4:3d, priority 1000, cookie 0x1dfd9000500000
168+
ct(commit,table=60,exec(set_field:0x1dfd90->ct_mark))
169+
set_field:0x1dfd90->ct_mark
170+
-> A clone of the packet is forked to recirculate. The forked pipeline will be resumed at table 60.
171+
-> Sets the packet to an untracked state, and clears all the conntrack fields.
172+
Final flow: recirc_id=0x908,eth,icmp,in_port=4,vlan_tci=0x0000,dl_src=fe:00:00:00:01:01,dl_dst=a6:c1:a7:15:a4:3d,nw_src=10.126.86.66,nw_dst=10.39.144.8,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=8,icmp_code=0
173+
Megaflow: recirc_id=0x908,ct_state=+new-est-rel-rpl+trk,ct_mark=0,eth,icmp,in_port=4,dl_dst=a6:c1:a7:15:a4:3d,nw_frag=no
174+
Datapath actions: ct(commit,mark=0x1dfd90/0xffffffff),recirc(0x909)
175+
===============================================================================
176+
recirc(0x909) - resume conntrack with default ct_state=trk|new (use --ct-next to customize)
177+
===============================================================================
178+
Flow: recirc_id=0x909,ct_state=new|trk,ct_mark=0x1dfd90,eth,icmp,in_port=4,vlan_tci=0x0000,dl_src=fe:00:00:00:01:01,dl_dst=a6:c1:a7:15:a4:3d,nw_src=10.126.86.66,nw_dst=10.39.144.8,nw_tos=0,nw_ecn=0,nw_ttl=0,icmp_type=8,icmp_code=0
179+
bridge("br0")
180+
-------------
181+
thaw
182+
Resuming from table 60
183+
60. priority 100
184+
resubmit(,62)
185+
62. priority 100
186+
resubmit(,65)
187+
65. ip,vlan_tci=0x0000/0x1fff,dl_dst=a6:c1:a7:15:a4:3d,nw_dst=10.39.144.8, priority 1000, cookie 0x1dfd9000400000
188+
output:30
189+
Final flow: unchanged
190+
Megaflow: recirc_id=0x909,eth,ip,tun_id=0,in_port=4,vlan_tci=0x0000/0x1fff,dl_dst=a6:c1:a7:15:a4:3d,nw_src=10.64.0.0/10,nw_dst=10.39.144.8,nw_frag=no
191+
Datapath actions: 7`,
192+
datapathActions: NewDataPathActions("7"),
193+
flowActions: []string{
194+
"resubmit(,25)",
195+
"pop_vlan",
196+
"set_field:fe:00:00:00:01:01->eth_src",
197+
"set_field:a6:c1:a7:15:a4:3d->eth_dst",
198+
"resubmit(,28)",
199+
"resubmit(,35)",
200+
"resubmit(,45)",
201+
"resubmit(,50)",
202+
"ct(table=51)",
203+
"drop",
204+
"recirc",
205+
"resubmit(,55)",
206+
"ct(commit,table=60,exec(set_field:0x1dfd90->ct_mark))",
207+
"set_field:0x1dfd90->ct_mark",
208+
"recirc",
209+
"resubmit(,62)",
210+
"resubmit(,65)",
211+
"output:30",
212+
},
213+
},
214+
{
215+
name: "connection tracker trace with 2 legs",
216+
output: `Flow: ct_mark=0x1e240,ip,in_port=6,vlan_tci=0x0000,dl_src=56:03:b3:97:ac:c8,dl_dst=4a:72:d2:56:78:d1,nw_src=10.36.96.36,nw_dst=10.36.96.37,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
217+
218+
bridge("br0")
219+
-------------
220+
0. ip,in_port=6,dl_src=56:03:b3:97:ac:c8,nw_src=10.36.96.36, priority 2000, cookie 0x1e24001800000
221+
resubmit(,25)
222+
25. ip,vlan_tci=0x0000/0x1fff,nw_src=10.36.96.36, priority 2000, cookie 0x1e24001800000
223+
push_vlan:0x8100
224+
set_field:4118->vlan_vid
225+
resubmit(,25)
226+
25. ip,dl_vlan=22,nw_dst=10.36.96.37, priority 2020, cookie 0x1d97c01800000
227+
pop_vlan
228+
resubmit(,28)
229+
28. ip,in_port=6, priority 110, cookie 0x1e24001900000
230+
ct(table=30)
231+
drop
232+
-> A clone of the packet is forked to recirculate. The forked pipeline will be resumed at table 30.
233+
234+
Final flow: ip,in_port=6,vlan_tci=0x0000,dl_src=56:03:b3:97:ac:c8,dl_dst=4a:72:d2:56:78:d1,nw_src=10.36.96.36,nw_dst=10.36.96.37,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
235+
Megaflow: recirc_id=0,eth,ip,in_port=6,vlan_tci=0x0000/0x1fff,dl_src=56:03:b3:97:ac:c8,dl_dst=00:00:00:00:00:00/01:00:00:00:00:00,nw_src=10.36.96.36,nw_dst=10.36.96.37,nw_proto=0,nw_frag=no
236+
Datapath actions: ct,recirc(0x2)
237+
238+
===============================================================================
239+
recirc(0x2) - resume conntrack with ct_state=est|trk
240+
===============================================================================
241+
242+
Flow: recirc_id=0x2,ct_state=est|trk,ct_mark=0x1e240,eth,ip,in_port=6,vlan_tci=0x0000,dl_src=56:03:b3:97:ac:c8,dl_dst=4a:72:d2:56:78:d1,nw_src=10.36.96.36,nw_dst=10.36.96.37,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
243+
244+
bridge("br0")
245+
-------------
246+
thaw
247+
Resuming from table 30
248+
30. ct_state=+est+trk,ct_mark=0x1e240,in_port=6, priority 220, cookie 0x1e24001900000
249+
resubmit(,35)
250+
35. priority 100
251+
resubmit(,45)
252+
45. priority 100
253+
resubmit(,50)
254+
50. priority 100
255+
resubmit(,60)
256+
60. ip,in_port=6,dl_src=56:03:b3:97:ac:c8,nw_src=10.36.96.36, priority 1020, cookie 0x1e24001800000
257+
resubmit(,62)
258+
62. ip,tun_id=0,nw_dst=10.36.96.0/20, priority 1000
259+
resubmit(,67)
260+
67. conj_id=2, priority 1500, cookie 0x200020000
261+
set_field:0x2->tun_id
262+
resubmit(,68)
263+
68. ip,tun_id=0x2,nw_dst=10.36.96.37, priority 1500, cookie 0x200020000
264+
set_field:10.39.129.11->tun_dst
265+
output:9
266+
-> output to native tunnel
267+
>> native tunnel routing failed
268+
269+
Final flow: recirc_id=0x2,ct_state=est|trk,ct_mark=0x1e240,eth,ip,tun_src=0.0.0.0,tun_dst=10.39.129.11,tun_ipv6_src=::,tun_ipv6_dst=::,tun_gbp_id=0,tun_gbp_flags=0,tun_tos=0,tun_ttl=0,tun_flags=0,in_port=6,vlan_tci=0x0000,dl_src=56:03:b3:97:ac:c8,dl_dst=4a:72:d2:56:78:d1,nw_src=10.36.96.36,nw_dst=10.36.96.37,nw_proto=0,nw_tos=0,nw_ecn=0,nw_ttl=0
270+
Megaflow: recirc_id=0x2,ct_state=+est+trk,ct_mark=0x1e240,eth,ip,tun_id=0,tun_dst=0.0.0.0,in_port=6,dl_src=56:03:b3:97:ac:c8,dl_dst=4a:72:d2:56:78:d1,nw_src=10.36.96.36,nw_dst=10.36.96.37,nw_ecn=0,nw_frag=no
271+
Datapath actions: drop`,
272+
datapathActions: NewDataPathActions("drop"),
273+
flowActions: []string{
274+
"resubmit(,25)",
275+
"push_vlan:0x8100",
276+
"set_field:4118->vlan_vid",
277+
"resubmit(,25)",
278+
"pop_vlan",
279+
"resubmit(,28)",
280+
"ct(table=30)",
281+
"drop",
282+
"recirc",
283+
"resubmit(,35)",
284+
"resubmit(,45)",
285+
"resubmit(,50)",
286+
"resubmit(,60)",
287+
"resubmit(,62)",
288+
"resubmit(,67)",
289+
"set_field:0x2->tun_id",
290+
"resubmit(,68)",
291+
"set_field:10.39.129.11->tun_dst",
292+
"output:9",
293+
},
294+
},
129295
}
130296

131297
for _, testcase := range testcases {

0 commit comments

Comments
 (0)