Frenetic: A Programming Language for SDNs (text)
by
Sasha Shkrebets
—
last modified
Mar 20, 2023 08:05 AM
We are continuing our discussion of programming SDNs, and we
are now starting a lesson on programming languages for SDNs.
Welcome back.
We are continuing our discussion of programming SDNs, and we
are now starting a lesson on programming languages for SDNs.
We will start with looking at how to use
a programming language for an SDN to read network state.
In subsequent lessons, we'll talk about how to use
the state that we read from the network to write
and compose policies, and we'll also later talk about how
to change network policies as network events and conditions change.
Let's remind ourselves about the challenges
involved in programming SDNs in today's environment.
Today's software defined networks provide a controller platform that
offers network-wide visibility and control, as well as a channel to
control switch behavior via a direct open interface, such as OpenFlow.
However, these southbound APIs, like OpenFlow, are still relatively tied to
the underlying hardware, thus making it
difficult to develop controller applications at
the right levels of abstraction that are independent from the underlying
switch hardware and the protocols that are used to control the switches.
Developing better abstractions for SDN programming involves recognizing that
this programming involves a control loop that effectively has three steps.
So, if we have a network of OpenFlow switches and a controller, the first step
is that that controller must be able to read and monitor the network state.
It must also be able to monitor for events that change the network conditions,
such as changes in traffic load, security events, and so forth.
Based on that state and the events
that the controller receives, it can compute forwarding
behavior for the switches in the network
based on policies that the network operator specifies.
It's possible, of course, that the controller may have multiple
policies that either conflict or otherwise need to be composed.
We will talk about policy composition in the next lesson.
After computing the policy, the controller must
write that policy back to the OpenFlow switches.
In this lesson, we will focus on aspects of SDN
programming that allow a controller to read and monitor state.
In a later lesson, we will talk about how to extend
the control framework so that it can also process and handle events.
Reading network state typically involves processing multiple rules.
Let's suppose that we have traffic counters, where each
rule counts bytes and packets associated with a particular flow.
The controller can, of course, poll those counters, but we might have a situation
that requires the operator to express a policy in terms of multiple rules.
For example, the operator might wish to monitor web server traffic, except
for traffic coming from the host with source IP address 1.2.3.4.
Doing so involves writing two rules.
One for monitoring the port 80 traffic and the other for expressing the exception.
When we have multiple rules, we need a way of expressing
priorities so that rule number one takes precedence over rule number two.
The solution to this problem is to introduce predicates.
For example, we can introduce a predicate that refers to flows for which
the source IP address is not 1.2.3.4 and the source port is 80.
Of course, we need a run-time system that also translates
these predicates into the appropriate switch patterns and flow table entries.
Another challenge in reading network state is that there are a
limited number of rules that can be installed in the switch.
Switches have limited space for rules, and we cannot install all possible patterns.
For example, suppose we would like to monitor traffic in a way that allows
us to produce a histogram of traffic volumes, according to IP address.
It clearly does not make sense to create a rule for every IP address,
so what we'd like to have happen is, for the switch to create
rules as new packets arrive from distinct source IP addresses.
The solution to this problem is a primitive called
dynamic unfolding, whereby the programmer can specify a GroupBy predicate,
and the run-time system will dynamically add the rules to
the switch as packets from distinct source IP addresses arrive.
Another challenge with reading network state is the common
OpenFlow programming idiom, whereby if a switch does not know
how to handle a packet with the existing flow
table entries, it will send that packet to the controller.
The controller subsequently installs rules in the switch, according to that policy.
But what happens when more packets arrive at
the controller before the switch has the rule installed?
Subsequent packets may reach the controller, and the controller
must figure out what to do with those packets
after it has handled the first packet, but before
the flow table entries are installed at the switch.
The solution to this problem is to
suppress extra events, allowing the programmer to specify
a limit, whereby the run-time system prevents the
controller from seeing these extra packet arrival events.
These primitives have been put together in a programming language
for SDNs called Frenetic, which is a SQL-like query language
that allows the network programmer to read network state.
Frenetic allows the programmer to get what they
ask for and see nothing more and nothing less.
It is a SQL-like query language that offers a familiar
abstraction and then returns a stream of packets, over which the
programmer can issue subsequent queries to get exactly the type
of information that the programmer is looking for from the network.
The Frenetic run-time is designed to minimize controller overhead.
It filters traffic using high level patterns,
it limits the number of values that
are returned, and it aggregates queries according
to the number and size of packets.
For example, this program allows the operator
to express that they are interested in the
number of bytes arriving on port 2, where the packets have a source port of 80.
The byte counters are grouped by destination MAC
address, and the counts are recorded every 60 seconds.
As another example, suppose that the controller
needs to learn the location of the host.
This might be a useful primitive in applications involving host
mobility, where devices are moving around the network and may be
sending traffic to different switchboards at different times.
In this case, the programmer would like to see packets grouped by the
source MAC address, but have those packets split when the input port changes.
Now, the programmer does not need to see every packet, but rather,
only the first packet that is sent when the input port changes.
This function can be achieved by applying the limit primitive over the query,
which indicates that the control program should only see the first packet.
Returning to our abstractions, we can see, now, how to develop programming.
We've seen one example of a programming langauge that
allows the controller to read and monitor network state.
The next step is to develop mechanisms that allow the programmer to write
control programs that compute network policy, based on that state.
Computing policy is particularly challenging because each of
these modules that observe network state can partially specify how
traffic should be handled, but those modules might conflict.
For example, you might write a routing module
that explicitly conflicts with a firewall or load-balancing module.
In the next lesson, we will talk about programming constructs that allow the
programmer to combine modules into a
complete application that seamlessly resolves these conflicts.
In summary, SDN control programs have common abstractions: reading and
monitoring network state and events, computing policy, and writing state.
In this lesson, we've taken the first step by
looking at how an SDN program can read network state.
Frenetic is a SQL-like query language that allows the programmer to control the
traffic that's seen at the controller, while monitoring network state.
Other challenges involve composing policy, responding
to events, and compiling those policies
into rules that can be efficiently
implemented in flow table entries at switches.
The remaining lessons in this module will explore how to tackle these challenges.