[go: up one dir, main page]

Academia.eduAcademia.edu
Checking-in on Network Functions Zeeshan Lakhani Heather Miller Carnegie Mellon University Pittsburgh, Pennsylvania zlakhani@cs.cmu.edu Carnegie Mellon University Pittsburgh, Pennsylvania heather.miller@cs.cmu.edu arXiv:1812.11145v2 [cs.NI] 29 Jun 2019 Abstract When programming network functions, changes within a packet tend to have consequencesÐside effects which must be accounted for by network programmers or administrators via arbitrary logic and an innate understanding of dependencies. Examples of this include updating checksums when a packet’s contents has been modified or adjusting a payload length field of a IPv6 header if another header is added or updated within a packet. While static-typing captures interface specifications and how packet contents should behave, it does not enforce precise invariants around runtime dependencies like the examples above. Instead, during the design phase of network functions, programmers should be given an easier way to specify checks up front, all without having to account for and keep track of these consequences at each and every step during the development cycle. In keeping with this view, we present a unique approach for adding and generating both static checks and dynamic contracts for specifying and checking packet processing operations. We develop our technique within an existing framework called NetBricks and demonstrate how our approach simplifies and checks common dependent packet and header processing logic that other systems take for granted, all without adding much overhead during development. CCS Concepts · Networks → Programming interfaces. Keywords network functions, design-by-contract, software verification 1 Introduction Writing network functions (NFs) today is as capable as ever, with numerous frameworks and domain-specific languages 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 permissions@acm.org. ANRW ’19, July 22, 2019, Montreal, QC, Canada © 2019 Association for Computing Machinery. ACM ISBN 978-1-4503-6848-3/19/07. . . $15.00 https://doi.org/10.1145/3340301.3341131 to choose from. Some target development ease, reusable abstractions, or a familiar programming model that is in vogue within software development at large, while others stress performance guarantees or deployment at scale. Irrespective of which framework or model is used, NFs tend to be comprised of code exercising arbitrary logic and domain knowledge that only network programmers or administrators would know, or should know. Consider the payload length field of an IPv6 header, a field whose value is dependent upon the consequence of processing and manipulating the rest of the packet it’s a part of. If an extension header [6] is added to or removed from the packet, or a layer 4 protocol’s payload is modified in any way, then this payload length field must be incremented or decremented accordingly. Other łmiddleboxesž downstream in the network will apply functionality based on the value held in this fieldÐwithout calculating the length of the rest of the packetÐor just drop the packet outright if it’s wrong. Handling this effect is often taken for granted, a piece of arbitrary logic that network programmers have to remember to apply and validate at different steps in a function pipeline or at egress. For example, in the still widely used [9, 18, 24] packet processing system Click [21], a CheckIP6Header module provides validation on the payload length field via: 1 2 if(ntohs(ip->ip6_plen) > (plen - 40 )) goto bad ; While this code does do a łcheckž on the field, it hard codes the value as part the conditional check instead of using a constant or variable to better express meaning ( 40 is the fixed size of an IPv6 header). Additionally, if the check is invalid, goto bad executes a jump, leaving very little in the way of failure handling and unambiguous messaging. Besides a few per-field validations within the element file, the module does not account for related changes downstream when composed with other modules or the possibility of extension headers or variable-length fields. The functionality expressed in this snippet need not be so unwieldy, as validations should be a first-class part of programmable network architecture. In this paper, we present a novel approach that clearly describes and validates these arbitrary effects via the addition and generation of both static checks and dynamic, runtime contracts for specifying conditional dependencies in common packet processing actions. Our work makes use of three well-known programming paradigms. ANRW ’19, July 22, 2019, Montreal, QC, Canada Static Assertions and Types Our prototype is incorporated within a framework built using the Rust programming language, which emphasizes a strong, static type system with first-class polymorphism [32] (parametric and ad-hoc). Headers within a packet are explicitly-typed, e.g. Ipv4Hdr or Ipv6Hdr for instance, and contain associated types [8] that define which header(s) can precede it, e.g. an IPv6 Header relies on an Ethernet header existing before it within a packet. We leverage the type system along with the concept of static assertions [19] to provide compile-time checking for a subset of network function components, including constant checks at the call site of these functions and ensuring that packet order and definitions adhere to specification. Design by Contract We take inspiration from D’s contract system [12], whose design was inspired by contract schemes [25] where contracts are provided and run during testing, debugging, and development stagesÐthe design phaseÐbut are usually omitted for release builds in order to maximize performance. In using this style of contracts, programmers are able to code, test, and simulate NF’s around pre (ingress) and post (egress) invariants, checking which conditions must hold as packets are transformed by functions. These contracts allow developers to identify the intentional consequences of their packet processing algorithms. Code Generation Though contract programming aids in checking and asserting if specifications and dependencies between operations hold, these checks are only (recommended to be) provided during time of development or within testing environments. Additionally, we do not want developers to have to sprinkle contracts throughout their NFs or implement logic to traverse or backtrack from one header, payload, or set of bytes to another just for the sake of validation. Instead, our approach allows developers to specify a set of dependencies and conditions up front via macros [20], which in turn get translated into contracts. We develop our technique as an extension to the NetBricks framework and programming model [29], illustrating the efficacy of our approach through two examples: 1) updating the IPv6 payload length of a packet in the context of changes to an extension header; and 2) transitioning an invalid TCP request (based on an MTUÐMaximum Transmission UnitÐ threshold), into that of an ICMPv6 Packet Too Big response [5]. We evaluate our prototype by examining syntax additions, compilation times, and possible runtime overheads. In Section 6, we discuss our examples within the context of a couple real-world implementations, Onos and Facebook network code, where our approach could be beneficial. 2 Motivation Choosing between NF architectures and/or network programming languages has become a non-trivial process: What kind of programming paradigm should one choose for packet processing, e.g. functional, dataflow, or imperative? Should Zeeshan Lakhani and Heather Miller the framework support the OpenFlow protocol or be composed of its own data plane and control plane layers? What are the most important facets of the system or application: performance, usability and reconfiguration, reliability? There are many choices and abstractions to deliberate on, see sections 6 and 1, yet most only provide a subset of safety, design benefits, or degrees of freedom for which kinds of applications can be executed. We illustrate two specific challenges in defining a better way forward. The Limits of Correctness In the interest of handling correctness and ensuring network programs satisfy specification, there are several efforts which have experimented with verifying networking constructs. For example, a language like NetKat [3], based on proven semantic and type theoretic foundations, provides static checking for reachability, guarantees non-interference between programs, and supports first-class primitives for the filtering, modifying, and transmitting of packets. However, though powerful, NetKat is limited in what logic it can check for and what protocols and actions it can support, as all programs must conform to the OpenFlow flow tableÐits compilation target. While OpenFlow is used in practice, its model is limited in terms of interface, protocol, and field support, especially for newer, experimental features. Due to this coupling with OpenFlow, along with a lack for handling arbitrary logic in packet processing, NetKat does not present a generalized solution. Arbitrary Logic & Variable-length Data As described in Section 1, many network programs contain operational logic that’s only applied based on the IETF or similar specifications they conform to. Some even define their own inspiredby protocols without a formalized spec [17]. One major component of the IPv6 protocol specification that has been left unsupported by many NF frameworks is that of IPv6 extension headers. Traffic containing such a header is usually dropped in practice and considered a łthreat to the Internetž [15]. In skipping support for extension headers, packet-processing paradigms can avoid dealing with variable-length dataÐthe specs of these headers contain fields with variable-byte-sized dataÐand complex header chaining dependencies, as these headers can be stacked upon each other to no end. However, as unique applications for programmable networks that make use of these extensions are constantly being explored [7], we must provide programmatic abstractions for adhering to the conditions of these protocols while also being amenable to new, experimental ones down the line whether they’re used in industry or proposed in research. 3 Kinds of Contracts 3.1 Design by Contract The Eiffel programming language made design by contract first-class, focusing primarily on how runtime contracts can be turned on for monitoring and testing situations so that Checking-in on Network Functions developers can łsit back, and just watch their contracts be violatedž [26]. The key idea behind the approach is that elements of a software system collaborate with each other on the basis of mutual obligations and benefits, driven by dependencies and related components in the system. These contracts are usually separated into pre (input/ingress) and post conditions (output/egress), where invariants can be asserted on for incoming and outgoing data accordingly. In our system, design by contract-styled assertions help programmers articulate what the values of fields in a header should be equal to, bound by, approximate to, or how these values may have shifted during packet transformation (e.g. swapping of MAC addresses). From a processing perspective, the input precondition runs when the packet enters a NF and the postcondition runs as the packet is exiting the function. 3.2 Static Assertions Static assertions, popularized in the C, C++, and D languages, allow for compile-time assertions of statically defined expressions, e.g. constants, statics. Beyond just checking for specific values, static assertions can be used to enforce fields on struct types and check if a pointer’s underlying value is the same when coerced to another type. NF programs tend to be comprised of many constants referring to values derived from specifications. For example, the IPv6 minimum MTU value is 1280 [6], but is actually 1294 in practice when the Ethernet header is included. Our approach can check this caveat statically at the call site where the NF is definedÐnot where it’s instantiatedÐvia compile-time assertions in our prototype for constant checking. Additionally, thanks to conditional compilation (see 4.1 for more information), static assertions remain in release binaries. 3.3 Static Order-Persevering Headers With our approach implemented in NetBricks, we were given a head start toward better validation mechanics with a strong, static type system and framework for programming NFs in a map-reduce fashion. To add packet headers in NetBricks, you define a struct with the appropriate fields, as you would do in C or P4 for example. All structs must implement a trait1 containing an associated type that is defined as PreviousHdr: 1 2 3 4 impl EndOffset for Ipv6Hdr { type PreviousHdr=EthHdr ; fn offset(&self) -> usize { 40 } } When parsing a packet within an NF, the order is guaranteed by the defined PreviousHdr . Given any other order (e.g. parsing an IPv6 header after a ICMPv6 header), the type checker will throw a compile-time error. In our prototype, ANRW ’19, July 22, 2019, Montreal, QC, Canada we leverage this statically-defined order mechanism on headers (4) to ensure that incoming and outgoing packet header ordering is preserved according to encoded expectations. 4 Implementation We have developed a prototype2 that extends the NetBricks programming model via macro-based metaprogramming with very little additional syntax. Instead of having to manually incorporate or implement all of the contract methodologies described in section 3 throughout a NF code base, our contracts extension can be used gradually, i.e. on certain NFs and not others, as well as retroactively on existing NFs with just a few easy steps: (i.) import our check library into an NF module; then (ii.) identify an NF to validate, and mark it; and then (iii.) specify contracts at the beginning and end of a NF based on properties that the developer wants to uphold. Once introduced, these contracts rewrite NFs to include mechanisms for storing runtime info (used for checking outgoing packets), generating validations, assertions, logging facilities, and flag checks for conditional compilation. Initialization As seen in Figure 1, the check attribute macro (surrounded by brackets) is responsible for three steps in the contract generation process. Firstly, it identifies that the developer wants this NF to be łchecked,ž which means it can be used gradually over time. Secondly, by designating that this function has contract-checking on, we are then able to parse specific keywords, i.e. pre, post, in the figure that we want to rewrite and generate assertions from. Lastly, it performs a series of initialization operations, including turning-on specialized logging facilities and lazily instantiating a runtime hashmap that’s used to store all the headers as part of the input packet to create a mirror of the contents of the packet entering the NF. Building this map allows us to store header information for tracing, analysis, and further checks throughout the processing lifecycle, all the while producing a series of iterative steps to parse through the packet header-by-header, based on the order specified by the code. Macro Expansion The generation of code from macros ingress_check! and egress_check! occurs prior to the NF program being subjected to Rust’s type-checker, i.e. occurring in a separate compilation step. Of note, the order key in both the pre and post assertions, specified by the developer, allows us to match on the header-order within the contents of the packet itself, as all parsing of headers requires explicit type annotations in NetBricks under the hood. If the expected order does not match up on either ingress or egress checks, a compile-time error is thrown (as per 3.3). Ingress and Egress Contracts Figure 1 exhibits how contracts are extended into an NF. This example checks if an incoming TCP packet is beyond the valid MTU threshold, 1 Traits are used to define shared behavior in Rust, similar to interfaces in other languages [8]. 2 Openly accessible as a branch on Github. ANRW ’19, July 22, 2019, Montreal, QC, Canada Zeeshan Lakhani and Heather Miller #[ check (IPV6_MIN_MTU = 1280)] fn send_too_big { .pre(box pkt { ingress_check! { input: pkt, order : [EthHdr=>Ipv6Hdr=>TcpHdr<Ipv6Hdr>], checks: [( payload_len[Ipv6Hdr] , >, modes, while ensuring all static information and assertions remain in the finalized, production program binaries. IPV6_MIN_MTU )] }}) ...filter/map/group_by operations... .post(box pkt { egress_check! { input: pkt, order :[EthHdr=>Ipv6Hdr=>Icmpv6PktTooBig<...>], checks:[( checksum[Icmpv6PktTooBig] , neq, ( payload_len[Ipv6Hdr] , ==, 1240 ), Setup In our experimental setup, we ran NetBricks within an Ubuntu Docker container on a local VirtualBox VM. NetBricks uses DPDK [29] for fast packet I/O, which we have properly set up within the VM and container. We used MoonGen [10] to generate varying packet captures (pcaps) for our testing and evaluation harness. We looked at three factors in evaluating our technique for the design of NFs: (i.) additional syntax (LoCÐlines of code); (ii.) compilation-time added to our two example NFs; (iii.) and runtime overhead of ingress and egress contract generation. ( src[Ipv6Hdr] , ==, dst[Ipv6Hdr] ), 5.1 Syntax Added checksum[TcpHdr<Ipv6Hdr>] ), 5 Evaluation In this section, we evaluate the possible overheads of our approach, including profiling its runtime cost by sampling the call graph during a packet’s run through an NF. ( dst[Ipv6Hdr] , ==, src[Ipv6Hdr] ), ( .src[EthHdr] , ==, .dst[EthHdr] ), ( .dst[EthHdr] , ==, .src[EthHdr] )] }}) Figure 1: Pre and Post contracts on MTU example and, if so, then rewrites the packet into that of an ICMPv6 Packet Too Big response which gets returned to the source sender. As mentioned, the incoming and outgoing order lists reveal how the packet should be transformed throughout the main body of the function. Egress checks compare the values of fields and functions on the current, outgoing packet ( left-hand side of each check ) to values that are either literal integers or integer expressions, or functions or fields from the original, incoming packet ( right-hand side ). In this example, if it has to return to sender, this means swapping Ethernet addresses and IPv6 source and destination addresses from the original input. The checks presented here would fail or throw errors if the inner body’s logic did not account for these swap operations. 4.1 Conditional Compilation As previously mentioned, design by contract systems were devised with the intention that contracts would be applied during simulation, testing, and debugging stages of development. Our approach combines these kinds of runtime, dynamic assertions, which capture arbitrary logic and values, with static assertions and compile-time type checking. As shown in our evaluation of the runtime cost of our prototype, 5.3, runtime-checking and initialization accrue a penalty, which is manageable during NF development, not production. We leverage Rust’s compile-time feature-flags [33] to only generate dynamic, runtime contracts for debug and testing LoC run mtu-too-big: Contracts ON mtu-too-big: Contracts OFF mtu-too-big: Contracts ON mtu-too-big: Contracts OFF mtu-too-big: Contracts ON mtu-too-big: Contracts OFF lang files lines code rust rust toml toml total total 2 2 1 1 3 3 214 189 19 16 233 205 183 158 16 13 199 171 0 +28 +28 Change Table 1: Syntax additions for our MTU NF (unexpanded) Being that most of the work in our implementation is centered around the macro generation of contracts, it’s not surprising to see that our non-expanded measure of LoC (Table 1) is minimal. We import a few libraries (crates in the Rust ecosystem), including our check library, into NetBricks. The extra crates are used for logging and assertion control around error handling and operations that we can match on. Minus boilerplate, most of the additional code comes from the specifications themselves, as there is no bound on the number of possible validations that can be added. In Table 1, we choose to show LoC without expansion, to faithfully represent the experience of the network function developer. At the outset of this project, we wanted to avoid altering many of the core NetBricks APIs or its existing example NFs. With our contract generation prototype, we increased our examples’ programs and build configurations by an average of 23 lines, or less than 10 percent. 5.2 Compilation Times One of the most important factors we wanted to consider was compilation time, as we did not want programmers to pay much of a penalty while developing NFs. Table 2 Checking-in on Network Functions ANRW ’19, July 22, 2019, Montreal, QC, Canada compile times / cargo build example mean (s) stddev (s) user (s) system (s) min (s) max (s) Contracts - Off srv6-change-pkt 26.039 3.286 0.631 10.715 22.330 33.230 Contracts - On srv6-change-pkt 25.099 2.398 0.549 11.697 20.238 28.220 -0.94 -0.888 -0.082 +0.982 -2.092 -5.01 Effect Contracts - Off mtu-too-big 21.652 2.202 0.537 9.201 18.528 25.191 Contracts - On mtu-too-big 26.052 1.858 0.650 10.851 22.165 28.346 +4.4 -0.344 +0.113 +1.65 +3.637 +3.155 Effect Table 2: Compile times running łcargo buildž for extension header NF example and MTU NF example compares our two example network function cases with contract generation turned on and off. For each of these runs, the build was compiled from a warm, incremental cache and then rebuilt from that cache ten times. As shown, with our contract generation system applied, the standard deviation across all builds was less than a second overall. Further, mean time even improved in the case of our extension header example. Though there is room for optimization, these results show that our technique doesn’t negatively affect developers during development. 5.3 Runtime Cost We’ve discussed how our objective for programmers is to be able to specify their checks up front as they build out NFs and test them. Knowing all of the initialization and setup we have to concoct on behalf of the contract engine, we were aware that the runtime cost would be problematic if the NF ran in production. But, how problematic would it be? For this evaluation, we ran our invalid MTU example and sent a packet at a time through it, tracing the call graph throughout the function and sampling it. To trace and visualize the effect, we used the Flame Graph approach [14], popularized in industry. The graph is illustrated in Figure 2. As expected, our precondition routine takes up a majority of the function’s execution time. This is mainly due to creating a copy of the incoming packet and parsing each header within it. Our egress macro, for example, does much less and spans less of the execution graph. Further optimizing our implementation’s code generation and how we store and parse the packet in the evaluated program would lessen the runtime cost of our technique in practice and eventually make it possible to include some dynamic contracts for in-production use-cases. Nonetheless, as we’ve stated before, to focus our technique on the design phase of NF writing, the current version of the library compiles away most of this generated code upon production buildsÐnot slowing down the runtime. 6 Discussion and Future Work Thus far, we’ve demonstrated our technique on a few simple yet practical NF examples. In this section, we discuss how our work could benefit practical programs and applications out in the wild. Then, we explore where we want to take the approach going forward. 6.1 Real-World Example: ONOS Routing One of the cases we’ve evaluated includes adding additional segments to a IPv6 segment routing extension header [11]. In Section 2, we mentioned that IPv6 extension headers were difficult to handle due to their variability, causing network operators to write rules to drop packets that contain them and NF frameworks avoiding their logic altogether. ONOS [4], the open network operating system, is a controller platform supporting a wide variety of SDN use cases, including support for the Routing header extension. Nonetheless, the most complex logic that the header entails is that of adding and removing segments, which then triggers effects on the Last Entry (the index into the stack of segments) and Segments Left (the number of route segments remaining to be processed) fields. These triggered events provide a good story for our implementation because the ONOS class [1] does not account for changes in the Routing header stack; instead, it just works on a minimal set of fields for reading these headers along the network path. With an approach like the one we’ve exhibited in our paper, more assurance could be given for handling the complex logic of variable-length information. 6.2 Real World Example: Katran Katran [30] is Facebook’s Layer 4 software load balancer, built on a data plane using the eBPF VM. While Katran is deployed at scale and processes packets at high speeds, its codebase lacks specifications and checks in logic; it also includes many hardcoded values and a myriad of constants. Similar to the invalid MTU example used throughout this paper, they too have a IPv6 Packet Too Big response function: 1 2 3 4 5 6 static inline int send_icmp6_too_big(...) { ... icmp6_hdr->icmp6_type = ICMPV6_PKT_TOOBIG; icmp6_hdr->icmp6_mtu = htonl( MAX_PCKT_SIZE -sizeof(struct eth_hdr) )} ANRW ’19, July 22, 2019, Montreal, QC, Canada Zeeshan Lakhani and Heather Miller Figure 2: Flame graph trace of call time, in samples, for single-packet runs While we expect that a test suite would capture possible bugs in arbitrary logic and pointer references, there are no abstractions within the programs themselves to ensure the validity of fields and what values they can possibly be. Furthermore, what if constants like MAX_PCKT_SIZE were changed within the upstream module that instantiated them? Leveraging static assertions like we do in our work could benefit functions like the one shown here. 6.3 Next Steps 6.3.1 Deployment Models Throughout this paper, we’ve explained how runtime assertions in our system are best used during the design phase of programming NFs. Yet within the ecosystem of network programming, our technique can be extended beyond design and integration testing. We would like to be able to turn on contracts automatically if our system is running within an environment acting as a simulation network, e.g. Mininet [22], or within production deployments for certain kinds of traffic, e.g. probe packets sent for NF monitoring or health checking. 6.3.2 Hinting and Feedback The major goal of our work is aiding in the development of network programs, especially those that involve arbitrary complexity and interact with changing dependencies. In our system, runtime errors look akin to typical assertion errors presented by other languages or frameworks. Even though our errors include some contextÐthe expectation vs. what actually happenedÐthey normally do not articulate anything related to the specification itself or what dependencies triggered it. We would like to take a page from the recent work in program slicing and compiler design (as demonstrated in languages like Elm and Rust) and provide feedback via hints to the programmer while they build out NFs during development. Leverage static analysis of input programs In our code generation step(s), we look for a set of explicit tokens to rewrite and incorporate seamlessly within the context of a given NF. However, by adding the check macro to a function, we’re able to walk the entire AST (Abstract Syntax Tree) of the input NF before it gets compiled, allowing us to perform static analysis on the function to find bugs [16] at compiletime. Further leveraging static analysis would allow us to limit the need for certain runtime contracts. Interactive feedback Good feedback is crucial when an error occurs. Modern type systems provide more context to type errors (beyond just which line propagated the error itself), by suggesting more precise types for the developer to include in their programs. In designing network function paradigms, we want to build off our prototype and expand to include helpful information about where contract errors occur at the boundary and in which ways the errors may be debugged. 7 Related Work Our approach builds upon a growing literature on contractdriven validation of programs. In Sections 1 and 3.1 we referenced our inspiration from D’s contract programming model, which was itself inspired by the system developed for Eiffel. Though our approach is unique within the field of programmable networks, contract programming has gained popularity as extensions to functional programming languages like Clojure (via Spec [27]) and Racket [2]. Racket also includes mechanisms for generating contracts from macros. Regarding type systems and ways to validate network programs, we’ve already mentioned NetKat [3], which has had follow-up pieces in literature, including probabilistic variants [31]. Recently, p4v [23] was published and is motivated by real-world examples; it attempts to find bugs in P4 programs and verify program properties by incorporating domain-specific assumptions into a constraint solver. Languages for network function specification exist within industry, including TOSCA [28], a templating metamodel for network function virtualization. Also related is Assert-P4 [13], which is a proposed approach for checking P4 programs that is also based on assertion checking. Combining assertions with symbolic execution, it finds bugs motivated by controller misconfiguration and code circumvention and gives developers that ability to specify properties about their programs. Their work is very P4-specific and does not provide examples of complex pipelines involving arbitrary dependencies, similar to the cases we’ve discussed in this paper. 8 Conclusion In this paper, we provide a hybrid-approach and implementation for checking and validating the arbitrary logic and side effects typically part of network functions by combining design by contract, static assertions and type-checking, and code generation via macros. We were able to build-out and incorporate our technique within an existing network function framework, without penalizing the developer or increasing the complexity that they already have to handle. We want to explore this space further and provide better tooling and interaction models for anyone programming networks. Checking-in on Network Functions Acknowledgments We want to thank Professor Justine Sherry from Carnegie Mellon University for inspiring this project while the first author was a part of her Programmable Networks class. Additionally, we’re thankful for the guidance and support from Professor Aurojit Panda (New York University) for authoring NetBricks and answering related questions, and Chas Emerick for his all-around help and guidance. Finally, an extra special thank you goes out to the Occam networking team at Comcast (i.e. Peter Cline, Daniel Jin, Andrew Wang, Michael Winslow, Chris Rollins, Paul Cleary, Jon Moore) for their continued support, code, instruction, and feedback. References [1] 2017. Routing.java. https://github.com/opennetworkinglab/onos/blob/ 021d2eb175b8e46d4690cd9e1243301ddd903bcc/utils/misc/src/main/ java/org/onlab/packet/ipv6/Routing.java. (2017). [2] 2018. Racket Contracts. (2018). https://docs.racket-lang.org/guide/ contracts.html [3] Carolyn Jane Anderson, Nate Foster, Arjun Guha, Jean-Baptiste Jeannin, Dexter Kozen, Cole Schlesinger, and David Walker. 2014. NetKAT: Semantic Foundations for Networks. In Proceedings of the 41st ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL ’14). ACM, New York, NY, USA, 113ś126. https://doi.org/10. 1145/2535838.2535862 [4] Pankaj Berde, Matteo Gerola, Jonathan Hart, Yuta Higuchi, Masayoshi Kobayashi, Toshio Koide, Bob Lantz, Brian O’Connor, Pavlin Radoslavov, William Snow, and Guru Parulkar. 2014. ONOS: Towards an Open, Distributed SDN OS. In Proceedings of the Third Workshop on Hot Topics in Software Defined Networking (HotSDN ’14). ACM, New York, NY, USA, 1ś6. https://doi.org/10.1145/2620728.2620744 [5] A. Conta, S. Deering, and M. Gupta. 2006. Internet Control Message Protocol (ICMPv6) for the Internet Protocol Version 6 (IPv6) Specification. RFC 4443. RFC Editor. http://www.rfc-editor.org/rfc/rfc4443.txt http: //www.rfc-editor.org/rfc/rfc4443.txt. [6] Stephen E. Deering and Robert M. Hinden. 1998. Internet Protocol, Version 6 (IPv6) Specification. RFC 2460. RFC Editor. http://www. rfc-editor.org/rfc/rfc2460.txt http://www.rfc-editor.org/rfc/rfc2460.txt. [7] Y. Desmouceaux, P. Pfister, J. Tollet, M. Townsley, and T. Clausen. 2017. SRLB: The Power of Choices in Load Balancing with Segment Routing. In 2017 IEEE 37th International Conference on Distributed Computing Systems (ICDCS). 2011ś2016. https://doi.org/10.1109/ICDCS.2017.180 [8] The Rust Project Developers. 2018. The Rust Programming Language, 2st ed. https://doc.rust-lang.org/1.30.0/book/2018-edition/. (2018). [9] Mihai Dobrescu, Norbert Egi, Katerina Argyraki, Byung-Gon Chun, Kevin Fall, Gianluca Iannaccone, Allan Knies, Maziar Manesh, and Sylvia Ratnasamy. 2009. RouteBricks: Exploiting Parallelism to Scale Software Routers. In Proceedings of the ACM SIGOPS 22Nd Symposium on Operating Systems Principles (SOSP ’09). ACM, New York, NY, USA, 15ś28. https://doi.org/10.1145/1629575.1629578 [10] Paul Emmerich, Sebastian Gallenmüller, Daniel Raumer, Florian Wohlfart, and Georg Carle. 2015. MoonGen: A Scriptable High-Speed Packet Generator. In Internet Measurement Conference. [11] Clarence Filsfils, Stefano Previdi, John Leddy, Satoru Matsushima, and daniel.voyer@bell.ca. 2018. IPv6 Segment Routing Header (SRH). Internet-Draft draft-ietf-6man-segment-routingheader-15. IETF Secretariat. http://www.ietf.org/internet-drafts/ draft-ietf-6man-segment-routing-header-15.txt http://www.ietf.org/ internet-drafts/draft-ietf-6man-segment-routing-header-15.txt. [12] D Language Foundation. 2018. Contract Programming. (2018). https: //dlang.org/spec/contracts.html ANRW ’19, July 22, 2019, Montreal, QC, Canada [13] Lucas Freire, Miguel Neves, Lucas Leal, Kirill Levchenko, Alberto Schaeffer-Filho, and Marinho Barcellos. 2018. Uncovering Bugs in P4 Programs with Assertion-based Verification. In Proceedings of the Symposium on SDN Research (SOSR ’18). ACM, New York, NY, USA, Article 4, 7 pages. https://doi.org/10.1145/3185467.3185499 [14] Brendan Gregg. 2018. Flame Graphs. (2018). http://www.brendangregg. com/flamegraphs.html [15] L. Hendriks, P. Velan, R. d. O. Schmidt, P. de Boer, and A. Pras. 2017. Threats and surprises behind IPv6 extension headers. In 2017 Network Traffic Measurement and Analysis Conference (TMA). 1ś9. https://doi. org/10.23919/TMA.2017.8002912 [16] David Hovemeyer and William Pugh. 2007. Finding More Null Pointer Bugs, but Not Too Many. In Proceedings of the 7th ACM SIGPLAN-SIGSOFT Workshop on Program Analysis for Software Tools and Engineering (PASTE ’07). ACM, New York, NY, USA, 9ś14. https: //doi.org/10.1145/1251535.1251537 [17] Xin Jin, Xiaozhou Li, Haoyu Zhang, Nate Foster, Jeongkeun Lee, Robert Soulé, Changhoon Kim, and Ion Stoica. 2018. NetChain: Scale-Free Sub-RTT Coordination. In NSDI. [18] Murad Kablan, Blake Caldwell, Richard Han, Hani Jamjoom, and Eric Keller. 2015. Stateless Network Functions. In Proceedings of the 2015 ACM SIGCOMM Workshop on Hot Topics in Middleboxes and Network Function Virtualization (HotMiddlebox ’15). ACM, New York, NY, USA, 49ś54. https://doi.org/10.1145/2785989.2785993 [19] Robert Klarer, John Maddock, Beman Dawes, and Howard Hinnant. 2008. Static Assertions. http://www.open-std.org/jtc1/sc22/wg14/ www/docs/n1330.pdf. (2008). [20] E. E. Kohlbecker and M. Wand. 1987. Macro-by-example: Deriving Syntactic Transformations from Their Specifications. In Proceedings of the 14th ACM SIGACT-SIGPLAN Symposium on Principles of Programming Languages (POPL ’87). ACM, New York, NY, USA, 77ś84. https://doi.org/10.1145/41625.41632 [21] Eddie Kohler, Robert Morris, Benjie Chen, John Jannotti, and M. Frans Kaashoek. 2000. The Click Modular Router. ACM Trans. Comput. Syst. 18, 3 (Aug. 2000), 263ś297. https://doi.org/10.1145/354871.354874 [22] Bob Lantz, Brandon Heller, and Nick McKeown. 2010. A network in a laptop: rapid prototyping for software-defined networks. In HotNets. [23] Jed Liu, William Hallahan, Cole Schlesinger, Milad Sharif, Jeongkeun Lee, Robert Soulé, Han Wang, Călin Caşcaval, Nick McKeown, and Nate Foster. 2018. P4V: Practical Verification for Programmable Data Planes. In Proceedings of the 2018 Conference of the ACM Special Interest Group on Data Communication (SIGCOMM ’18). ACM, New York, NY, USA, 490ś503. https://doi.org/10.1145/3230543.3230582 [24] Ahmed M. Medhat, Tarik Taleb, Asma Elmangoush, Giuseppe A. Carella, Stefan Covaci, and Thomas Magedanz. 2017. Service Function Chaining in Next Generation Networks: State of the Art and Research Challenges. Comm. Mag. 55, 2 (Feb. 2017), 216ś223. https: //doi.org/10.1109/MCOM.2016.1600219RP [25] Bertrand Meyer. 1992. Applying "Design by Contract". Computer 25, 10 (Oct. 1992), 40ś51. https://doi.org/10.1109/2.161279 [26] Bertrand Meyer. 2018. Why not program right? (2018). https:// bertrandmeyer.com/2018/05/24/not-program-right [27] Alex Miller. 2018. spec Guide. (2018). https://clojure.org/about/spec [28] Organization for the Advancement of Structured Information Standards. 2017. TOSCA Simple Profile for Network Functions Virtualization (NFV)âĂŤVersion 1.0. (2017). http://docs.oasis-open.org/tosca/ tosca-nfv/v1.0/tosca-nfv-v1.0.html [29] Aurojit Panda, Sangjin Han, Keon Jang, Melvin Walls, Sylvia Ratnasamy, and Scott Shenker. 2016. NetBricks: Taking the V out of NFV. In Proceedings of the 12th USENIX Conference on Operating Systems Design and Implementation (OSDI’16). USENIX Association, Berkeley, CA, USA, 203ś216. http://dl.acm.org/citation.cfm?id=3026877.3026894 ANRW ’19, July 22, 2019, Montreal, QC, Canada [30] Nikita Shirokov and Ranjeeth Dasineni. 2018. Opensourcing Katran, a scalable network load balhttps://code.fb.com/open-source/ ancer. (2018). open-sourcing-katran-a-scalable-network-load-balancer/ [31] Steffen Smolka, David Kahn, Praveen Kumar, Nate Foster, Dexter Kozen, and Alexandra Silva. 2017. Deciding Probabilistic Program Equivalence in NetKAT. CoRR abs/1707.02772 (2017). arXiv:1707.02772 http://arxiv.org/abs/1707.02772 [32] Aaron Weiss. 2018. Reasoning with Types in Rust. (2018). https:// aaronweiss.us/posts/2018-02-26-reasoning-with-types-in-rust.html [33] Justin Worthe. 2018. Compile Time Feature Flags in Rust. (2018). https://www.worthe-it.co.za/programming/2018/11/18/ compile-time-feature-flags-in-rust.html Zeeshan Lakhani and Heather Miller