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.