├── .gitignore ├── README.md ├── experimental └── IOS-parser │ ├── IOS │ ├── IOS-satur-int.flg │ ├── config1.txt │ ├── talk-natfw.txt │ ├── talk-outerfw-racl.txt │ └── talk-outerfw.txt │ ├── Makefile │ ├── convert_dir.sh │ ├── floodtest.flg │ ├── ios-compile.ss │ ├── ios-flowlog-helpers.rkt │ ├── ios-parse.ss │ ├── ios.ss │ ├── ios2flowlog.ss │ ├── measure.sh │ ├── measure_all.sh │ ├── notes.txt │ ├── routers.rkt │ ├── stanford │ ├── bbra_rtr_config.txt │ ├── bbrb_rtr_config.txt │ ├── boza_rtr_config.txt │ ├── bozb_rtr_config.txt │ ├── coza_rtr_config.txt │ ├── cozb_rtr_config.txt │ ├── goza_rtr_config.txt │ ├── gozb_rtr_config.txt │ ├── poza_rtr_config.txt │ ├── pozb_rtr_config.txt │ ├── roza_rtr_config.txt │ ├── rozb_rtr_config.txt │ ├── soza_rtr_config.txt │ ├── sozb_rtr_config.txt │ ├── yoza_rtr_config.txt │ └── yozb_rtr_config.txt │ ├── templates │ ├── Arp_Cache.template.flg │ ├── L3acl.template.flg │ ├── L3external.template.flg │ ├── L3router.template.flg │ ├── Mac_Learning.inc.template.flg │ ├── NATgeneric.template.flg │ ├── NIB.template.flg │ ├── StartupConfig.template.flg │ └── Vlans.template.flg │ ├── testnc.nc │ ├── testnc2.nc │ ├── testnc3.nc │ ├── tests │ ├── nat │ │ ├── ext.txt │ │ └── to-test.txt │ ├── paper1 │ │ ├── configA.txt │ │ ├── configB.txt │ │ ├── configC.txt │ │ ├── configD.txt │ │ ├── conns.txt │ │ ├── ext.txt │ │ ├── to-test.txt │ │ └── topo.dot │ ├── pr │ │ └── tasconfig.txt │ ├── triangle │ │ ├── configA.txt │ │ ├── configC.txt │ │ └── configD.txt │ └── vlan1 │ │ ├── configA.txt │ │ ├── configB.txt │ │ └── conns.txt │ ├── tnat.sh │ ├── tpaper.sh │ ├── ttri.sh │ ├── tvlan.sh │ └── verif │ ├── Arp-props.als │ ├── Arp.flg │ ├── NAT-props.als │ ├── NAT.flg │ ├── Vlan-props.als │ └── Vlans.flg ├── interpreter ├── Flowlog_Builtins.ml ├── Flowlog_Graphs.ml ├── Flowlog_Helpers.ml ├── Flowlog_Packets.ml ├── Flowlog_Parse_Helpers.ml ├── Flowlog_Safety.ml ├── Flowlog_To_Alloy.ml ├── Flowlog_Types.ml ├── Makefile ├── Partial_Eval.ml ├── Partial_Eval_Validation.ml ├── README.md ├── SafeEmitQueue.ml ├── Tests.ml ├── Xsb_Communication.ml ├── _tags ├── examples │ ├── AppleTV │ │ ├── AppleTV.flg │ │ ├── ArpAppleMac.flg │ │ └── Mac_Learning.inc.flg │ ├── Arp_Cache.flg │ ├── BasicNAT.flg │ ├── LoadBalancer.flg │ ├── Mac_Learning.flg │ ├── NIB.flg │ ├── Smart_Mac.flg │ ├── Stolen.flg │ ├── StolenLimited.flg │ ├── detectLocation.flg │ ├── discovery.flg │ ├── experiments │ │ ├── Joins.flg │ │ ├── L3_test_masks.flg │ │ ├── Timeout_Mac.flg │ │ ├── basic_nat.flg │ │ └── packetin1.flg │ ├── getting-started │ │ ├── sfw-plus.flg │ │ └── sfw.flg │ ├── old_l3 │ │ ├── ACLsample.flg │ │ ├── L3external.flg │ │ ├── L3router.flg │ │ ├── L3sample.flg │ │ ├── NATgeneric.flg │ │ └── NATsample.flg │ └── timeout.flg ├── flowlog.ml ├── testcp.ml ├── tests │ ├── Mac_Learning_Diffnames.flg │ ├── Mac_Learning_Missing_Flood.flg │ ├── any_port.flg │ ├── arp_and_packet_both.flg │ ├── bad_do_field.flg │ ├── bad_do_mixed_field_nonfield.flg │ ├── bad_input_field_in_body.flg │ ├── bad_input_field_in_head.flg │ ├── bad_insert_arity.flg │ ├── bad_insert_field.flg │ ├── bad_insert_not_field.flg │ ├── chimp │ │ ├── Change_Equality_a.flg │ │ ├── Change_Equality_b.flg │ │ ├── Change_Reach1_A.flg │ │ ├── Change_Reach1_B.flg │ │ ├── Change_SimpleNeg_A.flg │ │ └── Change_SimpleNeg_B.flg │ ├── lpmatch.flg │ ├── multiple_decls_of_emit.flg │ ├── multiple_reacts_on_emit.flg │ ├── negatedport.flg │ ├── pe_startup.flg │ ├── safety │ │ ├── insert-unsafe-var.flg │ │ ├── new-dlsrc-neq-p-dlsrc.flg │ │ └── should-pass.flg │ ├── test_forward_inference.flg │ ├── test_subtype_triggers.flg │ ├── testclocktime.flg │ ├── testcounting1.flg │ ├── testcounting_preds.als │ ├── testlessthan.flg │ ├── testmultimod.flg │ ├── testroutes.flg │ ├── vlan │ │ └── vlan.flg │ └── warnings │ │ └── typewarning_multiple.flg ├── thrift │ ├── Flowlog_Thrift_In.ml │ ├── Flowlog_Thrift_Out.ml │ ├── Makefile │ ├── _oasis │ ├── _tags │ ├── configure │ ├── flowlog_rpc.thrift │ ├── gen-ocaml │ │ ├── BlackBox.ml │ │ ├── BlackBox.mli │ │ ├── FlowLogInterpreter.ml │ │ ├── FlowLogInterpreter.mli │ │ ├── META │ │ ├── flowlog_rpc_consts.ml │ │ ├── flowlog_rpc_types.ml │ │ ├── flowlog_rpc_types.mli │ │ ├── flowlog_thrift.mldylib │ │ └── flowlog_thrift.mllib │ ├── myocamlbuild.ml │ ├── notify.ml │ ├── placeholder.ml │ ├── police_tipline.ml │ ├── run.sh │ ├── setup.data │ ├── setup.ml │ └── timer.ml └── yacc │ ├── Makefile │ ├── Surface_Lexer.mll │ └── Surface_Parser.mly ├── mininet ├── 3-cycle.py ├── FlowlogMN.py ├── FlowlogMNUtil.py ├── NatMN.py ├── read.py ├── routers.proto ├── routers_pb2.py ├── sample-routers.pb.txt └── start3.sh └── py ├── rseen.sh └── seen.py /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .* 3 | *.log 4 | interpreter/_build 5 | interpreter/thrift/_build 6 | *.d.byte 7 | *.native 8 | interpreter/examples/*.json 9 | Surface_Lexer.ml 10 | Surface_Parser.ml 11 | Surface_Parser.mli 12 | *.pyc 13 | experimental/IOS-parser/**/*.p 14 | experimental/IOS-parser/**/*.flg 15 | !experimental/IOS-parser/templates/*.flg 16 | experimental/IOS-parser/**/*.pb.bin 17 | experimental/IOS-parser/compiled/ 18 | experimental/IOS-parser/ios2flowlog 19 | *.bak 20 | *_rtr_arp_table.txt 21 | *_rtr_route.txt 22 | *_rtr_spanning_tree.txt 23 | *_rtr_mac_table.txt 24 | *.als 25 | pan.* 26 | pan 27 | *.pml.trail 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FlowLog 2 | ======= 3 | 4 | To use the interpreter, you need: 5 | 6 | * XSB, a Prolog engine. This can be downloaded for free at: 7 | http://xsb.sourceforge.net/downloads/downloads.html 8 | 9 | * The latest version of OCaml. 10 | 11 | * The Frenetic package along with its dependencies. These can be found at: 12 | http://www.github.com/frenetic-lang 13 | (and via opam) 14 | 15 | * To run analysis via Alloy, download either Alloy Analyzer: 16 | http://alloy.mit.edu/alloy/download.html 17 | or the Aluminum "Minimal Alloy" analyzer: 18 | http://cs.brown.edu/research/plt/dl/aluminum/ 19 | 20 | * The "yojson" package (opam install yojson) 21 | (We use this to output dependency graphs in JSON format.) 22 | 23 | * The "thrift" package. 24 | 25 | * To use our mininet scripts: 26 | * mininet 2.1.0 or later: http://mininet.org/download/ 27 | * python-protobuf 28 | 29 | 30 | 31 | 32 | ----------------------------- 33 | Legacy notes: 34 | 35 | For the pvs verification, you need PVS, which can be downloaded at: 36 | http://pvs.csl.sri.com/download.shtml 37 | -------------------------------------------------------------------------------- /experimental/IOS-parser/IOS/IOS-satur-int.flg: -------------------------------------------------------------------------------- 1 | INCLUDE "IOS/L3router.flg"; 2 | INCLUDE "IOS/L3acl.flg"; 3 | INCLUDE "IOS/Mac_Learning.inc.flg"; 4 | 5 | TABLE routerAlias(string, switchid); 6 | TABLE portAlias(string, string, portid); 7 | 8 | // cached, switches_without_mac_learning, subnets: declared in INCLUDED files 9 | 10 | // Maps subnet number -> (host side, router side) 11 | // 12 | // TODO(tn): to be replaced with: 13 | // 14 | // router_portmap(rp, host, rside) = 15 | // math_mult(2, tmp, rside) and math_sub(rport, 1, tmp) 16 | // and math_sub(rside, 1, host) 17 | // 18 | TABLE router_portmap(portid, portid, portid); 19 | 20 | /******************************************************************************* 21 | * 22 | * Startup values 23 | * 24 | ******************************************************************************/ 25 | 26 | ON startup(e): 27 | INSERT ("int", 0x100000000001) INTO routerAlias; 28 | INSERT (0x100000000001) INTO switches_without_mac_learning; // auto 29 | INSERT (192.168.0.0, 16, 192.168.1.1, ca:fe:00:01:00:01, 0x100000000001, 2) INTO subnets; 30 | INSERT (192.168.1.1, ca:fe:00:01:00:01) INTO cached; // auto 31 | INSERT ("int", "in_lan", 1) INTO portAlias; 32 | INSERT (0x100000000001, 192.168.0.0, 16) INTO needs_nat; 33 | INSERT ("int-in_lan-acl", 0x500000000001, 1, 2) INTO aclAlias; 34 | INSERT (0x400000000001,1,1,10.1.1.1) INTO natconfig; 35 | INSERT (10.1.1.1, 0x6, 10000) INTO seqpt; // auto 36 | INSERT (10.1.1.1, 0x11, 10000) INTO seqpt; // auto 37 | INSERT (10.1.1.0, 24, 10.1.1.1, ca:fe:00:01:00:02, 0x100000000001, 3) INTO subnets; 38 | INSERT (10.1.1.1, ca:fe:00:01:00:02) INTO cached; // auto 39 | INSERT ("int", "in_dmz", 2) INTO portAlias; 40 | INSERT ("int-in_dmz-acl", 0x500000000001, 3, 4) INTO aclAlias; 41 | INSERT (0x400000000001) INTO switches_without_mac_learning; // auto 42 | INSERT (0x400000000001) INTO switches_without_arp; // auto 43 | INSERT (0x100000000001, 0x400000000001) INTO router_nat; 44 | INSERT (0x200000000001) INTO switches_without_mac_learning; // auto 45 | INSERT (0x200000000001) INTO switches_without_arp; // auto 46 | INSERT (0x100000000001, 0x200000000001) INTO router_tr; 47 | INSERT (0x500000000001) INTO switches_without_mac_learning; // auto 48 | INSERT (0x500000000001) INTO switches_without_arp; // auto 49 | INSERT (0x100000000001, 0x500000000001) INTO router_acl; 50 | 51 | 52 | // LOL. MATH. 53 | INSERT (3, 3, 4) INTO router_portmap; 54 | INSERT (2, 1, 2) INTO router_portmap; 55 | 56 | // ADDED to get saturated tables: 57 | INSERT (192.168.1.2, 00:00:00:00:00:01) INTO cached; 58 | INSERT (192.168.1.3, 00:00:00:00:00:02) INTO cached; 59 | INSERT (10.1.1.3, 00:00:00:00:00:03) INTO cached; 60 | INSERT (10.1.1.4, 00:00:00:00:00:04) INTO cached; 61 | // NIC on other router: 62 | INSERT (10.1.1.2, ca:fe:00:02:00:01) INTO cached; 63 | 64 | 65 | -------------------------------------------------------------------------------- /experimental/IOS-parser/IOS/config1.txt: -------------------------------------------------------------------------------- 1 | interface fe0 2 | ip address 10.1.1.1 255.255.255.254 3 | ip access-group 101 in 4 | ! 5 | interface vlan1 6 | ip address 192.128.5.1 255.255.255.0 7 | ip access-group 102 in 8 | ! 9 | access-list 101 deny ip host 10.1.1.2 any 10 | access-list 101 permit tcp any host 192.168.5.10 eq 80 11 | access-list 101 permit tcp any host 192.168.5.11 eq 25 12 | ! 13 | ! added this rule to test multi-level ip address types 14 | ! should produce: 15 | ! (IP-192.168.5.0/255.255.255.0 > IP-192.168.5.11 IP-192.168.5.10) 16 | access-list 101 deny ip 192.168.5.10 0.0.0.255 any 17 | ! 18 | ! NOT THIS: access-list 101 deny ip 192.168.5.10 255.255.255.0 any 19 | ! Which produces a garbage IP range because Cisco IOS access-lists flip the bits of the mask 20 | ! 21 | access-list 101 deny any 22 | ! 23 | access-list 102 permit any 24 | ! 25 | ! // need to give the end command 26 | end 27 | -------------------------------------------------------------------------------- /experimental/IOS-parser/IOS/talk-natfw.txt: -------------------------------------------------------------------------------- 1 | hostname int 2 | ! 3 | interface in_dmz 4 | ip address 10.1.1.1 255.255.255.0 5 | ip nat outside 6 | ! 7 | interface in_lan 8 | ip access-group 102 in 9 | ip address 192.168.1.1 255.255.0.0 10 | ip nat inside 11 | ! 12 | access-list 102 deny ip 192.168.4.1 0.0.0.255 host 10.1.1.3 13 | access-list 102 permit tcp any host 10.1.1.3 eq 25 14 | access-list 102 permit tcp any any eq 80 15 | access-list 102 permit tcp any any eq 22 16 | access-list 102 deny any 17 | ! 18 | ip nat inside source list 1 interface in_dmz overload 19 | ! 20 | ! Project does not support in_dmz (which was the "next hop" before) 21 | ! 22 | ip route 0.0.0.0 0.0.0.0 10.1.1.2 23 | ! 24 | ! TEST [not included in paper config]: make sure needs-nat gets all subnets that appear here. 25 | ! Needs-nat is not limited to (or indeed, guaranteed to cover all of) the interface subnet. 26 | ! So here is a piece of the LAN (a /24 out of a /16) and another /24 that 27 | ! (in theory) comes via another L3 device: 28 | access-list 1 permit 192.168.1.1 0.0.0.255 29 | access-list 1 permit 192.167.1.1 0.0.0.255 30 | ! // need to give the end command 31 | end -------------------------------------------------------------------------------- /experimental/IOS-parser/IOS/talk-outerfw-racl.txt: -------------------------------------------------------------------------------- 1 | hostname ext 2 | 3 | interface out_dmz 4 | ip access-group 103 in 5 | ip address 10.1.1.2 255.255.255.0 6 | 7 | interface out_inet 8 | ip access-group 104 in 9 | ip address 10.200.1.1 255.255.0.0 10 | 11 | 12 | ! TODO: require reflect X to appear before evaluate X 13 | ! or the parser will not properly add 14 | 15 | ! applied to dmz side (internal) 16 | ip access-list extended 103 17 | deny ip any host 10.200.200.200 18 | deny tcp any any eq 23 19 | permit tcp host 10.1.1.1 any eq 80 reflect returnflow 20 | permit tcp host 10.1.1.1 any eq 22 reflect returnflow 21 | deny any 22 | 23 | 24 | ! applied to inet side (external) 25 | ip access-list extended 104 26 | deny host 10.200.200.200 27 | permit tcp any host 10.1.1.3 eq 25 28 | permit tcp any host 10.1.1.4 eq 80 29 | evaluate returnflow 30 | deny any 31 | 32 | 33 | 34 | ! TODO: comment in middle of block breaks the parser :-( 35 | 36 | ! next line was 192.168.1.2, but it should be the NAT, not a 192.168.x 37 | 38 | 39 | ! added for return traffic to NAT 40 | ! permit tcp any host 10.1.1.1 41 | -------------------------------------------------------------------------------- /experimental/IOS-parser/IOS/talk-outerfw.txt: -------------------------------------------------------------------------------- 1 | hostname ext 2 | 3 | interface out_dmz 4 | ip access-group 103 in 5 | ip address 10.1.1.2 255.255.255.0 6 | 7 | interface out_inet 8 | ip access-group 104 in 9 | ip address 10.200.1.1 255.255.0.0 10 | 11 | access-list 104 deny 10.200.200.200 12 | access-list 104 permit tcp any host 10.1.1.3 eq 25 13 | access-list 104 permit tcp any host 10.1.1.4 eq 80 14 | ! added for return traffic to NAT 15 | access-list 104 permit tcp any host 10.1.1.1 16 | access-list 104 deny any 17 | 18 | access-list 103 deny ip any host 10.200.200.200 19 | access-list 103 deny tcp any any eq 23 20 | ! next line was 192.168.1.2, but it should be the NAT, not a 192.168.x 21 | access-list 103 permit tcp host 10.1.1.1 any eq 80 22 | access-list 103 permit tcp host 10.1.1.1 any eq 22 23 | access-list 103 deny any 24 | -------------------------------------------------------------------------------- /experimental/IOS-parser/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | raco exe ios2flowlog.ss 3 | 4 | protobuf: 5 | protoc --proto_path=../../mininet/ --plugin=`which protoc-gen-racket` --racket_out=. ../../mininet/routers.proto 6 | protoc --proto_path=../../mininet/ --python_out=../../mininet/ ../../mininet/routers.proto 7 | -------------------------------------------------------------------------------- /experimental/IOS-parser/convert_dir.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$1" 4 | FILES="" 5 | 6 | for file in `ls -1 $DIR/*_rtr_config.txt`; do 7 | config=`basename $file` 8 | FILES="$FILES $config" 9 | done 10 | 11 | echo "./ios2flowlog --path $DIR $FILES" 12 | 13 | ./ios2flowlog --path $DIR $FILES 14 | -------------------------------------------------------------------------------- /experimental/IOS-parser/floodtest.flg: -------------------------------------------------------------------------------- 1 | 2 | // For testing basic mininet connectivity in Exodus. 3 | // Loops are bad even here, because will lock up mininet on discovery 4 | 5 | TABLE topology(switchid, portid, switchid, portid); 6 | 7 | ON switch_port(swpt): 8 | do emit(new) where new.locsw = swpt.sw and new.locpt = swpt.pt and 9 | new.dlsrc = new.locsw and new.dldst = new.locpt and new.dltyp = 0x1001 and new.dlvlan = -1; 10 | 11 | ON packet(p) WHERE p.dltyp = 0x1001: 12 | INSERT (p.locSw, p.locPt, p.dlSrc, p.dlDst) INTO topology; 13 | 14 | 15 | -------------------------------------------------------------------------------- /experimental/IOS-parser/ios2flowlog.ss: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | 3 | ; Simple wrapper for ios-compile.ss 4 | ; 5 | ; build me with: 6 | ; $ raco exe ios2flowlog.ss 7 | ; 8 | ; example for how to run on file IOS/talk-natfw.txt: 9 | ; $ ios2flowlog --path IOS talk-natfw.txt 10 | 11 | (require racket/cmdline) 12 | (require "ios-compile.ss") 13 | 14 | (define path ".") 15 | (define connfilename #f) 16 | (define default-permit #f) 17 | 18 | (command-line 19 | #:program "ios2flowlog" 20 | #:once-each 21 | ("--default-permit" "Use a default permit ACL" 22 | (set! default-permit #t)) 23 | ("--default-deny" "Use a default deny ACL [default]" 24 | (set! default-permit #f)) 25 | ("--path" input "Directory path to IOS files [default: .]" 26 | (set! path input)) 27 | ("--conn" input "Name of file (in --path) with L2 connections spec" 28 | (set! connfilename input)) 29 | #:args files 30 | (when (> (length files) 0) 31 | (compile-configurations path files default-permit connfilename))) 32 | -------------------------------------------------------------------------------- /experimental/IOS-parser/measure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo 'Measuring' $1 3 | echo 'vlan:' `sudo ovs-ofctl dump-flows $1-vlan | grep "cookie" | wc -l` 4 | echo 'acl:' `sudo ovs-ofctl dump-flows $1-acl | grep "cookie" | wc -l` 5 | echo 'tr:' `sudo ovs-ofctl dump-flows $1-tr | grep "cookie" | wc -l` 6 | echo 'rtr:' `sudo ovs-ofctl dump-flows $1-rtr | grep "cookie" | wc -l` 7 | echo 'nat:' `sudo ovs-ofctl dump-flows $1-nat | grep "cookie" | wc -l` 8 | 9 | -------------------------------------------------------------------------------- /experimental/IOS-parser/measure_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo '-----------------------------------------------------' 3 | for rtrid in `ls $1 | grep "config" | cut -d_ -f1`; do ./measure.sh $rtrid; done 4 | echo '-----------------------------------------------------'g 5 | 6 | # ./prepare.sh $1 $rtrid; sleep 15; 7 | # sudo killall mn; sudo mn -c; sudo killall flowlog.native -------------------------------------------------------------------------------- /experimental/IOS-parser/notes.txt: -------------------------------------------------------------------------------- 1 | Questions and concerns 2 | ---------------------- 3 | 4 | Do we need to keep a spanning tree per Vlan? 5 | 6 | For tree-flood, do we need to send out host interfaces as well as those on the 7 | spanning tree? 8 | 9 | Will MAC learning on the Vlan switches avoid sending rtr -> physical packets 10 | out ALL interfaces for the appropriate vlan? 11 | 12 | Does a static routing command just add routes to the table (with next hop?) 13 | 14 | 15 | 16 | subnets table may be too large for analysis -------------------------------------------------------------------------------- /experimental/IOS-parser/routers.rkt: -------------------------------------------------------------------------------- 1 | #lang racket/base 2 | ;; Generated using protoc-gen-racket v1.1 3 | (require (planet murphy/protobuf:1/syntax)) 4 | 5 | (define-message-type 6 | subnet 7 | ((optional primitive:string addr 1) 8 | (optional primitive:int32 mask 2) 9 | (optional primitive:string gw 3) 10 | (optional primitive:string ifname 6) 11 | (repeated primitive:int32 physical-portid 7))) 12 | (define-message-type 13 | port 14 | ((optional primitive:int32 id 1) 15 | (optional primitive:string name 2) 16 | (optional primitive:string vlan-type 3))) 17 | (define-message-type 18 | connection 19 | ((optional primitive:string router1 1) 20 | (optional primitive:string router2 2) 21 | (optional primitive:string iface1 3) 22 | (optional primitive:string iface2 4))) 23 | (define-message-type 24 | network 25 | ((optional primitive:string addr 1) (optional primitive:int32 mask 2))) 26 | (define-message-type 27 | peer 28 | ((optional primitive:string ip 1) 29 | (optional primitive:int32 mask 2) 30 | (optional primitive:string mac 3) 31 | (repeated struct:network networks 4))) 32 | (define-message-type 33 | router 34 | ((optional primitive:string name 1) 35 | (optional primitive:string self-dpid 2) 36 | (optional primitive:string nat-dpid 3) 37 | (optional primitive:string tr-dpid 6) 38 | (optional primitive:string acl-dpid 7) 39 | (optional primitive:string vlan-dpid 8) 40 | (optional primitive:int32 num-physical 10) 41 | (repeated struct:subnet subnets 4) 42 | (repeated struct:peer peers 5) 43 | (repeated struct:port ports 9))) 44 | (define-message-type 45 | routers 46 | ((repeated struct:router routers 1) 47 | (optional primitive:string subnet-base-dpid 2) 48 | (repeated struct:connection connections 3))) 49 | 50 | (provide (all-defined-out)) 51 | -------------------------------------------------------------------------------- /experimental/IOS-parser/templates/L3acl.template.flg: -------------------------------------------------------------------------------- 1 | /* 2 | * Layer 3 ACL tables. 3 | * 4 | * (description goes here) 5 | * 6 | */ 7 | 8 | // maps "-" -> (ACL switch, host side, router side) 9 | TABLE aclAlias(string, switchid, portid, portid); 10 | 11 | /////////////////////////// 12 | // ACLs 13 | // on the ACL switches, port (2 * N - 1) faces toward subnet N 14 | // port (2 * N) faces toward the router. 15 | /////////////////////////// 16 | 17 | ON tcp_packet(pkt) WHERE aclAlias(ANY, pkt.locSw, ANY, ANY): 18 | DO forward(new) WHERE 19 | @inboundacl-tcp 20 | ; 21 | 22 | ON udp_packet(pkt) WHERE aclAlias(ANY, pkt.locSw, ANY, ANY): 23 | DO forward(new) WHERE 24 | @inboundacl-udp 25 | ; 26 | 27 | ON ip_packet(pkt) WHERE aclAlias(ANY, pkt.locSw, ANY, ANY): 28 | DO forward(new) WHERE 29 | @inboundacl-ip 30 | ; 31 | 32 | /////////////////////////// 33 | 34 | ON tcp_packet(pkt) WHERE aclAlias(ANY, pkt.locSw, ANY, ANY): 35 | DO forward(new) WHERE 36 | @outboundacl-tcp 37 | ; 38 | 39 | ON udp_packet(pkt) WHERE aclAlias(ANY, pkt.locSw, ANY, ANY): 40 | DO forward(new) WHERE 41 | @outboundacl-udp 42 | ; 43 | 44 | ON ip_packet(pkt) WHERE aclAlias(ANY, pkt.locSw, ANY, ANY): 45 | DO forward(new) WHERE 46 | @outboundacl-ip 47 | ; 48 | 49 | //////////////////////////////////////////////////// 50 | // If there are reflexive ACL inserts, they go here: 51 | TABLE reflexiveACL(string, ipaddr, tpport, nwproto, ipaddr, tpport); 52 | @reflexive-inserts-tcp 53 | @reflexive-inserts-udp 54 | -------------------------------------------------------------------------------- /experimental/IOS-parser/templates/L3external.template.flg: -------------------------------------------------------------------------------- 1 | /* 2 | * Basic functionality for handling external (non-directly attached) subnets. 3 | * 4 | * The core of this module is a routing table with a list of subnets and their 5 | * corresponding gateway's IP address. 6 | * 7 | * 8 | * TODO: 9 | * - should issue ARP requests for every nexthop (gateway) IP 10 | * automatically, rather than requiring they be entered into the cached 11 | * relation by hand at startup. 12 | */ 13 | 14 | /******************************************************************************* 15 | * 16 | * Data Structures 17 | * 18 | ******************************************************************************/ 19 | 20 | // Routing table for non-directly attached subnets 21 | // subnet, mask, next-hop IP 22 | // TABLE routes(ipaddr, int, ipaddr); 23 | 24 | // router id, prefix, mask, outport 25 | REMOTE TABLE routes(switchid, ipaddr, int, portid) 26 | FROM routes AT 127.0.0.1 9999 27 | TIMEOUT 25 seconds; 28 | 29 | /******************************************************************************* 30 | * 31 | * L3 routing to non-directly attached subnets 32 | * 33 | * TODO(adf): would be great if NAT were not explicitly considered here 34 | * 35 | ******************************************************************************/ 36 | 37 | // packets destined to outside whose source does NOT need NATing 38 | // POLICY ROUTING 39 | ON ip_packet(pkt): 40 | DO forward(new) WHERE 41 | // not in a local subnet for this router 42 | NOT inLocalSubnet(pkt.locSw, pkt.nwDst) 43 | // don't route packets which need NAT'ing (must send them to NAT first: see L3router) 44 | and NOT @needs-nat-disj 45 | 46 | // next-hop obtained from policy routing 47 | and @policyroute-route 48 | and subnets(nexthop_subnet, nexthop_mask, ANY, new.dlSrc, pkt.locSw, new.locPt) 49 | and nexthop IN nexthop_subnet/nexthop_mask 50 | and cached(nexthop, new.dlDst); // MAC addr of nexthop IP 51 | 52 | // STATIC OR OSPF ROUTING 53 | // (no support for default-policy routing) 54 | ON ip_packet(pkt): 55 | DO forward(new) WHERE 56 | ///// 57 | // routing table 58 | routes(pkt.locSw, pre, mask, new.locPt) AND 59 | pkt.nwDst IN pre/mask AND 60 | // special predicate with special compilation "routes" is hard-coded, do not change! 61 | NOT hasLongerPrefixMatch(pkt.locSw, pkt.nwDst, pre, mask) 62 | 63 | // no policy route available 64 | // used to refer to policyroute-pass, but can just negate 65 | and not @policyroute-route 66 | 67 | // output dlsrc and port (bind output gateway IP for comparison later) 68 | and subnets(ANY, ANY, out_gateway_ip, new.dlSrc, pkt.locSw, new.locPt) 69 | 70 | ///// 71 | // only route packets that don't need natting first 72 | // (either they don't match the NAT ACL, or they are not to a "nat outside" interface) 73 | and (NOT @needs-nat-disj OR 74 | NOT natconfig(natsw, ANY, ANY, out_gateway_ip)) 75 | 76 | ///// 77 | // not in a local subnet for this router 78 | and NOT inLocalSubnet(pkt.locSw, pkt.nwDst) 79 | 80 | // allTopology has vlan switch ids. need to convert both pkt.locSw->vlan and nextsw -> rtr. 81 | and router_vlan(pkt.locSw, currvlsw) 82 | and vr2rr(currvlsw, c_rsidept, new.locPt) // c_rsidept: the router-side port on the vlan switch the new pkt will emerge from 83 | and p2r(currvlsw, currphysport, c_rsidept) // currphysport: the host-side port of the vlan switch the new pkt will emerge from 84 | 85 | // must set output dldst, too. don't need to use the cache here. just find the gateway 86 | AND allTopology(currvlsw, currphysport, nextvlansw, nextphyspt) 87 | and router_vlan(nextrtr, nextvlansw) 88 | 89 | and vr2rr(nextvlansw, n_rsidept, nextrpt) 90 | and p2r(nextvlansw, nextphyspt, n_rsidept) 91 | 92 | and subnets(ANY, ANY, ANY, new.dlDst, nextrtr, nextrpt); 93 | 94 | // NAT for destination not immediately attached: matches NAT ACL and will be routed to a "nat outside" interface 95 | ON ip_packet(pkt): 96 | DO forward(new) WHERE 97 | @needs-nat-disj // matches NAT ACL 98 | and routes(pkt.locSw, pre, mask, nextpt) 99 | and pkt.nwDst IN pre/mask 100 | and NOT hasLongerPrefixMatch(pkt.locSw, pkt.nwDst, pre, mask) 101 | and router_nat(pkt.locSw, natsw) 102 | and natconfig(natsw, ANY, ANY, natgw) // we will send out a "nat outside" interface (IP = natgw) 103 | and subnets(ANY, ANY, natgw, ANY, pkt.locSw, nextpt) 104 | and new.locPt = 1; 105 | 106 | 107 | /******************************************************************************* 108 | * 109 | * Per-subnet translators: outbound path to nexthop MAC addr where destination 110 | * MAC address is already set. 111 | * 112 | * TN note: the _inbound_ path for TR switches is handled in L3router.flg 113 | * This isn't just passthrough: we need to make sure the packet has been hit by the above rule. 114 | * 115 | ******************************************************************************/ 116 | 117 | 118 | 119 | // Packet arrives at TR from router side, need to send out host side 120 | // TODO(TN): this will not work properly for policy routing 121 | 122 | // Not *quite* perfect. Ideally we'd say NOT inLocalSubnet(router, pkt.nwDst), but the compiler 123 | // is not optimizing that---giving us quadratic blowup when it isn't needed. 124 | // Instead, consider that this is an externally-routed packet IFF the dlDst is a non-local gateway 125 | // Concerned that this won't work well with an external network. - TN (TODO) 126 | 127 | ON ip_packet(pkt) WHERE router_tr(router, pkt.locSw): 128 | DO forward(new) WHERE 129 | 130 | // not in a local subnet for this router 131 | // (prevent overlap with L3router.flg; avoiding universal quantification issue) 132 | //NOT inLocalSubnet(router, pkt.nwDst) 133 | 134 | // idea; doesn't go through (TODO) 135 | // We've got a layer-2 destination that is a non-local gateway 136 | subnets(ANY, ANY, ANY, pkt.dlDst, router2, ANY) 137 | and router != router2 138 | 139 | and router_portmap(ANY, new.locPt, pkt.locPt); // router -> host (only one instance of this number in the table) 140 | // router already set the destination MAC, so use that to find the output port 141 | // and subnets(ANY, ANY, ANY, pkt.dlDst, router, rport) 142 | //and router_portmap(rport, new.locPt, pkt.locPt); // router -> host 143 | -------------------------------------------------------------------------------- /experimental/IOS-parser/templates/Mac_Learning.inc.template.flg: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Primitive contents of "Mac_Learning.inc.flg" 4 | * 5 | ******************************************************************************/ 6 | 7 | TABLE learned(switchid, portid, macaddr); 8 | 9 | TABLE switches_without_mac_learning(switchid); 10 | 11 | /* 12 | * TODO: how should we create the syntax to indicate that MAC learning should 13 | * not be handle packets handled elsewhere? at the same time, it is the 14 | * forwarding module, so we may want it to handle traffic which doesn't enter 15 | * via packet_in anyway. Note that we *do* want to learn from the ARP traffic, 16 | * although we are ignoring it for now... 17 | * 18 | * TODO: Positioning of "pkt.dlTyp != 0x0806" is an open question -- Can we 19 | * achieve both of these goals? 20 | * 1) learn from packets which come through the system 21 | * 2) not affect forwarding of ARP traffic in any way? (including via the 22 | * INSERT rule) 23 | * When positioned on the initial `ON packet_in(pkt)`, we get smaller flow 24 | * tables from NetCore (helping compensate for NetCore issue #142), but we don't 25 | * learn anything from ARP traffic. A wholly different approach would be for the 26 | * ARP handlers above to explicitly add to the learned relation, but I don't 27 | * like that mixing as it violates modularity... 28 | * 29 | */ 30 | 31 | // These rules are for MAC learning on root-switches and subnet switches. 32 | // More complex rules involving vlans on routers: see Vlans.template.flg 33 | 34 | ON packet(pkt) WHERE pkt.dlTyp != 0x0806 and // ARP 35 | pkt.dlTyp != 0x1001 AND // not a probe 36 | pkt.dlDst != 0x01005e000016 and // IGMP ethernet multicast. 37 | // this should be an explicit drop in AppleTV.flg. 38 | pkt.dlDst != 0x3333000000fb and // mDNS IPv6 ethernet multicast 39 | pkt.dlDst != 0x01005e0000fb and // mDNS IPv4 ethernet multicast 40 | 41 | // TODO(adf): drop 802.1D bridge-local frames 42 | not switches_without_mac_learning(pkt.locSw): 43 | 44 | INSERT (pkt.locSw, pkt.locPt, pkt.dlSrc) INTO learned; 45 | 46 | DELETE (pkt.locSw, pt, pkt.dlSrc) FROM learned WHERE 47 | not pt = pkt.locPt; 48 | 49 | DO forward(new) WHERE 50 | learned(pkt.locSw, new.locPt, pkt.dlDst) 51 | OR 52 | (NOT learned(pkt.locSw, ANY, pkt.dlDst) AND 53 | pkt.locPt != new.locPt); 54 | 55 | ON switch_down(swd): 56 | DELETE (swd.sw, ANY, ANY) FROM learned; 57 | -------------------------------------------------------------------------------- /experimental/IOS-parser/templates/StartupConfig.template.flg: -------------------------------------------------------------------------------- 1 | INCLUDE "@|basename|/L3router.flg"; 2 | INCLUDE "@|basename|/L3acl.flg"; 3 | INCLUDE "@|basename|/Mac_Learning.inc.flg"; 4 | INCLUDE "@|basename|/NIB.flg"; 5 | INCLUDE "@|basename|/Vlans.flg"; 6 | 7 | /* Explanation of port number types: 8 | 9 | Because Exodus splits a router into multiple sub-switches with different notions of "port", 10 | different namespaces can get confusing. 11 | 12 | PHYSICAL PORTS: on the "host" side of the vlan sub-switch. 13 | These include switchport interfaces and normal interfaces that aren't virtual 14 | 15 | ROUTER PORTS: on the "host" side of the router sub-switch 16 | These include all interfaces visible from L3, including virtuals. 17 | The indices start with 2, because the 18 | 19 | ADJUSTED ROUTER PORTS: these are the "router" side ports on the vlan-subswitch. 20 | Same number as the router ports, but offset by the number of physical ports 21 | on the subswitch. (1...n = physical; n+1...n+m = adjusted router) 22 | 23 | So the "router port" should be used in relations like subnet, that operate at the 24 | router-subswitch level, but the "adjusted" port should be used to talk about the router's 25 | counterparts on the vlan switch. 26 | 27 | There won't always be a 1-1 relationship between these! For instance, a vlan with 2 physical ports but one 28 | subnet will have a single switched virtual interface (a single routing port visible at L3). 29 | 30 | The p2r relation holds each vlan's physical<--->adjusted router mapping. 31 | The vr2rr relation holds each vlan's adjusted router <----> router mapping. 32 | 33 | */ 34 | 35 | 36 | 37 | TABLE routerAlias(string, switchid); 38 | TABLE portAlias(string, string, portid); 39 | 40 | // OSPF outgoing costs 41 | // switch -> routing port id -> cost 42 | TABLE ospf_costs(switchid, portid, int); 43 | 44 | // cached, switches_without_mac_learning, subnets: declared in INCLUDED files 45 | 46 | // Maps subnet number -> (host side, router side) 47 | // 48 | // TODO(tn): to be replaced with: 49 | // 50 | // router_portmap(rp, host, rside) = 51 | // math_mult(2, tmp, rside) and math_sub(rport, 1, tmp) 52 | // and math_sub(rside, 1, host) 53 | // 54 | TABLE router_portmap(portid, portid, portid); 55 | 56 | 57 | // holds static routes given in config files. equal standing with ospf routes. 58 | // superceded by policy routes, though 59 | // Will be joined with subnets to find port for next hop in 4th column 60 | TABLE static_nexthops(switchid, ipaddr, int, ipaddr); 61 | 62 | /******************************************************************************* 63 | * 64 | * Startup values 65 | * 66 | ******************************************************************************/ 67 | 68 | ON startup(e): 69 | @startupinserts 70 | 71 | // LOL. MATH. 72 | @routerportmap 73 | -------------------------------------------------------------------------------- /experimental/IOS-parser/testnc.nc: -------------------------------------------------------------------------------- 1 | (filter ( !(dstIP / 10.1.1.0 0.0.0.8 || dstIP / 10.1.2.0 0.0.0.8) && inPort = 1); fwd(2)) 2 | + 3 | (filter ( !(dstIP / 10.1.1.0 0.0.0.8 || dstIP / 10.1.2.0 0.0.0.8) && inPort = 3); fwd(4)) -------------------------------------------------------------------------------- /experimental/IOS-parser/testnc2.nc: -------------------------------------------------------------------------------- 1 | 2 | (if (inPort = 1 && !(dstIP / 10.1.1.0 0.0.0.8 || dstIP / 10.1.2.0 0.0.0.8)) then fwd(2) else drop) 3 | + 4 | (if (inPort = 3 && !(dstIP / 10.1.1.0 0.0.0.8 || dstIP / 10.1.2.0 0.0.0.8)) then fwd(4) else drop) 5 | 6 | 7 | -------------------------------------------------------------------------------- /experimental/IOS-parser/testnc3.nc: -------------------------------------------------------------------------------- 1 | 2 | (if ( inPort = 1 && !(dstIP / 10.1.1.0 0.0.0.8 || dstIP / 10.1.2.0 0.0.0.8)) then fwd(2) else drop) 3 | + 4 | (if (!(dstIP / 10.1.1.0 0.0.0.8 || dstIP / 10.1.2.0 0.0.0.8) && inPort = 3) then fwd(4) else drop) 5 | 6 | 7 | -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/nat/ext.txt: -------------------------------------------------------------------------------- 1 | hostname ext 2 | 3 | interface out_inet 4 | ip access-group 101 in 5 | ip access-group 201 out 6 | ip address 10.100.1.1 255.255.0.0 7 | ip nat outside 8 | 9 | ! EGRESS filter: Activate the RACL *after* NAT has taken effect 10 | interface out_corp 11 | ip address 10.200.1.1 255.255.0.0 12 | ip nat inside 13 | ospf cost 6 14 | 15 | ! TODO: require reflect X to appear before evaluate X 16 | ! or the parser will not properly add 17 | 18 | ! applied to INTERNAL SIDE on EGRESS 19 | ! (apply racl to post-nat traffic) 20 | ! internal hosts can do anything but telnet or access a blacklisted host 21 | ip access-list extended 201 22 | deny ip any host 10.100.100.100 23 | deny tcp any any eq 23 24 | permit tcp host 10.100.1.1 any eq 80 reflect returnflow 25 | deny any 26 | 27 | ! applied to INTERNET SIDE on INGRESS 28 | ! allow return traffic AND allow anyone but blacklisted to access 10.200.1.4 29 | ip access-list extended 101 30 | deny ip host 10.100.100.100 any 31 | permit ip any host 10.200.1.4 32 | evaluate returnflow 33 | deny any 34 | 35 | ! activate NAT on corporate interface 36 | ip nat inside source list 1 interface out_corp overload 37 | 38 | ! default route: send out to ISP router (outside our Exodus configuration) 39 | ip route 0.0.0.0 0.0.0.0 10.100.1.2 40 | 41 | ! The subnet out the internal interface is a /16. However 42 | ! only apply NAT to this /24 inside it. 43 | access-list 1 permit 10.200.1.0 0.0.0.255 44 | 45 | end 46 | 47 | ! TODO: need to give the end command? 48 | ! TODO: comment in middle of block breaks the parser :-( 49 | -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/nat/to-test.txt: -------------------------------------------------------------------------------- 1 | 2 | xterm host2 3 | python -m SimpleHTTPServer 80 4 | 5 | xterm host3 6 | curl http://10.100.1.3:80 7 | 8 | should get a reply. each curl command will create a new nat flow. -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/paper1/configA.txt: -------------------------------------------------------------------------------- 1 | hostname vltA 2 | 3 | ! two vlans 4 | vlan 2 5 | name test2 6 | vlan 3 7 | name test3 8 | 9 | ! a trunk 10 | interface TenGigabitEthernet1/1 11 | switchport 12 | switchport trunk encapsulation dot1q 13 | switchport trunk allowed vlan 2,3 14 | switchport mode trunk 15 | 16 | ! access ports for the two vlans 17 | interface GigabitEthernet1/1 18 | switchport 19 | switchport access vlan 2 20 | switchport mode access 21 | 22 | interface GigabitEthernet1/2 23 | switchport 24 | switchport access vlan 3 25 | switchport mode access 26 | 27 | ! virtual interfaces for the two vlans (allow routing) 28 | ! A = .1, B = .2 29 | ! "switched virtual interfaces" or "management interfaces"; 30 | ! switchports don't have IP addresses themselves. 31 | ! 32 | ! can either be "interface vlan id or interface Vlanid" 33 | interface vlan 2 34 | ip address 10.5.102.1 255.255.255.0 35 | ! parser requires a comment between interfaces 36 | interface Vlan3 37 | ip address 10.5.103.1 255.255.255.0 38 | 39 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 40 | 41 | ! a physical L3 interface; no vlan 42 | // To C 43 | interface GigabitEthernet1/3 44 | ip address 192.168.1.1 255.255.255.0 45 | ospf cost 20 46 | 47 | // To D 48 | interface GigabitEthernet1/4 49 | ip address 192.168.2.1 255.255.255.0 50 | ospf cost 5 51 | 52 | 53 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 54 | 55 | ! Default: 56 | ip route 0.0.0.0 0.0.0.0 192.168.2.2 57 | 58 | 59 | end -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/paper1/configB.txt: -------------------------------------------------------------------------------- 1 | hostname vltB 2 | 3 | ! two vlans 4 | vlan 2 5 | name test2 6 | vlan 3 7 | name test3 8 | 9 | ! a trunk 10 | interface TenGigabitEthernet2/1 11 | switchport 12 | switchport trunk encapsulation dot1q 13 | switchport trunk allowed vlan 2,3 14 | switchport mode trunk 15 | 16 | ! access ports for the two vlans 17 | interface GigabitEthernet2/1 18 | switchport 19 | switchport access vlan 2 20 | switchport mode access 21 | 22 | interface GigabitEthernet2/2 23 | switchport 24 | switchport access vlan 3 25 | switchport mode access 26 | 27 | ! virtual interfaces for the two vlans (allow routing) 28 | ! gateways: A = .1, B = .2 29 | interface Vlan2 30 | ip address 10.5.102.2 255.255.255.0 31 | ! parser requires a comment between interfaces 32 | interface Vlan3 33 | ip address 10.5.103.2 255.255.255.0 34 | 35 | ! need a way for traffic from ge2/1 and ge2/2 to get out to the larger network 36 | ! unfortunately giving them the SVI isn't enough: we also config a gateway via mininet 37 | ! with the IP address of the appropriate SVI. But the rest of the network is not routable 38 | ! from the SVI unless we (1) change the gateway to one of the SVIs on routerA or... 39 | ! What we do: go via L2 hop on one of the vlans to reach the gateway on A 40 | ! choice of A is arbitrary; could use 10.5.103.1 as well. 41 | ip route 0.0.0.0 0.0.0.0 10.5.102.1 42 | 43 | 44 | end -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/paper1/configC.txt: -------------------------------------------------------------------------------- 1 | hostname vltC 2 | 3 | ! To A (A uses .1) 4 | interface GigabitEthernet2/1 5 | ip address 192.168.1.2 255.255.255.0 6 | ospf cost 20 7 | 8 | ! To D 9 | interface GigabitEthernet2/2 10 | ip address 192.168.3.1 255.255.255.0 11 | ospf cost 10 12 | 13 | ! To ext (ext uses .1) 14 | interface GigabitEthernet2/3 15 | ip address 10.200.1.2 255.255.0.0 16 | ospf cost 6 17 | 18 | ! Default: 19 | ip route 0.0.0.0 0.0.0.0 10.200.1.1 20 | 21 | end -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/paper1/configD.txt: -------------------------------------------------------------------------------- 1 | hostname vltD 2 | 3 | // To A (A uses .1) 4 | interface GigabitEthernet2/1 5 | ip address 192.168.2.2 255.255.255.0 6 | ospf cost 5 7 | 8 | // To C (C uses .1) 9 | interface GigabitEthernet2/2 10 | ip address 192.168.3.2 255.255.255.0 11 | ospf cost 10 12 | 13 | end -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/paper1/conns.txt: -------------------------------------------------------------------------------- 1 | vltA TenGigabitEthernet1/1 vltB TenGigabitEthernet2/1 -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/paper1/ext.txt: -------------------------------------------------------------------------------- 1 | hostname ext 2 | 3 | interface out_inet 4 | ip access-group 101 in 5 | ip access-group 201 out 6 | ip address 10.100.1.1 255.255.0.0 7 | ip nat outside 8 | 9 | ! EGRESS filter: Activate the RACL *after* NAT has taken effect 10 | interface out_corp 11 | ip address 10.200.1.1 255.255.0.0 12 | ip nat inside 13 | ospf cost 6 14 | 15 | ! TODO: require reflect X to appear before evaluate X 16 | ! or the parser will not properly add 17 | 18 | ! applied to INTERNAL SIDE on EGRESS 19 | ! (apply racl to post-nat traffic) 20 | ! internal hosts can do anything but telnet or access a blacklisted host 21 | ip access-list extended 201 22 | deny ip any host 10.100.100.100 23 | deny tcp any any eq 23 24 | permit tcp host 10.100.1.1 any reflect returnflow 25 | deny any 26 | 27 | ! applied to INTERNET SIDE on INGRESS 28 | ! allow return traffic AND allow anyone but blacklisted to access 10.200.1.4 29 | ip access-list extended 101 30 | deny ip host 10.100.100.100 any 31 | permit ip any host 10.200.1.4 32 | evaluate returnflow 33 | deny any 34 | 35 | ! activate NAT on corporate interface 36 | ip nat inside source list 1 interface out_corp overload 37 | 38 | ! default route: send out to ISP router (outside our Exodus configuration) 39 | ip route 0.0.0.0 0.0.0.0 10.100.1.2 40 | 41 | ! The subnet out the internal interface is a /16. However 42 | ! only apply NAT to this /24 inside it. 43 | access-list 1 permit 10.200.1.0 0.0.0.255 44 | 45 | end 46 | 47 | ! TODO: need to give the end command? 48 | ! TODO: comment in middle of block breaks the parser :-( 49 | -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/paper1/to-test.txt: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | First ping will be lost to final router ARPing destination. 4 | */ 5 | 6 | // connectivity tests 7 | 8 | // vlan to vlan 9 | // host21 = 10.5.202.4 10 | 11 | host21 ping -c 1 10.5.102.3 // vlan2 to vlan2 12 | host21 ping -c 1 10.5.103.3 // vlan2 to 3 [cross trunk] 13 | host21 ping -c 1 10.5.103.4 // vlan2 to 3 [no trunk needed] 14 | 15 | // vlan to nonvlan 16 | 17 | host21 ping -c 1 192.168.3.3 18 | host21 ping -c 1 192.168.1.3 19 | host21 ping -c 1 192.168.2.4 20 | host21 ping -c 1 10.200.1.4 21 | 22 | // NAT/racl 23 | host1 (10.100.1.2): python -m SimpleHTTPServer 80 & 24 | 25 | // should get through and see a reply (~2 tries) 26 | host3 (10.200.1.3) curl http://10.100.1.2:80 27 | 28 | // should get as far as the reflexive ACL on egress from EXT toward host1 29 | // but the RACL will only match traffic from the NAT gateway. (so won't get to the server) 30 | host21 curl http://10.100.1.2:80 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/paper1/topo.dot: -------------------------------------------------------------------------------- 1 | graph vr1 { 2 | subgraph cluster_routers { 3 | A; 4 | B; 5 | C; 6 | D; 7 | int; 8 | ext; 9 | // root switches live in the top cluster for pretty-display 10 | sr1; 11 | sr2; 12 | sr3; 13 | sr4; 14 | sr5; 15 | sr6; 16 | } 17 | 18 | subgraph cluster_vlan2 { 19 | color=blue; 20 | h1; 21 | h5; 22 | label = "vlan 2\n(10.5.102.0/24)" 23 | }; 24 | 25 | subgraph cluster_vlan3 { 26 | color=blue; 27 | h2; 28 | h6; 29 | label = "vlan 3\n(10.5.103.0/24)" 30 | }; 31 | 32 | // A to C 33 | subgraph cluster_subnet1 { 34 | color=blue; 35 | h3 -- s1 36 | h4 -- s1 37 | s1 -- sr1 38 | label = "A to C\n(192.168.1.0/24)" // cost 20 39 | }; 40 | sr1 -- A 41 | sr1 -- C 42 | 43 | // A to D 44 | subgraph cluster_subnet2 { 45 | color=blue; 46 | h7 -- s2 47 | h8 -- s2 48 | s2 -- sr2 49 | label = "A to D\n(192.168.2.0/24)" 50 | }; 51 | sr2 -- A 52 | sr2 -- D 53 | 54 | // C to D 55 | subgraph cluster_subnet3 { 56 | color=blue; 57 | h9 -- s3 58 | h10 -- s3 59 | s3 -- sr3 60 | label = "C to D\n(192.168.3.0/24)" 61 | }; 62 | sr3 -- C 63 | sr3 -- D 64 | 65 | A -- B [label = "tr: 2,3"]; 66 | h1 -- B [label = "acc: 2"]; 67 | h2 -- B [label = "acc: 3"]; 68 | h5 -- A [label = "acc: 2"]; 69 | h6 -- A [label = "acc: 3"]; 70 | 71 | // A -- C [label ="cost: 20"]; 72 | // A -- D [label ="cost: 5"]; 73 | // C -- D [label ="cost: 10"]; 74 | 75 | // C to ext 76 | subgraph cluster_subnet4 { 77 | color=blue; 78 | h11 -- s4 79 | h12 -- s4 80 | s4 -- sr4 81 | label = "C to ext\n(10.200.0.0/16)" 82 | }; 83 | C -- sr4 84 | ext -- sr4 85 | 86 | // int to ext 87 | subgraph cluster_subnet5 { 88 | color=blue; 89 | h13 -- s5 90 | h14 -- s5 91 | s5 -- sr5 92 | label = "int to ext\n(10.100.0.0/16)" 93 | }; 94 | ext -- sr5 95 | } -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/pr/tasconfig.txt: -------------------------------------------------------------------------------- 1 | ! modified from LISA '10 paper 2 | ! made IP ranges more readable; removed an interface; removed ACLs 3 | hostname tas 4 | ! 5 | interface GigabitEthernet0/0 6 | ip address 10.232.0.1 255.255.255.0 7 | ip policy route-map internet 8 | ! 9 | interface GigabitEthernet0/1 10 | ip address 10.232.8.1 255.255.255.0 11 | 12 | ! source routing: 13 | access-list 10 permit 10.100.0.0 0.0.255.255 14 | access-list 10 permit 10.200.0.0 0.0.255.255 15 | access-list 20 permit 20.100.0.0 0.0.255.255 16 | access-list 20 permit 20.200.0.0 0.0.255.255 17 | ! 18 | ! TN: the ID "internet" has two map entries, which are checked in numeric order. 19 | ! It just so happens that in this case, the ACL ids and the entry ids are the same. 20 | ! If no "match" was specified, then the map would apply to all packets. 21 | ! 22 | route-map internet permit 10 23 | match ip address 10 24 | set ip next-hop 10.232.0.10 25 | ! 26 | route-map internet permit 20 27 | match ip address 20 28 | set ip next-hop 10.232.0.20 29 | ! 30 | end 31 | -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/triangle/configA.txt: -------------------------------------------------------------------------------- 1 | hostname vltA 2 | 3 | 4 | ! a physical L3 interface; no vlan 5 | !// To C 6 | interface GigabitEthernet1/3 7 | ip address 192.168.1.1 255.255.255.0 8 | ospf cost 20 9 | 10 | !// To D 11 | interface GigabitEthernet1/4 12 | ip address 192.168.2.1 255.255.255.0 13 | ospf cost 5 14 | 15 | end -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/triangle/configC.txt: -------------------------------------------------------------------------------- 1 | hostname vltC 2 | 3 | // To A (A uses .1) 4 | interface GigabitEthernet2/1 5 | ip address 192.168.1.2 255.255.255.0 6 | ospf cost 20 7 | 8 | // To D 9 | interface GigabitEthernet2/2 10 | ip address 192.168.3.1 255.255.255.0 11 | ospf cost 10 12 | 13 | end -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/triangle/configD.txt: -------------------------------------------------------------------------------- 1 | hostname vltD 2 | 3 | // To A (A uses .1) 4 | interface GigabitEthernet2/1 5 | ip address 192.168.2.2 255.255.255.0 6 | ospf cost 5 7 | 8 | // To C (C uses .1) 9 | interface GigabitEthernet2/2 10 | ip address 192.168.3.2 255.255.255.0 11 | ospf cost 10 12 | 13 | end -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/vlan1/configA.txt: -------------------------------------------------------------------------------- 1 | hostname vltA 2 | 3 | ! two vlans 4 | vlan 2 5 | name test2 6 | vlan 3 7 | name test3 8 | 9 | ! a trunk 10 | interface TenGigabitEthernet1/1 11 | switchport 12 | switchport trunk encapsulation dot1q 13 | switchport trunk allowed vlan 2,3 14 | switchport mode trunk 15 | 16 | ! access ports for the two vlans 17 | interface GigabitEthernet1/1 18 | switchport 19 | switchport access vlan 2 20 | switchport mode access 21 | 22 | interface GigabitEthernet1/2 23 | switchport 24 | switchport access vlan 3 25 | switchport mode access 26 | 27 | ! virtual interfaces for the two vlans (allow routing) 28 | ! A = .1, B = .2 29 | ! "switched virtual interfaces" or "management interfaces"; 30 | ! switchports don't have IP addresses themselves. 31 | ! 32 | ! can either be "interface vlan id or interface Vlanid" 33 | interface vlan 2 34 | ip address 10.1.102.1 255.255.255.0 35 | ! parser requires a comment between interfaces 36 | interface Vlan3 37 | ip address 10.1.103.1 255.255.255.0 38 | 39 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 40 | 41 | ! a physical L3 interface; no vlan 42 | interface GigabitEthernet1/3 43 | ip address 192.168.1.1 255.255.255.0 44 | 45 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 46 | 47 | ! a pair of LAN ports with no subnet 48 | interface FastEthernet0/1 49 | switchport 50 | switchport access vlan 4 51 | switchport mode access 52 | 53 | interface FastEthernet0/2 54 | switchport 55 | switchport access vlan 4 56 | switchport mode access 57 | 58 | 59 | end -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/vlan1/configB.txt: -------------------------------------------------------------------------------- 1 | hostname vltB 2 | 3 | ! two vlans 4 | vlan 2 5 | name test2 6 | vlan 3 7 | name test3 8 | 9 | ! a trunk 10 | interface TenGigabitEthernet2/1 11 | switchport 12 | switchport trunk encapsulation dot1q 13 | switchport trunk allowed vlan 2,3 14 | switchport mode trunk 15 | 16 | ! access ports for the two vlans 17 | interface GigabitEthernet2/1 18 | switchport 19 | switchport access vlan 2 20 | switchport mode access 21 | 22 | interface GigabitEthernet2/2 23 | switchport 24 | switchport access vlan 3 25 | switchport mode access 26 | 27 | ! virtual interfaces for the two vlans (allow routing) 28 | ! A = .1, B = .2 29 | interface Vlan2 30 | ip address 10.1.102.2 255.255.255.0 31 | ! 32 | interface Vlan3 33 | ip address 10.1.103.2 255.255.255.0 34 | 35 | end -------------------------------------------------------------------------------- /experimental/IOS-parser/tests/vlan1/conns.txt: -------------------------------------------------------------------------------- 1 | vltA TenGigabitEthernet1/1 vltB TenGigabitEthernet2/1 -------------------------------------------------------------------------------- /experimental/IOS-parser/tnat.sh: -------------------------------------------------------------------------------- 1 | ./ios2flowlog --path tests/nat/ ext.txt -------------------------------------------------------------------------------- /experimental/IOS-parser/tpaper.sh: -------------------------------------------------------------------------------- 1 | ./ios2flowlog --path tests/paper1/ --conn conns.txt configA.txt configB.txt configC.txt configD.txt ext.txt -------------------------------------------------------------------------------- /experimental/IOS-parser/ttri.sh: -------------------------------------------------------------------------------- 1 | ./ios2flowlog --path tests/triangle/ configA.txt configC.txt configD.txt -------------------------------------------------------------------------------- /experimental/IOS-parser/tvlan.sh: -------------------------------------------------------------------------------- 1 | ./ios2flowlog --path tests/vlan1/ configA.txt configB.txt -------------------------------------------------------------------------------- /experimental/IOS-parser/verif/Arp-props.als: -------------------------------------------------------------------------------- 1 | open Arp as arp 2 | 3 | // Remember to remove the built_ins sig every time we re-generate 4 | 5 | fact assumptions { 6 | all s: State | { 7 | // p2r is irreflexive, column partitions 8 | no iden & Switchid.(s.p2r) 9 | all sw : Switchid | no (s.p2r[sw]).Portid & (s.p2r[sw])[Portid] 10 | // router_vlan irreflexive 11 | no iden & s.router_vlan 12 | // modes are either "access" or "trunk" (no trunk used in this module) 13 | s.sp_modes[Switchid][Portid] in C_string_access //+ C_string_trunk 14 | 15 | /* force these constants to exist, otherwise expressions like 16 | C_string_access in st.sp_modes[(EVpacket <: ev).locsw][out0.locpt] 17 | will be trivially true if the model only makes C_string_access empty 18 | */ 19 | // some C_string_trunk 20 | some C_string_access 21 | 22 | 23 | // ARP packets are forced to have ARP ethtyp (this could be produced by the compiler) 24 | EVarp_packet.dltyp = C_ethtyp_arp 25 | // Never cache the controller address 26 | no s.cached[C_ipaddr_10_10_10_1] 27 | no s.cached.C_macaddr_00_00_ca_fe_ca_fe 28 | 29 | } 30 | } 31 | 32 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 33 | 34 | assert outKnownNeverRequested 35 | { 36 | all s: State, pin: EVarp_packet, pout: EVarp_packet | 37 | (arp/outpolicy[s, pin, pout] and // policy fwds thusly 38 | pin.arp_tpa in s.cached.Macaddr) // known 39 | implies 40 | pout.arp_op = C_int_2 // only ever reply 41 | } 42 | check outKnownNeverRequested 43 | for 5 but 1 State, 2 Event, 3 Switchid, 3 Portid, 9 Macaddr, 4 Ipaddr, 4 FLInt, 2 Ethtyp, 1 FLString 44 | 45 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 46 | 47 | assert outKnownReplyAndNoQueued 48 | { 49 | all s: State, pin: EVarp_packet | 50 | (pin.arp_op = C_int_1 and // ARP request incoming 51 | pin.arp_tpa in s.cached.Macaddr) // known 52 | implies 53 | { 54 | // unbounded universal issue just saying "some pout : ..." 55 | // instead say: if you've created the right packet in your scenario, it's sent 56 | (all pout: EVarp_packet | { 57 | pout.arp_op = C_int_2 58 | pout.arp_spa = pin.arp_tpa 59 | pout.arp_tha = pin.arp_sha 60 | pout.arp_tpa = pin.arp_spa 61 | pout.dltyp = pin.dltyp 62 | pout.dldst = pin.dlsrc 63 | pout.dlsrc = pout.arp_sha 64 | pout.locsw = pin.locsw 65 | pout.locpt = pin.locpt 66 | pout.dlvlan = pin.dlvlan 67 | pin.locsw not in s.switches_without_arp 68 | pin.arp_sha != C_macaddr_00_00_ca_fe_ca_fe 69 | pin.dlsrc != C_macaddr_00_00_ca_fe_ca_fe 70 | pin.arp_spa != C_ipaddr_10_10_10_1 71 | pout.arp_sha = s.cached[pin.arp_tpa] 72 | pin.arp_tpa != pin.arp_spa // not a senseless query; needed because of XOR 73 | 74 | } 75 | implies arp/outpolicy[s, pin, pout]) 76 | 77 | no { x:Ipaddr, y:Macaddr, z:Ipaddr | plus_queuedrequests[s, pin, x,y,z] } 78 | } 79 | } 80 | check outKnownReplyAndNoQueued 81 | for 5 but 1 State, 2 Event, 3 Switchid, 3 Portid, 9 Macaddr, 4 Ipaddr, 4 FLInt, 2 Ethtyp, 1 FLString 82 | 83 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 84 | 85 | 86 | /* 87 | CEILINGS 88 | 89 | Single event ceiling: 1 Switchid,1 Portid,4 Macaddr,2 Ipaddr,2 FLInt,1 Ethtyp 90 | 91 | Each of these 3 queries have 2 Events, 1 State. Multiply Ceiling by 2, and then add program-vars. 92 | +1 FLString for the assumptions (force "access" to exist) 93 | + Also need 2 Ipaddr, 1 Macaddr for the secondary part of outKnownReplyAndNoQueued [only this one] 94 | 95 | Since these queries call positive outpolicy only, and for only one output packet, it suffices to take the ceiling 96 | of all individual action rules [ignoring negatives since we don't negate or have priorities over the rules]: 97 | 98 | 1 Switchid, 1 Portid, [forward/emit only, not insert/delete; not needed] 99 | */ 100 | 101 | -------------------------------------------------------------------------------- /experimental/IOS-parser/verif/Vlan-props.als: -------------------------------------------------------------------------------- 1 | open Vlans as vlans 2 | 3 | // Remember to remove the built_ins sig every time we re-generate 4 | 5 | fact assumptions { 6 | all s: State | { 7 | // no VLAN configured to use -1 (special ID for no encap) 8 | no s.sp_vlans.C_int_neg1 9 | no s.virtual_interfaces.C_int_neg1 10 | // virtual interfaces only configured on VLAN sub-routers 11 | s.virtual_interfaces.FLInt.Portid in s.router_vlan[Switchid] 12 | // VLANs configured on physical ports only 13 | no s.sp_vlans.FLInt & s.virtual_interfaces.FLInt 14 | // p2r is irreflexive, column partitions 15 | no iden & Switchid.(s.p2r) 16 | all sw : Switchid | no (s.p2r[sw]).Portid & (s.p2r[sw])[Portid] 17 | // router_vlan irreflexive 18 | no iden & s.router_vlan 19 | // never learn a virtual interface as they are never physical 20 | no (s.virtual_interfaces).FLInt & (s.learned).Macaddr 21 | // sp_vlans and sp_mode map the same sw/pt pairs 22 | s.sp_vlans.FLInt = s.sp_modes.FLString 23 | // modes are either "access" or "trunk" 24 | s.sp_modes[Switchid][Portid] in C_string_access + C_string_trunk 25 | // if a port is a vlan physical port, it's in col 1 of p2r 26 | s.sp_vlans.FLInt in s.p2r.Portid 27 | // p2r maps vlan <--> virtual. 28 | all sw: Switchid, pp, rp: Portid | (pp -> rp) in s.p2r[sw] implies 29 | { pp in s.sp_vlans[sw].FLInt iff rp in s.virtual_interfaces[sw].FLInt 30 | // recall one virtual interface per vlan; if p2r connects, must share vlan id 31 | s.virtual_interfaces[sw][rp] in s.sp_vlans[sw][pp] 32 | } 33 | 34 | /* force these constants to exist, otherwise expressions like 35 | C_string_access in st.sp_modes[(EVpacket <: ev).locsw][out0.locpt] 36 | will be trivially true if the model only makes C_string_access empty 37 | */ 38 | some C_string_trunk 39 | some C_string_access 40 | 41 | } 42 | } 43 | 44 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 45 | 46 | assert outTrunkTagged 47 | { 48 | all s: State, pin, pout: EVpacket | 49 | (vlans/outpolicy[s, pin, pout] and // policy fwds thusly 50 | s.sp_modes[pout.locsw, pout.locpt] = C_string_trunk) // out on a trunk 51 | implies 52 | pout.dlvlan != C_int_neg1 53 | } 54 | //check outTrunkTagged for 5 55 | // for 6: 83 atoms; arity 4 is too much 56 | // for 5 is doable (~10sec, no counterexs) 57 | check outTrunkTagged for 3 but 1 State, 2 Event, 3 Switchid, 3 Portid, 4 Macaddr, 4 FLInt, 2 Ethtyp, 2 FLString 58 | 59 | 60 | // ISSUE: 61 | // in from VI; out trunk. still -1? Yes, because the VI -> host-side forgets to tag. 62 | // missing a tagging block in the routerside->hostside inter-vlan rule 63 | // ^^ FIXED 64 | 65 | // ISSUE: 66 | // in from trunk, but untagged; out on different trunk: still untagged 67 | // need an sp_modes(p.locSw, p.locPt, "access") protection on the -1 intra-vlan rule 68 | // (really, should be an error if a packet arrives untagged from a trunk) 69 | // ^^ Actually this isn't quite right. What else to do but send on ALL vlans for that trunk? 70 | 71 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 72 | 73 | // TODO: Required concession to analyzer: explicit "no change" for dlvlan field 74 | // This is because the FL->Alloy converter underconstrains ORs; defaults are only 75 | // produced at the RULE level, not the CLAUSE level. 76 | 77 | assert outAccessUntagged 78 | { 79 | all s: State, pin, pout: EVpacket | 80 | (vlans/outpolicy[s, pin, pout] and // policy fwds thusly 81 | s.sp_modes[pout.locsw, pout.locpt] = C_string_access) // out on an access port 82 | implies 83 | pout.dlvlan = C_int_neg1 // untagged 84 | } 85 | //check outAccessUntagged 86 | check outAccessUntagged for 3 but 1 State, 2 Event, 3 Switchid, 3 Portid, 4 Macaddr, 4 FLInt, 2 Ethtyp, 2 FLString 87 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 88 | 89 | // coming in tagged, will send out only interfaces with that tag. (weak isolation) 90 | assert outIntra_UseTagIfTagged 91 | { 92 | all s: State, pin, pout: EVpacket | 93 | (vlans/outpolicy[s, pin, pout] and // policy fwds thusly 94 | pin.locpt in s.sp_vlans[pin.locsw].FLInt and // in from vlan physical 95 | pout.locpt in s.sp_vlans[pout.locsw].FLInt and // out vlan physical (together w/ above = intra) 96 | pin.dlvlan != C_int_neg1) // comes in tagged 97 | implies 98 | pin.dlvlan in s.sp_vlans[pout.locsw, pout.locpt] // out on a trunk or access for that tag 99 | } 100 | //check outIntra_UseTagIfTagged for 4 101 | check outIntra_UseTagIfTagged for 3 but 1 State, 2 Event, 3 Switchid, 3 Portid, 4 Macaddr, 4 FLInt, 2 Ethtyp, 2 FLString 102 | 103 | 104 | /* 105 | CEILINGS 106 | 107 | Single event ceiling: 1 Switchid, 1 Portid, 2 Macaddr, 1 FLInt, 1 Ethtyp 108 | Each of these 3 queries have 2 Events, 1 State. Multiply Ceiling by 2, and then add program-vars. 109 | +2 FLString for the assumptions (force "access" and "trunk" to exist) 110 | 111 | Since these queries call positive outpolicy only, and for only one output packet, it suffices to take the ceiling 112 | of all individual action rules: 1 switchid, 1 portid, 1 FLint (for vlan id). 113 | */ 114 | -------------------------------------------------------------------------------- /interpreter/Flowlog_Builtins.ml: -------------------------------------------------------------------------------- 1 | open Flowlog_Types 2 | open Flowlog_Helpers 3 | open NetCore_Types 4 | open Printf 5 | open ExtList.List 6 | 7 | (* Some predicates are built in, e.g. "add". So for instance if a formula is: 8 | FAtom("", "add", [TConst("1"), TVar("x"), TVar("pt")]) the add is interpreted by this module, not 9 | treated as a state predicate. *) 10 | 11 | (* predid, 12 | arity (types), 13 | XSB definition, 14 | can-compile? predicate on specific fmla, 15 | prepare: run this once in xsb at startup 16 | compile: this is the expression to give to XSB 17 | *) 18 | type builtin_predicate = { (* ID must be lowercase *) 19 | bipid: string; 20 | biparity: typeid list; 21 | bipxsb: xsbmode -> term list -> string; 22 | (* PREFIX ALL VARIABLES WITH UNDERSCORE IN PREPARE, OR XSB INTERFACE WILL FREEZE *) 23 | bip_prepare: (string list) option; 24 | bip_compile: unit option};; 25 | 26 | (* TODO: concern about types here: add has arity (int,int,int) but also should apply to 27 | any other numeric type, like tpport or macaddr. *) 28 | 29 | exception BIPAddException of term list;; 30 | exception BIPLTException of term list;; 31 | exception BIPPrefixException of term list;; 32 | 33 | (* FAtom("", "add", ...) --> 34 | *) 35 | let bip_add_xsb (mode:xsbmode) (tl: term list): string = 36 | match tl with 37 | | [t1; t2; t3] -> 38 | (*printf "%s %s %s\n%!" (string_of_term t1) (string_of_term t2) (string_of_term t3);*) 39 | 40 | (* XSB requires the "result" to be on the LHS. E.g., 5 is X + 4 will give an error. 41 | TODO validate: how to know which variable goes on the LHS? May be multiples! 42 | E.g., Y is X + 4 is fine if preceded by r(X). 43 | For now, use 3rd arg as LHS. *) 44 | sprintf "(%s is %s + %s)" 45 | (xsb_of_term ~mode:mode t3) (xsb_of_term ~mode:mode t1) (xsb_of_term ~mode:mode t2) 46 | | _ -> raise (BIPAddException(tl));; 47 | 48 | let bip_add = { bipid="add"; 49 | biparity = ["int"; "int"; "int"]; 50 | bipxsb = bip_add_xsb; 51 | bip_prepare = None; 52 | bip_compile = None};; 53 | 54 | (*********************************************************************************) 55 | 56 | let bip_lt_xsb (mode:xsbmode) (tl: term list): string = 57 | match tl with 58 | | [t1; t2] -> 59 | (*printf "%s %s %s\n%!" (string_of_term t1) (string_of_term t2) (string_of_term t3);*) 60 | sprintf "(%s < %s)" 61 | (xsb_of_term ~mode:mode t1) (xsb_of_term ~mode:mode t2) 62 | | _ -> raise (BIPLTException(tl));; 63 | 64 | let bip_lessthan = { bipid="lessthan"; 65 | biparity = ["int"; "int"]; 66 | bipxsb = bip_lt_xsb; 67 | bip_prepare = None; 68 | bip_compile = None};; 69 | 70 | (*********************************************************************************) 71 | 72 | (* hasLongerPrefixMatch(p.locSw, pre, mask) 73 | HARD CODED to use a SPECIFIC table name and arity! *) 74 | 75 | let bip_has_longer_xsb (mode:xsbmode) (tl: term list) : string = 76 | match tl with 77 | | [sw;dst;pre;mask] -> 78 | printf "\nInvoked: hasLongerPrefix: %s %s %s %s\n%!" (string_of_term sw) (string_of_term dst) (string_of_term pre) (string_of_term mask); 79 | sprintf "hasLongerPrefix(%s, %s, %s, %s)" 80 | (xsb_of_term ~mode:mode sw) (xsb_of_term ~mode:mode dst) (xsb_of_term ~mode:mode pre) (xsb_of_term ~mode:mode mask) 81 | | _ -> raise (BIPPrefixException(tl));; 82 | 83 | (* Note the hard-coded relation name (routes) and arity. 84 | Dest is in this new range, and the new range is smaller, and in the old range *) 85 | let bip_prepare_prefix_xsb = 86 | ["assert((hasLongerPrefix(_SW, _DST, _PRE, _MASK) :- routes(_SW, _P2, _M2, _), _M2 > _MASK, in_ipv4_range(_P2, _PRE, _MASK), in_ipv4_range(_DST, _P2, _M2)))."; 87 | "assert((getPossibleLongerPrefixMasks(_SW, _PRE, _MASK, _P2, _M2) :- routes(_SW, _P2, _M2, _), _M2 > _MASK, in_ipv4_range(_P2, _PRE, _MASK)))."];; 88 | 89 | let bip_hasLongerPrefixMatch = {bipid = "haslongerprefixmatch"; 90 | biparity = ["switchid"; "ipaddr"; "ipaddr"; "int"]; 91 | bipxsb = bip_has_longer_xsb; 92 | bip_prepare = Some bip_prepare_prefix_xsb; 93 | bip_compile = Some () 94 | } 95 | 96 | (*********************************************************************************) 97 | 98 | (* inLocalSubnet(p.locSw, p.nwDst) 99 | HARD CODED to use a SPECIFIC table name and arity! *) 100 | 101 | let bip_in_local_xsb (mode:xsbmode) (tl: term list) : string = 102 | match tl with 103 | | [sw;dst] -> 104 | printf "\nInvoked: inLocalSubnet: %s %s\n%!" (string_of_term sw) (string_of_term dst); 105 | sprintf "inLocalSubnet(%s, %s)" 106 | (xsb_of_term ~mode:mode sw) (xsb_of_term ~mode:mode dst) 107 | | _ -> raise (BIPPrefixException(tl));; 108 | 109 | (* getting local subnets is just subnets(_SW, _PRE, _MASK, _,_,_)*) 110 | let bip_prepare_local_xsb = 111 | ["assert((inLocalSubnet(_SW, _DST) :- subnets(_PRE, _MASK, _, _, _SW, _), in_ipv4_range(_DST, _PRE, _MASK)))."];; 112 | 113 | let bip_isLocalSubnet = {bipid = "inlocalsubnet"; 114 | biparity = ["switchid"; "ipaddr"]; 115 | bipxsb = bip_in_local_xsb; 116 | bip_prepare = Some bip_prepare_local_xsb; 117 | bip_compile = Some () 118 | } 119 | 120 | (*********************************************************************************) 121 | 122 | (**************************************) 123 | (* WARNING: when adding new built-in, also add it to Flowlog_Helpers.get_non_field_head_vars_generated_by_builtins *) 124 | let builtin_predicates = [(bip_add.bipid, bip_add); 125 | (bip_hasLongerPrefixMatch.bipid, bip_hasLongerPrefixMatch); 126 | (bip_isLocalSubnet.bipid, bip_isLocalSubnet); 127 | (bip_lessthan.bipid, bip_lessthan)];; 128 | 129 | 130 | let is_built_in (relname: string): bool = 131 | mem_assoc relname builtin_predicates;; 132 | 133 | let get_built_in (relname: string): builtin_predicate = 134 | assoc relname builtin_predicates;; 135 | 136 | let is_uncompilable_built_in (relname: string): bool = 137 | is_built_in relname && (get_built_in relname).bip_compile = None;; 138 | 139 | let xsb_for_built_in (mode:xsbmode) (relname: string) (tl: term list): string = 140 | let bip = get_built_in relname in 141 | bip.bipxsb mode tl;; 142 | 143 | (* if unused for... do ... ? *) -------------------------------------------------------------------------------- /interpreter/Flowlog_Graphs.ml: -------------------------------------------------------------------------------- 1 | open Flowlog_Types 2 | open Flowlog_Parse_Helpers 3 | open Flowlog_Helpers 4 | open Printf 5 | open ExtList.List 6 | open Yojson 7 | 8 | (**********************************************************************) 9 | (* Produce dependency graphs (and associated relations) for Flowlog programs *) 10 | (**********************************************************************) 11 | 12 | (* ~~ Expect we'll eventually add new fields 13 | e.g., conditions on events/packets. For now, 14 | just table-level dependencies. 15 | 16 | ~~ +/-/empty denotes result of the dependency. e.g. 17 | ("learned", "packet_in", "+") means that packet_in affects 18 | learned positively. 19 | *) 20 | 21 | type data_node = | NLocalTable of string 22 | | NRemoteTable of string 23 | | NIncomingTable of string 24 | | NOutgoingTable of string;; 25 | 26 | type data_edge = { dsrc: data_node; 27 | dsink: data_node; 28 | mode: string};; 29 | 30 | type depend_graph = {datanodes: data_node list; 31 | dependencies: data_edge list};; 32 | 33 | type named_program = {program: flowlog_program; 34 | name: string} 35 | 36 | let make_data_node (prgm: named_program) (r: string): data_node = 37 | if is_local_table prgm.program r then NLocalTable(r) 38 | else if is_remote_table prgm.program r then NRemoteTable(r) 39 | else if is_incoming_table prgm.program r then NIncomingTable(r) 40 | else if is_outgoing_table prgm.program r then NOutgoingTable(r) 41 | else failwith ("make_data_node: unknown "^r);; 42 | 43 | let depends_from_rule (prgm: named_program) (datamod: string) (triggerrel: string) 44 | (headrel: string) (wherefmla: formula): data_edge list = 45 | let sink = make_data_node prgm headrel in 46 | let body_atoms = get_atoms wherefmla in 47 | let body_sources = map (make_data_node prgm) 48 | (map atom_to_relname body_atoms) in 49 | let trigger_source = make_data_node prgm triggerrel in 50 | map (fun src -> {dsrc = src; dsink=sink; mode=datamod}) (trigger_source::body_sources);; 51 | 52 | let depends_from_clause (prgm: named_program) (cl: clause): data_edge list = 53 | match cl.orig_rule.action with 54 | | AInsert(headrel, _, fmla) -> 55 | depends_from_rule prgm "+" cl.orig_rule.onrel headrel fmla 56 | | ADelete(headrel, _, fmla) -> 57 | depends_from_rule prgm "-" cl.orig_rule.onrel headrel fmla 58 | | ADo(headrel, _, fmla) -> 59 | depends_from_rule prgm "" cl.orig_rule.onrel headrel fmla 60 | | AForward(p, fmla, tout) -> 61 | depends_from_rule prgm "" cl.orig_rule.onrel "forward" fmla 62 | | AStash(p, where, until, thens) -> 63 | depends_from_rule prgm "" cl.orig_rule.onrel "stash" where;; 64 | 65 | let enhance_graph_with_clause (prgm: named_program) (acc: depend_graph) (cl: clause): depend_graph = 66 | let new_dependencies = unique (depends_from_clause prgm cl @ acc.dependencies) in 67 | let new_datanodes = unique (fold_left (fun acc dep -> dep.dsrc :: dep.dsink :: acc) [] new_dependencies) in 68 | {datanodes = new_datanodes; dependencies = new_dependencies};; 69 | 70 | (* Roll over all the clauses, adding nodes/edges for each *) 71 | let build_depend_graph (prgms: named_program list): depend_graph = 72 | fold_left (fun (acc: depend_graph) (prg: named_program) -> 73 | fold_left (enhance_graph_with_clause prg) acc prg.program.clauses) 74 | {datanodes = []; dependencies = []} prgms;; 75 | 76 | let string_of_data_node (n: data_node): string = 77 | match n with 78 | | NLocalTable(str) -> "TABLE("^str^")" 79 | | NRemoteTable(str) -> "REMOTE("^str^")" 80 | | NIncomingTable(str) -> "IN("^str^")" 81 | | NOutgoingTable(str) -> "OUT("^str^")";; 82 | 83 | let get_nodes_affecting (datamode: string) (es: data_edge list) (n: data_node): data_node list = 84 | filter_map (fun e -> 85 | if e.dsink <> n || e.mode <> datamode then None 86 | else Some e.dsrc) 87 | es;; 88 | let get_nodes_affected_by (es: data_edge list) (n: data_node): data_node list = 89 | filter_map (fun e -> 90 | if e.dsrc <> n then None 91 | else Some e.dsink) 92 | es;; 93 | 94 | let depends_or_nothing (datamode: string) (nodes: data_node list): string = 95 | if (length nodes) > 0 then 96 | sprintf "%s: %s" datamode (String.concat ", " (map string_of_data_node nodes))^"\n" 97 | else "";; 98 | 99 | let string_of_edges (g: depend_graph): string = 100 | String.concat "\n" (map (fun e -> (string_of_data_node e.dsink) 101 | ^", "^(e.mode)^", "^ 102 | string_of_data_node e.dsrc) 103 | g.dependencies);; 104 | 105 | (* This includes a line for nodes that have no edges at all *) 106 | let pretty_string_of_dependencies (g: depend_graph): string = 107 | let nodestrs = map (fun (n: data_node) -> 108 | let plus_affecting = (get_nodes_affecting "+" g.dependencies n) in 109 | let minus_affecting = (get_nodes_affecting "-" g.dependencies n) in 110 | let do_affecting = (get_nodes_affecting "" g.dependencies n) in 111 | (string_of_data_node n) 112 | ^" <-- \n"^ 113 | (depends_or_nothing " +" plus_affecting)^ 114 | (depends_or_nothing " -" minus_affecting)^ 115 | (depends_or_nothing " do" do_affecting)) 116 | g.datanodes in 117 | 118 | String.concat "\n" nodestrs;; 119 | 120 | let files_to_graph (fnames: string list): depend_graph = 121 | let prgms = map (fun fname -> 122 | {name=fname; program=desugared_program_of_ast (read_ast fname) fname}) fnames in 123 | build_depend_graph prgms;; 124 | 125 | (* Remember that cycles in the dependency graph do not mean Datalog recursion. 126 | E.g. TABLE(ucst) <- TABLE(ucst) means that a modification (+ or -) of ucst 127 | depends on its current value.*) 128 | 129 | (* TODO: use program name to disambiguate relation names *) 130 | 131 | let node_to_json (d: data_node): json = 132 | `String(string_of_data_node d);; 133 | 134 | let edge_to_json (e: data_edge): json = 135 | `List([node_to_json e.dsrc; node_to_json e.dsink; `String e.mode]);; 136 | 137 | let json_graph (g: depend_graph) : json = 138 | `Assoc([("datanodes", `List (map node_to_json g.datanodes)); 139 | ("dependencies",`List (map edge_to_json g.dependencies))]);; 140 | 141 | let output_single_dependency_graph (fn: string): unit = 142 | let out = open_out ((Filename.chop_extension fn)^".json") in 143 | Yojson.pretty_to_channel out (json_graph (files_to_graph [fn])); 144 | close_out out;; 145 | 146 | let example_print(): unit = 147 | printf "%s\n%!" (pretty_string_of_dependencies (files_to_graph ["examples/NIB.flg"; "examples/Mac_Learning.flg"])); 148 | printf "%s\n%!" (string_of_edges (files_to_graph ["examples/Mac_Learning.flg"]));; 149 | -------------------------------------------------------------------------------- /interpreter/Flowlog_Safety.ml: -------------------------------------------------------------------------------- 1 | open Flowlog_Types 2 | open Flowlog_Helpers 3 | open Printf 4 | open ExtList.List 5 | open Xsb_Communication 6 | 7 | (* if the body contains no triggering atom, safety is called "strong safety" *) 8 | 9 | (* Need to know context in order to expand variable names into component fields as needed *) 10 | let get_safe_terms (prgm: flowlog_program) (cl_for_context: clause option) (fmla: formula): term list = 11 | (* only keep positive atomic formulas *) 12 | let atoms = filter_map (fun (s,a) -> if s = true then Some(a) else None) (get_atoms_with_sign fmla) in 13 | let eqs = filter_map (fun (s,a) -> if s = true then Some(a) else None) (get_equalities fmla) in 14 | 15 | (* Don't count IN as making something safe. new.nwSrc IN 10.0.0.0/8 is "safe" but not SAFE. *) 16 | (*let ins = filter_map (fun s,a -> if s = true then Some(a) else None) get_ins body in*) 17 | 18 | (* todo concern: what if the atom is a built-in predicate? is that still valid? *) 19 | let get_immediate_safe_terms_from (f: formula): term list = 20 | match f with 21 | | FAtom(modname, relname, tl) -> 22 | (* If this is a field variable, we need to say that every field is safe, not just a variable. 23 | If this variable appears in a SameAsOn action pipe like forward(v), need to take into account the type of the trigger. *) 24 | 25 | if is_incoming_table prgm relname then 26 | begin 27 | let on_context = (match cl_for_context with | None -> None | Some cl -> Communication.get_on_context prgm cl) in 28 | let pfields = Communication.decls_expand_fields prgm modname relname on_context 1 (hd tl) in 29 | pfields@tl 30 | end 31 | else 32 | tl 33 | | FEquals(x, TConst(_)) 34 | | FEquals(TConst(_), x) -> [x] | _ -> [] in 35 | let get_equal_deps (eq: formula): (term * term) list = 36 | match eq with | FEquals(TConst(_), x) | FEquals(x, TConst(_)) -> [] | FEquals(t1, t2) -> [(t1,t2);(t2,t1)] | _ -> [] in 37 | 38 | let immediates = unique (flatten (map get_immediate_safe_terms_from (atoms @ eqs))) in 39 | let eqsteps = unique (flatten (map get_equal_deps eqs)) in 40 | 41 | let rec gst_helper (proven: term list): term list = 42 | let new_proven = unique (proven @ 43 | (* follow the dependencies discovered via equalities *) 44 | (filter_map (fun (ante,cons) -> if mem ante proven then Some(cons) else None) eqsteps) @ 45 | (* Also: If TVar(x) is proven, then so too is TField(x, f) for any f. Check after the fact: *) 46 | (filter_map (fun (ante,cons) -> 47 | (match ante with 48 | | TField(v, f) -> if mem (TVar(v)) proven then Some(cons) else None 49 | | _ -> None)) eqsteps) 50 | ) in 51 | if (length new_proven) > (length proven) then gst_helper new_proven 52 | else proven in 53 | gst_helper immediates;; 54 | -------------------------------------------------------------------------------- /interpreter/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: dummy all clean yacc .force 2 | 3 | YACC_DIR = yacc 4 | 5 | OCAMLBUILD = ocamlbuild -use-ocamlfind -I thrift -I thrift/gen-ocaml 6 | 7 | default: yacc flowlog.native 8 | @echo "Compiled FlowLog. If you would rather build everyting, use 'make all'" 9 | 10 | tests: yacc Tests.native 11 | @echo "Built Flowlog tests." 12 | 13 | all: $(patsubst %.ml,%.native,$(wildcard *.ml)) 14 | 15 | %.native: .force 16 | $(OCAMLBUILD) -cflag -g -cflag -ppopt -cflag -lwt-debug $@ 17 | 18 | clean: 19 | $(OCAMLBUILD) -clean 20 | 21 | yacc: 22 | $(MAKE) -C $(YACC_DIR) 23 | -------------------------------------------------------------------------------- /interpreter/README.md: -------------------------------------------------------------------------------- 1 | 2 | The code for the interpreter goes here. 3 | 4 | Test cases (Flowlog source) are in the subfolder. 5 | 6 | You'll need to install: packet, openflow, and frenetic packages, along with 7 | their dependencies. The best way to do this is via their repos, rather than 8 | using opam. 9 | 10 | Via opam, you should also install extlib, thrift, and yojson. 11 | 12 | make clean 13 | make reinstall 14 | 15 | If that doesn't work, try: 16 | 17 | make clean 18 | make 19 | sudo make install 20 | 21 | -------------------------------------------------------------------------------- /interpreter/SafeEmitQueue.ml: -------------------------------------------------------------------------------- 1 | open Printf;; 2 | open NetCore_Types;; 3 | 4 | (* 5 | Provide a safe queue for passing packets to emit between Thrift's (OCaml) Thread and 6 | NetCore's (LWT) threads. Use a pipe to avoid polling. 7 | 8 | Also need to safe push new policies. 9 | *) 10 | 11 | class cl_SafeEmitQueue = 12 | object 13 | (* Docs: "The first component of the result is opened for reading, that's the exit to the pipe. 14 | The second component is opened for writing, that's the entrance to the pipe." *) 15 | val emit_fds = Lwt_unix.pipe_in (); (* write: unix; read: lwt_unix*) 16 | val policy_fds = Lwt_unix.pipe_in (); (* write: unix; read: lwt_unix*) 17 | val mutable to_emit = []; 18 | val mutable to_push_policy = None; 19 | val queue_mutex = Mutex.create (); 20 | 21 | method enqueue_emit (emit: (NetCore_Types.switchId * NetCore_Types.portId * OpenFlow0x01_Core.payload) option): unit = 22 | printf "enqueue_emit in current thread ID: %d\n%!" (Thread.id (Thread.self ())); 23 | let _,wr = emit_fds in 24 | Mutex.lock queue_mutex; 25 | to_emit <- emit::to_emit; 26 | Mutex.unlock queue_mutex; 27 | ignore (Unix.write wr "0" 0 1); 28 | printf "enqueued..."; 29 | (); (* write one byte to the pipe, containing zero *) 30 | 31 | method dequeue_emit (): (NetCore_Types.switchId * NetCore_Types.portId * OpenFlow0x01_Core.payload) option list Lwt.t = 32 | let rd,_ = emit_fds in 33 | Lwt_unix.set_blocking rd true; 34 | lwt bl = (Lwt_unix.blocking rd) in 35 | printf "dequeue_emit in current thread ID: %d. blocking for rd: %b\n%!" (Thread.id (Thread.self ())) bl; 36 | let buff = String.create 2 in 37 | (* Threat: not checking to see if the write and read go through correctly *) 38 | (* Imperative to use Lwt_unix here! If not, will block NetCore's entire set of LWTs *) 39 | lwt read_result = Lwt_unix.read rd buff 0 1 in 40 | printf "dequeueing..."; 41 | Mutex.lock queue_mutex; 42 | let result = to_emit in 43 | to_emit <- []; 44 | Mutex.unlock queue_mutex; 45 | Lwt.return result; 46 | 47 | (* enqueuing a policy is done within the XSB mutex in RTN *) 48 | method enqueue_pol (policy: pol): unit = 49 | printf "enqueue_pol in current thread ID: %d\n%!" (Thread.id (Thread.self ())); 50 | let _,wr = policy_fds in 51 | Mutex.lock queue_mutex; 52 | (match to_push_policy with 53 | | Some x -> printf "OVERWRITING PREVIOUS POLICY THAT HAS NOT BEEN READ\n%!" 54 | | None -> printf "No policy to overwrite\n%!"); 55 | to_push_policy <- Some policy; 56 | Mutex.unlock queue_mutex; 57 | ignore (Unix.write wr "0" 0 1); 58 | printf "enqueued..."; 59 | (); (* write one byte to the pipe, containing zero *) 60 | 61 | method dequeue_pol (): pol option Lwt.t = 62 | let rd,_ = policy_fds in 63 | Lwt_unix.set_blocking rd true; 64 | lwt bl = (Lwt_unix.blocking rd) in 65 | printf "dequeue_pol in current thread ID: %d. blocking for rd: %b\n%!" (Thread.id (Thread.self ())) bl; 66 | let buff = String.create 2 in 67 | (* Threat: not checking to see if the write and read go through correctly *) 68 | (* Imperative to use Lwt_unix here! If not, will block NetCore's entire set of LWTs *) 69 | lwt read_result = Lwt_unix.read rd buff 0 1 in 70 | printf "dequeueing..."; 71 | Mutex.lock queue_mutex; 72 | let result = to_push_policy in 73 | to_push_policy <- None; 74 | Mutex.unlock queue_mutex; 75 | Lwt.return result; 76 | 77 | end;; 78 | -------------------------------------------------------------------------------- /interpreter/_tags: -------------------------------------------------------------------------------- 1 | true : package(lwt), package(oUnit), package(extlib), package(netcore), package(str), package(packet), package(openflow), package(thrift), thread 2 | true : package(yojson) 3 | true : syntax(camlp4o -lwt-debug), package(lwt.syntax) 4 | -------------------------------------------------------------------------------- /interpreter/examples/AppleTV/AppleTV.flg: -------------------------------------------------------------------------------- 1 | /* 2 | * An application to create paired flows for multicast traffic between an 3 | * AppleTV and the host which wishes to use it. 4 | * 5 | * TODO: 6 | * - Rules should be placed on the switches where the hosts are attached 7 | * - Make state soft state -- requires timeouts. 8 | * - We should delete entries in atv_cxns if either side moves -- this would 9 | * represent a laptop leaving a given conference room, for example. 10 | * - Also remove pairings by listening to client-initated IGMP departures 11 | * 12 | * - Might not actually need IGMP messages. Also, might only need 1 mDNS msg 13 | */ 14 | 15 | /******************************************************************************* 16 | * 17 | * Data Structures 18 | * 19 | ******************************************************************************/ 20 | 21 | EVENT atv_connect { atv_mac: macaddr, client_mac: macaddr }; 22 | EVENT atv_announce { atv_mac: macaddr, client_mac: macaddr }; 23 | EVENT atv_disconnect { mac: macaddr }; 24 | 25 | TABLE atv_cxns(macaddr, macaddr); 26 | 27 | /******************************************************************************* 28 | * 29 | * Handle updates to the database 30 | * 31 | ******************************************************************************/ 32 | 33 | ON atv_connect(evt): // XXX: can't be named "event" btw (conflict with keyword) 34 | INSERT (evt.atv_mac, evt.client_mac) INTO atv_cxns; 35 | INSERT (evt.client_mac, evt.atv_mac) INTO atv_cxns; 36 | 37 | ON atv_announce(evt): 38 | DO emit_igmp(new) WHERE 39 | (( new.dlDst = evt.client_mac and 40 | new.dlSrc = evt.atv_mac and 41 | new.nwSrc = 0x0A0A006A // TODO(adf): do we need correct IP addrs? 42 | ) OR ( 43 | new.dlDst = evt.atv_mac and 44 | new.dlSrc = evt.client_mac and 45 | new.nwSrc = 0x0A0A0064 // TODO(adf): do we need correct IP addrs? 46 | )) and 47 | new.nwDst = 224.0.0.22 and // 224.0.0.22 (IGMP) 48 | //new.nwTtl = 1 and 49 | new.igmp_ver_and_typ = 0x22 and 50 | new.igmp_v3typ = 0x4 and 51 | new.igmp_addr = 224.0.0.251 and // 224.0.0.251 (mDNS) 52 | new.locSw = 1 and // DPID of switch controller is connected to 53 | new.locPt = 0xfff9; // OFPP_TABLE 54 | 55 | DO emit_mdns(new) WHERE 56 | new.dlDst = evt.atv_mac and 57 | new.dlSrc = evt.client_mac and 58 | new.nwSrc = 10.10.0.1 and // 10.10.0.0.1 (controller) 59 | new.nwDst = 224.0.0.251 and // 224.0.0.251 (IGMP) 60 | new.tpSrc = 5353 and 61 | new.tpDst = 5353 and 62 | (new.mdns_question = "_airplay._tcp.local" OR 63 | new.mdns_question = "_appletv._tcp.local" OR 64 | new.mdns_question = "_raop._tcp.local") and 65 | new.locSw = 1 and // DPID of switch controller is connected to (0x1b219cfe57) 66 | new.locPt = 0xfff9; // OFPP_TABLE 67 | 68 | 69 | // For now, let's go with a model that either side can break the connection 70 | ON atv_disconnect(evt): 71 | DELETE (evt.mac, ANY) FROM atv_cxns; 72 | DELETE (ANY, evt.mac) FROM atv_cxns; 73 | // TODO: also generate IGMP membership departure (igmp_v3typ = 0x03) 74 | 75 | /******************************************************************************* 76 | * 77 | * Packet handling 78 | * 79 | ******************************************************************************/ 80 | 81 | ON udp_packet(pkt) WHERE pkt.dlDst = 01:00:5e:00:00:fb and // mDNS IPv4 ethernet address 82 | pkt.nwDst = 224.0.0.251 and // 224.0.0.251 (mDNS group) 83 | pkt.tpDst = 5353: // (mDns port) 84 | 85 | DO forward(new) WHERE 86 | atv_cxns(pkt.dlSrc, new.dlDst) and 87 | pkt.locPt = 4 and // TODO(adf): temp HACK since Pronto does not support wildcarded 88 | // in-port for rules with more than one output port (eg, ALL). 89 | // We should be setting this more carefully based on the NIB, but 90 | // for now, capitalize on the fact that the AppleTV is connected 91 | // to the same port num on the Pronto as wifi is on the Pantou. 92 | new.locPt != pkt.locPt; // flooding; TODO: should be forwarding directly 93 | -------------------------------------------------------------------------------- /interpreter/examples/AppleTV/ArpAppleMac.flg: -------------------------------------------------------------------------------- 1 | 2 | INCLUDE "examples/AppleTV/AppleTV.flg"; 3 | 4 | INCLUDE "examples/Arp_Cache.flg"; 5 | 6 | INCLUDE "examples/AppleTV/Mac_Learning.inc.flg"; 7 | 8 | -------------------------------------------------------------------------------- /interpreter/examples/AppleTV/Mac_Learning.inc.flg: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * 3 | * Primitive contents of "Mac_Learning.inc.flg" 4 | * 5 | ******************************************************************************/ 6 | 7 | TABLE learned(switchid, portid, macaddr); 8 | 9 | TABLE switches_without_mac_learning(switchid); 10 | 11 | /* 12 | * TODO: how should we create the syntax to indicate that MAC learning should 13 | * not be handle packets handled elsewhere? at the same time, it is the 14 | * forwarding module, so we may want it to handle traffic which doesn't enter 15 | * via packet_in anyway. Note that we *do* want to learn from the ARP traffic, 16 | * although we are ignoring it for now... 17 | * 18 | * TODO: Positioning of "pkt.dlTyp != 0x0806" is an open question -- Can we 19 | * achieve both of these goals? 20 | * 1) learn from packets which come through the system 21 | * 2) not affect forwarding of ARP traffic in any way? (including via the 22 | * INSERT rule) 23 | * When positioned on the initial `ON packet_in(pkt)`, we get smaller flow 24 | * tables from NetCore (helping compensate for NetCore issue #142), but we don't 25 | * learn anything from ARP traffic. A wholly different approach would be for the 26 | * ARP handlers above to explicitly add to the learned relation, but I don't 27 | * like that mixing as it violates modularity... 28 | * 29 | */ 30 | 31 | ON packet(pkt) WHERE pkt.dlTyp != 0x0806 and // ARP 32 | pkt.dlDst != 0x01005e000016 and // IGMP ethernet multicast. 33 | // this should be an explicit drop in AppleTV.flg. 34 | pkt.dlDst != 0x3333000000fb and // mDNS IPv6 ethernet multicast 35 | pkt.dlDst != 0x01005e0000fb and // mDNS IPv4 ethernet multicast 36 | 37 | // TODO(adf): drop 802.1D bridge-local frames 38 | not switches_without_mac_learning(pkt.locSw): 39 | 40 | // ***TODO*** Add these restrictions (which only exist for limiting to-controller traffic) 41 | // automatically. But not always easy! Naive learned/notlearned have some issues 42 | // in that to use them one sometimes needs complex joins? 43 | 44 | // Instead, bespoke optimizations that can be tested via change impact. (old, unoptimized == 45 | // new, optimized?) 46 | 47 | INSERT (pkt.locSw, pkt.locPt, pkt.dlSrc) INTO learned WHERE 48 | not learned(pkt.locSw, pkt.locPt, pkt.dlSrc); 49 | 50 | DELETE (pkt.locSw, pt, pkt.dlSrc) FROM learned WHERE 51 | not pt = pkt.locPt 52 | // The optimization requirement: ctrlr will learn something new 53 | AND not learned(pkt.locSw, pkt.locPt, pkt.dlSrc); 54 | 55 | // This rule doesn't have the negations all at the end of the 56 | // resulting clauses. If no pkts forwarded, it means negation-shuffling has failed. 57 | DO forward(new) WHERE 58 | (learned(pkt.locSw, new.locPt, pkt.dlDst) AND 59 | // NOTE: this line wasn't required in fully reactive version. 60 | // But now, we may have learned (sw=x,pt=y,mac=z) and see a packet for z 61 | // on port y (because the switch on that end hasn't yet learned where z is.) 62 | // This is missing from the stock Mac Learning Frenetic functionality, and 63 | // lacking it can result in dupe packets when pinging at topo=tree,3,3 64 | pkt.locPt != new.locPt 65 | ) 66 | OR 67 | (NOT learned(pkt.locSw, x, pkt.dlDst) AND 68 | NOT pkt.locPt = new.locPt 69 | // Commented out as **optimization**, only sound if compiled. 70 | // (If interpreted in XSB, will get a problem because new.locPt isn't bound) 71 | // AND switch_has_port(pkt.locSw, new.locPt) 72 | ); 73 | 74 | 75 | ON switch_down(swd): 76 | DELETE (swd.sw, ANY, ANY) FROM learned; 77 | -------------------------------------------------------------------------------- /interpreter/examples/Arp_Cache.flg: -------------------------------------------------------------------------------- 1 | /* 2 | * A simple ARP proxy with cache. 3 | * 4 | * All ARP requests are sent to the controller (except those issued by the 5 | * controller, of course). The controller attempts to reply to them from its 6 | * cache. If the mapping is not found in the cache, then the controller issues 7 | * its own ARP request to answer the original. We don't want to issue more 8 | * than one ARP request to any given host, thus we keep a queue of outstanding 9 | * requests which we are actively proxying. 10 | * 11 | * TODO: 12 | * - Implement with soft state. Requires timeouts and enables updates. 13 | * - Replace controller MAC and IP addresses & arp_op with constants 14 | * (requires FlowLog improvements). 15 | */ 16 | 17 | TABLE switches_without_arp(switchid); 18 | 19 | /******************************************************************************* 20 | * 21 | * Data Structures 22 | * 23 | ******************************************************************************/ 24 | 25 | // Cache for issuing ARP replies. Maps IP to MAC address. 26 | TABLE cached(ipaddr, macaddr); 27 | 28 | // Table of queued ARP requests for which we are awaiting a reply from the target 29 | // 30 | // Column values: 31 | // request ip, src mac, src ip, src switch, src port 32 | // [last 3 should live in NIB; join with NIB] 33 | TABLE queued(ipaddr, macaddr, ipaddr, switchid, portid); 34 | 35 | /******************************************************************************* 36 | * 37 | * Only flood ARP requests from the controller 38 | * 39 | ******************************************************************************/ 40 | 41 | ON arp_packet(request) WHERE request.dlSrc = 00:00:ca:fe:ca:fe // Controller MAC 42 | and not switches_without_arp(request.locSw): 43 | DO forward(new) WHERE 44 | new.locPt != request.locPt; 45 | 46 | /******************************************************************************* 47 | * 48 | * Intercept and process all ARP requests not from the controller 49 | * 50 | ******************************************************************************/ 51 | 52 | ON arp_packet(request) WHERE request.arp_op = 1 AND 53 | request.dlSrc != 00:00:ca:fe:ca:fe // Controller MAC 54 | and not switches_without_arp(request.locSw): 55 | 56 | // 57 | // If we have a hardware addr cached for the target protocol addr, 58 | // or it is a self-request, then emit reply: 59 | // 60 | 61 | DO emit_arp(new) WHERE 62 | (cached(request.arp_tpa, new.arp_sha) XOR // XOR: don't dup packets when also cached 63 | (request.arp_tpa = request.arp_spa and 64 | new.arp_sha = request.arp_sha) 65 | ) and 66 | 67 | // Make it an ARP reply 68 | new.arp_op = 2 and 69 | 70 | // Constrain other ARP fields based on request 71 | new.arp_spa = request.arp_tpa and 72 | new.arp_tha = request.arp_sha and 73 | new.arp_tpa = request.arp_spa and 74 | 75 | // Constrain ethernet header appropriately 76 | new.dlDst = request.dlSrc and 77 | new.dlSrc = new.arp_sha and 78 | 79 | // Finally, turn packet around (set exit port as entrance) 80 | new.locSw = request.locSw and 81 | new.locPt = request.locPt; 82 | 83 | // 84 | // If request is NOT cached and is NOT from us (the controller), 85 | // then add it to the table of requests queued their reply 86 | // 87 | 88 | INSERT (request.arp_tpa, request.arp_sha, request.arp_spa, 89 | request.locSw, request.locPt) INTO queued WHERE 90 | not cached(request.arp_tpa, ANY); 91 | 92 | // 93 | // Finally, issue an arp request from the controller for unlearned 94 | // addresses without an existing queued request. The new request is 95 | // issued from the same switch that started the process. 96 | // 97 | 98 | DO emit_arp(new) WHERE 99 | not cached(request.arp_tpa, ANY) and 100 | not queued(request.arp_tpa, ANY, ANY, ANY, ANY) and 101 | not request.arp_tpa = request.arp_spa and // skip self-requests 102 | new.locSw = request.locSw and 103 | new.dlDst = request.dlDst and 104 | new.dlSrc = 00:00:ca:fe:ca:fe and 105 | 106 | // Make it an ARP request 107 | new.arp_op = 1 and 108 | 109 | // Constrain other ARP fields based on original request 110 | new.arp_tpa = request.arp_tpa and 111 | new.arp_sha = 00:00:CA:FE:CA:FE and // Controller MAC 112 | new.arp_spa = 10.10.10.1; // Controller IP addr (10.10.10.1) 113 | 114 | // 115 | // Bonus: We can cache the source of this ARP request 116 | // 117 | 118 | INSERT (request.arp_spa, request.arp_sha) INTO cached WHERE 119 | not cached(request.arp_spa, ANY); 120 | 121 | /******************************************************************************* 122 | * 123 | * Handle all ARP replies 124 | * 125 | * - Must explicitly specify that no replies will be from the controller; 126 | * otherwise, NetCore will try to send copy all controller-issued ARP 127 | * traffic back to the controller. 128 | * 129 | ******************************************************************************/ 130 | 131 | ON arp_packet(reply) WHERE reply.arp_op = 2 AND 132 | reply.dlSrc != 00:00:ca:fe:ca:fe 133 | and not switches_without_arp(reply.locSw): 134 | 135 | // 136 | // Learn from replies 137 | // (No support for changing IP address here: once learned, always learned) 138 | // 139 | 140 | INSERT (reply.arp_spa, reply.arp_sha) INTO cached WHERE 141 | not cached(reply.arp_spa, ANY); 142 | 143 | // 144 | // Clear requests queued for this reply 145 | // 146 | 147 | DELETE (reply.arp_spa, ANY, ANY, ANY, ANY) FROM queued; 148 | 149 | // 150 | // Send replies for any queued requests 151 | // 152 | 153 | DO emit_arp(new) WHERE 154 | // Make it an ARP reply 155 | new.arp_op = 2 and 156 | 157 | // Constrain ARP-protocol fields based on queued request 158 | queued(reply.arp_spa, new.arp_tha, new.arp_tpa, new.locSw, new.locPt) and 159 | 160 | // Constrain remaining fields 161 | new.dlDst = new.arp_tha and 162 | new.dlSrc = reply.dlSrc and 163 | new.arp_spa = reply.arp_spa and 164 | new.arp_sha = reply.arp_sha; 165 | -------------------------------------------------------------------------------- /interpreter/examples/BasicNAT.flg: -------------------------------------------------------------------------------- 1 | TABLE nat(macaddr, ipaddr, tpport, tpport); 2 | VAR nextport: tpport = 10000; 3 | 4 | ON tcp_packet(p) WHERE p.locPt = 1 AND 5 | nat(p.dlSrc, p.nwSrc, p.tpSrc, natport): 6 | DO forward(new) WHERE new.tpSrc = natport AND 7 | new.nwSrc = 192.168.100.100 AND 8 | new.dlSrc = 00:00:00:00:00:FF AND new.locPt = 2; 9 | 10 | ON tcp_packet(p) WHERE p.locPt = 1 AND 11 | NOT nat(p.dlSrc, p.nwSrc, p.tpSrc, ANY): 12 | INSERT(p.dlSrc, p.nwSrc, p.tpSrc, nextport) INTO nat; 13 | INCREMENT nextport; 14 | 15 | DO forward(new) WHERE new.tpSrc = nextport AND 16 | new.nwSrc = 192.168.100.100 AND 17 | new.dlSrc = 00:00:00:00:00:FF AND new.locPt = 2; 18 | 19 | // Return traffic in a nat flow 20 | ON tcp_packet(p) WHERE p.locpt = 2 AND 21 | nat(origmac, origip, origport, p.tpDst): 22 | DO forward(new) WHERE new.tpDst = origport AND 23 | new.nwDst = origip AND 24 | new.dlDst = origmac AND 25 | new.locPt = 1; -------------------------------------------------------------------------------- /interpreter/examples/LoadBalancer.flg: -------------------------------------------------------------------------------- 1 | /* 2 | Per-flow, round-robin flow-balancer 3 | 4 | Configure via the load_balancing relation. 5 | Flows that are decided already are in the lb_state relation. 6 | Alternations in the lb_alt relation. 7 | 8 | Assumption: flows can be fully identified by (mac src, mac dst) pairs. 9 | 10 | Language limitation: can't just list outgoing port candidates in the config 11 | and pick the "next" one every time. Can't pick an arbitrary element or the smallest one! 12 | Instead, only allow fixed number of options. 13 | 14 | */ 15 | 16 | // Configure load-balancer: switch, incoming port, outgoing ports x2 17 | TABLE load_balancing(switchid, portid, portid, portid); 18 | 19 | // Current state of load-balancer 20 | TABLE lb_state(switchid, macaddr, macaddr, portid); // established assignments 21 | TABLE lb_alt(switchid, portid, portid); // what port was last used? 22 | 23 | ///////////////////////////////////////// 24 | // sudo mn --arp --mac --controller=remote --topo=tree,depth=1,fanout=3 25 | 26 | ON startup(e): 27 | INSERT (1, 1, 2, 3) INTO load_balancing; 28 | INSERT (1, 1, 2) INTO lb_alt; // EDIT 29 | 30 | ///////////////////////////////////////// 31 | 32 | // Arrival for established flow (both directions) 33 | ON packet(p): 34 | DO forward(new) where lb_state(p.locSw, p.dlSrc, p.dlDst, new.locPt); 35 | DO forward(new) where lb_state(p.locSw, p.dlDst, p.dlSrc, new.locPt); 36 | 37 | // Unestablished flow (in either direction) 38 | // Must be on a configured "in" port or will be dropped 39 | ON packet(p) where not lb_state(p.locSw, p.dlSrc, p.dlDst, ANY) and 40 | not lb_state(p.locSw, p.dlDst, p.dlSrc, ANY) and 41 | load_balancing(p.locSw, p.locPt, opt1, opt2) and 42 | 43 | // This is an awfully ugly way to write a let statement... 44 | ((NOT lb_alt(p.locSw, p.locPt, opt1) and choice = opt1) 45 | OR 46 | (NOT lb_alt(p.locSw, p.locPt, opt2) and choice = opt2)): 47 | 48 | DO forward(new) where new.locPt = choice; 49 | 50 | INSERT (p.locSw, p.dlSrc, p.dlDst, choice) INTO lb_state; // established flow -> 51 | INSERT (p.locSw, p.dlDst, p.dlSrc, choice) INTO lb_state; // established flow <- 52 | 53 | INSERT (p.locSw, p.locPt, choice) INTO lb_alt; // round-robin 54 | DELETE (p.locSw, p.locPt, ANY) FROM lb_alt; 55 | 56 | ///////////////////////////////////////// -------------------------------------------------------------------------------- /interpreter/examples/Mac_Learning.flg: -------------------------------------------------------------------------------- 1 | TABLE learned(switchid, portid, macaddr); 2 | 3 | ON packet(pkt): 4 | INSERT (pkt.locSw, pkt.locPt, pkt.dlSrc) INTO learned; 5 | 6 | DELETE (pkt.locSw, pt, pkt.dlSrc) FROM learned WHERE 7 | not pt = pkt.locPt; 8 | 9 | DO forward(new) WHERE 10 | learned(pkt.locSw, new.locPt, pkt.dlDst) 11 | OR 12 | (NOT learned(pkt.locSw, ANY, pkt.dlDst) AND 13 | pkt.locPt != new.locPt); 14 | 15 | ON switch_down(swd): 16 | DELETE (swd.sw, ANY, ANY) FROM learned; -------------------------------------------------------------------------------- /interpreter/examples/NIB.flg: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////// 2 | // Before panicking, make sure that you aren't testing with single,n. 3 | // There's no topology to learn there! 4 | // 5 | // Moreover: pkt.dlTyp = 0x1001 represents a NIB test packet 6 | /////////////////////////////////////////////////////////// 7 | 8 | 9 | EVENT start_timer {seconds: int, id: string}; 10 | EVENT timer_expired {id: string}; 11 | EVENT down_alert {sw1: switchid, pt1: portid, sw2: switchid, pt2: portid}; 12 | 13 | TABLE ucST(switchid, portid, switchid, portid); 14 | TABLE switchTopology(switchid, portid, switchid, portid); 15 | TABLE ucTree(switchid, portid); 16 | TABLE spanningTree(switchid, portid); 17 | TABLE ucTC(switchid, switchid); 18 | TABLE nonSwitchPorts(switchid, portid); 19 | TABLE macConnectedAt(macaddr, switchid, portid); 20 | 21 | OUTGOING start_timer_out(start_timer) THEN 22 | SEND TO 127.0.0.1 9091; 23 | 24 | OUTGOING dummy_down_alert(down_alert) THEN 25 | SEND TO 127.0.0.1 9091; 26 | 27 | ///////////////////// 28 | 29 | ON startup(empty_event): 30 | DO start_timer_out(st) WHERE 31 | st.seconds = 10 AND st.id = "tNIB"; 32 | 33 | ON switch_port(swpt): 34 | DO emit(newpkt) WHERE 35 | newpkt.locSw = swpt.sw AND 36 | newpkt.locPt = swpt.pt AND 37 | newpkt.dlTyp = 0x1001 AND 38 | newpkt.dlSrc = swpt.sw AND 39 | newpkt.dlDst = swpt.pt; 40 | 41 | ON timer_expired(timer) WHERE timer.id = "tNIB": 42 | DO emit(newpkt) WHERE 43 | switch_has_port(newpkt.locSw, newpkt.locPt) AND 44 | newpkt.dlTyp = 0x1001 AND 45 | newpkt.dlSrc = newpkt.locSw AND newpkt.dlDst = newpkt.locPt; 46 | 47 | DO start_timer_out(st) WHERE st.seconds = 10 AND st.id = "tNIB"; 48 | 49 | DELETE (sw1, pt1, sw2, pt2) FROM ucST WHERE 50 | ucST(sw1, pt1, sw2, pt2); // <-- to constrain 51 | DELETE (sw1, pt1, sw2, pt2) FROM switchTopology WHERE 52 | switchTopology(sw1, pt1, sw2, pt2); // <-- to constrain 53 | INSERT (sw1, pt1, sw2, pt2) INTO switchTopology WHERE 54 | ucST(sw1, pt1, sw2, pt2); 55 | DELETE (sw, pt) FROM nonSwitchPorts WHERE 56 | nonSwitchPorts(sw, pt); 57 | DELETE (sw1, sw2) FROM ucTC WHERE 58 | ucTC(sw1, sw2); 59 | DELETE (sw, pt) FROM ucTree WHERE 60 | ucTree(sw, pt); 61 | DELETE (sw, pt) FROM spanningTree WHERE 62 | spanningTree(sw, pt); 63 | INSERT (sw, pt) INTO spanningTree WHERE 64 | ucTree(sw, pt); 65 | INSERT (sw, pt) INTO nonSwitchPorts WHERE 66 | switch_has_port(sw, pt) AND // <--- constrain below vars 67 | NOT ucST(sw, pt, someothersw, someotherpt); // vital to use ucST here 68 | 69 | // Send an alert in case anyone is listening 70 | DO dummy_down_alert(al) WHERE 71 | switchTopology(al.sw1, al.pt1, al.sw2, al.pt2) AND 72 | not ucST(al.sw1, al.pt1, al.sw2, al.pt2); 73 | 74 | 75 | // All the dlTyp = 0x1001 traffic will get sent to ctrlr via policy 76 | ON packet(pkt) WHERE pkt.dlTyp = 0x1001: 77 | INSERT (pkt.dlSrc, pkt.dlDst, pkt.locSw, pkt.locPt) INTO ucST; 78 | INSERT (pkt.dlSrc, pkt.locSw) INTO ucTC; 79 | INSERT (sw, pkt.locSw) INTO ucTC WHERE ucTC(sw, pkt.dlSrc); 80 | INSERT (pkt.dlSrc, sw) INTO ucTC WHERE ucTC(pkt.locSw, sw); 81 | INSERT (sw1, sw2) INTO ucTC WHERE ucTC(sw1, pkt.dlSrc) AND ucTC(pkt.locSw, sw2); 82 | 83 | // recall: smuggling (orig sw, orig pt) in (dlSrc, dlDst) 84 | INSERT (pkt.dlSrc, pkt.dlDst) INTO ucTree WHERE 85 | NOT ucTC(pkt.dlSrc, pkt.locSw) AND NOT ucTC(pkt.locSw, pkt.dlSrc); 86 | INSERT (pkt.locSw, pkt.locPt) INTO ucTree WHERE 87 | NOT ucTC(pkt.dlSrc, pkt.locSw) AND NOT ucTC(pkt.locSw, pkt.dlSrc); 88 | 89 | ON packet(pkt) WHERE pkt.dlTyp != 0x1001: 90 | INSERT (pkt.dlSrc, pkt.locSw, pkt.locPt) INTO macConnectedAt WHERE 91 | nonSwitchPorts(pkt.locSw, pkt.locPt) AND 92 | // This line is safe since the INSERT rule and the DELETE rules never overlap on same pkt 93 | NOT macConnectedAt(pkt.dlSrc, pkt.locSw, pkt.locPt); // pol opt 94 | // new mac on this port 95 | DELETE (mac, pkt.locSw, pkt.locPt) FROM macConnectedAt WHERE 96 | mac != pkt.dlSrc AND 97 | macConnectedAt(mac, pkt.locSw, pkt.locPt); 98 | // known to be elsewhere 99 | DELETE (pkt.dlSrc, sw, pkt.locPt) FROM macConnectedAt WHERE 100 | sw != pkt.locSw AND 101 | macConnectedAt(pkt.dlSrc, sw, pkt.locPt); 102 | DELETE (pkt.dlSrc, pkt.locSw, pt) FROM macConnectedAt WHERE 103 | pt != pkt.locPt AND 104 | macConnectedAt(pkt.dlSrc, pkt.locSw, pt); 105 | -------------------------------------------------------------------------------- /interpreter/examples/Smart_Mac.flg: -------------------------------------------------------------------------------- 1 | 2 | TABLE learned(int, int, int); 3 | TABLE switch_has_port(int, int); 4 | 5 | EVENT start_timer {seconds, id}; 6 | EVENT timer_expired {id}; 7 | 8 | TABLE switch_has_port(int, int); 9 | TABLE nonSwitchPorts(int, int); 10 | TABLE macConnectedAt(int48, int, int); 11 | TABLE ucST(int, int, int, int); 12 | TABLE switchTopology(int, int, int, int); 13 | 14 | TABLE ucTree(int, int); 15 | TABLE spanningTree(int, int); 16 | 17 | TABLE ucTC(int, int); 18 | 19 | OUTGOING start_timer(int, string); 20 | INCOMING timer_expired(timer_expired); 21 | 22 | ///////////////////// 23 | 24 | INCOMING timer_expired THEN INSERT INTO timer_expired; 25 | OUTGOING start_timer(seconds, id) THEN 26 | SEND EVENT start_timer {seconds:=seconds, id:=id} TO 127.0.0.1 9091; 27 | 28 | //////////////////////////////////////////// 29 | 30 | // -> Probably have to delete them all, actually? Connectivity violated. 31 | ON switch_down(swd): 32 | DELETE (swd.sw, any) FROM switch_has_port; 33 | DELETE (swd.sw, any1, any2) FROM learned; 34 | 35 | ON startup(empty_event): 36 | DO start_timer(10, "tNIB"); 37 | 38 | ON switch_port(swpt): 39 | INSERT (swpt.sw, swpt.pt) INTO switch_has_port; 40 | 41 | DO emit(newpkt) WHERE 42 | newpkt.locSw = swpt.sw AND 43 | newpkt.locPt = swpt.pt AND 44 | newpkt.dlTyp = 0x1001 AND 45 | newpkt.dlSrc = swpt.sw AND 46 | newpkt.dlDst = swpt.pt; 47 | 48 | ON timer_expired(timer) WHERE timer.id = "tNIB": 49 | DO start_timer(10, "tNIB"); 50 | 51 | DO emit(newpkt) WHERE 52 | switch_has_port(newpkt.locSw, newpkt.locPt) AND 53 | newpkt.dlTyp = 0x1001 AND 54 | newpkt.dlSrc = newpkt.locSw AND 55 | newpkt.dlDst = newpkt.locPt; 56 | DELETE (sw1, pt1, sw2, pt2) FROM ucST WHERE 57 | ucST(sw1, pt1, sw2, pt2); // <-- to constrain 58 | DELETE (sw1, pt1, sw2, pt2) FROM switchTopology WHERE 59 | switchTopology(sw1, pt1, sw2, pt2); // <-- to constrain 60 | INSERT (sw1, pt1, sw2, pt2) INTO switchTopology WHERE 61 | ucST(sw1, pt1, sw2, pt2); 62 | DELETE (sw1, sw2) FROM ucTC WHERE 63 | ucTC(sw1, sw2); 64 | DELETE (sw, pt) FROM ucTree WHERE 65 | ucTree(sw, pt); 66 | DELETE (sw, pt) FROM spanningTree WHERE 67 | spanningTree(sw, pt); 68 | INSERT (sw, pt) INTO spanningTree WHERE 69 | ucTree(sw, pt); 70 | DELETE (sw, pt) FROM nonSwitchPorts WHERE 71 | nonSwitchPorts(sw, pt); 72 | INSERT (sw, pt) INTO nonSwitchPorts WHERE 73 | switch_has_port(sw, pt) AND // <--- constrain below vars 74 | NOT ucST(sw, pt, someothersw, someotherpt); // vital to use ucST here 75 | 76 | 77 | ON packet_in(pkt): 78 | INSERT (pkt.dlSrc, pkt.dlDst, pkt.locSw, pkt.locPt) INTO ucST WHERE 79 | pkt.dlTyp = 0x1001; 80 | 81 | // IGNORING SYMMETRY 82 | //INSERT (pkt.locSw, pkt.dlSrc) INTO ucTC WHERE pkt.dlTyp = 0x1001; 83 | INSERT (pkt.dlSrc, pkt.locSw) INTO ucTC WHERE pkt.dlTyp = 0x1001; 84 | INSERT (sw, pkt.locSw) INTO ucTC WHERE pkt.dlTyp = 0x1001 85 | AND ucTC(sw, pkt.dlSrc); 86 | INSERT (pkt.dlSrc, sw) INTO ucTC WHERE pkt.dlTyp = 0x1001 87 | AND ucTC(pkt.locSw, sw); 88 | INSERT (sw1, sw2) INTO ucTC WHERE pkt.dlTyp = 0x1001 89 | AND ucTC(sw1, pkt.dlSrc) AND ucTC(pkt.locSw, sw2); 90 | 91 | INSERT (pkt.dlSrc, pkt.locSw, pkt.locPt) INTO macConnectedAt WHERE 92 | NOT pkt.dlTyp = 0x1001 AND 93 | nonSwitchPorts(pkt.locSw, pkt.locPt) AND 94 | NOT macConnectedAt(pkt.dlSrc, pkt.locSw, pkt.locPt); // pol opt 95 | // new mac on this port 96 | DELETE (mac, pkt.locSw, pkt.locPt) FROM macConnectedAt WHERE 97 | NOT pkt.dlTyp = 0x1001 AND mac != pkt.dlSrc AND 98 | macConnectedAt(mac, pkt.locSw, pkt.locPt); 99 | // known to be elsewhere 100 | DELETE (pkt.dlSrc, sw, pkt.locPt) FROM macConnectedAt WHERE 101 | NOT pkt.dlTyp = 0x1001 AND sw != pkt.locSw AND 102 | macConnectedAt(pkt.dlSrc, sw, pkt.locPt); 103 | DELETE (pkt.dlSrc, pkt.locSw, pt) FROM macConnectedAt WHERE 104 | NOT pkt.dlTyp = 0x1001 AND pt != pkt.locPt AND 105 | macConnectedAt(pkt.dlSrc, pkt.locSw, pt); 106 | 107 | 108 | // recall: smuggling (orig sw, orig pt) in (dlSrc, dlDst) 109 | INSERT (pkt.dlSrc, pkt.dlDst) INTO ucTree WHERE pkt.dlTyp = 0x1001 AND 110 | NOT ucTC(pkt.dlSrc, pkt.locSw) AND NOT ucTC(pkt.locSw, pkt.dlSrc); 111 | INSERT (pkt.locSw, pkt.locPt) INTO ucTree WHERE pkt.dlTyp = 0x1001 AND 112 | NOT ucTC(pkt.dlSrc, pkt.locSw) AND NOT ucTC(pkt.locSw, pkt.dlSrc); 113 | 114 | //////////////////////////////////////////// 115 | 116 | // For MACL 117 | ON packet_in(pkt): 118 | 119 | INSERT (pkt.locSw, pkt.locPt, pkt.dlSrc) INTO learned WHERE 120 | not pkt.dlTyp = 0x1001 and 121 | not learned(pkt.locSw, pkt.locPt, pkt.dlSrc); 122 | 123 | DELETE (pkt.locSw, pt, pkt.dlSrc) FROM learned WHERE 124 | not pkt.dlTyp = 0x1001 and 125 | NOT pt = pkt.locPt 126 | // The optimization requirement: ctrlr will learn something new 127 | AND not learned(pkt.locSw, pkt.locPt, pkt.dlSrc); 128 | 129 | DO forward(new) WHERE 130 | // know where to go 131 | (learned(pkt.locSw, new.locPt, pkt.dlDst) AND 132 | not pkt.dlTyp = 0x1001 and 133 | pkt.locPt != new.locPt) 134 | OR 135 | // don't know where to go: forward along new edges of spanning tree 136 | (NOT learned(pkt.locSw, x, pkt.dlDst) AND 137 | NOT pkt.locPt = new.locPt AND 138 | spanningTree(pkt.locSw, new.locPt) AND // use pkt.sw here, not new 139 | not pkt.dlTyp = 0x1001) 140 | OR 141 | // don't know where to go: also send to adjacent non-fabric ports 142 | (NOT learned(pkt.locSw, x, pkt.dlDst) AND 143 | NOT pkt.locPt = new.locPt AND 144 | nonSwitchPorts(pkt.locSw, new.locPt) AND // use pkt.sw here, not new 145 | not pkt.dlTyp = 0x1001); 146 | 147 | -------------------------------------------------------------------------------- /interpreter/examples/Stolen.flg: -------------------------------------------------------------------------------- 1 | /* 2 | MUST BE STARTED FIRST: 3 | thrift/timer.native 4 | thrift/police_tipline.native 5 | 6 | To send events, use 7 | thrift/notify.native 8 | e.g., 9 | ./notify.native stolen_laptop_report mac=1 10 | (addresses must be passed as decimal numbers for now) 11 | */ 12 | 13 | EVENT stolen_laptop_report {mac: macaddr}; 14 | EVENT stolen_laptop_cancel {mac: macaddr}; 15 | EVENT stolen_laptop_found {macaddr: macaddr, swid: switchid, time: int}; 16 | 17 | TABLE stolen(macaddr); 18 | 19 | // Outgoing defn just defines a named pipe for events: 20 | OUTGOING notify_police(stolen_laptop_found) THEN 21 | SEND TO 127.0.0.1 5050; 22 | 23 | // Basic test for REMOTE TABLE: current time 24 | REMOTE TABLE get_time(int) 25 | FROM time AT 127.0.0.1 9091 26 | TIMEOUT 1 seconds; 27 | 28 | ON stolen_laptop_cancel(rec): 29 | DELETE (rec.mac) FROM stolen; 30 | 31 | ON stolen_laptop_report(stolen): 32 | INSERT (stolen.mac) INTO stolen; 33 | 34 | ON packet(pkt): 35 | // For demonstration only: flood. 36 | DO forward(new) WHERE 37 | new.locPt != pkt.locPt; 38 | 39 | DO notify_police(sto) WHERE 40 | sto.macaddr = pkt.dlSrc AND sto.time=time AND sto.swid = pkt.locSw AND 41 | stolen(pkt.dlSrc) AND 42 | get_time(time); 43 | 44 | -------------------------------------------------------------------------------- /interpreter/examples/StolenLimited.flg: -------------------------------------------------------------------------------- 1 | EVENT stolen_laptop_report {macr: macaddr}; 2 | EVENT stolen_laptop_cancel {macc: macaddr}; 3 | EVENT stolen_laptop_found {macaddr: macaddr, swid: switchid, time: int}; 4 | EVENT start_timer {seconds: int, id: string}; 5 | EVENT timer_expired {id: string}; 6 | 7 | TABLE stolen(macaddr); 8 | TABLE limit(macaddr); 9 | 10 | OUTGOING notify_police(stolen_laptop_found) THEN 11 | SEND TO 127.0.0.1 5050; 12 | OUTGOING start_timer_out(start_timer) THEN 13 | SEND TO 127.0.0.1 9091; 14 | 15 | REMOTE TABLE get_time(int) 16 | FROM time AT 127.0.0.1 9091 17 | TIMEOUT 1 seconds; 18 | 19 | ON stolen_laptop_cancel(rec): 20 | DELETE (rec.macc) FROM stolen; 21 | ON stolen_laptop_report(stolen): 22 | INSERT (stolen.macr) INTO stolen; 23 | 24 | ON packet(pkt): 25 | DO forward(new) WHERE 26 | new.locPt != pkt.locPt; 27 | 28 | DO notify_police(sto) WHERE 29 | sto.macaddr = pkt.dlSrc AND sto.time=currtime AND sto.swid = pkt.locSw AND 30 | stolen(pkt.dlSrc) AND 31 | NOT limit(pkt.dlSrc) AND 32 | get_time(currtime); 33 | INSERT (pkt.dlSrc) INTO limit WHERE 34 | stolen(pkt.dlSrc) and NOT limit(pkt.dlSrc); 35 | 36 | // Every 15 seconds, clean out the rate-limiter table 37 | ON startup(empty_event): 38 | DO start_timer_out(st) WHERE 39 | st.seconds = 15 AND st.id = "tStolen"; 40 | ON timer_expired(exp) WHERE exp.id = "tStolen": 41 | DELETE (ANY) FROM limit; 42 | DO start_timer_out(st) WHERE 43 | st.seconds = 15 AND st.id = "tStolen"; 44 | -------------------------------------------------------------------------------- /interpreter/examples/detectLocation.flg: -------------------------------------------------------------------------------- 1 | TABLE connectedAt(switchid, portid, macaddr); 2 | 3 | // Record the first time a MAC address (src) is seen on the network. 4 | ON packet(pkt) WHERE NOT connectedAt(ANY, ANY, pkt.dlSrc): 5 | INSERT (pkt.locSw, pkt.locPt, pkt.dlSrc) INTO connectedAt; 6 | 7 | // Flood as L2 policy (keep simple for example) 8 | ON packet(pkt): 9 | DO forward(new) WHERE pkt.locPt != new.locPt; 10 | 11 | -------------------------------------------------------------------------------- /interpreter/examples/discovery.flg: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////// 2 | // Send discovery packets. Used to detect errors in mininet topologies. 3 | /////////////////////////////////////////////////////////// 4 | 5 | 6 | EVENT start_timer {seconds: int, id: string}; 7 | EVENT timer_expired {id: int}; 8 | 9 | OUTGOING start_timer_out(start_timer) THEN 10 | SEND TO 127.0.0.1 9091; 11 | 12 | TABLE topo(switchid, portid, switchid, portid); 13 | 14 | ///////////////////// 15 | 16 | ON startup(empty_event): 17 | DO start_timer_out(st) WHERE 18 | st.seconds = 15 AND st.id = "tNIB"; 19 | 20 | ON packet(p) WHERE p.dltyp = 0x1001: 21 | insert (p.dlsrc, p.dldst, p.locsw, p.locpt) into topo; 22 | 23 | ON timer_expired(timer) WHERE timer.id = "tNIB": 24 | DO emit(newpkt) WHERE 25 | switch_has_port(newpkt.locSw, newpkt.locPt) AND 26 | newpkt.dlTyp = 0x1001 AND 27 | newpkt.dlSrc = newpkt.locSw AND newpkt.dlDst = newpkt.locPt; 28 | 29 | -------------------------------------------------------------------------------- /interpreter/examples/experiments/Joins.flg: -------------------------------------------------------------------------------- 1 | /* 2 | Use log_for_flowlog.log 3 | and 4 | dpctl dump-flows 5 | 6 | to evaluate compiled output. 7 | */ 8 | 9 | 10 | TABLE test1(macaddr, macaddr); 11 | TABLE test2(macaddr, macaddr); 12 | TABLE test3(macaddr); 13 | 14 | ON startup(empty): 15 | 16 | // TO TEST NEGATION: expect only 1 predicate 17 | /* 18 | insert (1, 101) into test1; 19 | insert (2, 102) into test1; 20 | insert (3, 103) into test1; 21 | insert (3, 104) into test1; 22 | insert (101, 4) into test2; 23 | insert (102, 5) into test2; 24 | insert (103, 6) into test2; 25 | //insert (101) into test3; 26 | insert (102) into test3; 27 | insert (103) into test3; 28 | */ 29 | 30 | // TO TEST MULTIPLE NEW VALUES: expect 2 singles and 1 dupe 31 | insert (1, 101) into test1; 32 | insert (2, 102) into test1; 33 | insert (3, 103) into test1; 34 | insert (3, 104) into test1; 35 | insert (101, 4) into test2; 36 | insert (102, 5) into test2; 37 | insert (103, 6) into test2; 38 | insert (104, 7) into test2; 39 | 40 | 41 | ///////////////////////////////////////////// 42 | ON packet(pkt): 43 | DO forward(new) WHERE 44 | test1(pkt.dlDst, x) and 45 | test2(x, new.dlDst) and 46 | not test3(x) and 47 | pkt.locPt != new.locPt; -------------------------------------------------------------------------------- /interpreter/examples/experiments/L3_test_masks.flg: -------------------------------------------------------------------------------- 1 | // TEST for mask syntax and compilation 2 | 3 | // TODO: path resolution is bad here if we try to run from examples/experiments. Will fail to resolve NIB (which ARP uses) 4 | 5 | INCLUDE "examples/Arp_Cache.flg"; 6 | INCLUDE "examples/Mac_Learning.inc.flg"; 7 | 8 | // subnets(subnet addr, subnet mask, gw ip, gw mac, locSw, locpt) 9 | TABLE subnets(ipaddr, int, ipaddr, macaddr, switchid, portid); 10 | 11 | table router_queued(switchid); 12 | 13 | // MAC learning running on other switches (we manually prevent it from running on swid = 0x1000000000000001) 14 | ON ip_packet(pkt) WHERE pkt.locSw = 0x1000000000000001: 15 | DO forward(new) WHERE 16 | ((pkt.nwDst IN 10.0.1.0/24 and new.dlDst = 00:00:00:00:00:01) 17 | or 18 | (pkt.nwDst IN 10.0.2.0/24 and new.dlDst = 00:00:00:00:00:02)) 19 | and 20 | subnets(addr, mask, gwip, new.dlsrc, pkt.locSw, new.locPt) 21 | and pkt.nwdst IN addr/mask; // gwip unused? 22 | 23 | 24 | 25 | insert (pkt.locsw) into router_queued where 26 | pkt.nwsrc in 200.200.200.200/24 27 | or 28 | pkt.nwdst in 200.200.200.200/24; 29 | 30 | // AND translate for outbound (should be sequentially composed with above) 31 | 32 | //ON ip_packet(pkt) WHERE pkt.nwDst = 10.0.1.2: 33 | // DO forward(new) WHERE 34 | // new.dlDst = 00:00:00:00:00:01; 35 | 36 | //ON ip_packet(pkt) WHERE pkt.nwDst = 10.0.2.2: 37 | // DO forward(new) WHERE 38 | // new.dlDst = 00:00:00:00:00:02; 39 | 40 | // config 41 | 42 | ON startup(ev): 43 | INSERT (10.0.1.1, ca:fe:ca:fe:00:01) INTO cached; // 10.0.1.1/24 gw mac addr 44 | INSERT (10.0.2.1, ca:fe:ca:fe:00:02) INTO cached; // 10.0.2.1/24 gw mac addr 45 | 46 | // subnets(subnet addr, subnet mask, gw ip, gw mac, locSw, locpt) 47 | INSERT (10.0.1.0, 24, 10.0.1.1, ca:fe:ca:fe:00:01, 0x1000000000000001, 1) INTO subnets; 48 | INSERT (10.0.2.0, 24, 10.0.2.1, ca:fe:ca:fe:00:02, 0x1000000000000001, 2) INTO subnets; -------------------------------------------------------------------------------- /interpreter/examples/experiments/Timeout_Mac.flg: -------------------------------------------------------------------------------- 1 | 2 | TABLE learned(switchid, portid, macaddr); 3 | TABLE switch_has_port(switchid, portid); 4 | 5 | 6 | ON packet(pkt): 7 | INSERT (pkt.locSw, pkt.locPt, pkt.dlSrc) INTO learned WHERE 8 | not learned(pkt.locSw, pkt.locPt, pkt.dlSrc); 9 | 10 | DELETE (pkt.locSw, pt, pkt.dlSrc) FROM learned WHERE 11 | not pt = pkt.locPt 12 | AND not learned(pkt.locSw, pkt.locPt, pkt.dlSrc); 13 | 14 | // known branch: timeout 15, timeout 10 15 | DO forward(new) WHERE 16 | learned(pkt.locSw, new.locPt, pkt.dlDst) AND 17 | pkt.locPt != new.locPt 18 | TIMEOUT 15; 19 | DO forward(new) WHERE 20 | learned(pkt.locSw, new.locPt, pkt.dlDst) AND 21 | pkt.locPt != new.locPt 22 | TIMEOUT 10; 23 | 24 | // unknown branch: no-timeout + 15 25 | DO forward(new) WHERE 26 | NOT learned(pkt.locSw, ANY, pkt.dlDst) AND 27 | NOT pkt.locPt = new.locPt 28 | TIMEOUT 20; 29 | DO forward(new) WHERE 30 | NOT learned(pkt.locSw, ANY, pkt.dlDst) AND 31 | NOT pkt.locPt = new.locPt; 32 | 33 | TABLE omgremoved (macaddr); 34 | 35 | ON flow_removed (fr): 36 | INSERT (fr.dlSrc) INTO omgremoved 37 | WHERE fr.reason = "idletimeout"; 38 | 39 | 40 | ON switch_port(swpt): 41 | INSERT (swpt.sw, swpt.pt) INTO switch_has_port; 42 | 43 | ON switch_down(swd): 44 | DELETE (swd.sw, ANY) FROM switch_has_port; 45 | DELETE (swd.sw, ANY, ANY) FROM learned; 46 | -------------------------------------------------------------------------------- /interpreter/examples/experiments/basic_nat.flg: -------------------------------------------------------------------------------- 1 | // sudo mn --controller=remote --topo=tree,depth=1,fanout=2 --mac --arp 2 | // h1 curl h2 3 | 4 | TABLE nextpt(portid); 5 | TABLE nat(ipaddr, portid, portid); 6 | 7 | ON startup(empty): 8 | INSERT (10000) INTO nextpt; 9 | 10 | // Outgoing traffic (new flow) 11 | ON tcp_packet(p) WHERE p.locPt = 1 AND NOT nat(p.nwSrc, p.tpSrc, ANY): 12 | DO forward(new) WHERE 13 | // new.locPt = 2 AND nextpt(new.tpSrc); <-- gets compiled (leads to race condition) 14 | new.locPt = 2 AND nextpt(old) AND add(old, 1, new.tpSrc); // <-- not compiled (correct) 15 | DELETE (x) FROM nextpt; 16 | INSERT (x) INTO nextpt WHERE 17 | nextpt(old) AND add(old, 1, x); 18 | INSERT (p.nwSrc, p.tpSrc, pt) INTO nat WHERE 19 | nextpt(old) AND add(old, 1, pt); 20 | 21 | // Outgoing traffic (existing flow) 22 | ON tcp_packet(p) WHERE p.locPt = 1 AND nat(p.nwSrc, p.tpSrc, pt): 23 | DO forward(new) WHERE 24 | new.locPt = 2 AND new.tpSrc = pt; 25 | 26 | // Return traffic (known destination) 27 | ON tcp_packet(p) WHERE p.locPt = 2 AND nat(p.nwDst, oldpt, p.tpDst): 28 | DO forward(new) WHERE 29 | new.locPt = 1 AND new.tpDst = oldpt; 30 | 31 | 32 | // TEST 33 | 34 | TABLE temptbl(macaddr); 35 | ON flow_removed(fr): 36 | INSERT (fr.dlSrc) INTO temptbl 37 | WHERE reason = "delete"; -------------------------------------------------------------------------------- /interpreter/examples/experiments/packetin1.flg: -------------------------------------------------------------------------------- 1 | 2 | // As this pipe takes packets, it will automatically package the notification 3 | // as a packet_in, not a Thrift RPC. Since the swid is not carried in the 4 | // packet_in, Flowlog will proxy as "Switches" of the appropriate ID with no 5 | // capabilities. 6 | 7 | OUTGOING send_to_cp(ip_packet) THEN 8 | SEND TO 127.0.0.1:9999; 9 | 10 | // Flood the packet on the DP 11 | // ... AND send a copy of it elsewhere on the CP. 12 | 13 | ON ip_packet(p): 14 | // don't forward and then re-forward 15 | //DO forward(new) WHERE new.locPt != p.locPt; 16 | 17 | // Want to copy everything, including the payload. ("forward"-style behavior for now) 18 | DO send_to_cp(p); 19 | 20 | // TODO: distinguish forward and emit-type behaviors in these pipes (does the payload get copied?) 21 | 22 | // TODO: one port per pipe problem; not an issue here but will come up in the 23 | // future. (No way to index pipe by a first-class value like swid) 24 | 25 | // TODO: Allow modification to nonsw, nonpt values in outgoing packet_in 26 | 27 | ////////////////////////////////// 28 | 29 | // Receive packet_outs from the control plane (_out because Flowlog proxies a 30 | // switch). For now, use built-in cp_packet pipes. INCOMING reactives now have 31 | // a third datum: whether the event comes via the DP, the CP, or Thrift. The 32 | // parser will always make declared reactives come from thrift, and reactives 33 | // created by event declarations will likewise come from Thrift. 34 | 35 | // TODO (later): add some flexibility for packet-out processing. 36 | 37 | // Take this packet and flood it on all non-eq ports for the specified switch. 38 | // (This tests that the source portid and swid are taken into account.) 39 | // We currently ignore the actions given by the external controller. 40 | 41 | ON cp_ip_packet(p): 42 | DO forward(new) WHERE new.locPt != p.locPt; 43 | 44 | 45 | // TODO: Keepalive doesn't seem to be required for Pox. What about Ryu? 46 | // TODO: Pox sends a barrier before it's ready to process packet_ins. Is Ryu the same? 47 | 48 | // TODO later: actions in packet_out. Can't currently represent records in a flowlog event, 49 | // so packet_outs are treated as normal packets, using the in_port field for the locPt. (pt=0 if none) 50 | -------------------------------------------------------------------------------- /interpreter/examples/getting-started/sfw-plus.flg: -------------------------------------------------------------------------------- 1 | /* 2 | Meant to be run with: 3 | sudo mn --mac --arp --controller=remote --topo=tree,depth=3,fanout=2 4 | */ 5 | 6 | TABLE permit_back(switchid, macaddr, macaddr); 7 | TABLE mac_cache(switchid, portid, macaddr); 8 | TABLE stateful_firewall(switchid, portid); 9 | 10 | ON startup(empty_event): 11 | INSERT (6,1) INTO stateful_firewall; 12 | INSERT (6,2) INTO stateful_firewall; 13 | INSERT (7,1) INTO stateful_firewall; 14 | INSERT (7,2) INTO stateful_firewall; 15 | 16 | 17 | ON packet(p) WHERE NOT mac_cache(p.locSw, ANY, p.dlSrc): 18 | INSERT (p.locSw, p.locPt, p.dlSrc) INTO mac_cache; 19 | 20 | ON packet(p) WHERE stateful_firewall(p.locSw, p.locPt): 21 | INSERT (p.locSw, p.dlSrc, p.dlDst) INTO permit_back; 22 | 23 | ON packet(p) WHERE mac_cache(p.locSw, out_port, p.dlDst) OR 24 | (NOT mac_cache(p.locSw, ANY, p.dlDst) AND out_port != p.locPt): 25 | DO forward(new) WHERE 26 | new.locPt = out_port AND 27 | (NOT stateful_firewall(p.locSw, out_port) OR permit_back(p.locSw, p.dlDst, p.dlSrc)); 28 | 29 | 30 | // TODO: can we re-use maclearning table, or drop it (reducing us to just 1 table?) 31 | // TODO: ^ one of the branches above does not compile (says new.locPt not SSafe?) -------------------------------------------------------------------------------- /interpreter/examples/getting-started/sfw.flg: -------------------------------------------------------------------------------- 1 | TABLE permit_back(ipaddr, ipaddr); 2 | 3 | ON ip_packet(p) WHERE p.locPt = 1: 4 | DO forward(new) WHERE new.locPt = 2; 5 | INSERT (p.nwSrc, p.nwDst) INTO permit_back; 6 | 7 | ON ip_packet(p) WHERE p.locPt = 2 AND 8 | permit_back(p.nwDst, p.nwSrc): 9 | DO forward(new) WHERE new.locPt = 1; 10 | -------------------------------------------------------------------------------- /interpreter/examples/old_l3/ACLsample.flg: -------------------------------------------------------------------------------- 1 | /* 2 | * Basic functionality for ACL tables. 3 | * 4 | * (description goes here) 5 | * 6 | */ 7 | 8 | ON ip_packet(pkt) WHERE pkt.locSw = 0x5000000000000001 and pkt.locPt = 1: 9 | DO forward(new) WHERE 10 | new.locPt = 2; 11 | 12 | ON ip_packet(pkt) WHERE pkt.locSw = 0x5000000000000001 and pkt.locPt = 2: 13 | DO forward(new) WHERE 14 | new.locPt = 1; 15 | 16 | ON ip_packet(pkt) WHERE pkt.locSw = 0x5000000000000001 and pkt.locPt = 3: 17 | DO forward(new) WHERE 18 | new.locPt = 4; 19 | 20 | ON ip_packet(pkt) WHERE pkt.locSw = 0x5000000000000001 and pkt.locPt = 4: 21 | DO forward(new) WHERE 22 | new.locPt = 3; 23 | -------------------------------------------------------------------------------- /interpreter/examples/old_l3/L3external.flg: -------------------------------------------------------------------------------- 1 | /* 2 | * Basic functionality for handling external (non-directly attached) subnets. 3 | * 4 | * The core of this module is a routing table with a list of subnets and their 5 | * corresponding gateway's IP address. 6 | * 7 | * 8 | * TODO: 9 | * - expose routes as an external table which is managed by Quagga 10 | * - should issue ARP requests for every nexthop (gateway) IP 11 | * automatically, rather than requiring they be entered into the cached 12 | * relation by hand at startup. 13 | */ 14 | 15 | /******************************************************************************* 16 | * 17 | * Data Structures 18 | * 19 | ******************************************************************************/ 20 | 21 | // Routing table for non-directly attached subnets 22 | // 23 | // Column values: 24 | // subnet, mask, next-hop IP 25 | TABLE routes(ipaddr, int, ipaddr); 26 | 27 | /******************************************************************************* 28 | * 29 | * L3 routing to non-directly attached subnets 30 | * 31 | * TODO(adf): would be great if NAT were not explicitly considered here 32 | * 33 | ******************************************************************************/ 34 | 35 | // packets destined to outside whose source does NOT need NATing 36 | 37 | ON ip_packet(pkt): 38 | DO forward(new) WHERE 39 | 40 | // generic nexthop IP selection 41 | routes(addr, mask, nexthop) 42 | and pkt.nwDst IN addr/mask 43 | 44 | // templated nexthop IP selection 45 | // @nexthop-fragment 46 | // AND 47 | // not in a local subnet for this router 48 | // NOT (@dst-local-subnet) 49 | 50 | and subnets(nexthop_subnet, nexthop_mask, ANY, new.dlSrc, pkt.locSw, new.locPt) 51 | and nexthop IN nexthop_subnet/nexthop_mask 52 | and cached(nexthop, new.dlDst) // MAC addr of nexthop IP 53 | 54 | // don't route packets which need NAT'ing 55 | and needs_nat(pkt.locSw, needs_nat_subnet, needs_nat_mask) 56 | and NOT (pkt.nwSrc IN needs_nat_subnet/needs_nat_mask); 57 | 58 | /* Changed to make this apply in L3router instead, so it applies to both directly- 59 | * attached subnets, and to external subnets (which are handled in this file). 60 | 61 | // packets destined to outside whose source says they need NATing 62 | 63 | ON ip_packet(pkt): 64 | DO forward(new) WHERE 65 | routes(addr, mask, nexthop) 66 | and pkt.nwDst IN addr/mask 67 | 68 | and subnets(ANY, ANY, ANY, ANY, pkt.locSw, ANY) // TODO(adf): make a smaller relation? 69 | 70 | and needs_nat(pkt.locSw, needs_nat_subnet, needs_nat_mask) 71 | and (pkt.nwSrc IN needs_nat_subnet/needs_nat_mask) 72 | 73 | and new.locPt = 1; // TODO(adf): make magic NAT port configurable? 74 | 75 | */ 76 | 77 | /******************************************************************************* 78 | * 79 | * Per-subnet translators: outbound path to nexthop MAC addr where destination 80 | * MAC address is already set. 81 | * 82 | ******************************************************************************/ 83 | 84 | ON ip_packet(pkt): 85 | DO forward(new) WHERE 86 | 87 | // generic nexthop IP selection 88 | routes(addr, mask, nexthop) 89 | and pkt.nwDst IN addr/mask 90 | 91 | // templated nexthop IP selection 92 | 93 | //@nexthop-fragment-for-tr 94 | 95 | and subnets(nexthop_subnet, nexthop_mask, ANY, gwmac, router, rport) 96 | and router_tr(router, pkt.locSw) 97 | and nexthop IN nexthop_subnet/nexthop_mask 98 | and cached(nexthop, pkt.dlDst) 99 | and pkt.dlSrc = gwmac 100 | and pkt.nwDst != nexthop // this case is handled by internal routing 101 | and router_portmap(rport, new.locPt, pkt.locPt); // router -> host 102 | -------------------------------------------------------------------------------- /interpreter/examples/old_l3/L3router.flg: -------------------------------------------------------------------------------- 1 | /* 2 | * Basic functionality for routing to directly attached subnets. 3 | * 4 | * (description goes here) 5 | * 6 | * TODO: 7 | * - split out an "interfaces" relation which has (ip, mac, locsw, locpt) 8 | * - would be great if NAT were't explicitly included here 9 | */ 10 | 11 | INCLUDE "examples/Arp_Cache.flg"; 12 | INCLUDE "examples/NATgeneric.flg"; 13 | INCLUDE "examples/L3external.flg"; 14 | 15 | /******************************************************************************* 16 | * 17 | * Data Structures 18 | * 19 | ******************************************************************************/ 20 | 21 | // Information about directly attached subnets 22 | // 23 | // Column values: 24 | // subnet addr, subnet mask, gw ip, gw mac, locSw, locpt 25 | TABLE subnets(ipaddr, int, ipaddr, macaddr, switchid, portid); 26 | 27 | // Table representing packets which router needs to send an ARP query for 28 | // 29 | // TODO(adf): how do we deliver this packet later??? 30 | // TODO(adf): actually store the packet buffer, not just its nwdst 31 | TABLE router_queued(ipaddr); 32 | 33 | // Table matching routers with their NATs 34 | // 35 | // Column values: 36 | // router locSw, NAT locSw 37 | TABLE router_nat(switchid, switchid); 38 | 39 | // Table matching routers with their translation switches 40 | // 41 | // Column values: 42 | // router locSw, translator locSw 43 | TABLE router_tr(switchid, switchid); 44 | 45 | // Table matching routers with their ACL switches 46 | // 47 | // Column values: 48 | // router locSw, acl locSw 49 | TABLE router_acl(switchid, switchid); 50 | 51 | /******************************************************************************* 52 | * 53 | * L3 routing to directly attached subnets 54 | * 55 | * Only wrinkle is that we need to send packets to our NAT switch if they need 56 | * to be un-NAT'd before processing. L3external.flg contains the rule which 57 | * sends to the NAT for packets which need NATing before hitting external. 58 | * 59 | ******************************************************************************/ 60 | 61 | ON ip_packet(pkt): 62 | DO forward(new) WHERE 63 | subnets(addr, mask, ANY, new.dlSrc, pkt.locSw, new.locPt) 64 | and pkt.nwDst IN addr/mask 65 | // exempt packets which need NAT 66 | and needs_nat(pkt.locSw, needs_nat_subnet, needs_nat_mask) 67 | and NOT (pkt.nwSrc IN needs_nat_subnet/needs_nat_mask) 68 | // exempt packets to the NAT (note positive use of router_nat since 1-1 between rtr & nat) 69 | and router_nat(pkt.locSw, natid) 70 | and NOT natconfig(natid, ANY, ANY, pkt.nwDst); 71 | 72 | // on the router, send all NAT-destined traffic to the NAT 73 | ON ip_packet(pkt): 74 | DO forward(new) WHERE 75 | natconfig(natid, ANY, ANY, pkt.nwDst) 76 | and router_nat(pkt.locSw, natid) 77 | and subnets(ANY, ANY, ANY, ANY, pkt.locSw, ANY) // TODO(adf): make a smaller relation? 78 | and pkt.locPt != 1 79 | and new.locPt = 1; // TODO(adf): make magic NAT port configurable? 80 | 81 | // packets destined whose source says they need NATing 82 | 83 | ON ip_packet(pkt): 84 | DO forward(new) WHERE 85 | subnets(ANY, ANY, ANY, ANY, pkt.locSw, ANY) // TODO(adf): make a smaller relation? 86 | 87 | and needs_nat(pkt.locSw, needs_nat_subnet, needs_nat_mask) 88 | and (pkt.nwSrc IN needs_nat_subnet/needs_nat_mask) 89 | and NOT (pkt.nwDst IN needs_nat_subnet/needs_nat_mask) 90 | 91 | and new.locPt = 1; // TODO(adf): make magic NAT port configurable? 92 | 93 | 94 | /******************************************************************************* 95 | * 96 | * Per-subnet translators: inbound path 97 | * 98 | * The inbound path is simple: just pass from port 2N-1 to port 2N for every 99 | * subnet N. Note that subnet N is attached to port N+1 on the router. 100 | * 101 | ******************************************************************************/ 102 | 103 | ON ip_packet(pkt): 104 | DO forward(new) WHERE 105 | subnets(ANY, ANY, ANY, pkt.dlDst, router, rport) 106 | and router_tr(router, pkt.locSw) 107 | and router_portmap(rport, pkt.locPt, new.locPt); // host -> router 108 | 109 | /******************************************************************************* 110 | * 111 | * Per-subnet translators: outbound path 112 | * 113 | * Our goal here is to rewrite the destination MAC address (dlDst) based on the 114 | * IP address. This mapping is obtained from ARP, so we first try to do the 115 | * rewrite based on the ARP cache. If the ARP cache lacks a mapping, we store 116 | * the packet, issue an ARP request, and later forward the packet once the ARP 117 | * reply is received. 118 | * 119 | ******************************************************************************/ 120 | 121 | // Default path: set dlDst based on ARP cache and send to subnet N (port 2N). 122 | // we receive from port 2N-1. note that subnet N is at port N+1 on the router. 123 | 124 | ON ip_packet(pkt): 125 | DO forward(new) WHERE 126 | subnets(addr, mask, gwip, ANY, router, rport) 127 | and router_tr(router, pkt.locSw) 128 | and pkt.nwDst IN addr/mask 129 | and cached(pkt.nwDst, new.dlDst) 130 | and pkt.nwDst != gwip 131 | and router_portmap(rport, new.locPt, pkt.locPt); // router -> host 132 | 133 | // Alternate path: send an ARP request for IPs we can't translate and queue the pkt 134 | 135 | ON ip_packet(pkt) WHERE subnets(addr, mask, gwip, gwmac, router, rport) 136 | and router_tr(router, pkt.locSw) 137 | and pkt.nwDst IN addr/mask 138 | and not cached(pkt.nwDst, ANY) 139 | and pkt.dlDst != gwmac 140 | and pkt.nwDst != gwip 141 | and router_portmap(rport, hside, pkt.locPt): // any router side 142 | 143 | INSERT (pkt.nwDst) INTO router_queued; 144 | 145 | DO emit_arp(new) WHERE 146 | not router_queued(pkt.nwDst) and 147 | router_acl(router, new.locSw) and 148 | new.locPt = hside and 149 | new.dlDst = ff:ff:ff:ff:ff:ff and 150 | new.dlSrc = 00:00:ca:fe:ca:fe and // controller mac addr 151 | 152 | new.arp_op = 1 and // ARP request 153 | new.arp_tpa = pkt.nwDst and 154 | new.arp_sha = 00:00:CA:FE:CA:FE and // Controller MAC 155 | new.arp_spa = 10.10.10.1 and // Controller IP addr (10.10.10.1) 156 | 157 | switch_has_port(new.locSw, new.locPt); // REQUIRED? this rule is not compiled TODO(adf): needed??? 158 | 159 | 160 | // with the above physically wired translators, we are trying to achieve 161 | // sequential composition with what is, logically, this program: 162 | // 163 | // ON ip_packet(pkt): 164 | // DO forward(new) WHERE 165 | // cached(pkt.nwDst, new.dlDst); 166 | // 167 | // (on the outbound direction of the router). plus, issuing ARP requests 168 | // for addresses we don't know how to translate 169 | -------------------------------------------------------------------------------- /interpreter/examples/old_l3/L3sample.flg: -------------------------------------------------------------------------------- 1 | /* 2 | * Sample program which configures Layer 3 routing between two directly- 3 | * attached subnets. Forwarding within subnets is handled by MAC Learning. 4 | * A third, external subnet is also attached, representing "the internet". 5 | * 6 | * Note: "gw" is the abbreviation for "gateway" 7 | * 8 | * 9 | * The router is identified by DPID 0x1000000000000001 10 | * 11 | * The first subnet is 10.0.1.0/24 (gw 10.0.1.1), attached to port 2 12 | * The second is 10.0.2.0/24 (gw 10.0.2.1), attached to port 3 13 | * 14 | * Each subnet has a "translator switch" which represents the flow table 15 | * between the router and the root ("Layer 2") switch of the subnet. The 16 | * translator switch rewrites the destination MAC address using ARP. 17 | * 18 | */ 19 | 20 | INCLUDE "examples/L3router.flg"; 21 | INCLUDE "examples/ACLsample.flg"; 22 | INCLUDE "examples/Mac_Learning.inc.flg"; 23 | 24 | TABLE routerAlias(string, switchid); 25 | TABLE portAlias(string, string, portid); 26 | TABLE aclDPID(switchid); 27 | 28 | // cached, switches_without_mac_learning, subnets: declared in INCLUDED files 29 | 30 | // Maps subnet number -> (host side, router side) 31 | // 32 | // TODO(tn): to be replaced with: 33 | // 34 | // router_portmap(rp, hside, rside) = 35 | // math_mult(2, tmp, rside) and math_sub(rport, 1, tmp) 36 | // and math_sub(rside, 1, hside) 37 | // 38 | TABLE router_portmap(portid, portid, portid); 39 | 40 | /******************************************************************************* 41 | * 42 | * Startup values 43 | * 44 | ******************************************************************************/ 45 | 46 | ON startup(ev): 47 | 48 | // Config for Directly-attached Subnets 49 | 50 | // remember: ports 1 is reserved for NAT (at the moment) 51 | 52 | // subnets(addr, mask, gw ip, gw mac, locSw, locpt) 53 | INSERT (10.0.1.0, 24, 10.0.1.1, ca:fe:00:01:00:01, 0x1000000000000001, 2) INTO subnets; 54 | INSERT (10.0.2.0, 24, 10.0.2.1, ca:fe:00:01:00:02, 0x1000000000000001, 3) INTO subnets; 55 | 56 | INSERT (0x1000000000000001, 0x2000000000000001) INTO router_tr; // auto 57 | INSERT (0x1000000000000001, 0x4000000000000001) INTO router_nat; // auto 58 | INSERT (0x1000000000000001, 0x5000000000000001) INTO router_acl; // auto 59 | 60 | // for ARP cache. TODO(adf): should be derived from above automatically! 61 | INSERT (10.0.1.1, ca:fe:00:01:00:01) INTO cached; // 10.0.1.1/24 gw mac addr 62 | INSERT (10.0.2.1, ca:fe:00:01:00:02) INTO cached; // 10.0.2.1/24 gw mac addr 63 | 64 | // Config for External Subnets 65 | 66 | // TODO(adf): should not advertise this subnet, probably. maybe break out an "interfaces" table? 67 | // then, local subnets will be those where nexthop is in "interfaces" 68 | INSERT (192.168.1.0, 24, 192.168.1.2, be:ef:be:ef:00:00, 0x1000000000000001, 4) INTO subnets; 69 | INSERT (192.168.1.1, be:ef:be:ef:00:01) INTO cached; // BGP peer TODO(adf): should get from ARP! 70 | INSERT (192.168.1.2, be:ef:be:ef:00:00) INTO cached; 71 | 72 | INSERT (8.0.0.0, 8, 192.168.1.1) INTO routes; 73 | INSERT (4.4.0.0, 16, 192.168.1.1) INTO routes; 74 | 75 | 76 | // Config for NAT 77 | // NAT has external-facing IP 192.168.1.10 78 | 79 | // Public IPs which NAT should use 80 | INSERT (0x4000000000000001, 1, 1, 192.168.1.10) INTO natconfig; 81 | 82 | // [policy] which internal subnets need to be nat'd 83 | INSERT (0x1000000000000001, 10.0.1.0, 24) INTO needs_nat; 84 | // INSERT (10.0.2.0, 24) INTO needs_nat; TODO(tn): BUG HERE!!! 85 | 86 | // TODO(adf): NATgeneric.flg should have these automatically based on natconfig 87 | INSERT (192.168.1.10, 0x6, 10000) INTO seqpt; 88 | INSERT (192.168.1.10, 0x11, 10000) INTO seqpt; 89 | 90 | INSERT (192.168.1.10, ca:fe:00:01:00:00) INTO cached; 91 | 92 | // Config to disable MAC learning on router, translators, NAT, and ACLs: 93 | 94 | INSERT (0x1000000000000001) INTO switches_without_mac_learning; 95 | INSERT (0x2000000000000001) INTO switches_without_mac_learning; 96 | INSERT (0x4000000000000001) INTO switches_without_mac_learning; 97 | INSERT (0x5000000000000001) INTO switches_without_mac_learning; 98 | 99 | // Config to disable ARP on translators, NAT, and ACLs: 100 | 101 | INSERT (0x2000000000000001) INTO switches_without_arp; 102 | INSERT (0x4000000000000001) INTO switches_without_arp; 103 | INSERT (0x5000000000000001) INTO switches_without_arp; 104 | 105 | // LOL. MATH. 106 | 107 | INSERT (2, 1, 2) INTO router_portmap; 108 | INSERT (3, 3, 4) INTO router_portmap; 109 | -------------------------------------------------------------------------------- /interpreter/examples/old_l3/NATsample.flg: -------------------------------------------------------------------------------- 1 | INCLUDE "examples/NATgeneric.flg"; 2 | 3 | INCLUDE "examples/Arp_Cache.flg"; 4 | 5 | 6 | ON startup(empty): 7 | INSERT (0x3000000000000000, 1, 2, 192.168.1.10) INTO natconfig; 8 | 9 | // TODO(adf): NATgeneric.flg should have these automatically based on natconfig 10 | INSERT (192.168.1.10, 0x6, 10000) INTO seqpt; 11 | INSERT (192.168.1.10, 0x11, 10000) INTO seqpt; 12 | 13 | INSERT (192.168.1.10, 00:00:00:00:00:01) INTO cached; 14 | -------------------------------------------------------------------------------- /interpreter/examples/timeout.flg: -------------------------------------------------------------------------------- 1 | /* 2 | MAC Learning equipped with timeouts. Removed mobility support for brevity. 3 | TN Dec 2014 4 | 5 | The challenge here is synchronizing the controller's state (which proactively generates rules) 6 | with whatever an expiration means. In this case, we catch the timeout notification from the switch 7 | and remove the appropriate row from the learned table. 8 | 9 | This is an imperfect, leaky abstraction because our tierless program is now aware of the supposedly 10 | invisible, lower tier. 11 | */ 12 | 13 | TABLE learned(switchid, portid, macaddr); 14 | 15 | ON packet(pkt): 16 | INSERT (pkt.locSw, pkt.locPt, pkt.dlSrc) INTO learned; 17 | 18 | DO forward(new) WHERE 19 | learned(pkt.locSw, new.locPt, pkt.dlDst) 20 | TIMEOUT 5; // current TIMEOUT syntax supports seconds only 21 | 22 | DO forward(new) WHERE 23 | NOT learned(pkt.locSw, ANY, pkt.dlDst) AND 24 | pkt.locPt != new.locPt 25 | TIMEOUT 5; 26 | 27 | // the flow_removed event contains only fields for the predicate of the rule, not the actions it takes 28 | // for more fields, see Flowlog_Packets.ml line ~155. 29 | 30 | ON flow_removed(flexp) WHERE flexp.reason = "IdleTimeout": 31 | DELETE (flexp.sw, ANY, flexp.dldst) FROM learned; // may need tweaking: what if dldst is a wildcard? 32 | -------------------------------------------------------------------------------- /interpreter/testcp.ml: -------------------------------------------------------------------------------- 1 | open Flowlog_Packet_Out 2 | 3 | test_send_packets(); -------------------------------------------------------------------------------- /interpreter/tests/Mac_Learning_Diffnames.flg: -------------------------------------------------------------------------------- 1 | 2 | /* This variation on Mac_Learning is identical except for different names. 3 | Used for change-impact soundness testing. */ 4 | 5 | // WARNING: This version is old, do not use it as an example of how to run Mac Learning! 6 | 7 | TABLE learned(switchid, portid, macaddr); 8 | 9 | 10 | ON packet(p): 11 | 12 | INSERT (p.locSw, p.locPt, p.dlSrc) INTO learned WHERE 13 | not learned(p.locSw, p.locPt, p.dlSrc); 14 | 15 | DELETE (p.locSw, pt, p.dlSrc) FROM learned WHERE 16 | not pt = p.locPt 17 | // The optimization requirement: ctrlr will learn something new 18 | AND not learned(p.locSw, p.locPt, p.dlSrc); 19 | 20 | // This rule doesn't have the negations all at the end of the 21 | // resulting clauses. If no ps forwarded, it means negation-shuffling has failed. 22 | DO forward(n) WHERE 23 | (learned(p.locSw, n.locPt, p.dlDst) AND 24 | p.locPt != n.locPt 25 | ) 26 | OR 27 | (NOT learned(p.locSw, x, p.dlDst) AND 28 | NOT p.locPt = n.locPt 29 | ); 30 | -------------------------------------------------------------------------------- /interpreter/tests/Mac_Learning_Missing_Flood.flg: -------------------------------------------------------------------------------- 1 | 2 | /* This variation on Mac_Learning is identical except for different names. 3 | Used for change-impact soundness testing. */ 4 | 5 | // WARNING: This version is old, do not use it as an example of how to run Mac Learning! 6 | 7 | TABLE learned(switchid, portid, macaddr); 8 | 9 | // all the events used here are built-in 10 | 11 | ON packet(p): 12 | 13 | INSERT (p.locSw, p.locPt, p.dlSrc) INTO learned WHERE 14 | not learned(p.locSw, p.locPt, p.dlSrc); 15 | 16 | DELETE (p.locSw, pt, p.dlSrc) FROM learned WHERE 17 | not pt = p.locPt 18 | // The optimization requirement: ctrlr will learn something new 19 | AND not learned(p.locSw, p.locPt, p.dlSrc); 20 | 21 | // This rule doesn't have the negations all at the end of the 22 | // resulting clauses. If no ps forwarded, it means negation-shuffling has failed. 23 | DO forward(n) WHERE 24 | learned(p.locSw, n.locPt, p.dlDst) AND 25 | p.locPt != n.locPt; 26 | 27 | -------------------------------------------------------------------------------- /interpreter/tests/any_port.flg: -------------------------------------------------------------------------------- 1 | 2 | // Should flood + backflow. 3 | ON packet(p): 4 | DO forward(new) WHERE 5 | new.locPt = ANY; -------------------------------------------------------------------------------- /interpreter/tests/arp_and_packet_both.flg: -------------------------------------------------------------------------------- 1 | 2 | TABLE test(ipaddr); 3 | 4 | ON arp_packet_in(apkt): 5 | INSERT (apkt.arp_spa) INTO test; 6 | 7 | ON packet_in(pkt): 8 | DO forward(new) WHERE 9 | NOT pkt.locPt = new.locPt ; 10 | // AND switch_has_port(pkt.locSw, new.locPt); 11 | -------------------------------------------------------------------------------- /interpreter/tests/bad_do_field.flg: -------------------------------------------------------------------------------- 1 | 2 | // This program should produce a compiler error: 3 | 4 | ON packet(pkt): 5 | DO forward(new) WHERE new.omgwtfbbq = 5; 6 | 7 | -------------------------------------------------------------------------------- /interpreter/tests/bad_do_mixed_field_nonfield.flg: -------------------------------------------------------------------------------- 1 | ON packet(pkt): 2 | DO forward(pkt) WHERE pkt = 1; 3 | 4 | -------------------------------------------------------------------------------- /interpreter/tests/bad_input_field_in_body.flg: -------------------------------------------------------------------------------- 1 | 2 | // This program should produce a compiler error: 3 | // It uses an unknown field for switch_port. 4 | 5 | // MALFORMED CLAUSE *BODY* 6 | ON switch_port(swpt): 7 | INSERT (swpt.sw, swpt.pt) INTO switch_has_port 8 | WHERE swpt.omgwtfbbq = swpt.omgwtfbbq; 9 | 10 | -------------------------------------------------------------------------------- /interpreter/tests/bad_input_field_in_head.flg: -------------------------------------------------------------------------------- 1 | 2 | // This program should produce a compiler error: 3 | // It uses an unknown field for switch_port. 4 | 5 | // MALFORMED CLAUSE *HEAD* 6 | ON switch_port(swpt): 7 | INSERT (swpt.notafieldomg, swpt.pt) INTO switch_has_port; 8 | 9 | -------------------------------------------------------------------------------- /interpreter/tests/bad_insert_arity.flg: -------------------------------------------------------------------------------- 1 | ON switch_port(swpt): 2 | INSERT (swpt.sw) INTO switch_has_port; 3 | 4 | -------------------------------------------------------------------------------- /interpreter/tests/bad_insert_field.flg: -------------------------------------------------------------------------------- 1 | ON switch_port(swpt): 2 | INSERT (swpt.swx, swpt.pt) INTO switch_has_port; 3 | 4 | -------------------------------------------------------------------------------- /interpreter/tests/bad_insert_not_field.flg: -------------------------------------------------------------------------------- 1 | ON switch_port(swpt): 2 | INSERT (swpt, swpt.pt) INTO switch_has_port; 3 | 4 | -------------------------------------------------------------------------------- /interpreter/tests/chimp/Change_Equality_a.flg: -------------------------------------------------------------------------------- 1 | /* 2 | May not be executable Flowlog, but meant to test change-impact. 3 | */ 4 | 5 | ON packet_in(p): 6 | 7 | DO forward(n) WHERE 8 | n.locSw = p.locSw and 9 | p.locSw = p.locPt; 10 | 11 | -------------------------------------------------------------------------------- /interpreter/tests/chimp/Change_Equality_b.flg: -------------------------------------------------------------------------------- 1 | /* 2 | May not be executable Flowlog, but meant to test change-impact. 3 | */ 4 | 5 | ON packet_in(p): 6 | 7 | DO forward(n) WHERE 8 | n.locSw = p.locPt and 9 | p.locSw = n.locSw; 10 | 11 | -------------------------------------------------------------------------------- /interpreter/tests/chimp/Change_Reach1_A.flg: -------------------------------------------------------------------------------- 1 | /* 2 | May not be executable Flowlog, but meant to test change-impact. 3 | */ 4 | 5 | EVENT trigger {unused: macaddr}; 6 | 7 | TABLE tab(macaddr); 8 | 9 | ON packet(p): 10 | INSERT (p.dlSrc) INTO tab; 11 | 12 | ON trigger(e): 13 | DELETE (ANY) FROM tab WHERE 14 | tab(x) and tab(y) and tab(z) and 15 | x != y and y != z and x != z; 16 | 17 | -------------------------------------------------------------------------------- /interpreter/tests/chimp/Change_Reach1_B.flg: -------------------------------------------------------------------------------- 1 | /* 2 | May not be executable Flowlog, but meant to test change-impact. 3 | */ 4 | 5 | EVENT trigger {unused: macaddr}; 6 | 7 | TABLE tab(macaddr); 8 | 9 | ON packet(p): 10 | INSERT (p.dlSrc) INTO tab; 11 | 12 | // Lacking the trigger from A 13 | // Triggers "atemporal" change impact 14 | // But the question remains: can that rule ever be satisfied in a reachable state? 15 | 16 | -------------------------------------------------------------------------------- /interpreter/tests/chimp/Change_SimpleNeg_A.flg: -------------------------------------------------------------------------------- 1 | /* 2 | May not be executable Flowlog, but meant to test change-impact. 3 | */ 4 | 5 | TABLE p(switchid); 6 | TABLE r(switchid); 7 | 8 | ON packet_in(p): 9 | 10 | DO forward(n) WHERE 11 | p(n.locSw); 12 | 13 | -------------------------------------------------------------------------------- /interpreter/tests/chimp/Change_SimpleNeg_B.flg: -------------------------------------------------------------------------------- 1 | /* 2 | May not be executable Flowlog, but meant to test change-impact. 3 | */ 4 | 5 | TABLE p(switchid); 6 | TABLE r(switchid); 7 | 8 | ON packet_in(p): 9 | 10 | // Should be equivalent to version without examining R 11 | DO forward(n) WHERE 12 | p(n.locSw) and not r(n.locSw); 13 | DO forward(n) WHERE 14 | p(n.locSw) and r(n.locSw); 15 | -------------------------------------------------------------------------------- /interpreter/tests/lpmatch.flg: -------------------------------------------------------------------------------- 1 | TABLE routes(switchid, ipaddr, int, portid); 2 | 3 | ON startup(e): 4 | INSERT (1, 10.0.0.0, 16, 1) INTO routes; 5 | INSERT (1, 10.0.0.2, 32, 2) INTO routes; 6 | INSERT (2, 10.0.0.0, 24, 3) INTO routes; 7 | 8 | ON ip_packet(p): 9 | DO forward(new) WHERE 10 | 11 | // TODO: right now, need routes BEFORE the ipv4 range. why? 12 | // With that ordering, works fine with -notables, now 13 | 14 | routes(p.locSw, pre, mask, new.locPt) AND 15 | p.nwDst IN pre/mask AND 16 | NOT hasLongerPrefixMatch(p.locSw, p.nwDst, pre, mask); 17 | 18 | 19 | /* 20 | 21 | WANT: 22 | p.nwDst IN X/Y AND NOT (p.nwDst IN x'/y' OR ...) 23 | ISSUE: 24 | compiler produces only equalities inside NOTs at the moment 25 | (and desugar converts to NNF) 26 | 27 | SOLUTION: Brittle special-case in Partial_Eval that produces special compiler result for hasLongerPrefixMatch 28 | when it occurs in *NEGATED* form. Will break if invoked positively. 29 | 30 | */ 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /interpreter/tests/multiple_decls_of_emit.flg: -------------------------------------------------------------------------------- 1 | 2 | // This program should produce a compiler error: 3 | 4 | OUTGOING emit(packet); 5 | 6 | ON packet(pkt): 7 | DO forward(new) WHERE new.locPt != pkt.locPt; -------------------------------------------------------------------------------- /interpreter/tests/multiple_reacts_on_emit.flg: -------------------------------------------------------------------------------- 1 | 2 | // This program should produce a compiler error: 3 | 4 | OUTGOING emit(packet) THEN 5 | SEND TO 127.0.0.1 9091; 6 | 7 | ON packet(pkt): 8 | DO forward(new) WHERE new.locPt != pkt.locPt; 9 | 10 | -------------------------------------------------------------------------------- /interpreter/tests/negatedport.flg: -------------------------------------------------------------------------------- 1 | // ~~~~~~~~~~~~ 2 | // Test extraction of ALL parallel to negated constraints on action 3 | // sudo mn --mac --arp --controller=remote --topo=tree,depth=1,fanout=4 4 | 5 | TABLE forbidden_out(portid); 6 | TABLE nonempty(portid); 7 | 8 | ON startup(e): 9 | INSERT (1) INTO forbidden_out; 10 | INSERT (2) INTO forbidden_out; 11 | INSERT (1) INTO nonempty; 12 | INSERT (2) INTO nonempty; 13 | INSERT (3) INTO nonempty; 14 | 15 | ////////////////////////////////////////////////////////////////////////// 16 | 17 | // EXAMPLE: should be 3, 4 minus inport (compiled) 18 | ON packet(p) WHERE p.dlTyp = 0x0101: // decimal 257 19 | DO forward(new) WHERE 20 | not forbidden_out(new.locPt) and 21 | new.locpt != p.locPt; 22 | 23 | // EXAMPLE: should be drop always (compiled) 24 | ON packet(p) WHERE p.dlTyp = 0x0102: // decimal 258 25 | DO forward(new) WHERE 26 | not forbidden_out(new.locPt) and 27 | forbidden_out(new.locPt) and 28 | new.locpt != p.locPt; 29 | 30 | // EXAMPLE: should be fwd on 3 unless inport = 3 (compiled) 31 | ON packet(p) WHERE p.dlTyp = 0x0103: // decimal 259 32 | DO forward(new) WHERE 33 | not forbidden_out(new.locPt) and 34 | nonempty(new.locPt) and 35 | new.locpt != p.locPt; 36 | 37 | // EXAMPLE: should be flood, provided in port is 1, 2, or 3 (not 4) 38 | ON packet(p) WHERE p.dlTyp = 0x0104: // decimal 260 39 | DO forward(new) WHERE 40 | nonempty(p.locPt) and 41 | new.locpt != p.locPt; 42 | 43 | // EXAMPLE: direct inequality for new.locPt, should be 1,2,4 (compiled) 44 | ON packet(p) WHERE p.dlTyp = 0x0105: // decimal 261 45 | DO forward(new) WHERE 46 | new.locpt != 3; 47 | 48 | // EXAMPLE: switch_has_port added, compiled, out all ports (no ALL; though ALL+INPORT might be cleaner?) 49 | ON packet(p) WHERE p.dlTyp = 0x0106: // decimal 262 50 | DO forward(new) WHERE 51 | new.locpt = ANY; 52 | 53 | // EXAMPLE: should backflow (no mention of a field -> field identical) 54 | ON packet(p) WHERE p.dlTyp = 0x0107: // decimal 263 55 | DO forward(new) WHERE 56 | true; 57 | 58 | // EXAMPLE: simple flood. should use ANY (not port-by-port enumeration) 59 | ON packet(p) WHERE p.dlTyp = 0x0108: // decimal 264 60 | DO forward(new) WHERE 61 | new.locpt != p.locpt; 62 | 63 | // EXAMPLE: send to all but 3 and inport 64 | ON packet(p) WHERE p.dlTyp = 0x0109: // decimal 265 65 | DO forward(new) WHERE 66 | new.locpt != 3 AND 67 | new.locpt != p.locpt; 68 | 69 | // EXAMPLE: Like 107, but with explicit equality 70 | ON packet(p) WHERE p.dlTyp = 0x0110: // decimal 266 71 | DO forward(new) WHERE 72 | new.locpt = p.locPt; 73 | 74 | 75 | /* 76 | 77 | // Not optimized (see semantic equiv between 107 and 110, but one splits out into a rule per inport and one does not) 78 | 79 | *** s1 ------------------------------------------------------------------------ 80 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,dl_type=0x0108 actions=ALL 81 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,dl_type=0x0107 actions=IN_PORT 82 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=1,dl_type=0x0103 actions=output:3 83 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=2,dl_type=0x0110 actions=IN_PORT 84 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=3,dl_type=0x0104 actions=ALL 85 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=4,dl_type=0x0109 actions=output:1,output:2 86 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=1,dl_type=0x0105 actions=IN_PORT,output:2,output:4 87 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=4,dl_type=0x0110 actions=IN_PORT 88 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=1,dl_type=0x0104 actions=ALL 89 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=3,dl_type=0x0101 actions=output:4 90 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=1,dl_type=0x0106 actions=IN_PORT,output:2,output:3,output:4 91 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=1,dl_type=0x0109 actions=output:2,output:4 92 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=1,dl_type=0x0110 actions=IN_PORT 93 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=3,dl_type=0x0110 actions=IN_PORT 94 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=1,dl_type=0x0101 actions=output:3,output:4 95 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=2,dl_type=0x0109 actions=output:1,output:4 96 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=4,dl_type=0x0101 actions=output:3 97 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65535,in_port=2,dl_type=0x0104 actions=ALL 98 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65534,in_port=1 actions=drop 99 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65533,dl_type=0x0109 actions=output:1,output:2,output:4 100 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65533,dl_type=0x0106 actions=output:1,output:2,output:3,output:4 101 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65533,dl_type=0x0105 actions=output:1,output:2,output:4 102 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65532,in_port=3 actions=drop 103 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65531,dl_type=0x0101 actions=output:3,output:4 104 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65531,dl_type=0x0103 actions=output:3 105 | cookie=0x0, duration=4.181s, table=0, n_packets=0, n_bytes=0, priority=65530 actions=drop 106 | 107 | // TODO: In MAC Learning, can get many controller rules at very end. Why? 108 | 109 | */ 110 | -------------------------------------------------------------------------------- /interpreter/tests/pe_startup.flg: -------------------------------------------------------------------------------- 1 | TABLE t1(ipaddr); 2 | TABLE t2(ipaddr); 3 | TABLE t3(ipaddr); 4 | TABLE t4(ipaddr); 5 | 6 | // Expect only one clause left post-PE. 7 | ON ip_packet(pkt): 8 | DO forward(new) WHERE 9 | new.locPt != pkt.locPt AND 10 | t1(pkt.nwSrc) and t2(pkt.nwSrc) and t3(pkt.nwSrc) and t4(pkt.nwSrc); 11 | 12 | ON startup(e): 13 | INSERT (10.0.0.1) INTO t1; 14 | INSERT (10.0.0.1) INTO t2; 15 | INSERT (10.0.0.1) INTO t3; 16 | INSERT (10.0.0.1) INTO t4; 17 | 18 | // A bunch of garbage in the other tables 19 | 20 | INSERT (10.0.0.2) INTO t2; 21 | INSERT (10.0.0.2) INTO t3; 22 | INSERT (10.0.0.2) INTO t4; 23 | 24 | INSERT (10.0.0.3) INTO t2; 25 | INSERT (10.0.0.3) INTO t3; 26 | INSERT (10.0.0.3) INTO t4; 27 | -------------------------------------------------------------------------------- /interpreter/tests/safety/insert-unsafe-var.flg: -------------------------------------------------------------------------------- 1 | // Test safety check 2 | 3 | TABLE test(ipaddr); 4 | 5 | ON packet(p): 6 | INSERT (X) INTO test WHERE p.dlSrc = 5; -------------------------------------------------------------------------------- /interpreter/tests/safety/new-dlsrc-neq-p-dlsrc.flg: -------------------------------------------------------------------------------- 1 | // Test safety check 2 | 3 | ON packet(p): 4 | DO forward(new) WHERE new.dlSrc != p.dlSrc; -------------------------------------------------------------------------------- /interpreter/tests/safety/should-pass.flg: -------------------------------------------------------------------------------- 1 | // Test safety check 2 | 3 | TABLE test(ipaddr); 4 | TABLE test2(ipaddr, ipaddr); 5 | 6 | ON ip_packet(p): 7 | // Implicitly safe since we're talking about tuples already in test. 8 | DELETE (X) FROM test; 9 | 10 | // new.nwDst is safe from appearing in test. 11 | DO forward(new) WHERE test(new.nwDst) AND p.nwSrc!=new.nwDst; 12 | // In fact, this rule should compile. 13 | // Doing so requires substitution of fields as if they were vars, without losing the associated constraints. 14 | // Keeping in mind that this should also be compilable: 15 | DO forward(new) WHERE test(new.nwDst) AND p.nwSrc=new.nwDst; 16 | // if test has 5 and 6 in it, then we get two rows: 17 | //new.nwDst = 5 AND p.nwSrc=5 18 | //new.nwDst = 6 AND p.nwSrc=6 19 | // same here: 20 | DO forward(new) where test(new.nwDst) AND new.nwSrc=new.nwDst; 21 | 22 | // Post-PE: "I see new.nwDst = 5, so substitute it out of ALL *OTHER* instances." 23 | // (Note that this is diff. from *variables*, which get fully substituted out.) 24 | // Alternatively, subs all instances out, then add conjunction of all assignments that we substituted. 25 | 26 | 27 | // But note that 28 | DO forward(new) where p.nwSrc = p.nwDst; 29 | // still shouldn't compile. as well as ...? 30 | DO forward(new) where test(new.nwDst) AND new.nwSrc=p.nwDst; 31 | 32 | // all terms are still SAFE, but needs something stronger for compilation 33 | // Notion of "strong" safety---what is it? "this conj will have a fixed 34 | // value for this field via PE, because it appears in a positive relational 35 | // ref." 36 | 37 | // and what about in? this should compile: 38 | DO forward(new) where test2(x, new.nwSrc) AND p.nwSrc in new.nwSrc/x; 39 | // but this shouldnt 40 | DO forward(new) where test(x) AND p.nwSrc in p.nwDst/x; 41 | 42 | // More simply: 43 | // This can be compiled, and requires less work on the part of the compiler. 44 | // (But can't eliminate X, as we currently do, to pull it off.) 45 | DO forward(new) WHERE test(x) AND p.nwSrc!=x AND new.nwDst=x; -------------------------------------------------------------------------------- /interpreter/tests/test_forward_inference.flg: -------------------------------------------------------------------------------- 1 | /* 2 | Single rule to rewrite network source address field of an ip packet. 3 | Should (1) pass validation 4 | and (2) actually perform the modification when tested with h1 ping h2. 5 | (3) When replacing ip_packet with packet, should not pass validation. 6 | 7 | (4) Moreover, this should be tested with -notables to 8 | confirm that fields are handled properly in XSB. 9 | 10 | */ 11 | 12 | 13 | //ON packet(p): 14 | ON ip_packet(p): 15 | DO forward(new) WHERE 16 | new.nwSrc = 100 AND 17 | new.locPt != p.locPt; 18 | 19 | // With packet: 20 | // Fatal error: exception Flowlog_Parse_Helpers.UndeclaredField("new", "nwsrc") -------------------------------------------------------------------------------- /interpreter/tests/test_subtype_triggers.flg: -------------------------------------------------------------------------------- 1 | /* 2 | h1 arping h2 3 | h3 ping h1 4 | 5 | Should see ethtest(1), ethtest(3), iptest(...3), arptest(...1) 6 | */ 7 | 8 | TABLE arptest(ipaddr); 9 | TABLE iptest(ipaddr); 10 | TABLE ethtest(macaddr); 11 | 12 | ON packet(p): 13 | INSERT (p.dlSrc) INTO ethtest; 14 | 15 | ON ip_packet(p): 16 | INSERT (p.nwSrc) INTO iptest; 17 | 18 | ON arp_packet(p): 19 | INSERT (p.arp_spa) INTO arptest; 20 | 21 | -------------------------------------------------------------------------------- /interpreter/tests/testclocktime.flg: -------------------------------------------------------------------------------- 1 | 2 | REMOTE TABLE clocktime(string, int) 3 | FROM clock AT 127.0.0.1:9091 4 | TIMEOUT 1 seconds; // must be "seconds" not "second" 5 | 6 | EVENT note {sec: int, min: int}; 7 | OUTGOING note_out(note) THEN 8 | SEND TO 127.0.0.1:20000; 9 | 10 | EVENT push {}; 11 | 12 | // limitation: 13 | // 1-day scope. timer service assumes LATER TIME in THIS DAY 14 | // so cannot wrap around yet 15 | EVENT set_alarm {id: string, sec: int, min: int, hr24:int}; 16 | OUTGOING set_alarm_out(set_alarm) THEN 17 | SEND TO 127.0.0.1:9091; 18 | 19 | TABLE recorded_sec(int); 20 | TABLE recorded_min(int); 21 | 22 | ON push(e): 23 | // sec, min, hr12, hr24, mday, mon, year, yday, wday, isdst 24 | INSERT (x) INTO recorded_sec WHERE 25 | clocktime("sec", x); 26 | INSERT (x) INTO recorded_min WHERE 27 | clocktime("min", x); 28 | 29 | DO note_out(n) WHERE clocktime("sec", n.sec) AND clocktime("min", n.min); 30 | 31 | // do nothing with this expiration (yet) 32 | DO set_alarm_out(al) WHERE al.id = "test" and alarmsettings(al.hr24, al.min, al.sec ); 33 | 34 | TABLE alarmsettings(int, int, int); 35 | ON startup(e): 36 | insert (16, 12, 0) into alarmsettings; -------------------------------------------------------------------------------- /interpreter/tests/testcounting1.flg: -------------------------------------------------------------------------------- 1 | // Test for counting in presence of add 2 | // add acts as a generator, requiring an introduction of a new whenever it is used 3 | // 4 | 5 | 6 | table r(int, int); 7 | 8 | on packet(p): 9 | insert (p.dlvlan, x) into r where add(p.dlvlan, 1, x); -------------------------------------------------------------------------------- /interpreter/tests/testcounting_preds.als: -------------------------------------------------------------------------------- 1 | /*********************************************************************************/ 2 | 3 | fact add { 4 | // no cycles of length 1 5 | all x,y,z: univ | z in BuiltIns.add[x][y] implies not z = y and not z = x 6 | } 7 | 8 | // Otherwise the containment check in the +R rule is vacuously true 9 | fact constants_exist { 10 | some C_1 11 | } 12 | 13 | pred testPred[] { 14 | some s, s': State, e: Event | 15 | { 16 | transition[s, e, s'] 17 | s.r != s'.r 18 | } 19 | } 20 | 21 | // unsat 22 | run testPred for 0 but 2 State, 1 Event, 23 | 1 Switchid,1 Portid,2 Macaddr,1 FLInt,1 Ethtyp 24 | 25 | // sat 26 | run testPred for 0 but 2 State, 1 Event, 27 | 1 Switchid,1 Portid,2 Macaddr,2 FLInt,1 Ethtyp 28 | -------------------------------------------------------------------------------- /interpreter/tests/testlessthan.flg: -------------------------------------------------------------------------------- 1 | 2 | 3 | ON packet(p) WHERE lessthan(p.dlSrc,5): 4 | DO forward(new) where p.locPt != new.locPt; 5 | 6 | TABLE timerlen(int); 7 | 8 | EVENT start_timer {seconds: int, ms:int, id: string, id2: string}; 9 | EVENT timer_expired {id: string, id2: string}; 10 | OUTGOING start_timer_out(start_timer) THEN 11 | SEND TO 127.0.0.1 9091; 12 | 13 | ON startup(e): 14 | DO start_timer_out(t) WHERE t.id = "test" and t.id2 = 5 and t.seconds = 0 and t.ms = 100; 15 | INSERT (500) into timerlen; 16 | 17 | ON timer_expired(tex): 18 | 19 | DO start_timer_out(t) WHERE t.id = "test" and t.id2 = 192.168.1.1 and t.seconds = 0 and timerlen(old) and add(old, 100, t.ms); 20 | 21 | INSERT (x) into timerlen WHERE timerlen(y) AND add(y, 100, x); 22 | DELETE(x) from timerlen; // don't forget this, or you'll get multiple timers at once -------------------------------------------------------------------------------- /interpreter/tests/testmultimod.flg: -------------------------------------------------------------------------------- 1 | 2 | // OK 3 | //ON ip_packet(p) WHERE p.locPt = 1: 4 | // DO forward(new) WHERE new.locPt = 2 AND new.dlSrc = ca:fe:ff:ff:ff:ff AND new.dlDst = ca:fe:aa:aa:aa:aa; 5 | 6 | // OK---but compiler produces "IN_PORT" here. 7 | //ON ip_packet(p) WHERE p.locPt = 2: 8 | // DO forward(new) WHERE new.locPt = 2 AND new.dlSrc = ab:ba:ff:ff:ff:ff AND new.dlDst = ab:ba:aa:aa:aa:aa; 9 | 10 | 11 | ON ip_packet(p): 12 | DO forward(new) WHERE 13 | (new.locPt = 2 OR new.locPt = 3) AND 14 | new.dlSrc = ab:ba:ff:ff:ff:ff AND 15 | new.dlDst = ab:ba:aa:aa:aa:aa; 16 | 17 | 18 | /* 19 | 20 | // Expect this on tree,fanout=3,depth=1 21 | // TODO: note the repeated mods, but at least it is sound 22 | 23 | mininet> dpctl dump-flows --rsort 24 | *** s1 ------------------------------------------------------------------------ 25 | cookie=0x0, duration=39.473s, table=0, n_packets=0, n_bytes=0, priority=65535,ip,in_port=2 actions=mod_dl_src:ab:ba:ff:ff:ff:ff,mod_dl_dst:ab:ba:aa:aa:aa:aa,IN_PORT,mod_dl_src:ab:ba:ff:ff:ff:ff,mod_dl_dst:ab:ba:aa:aa:aa:aa,output:3 26 | cookie=0x0, duration=39.473s, table=0, n_packets=0, n_bytes=0, priority=65535,ip,in_port=3 actions=mod_dl_src:ab:ba:ff:ff:ff:ff,mod_dl_dst:ab:ba:aa:aa:aa:aa,output:2,mod_dl_src:ab:ba:ff:ff:ff:ff,mod_dl_dst:ab:ba:aa:aa:aa:aa,IN_PORT 27 | cookie=0x0, duration=39.473s, table=0, n_packets=0, n_bytes=0, priority=65534,ip actions=mod_dl_src:ab:ba:ff:ff:ff:ff,mod_dl_dst:ab:ba:aa:aa:aa:aa,output:2,mod_dl_src:ab:ba:ff:ff:ff:ff,mod_dl_dst:ab:ba:aa:aa:aa:aa,output:3 28 | cookie=0x0, duration=39.473s, table=0, n_packets=0, n_bytes=0, priority=65533 actions=drop 29 | */ -------------------------------------------------------------------------------- /interpreter/tests/testroutes.flg: -------------------------------------------------------------------------------- 1 | /* Test dynamic routing 2 | */ 3 | 4 | EVENT linkstate_up {srcsw: switchid, srcpt: portid, dstsw: switchid, dstpt: portid, cost: int}; 5 | EVENT linkstate_down {srcsw: switchid, srcpt: portid, dstsw: switchid, dstpt: portid}; 6 | EVENT switch_config {swid: switchid, ptid: portid, prefix: ipaddr, mask: int}; 7 | EVENT trigger_send {}; 8 | 9 | REMOTE TABLE routes(switchid, ipaddr, ipaddr, portid) 10 | FROM routes AT 127.0.0.1:9999; 11 | 12 | OUTGOING send_linkstate_up(linkstate_up) THEN 13 | SEND TO 127.0.0.1:9999; 14 | OUTGOING send_linkstate_down(linkstate_down) THEN 15 | SEND TO 127.0.0.1:9999; 16 | OUTGOING send_switch_config(switch_config) THEN 17 | SEND TO 127.0.0.1:9999; 18 | 19 | TABLE linkstate(switchid, portid, switchid, portid, int); 20 | TABLE config(switchid, portid, ipaddr, int); 21 | 22 | /////////////////////////////////////////////////////////////////////// 23 | ON startup(e): 24 | // 3 switches, triangle topology. the link between sw=1 and sw=2 is expensive 25 | INSERT (1, 1, 2, 1, 10) INTO linkstate; 26 | INSERT (2, 2, 3, 1, 4) INTO linkstate; 27 | INSERT (3, 2, 1, 2, 4) INTO linkstate; 28 | INSERT (2, 1, 1, 1, 10) INTO linkstate; 29 | INSERT (3, 1, 2, 2, 4) INTO linkstate; 30 | INSERT (1, 2, 3, 2, 4) INTO linkstate; 31 | // 3 link subnets 32 | INSERT (1, 1, 10.1.1.0, 24) INTO config; 33 | INSERT (2, 1, 10.1.1.0, 24) INTO config; 34 | INSERT (2, 2, 10.1.2.0, 24) INTO config; 35 | INSERT (3, 1, 10.1.2.0, 24) INTO config; 36 | INSERT (1, 2, 10.1.3.0, 24) INTO config; 37 | INSERT (3, 2, 10.1.3.0, 24) INTO config; 38 | // host subnets 39 | INSERT (1, 3, 10.100.0.0, 16) INTO config; 40 | INSERT (2, 3, 10.200.1.0, 24) INTO config; 41 | INSERT (3, 3, 10.200.2.0, 24) INTO config; 42 | INSERT (3, 4, 10.150.0.0, 16) INTO config; 43 | 44 | /////////////////////////////////////////////////////////////////////// 45 | 46 | ON trigger_send(e): 47 | DO send_linkstate_up(ls) WHERE 48 | linkstate(ls.srcsw, ls.srcpt, ls.dstsw, ls.dstpt, ls.cost); 49 | ON trigger_send(e): 50 | DO send_switch_config(c) WHERE 51 | config(c.swid, c.ptid, c.prefix, c.mask); 52 | 53 | /////////////////////////////////////////////////////////////////////// 54 | 55 | ON ip_packet(p): 56 | DO forward(new) WHERE 57 | routes(p.locSw, prefix, mask, new.locPt) AND 58 | p.nwDst IN prefix/mask; 59 | 60 | // possible issue: not doing smallest-prefix; may need to 61 | -------------------------------------------------------------------------------- /interpreter/tests/vlan/vlan.flg: -------------------------------------------------------------------------------- 1 | // VLan test 2 | 3 | // sudo mn --topo=tree,depth=2,fanout=2 --controller=remote --mac --arp 4 | 5 | ////////////////////////////////////////////////////////////////////// 6 | 7 | // vlan set -> increment the vlan field when passing a switch 8 | // not a standard use of the field, but good test of *controller* behavior 9 | // (this can't be compiled) 10 | ON packet(p) WHERE p.dlvlan != -1: 11 | DO forward(new) WHERE new.locPt != p.locPt 12 | AND add(p.dlvlan, 1, new.dlvlan); 13 | 14 | // REMEMBER: all but the final arg to add must be strongly safe. 15 | // (i.e., the "result" needs to be the last arg) 16 | 17 | ////////////////////////////////////////////////////////////////////// 18 | 19 | // vlan not set -> just flood 20 | // (this WILL be compiled) 21 | ON packet(p) WHERE p.dlvlan = -1: 22 | DO forward(new) WHERE new.locPt != p.locPt; 23 | 24 | ////////////////////////////////////////////////////////////////////// 25 | 26 | // Allow us to manufacture packets that DO have the vlan set 27 | 28 | EVENT vlan_test {vlan: int}; 29 | 30 | ON vlan_test(t): 31 | DO emit(new) WHERE new.dlvlan = t.vlan 32 | AND new.dltyp = 0x001 // arbitrary value. don't try 0x800 or it'l try parsing IP fields 33 | AND new.dldst = 00:00:00:00:00:01 34 | AND new.dlsrc = ca:fe:ca:fe:00:01 35 | AND switch_has_port(new.locSw, new.locPt); 36 | 37 | // TODO: > ? -------------------------------------------------------------------------------- /interpreter/tests/warnings/typewarning_multiple.flg: -------------------------------------------------------------------------------- 1 | TABLE foo(ipaddr); 2 | 3 | // Expect an warning here: foo's first column is IPaddr, not Mac Addr 4 | ON ip_packet(p) WHERE foo(p.dlSrc): 5 | DO forward(new) WHERE new.locPt != p.locPt; 6 | 7 | // Expect a warning here: port vs. switch. possible mistake. 8 | ON ip_packet(p): 9 | DO forward(new) WHERE new.locPt != p.locSw; 10 | -------------------------------------------------------------------------------- /interpreter/thrift/Flowlog_Thrift_In.ml: -------------------------------------------------------------------------------- 1 | (* 2 | Modified from ocaml tutorial by Tim 3 | 4 | This is the FlowLog server for catching notifications, 5 | sending queries, and sending notifications. 6 | *) 7 | 8 | (* note to self: all open does is avoid the "module." 9 | not quite like #include or require. *) 10 | open Arg 11 | open Thrift 12 | open Flowlog_rpc_types 13 | open Thread 14 | open Flowlog_Types 15 | open ExtList.List 16 | open Flowlog_Helpers 17 | open Partial_Eval 18 | open Printf 19 | 20 | (*open Lwt*) 21 | 22 | (* "Die, Bart, Die!" is German for "The, Bart, The!". -- Sideshow Bob *) 23 | exception Die;; 24 | let sod = function 25 | Some v -> v 26 | | None -> raise Die;; 27 | 28 | let lowercase_notif_values tbl = 29 | Hashtbl.iter (fun k v -> 30 | (* newbie ocaml note: <> is structural inequality, != is physical *) 31 | if (String.lowercase k) <> k then 32 | begin 33 | Hashtbl.add tbl (String.lowercase k) v; 34 | Hashtbl.remove tbl k; 35 | Printf.printf "Replacing %s key with lowercase: %s -> %s\n%!" k (String.lowercase k) v; 36 | end; 37 | Printf.printf "%s -> %s\n%!" k v) tbl; 38 | 39 | class fl_handler (a_program : flowlog_program) = 40 | object (self) 41 | inherit FlowLogInterpreter.iface 42 | 43 | (* Always created within a program context *) 44 | val the_program : flowlog_program = a_program; 45 | 46 | method notifyMe rpc_notif : unit = 47 | let ntypestr = sod ((sod rpc_notif)#get_notificationType) in 48 | let values = sod ((sod rpc_notif)#get_values) in 49 | 50 | Printf.printf "received notification. type=%s\n%!" ntypestr; 51 | lowercase_notif_values values; (* case insensitive field names *) 52 | try 53 | let fieldlist = map String.lowercase (get_fields_for_type the_program ntypestr) in 54 | (* construct a list of terms from the hashtbl in values. use the type as an index *) 55 | 56 | let vals = fold_left (fun acc fld -> 57 | if (not (Hashtbl.mem values fld)) then 58 | raise (Failure ("Field "^fld^" was not included in an incoming event of type: "^ntypestr)) 59 | else 60 | (fld, Hashtbl.find values fld) :: acc) 61 | [] fieldlist in 62 | 63 | let xsb_vals = map (fun (fname, fval) -> (fname, flvalue_to_xsbable fval)) vals in 64 | let ev: event = {typeid = ntypestr; values = construct_map xsb_vals} in 65 | ignore (respond_to_notification the_program ev IncThrift); 66 | 67 | with 68 | | Not_found -> (Printf.printf " *** Nothing to be done with this notification.\n%!") 69 | | Failure(msg) -> (Printf.printf " *** ERROR! Ignoring notification (type=%s) for reason: %s\n%!" ntypestr msg) 70 | 71 | end 72 | 73 | type connection = { 74 | trans : Transport.t ; 75 | proto : Thrift.Protocol.t; 76 | bb : BlackBox.client ; 77 | } 78 | 79 | (* The ~ denotes a keyword argument *) 80 | let connect ~host port = 81 | let tx = new TSocket.t host port in 82 | let proto = new TBinaryProtocol.t tx in 83 | let bb = new BlackBox.client proto proto in 84 | tx#opn; 85 | { trans = tx ; proto = proto; bb = bb};; 86 | 87 | let start_listening (a_program : flowlog_program) : unit = 88 | let h = new fl_handler a_program in 89 | let proc = new FlowLogInterpreter.processor h in 90 | let port = 9090 in (* FL listen on 9090 *) 91 | let pf = new TBinaryProtocol.factory in 92 | let server = new TThreadedServer.t 93 | proc 94 | (new TServerSocket.t port) 95 | (new Transport.factory) 96 | pf 97 | pf 98 | in 99 | (* first thing: listen for notifications *) 100 | (* returns handle of thread. ignore result to avoid warning *) 101 | 102 | ignore (Thread.create (fun x -> (server#serve)) 0); 103 | (*Lwt.async (fun () -> return (server#serve));*) 104 | printf "Started to listen for notifications.\n%!";; 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /interpreter/thrift/Flowlog_Thrift_Out.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | open Thrift 4 | open Flowlog_rpc_types 5 | open Flowlog_Types 6 | open Flowlog_Helpers 7 | open ExtList.List 8 | open Printf 9 | 10 | type connection = { 11 | trans : Transport.t ; 12 | proto : Thrift.Protocol.t; 13 | bb : BlackBox.client ; 14 | } 15 | 16 | (* The ~ denotes a keyword argument *) 17 | let connect ~host port = 18 | let tx = new TSocket.t host port in 19 | let proto = new TBinaryProtocol.t tx in 20 | let bb = new BlackBox.client proto proto in 21 | tx#opn; 22 | { trans = tx ; proto = proto; bb = bb} 23 | ;; 24 | 25 | (* "Die, Bart, Die!" is German for "The, Bart, The!". -- Sideshow Bob *) 26 | exception Die;; 27 | let sod = function 28 | Some v -> v 29 | | None -> raise Die;; 30 | 31 | let print_notif_values tbl = 32 | Hashtbl.iter (fun k v -> 33 | printf "%s -> %s\n%!" k v) tbl 34 | 35 | 36 | (* The resulting list contains all the tuples (as string lists) returned. 37 | 38 | Note: each query opens a separate, new connection to the black-box. 39 | *) 40 | let doBBquery (qryname: string) (bbip: string) (bbport: string) (args: term list) (types: typeid list): string list list = 41 | let dotted_host = Packet.string_of_ip (nwaddr_of_int_string bbip) in 42 | printf "Sending BB query to %s:%s...\n%!" dotted_host bbport; 43 | let cli = connect ~host:dotted_host (int_of_string bbport) in 44 | try 45 | printf "Connected.\n%!"; 46 | let qry = new query in 47 | qry#set_relName qryname; 48 | 49 | let arguments = (map2 (fun t typid -> (pretty_print_value typid (string_of_term t))) args types) in 50 | printf "Args: %s\n%!" (string_of_list "," identity arguments); 51 | qry#set_arguments arguments; 52 | 53 | let qresult = cli.bb#doQuery qry in 54 | printf "doQuery called.\n%!"; 55 | let result = (sod qresult#get_result) in 56 | let xsb_result = map (fun tup -> map flvalue_to_xsbable tup) result in 57 | cli.trans#close; 58 | xsb_result 59 | 60 | with Transport.E (_,what) -> 61 | printf "ERROR sending query: %s\n%!" what; 62 | raise (Failure what);; 63 | 64 | (* 65 | Sends a notification to blackbox. 66 | Each notification opens a separate, new, connection to the black-box. 67 | *) 68 | 69 | let doBBnotify (ev: event) (bbip: string) (bbport: string) (evfields: (string * typeid) list): unit = 70 | let dotted_host = Packet.string_of_ip (nwaddr_of_int_string bbip) in 71 | let cli = connect ~host:dotted_host (int_of_string bbport) in 72 | try 73 | printf "Sending notification to %s:%s...\n%!" dotted_host bbport; 74 | let notif = new notification in 75 | let tbl = (Hashtbl.create (StringMap.cardinal ev.values)) in 76 | notif#set_notificationType ev.typeid; 77 | notif#set_values tbl; 78 | StringMap.iter (fun k v -> Hashtbl.add tbl k (pretty_print_value (assoc k evfields) v)) ev.values; 79 | Hashtbl.iter (fun k v -> printf " %s -> %s\n%!" k v) tbl; 80 | printf "Making RPC invocation...\n%!"; 81 | cli.bb#notifyMe notif; 82 | printf "RPC invocation complete. Closing socket...\n%!"; 83 | cli.trans#close; 84 | printf "RPC invocation complete. Socket closed.\n%!"; 85 | with | Transport.E (_,what) -> 86 | printf "ERROR sending notification: %s\n%!" what; 87 | raise (Failure what) 88 | | _ -> printf "Unknown problem sending event.\n%!"; 89 | 90 | -------------------------------------------------------------------------------- /interpreter/thrift/Makefile: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: a3c674b4239234cbbe53afe090018954) 3 | 4 | SETUP = ocaml setup.ml 5 | 6 | build: setup.data 7 | $(SETUP) -build $(BUILDFLAGS) 8 | 9 | doc: setup.data build 10 | $(SETUP) -doc $(DOCFLAGS) 11 | 12 | test: setup.data build 13 | $(SETUP) -test $(TESTFLAGS) 14 | 15 | all: 16 | $(SETUP) -all $(ALLFLAGS) 17 | 18 | install: setup.data 19 | $(SETUP) -install $(INSTALLFLAGS) 20 | 21 | uninstall: setup.data 22 | $(SETUP) -uninstall $(UNINSTALLFLAGS) 23 | 24 | reinstall: setup.data 25 | $(SETUP) -reinstall $(REINSTALLFLAGS) 26 | 27 | clean: 28 | $(SETUP) -clean $(CLEANFLAGS) 29 | 30 | distclean: 31 | $(SETUP) -distclean $(DISTCLEANFLAGS) 32 | 33 | setup.data: 34 | $(SETUP) -configure $(CONFIGUREFLAGS) 35 | 36 | configure: 37 | $(SETUP) -configure $(CONFIGUREFLAGS) 38 | 39 | .PHONY: build doc test all install uninstall reinstall clean distclean configure 40 | 41 | # OASIS_STOP 42 | -------------------------------------------------------------------------------- /interpreter/thrift/_oasis: -------------------------------------------------------------------------------- 1 | Name: flowlog_thrift 2 | Version: 1.0 3 | OASISFormat: 0.3 4 | Synopsis: FlowLog Thrift 5 | Authors: Tim Nelson 6 | License: Apache-2.0 7 | Homepage: http://cs.wpi.edu/~tn 8 | BuildTools: ocamlbuild 9 | Plugins: META (0.3), 10 | DevFiles (0.3) 11 | 12 | Library flowlog_thrift 13 | Path: gen-ocaml 14 | FindlibName: flowlog_thrift 15 | buildTools: ocamlbuild 16 | BuildDepends: threads,thrift 17 | Modules: Flowlog_rpc_consts,BlackBox,FlowLogInterpreter 18 | XMETARequires: threads 19 | 20 | Executable notify 21 | Path: . 22 | MainIs: notify.ml 23 | Build$: true 24 | CompiledObject: best 25 | BuildDepends: thrift, flowlog_thrift, threads, str 26 | 27 | Executable Timer 28 | Path: . 29 | MainIs: timer.ml 30 | Build$: true 31 | CompiledObject: best 32 | BuildDepends: thrift, flowlog_thrift, threads, extlib 33 | 34 | Executable Police 35 | Path: . 36 | MainIs: police_tipline.ml 37 | Build$: true 38 | CompiledObject: best 39 | BuildDepends: thrift, flowlog_thrift, threads 40 | 41 | Executable Placeholder 42 | Path: . 43 | MainIs: placeholder.ml 44 | Build$: true 45 | CompiledObject: best 46 | BuildDepends: thrift, flowlog_thrift, threads 47 | -------------------------------------------------------------------------------- /interpreter/thrift/_tags: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: aed94110a67d0a9f8b6bf1c459f801a4) 3 | # Ignore VCS directories, you can use the same kind of rule outside 4 | # OASIS_START/STOP if you want to exclude directories that contains 5 | # useless stuff for the build process 6 | <**/.svn>: -traverse 7 | <**/.svn>: not_hygienic 8 | ".bzr": -traverse 9 | ".bzr": not_hygienic 10 | ".hg": -traverse 11 | ".hg": not_hygienic 12 | ".git": -traverse 13 | ".git": not_hygienic 14 | "_darcs": -traverse 15 | "_darcs": not_hygienic 16 | # Library flowlog_thrift 17 | "gen-ocaml/flowlog_thrift.cmxs": use_flowlog_thrift 18 | : pkg_threads 19 | : pkg_thrift 20 | # Executable notify 21 | : pkg_str 22 | : pkg_threads 23 | : pkg_thrift 24 | : use_flowlog_thrift 25 | <*.ml{,i}>: pkg_str 26 | # Executable Timer 27 | : pkg_extlib 28 | : pkg_threads 29 | : pkg_thrift 30 | : use_flowlog_thrift 31 | <*.ml{,i}>: pkg_extlib 32 | # Executable Police 33 | : pkg_threads 34 | : pkg_thrift 35 | : use_flowlog_thrift 36 | # Executable Placeholder 37 | : pkg_threads 38 | : pkg_thrift 39 | : use_flowlog_thrift 40 | <*.ml{,i}>: pkg_threads 41 | <*.ml{,i}>: pkg_thrift 42 | <*.ml{,i}>: use_flowlog_thrift 43 | # OASIS_STOP 44 | -------------------------------------------------------------------------------- /interpreter/thrift/configure: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # OASIS_START 4 | # DO NOT EDIT (digest: dc86c2ad450f91ca10c931b6045d0499) 5 | set -e 6 | 7 | FST=true 8 | for i in "$@"; do 9 | if $FST; then 10 | set -- 11 | FST=false 12 | fi 13 | 14 | case $i in 15 | --*=*) 16 | ARG=${i%%=*} 17 | VAL=${i##*=} 18 | set -- "$@" "$ARG" "$VAL" 19 | ;; 20 | *) 21 | set -- "$@" "$i" 22 | ;; 23 | esac 24 | done 25 | 26 | ocaml setup.ml -configure "$@" 27 | # OASIS_STOP 28 | -------------------------------------------------------------------------------- /interpreter/thrift/flowlog_rpc.thrift: -------------------------------------------------------------------------------- 1 | /* Apache Thrift RPC definition for FlowLog */ 2 | 3 | ////////////////////////////////////////////////////// 4 | // What kinds of atomic values get passed back and forth? 5 | // Not necessarily just numbers! An FLValue represents 6 | // some FlowLog term, which is a constant or a variable. 7 | ////////////////////////////////////////////////////// 8 | 9 | typedef string FLValue 10 | 11 | ////////////////////////////////////////////////////// 12 | // A notification (from either a black-box to the 13 | // controller or vice versa) has this form: a string 14 | // describing the type of notification, and a map 15 | // containing the fields for that type. E.g. an AppleTV 16 | // registration event would contain fields for the mac addresses 17 | // of the requesting machine and the apple tv in question. 18 | // 19 | // ASSUMPTION: values of fields are atomic. 20 | ////////////////////////////////////////////////////// 21 | 22 | struct Notification 23 | { 24 | // Use a map to take field names to atomic values. 25 | // (The Thrift format requires we assign a number to each field.) 26 | 1: required string notificationType; 27 | 2: required map values; 28 | } 29 | 30 | ////////////////////////////////////////////////////// 31 | // A query to a black-box from the controller asks: 32 | // "For what tuples is R(t_1, ..., t_m) 33 | // true?" (where each t_1 is either a constant or an X_i). 34 | // 35 | // ASSUMPTION: Is this enough to cover all cases? 36 | // It covers the Boolean case (where all terms are constant) 37 | // since the result will be either a singleton or non-empty. 38 | ////////////////////////////////////////////////////// 39 | 40 | struct Query 41 | { 42 | 1: required string relName; 43 | 2: required list arguments; 44 | } 45 | 46 | ////////////////////////////////////////////////////// 47 | // A reply to a query is a set of tuples. (See above.) 48 | // TN 8/25: Changed to be a list of lists instead of a set of lists 49 | // to ease interaction with Python 50 | ////////////////////////////////////////////////////// 51 | 52 | struct QueryReply 53 | { 54 | 1: required list> result; 55 | 2: optional string exception_code; 56 | 3: optional string exception_message; 57 | } 58 | 59 | ////////////////////////////////////////////////////// 60 | // A *SERVICE* is like a Java interface: it defines 61 | // functions that implementors of the service provide. 62 | // Here the FlowLog interpreter has the ability to 63 | // receive notifications from external code. 64 | // 65 | // (Like in structs, Thrift makes us give a unique number 66 | // ID for each argument to each function.) 67 | ////////////////////////////////////////////////////// 68 | 69 | service FlowLogInterpreter 70 | { 71 | void notifyMe(1:Notification notify); 72 | } 73 | 74 | ////////////////////////////////////////////////////// 75 | // Likewise, a black-box has the power to receive 76 | // notifications, but can also be queried. 77 | ////////////////////////////////////////////////////// 78 | 79 | service BlackBox 80 | { 81 | // A black-box answers queries. 82 | QueryReply doQuery(1:Query q); 83 | void notifyMe(1:Notification notify); 84 | } 85 | 86 | -------------------------------------------------------------------------------- /interpreter/thrift/gen-ocaml/BlackBox.mli: -------------------------------------------------------------------------------- 1 | (* 2 | Autogenerated by Thrift Compiler (0.9.0) 3 | 4 | DO NOT EDIT UNLESS YOU ARE SURE YOU KNOW WHAT YOU ARE DOING 5 | *) 6 | 7 | open Thrift 8 | open Flowlog_rpc_types 9 | 10 | class virtual iface : 11 | object 12 | method virtual doQuery : query option -> queryReply 13 | method virtual notifyMe : notification option -> unit 14 | end 15 | 16 | class client : Protocol.t -> Protocol.t -> 17 | object 18 | method doQuery : query -> queryReply 19 | method notifyMe : notification -> unit 20 | end 21 | 22 | class processor : iface -> 23 | object 24 | inherit Processor.t 25 | 26 | val processMap : (string, int * Protocol.t * Protocol.t -> unit) Hashtbl.t 27 | method process : Protocol.t -> Protocol.t -> bool 28 | end 29 | 30 | -------------------------------------------------------------------------------- /interpreter/thrift/gen-ocaml/FlowLogInterpreter.ml: -------------------------------------------------------------------------------- 1 | (* 2 | Autogenerated by Thrift Compiler (0.9.0) 3 | 4 | DO NOT EDIT UNLESS YOU ARE SURE YOU KNOW WHAT YOU ARE DOING 5 | *) 6 | 7 | open Thrift 8 | open Flowlog_rpc_types 9 | 10 | (* HELPER FUNCTIONS AND STRUCTURES *) 11 | 12 | class notifyMe_args = 13 | object (self) 14 | val mutable _notify : notification option = None 15 | method get_notify = _notify 16 | method grab_notify = match _notify with None->raise (Field_empty "notifyMe_args.notify") | Some _x50 -> _x50 17 | method set_notify _x50 = _notify <- Some _x50 18 | method unset_notify = _notify <- None 19 | method reset_notify = _notify <- None 20 | 21 | method copy = 22 | let _new = Oo.copy self in 23 | if _notify <> None then 24 | _new#set_notify self#grab_notify#copy; 25 | _new 26 | method write (oprot : Protocol.t) = 27 | oprot#writeStructBegin "notifyMe_args"; 28 | (match _notify with None -> () | Some _v -> 29 | oprot#writeFieldBegin("notify",Protocol.T_STRUCT,1); 30 | _v#write(oprot); 31 | oprot#writeFieldEnd 32 | ); 33 | oprot#writeFieldStop; 34 | oprot#writeStructEnd 35 | end 36 | let rec read_notifyMe_args (iprot : Protocol.t) = 37 | let _str53 = new notifyMe_args in 38 | ignore(iprot#readStructBegin); 39 | (try while true do 40 | let (_,_t54,_id55) = iprot#readFieldBegin in 41 | if _t54 = Protocol.T_STOP then 42 | raise Break 43 | else (); 44 | (match _id55 with 45 | | 1 -> (if _t54 = Protocol.T_STRUCT then 46 | _str53#set_notify (read_notification iprot) 47 | else 48 | iprot#skip _t54) 49 | | _ -> iprot#skip _t54); 50 | iprot#readFieldEnd; 51 | done; () 52 | with Break -> ()); 53 | iprot#readStructEnd; 54 | _str53 55 | 56 | class notifyMe_result = 57 | object (self) 58 | method copy = 59 | let _new = Oo.copy self in 60 | _new 61 | method write (oprot : Protocol.t) = 62 | oprot#writeStructBegin "notifyMe_result"; 63 | oprot#writeFieldStop; 64 | oprot#writeStructEnd 65 | end 66 | let rec read_notifyMe_result (iprot : Protocol.t) = 67 | let _str58 = new notifyMe_result in 68 | ignore(iprot#readStructBegin); 69 | (try while true do 70 | let (_,_t59,_id60) = iprot#readFieldBegin in 71 | if _t59 = Protocol.T_STOP then 72 | raise Break 73 | else (); 74 | (match _id60 with 75 | | _ -> iprot#skip _t59); 76 | iprot#readFieldEnd; 77 | done; () 78 | with Break -> ()); 79 | iprot#readStructEnd; 80 | _str58 81 | 82 | class virtual iface = 83 | object (self) 84 | method virtual notifyMe : notification option -> unit 85 | end 86 | 87 | class client (iprot : Protocol.t) (oprot : Protocol.t) = 88 | object (self) 89 | val mutable seqid = 0 90 | method notifyMe notify = 91 | self#send_notifyMe notify; 92 | self#recv_notifyMe 93 | method private send_notifyMe notify = 94 | oprot#writeMessageBegin ("notifyMe", Protocol.CALL, seqid); 95 | let args = new notifyMe_args in 96 | args#set_notify notify; 97 | args#write oprot; 98 | oprot#writeMessageEnd; 99 | oprot#getTransport#flush 100 | method private recv_notifyMe = 101 | let (fname, mtype, rseqid) = iprot#readMessageBegin in 102 | (if mtype = Protocol.EXCEPTION then 103 | let x = Application_Exn.read iprot in 104 | (iprot#readMessageEnd; raise (Application_Exn.E x)) 105 | else ()); 106 | let _ = read_notifyMe_result iprot in 107 | iprot#readMessageEnd; 108 | () 109 | end 110 | 111 | class processor (handler : iface) = 112 | object (self) 113 | inherit Processor.t 114 | 115 | val processMap = Hashtbl.create 1 116 | method process iprot oprot = 117 | let (name, typ, seqid) = iprot#readMessageBegin in 118 | if Hashtbl.mem processMap name then 119 | (Hashtbl.find processMap name) (seqid, iprot, oprot) 120 | else ( 121 | iprot#skip(Protocol.T_STRUCT); 122 | iprot#readMessageEnd; 123 | let x = Application_Exn.create Application_Exn.UNKNOWN_METHOD ("Unknown function "^name) in 124 | oprot#writeMessageBegin(name, Protocol.EXCEPTION, seqid); 125 | x#write oprot; 126 | oprot#writeMessageEnd; 127 | oprot#getTransport#flush 128 | ); 129 | true 130 | method private process_notifyMe (seqid, iprot, oprot) = 131 | let args = read_notifyMe_args iprot in 132 | iprot#readMessageEnd; 133 | let result = new notifyMe_result in 134 | (handler#notifyMe args#get_notify); 135 | oprot#writeMessageBegin ("notifyMe", Protocol.REPLY, seqid); 136 | result#write oprot; 137 | oprot#writeMessageEnd; 138 | oprot#getTransport#flush 139 | initializer 140 | Hashtbl.add processMap "notifyMe" self#process_notifyMe; 141 | end 142 | 143 | -------------------------------------------------------------------------------- /interpreter/thrift/gen-ocaml/FlowLogInterpreter.mli: -------------------------------------------------------------------------------- 1 | (* 2 | Autogenerated by Thrift Compiler (0.9.0) 3 | 4 | DO NOT EDIT UNLESS YOU ARE SURE YOU KNOW WHAT YOU ARE DOING 5 | *) 6 | 7 | open Thrift 8 | open Flowlog_rpc_types 9 | 10 | class virtual iface : 11 | object 12 | method virtual notifyMe : notification option -> unit 13 | end 14 | 15 | class client : Protocol.t -> Protocol.t -> 16 | object 17 | method notifyMe : notification -> unit 18 | end 19 | 20 | class processor : iface -> 21 | object 22 | inherit Processor.t 23 | 24 | val processMap : (string, int * Protocol.t * Protocol.t -> unit) Hashtbl.t 25 | method process : Protocol.t -> Protocol.t -> bool 26 | end 27 | 28 | -------------------------------------------------------------------------------- /interpreter/thrift/gen-ocaml/META: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: f2afe00b5f8adee9682dfe01974a9a3d) 3 | version = "1.0" 4 | description = "FlowLog Thrift" 5 | requires = "threads" 6 | archive(byte) = "flowlog_thrift.cma" 7 | archive(byte, plugin) = "flowlog_thrift.cma" 8 | archive(native) = "flowlog_thrift.cmxa" 9 | archive(native, plugin) = "flowlog_thrift.cmxs" 10 | exists_if = "flowlog_thrift.cma" 11 | # OASIS_STOP 12 | 13 | -------------------------------------------------------------------------------- /interpreter/thrift/gen-ocaml/flowlog_rpc_consts.ml: -------------------------------------------------------------------------------- 1 | (* 2 | Autogenerated by Thrift Compiler (0.9.0) 3 | 4 | DO NOT EDIT UNLESS YOU ARE SURE YOU KNOW WHAT YOU ARE DOING 5 | *) 6 | 7 | open Thrift 8 | open Flowlog_rpc_types 9 | -------------------------------------------------------------------------------- /interpreter/thrift/gen-ocaml/flowlog_rpc_types.mli: -------------------------------------------------------------------------------- 1 | (* 2 | Autogenerated by Thrift Compiler (0.9.0) 3 | 4 | DO NOT EDIT UNLESS YOU ARE SURE YOU KNOW WHAT YOU ARE DOING 5 | *) 6 | 7 | open Thrift 8 | class notification : 9 | object ('a) 10 | method get_notificationType : string option 11 | method grab_notificationType : string 12 | method set_notificationType : string -> unit 13 | method unset_notificationType : unit 14 | method reset_notificationType : unit 15 | method get_values : (string,string) Hashtbl.t option 16 | method grab_values : (string,string) Hashtbl.t 17 | method set_values : (string,string) Hashtbl.t -> unit 18 | method unset_values : unit 19 | method reset_values : unit 20 | method copy : 'a 21 | method write : Protocol.t -> unit 22 | end 23 | val read_notification : Protocol.t -> notification 24 | class query : 25 | object ('a) 26 | method get_relName : string option 27 | method grab_relName : string 28 | method set_relName : string -> unit 29 | method unset_relName : unit 30 | method reset_relName : unit 31 | method get_arguments : string list option 32 | method grab_arguments : string list 33 | method set_arguments : string list -> unit 34 | method unset_arguments : unit 35 | method reset_arguments : unit 36 | method copy : 'a 37 | method write : Protocol.t -> unit 38 | end 39 | val read_query : Protocol.t -> query 40 | class queryReply : 41 | object ('a) 42 | method get_result : string list list option 43 | method grab_result : string list list 44 | method set_result : string list list -> unit 45 | method unset_result : unit 46 | method reset_result : unit 47 | method get_exception_code : string option 48 | method grab_exception_code : string 49 | method set_exception_code : string -> unit 50 | method unset_exception_code : unit 51 | method reset_exception_code : unit 52 | method get_exception_message : string option 53 | method grab_exception_message : string 54 | method set_exception_message : string -> unit 55 | method unset_exception_message : unit 56 | method reset_exception_message : unit 57 | method copy : 'a 58 | method write : Protocol.t -> unit 59 | end 60 | val read_queryReply : Protocol.t -> queryReply 61 | type fLValue = string 62 | 63 | -------------------------------------------------------------------------------- /interpreter/thrift/gen-ocaml/flowlog_thrift.mldylib: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: d98b5f08dbd3142748dd1b7aeb9584df) 3 | Flowlog_rpc_consts 4 | BlackBox 5 | FlowLogInterpreter 6 | # OASIS_STOP 7 | -------------------------------------------------------------------------------- /interpreter/thrift/gen-ocaml/flowlog_thrift.mllib: -------------------------------------------------------------------------------- 1 | # OASIS_START 2 | # DO NOT EDIT (digest: d98b5f08dbd3142748dd1b7aeb9584df) 3 | Flowlog_rpc_consts 4 | BlackBox 5 | FlowLogInterpreter 6 | # OASIS_STOP 7 | -------------------------------------------------------------------------------- /interpreter/thrift/notify.ml: -------------------------------------------------------------------------------- 1 | (* 2 | This Blackbox sends a notification (specified in cmd line args) and then exits. 3 | *) 4 | 5 | (*#load "str.cma"*) 6 | 7 | open Arg 8 | open Thrift 9 | open Flowlog_rpc_types 10 | open Str 11 | 12 | type connection = { 13 | trans : Transport.t ; 14 | proto : Thrift.Protocol.t; 15 | fl : FlowLogInterpreter.client ; 16 | } 17 | 18 | (* The ~ denotes a keyword argument *) 19 | let connect ~host port = 20 | let tx = new TSocket.t host port in 21 | let proto = new TBinaryProtocol.t tx in 22 | let fl = new FlowLogInterpreter.client proto proto in 23 | tx#opn; 24 | { trans = tx ; proto = proto; fl = fl} 25 | ;; 26 | 27 | if (Array.length Sys.argv) < 2 then failwith "Did not give a notification type." 28 | let notifname = Sys.argv.(1);; 29 | let numargs = (Array.length Sys.argv) - 2;; 30 | Printf.printf "Notif type: %s\nNumber args: %d\n%!" notifname numargs;; 31 | 32 | let tbl = (Hashtbl.create numargs);; 33 | 34 | (* silly naive newbie parsing. replace with better soon. so bad! *) 35 | Array.iter (fun str -> match (split (regexp "=") str) with 36 | | [k;v] -> Printf.printf "Arg: %s -> %s\n%!" k v; 37 | Hashtbl.add tbl k v; 38 | | _ -> failwith "bad field (use fieldname=value)") 39 | (Array.sub Sys.argv 2 ((Array.length Sys.argv) - 2));; 40 | 41 | 42 | let dobb () = 43 | let cli = connect ~host:"127.0.0.1" 9090 in 44 | try 45 | Printf.printf "sending notification...\n%!"; 46 | let notif = new notification in 47 | notif#set_notificationType notifname; 48 | notif#set_values tbl; 49 | cli.fl#notifyMe notif; 50 | cli.trans#close; 51 | with Transport.E (_,what) -> 52 | Printf.printf "ERROR: %s\n" what ; flush stdout 53 | ;; 54 | 55 | dobb();; 56 | 57 | -------------------------------------------------------------------------------- /interpreter/thrift/placeholder.ml: -------------------------------------------------------------------------------- 1 | (* 2 | Service listens for Flowlog events and prints them out in stdout. 3 | *) 4 | 5 | open Arg 6 | open Thrift 7 | open Flowlog_rpc_types 8 | open Thread 9 | open Printf 10 | open Unix 11 | 12 | let listen_port = 20000;; 13 | 14 | (* "Die, Bart, Die!" is German for "The, Bart, The!". -- Sideshow Bob *) 15 | exception Die;; 16 | let sod = function 17 | Some v -> v 18 | | None -> raise Die;; 19 | 20 | type connection = { 21 | trans : Transport.t ; 22 | proto : Thrift.Protocol.t; 23 | fl : FlowLogInterpreter.client ; 24 | } 25 | 26 | (* The ~ denotes a keyword argument *) 27 | let connect ~host port = 28 | let tx = new TSocket.t host port in 29 | let proto = new TBinaryProtocol.t tx in 30 | let fl = new FlowLogInterpreter.client proto proto in 31 | tx#opn; 32 | { trans = tx ; proto = proto; fl = fl};; 33 | 34 | let send_notif notif = 35 | let cli = connect ~host:"127.0.0.1" 9090 in 36 | try 37 | cli.fl#notifyMe notif; 38 | cli.trans#close; 39 | with Transport.E (_,what) -> 40 | Printf.printf "ERROR: %s\n" what ; flush Pervasives.stdout 41 | 42 | class bb_handler = 43 | object (self) 44 | inherit BlackBox.iface 45 | 46 | method notifyMe notif = 47 | let ntype = sod ((sod notif)#get_notificationType) in 48 | let values = sod ((sod notif)#get_values) in 49 | Printf.printf "received notification. type=%s\n%!" ntype; 50 | Hashtbl.iter (fun k v -> printf "%s => %s\n%!" k v) values 51 | 52 | (********************************************************************************) 53 | method doQuery qry = 54 | let relname = (sod (sod qry)#get_relName) in 55 | let rep = new queryReply in 56 | Printf.printf "invalid query relation %s\n%!" relname; 57 | rep#set_exception_code "2"; 58 | rep#set_exception_message "No query support in placeholder listener."; 59 | rep#set_result []; 60 | rep 61 | end 62 | 63 | let dobb () = 64 | let h = new bb_handler in 65 | let proc = new BlackBox.processor h in 66 | let port = listen_port in 67 | let pf = new TBinaryProtocol.factory in 68 | let server = new TThreadedServer.t 69 | proc 70 | (new TServerSocket.t port) 71 | (new Transport.factory) 72 | pf 73 | pf 74 | in 75 | (* Listen in a separate thread. *) 76 | (* returns handle to new thread. ignore to avoid warning *) 77 | Printf.printf "Starting listener (in main thread; this should block)...\n%!"; 78 | server#serve; 79 | ;; 80 | 81 | dobb();; 82 | 83 | -------------------------------------------------------------------------------- /interpreter/thrift/police_tipline.ml: -------------------------------------------------------------------------------- 1 | (* 2 | Police tipline: 3 | Listen for reports that stolen laptops have been seen on the network 4 | *) 5 | 6 | (* TODO: Michael points out that we ought to have a Black-box functor, to aid in abstraction and avoid code-reuse. 7 | Much of this is boilerplate... *) 8 | 9 | open Arg 10 | open Thrift 11 | open Flowlog_rpc_types 12 | open Thread 13 | open Printf 14 | 15 | let police_port = 5050;; (* the five oh *) 16 | 17 | (* "Die, Bart, Die!" is German for "The, Bart, The!". -- Sideshow Bob *) 18 | exception Die;; 19 | let sod = function 20 | Some v -> v 21 | | None -> raise Die;; 22 | 23 | type connection = { 24 | trans : Transport.t ; 25 | proto : Thrift.Protocol.t; 26 | fl : FlowLogInterpreter.client ; 27 | } 28 | 29 | (* The ~ denotes a keyword argument *) 30 | let connect ~host port = 31 | let tx = new TSocket.t host port in 32 | let proto = new TBinaryProtocol.t tx in 33 | let fl = new FlowLogInterpreter.client proto proto in 34 | tx#opn; 35 | { trans = tx ; proto = proto; fl = fl} 36 | ;; 37 | 38 | let send_notif notif = 39 | let cli = connect ~host:"127.0.0.1" 9090 in 40 | try 41 | cli.fl#notifyMe notif; 42 | cli.trans#close; 43 | with Transport.E (_,what) -> 44 | printf "ERROR: %s\n" what ; flush stdout 45 | 46 | 47 | class bb_handler = 48 | object (self) 49 | inherit BlackBox.iface 50 | 51 | method notifyMe notif = 52 | let ntype = sod ((sod notif)#get_notificationType) in 53 | let values = sod ((sod notif)#get_values) in 54 | printf "received notification. type=%s\n%!" ntype; 55 | (* <>, not != *) 56 | if ntype = "stolen_laptop_found" then 57 | begin 58 | (* case-sensitive. But Flowlog's parser downcases everything. So fields are always lowercase.*) 59 | try 60 | let mac = (Hashtbl.find values "macaddr") in 61 | let swid = (Hashtbl.find values "swid") in 62 | let time = (Hashtbl.find values "time") in 63 | printf "Our anonymous tipster, who sounds like a Flowlog Controller, saw %s on switch %s at time=%s.\n%!" mac swid time 64 | (* Don't leave this check out: without it, this app could freeze. *) 65 | with Not_found -> 66 | printf "...but did not contain well-formed fields.\n%!"; 67 | end 68 | else if ntype = "host_report" then 69 | begin 70 | try 71 | let mac = (Hashtbl.find values "mac") in 72 | let swid = (Hashtbl.find values "sw") in 73 | let ptid = (Hashtbl.find values "pt") in 74 | printf "[host_report] saw %s on switch %s port %s.\n%!" mac swid ptid 75 | with Not_found -> 76 | printf "...but did not contain well-formed fields.\n%!"; 77 | end 78 | else 79 | begin 80 | let reply = new notification in 81 | let tbl = (Hashtbl.create 2) in 82 | (* TODO: abstract this out *) 83 | reply#set_notificationType "exception"; 84 | Hashtbl.add tbl "sender" "Police_Tipline"; 85 | Hashtbl.add tbl "message" "The tipline can't accept anything but stolen_laptop_found."; 86 | reply#set_values tbl; 87 | send_notif reply; 88 | Printf.printf "Sent exception.\n%!"; 89 | end 90 | 91 | 92 | method doQuery qry = 93 | let relname = (sod (sod qry)#get_relName) in 94 | (*let args = (sod (sod qry)#get_arguments) in*) 95 | let rep = new queryReply in 96 | printf "The _man_ answers to no one's queries! Was: %s\n%!" relname; 97 | rep#set_exception_code "2"; 98 | rep#set_exception_message "The _man_ answers to no one's queries!"; 99 | rep#set_result []; 100 | rep 101 | 102 | end 103 | 104 | let dobb () = 105 | let h = new bb_handler in 106 | let proc = new BlackBox.processor h in 107 | let port = police_port in 108 | let pf = new TBinaryProtocol.factory in 109 | let server = new TThreadedServer.t 110 | proc 111 | (new TServerSocket.t port) 112 | (new Transport.factory) 113 | pf 114 | pf 115 | in 116 | (* Listen in a separate thread. *) 117 | (* returns handle to new thread. ignore to avoid warning *) 118 | printf "Starting listener for STOLEN LAPTOP TIPS! (in main thread; this should block)...\n%!"; 119 | server#serve; 120 | ;; 121 | 122 | dobb();; 123 | 124 | 125 | -------------------------------------------------------------------------------- /interpreter/thrift/run.sh: -------------------------------------------------------------------------------- 1 | # Run thrift to generate ocaml code for our black-box interface 2 | 3 | thrift -r --gen ocaml flowlog.thrift 4 | -------------------------------------------------------------------------------- /interpreter/thrift/setup.data: -------------------------------------------------------------------------------- 1 | ocamlfind="/home/flowlog/.opam/system/bin/ocamlfind" 2 | ocamlc="/usr/bin/ocamlc.opt" 3 | ocamlopt="/usr/bin/ocamlopt.opt" 4 | ocamlbuild="/usr/bin/ocamlbuild" 5 | pkg_name="flowlog_thrift" 6 | pkg_version="1.0" 7 | os_type="Unix" 8 | system="linux" 9 | architecture="amd64" 10 | ccomp_type="cc" 11 | ocaml_version="4.01.0" 12 | standard_library_default="/usr/lib/ocaml" 13 | standard_library="/usr/lib/ocaml" 14 | standard_runtime="/usr/bin/ocamlrun" 15 | bytecomp_c_compiler="gcc -D_FILE_OFFSET_BITS=64 -D_REENTRANT -fPIC" 16 | native_c_compiler="gcc -D_FILE_OFFSET_BITS=64 -D_REENTRANT" 17 | model="default" 18 | ext_obj=".o" 19 | ext_asm=".s" 20 | ext_lib=".a" 21 | ext_dll=".so" 22 | default_executable_name="a.out" 23 | systhread_supported="true" 24 | prefix="/usr/local" 25 | exec_prefix="$prefix" 26 | bindir="$exec_prefix/bin" 27 | sbindir="$exec_prefix/sbin" 28 | libexecdir="$exec_prefix/libexec" 29 | sysconfdir="$prefix/etc" 30 | sharedstatedir="$prefix/com" 31 | localstatedir="$prefix/var" 32 | libdir="$exec_prefix/lib" 33 | datarootdir="$prefix/share" 34 | datadir="$datarootdir" 35 | infodir="$datarootdir/info" 36 | localedir="$datarootdir/locale" 37 | mandir="$datarootdir/man" 38 | docdir="$datarootdir/doc/$pkg_name" 39 | htmldir="$docdir" 40 | dvidir="$docdir" 41 | pdfdir="$docdir" 42 | psdir="$docdir" 43 | findlib_version="1.5.2" 44 | is_native="true" 45 | suffix_program="" 46 | rm="rm -f" 47 | rmdir="rm -rf" 48 | debug="true" 49 | profile="false" 50 | native_dynlink="true" 51 | ocamlbuildflags="" 52 | docs="true" 53 | tests="false" 54 | pkg_threads="/usr/lib/ocaml" 55 | pkg_thrift="/home/flowlog/.opam/system/lib/thrift" 56 | pkg_str="/usr/lib/ocaml" 57 | -------------------------------------------------------------------------------- /interpreter/yacc/Makefile: -------------------------------------------------------------------------------- 1 | # Create ML code for the lexer and parser, and put them in the directory below. 2 | # If we tried to have everything (.mll, .mly, .ml) in the same directory, we'd 3 | # get an error message when trying to compile via make. 4 | 5 | default: 6 | ocamllex -o ../Surface_Lexer.ml Surface_Lexer.mll 7 | ocamlyacc -b../Surface_Parser Surface_Parser.mly 8 | sed -i '1iopen Flowlog_Types;;' ../Surface_Parser.mli 9 | -------------------------------------------------------------------------------- /interpreter/yacc/Surface_Lexer.mll: -------------------------------------------------------------------------------- 1 | { 2 | open Surface_Parser (* The tokens are defined in this mli *) 3 | open Flowlog_Helpers 4 | (* NOTE: To get case-insensitivity, make sure to lowercase the stream before passing in. *) 5 | 6 | (* TODO: We've likely hit the point where the table is blowing up because we have a lot of keywords. 7 | Could be faster to use the hash-table lookup method for keywords? *) 8 | 9 | (* COMMENTS --- the pattern below for multiline /* */ assumes no strings containing 10 | /* etc. Standard "dumb" comment assumptions due to limited language syntax. *) 11 | 12 | let any_counter = ref 0;; 13 | 14 | } 15 | rule token = parse 16 | [' ' '\t' '\r'] { token lexbuf } 17 | | "\n" { Lexing.new_line lexbuf; token lexbuf } 18 | | "//" [^ '\n']* { token lexbuf } 19 | | "/*" {commenting lexbuf} 20 | | eof { EOF } 21 | 22 | | "include" { INCLUDE } 23 | | "do" { DO } 24 | | "into" { INTO } 25 | | "from" { FROM } 26 | | "insert" { INSERT } 27 | | "delete" { DELETE } 28 | | "table" { TABLE } 29 | | "remote" { REMOTE } 30 | | "at" { AT } 31 | | "to" { TO } 32 | | "send" { SEND } 33 | | "event" { EVENT } 34 | | "incoming" { INCOMING } 35 | | "outgoing" { OUTGOING } 36 | | "where" { WHERE } 37 | | "timeout" { TIMEOUT } 38 | | "never" { NEVER } 39 | | "then" { THEN } 40 | | "on" { ON } 41 | 42 | | "increment" { INCREMENT } 43 | | "var" { VAR } 44 | 45 | | "forward" { FORWARD } 46 | | "stash" { STASH } 47 | | "then" { THEN } 48 | | "until" { UNTIL } 49 | 50 | | "in" { IN } 51 | | "not" { NOT } 52 | | "or" { OR } 53 | | "xor" { XOR } 54 | | "and" { AND } 55 | | "implies" { IMPLIES } 56 | | "iff" { IFF } 57 | | "true" { TRUE } 58 | | "false" { FALSE } 59 | 60 | | '/' { SLASH } 61 | | '.' { PERIOD } 62 | | ":=" { COLON_EQUALS } 63 | | ':' { COLON } 64 | | ';' { SEMICOLON } 65 | | '=' { EQUALS } 66 | | "<>" { NOTEQUALS } 67 | | "!=" { NOTEQUALS } 68 | | '{' { LCURLY } 69 | | '}' { RCURLY } 70 | | ',' { COMMA } 71 | | '(' { LPAREN } 72 | | ')' { RPAREN } 73 | 74 | | "any" { any_counter := !any_counter+1; NAME("any"^(string_of_int !any_counter)) } 75 | 76 | | '"'['.''/''\\''a'-'z''A'-'Z''_''0'-'9''-'':']+'"' as id { QUOTED_IDENTIFIER(id) } 77 | 78 | (* Every numeric string not in standard decimal format gets converted here. *) 79 | | ['0'-'9']?['0'-'9']?['0'-'9']"."['0'-'9']?['0'-'9']?['0'-'9']"."['0'-'9']?['0'-'9']?['0'-'9']"."['0'-'9']?['0'-'9']?['0'-'9'] as dotted_ip 80 | { NUMBER(nwaddr_to_int_string (Packet.ip_of_string dotted_ip))} 81 | | ['0'-'9''a'-'f']?['0'-'9''a'-'f']":"['0'-'9''a'-'f']?['0'-'9''a'-'f']":"['0'-'9''a'-'f']?['0'-'9''a'-'f']":" 82 | ['0'-'9''a'-'f']?['0'-'9''a'-'f']":"['0'-'9''a'-'f']?['0'-'9''a'-'f']":"['0'-'9''a'-'f']?['0'-'9''a'-'f'] as mac 83 | { NUMBER(macaddr_to_int_string (Packet.mac_of_string mac))} 84 | | '0''x'['0'-'9''a'-'f']+ as hexnum 85 | { NUMBER(hex_str_to_int_string hexnum)} 86 | 87 | (* allow both positive and negative *) 88 | | '-'['0'-'9']+ as number 89 | | ['0'-'9']+ as number 90 | { NUMBER(number) } 91 | 92 | | ['a'-'z''A'-'Z''_''0'-'9']+ as name { NAME(name) } 93 | 94 | | ['a'-'z''A'-'Z''_''0'-'9''-']+ as name 95 | { Printf.printf "Bad identifier: %s. Cannot use dashes. Use underscore instead.\n%!" name; 96 | token lexbuf; } 97 | 98 | | _ as c { Printf.printf "Unknown character: %c\n" c; token lexbuf;} 99 | and commenting = parse 100 | | "*/" { token lexbuf } 101 | | "\n" { Lexing.new_line lexbuf; commenting lexbuf } 102 | | eof { EOF } 103 | | _ { commenting lexbuf } 104 | -------------------------------------------------------------------------------- /mininet/3-cycle.py: -------------------------------------------------------------------------------- 1 | from mininet.topo import Topo 2 | 3 | class MyTopo( Topo ): 4 | "3-switch cycle topology to test spanning tree." 5 | 6 | def __init__( self ): 7 | "Create custom topo..." 8 | 9 | # Initialize topology 10 | Topo.__init__( self ) 11 | 12 | # Add hosts and switches 13 | h1 = self.addHost( 'h1' ) 14 | h2 = self.addHost( 'h2' ) 15 | h3 = self.addHost( 'h3' ) 16 | s1 = self.addSwitch( 's1' ) 17 | s2 = self.addSwitch( 's2' ) 18 | s3 = self.addSwitch( 's3' ) 19 | 20 | # Add links 21 | self.addLink( h1, s1 ) 22 | self.addLink( h2, s2 ) 23 | self.addLink( h3, s3 ) 24 | self.addLink( s1, s2 ) 25 | self.addLink( s2, s3 ) 26 | self.addLink( s3, s1 ) 27 | 28 | 29 | topos = { '3cycle': ( lambda: MyTopo() ) } -------------------------------------------------------------------------------- /mininet/NatMN.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Based on PaneDemo.py from PANE VM available at http://pane.cs.brown.edu 4 | 5 | import math 6 | 7 | from optparse import OptionParser 8 | 9 | from mininet.net import Mininet 10 | from mininet.cli import CLI 11 | from mininet.log import lg 12 | from mininet.node import Node, UserSwitch, RemoteController, CPULimitedHost 13 | from mininet.link import TCLink 14 | from mininet.util import irange, customConstructor 15 | 16 | from FlowlogMNUtil import FlowlogTopo, OVSHtbQosSwitch, addDictOption 17 | 18 | 19 | CONTROLLERDEF = 'remote' 20 | CONTROLLERS = { 'remote': RemoteController } 21 | 22 | SWITCHDEF = 'ovshtb' 23 | SWITCHES = { 'ovshtb': OVSHtbQosSwitch, 24 | 'user': UserSwitch } 25 | 26 | 27 | class FlowlogDemo(object): 28 | "Run a Flowlog demo." 29 | 30 | def __init__(self): 31 | self.options = None 32 | self.args = None 33 | 34 | self.parseArgs() 35 | lg.setLogLevel('info') 36 | self.runDemo() 37 | 38 | def parseArgs(self): 39 | opts = OptionParser() 40 | addDictOption(opts, SWITCHES, SWITCHDEF, 'switch') 41 | addDictOption(opts, CONTROLLERS, CONTROLLERDEF, 'controller') 42 | self.options, self.args = opts.parse_args() 43 | 44 | def buildTopo(self): 45 | num_subnets = 2 46 | num_hosts_per_subnet = 2 47 | # 15 Mbps bandwidth and 2 ms delay on each link 48 | linkopts = dict(bw=15, delay='2ms', loss=0, use_htb=True) 49 | 50 | topo = FlowlogTopo() 51 | nat = topo.addSwitch('nat', dpid="3000000000000000") # dpid is in hex by default 52 | 53 | # Add some regular hosts 54 | for s in irange(1, num_subnets): 55 | for h in irange(1, num_hosts_per_subnet): 56 | global_host = (s - 1) * num_hosts_per_subnet + h 57 | host = topo.addHost('host%s' % global_host, ip='10.0.%s.%s/24' % (s, 1 + h), 58 | defaultRoute='dev host%s-eth0 via 10.0.%s.1' % (global_host, s), 59 | cpu=0.4/(num_subnets * num_hosts_per_subnet)) 60 | topo.addLink(host, nat, **linkopts) 61 | 62 | return topo 63 | 64 | def launchNetwork(self, network, host_cmd, host_cmd_opts): 65 | network.start() 66 | 67 | # Increase ARP timeout from 60 seconds to 1 hour 68 | for host in network.hosts: 69 | host.cmd("sysctl -w net.ipv4.neigh." + str(host.defaultIntf()) + 70 | ".gc_stale_time=3600") 71 | 72 | print 73 | print "*** Hosts are running sshd at the following addresses:" 74 | print 75 | for host in network.hosts: 76 | host.cmd(host_cmd + ' ' + host_cmd_opts + '&') 77 | print host.name, host.IP() 78 | 79 | def teardownNetwork(self, network, host_cmd): 80 | print 81 | print "*** Shutting-down SSH daemons" 82 | print 83 | for host in network.hosts: 84 | host.cmd( 'kill %' + host_cmd ) 85 | 86 | network.stop() 87 | 88 | def runDemo(self, host_cmd='/usr/sbin/sshd', host_cmd_opts='-D'): 89 | topo = self.buildTopo() 90 | controller = customConstructor(CONTROLLERS, self.options.controller) 91 | switch = customConstructor(SWITCHES, self.options.switch) 92 | 93 | network = Mininet(topo, controller=controller, link=TCLink, 94 | # host=CPULimitedHost, # seems better without this 95 | switch=switch, ipBase='10.0.0.0/24', 96 | autoSetMacs=True) 97 | 98 | self.launchNetwork(network, host_cmd, host_cmd_opts) 99 | self.demo(network) 100 | self.teardownNetwork(network, host_cmd) 101 | 102 | def demo(self, network): 103 | # Generally, this will be overriden for more interesting demos 104 | print 105 | print "*** Type 'exit' or control-D to shut down network" 106 | CLI( network ) 107 | 108 | 109 | if __name__ == '__main__': 110 | FlowlogDemo() 111 | -------------------------------------------------------------------------------- /mininet/read.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import routers_pb2 4 | import sys 5 | 6 | routers = routers_pb2.Routers() 7 | 8 | f = open(sys.argv[1], "rb") 9 | routers.ParseFromString(f.read()) 10 | f.close() 11 | 12 | print routers 13 | -------------------------------------------------------------------------------- /mininet/routers.proto: -------------------------------------------------------------------------------- 1 | // Protocol Buffer describing the routers (and their associated subnets 2 | // and peers) for our Cisco IOS conversion. 3 | // 4 | // Author: Andrew Ferguson (adf@cs.brown.edu) 5 | // Modified by Tim Nelson (tn@cs.brown.edu) 6 | 7 | package flowlog; 8 | 9 | message Subnet { 10 | optional string addr = 1; // required 11 | optional int32 mask = 2; // required 12 | optional string gw = 3; // required 13 | optional string ifname = 6; 14 | repeated int32 physical_portid = 7; 15 | } 16 | 17 | message Port { 18 | // physical mininet port number and IOS interface name 19 | optional int32 id = 1; 20 | optional string name = 2; 21 | 22 | // "access" or "trunk" or not present (i.e., L3-visible physical port) 23 | optional string vlan_type = 3; 24 | 25 | } 26 | 27 | message Connection { 28 | optional string router1 = 1; 29 | optional string router2 = 2; 30 | optional string iface1 = 3; 31 | optional string iface2 = 4; 32 | } 33 | 34 | message Network { 35 | optional string addr = 1; // required 36 | optional int32 mask = 2; // required 37 | } 38 | 39 | message Peer { 40 | optional string ip = 1; // required 41 | optional int32 mask = 2; // required 42 | optional string mac = 3; // required 43 | 44 | repeated Network networks = 4; 45 | } 46 | 47 | message Router { 48 | optional string name = 1; // required 49 | optional string self_dpid = 2; // required 50 | optional string nat_dpid = 3; // required 51 | optional string tr_dpid = 6; // required 52 | optional string acl_dpid = 7; // required 53 | optional string vlan_dpid = 8; 54 | optional int32 num_physical = 10; 55 | 56 | repeated Subnet subnets = 4; 57 | repeated Peer peers = 5; 58 | repeated Port ports = 9; 59 | } 60 | 61 | message Routers { 62 | repeated Router routers = 1; 63 | 64 | optional string subnet_base_dpid = 2; // required 65 | 66 | repeated Connection connections = 3; 67 | } 68 | -------------------------------------------------------------------------------- /mininet/sample-routers.pb.txt: -------------------------------------------------------------------------------- 1 | subnet_base_dpid: "3000000000000000" 2 | routers { 3 | name: "r1" 4 | self_dpid: "1000000000000001" 5 | nat_dpid: "4000000000000001" 6 | tr_dpid: "2000000000000001" 7 | acl_dpid: "5000000000000001" 8 | 9 | subnets { 10 | addr: "10.0.1.0" 11 | mask: 24 12 | gw: "10.0.1.1" 13 | } 14 | 15 | subnets { 16 | addr: "10.0.2.0" 17 | mask: 24 18 | gw: "10.0.2.1" 19 | } 20 | 21 | peers { 22 | ip: "192.168.1.1" 23 | mask: 24 24 | mac: "be:ef:be:ef:00:01" 25 | 26 | networks { 27 | addr: "8.0.0.0" 28 | mask: 8 29 | } 30 | 31 | networks { 32 | addr: "4.4.0.0" 33 | mask: 16 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mininet/start3.sh: -------------------------------------------------------------------------------- 1 | sudo mn --controller=remote --custom 3-cycle.py --topo 3cycle --mac --arp 2 | -------------------------------------------------------------------------------- /py/rseen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # To run controller listening on port 9999 4 | #PYTHONPATH=. ../../pox/pox.py log.level --DEBUG openflow.of_01 --port=9999 seen 5 | 6 | PYTHONPATH=. ../../pox/pox.py log.level --DEBUG seen 7 | -------------------------------------------------------------------------------- /py/seen.py: -------------------------------------------------------------------------------- 1 | # Modified from Pox's learning switch example by Tim 2 | 3 | from pox.core import core 4 | import pox.openflow.libopenflow_01 as of 5 | 6 | log = core.getLogger() 7 | 8 | class LoggerSwitch (object): 9 | 10 | def __init__ (self, connection): 11 | self.connection = connection 12 | self.seenTable = set() 13 | print "...", connection 14 | connection.addListeners(self) 15 | 16 | def _handle_PacketIn (self, event): 17 | packet = event.parsed 18 | 19 | def do_flood (): 20 | msg = of.ofp_packet_out() 21 | msg.actions.append(of.ofp_action_output(port = of.OFPP_FLOOD)) 22 | msg.data = event.ofp 23 | msg.buffer_id = None 24 | msg.in_port = event.port 25 | self.connection.send(msg) 26 | 27 | def install_nomore (): 28 | msg = of.ofp_flow_mod() 29 | msg.match = of.ofp_match(dl_src = packet.src) 30 | msg.buffer_id = event.ofp.buffer_id 31 | msg.actions.append(of.ofp_action_output(port = of.OFPP_FLOOD)) 32 | self.connection.send(msg) 33 | 34 | install_nomore() 35 | do_flood() 36 | 37 | log.info("switch "+str(self.connection.dpid)+" received " + str(packet)) 38 | self.seenTable.add(packet.src) 39 | log.info("new seenTable for switch "+str(self.connection.dpid)+" is: " + str(self.seenTable)) 40 | 41 | 42 | class seen(object): 43 | def __init__ (self): 44 | core.openflow.addListeners(self) 45 | 46 | def _handle_ConnectionUp (self, event): 47 | LoggerSwitch(event.connection) 48 | 49 | def launch (): 50 | core.registerNew(seen) 51 | --------------------------------------------------------------------------------