99
1010OPENFLOW_PROTOCOL = "OpenFlow11"
1111OVS_OFCTL_CMD = "ovs-ofctl"
12+ OVS_VSCTL_CMD = "ovs-vsctl"
1213VALID_PROTOCOLS = {"tcp" , "udp" , "icmp" , "ip" , "arp" }
1314PROTOCOLS_WITH_PORTS = {"tcp" , "udp" }
1415
1516# error codes
1617E_PARSER = 1
1718E_PARAMS = 2
18- E_BUILDER = 3
19- E_OVSCALL = 4
19+ E_OVSCALL = 3
20+ E_PORTS = 4
21+
22+ # rules names per direction
23+ TO = 0
24+ FROM = 1
25+
2026
2127def log_and_raise_error (code , desc ):
2228 _LOGGER .error (desc )
2329 raise_plugin_error (code , desc )
2430
31+
2532# All functions starting with `parse_` are helper functions compatible with the `Parser` class.
2633# Each should accept `args` as input and return either (result, None) on success,
2734# or (None, error_message) on failure.
@@ -39,7 +46,9 @@ def parse_bridge(self):
3946 log_and_raise_error (E_PARSER , "bridge parameter is missing" )
4047
4148 if not BRIDGE_REGEX .match (bridge ):
42- log_and_raise_error (E_PARSER , "'{}' is not a valid bridge name" .format (bridge ))
49+ log_and_raise_error (
50+ E_PARSER , "'{}' is not a valid bridge name" .format (bridge )
51+ )
4352
4453 return bridge
4554
@@ -50,7 +59,10 @@ def parse_mac(self):
5059 if mac_addr is None :
5160 return None
5261
53- if not MAC_REGEX .match (mac_addr ) or MAC_REGEX .match (mac_addr ).group (0 ) != mac_addr :
62+ if (
63+ not MAC_REGEX .match (mac_addr )
64+ or MAC_REGEX .match (mac_addr ).group (0 ) != mac_addr
65+ ):
5466 log_and_raise_error (E_PARSER , "'{}' is not a valid MAC" .format (mac_addr ))
5567
5668 return mac_addr
@@ -60,13 +72,15 @@ def parse_iprange(self):
6072 r"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}"
6173 r"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(/\d{1,2})?$"
6274 )
63- ip_range = self .args .get ("iprange " )
75+ ip_range = self .args .get ("ipRange " )
6476
6577 if ip_range is None :
66- log_and_raise_error (E_PARSER , "ip range parameter is missing" )
78+ log_and_raise_error (E_PARSER , "ipRange parameter is missing" )
6779
6880 if not IPRANGE_REGEX .match (ip_range ):
69- log_and_raise_error (E_PARSER , "'{}' is not a valid IP range" .format (ip_range ))
81+ log_and_raise_error (
82+ E_PARSER , "'{}' is not a valid IP range" .format (ip_range )
83+ )
7084
7185 return ip_range
7286
@@ -81,7 +95,9 @@ def parse_direction(self):
8195 has_from = "from" in dir
8296
8397 if not (has_to or has_from ):
84- log_and_raise_error (E_PARSER , "'{}' is not a valid direction" .format (direction ))
98+ log_and_raise_error (
99+ E_PARSER , "'{}' is not a valid direction" .format (direction )
100+ )
85101
86102 return (has_to , has_from )
87103
@@ -94,7 +110,9 @@ def parse_protocol(self):
94110 protocol = protocol .lower ()
95111
96112 if protocol not in VALID_PROTOCOLS :
97- log_and_raise_error (E_PARSER , "'{}' is not a supported protocol" .format (protocol ))
113+ log_and_raise_error (
114+ E_PARSER , "'{}' is not a supported protocol" .format (protocol )
115+ )
98116
99117 return protocol
100118
@@ -117,7 +135,10 @@ def parse_allow(self):
117135 log_and_raise_error (E_PARSER , "allow parameter is missing" )
118136
119137 if allow .lower () not in ["true" , "false" ]:
120- log_and_raise_error (E_PARSER , "allow parameter should be true or false, not '{}'" .format (allow ))
138+ log_and_raise_error (
139+ E_PARSER ,
140+ "allow parameter should be true or false, not '{}'" .format (allow ),
141+ )
121142
122143 if allow .lower () == "true" :
123144 return True
@@ -135,7 +156,9 @@ def parse_priority(self):
135156 raise ValueError
136157 return priority
137158 except ValueError :
138- log_and_raise_error (E_PARSER , "'{}' is not a valid priority" .format (priority ))
159+ log_and_raise_error (
160+ E_PARSER , "'{}' is not a valid priority" .format (priority )
161+ )
139162
140163 def read (self , key , parse_fn , dests = None ):
141164 # parse_fn can return a single value or a tuple of values.
@@ -147,13 +170,17 @@ def read(self, key, parse_fn, dests=None):
147170 if not isinstance (val , tuple ):
148171 log_and_raise_error (
149172 E_PARSER ,
150- "Internal error: parse {}: doesn't return a tuple while dests is set" .format (key )
173+ "Internal error: parse {}: doesn't return a tuple while dests is set" .format (
174+ key
175+ ),
151176 )
152177
153178 if len (dests ) != len (val ):
154179 log_and_raise_error (
155180 E_PARSER ,
156- "Internal error: parse {}: dests is {} while {} was expected" .format (key , len (dests ), len (val ))
181+ "Internal error: parse {}: dests is {} while {} was expected" .format (
182+ key , len (dests ), len (val )
183+ ),
157184 )
158185
159186 for d , v in zip (dests , val ):
@@ -163,6 +190,7 @@ def read(self, key, parse_fn, dests=None):
163190
164191 self .values [key ] = val
165192
193+
166194def json_error (name , desc ):
167195 return json .dumps (
168196 {
@@ -173,41 +201,102 @@ def json_error(name, desc):
173201 }
174202 )
175203
176- def build_rules_string (args ):
177- rules = []
178204
179- # tcp,dl_dst=26:bf:26:f0:4f:50,nw_src=192.168.38.0/24,tp_src=22 actions=NORMA
180- if args ["has_from" ]:
181- rule = ""
182- if "priority" in args and args ["priority" ]:
183- rule += "priority=" + args ["priority" ] + ","
184- rule += args ["protocol" ].lower ()
185- if "mac" in args and args ["mac" ]:
186- rule += ",dl_dst=" + args ["mac" ]
187- rule += ",nw_src=" + args ["iprange" ]
188- if "protocol" in args and args ["protocol" ] in PROTOCOLS_WITH_PORTS :
189- rule += ",tp_src=" + args ["port" ]
190- if "allow" in args :
191- rule += ",actions=" + ("normal" if args ["allow" ] else "drop" )
192- rules += [rule ]
193-
194- # tcp,in_port=3,dl_src=26:bf:26:f0:4f:50,nw_dst=192.168.38.0/24,tp_dst=2
195- if args ["has_to" ]:
196- rule = ""
197- if "priority" in args and args ["priority" ]:
198- rule += "priority=" + args ["priority" ] + ","
199- rule += args ["protocol" ].lower ()
200- if "mac" in args and args ["mac" ]:
201- rule += ",dl_src=" + args ["mac" ]
202- rule += ",nw_dst=" + args ["iprange" ]
203- if "protocol" in args and args ["protocol" ] in PROTOCOLS_WITH_PORTS :
204- rule += ",tp_dst=" + args ["port" ]
205- if "allow" in args :
206- rule += ",actions=" + ("normal" if args ["allow" ] else "drop" )
207- rules += [rule ]
205+ def run_vsctl_cmd (args ):
206+ vsctl_cmd = [OVS_VSCTL_CMD ] + args
207+ cmd = run_command (vsctl_cmd , check = False )
208+ if cmd ["returncode" ] != 0 :
209+ log_and_raise_error (
210+ E_OVSCALL ,
211+ "Error running ovs-vsctl command: %s: %s"
212+ % (format (vsctl_cmd ), cmd ["stderr" ]),
213+ )
214+ return cmd ["stdout" ]
215+
216+
217+ def update_args_from_ovs (args ):
218+ # get parent bridge to apply ruels to
219+ args ["parent-bridge" ] = run_vsctl_cmd (["br-to-parent" , args ["bridge" ]]).rstrip ()
208220
221+ # get ports names for our actual bridge, be it fake (vlan) or real (no vlan)
222+ ifs_in_bridge = run_vsctl_cmd (["list-ports" , args ["bridge" ]]).split ()
223+
224+ # get the list of all ports and filter them with the ports names to get all interfaces
225+ ports_j = json .loads (run_vsctl_cmd (["--format=json" , "list" , "port" ]))
226+ ports = [dict (zip (ports_j ["headings" ], row )) for row in ports_j ["data" ]]
227+ interfaces = []
228+ for port in ports :
229+ if port ["name" ] in ifs_in_bridge :
230+ interfaces .append (port ["interfaces" ])
231+ if len (interfaces ) == 0 :
232+ return
233+
234+ # get the list of all interfaces, filter with what we found previously and get their ofports number
235+ if_j = json .loads (run_vsctl_cmd (["--format=json" , "list" , "interface" ]))
236+ ifs = [dict (zip (if_j ["headings" ], row )) for row in if_j ["data" ]]
237+ ofports = []
238+ for interface in ifs :
239+ # port 65534 is the internal port for the bridge, we don't want to use it
240+ if interface ["_uuid" ] in interfaces and interface ["ofport" ] != 65534 :
241+ ofports .append (interface ["ofport" ])
242+
243+ args ["ofports" ] = ofports
244+
245+
246+ def build_rules_strings (args ):
247+ rules = []
248+ if args .get ("has_to" ):
249+ if args .get ("ofports" ):
250+ for ofport in args ["ofports" ]:
251+ rules .append (build_rule_string (TO , ofport , args ))
252+
253+ if args .get ("has_from" ):
254+ if args .get ("ofports" ):
255+ for ofport in args ["ofports" ]:
256+ rules .append (build_rule_string (FROM , ofport , args ))
209257 return rules
210258
259+
260+ def build_rule_string (direction , ofport , args ):
261+ rule = ""
262+ # To / From
263+ rule_parts = {
264+ "priority" : ("priority" , "priority" ),
265+ "protocol" : (None , None ),
266+ "ofport" : ("in_port" , "in_port" ),
267+ "mac" : ("dl_src" , "dl_dst" ),
268+ "iprange" : ("nw_dst" , "nw_src" ),
269+ "port" : ("tp_dst" , "tp_src" ),
270+ "allow" : ("actions" , "actions" ),
271+ }
272+
273+ if args .get ("priority" ):
274+ rule += "priority={}" .format (args ["priority" ]) + ","
275+ rule += args ["protocol" ]
276+ if ofport :
277+ rule += "," + rule_parts ["ofport" ][direction ] + "={}" .format (ofport )
278+ if args .get ("mac" ):
279+ rule += "," + rule_parts ["mac" ][direction ] + "={}" .format (args ["mac" ])
280+ rule += "," + rule_parts ["iprange" ][direction ] + "={}" .format (args ["iprange" ])
281+ if args .get ("port" ):
282+ rule += "," + rule_parts ["port" ][direction ] + "={}" .format (args ["port" ])
283+ if "allow" in args : # optional in case of del_rule
284+ rule += ",actions={}" .format ("normal" if args ["allow" ] else "drop" )
285+ return rule
286+
287+
288+ def run_ofctl_cmd (cmd , bridge , rule ):
289+ ofctl_cmd = [OVS_OFCTL_CMD , "-O" , OPENFLOW_PROTOCOL , cmd , bridge , rule ]
290+ cmd = run_command (ofctl_cmd , check = False )
291+ if cmd ["returncode" ] != 0 :
292+ log_and_raise_error (
293+ E_OVSCALL ,
294+ "Error running ovs-ofctl command: %s: %s"
295+ % (format (ofctl_cmd ), cmd ["stderr" ]),
296+ )
297+ _LOGGER .info ("Applied rule: {}" .format (ofctl_cmd ))
298+
299+
211300@error_wrapped
212301def add_rule (_session , args ):
213302 _LOGGER .info ("Calling add rule with args {}" .format (args ))
@@ -223,31 +312,39 @@ def add_rule(_session, args):
223312 parser .read ("allow" , parser .parse_allow )
224313 parser .read ("priority" , parser .parse_priority )
225314 except XenAPIPlugin .Failure as e :
226- log_and_raise_error (E_PARSER , "add_rule: Failed to get parameters: {}" .format (e .params [1 ]))
315+ log_and_raise_error (
316+ E_PARSER , "add_rule: Failed to get parameters: {}" .format (e .params [1 ])
317+ )
227318
228319 if parser .errors :
229- log_and_raise_error (E_PARSER , "add_rule: Failed to get parameters: {}" .format (parser .errors ))
320+ log_and_raise_error (
321+ E_PARSER , "add_rule: Failed to get parameters: {}" .format (parser .errors )
322+ )
230323
231324 rule_args = parser .values
232- _LOGGER .info ("successfully parsed: {}" .format (rule_args ))
233325
234326 # sanity check
235327 if rule_args ["protocol" ] in PROTOCOLS_WITH_PORTS and not rule_args ["port" ]:
236- log_and_raise_error (E_PARAMS , "add_rule: No port provided, tcp and udp requires one" )
328+ log_and_raise_error (
329+ E_PARAMS , "add_rule: No port provided, tcp and udp requires one"
330+ )
237331
238- ofctl_cmd = [OVS_OFCTL_CMD , "-O" , OPENFLOW_PROTOCOL , "add-flow" , rule_args ["bridge" ]]
332+ # update vlanid first, as bridge could be replaced by parent bridge
333+ update_args_from_ovs (rule_args )
334+ if rule_args ["ofports" ] is None :
335+ log_and_raise_error (
336+ E_PORTS , "No ports found for bridge: {}" .format (rule_args ["bridge" ])
337+ )
239338
240339 # We can now build the open flow rule
241- rules = build_rules_string (rule_args )
340+ rules = build_rules_strings (rule_args )
341+ _LOGGER .info ("Built rules: {}" .format (rules ))
242342
243343 if not rules :
244- log_and_raise_error (E_BUILDER , "add_rule: No rules were build" )
344+ log_and_raise_error (E_PORTS , "add_rule: No rules were build" )
245345
246346 for rule in rules :
247- cmd = run_command (ofctl_cmd + [rule ], check = False )
248- if cmd ['returncode' ] != 0 :
249- log_and_raise_error (E_OVSCALL , "Error adding rule: %s: %s" % (format (ofctl_cmd + [rule ]), cmd ['stderr' ]))
250- _LOGGER .info ("Added rule: {}" .format (ofctl_cmd + [rule ]))
347+ run_ofctl_cmd ("add-flow" , rule_args ["parent-bridge" ], rule )
251348
252349 return json .dumps (True )
253350
@@ -265,28 +362,33 @@ def del_rule(_session, args):
265362 parser .read ("iprange" , parser .parse_iprange )
266363 parser .read ("port" , parser .parse_port )
267364 except XenAPIPlugin .Failure as e :
268- log_and_raise_error (E_PARSER , "del_rule: Failed to get parameters: {}" .format (e .params [1 ]))
365+ log_and_raise_error (
366+ E_PARSER , "del_rule: Failed to get parameters: {}" .format (e .params [1 ])
367+ )
269368
270369 if parser .errors :
271- log_and_raise_error (E_PARSER , "del_rule: Failed to get parameters: {}" .format (parser .errors ))
370+ log_and_raise_error (
371+ E_PARSER , "del_rule: Failed to get parameters: {}" .format (parser .errors )
372+ )
272373
273374 rule_args = parser .values
274375 _LOGGER .info ("successfully parsed: {}" .format (rule_args ))
275376
276377 # sanity check
277378 if rule_args ["protocol" ] in PROTOCOLS_WITH_PORTS and not rule_args ["port" ]:
278- log_and_raise_error (E_PARAMS , "del_rule: No port provided, tcp and udp requires one" )
379+ log_and_raise_error (
380+ E_PARAMS , "del_rule: No port provided, tcp and udp requires one"
381+ )
279382
280- ofctl_cmd = [ OVS_OFCTL_CMD , "-O" , OPENFLOW_PROTOCOL , "del-flows" , rule_args [ "bridge" ]]
383+ update_args_from_ovs ( rule_args )
281384
282385 # We can now build the open flow rule
283- rules = build_rules_string (rule_args )
386+ rules = build_rules_strings (rule_args )
387+ _LOGGER .info ("Built rules: {}" .format (rules ))
284388
285389 for rule in rules :
286- cmd = run_command (ofctl_cmd + [rule ], check = False )
287- if cmd ['returncode' ]:
288- log_and_raise_error (E_OVSCALL , "Error deleting rule: %s: %s" % (format (ofctl_cmd + [rule ]), cmd ['stderr' ]))
289- _LOGGER .info ("Deleted rule: {}" .format (ofctl_cmd + [rule ]))
390+ run_ofctl_cmd ("del-flows" , rule_args ["parent-bridge" ], rule )
391+
290392 return json .dumps (True )
291393
292394
@@ -298,12 +400,17 @@ def dump_flows(_session, args):
298400 try :
299401 bridge = parser .parse_bridge ()
300402 except XenAPIPlugin .Failure as e :
301- log_and_raise_error (E_PARSER , "dump_flows: Failed to get parameters: {}" .format (e .params [1 ]))
403+ log_and_raise_error (
404+ E_PARSER , "dump_flows: Failed to get parameters: {}" .format (e .params [1 ])
405+ )
302406
303407 ofctl_cmd = [OVS_OFCTL_CMD , "-O" , OPENFLOW_PROTOCOL , "dump-flows" , bridge ]
304408 cmd = run_command (ofctl_cmd , check = False )
305- if cmd ['returncode' ]:
306- log_and_raise_error (E_OVSCALL , "Error dumping flows: %s: %s" % (format (ofctl_cmd ), cmd ['stderr' ]))
409+ if cmd ["returncode" ]:
410+ log_and_raise_error (
411+ E_OVSCALL ,
412+ "Error dumping flows: %s: %s" % (format (ofctl_cmd ), cmd ["stderr" ]),
413+ )
307414 return json .dumps (cmd )
308415
309416
0 commit comments