Skip to content

Commit 0a4a53a

Browse files
authored
Merge pull request #8 from digitalocean/mdl-ovs
ovs: open source package ovs
2 parents be839a1 + 42b91a6 commit 0a4a53a

30 files changed

+10473
-1
lines changed

ovs/action.go

Lines changed: 493 additions & 0 deletions
Large diffs are not rendered by default.

ovs/action_test.go

Lines changed: 558 additions & 0 deletions
Large diffs are not rendered by default.

ovs/actionparser.go

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
// Copyright 2017 DigitalOcean.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ovs
16+
17+
import (
18+
"bufio"
19+
"bytes"
20+
"fmt"
21+
"io"
22+
"net"
23+
"regexp"
24+
"strconv"
25+
"strings"
26+
)
27+
28+
// An actionParser is a parser for OVS flow actions.
29+
type actionParser struct {
30+
r *bufio.Reader
31+
s stack
32+
}
33+
34+
// newActionParser creates a new actionParser which wraps the input
35+
// io.Reader.
36+
func newActionParser(r io.Reader) *actionParser {
37+
return &actionParser{
38+
r: bufio.NewReader(r),
39+
s: make(stack, 0),
40+
}
41+
}
42+
43+
// eof is a sentinel rune for end of file.
44+
var eof = rune(0)
45+
46+
// read reads a single rune from the wrapped io.Reader. It returns eof
47+
// if no more runes are present.
48+
func (p *actionParser) read() rune {
49+
ch, _, err := p.r.ReadRune()
50+
if err != nil {
51+
return eof
52+
}
53+
return ch
54+
}
55+
56+
// Parse parses a slice of Actions using the wrapped io.Reader. The raw
57+
// action strings are also returned for inspection if needed.
58+
func (p *actionParser) Parse() ([]Action, []string, error) {
59+
var actions []Action
60+
var raw []string
61+
62+
for {
63+
a, r, err := p.parseAction()
64+
if err != nil {
65+
// No more actions remain
66+
if err == io.EOF {
67+
break
68+
}
69+
70+
return nil, nil, err
71+
}
72+
73+
actions = append(actions, a)
74+
raw = append(raw, r)
75+
}
76+
77+
return actions, raw, nil
78+
}
79+
80+
// parseAction parses a single Action and its raw text from the wrapped
81+
// io.Reader.
82+
func (p *actionParser) parseAction() (Action, string, error) {
83+
// Track runes encountered
84+
var buf bytes.Buffer
85+
86+
for {
87+
ch := p.read()
88+
89+
// If comma encountered and no open parentheses, at end of this
90+
// action string
91+
if ch == ',' && p.s.len() == 0 {
92+
break
93+
}
94+
95+
// If EOF encountered, at end of string
96+
if ch == eof {
97+
// If no items in buffer, end of this action
98+
if buf.Len() == 0 {
99+
return nil, "", io.EOF
100+
}
101+
102+
// Parse action from buffer
103+
break
104+
}
105+
106+
// Track open and closing parentheses using a stack to ensure
107+
// that they are appropriately matched
108+
switch ch {
109+
case '(':
110+
p.s.push()
111+
case ')':
112+
p.s.pop()
113+
}
114+
115+
_, _ = buf.WriteRune(ch)
116+
}
117+
118+
// Found an unmatched set of parentheses
119+
if p.s.len() > 0 {
120+
return nil, "", fmt.Errorf("invalid action: %q", buf.String())
121+
}
122+
123+
s := buf.String()
124+
act, err := parseAction(s)
125+
return act, s, err
126+
}
127+
128+
// A stack is a basic stack with elements that have no value.
129+
type stack []struct{}
130+
131+
// len returns the current length of the stack.
132+
func (s *stack) len() int {
133+
return len(*s)
134+
}
135+
136+
// push adds an element to the stack.
137+
func (s *stack) push() {
138+
*s = append(*s, struct{}{})
139+
}
140+
141+
// pop removes an element from the stack.
142+
func (s *stack) pop() {
143+
*s = (*s)[:s.len()-1]
144+
}
145+
146+
var (
147+
// resubmitRe is the regex used to match the resubmit action
148+
// with one or more of its parameters.
149+
resubmitRe = regexp.MustCompile(`resubmit\((\d*),(\d*)\)`)
150+
151+
// ctRe is the regex used to match the ct action with its
152+
// parameter list.
153+
ctRe = regexp.MustCompile(`ct\((\S+)\)`)
154+
155+
// loadRe is the regex used to match the load action
156+
// with its parameters.
157+
loadRe = regexp.MustCompile(`load:(\S+)->(\S+)`)
158+
159+
// setFieldRe is the regex used to match the set_field action
160+
// with its parameters.
161+
setFieldRe = regexp.MustCompile(`set_field:(\S+)->(\S+)`)
162+
)
163+
164+
// TODO(mdlayher): replace parsing regex with arguments parsers
165+
166+
// parseAction creates an Action function from the input string.
167+
func parseAction(s string) (Action, error) {
168+
// Simple actions which match a basic string
169+
switch strings.ToLower(s) {
170+
case actionDrop:
171+
return Drop(), nil
172+
case actionFlood:
173+
return Flood(), nil
174+
case actionInPort:
175+
return InPort(), nil
176+
case actionLocal:
177+
return Local(), nil
178+
case actionNormal:
179+
return Normal(), nil
180+
case actionStripVLAN:
181+
return StripVLAN(), nil
182+
}
183+
184+
// ActionCT, with its arguments
185+
if ss := ctRe.FindAllStringSubmatch(s, 1); len(ss) > 0 && len(ss[0]) == 2 {
186+
// Results are:
187+
// - full string
188+
// - arguments list
189+
return ConnectionTracking(ss[0][1]), nil
190+
}
191+
192+
// ActionModDataLinkDestination, with its hardware address.
193+
if strings.HasPrefix(s, patModDataLinkDestination[:len(patModDataLinkDestination)-2]) {
194+
var addr string
195+
n, err := fmt.Sscanf(s, patModDataLinkDestination, &addr)
196+
if err != nil {
197+
return nil, err
198+
}
199+
if n > 0 {
200+
mac, err := net.ParseMAC(addr)
201+
if err != nil {
202+
return nil, err
203+
}
204+
205+
return ModDataLinkDestination(mac), nil
206+
}
207+
}
208+
209+
// ActionModDataLinkSource, with its hardware address.
210+
if strings.HasPrefix(s, patModDataLinkSource[:len(patModDataLinkSource)-2]) {
211+
var addr string
212+
n, err := fmt.Sscanf(s, patModDataLinkSource, &addr)
213+
if err != nil {
214+
return nil, err
215+
}
216+
if n > 0 {
217+
mac, err := net.ParseMAC(addr)
218+
if err != nil {
219+
return nil, err
220+
}
221+
222+
return ModDataLinkSource(mac), nil
223+
}
224+
}
225+
226+
// ActionModNetworkDestination, with it hardware address
227+
if strings.HasPrefix(s, patModNetworkDestination[:len(patModNetworkDestination)-2]) {
228+
var ip string
229+
n, err := fmt.Sscanf(s, patModNetworkDestination, &ip)
230+
if err != nil {
231+
return nil, err
232+
}
233+
if n > 0 {
234+
ip4 := net.ParseIP(ip).To4()
235+
if ip4 == nil {
236+
return nil, fmt.Errorf("invalid IPv4 address: %s", ip)
237+
}
238+
239+
return ModNetworkDestination(ip4), nil
240+
}
241+
}
242+
243+
// ActionModNetworkSource, with it hardware address
244+
if strings.HasPrefix(s, patModNetworkSource[:len(patModNetworkSource)-2]) {
245+
var ip string
246+
n, err := fmt.Sscanf(s, patModNetworkSource, &ip)
247+
if err != nil {
248+
return nil, err
249+
}
250+
if n > 0 {
251+
ip4 := net.ParseIP(ip).To4()
252+
if ip4 == nil {
253+
return nil, fmt.Errorf("invalid IPv4 address: %s", ip)
254+
}
255+
256+
return ModNetworkSource(ip4), nil
257+
}
258+
}
259+
260+
// ActionModTransportDestinationPort, with its port.
261+
if strings.HasPrefix(s, patModTransportDestinationPort[:len(patModTransportDestinationPort)-2]) {
262+
var port uint16
263+
n, err := fmt.Sscanf(s, patModTransportDestinationPort, &port)
264+
if err != nil {
265+
return nil, err
266+
}
267+
if n > 0 {
268+
return ModTransportDestinationPort(port), nil
269+
}
270+
}
271+
272+
// ActionModTransportSourcePort, with its port.
273+
if strings.HasPrefix(s, patModTransportSourcePort[:len(patModTransportSourcePort)-2]) {
274+
var port uint16
275+
n, err := fmt.Sscanf(s, patModTransportSourcePort, &port)
276+
if err != nil {
277+
return nil, err
278+
}
279+
if n > 0 {
280+
return ModTransportSourcePort(port), nil
281+
}
282+
}
283+
284+
// ActionModVLANVID, with its VLAN ID
285+
if strings.HasPrefix(s, patModVLANVID[:len(patModVLANVID)-2]) {
286+
var vlan int
287+
n, err := fmt.Sscanf(s, patModVLANVID, &vlan)
288+
if err != nil {
289+
return nil, err
290+
}
291+
if n > 0 {
292+
return ModVLANVID(vlan), nil
293+
}
294+
}
295+
296+
// ActionOutput, with its port number
297+
if strings.HasPrefix(s, patOutput[:len(patOutput)-2]) {
298+
var port int
299+
n, err := fmt.Sscanf(s, patOutput, &port)
300+
if err != nil {
301+
return nil, err
302+
}
303+
if n > 0 {
304+
return Output(port), nil
305+
}
306+
}
307+
308+
// ActionResubmit, with one or both of port number and table number
309+
if ss := resubmitRe.FindAllStringSubmatch(s, 2); len(ss) > 0 && len(ss[0]) == 3 {
310+
var (
311+
port int
312+
table int
313+
314+
err error
315+
)
316+
317+
// Results are:
318+
// - full string
319+
// - port
320+
// - table
321+
if s := ss[0][1]; s != "" {
322+
port, err = strconv.Atoi(s)
323+
if err != nil {
324+
return nil, err
325+
}
326+
}
327+
if s := ss[0][2]; s != "" {
328+
table, err = strconv.Atoi(ss[0][2])
329+
if err != nil {
330+
return nil, err
331+
}
332+
}
333+
334+
return Resubmit(port, table), nil
335+
}
336+
337+
if ss := loadRe.FindAllStringSubmatch(s, 2); len(ss) > 0 && len(ss[0]) == 3 {
338+
// Results are:
339+
// - full string
340+
// - value
341+
// - field
342+
return Load(ss[0][1], ss[0][2]), nil
343+
}
344+
345+
if ss := setFieldRe.FindAllStringSubmatch(s, 2); len(ss) > 0 && len(ss[0]) == 3 {
346+
// Results are:
347+
// - full string
348+
// - value
349+
// - field
350+
return SetField(ss[0][1], ss[0][2]), nil
351+
}
352+
353+
return nil, fmt.Errorf("no action matched for %q", s)
354+
}

0 commit comments

Comments
 (0)