OF.CPP: Consistent Packet Processing for OpenFlow Peter Perešíni† ∗ , Maciej Kuźniar† ∗ , Nedeljko Vasić† , Marco Canini♯ , and Dejan Kostić‡ † † ♯ EPFL TU Berlin / T-Labs <name.surname>@epfl.ch ∗ ♯ Institute IMDEA Networks [email protected] ‡ [email protected] These authors contributed equally to this work ABSTRACT This paper demonstrates a new class of bugs that is likely to occur in enterprise OpenFlow deployments. In particular, step-by-step, reactive establishment of paths can cause network-wide inconsistencies or performance- and spacerelated inefficiencies. The cause for this behavior is inconsistent packet processing: as the packets travel through the network they do not encounter consistent state at the OpenFlow controller. To mitigate this problem, we propose to use transactional semantics at the controller to achieve consistent packet processing. We detail the challenges in achieving this goal (including the inability to directly apply database techniques), as well as a potentially promising approach. In particular, we envision the use of multi-commit transactions that could provide the necessary serialization and isolation properties without excessively reducing network performance. Categories and Subject Descriptors C.2.4 [Distributed Systems]: Network operating systems Keywords Software-Defined Networking, Consistency, Multi-commit transactions, ACID 1. ‡ INTRODUCTION Software-defined networking (SDN), and OpenFlow in particular, is increasingly being deployed. Despite having access to a logically centralized view of the network, when using any of the popular controller frameworks (e.g., NOX, POX, FloodLight) the network programmer uses the lowlevel OpenFlow interface. Thus, she still has to deal with the asynchronous, distributed nature of the underlying network. In this paper, we demonstrate a new class of bugs that can cause network-wide inconsistencies or performance- and Permission to make digital or hard copies of all or part of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. Copyrights for components of this work owned by others than ACM must be honored. Abstracting with credit is permitted. To copy otherwise, or republish, to post on servers or to redistribute to lists, requires prior specific permission and/or a fee. Request permissions from [email protected] HotSDN’13, August 16, 2013, Hong Kong, China. Copyright 2013 ACM 978-1-4503-2178-5/13/08 ...$15.00. space-related inefficiencies. In the first example, the controller that installs rules on a switch-by-switch basis can create a forwarding loop after an event that alters the topology. In the second example, a controller running the learning switch application floods a packet toward the (unknown) destination host, and learns the destination’s location when this responds to one of the flooded packets. The remains of the flood can then cause additional, unnecessary rules to be installed in the network, along which a packet storm might occur.1 These bugs can have serious consequences. Specifically, once created, the forwarding loop (first bug) stays until the rules expire. For the second example, when the rule amplification problem manifests itself, on average 30% more rules get installed in our experiments. The excess rules can decrease the network performance as a whole due to limited TCAM space in the switches, and sometimes limited intraswitch bandwidth for new rules . The underlying cause for the problem is inconsistent packet processing; as the packet(s) traverse the network they do not encounter consistent state at the controller (after being forwarded by switches with no rules for them). In the learning switch for example, it is the response from the host that changes the state in the controller in a way that negatively affects the remaining flooded packets. Although this problem may not manifest in environments that are running a proactive controller, it is nevertheless important. First, it is likely to occur in enterprise OpenFlow deployments where there is less of a chance of having a thoroughly maintained host map. Second, supporting zero-configuration requires a reactive controller. Third, administrators often insist on having control over the flows, to implement e.g.traffic redirection through middleboxes. The existence of hybrid networks and/or multiple controllers can also prevent installation of rules on entire paths. Finally, some controllers have to retain the ability to act in a reactive manner because there might not be enough rule space (either wildcarded or exact match). Recent work in this space addresses some issues with the low-level OpenFlow API. For example, Monsanto et al.  propose programming language techniques that raise the abstractions for managing SDNs and Reitblatt et al.  propose general mechanisms for managing network updates. However, they do not solve the problem described here. Specifically, consistent updates  deal only with the state 1 We refer to this problem as the “rule and packet amplification” problem (rule amplification for short). in the switches, whereas the inconsistent packet processing problem has to do with the state at the controller itself. To mitigate this problem, we propose to use transactional semantics at the controller to achieve consistent packet processing. However, we do not advocate a straightforward application of the classic ACID (Atomicity, Consistency, Isolation and Durability) semantics. Numerous issues discourage its use, including: (i) lack of clear specification of the events pertaining to a single transaction, (ii) difficulty in knowing when to commit (e.g., tracking when the last packet exits the network is difficult and costly), and (iii) difficulty of rolling back network state and packets already sent from the network’s edge. To achieve the desired isolation properties in the domain, we envision the use of multi-commit transactions. The main idea is to allow each event (e.g., PacketIn processing at the controller) to run as a subtransaction and commit, while a (high-level) transaction continues to exist. We carefully examine the possible orderings of state accesses and updates caused by concurrent subtransactions, and specify when they are allowed to commit. The (high-level) transaction still consists of all events related to a particular packet. The rule amplification problem for example would be avoided, as all packets in a flood interact with the controller in isolation, as if there are no other flows in the network. The isolation properties provided by multi-commit transactions are not complete. In particular, newer transactions see the effect of committed subtransactions of older transactions, while the older transactions do not see commits of new transactions. This transactional semantics avoids the difficult issues of: (i) rolling back an excessive amount of network state on aborts, and (ii) knowing when to commit a (large) transaction. As a result, our expectation is that network performance will be the same or marginally reduced. Note that our proposed semantics is different from nested transactions . The contributions of this paper are as follows: 1. We identify a new type of bugs and their root cause— inconsistent packet processing. (§2) 2. We examine the possibility of using traditional database semantics for SDN packet processing. (§3) 3. We propose the multi-commit transactional model, a first step toward addressing the problem. (§4) 2. INCONSISTENT PACKET PROCESSING In this section, we describe a new class of controller bugs we tentatively call “inconsistent packet processing” bugs. We first illustrate the problem on two examples. Loop installation bug. Consider an OpenFlow controller that installs a lowest latency path between the source and destination on a switch-by-switch basis. The straightforward implementation of such a controller contains a potentially harmful race bug: if the topology changes in the middle of path installation, the controller will install the rest of the path according to the new routing. This can potentially lead to a forwarding loop. Assume host h1 sends a packet to destination host h2 in the topology of Figure 1, with the s1 − s3 link initially being down. Then, the following sequence of events will result in a forwarding loop in the network: 5. ctrl 4. s2 6. 1. h1 2. s1 10ms 100ms 3. (link up) 10ms, initially down s3 h2 Figure 1: A loop formed by a buggy controller that installs paths on a switch-by-switch basis. 1. h1 sends a packet to switch s1, which sends it to the controller (because it has no rule for it); 2. the controller installs rule s1 → s2 (only way to reach h2) and sends the packet along the path; 3. meanwhile, the low-latency link s1 − s3 comes up; 4. s2 sends the packet to the controller (no rule); 5. the controller installs rule s2 → s1 (the shortest path is now s2 → s1 → s3) and sends the packet; 6. rule s2 → s1 is installed, thus creating a forwarding loop between s1 and s2; Note that in a real deployment OpenFlow switches will refuse to forward packets back to their ingress port and simply drop them, thus creating a black hole instead of a loop. One may wonder if this simple check (either in the controller or in the switch) would prevent loops. Unfortunately, a slightly extended version of the example will create loops even if such checks are present as shown in the technical report . “Rule amplification” bug. The second example involves l2_multi (a component of the popular POX platform), a multi-switch version of the learning switch that maintains a mapping host 7→ (switch, port) for every switch in the network. The controller code is involved in locating a destination host when a packet is directed to it. Specifically, when the host location hdst is unknown, the controller instructs the first switch that encounters a packet for hdst to flood the packet along a spanning tree. As flooded packets reach the adjacent switches, the controller instructs them to continue flooding the packet. Once host hdst is reached and responds with a packet of its own, the controller sees the reply packet and learns hdst ’s location. However, the remains of the flood could still be traversing the network and when these duplicate packets reach OpenFlow switches, they are once again sent to the controller. Because the destination hdst is now known, the controller routes these duplicate packets towards hdst . Depending on the number of duplicates (which reflects network size) and topology, this may potentially generate a packet storm.2 Moreover, the controller installs the (unnecessary) additional rules in the switches. Combined, these two effects ultimately hurt the performance of other flows in the network, as well as the overall network scalability for two reasons. First, additional rules occupy precious TCAM space. Second, the intra-switch bandwidth for rule installation can be surprisingly small . This phenomenon manifests itself anytime the controller does not know the 2 Similarly to the previous example, the packet storm might be mitigated by OpenFlow switches. If the shortest path from flooded packet to hdst points back to the link the packet came from, the OpenFlow switch will drop the packet unless OFPP_IN_PORT is used instead of a port number. end-host’s location, such as during controller startup, host mobility or periodic timeouts. Generalizing the bugs Both of our examples contain a race condition. If we examine it more closely, the root cause of the problem is what we term inconsistent packet processing: i.e., the change of controller state while packets traverse the network. More precisely, both mentioned controllers were written with the assumption that the subsequent PacketIn handler executions for the same packet will be handled with a state that was left from the last time a packet or its duplicate was seen Unfortunately, this assumption does not hold. More importantly, designing controller programs which do not require this assumption is hard. In the lowest path delay example, after receiving a topology change event, the controller would need to invalidate not only the inaccessible paths, but also all partially-installed paths. Similarly, POX’s l2_multi module would need to distinguish between copies of the flooded packet, and either drop or flood them. These bugs can have severe consequences. Once created, the forwarding loop caused by the first bug stays in place until the rules expire. When the rule amplification problem manifests itself, on average 30% more rules get installed in our experiments (Section 6). 3. ACID TASTE OF CHALLENGES of the network, especially the packets sent from the network edge, is infeasible. The cost of enforcing ACID. The biggest problem in trying to provide the ACID semantics is the fact that the network cannot undo the packets that have been delivered to end-hosts. Thus, providing atomicity and isolation comes at a high cost: buffering every change to the controller and network state including the switch rules being installed, and the packets associated with the transaction. The location where this additional data would have to be stored is orthogonal to our discussion. A further problem is that buffering PacketOut commands (which instruct switches to emit packets) causes additional issues: if a packet was eventually destined to reach a switch without any matching rule, we will not receive the consequent PacketIn event, rather, we need to emulate it. When it comes to dealing with packets destined for end-hosts, there are two possible approaches for providing full ACID semantics: (i) have a shadow (emulated) copy of the network and controller for each transaction; when the transaction commits, copy the updated state back to the original controller (including packets sent over the edge links of the shadow network), (ii) use the real network, but have the ability to intercept packets at the edge so they can be dropped if a transaction aborts. The first approach is not only expensive but increases the “inconsistency surface” as the state of the network now needs to be synchronized with the state of the shadow network.4 The second solution requires nontrivial capability at the edge links and introduces additional latency as packets would need to be delayed until the transaction commits. The cause of inconsistent packet processing is the absence of atomic processing of events related to the same packet. In both examples, the controller logic assumed isolation from unrelated events while the chain of related PacketIns was processed. These findings suggest examining the classic ACID transactional model. Defining transactions and their semantic. Intuitively, a transaction is a set of related events which are to be processed in isolation from other events. However, unlike traditional databases, in SDN there is no explicit notion of which controller operations are related and should be processed as a transaction. Requiring the programmer to specify which events belong to which transaction would be hard because the events in the network are asynchronous in nature and can cause events to occur at the controller at different points in time. Rather, we strive for a solution in which the controller framework knows which events it should put together as a transaction on behalf of the programmer. In the rest of the paper, we will concentrate only on PacketIn events.3 We state that a set of PacketIn events forms a transaction (i.e., the events are related) iff they are all events caused by a single packet and/or its duplicates. Work in that direction is however orthogonal to this paper. Commit! But when? We do not require the programmer to explicitly specify the end of transactions. Instead, they end implicitly when the last packet associated with the transaction leaves the network. Thus, we now face the issue of identifying such events. Rolling back transactions. Assuming we know when the transaction ended and should be committed, we need to take care of potential aborts, and rollback the modified state as if the transaction never happened. Note that state comprises not only the state of the controller, but also the state of the network. Unfortunately, rolling back the state To overcome the challenges of introducing the transactional model to SDN, we propose multi-commit transactions. The main idea is to relax the atomicity requirement. Instead of treating transactions as a single block of operations (which is either executed or aborted as a whole), we leverage the already present division of operations into different events. This split provides a sweet spot between proper atomicity and no atomicity at all. In essence, we treat every transaction as a set of subtransactions, with each subtransaction corresponding to one event. The commit/abort question is decided for each subtransaction separately and does not affect the previous decisions. If a subtransaction aborts, the state of the transaction rolls back to the state after the last successful (subtransaction) commit. Subsequent subtransactions will continue from this state and will be able to commit (unless another conflict occurs). This model fits SDN because it solves both important challenges: (i) how to buffer the transaction data so that we can rollback easily, and (ii) deciding when to commit. We simply buffer the current event’s actions until the end of the event handler and then commit. This is in contrast with a full ACID solution where we would need to buffer all actions and packets of all the event handlers called during the whole length of the transaction. Moreover, with multicommit transactions there is no need to know when the transaction ends because we commit after each event. After an appropriate timeout, we may however garbage-collect the transaction metadata as there will be no subsequent sub- 3 The controller processing a packet for which a switch did not have a forwarding rule. 4 For example, consider a rule expiring in the real network, while being still active in the shadow network. 4. MULTI-COMMIT TRANSACTIONS transactions. In the rest of this section, we describe how we can provide good consistency properties and what the exact semantics of our model is. Dependencies between transactions. The main reason for relaxing atomicity was to avoid expensive packet buffering. This leads us to a new problem—any packet leaving the network may potentially cause a response, i.e., traffic that is causally related to that packet. A response packet will start a second transaction that should be processed in isolation. Isolation however breaks the dependency relationship (causality) and may result in conflicts between transactions. We illustrate this isolation problem using the same l2_multi controller from section 2. This time, we assume it uses transactions, i.e., the controller will process (flood) clones of the original packet in isolation from other packets. The first packet from h1 to h2 starts transaction T1 which learns h1 and floods the packet and its duplicates throughout the network. When host h2 responds, the reply packet creates a new transaction T2 . Now consider that the original packet was still being flooded when the reply packet is observed, i.e., T1 did not finish. Assuming perfect isolation of controller state between T1 and T2 , T2 learns about h2 ’s location but does not know about h1 and thus floods. Although flooding in both directions is harmless, at commit time we will need to decide the state of both transactions. Should we commit both of them even if they are in a read-write conflict T1 = [read(h1 ), write(h2 )] versus T2 = [read(h2 ), write(h1 )]? In this case, committing both transactions does not create an inconsistency. However, ignoring read-write conflicts or employing an eventual consistency model in general does not work for all controllers.5 To preserve dependencies, we need to take care of the state that other (possibly concurrent) transactions see after each subtransaction of Ti commits. Because a commit of subtransaction of transaction Ti might be accompanied by packets sent to the edge of the network, any transaction Tj starting after this commit must be able to see this commit, even if Ti and Tj continue concurrently.6 On the other hand, old transactions should not be able to see modifications made by the newer transactions (i.e., to ensure the isolation property). In summary, let T1 and T2 be transactions, with T1 starting before T2 . Then, we would like the following behavior (x is a part of the controller state): • T1 and T2 both read x: Ok. • T2 reads x after T1 writes x and commits: T2 will see the write (Dependency) • T1 reads x after T2 writes x and commits: T1 will not see the write (Isolation). Guaranteeing consistency. To avoid inconsistencies in the controller, we need to provide serializability for the transactions.7 In particular, this means that all commits (subtransactions) of a transaction must be ordered together. When coupled with the dependency criterion, this requirement forces the only possible serial order T1,1 , T1,2 , . . . , T1,n1 , T2,1 , T2,2 , . . . , T2,n2 , T3,1 , . . . where Ti,j represents the j − th subtransaction of transaction i and 5 We include the example in the companion technical report . 6 This violates the traditional isolation property but it is required in our weak atomicity model. 7 We assume that a controller that is processing transactions serially is correct as it will avoid race conditions. [r(x), w(x,2), commit] Figure 2: Illustration of the interaction between transactions. T1 ’s write conflicts with already committed T2 ’s read. T1 must be aborted otherwise it could create an inconsistency. transactions are ordered by their starting time. This step also finalizes our “behavior table” by adding the following points: • T2 writes x after T1 reads x and commits: T2 should be able to commit. • T1 writes x after T2 reads x and commits: This is a read-write conflict and one of the subtransactions needs to abort. As T2 is already committed, T1 needs to abort Committing/aborting. All reads and writes in a transaction are logged. At commit time, the transaction Ti is able to commit either if (i) all pending operations are reads, or (ii) there is no read-write conflict in the controller. Although a write-write conflict of subtransactions does not necessarily need to be aborted, a write-write conflict spanning several subtransactions might cause inconsistencies. In our current model, we simply convert all write-write conflicts to readwrite conflicts by requiring transactions to first read variables that are being written. In the case a subtransaction aborts, we need to rollback its state. Fortunately, aborting a subtransaction is easy— because a subtransaction consists only of a single event handler, we can simply buffer all the controller state changes and network commands till the end of the handler. When the subtransaction commits, the buffers are flushed and in case of abort the buffers are cleared. The possibility of some subtransactions aborting might lead to a concern that this model can cause additional programmer effort and/or that the transaction itself might be left in an inconsistent state. Here, we simply argue that aborting a PacketIn event is equivalent to not receiving that event in the first place. So, from the standpoint of the controller, an aborted subtransaction is equivalent to a lost packet—a situation that the controller should anyway be able to handle. Moreover, subtransaction aborts are rarely necessary. Specifically, all read-only transactions are abort-free. Additionally, assuming a sequential execution of event handlers, all transactions that write only in the first subtransaction are also abort-free. We argue that most of the controllers are performing most of their writes in the first subtransaction and then they just read. Even if they continue writing state, it is usually just rewriting the state with the same values (e.g., re-learning MAC mapping), which is efficiently equivalent to reads. Finally, a careful reader might have noticed that in our case the aborted subtransaction always belongs to an older transaction. This might potentially lead to starvation. So 5. PROTOTYPE We have built an early prototype of OF.CPP for the POX controller platform written in Python. The goal here is to verify the feasibility of consistent packet processing. Though, the prototype is not optimized in any way. We leave the performance evaluation and any optimizations (especially in the transactional engine) for future work. Currently, the OF.CPP prototype does not buffer network operations, i.e., it does not provide full event rollback functionality. This is mainly because the controllers we tested do not cause abort operations (the write happens only in the first event/step). Moreover, the prototype does not garbage collect transactions. Usage. Using OF.CPP is simple, and we demonstrate this by providing the new transactional semantics in a drop-in replacement of the Python’s dictionary. The user only needs to replace the controller’s global state dictionaries with this new class. To relieve the programmer from specifying the transactions boundaries, we wrap the PacketIn handler with our own code. Transactional dictionary. The main part of the prototype is TransactionalDict, the multi-commit transaction dictionary (key-value store). The dictionary API is consistent with the Python’s API except for several new methods that are not visible to the programmer: • dict.newTransaction(): creates a new transaction and returns its id. • dict.checkout(id): selects the currently active transaction. All read/write operations from now on will belong to this transaction. • dict.commit(): tries to commit all pending changes of the current transaction. If there is a conflict, all pending changes are reverted and the transaction aborts (an exception is thrown). The standard dict[key] read/write dictionary access operations are logged and appended to the pending list of the transaction. Identifying transactions. To identify transactions, we store a “transaction tag” inside the packet header. Our current prototype uses the VLAN tag but it is easy to replace it with any other usable header field. Upon receiving an untagged packet, we create a new transaction and invoke the checkout operation. Upon receiving the tagged packet, we checkout the transaction with the id from the tag. Although we would need to intercept all commands to the switches that are related to the transaction, our prototype currently intercepts only the PacketOut commands. # rules in switches far we do not have evidence of this behavior but we need to confirm this using experiments with more controllers. Being optimistic. In addition to allowing our transactions to commit several times, we also require each subtransaction to be nonblocking. In particular, this requirement stems from the fact that the transaction cannot block because of other transactions (e.g., long-lived ones). Rather than rely on a locking mechanism, we advocate to use a form of optimistic concurrency control . Optimistic concurrency control is especially useful in low-contention scenarios and we expect controllers to be such. Moreover, it ensures that read-only (sub)transactions will never fail. 50 45 40 35 30 25 20 15 10 5 0 standard l2_multi transactional l2_multi 0 20 40 60 Time (s) 80 100 120 Figure 3: The number of rules installed by the standard and transactional versions of POX l2_multi in multiple trace replays in Mininet. 6. EVALUATION In this section, we examine: (i) the magnitude of the problem caused by the amplification bug in the POX l2_multi controller, and (ii) the applicability of our OF.CPP prototype to solve the problem. We set up an experiment in a Mininet  testbed with a Fat-Tree topology containing 20 switches and 16 end-hosts. Because the Fat-Tree topology contains cycles, we enable the POX spanning-tree module, which disables some links for flooding. This however affects only packets forwarded to the special “flood” (OFPP_FLOOD) port and leaves normal traffic unaffected. The experiment consists of a replay of a short trace with seven flows, emulating cold start of the controller. We modify the controller to issue non-expiring rules and record the total number of rules installed in the switches during the experiment. We compare this number against the one obtained by a transactional version of l2_multi running in our prototype, which avoids the rule amplification. The results in Figure 3 indicate that the rule amplification bug manifests itself across all experiments, albeit with varying intensity. On average, there are 30% more rules installed. Finally, the figure shows that OF.CPP behaves consistently. We manually confirmed that OF.CPP installs only the expected rules. 7. DISCUSSION AND FUTURE WORK We view this paper as a promising start to introducing a new transactional model into OpenFlow controllers. As the number of OpenFlow programmers increases in enterprise environments, we believe that OF.CPP will be effective in eradicating the difficult-to-diagnose kind of bugs we demonstrated in this paper. Next, we examine some potential questions: How general is OF.CPP? To further demonstrate OF.CPP’s generality, we plan to showcase its seamless integration into a few popular controllers. Doing so will require analyzing the data structures used by these controllers and developing their transactional counterparts. What about other type of events? There are other events besides PacketIns that might be important for the controller. As an example, consider link up/down events. Handling link up events in OF.CPP is easy as we can isolate the event from the old transactions. Link down events can be treated similarly if they do not conflict with concurrent transactions. However, if a link down event affects rules touched by a transaction, the transaction needs to abort. In this case, OF.CPP will need to inform the controller about the abort explicitly and the controller will then need to clean-up the state of previous sub-transactions similarly as in compensating transactions . Although this scenario requires special controller recovery logic, we still believe that OF.CPP might have a value by helping to separate related events into transactions and identifying conflicts. Is network performance going to suffer? We will run detailed micro-benchmarks, especially to examine the effect of each planned optimization. We will also quantify the network-wide performance. That said, we anticipate marginal, if any, performance impact. For example, although the events will effectively be serialized, the “database” is having only one client, namely the controller. Multithreaded controllers will start to get deployed, and we will address the important issue of incorporating multithreading in our future work. 8. RELATED WORK The most related work to ours is consistent updates . The important difference is that while consistent updates are solving the network-consistency problem, OF.CPP solves consistency issues inside the controller. NICE  exercises different network event orderings, while NDB  collects packet traces. Although both tools can uncover consistency problems (NICE in a model, NDB in production), they can only report the existence of a bug and do not fix it. Header Space Analysis  and VeriFlow  check the data-plane. These tools can verify the current network configuration and thus can detect the controller inconsistency only through the data plane. Frenetic  programs are still written on a switch-by-switch basis. Pyretic  maps high-level policies to low-level rules. While this may protect high-level programmers from consistency issues, developers of the runtime system would need to deal with these issues. Our idea of multi-commit transactions is based on ideas from optimistic concurrency control  and nested transactions . Unlike nested transactions, multi-commit transactions do not preserve atomicity and cannot be fully rolled back. Instead, by relaxing atomicity, multi-commit transactions can span long periods of time without blocking. To ensure consistency, we employ ideas from causal consistency  and serializability . 9. CONCLUSION In this paper, we have described a new class of problems affecting SDN that stem from the lack of isolated packet processing in OpenFlow controllers. We have detailed two examples of this problem, that decrease network reliability and performance, respectively. After discussing the inability of directly applying the classic database techniques to mitigate this problem, we have proposed a new semantics that we refer to as multi-commit transactions. Finally, we have demonstrated the ease of incorporating this new model into POX, a popular OpenFlow controller. Acknowledgments The research leading to these results has received funding from the European Research Council under the European Union’s Seventh Framework Programme (FP7/2007-2013) / ERC grant agreement 259110. 10. REFERENCES  M. Ahamad, G. Neiger, J. E. Burns, P. Kohli, and P. Hutto. Causal memory: Definitions, implementation and programming, 1994.  P. A. Bernstein, V. Hadzilacos, and N. Goodman. Concurrency control and recovery in database systems. Addison-Wesley Longman, 1987.  M. Canini, D. Venzano, P. Perešı́ni, D. Kostić, and J. Rexford. A NICE Way to Test OpenFlow Applications. In NSDI, 2012.  A. R. Curtis, J. C. Mogul, J. Tourrilhes, P. Yalagandula, P. Sharma, and S. Banerjee. Devoflow: scaling flow management for high-performance networks. In SIGCOMM, 2011.  N. Foster, R. Harrison, M. J. Freedman, C. Monsanto, J. Rexford, A. Story, and D. Walker. Frenetic: a network programming language. In ICFP, 2011.  N. Handigol, B. Heller, V. Jeyakumar, B. Lantz, and N. McKeown. Reproducible network experiments using container-based emulation. In CoNEXT, 2012.  N. Handigol, B. Heller, V. Jeyakumar, D. Mazières, and N. McKeown. Where is the debugger for my software-defined network? In HotSDN, 2012.  P. Kazemian, G. Varghese, and N. McKeown. Header Space Analysis: Static Checking for Networks. In NSDI, 2012.  A. Khurshid, X. Zou, W. Zhou, M. Caesar, and P. B. Godfrey. Veriflow: Verifying network-wide invariants in real time. In NSDI, 2013.  H. F. Korth, E. levy, and A. Silberschatz. A formal approach to recovery by compensating transactions. In 16th International Conference on Very Large Databases, 1990.  H. T. Kung and J. T. Robinson. On optimistic methods for concurrency control. ACM Trans. Database Syst., 6(2):213–226, June 1981.  C. Monsanto, J. Reich, N. Foster, J. Rexford, and D. Walker. Composing Software Defined Networks. In NSDI, 2013.  P. Peresini, M. Kuzniar, N. Vasic, M. Canini, and D. Kostic. OF.CPP: Consistent Packet Processing for OpenFlow. Technical report, EPFL, 2013.  M. Reitblatt, N. Foster, J. Rexford, C. Schlesinger, and D. Walker. Abstractions for Network Update. In SIGCOMM, 2012.  R. F. Resende and A. El Abbadi. On the serializability theorem for nested transactions. Inf. Process. Lett., 50(4):177–183, May 1994.
* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project