-
Notifications
You must be signed in to change notification settings - Fork 99
Language Basics
Conceptually, a Pyretic policy is a function that takes a packet as input and returns a set of packets. This function describes what the network switches should do with incoming packets. For example a function that takes any packet and returns the empty set when applied as the overall network policy will cause the network to drop all packets. Likewise a function that takes any packet arriving at a given location (switch and port) and returns the set of packets which are identical to the original but located respectively at the ports at that switch which lie on the network spanning tree, applied as the overall network policy will cause the network to flood all packets. However, Pyretic policies can be built from other policies using combinators such as parallel and sequential composition, so policies such as the ones above can be building blocks of the overall network policy, as opposed to only being used as the overall network policy itself. The MAC learner module provides an example of just such a policy.
As Pyretic is embedded in Python, we've chosen to implement these conceptual policy functions as instances of classes that descend from the Policy class defined in language.py. While these classes have lots of interesting implementation details (feel free to poke about the code), the member function 'eval' of a class comprises the conceptual function that takes a packet and returns a set of packets.
POLICY | SYNTAX | SEMANTICS | EXAMPLE |
match | match(f=v) | returns set containing packet if packet's field f matches value v, empty set otherwise | match(dstmac=EthAddr('00:00:00:00:00:01')) |
drop | drop | returns empty set | drop |
identity | identity | returns set containing copy of packet | identity |
modify | modify(f=v) | returns set containing copy of packet where field f is set to value v | modify(srcmac=EthAddr('00:00:00:00:00:01')) |
forward | fwd(a) | returns set containing copy of packet where outport field is set to a | fwd(1) |
flood | flood() | returns set containing one copy of packet for each port on the spanning tree | flood() |
parallel composition | A + B | returns the union of A's output and B's output | fwd(1) + fwd(2) |
sequential composition | A >> B | returns B's output where A's output is B's input | modify(dstip=IPAddr('10.0.0.2')) >> fwd(2) match(switch=1) >> flood() |
negation | ~A | returns logical negation of filter* policies | ~match(switch=1) |
Filter policies (or "filters" for short) are policies that don't change the packet - either a set containing just the packet is returned or the empty set is returned. match, drop, and identity are filters. The negation (~), conjunction (&), and disjunction (|) are only defined on filter policies - you will get a type error if you attempt to write something like "match(switch=1) & fwd(1)" or "~flood()". Negation is defined above. Conjunction (&) and disjunction (|) evaluate in exactly the same way as the sequential (>>) and parallel (+) composition operators, respectively. The only difference between
condition1 = match(dstmac=EthAddr(00:00:00:00:00:01)) & match(srcmac=EthAddr(00:00:00:00:00:02))
and
condition2 = match(dstmac=EthAddr(00:00:00:00:00:01)) >> match(srcmac=EthAddr(00:00:00:00:00:02))
is that the output of the former (condition1) is a FilterPolicy and the output of the latter (condition2) is just a Policy. condition1 and condition2 will produce identical output, but since condition2 isn't a subclass of FilterPolicy writing
~condition2
will result in a type error, while writing
~condition1
will work out just fine.
Filter policies are exactly what you want to conditionally execute code. For example, if you want to write a new policy 'split' that forwards traffic to destination IP 10.0.0.1 out port 1 and traffic to any other destination out port 2, then you can write this using the basic combinators discussed above as:
split = (match(dstip=IPAddr('10.0.0.1')) >> fwd(1)) + (~match(dstip=IPAddr('10.0.0.1')) >> fwd(2))
alternately, you can use the convenience 'if_' policy to write this more concisely as
split = if_(match(dstip=IPAddr('10.0.0.1')),fwd(1),fwd(2))
Both of these encodings are semantically identical - they mean the exact same thing and produce the exact same behavior. And in both cases 'split' is just a subclass of Policy which when eval'd on a packet will produce a set of packets.