├── src ├── cmd │ ├── show_tables.txt │ ├── table_dump.txt │ ├── table_info.txt │ ├── table_delete.txt │ └── table_add.txt ├── handle │ ├── readme.md │ └── s1_smac.txt ├── show_sw_tables.py ├── simple_switch_CLI ├── show_table_info.py ├── show_table_entry.py ├── table_delete_entry.py └── table_add_entry.py ├── p4web ├── P4_tools │ ├── host.png │ ├── switch.png │ ├── switch2.png │ ├── fonts │ │ └── glyphicons-halflings-regular.woff │ ├── .idea │ │ ├── vcs.xml │ │ ├── modules.xml │ │ ├── misc.xml │ │ ├── P4_tools.iml │ │ └── workspace.xml │ ├── js │ │ ├── xx.html │ │ ├── intputGroup.js │ │ ├── bootstrap.min.js │ │ └── jtopo-0.4.6-min.js │ ├── css │ │ ├── styles.css │ │ └── bootstrap-table.css │ ├── test.html │ └── index.html └── p4web.py ├── init ├── topo.txt ├── simple_switch_CLI └── run_demo.sh ├── cleanup.sh ├── env.sh ├── script ├── veth_setup.sh └── topo.py ├── p4src ├── includes │ ├── headers.p4 │ └── parser.p4 └── switch.p4 ├── README.md └── LICENSE /src/cmd/show_tables.txt: -------------------------------------------------------------------------------- 1 | show_tables 2 | -------------------------------------------------------------------------------- /src/cmd/table_dump.txt: -------------------------------------------------------------------------------- 1 | table_dump smac 2 | -------------------------------------------------------------------------------- /src/cmd/table_info.txt: -------------------------------------------------------------------------------- 1 | table_info smac 2 | -------------------------------------------------------------------------------- /src/cmd/table_delete.txt: -------------------------------------------------------------------------------- 1 | table_delete dmac 0 2 | -------------------------------------------------------------------------------- /src/handle/readme.md: -------------------------------------------------------------------------------- 1 | handle of table entries 2 | -------------------------------------------------------------------------------- /src/handle/s1_smac.txt: -------------------------------------------------------------------------------- 1 | 0 2 | 16777216 3 | 0 4 | -------------------------------------------------------------------------------- /src/cmd/table_add.txt: -------------------------------------------------------------------------------- 1 | table_add smac mac_learn 656 => 2 | -------------------------------------------------------------------------------- /p4web/P4_tools/host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FZU-SDN/NSP4/HEAD/p4web/P4_tools/host.png -------------------------------------------------------------------------------- /p4web/P4_tools/switch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FZU-SDN/NSP4/HEAD/p4web/P4_tools/switch.png -------------------------------------------------------------------------------- /p4web/P4_tools/switch2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FZU-SDN/NSP4/HEAD/p4web/P4_tools/switch2.png -------------------------------------------------------------------------------- /init/topo.txt: -------------------------------------------------------------------------------- 1 | switches 4 2 | hosts 6 3 | h1 s1 4 | h2 s1 5 | h3 s1 6 | h4 s4 7 | h5 s4 8 | h6 s4 9 | s1 s2 10 | s2 s4 11 | s1 s3 12 | s3 s4 13 | -------------------------------------------------------------------------------- /p4web/P4_tools/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FZU-SDN/NSP4/HEAD/p4web/P4_tools/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /cleanup.sh: -------------------------------------------------------------------------------- 1 | # FuZhou University SDNLab 2 | # Added by Chen, 2017.4.30 3 | 4 | # WARNNING: Don't move this file! 5 | 6 | rm -rf init/*.pcap 7 | rm -rf init/*.json 8 | rm -rf script/*.pyc 9 | sudo mn -c 10 | -------------------------------------------------------------------------------- /p4web/P4_tools/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /env.sh: -------------------------------------------------------------------------------- 1 | THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 2 | 3 | # ---------------- EDIT THIS ------------------ 4 | BMV2_PATH=/home/wpq/bmv2 5 | # e.g. BMV2_PATH=$THIS_DIR/../bmv2 6 | P4C_BM_PATH=/home/wpq/p4c-bm 7 | # e.g P4C_BM_PATH=$THIS_DIR/../p4c-bm 8 | # ---------------- END ------------------ 9 | -------------------------------------------------------------------------------- /p4web/P4_tools/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /p4web/P4_tools/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /p4web/P4_tools/.idea/P4_tools.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/show_sw_tables.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | from time import sleep 5 | import os 6 | import subprocess 7 | 8 | _THIS_DIR = os.path.dirname(os.path.realpath(__file__)) 9 | _THRIFT_BASE_PORT = 22222 10 | 11 | parser = argparse.ArgumentParser(description='P4 demo') 12 | parser.add_argument('--swname', help='Switch Name', 13 | type=str, action="store", required=True) 14 | args = parser.parse_args() 15 | 16 | def main(): 17 | # Get Thrift Port 18 | sw_name = args.swname 19 | index = int(sw_name[1:])-1 20 | thrift_port = _THRIFT_BASE_PORT+index 21 | 22 | cmd = "python /home/wpq/NSP4/src/simple_switch_CLI --thrift-port %d < /home/wpq/NSP4/src/cmd/show_tables.txt" % thrift_port 23 | os.system(cmd) 24 | 25 | if __name__ == '__main__': 26 | main() 27 | -------------------------------------------------------------------------------- /script/veth_setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | noOfVeths=18 3 | if [ $# -eq 1 ]; then 4 | noOfVeths=$1 5 | fi 6 | echo "No of Veths is $noOfVeths" 7 | idx=0 8 | let "vethpairs=$noOfVeths/2" 9 | while [ $idx -lt $vethpairs ] 10 | do 11 | intf0="veth$(($idx*2))" 12 | intf1="veth$(($idx*2+1))" 13 | idx=$((idx + 1)) 14 | if ! ip link show $intf0 &> /dev/null; then 15 | ip link add name $intf0 type veth peer name $intf1 16 | ip link set dev $intf0 up 17 | ip link set dev $intf1 up 18 | TOE_OPTIONS="rx tx sg tso ufo gso gro lro rxvlan txvlan rxhash" 19 | for TOE_OPTION in $TOE_OPTIONS; do 20 | /sbin/ethtool --offload $intf0 "$TOE_OPTION" off 21 | /sbin/ethtool --offload $intf1 "$TOE_OPTION" off 22 | done 23 | fi 24 | sysctl net.ipv6.conf.$intf0.disable_ipv6=1 25 | sysctl net.ipv6.conf.$intf1.disable_ipv6=1 26 | done 27 | -------------------------------------------------------------------------------- /init/simple_switch_CLI: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # Copyright 2013-present Barefoot Networks, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # 19 | # Antonin Bas (antonin@barefootnetworks.com) 20 | # 21 | # 22 | 23 | # This is just a wrapper script around sswitch_CLI.py 24 | # It makes sure that the script works correctly no matter where Python 25 | # dependencies are installed 26 | 27 | import sys 28 | sys.path.append("/usr/local/lib/python2.7/dist-packages") 29 | 30 | import sswitch_CLI 31 | sswitch_CLI.main() 32 | -------------------------------------------------------------------------------- /src/simple_switch_CLI: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # Copyright 2013-present Barefoot Networks, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # 19 | # Antonin Bas (antonin@barefootnetworks.com) 20 | # 21 | # 22 | 23 | # This is just a wrapper script around sswitch_CLI.py 24 | # It makes sure that the script works correctly no matter where Python 25 | # dependencies are installed 26 | 27 | import sys 28 | sys.path.append("/usr/local/lib/python2.7/dist-packages") 29 | 30 | import sswitch_CLI 31 | sswitch_CLI.main() 32 | -------------------------------------------------------------------------------- /src/show_table_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | from time import sleep 5 | import os 6 | import subprocess 7 | 8 | _THIS_DIR = os.path.dirname(os.path.realpath(__file__)) 9 | _THRIFT_BASE_PORT = 22222 10 | 11 | parser = argparse.ArgumentParser(description='P4 demo') 12 | parser.add_argument('--swname', help='Switch Name', 13 | type=str, action="store", required=True) 14 | parser.add_argument('--table-name', help='Table Name', 15 | type=str, action="store", required=True) 16 | args = parser.parse_args() 17 | 18 | def main(): 19 | # Get Thrift Port 20 | sw_name = args.swname 21 | index = int(sw_name[1:])-1 22 | thrift_port = _THRIFT_BASE_PORT+index 23 | 24 | # Get Table Name 25 | table_name = args.table_name 26 | 27 | table_info_cmd = "echo 'table_info %s' > /home/wpq/NSP4/src/cmd/table_info.txt" % table_name 28 | os.system(table_info_cmd) 29 | cmd = "python /home/wpq/NSP4/src/simple_switch_CLI --thrift-port %d < /home/wpq/NSP4/src/cmd/table_info.txt" % thrift_port 30 | os.system(cmd) 31 | os.system("rm -rf cmd/table_info.txt") 32 | 33 | if __name__ == '__main__': 34 | main() 35 | -------------------------------------------------------------------------------- /src/show_table_entry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | from time import sleep 5 | import os 6 | import subprocess 7 | 8 | _THIS_DIR = os.path.dirname(os.path.realpath(__file__)) 9 | _THRIFT_BASE_PORT = 22222 10 | 11 | parser = argparse.ArgumentParser(description='P4 demo') 12 | parser.add_argument('--swname', help='Switch Name', 13 | type=str, action="store", required=True) 14 | parser.add_argument('--table-name', help='Table Name', 15 | type=str, action="store", required=True) 16 | args = parser.parse_args() 17 | 18 | def main(): 19 | # Get Thrift Port 20 | sw_name = args.swname 21 | index = int(sw_name[1:])-1 22 | thrift_port = _THRIFT_BASE_PORT+index 23 | 24 | # Get Table Name 25 | table_name = args.table_name 26 | 27 | table_info_cmd = "echo 'table_dump %s' > /home/wpq/NSP4/src/cmd/table_dump.txt" % table_name 28 | os.system(table_info_cmd) 29 | cmd = "python /home/wpq/NSP4/src/simple_switch_CLI --thrift-port %d < /home/wpq/NSP4/src/cmd/table_dump.txt" % thrift_port 30 | os.system(cmd) 31 | os.system("rm -rf cmd/table_info.txt") 32 | 33 | if __name__ == '__main__': 34 | main() 35 | -------------------------------------------------------------------------------- /init/run_demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2013-present Barefoot Networks, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | THIS_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) 18 | 19 | source $THIS_DIR/../env.sh 20 | 21 | P4C_BM_SCRIPT=$P4C_BM_PATH/p4c_bm/__main__.py 22 | 23 | SWITCH_PATH=$BMV2_PATH/targets/simple_switch/simple_switch 24 | 25 | CLI_PATH=$THIS_DIR/../script/simple_switch_CLI.py 26 | 27 | $P4C_BM_SCRIPT ../p4src/switch.p4 --json switch.json 28 | sudo PYTHONPATH=$PYTHONPATH:$BMV2_PATH/mininet/ python ../script/topo.py \ 29 | --behavioral-exe $SWITCH_PATH \ 30 | --json switch.json \ 31 | --cli $CLI_PATH \ 32 | --mode l2 #\ 33 | # --controller-ip 127.0.0.1 \ 34 | # --controller-port 6653 35 | -------------------------------------------------------------------------------- /p4web/P4_tools/js/xx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | 9 | 10 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/table_delete_entry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import argparse 5 | from time import sleep 6 | import os 7 | import subprocess 8 | 9 | _THIS_DIR = os.path.dirname(os.path.realpath(__file__)) 10 | _THRIFT_BASE_PORT = 22222 11 | 12 | parser = argparse.ArgumentParser(description='P4 demo') 13 | parser.add_argument('--swname', help='Switch Name', 14 | type=str, action="store", required=True) 15 | parser.add_argument('--table-name', help='Table Name', 16 | type=str, action="store", required=True) 17 | parser.add_argument('--handle', help='Handle', 18 | type=str, action="store", required=True) 19 | #parser.add_argument('--ops-num', help='Operation Number', 20 | # type=int, action="store", required=True) 21 | args = parser.parse_args() 22 | 23 | def main(): 24 | # Get Thrift Port 25 | sw_name = args.swname 26 | index = int(sw_name[1:])-1 27 | thrift_port = _THRIFT_BASE_PORT+index 28 | 29 | # Get Table Name 30 | table_name = args.table_name 31 | 32 | # Get Handle 33 | table_handle = args.handle 34 | 35 | runtime_cmd = "table_delete %s %s" % (table_name, table_handle) 36 | os.system('echo %s > /home/wpq/NSP4/src/cmd/table_delete.txt' % runtime_cmd) 37 | os.system("python /home/wpq/NSP4/src/simple_switch_CLI --thrift-port %d < /home/wpq/NSP4/src/cmd/table_delete.txt" % thrift_port) 38 | os.system('rm -rf /home/wpq/NSP4/src/cmd/table_delete.txt') 39 | 40 | """ 41 | # Get Operation Number 42 | number = args.ops_num 43 | 44 | handle_file = 'handle/%s_%s.txt' % (sw_name, table_name) 45 | # remove the entry 46 | i, text = 1, open(handle_file, "r") 47 | for line in text.readlines(): 48 | if i != number : 49 | i = i+1 50 | else : 51 | table_handle = int(line) 52 | os.system("echo 'table_delete %s %s' > cmd/table_delete.txt" % (table_name, table_handle)) 53 | os.system("./simple_switch_CLI --thrift-port %d < cmd/table_delete.txt" % (thrift_port)) 54 | #os.system("rm -rf cmd/table_delete.txt") 55 | break 56 | """ 57 | 58 | if __name__ == '__main__': 59 | main() 60 | -------------------------------------------------------------------------------- /p4src/includes/headers.p4: -------------------------------------------------------------------------------- 1 | /********************************* 2 | FuZhou University, SDNLab 3 | Added by Chen, 2017.3.29 4 | *********************************/ 5 | 6 | /* Copyright 2017 FuZhou University SDNLab, Edu. 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. */ 16 | 17 | /********************************* 18 | template: header.p4 19 | *********************************/ 20 | 21 | header_type ethernet_t { 22 | fields { 23 | dstAddr : 48; 24 | srcAddr : 48; 25 | etherType : 16; 26 | } 27 | } 28 | 29 | header_type intrinsic_metadata_t { 30 | fields { 31 | mcast_grp : 4; 32 | egress_rid : 4; 33 | mcast_hash : 16; 34 | lf_field_list: 32; 35 | } 36 | } 37 | 38 | header_type ipv4_t { 39 | fields { 40 | version : 4; 41 | ihl : 4; 42 | diffserv : 8; 43 | totalLen : 16; 44 | identification : 16; 45 | flags : 3; 46 | fragOffset : 13; 47 | ttl : 8; 48 | protocol : 8; 49 | hdrChecksum : 16; 50 | srcAddr : 32; 51 | dstAddr: 32; 52 | } 53 | } 54 | 55 | header_type icmp_t { 56 | fields { 57 | typeCode : 16; 58 | hdrChecksum : 16; 59 | } 60 | } 61 | 62 | header_type tcp_t { 63 | fields { 64 | srcPort : 16; 65 | dstPort : 16; 66 | seqNo : 32; 67 | ackNo : 32; 68 | dataOffset : 4; 69 | res : 4; 70 | flags : 8; 71 | window : 16; 72 | checksum : 16; 73 | urgentPtr : 16; 74 | } 75 | } 76 | 77 | header_type udp_t { 78 | fields { 79 | srcPort : 16; 80 | dstPort : 16; 81 | length_ : 16; 82 | checksum : 16; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /p4web/P4_tools/css/styles.css: -------------------------------------------------------------------------------- 1 | /*Global Styles*/ 2 | 3 | body { 4 | background: #f1f4f7; 5 | padding-top: 50px; 6 | width: auto; 7 | font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; 8 | font-size: 14px; 9 | color: #999999; 10 | line-height: 25px; 11 | letter-spacing: 1px 12 | } 13 | 14 | p { 15 | color: #777; 16 | } 17 | 18 | 19 | .panel-heading { 20 | font-size: 20px; 21 | font-weight: 300; 22 | letter-spacing: 0.025em; 23 | line-height: 45px; 24 | } 25 | 26 | 27 | .navbar-header .navbar-brand { 28 | color: #fff; 29 | font-size: 16px; 30 | text-transform: uppercase; 31 | font-weight: 500; 32 | letter-spacing: 2px; 33 | } 34 | 35 | .navbar-brand span { 36 | color: #30a5ff; 37 | } 38 | 39 | 40 | /*Navbar*/ 41 | 42 | .navbar { 43 | border: 0; 44 | } 45 | 46 | .user-menu { 47 | display: inline-block; 48 | margin-top: 14px; 49 | margin-right: 10px; 50 | float: right; 51 | list-style: none; 52 | padding: 0; 53 | } 54 | 55 | .user-menu a { 56 | color: #fff; 57 | } 58 | 59 | .user-menu a:hover { 60 | text-decoration: none; 61 | } 62 | 63 | 64 | /* Sidebar */ 65 | 66 | .sidebar { 67 | display: block; 68 | background-color: #fff; 69 | padding: 0; 70 | display: none; 71 | } 72 | 73 | .sidebar form { 74 | padding: 20px 15px 5px 15px; 75 | border-bottom: 1px solid #eee; 76 | margin-bottom: 20px; 77 | } 78 | 79 | @media (min-width: 768px) { 80 | .sidebar { 81 | display: block; 82 | position: fixed; 83 | top: 50px; 84 | bottom: 0; 85 | left: 0; 86 | z-index: 1000; 87 | display: block; 88 | margin: 0; 89 | padding: 0; 90 | overflow-x: hidden; 91 | overflow-y: auto; 92 | background-color: #fff; 93 | box-shadow: 1px 0px 10px rgba(0, 0, 0, .05); 94 | } 95 | .navbar-header { 96 | width: 100%; 97 | } 98 | } 99 | 100 | .sidebar ul.nav li.divider { 101 | border-bottom: 1px solid #eee; 102 | margin: 20px 0; 103 | } 104 | 105 | .sidebar ul.nav .active a { 106 | color: #fff; 107 | background-color: #30a5ff; 108 | } 109 | 110 | .input-group{ 111 | width: 450px; 112 | } 113 | 114 | .input-group-addon{ 115 | width: 120px; 116 | } -------------------------------------------------------------------------------- /p4src/switch.p4: -------------------------------------------------------------------------------- 1 | /********************************* 2 | FuZhou University, SDNLab 3 | Added by Chen, 2017.3.29 4 | *********************************/ 5 | 6 | /* Copyright 2017 FuZhou University SDNLab, Edu. 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. */ 16 | 17 | /********************************* 18 | template: switch.p4 19 | *********************************/ 20 | 21 | #include "includes/headers.p4" 22 | #include "includes/parser.p4" 23 | // TODO: add your blocks 24 | 25 | // table and action 26 | 27 | action _drop() { 28 | drop(); 29 | } 30 | 31 | action _nop() { 32 | } 33 | 34 | #define MAC_LEARN_RECEIVER 1024 35 | 36 | field_list mac_learn_digest { 37 | ethernet.srcAddr; 38 | standard_metadata.ingress_port; 39 | } 40 | 41 | action mac_learn() { 42 | generate_digest(MAC_LEARN_RECEIVER, mac_learn_digest); 43 | } 44 | 45 | table smac { 46 | reads { 47 | ethernet.srcAddr : exact; 48 | } 49 | actions {mac_learn; _nop;} 50 | size : 512; 51 | } 52 | 53 | action forward(port) { 54 | modify_field(standard_metadata.egress_spec, port); 55 | } 56 | 57 | action broadcast() { 58 | modify_field(intrinsic_metadata.mcast_grp, 1); 59 | } 60 | 61 | table dmac { 62 | reads { 63 | ethernet.dstAddr : exact; 64 | //ethernet.srcAddr : exact; // test 65 | } 66 | actions { 67 | forward; 68 | broadcast; 69 | } 70 | size : 512; 71 | } 72 | 73 | table mcast_src_pruning { 74 | reads { 75 | standard_metadata.instance_type : exact; 76 | } 77 | actions {_nop; _drop;} 78 | size : 1; 79 | } 80 | 81 | // TODO: add your own control logic to the pipeline 82 | 83 | control ingress { 84 | apply(smac); 85 | apply(dmac); 86 | } 87 | 88 | control egress { 89 | if (standard_metadata.ingress_port == standard_metadata.egress_port) { 90 | apply(mcast_src_pruning); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/table_add_entry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import argparse 5 | from time import sleep 6 | import os 7 | import subprocess 8 | 9 | _THIS_DIR = os.path.dirname(os.path.realpath(__file__)) 10 | _THRIFT_BASE_PORT = 22222 11 | 12 | parser = argparse.ArgumentParser(description='P4 demo') 13 | parser.add_argument('--swname', help='Switch Name', 14 | type=str, action="store", required=True) 15 | parser.add_argument('--table-name', help='Table Name', 16 | type=str, action="store", required=True) 17 | parser.add_argument('--key', help='Match Key', nargs='*', 18 | type=str, action="store", required=True) 19 | parser.add_argument('--action', help='Action', 20 | type=str, action="store", required=True) 21 | parser.add_argument('para', nargs='*', type=str) 22 | args = parser.parse_args() 23 | 24 | def main(): 25 | # Get Thrift Port 26 | sw_name = args.swname 27 | index = int(sw_name[1:])-1 28 | thrift_port = _THRIFT_BASE_PORT+index 29 | 30 | # Get Table Name 31 | table_name = args.table_name 32 | 33 | key = '' 34 | # Get Match Key 35 | for i in args.key: 36 | key = key+' ' 37 | key = key+i 38 | 39 | # Get Action 40 | action = args.action 41 | 42 | paras = '' 43 | 44 | if args.para : 45 | para = args.para 46 | for i in args.para : 47 | paras = paras+' ' 48 | paras = paras+i 49 | table_info_cmd = "echo 'table_add %s %s%s =>%s' > /home/wpq/NSP4/src/cmd/table_add.txt" % (table_name, action, key, paras) 50 | else : 51 | table_info_cmd = "echo 'table_add %s %s%s =>' > /home/wpq/NSP4/src/cmd/table_add.txt" % (table_name, action, key) 52 | 53 | 54 | # Debug 55 | #print(table_info_cmd) 56 | 57 | os.system(table_info_cmd) 58 | cmd = "python /home/wpq/NSP4/src/simple_switch_CLI --thrift-port %d < /home/wpq/NSP4/src/cmd/table_add.txt" % thrift_port 59 | os.system(cmd) 60 | #os.system("%s > handle_tmp.txt" % cmd) 61 | os.system("rm -rf cmd/table_add.txt") 62 | 63 | """ 64 | # Get Handle 65 | text = open('handle_tmp.txt', "r") 66 | for line in text.readlines(): 67 | if line[0] == 'E': 68 | for i in range(len(line)): 69 | if line[i].isdigit(): 70 | break 71 | handle = line[i:-1] 72 | os.system("echo '%s' >> handle/%s_%s.txt" % (handle, sw_name, table_name)) 73 | os.system("rm -rf handle_tmp.txt") 74 | """ 75 | 76 | if __name__ == '__main__': 77 | main() 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NSP4: A Network Simulator for the P4 language 2 | 3 | **NSP4 is still under developing so that many functions are not supported in NSP4 yet. We invite you to extend NSP4 to create a better environment for learning the P4 language.** 4 | 5 | [发表在SDNLAB上的NSP4中文文档](https://www.sdnlab.com/19370.html) 6 | 7 | ## Experiment Preparation 8 | 9 | 1、In the directory of **`src/`**,change the path information from ```/home/wpq``` to the NSP4 directory information. The code need to modify contain the following file: 10 | 11 | - show_sw_tables.py 12 | - show_table_entry.py 13 | - show_table_info.py 14 | - table_add_entry.py 15 | - table_delete_entry.py 16 | 17 | Example, for the ```show_sw_tables.py```, you should change the sentence from 18 | 19 | ``` 20 | cmd = "python /home/wpq/NSP4/src/simple_switch_CLI --thrift-port %d < /home/wpq/NSP4/src/cmd/show_tables.txt" % thrift_port 21 | ``` 22 | 23 | to 24 | 25 | ``` 26 | cmd = "python /src/simple_switch_CLI --thrift-port %d < /home/wpq/NSP4/src/cmd/show_tables.txt" % thrift_port 27 | ``` 28 | 29 | 2、In the directory of **p4web**, change some path information in the code of the file ```p4web.py```. 30 | 31 | Like 1, you just modify the information from ```/home/wpq``` to the NSP4 directory, the line number of code includes 70、77、94、118、162、177、201. 32 | 33 | ## Hands-on Steps 34 | 35 | 1、Put the P4 code which is correct to the directory of **p4src/**. Note that the P4 program which contains the control flows "ingress" and "egress" must be given with the name "switch.p4". 36 | 37 | 2、Start up the ```p4web.py``` by the Ryu command **ryu-manager**. 38 | 39 | ``` 40 | ryu-manager ./p4web.py 41 | ``` 42 | 43 | 3、Type 127.0.0.1:8080 on your browser,you will see the following interface. 44 | 45 | ![1](http://images2015.cnblogs.com/blog/990007/201705/990007-20170531124619618-154243598.png) 46 | 47 | 4、Input the information of topology on the interface, and click the button of **提交**, you will find the GUI of topology appear the browser. Then start up the mininet with P4 by the following command. 48 | 49 | ![2](http://images2015.cnblogs.com/blog/990007/201705/990007-20170531124620539-1327136931.png) 50 | 51 | ![3](http://images2015.cnblogs.com/blog/990007/201705/990007-20170531124623196-1485318798.png) 52 | 53 | ``` 54 | cd init 55 | sudo ./run_demo.sh 56 | ``` 57 | 58 | 5、Then you can choose the switch by the switch number to config the P4 switch table. 59 | 60 | For example: 61 | 62 | 5-1、Choose S1 63 | 64 | ![4](http://images2015.cnblogs.com/blog/990007/201705/990007-20170531124630274-1078869477.png) 65 | 66 | 5-2、Add flow entry to the table of **dmac** 67 | 68 | ![5](http://images2015.cnblogs.com/blog/990007/201705/990007-20170531124632274-709174381.png) 69 | 70 | ![6](http://images2015.cnblogs.com/blog/990007/201705/990007-20170531124633461-1679913531.png) 71 | 72 | 5-3、Then you can fine the flow entry just downloaded appear the interface 73 | 74 | ![7](http://images2015.cnblogs.com/blog/990007/201705/990007-20170531124634618-1109635584.png) 75 | 76 | 5-4、Delete the flow entry 77 | 78 | ![8](http://images2015.cnblogs.com/blog/990007/201705/990007-20170531124637539-179970778.png) 79 | 80 | ## Existing Problems 81 | 82 | - The path information is complex 83 | - Some function such as counter is not supported 84 | - Non-support of P4-16 language 85 | -------------------------------------------------------------------------------- /p4web/P4_tools/js/intputGroup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by DreamBoy on 2016/6/4. 3 | */ 4 | /** 5 | * Created by DreamBoy on 2016/4/29. 6 | */ 7 | $(function() { 8 | $.fn.initInputGroup = function (options) { 9 | //1.Settings 初始化设置 10 | var c = $.extend({ 11 | widget: 'input', 12 | add: "", 13 | del: "", 14 | field: '', 15 | data: [] 16 | }, options); 17 | 18 | var _this = $(this); 19 | 20 | //添加序号为1的输入框组 21 | addInputGroup(1); 22 | 23 | /** 24 | * 添加序号为order的输入框组 25 | * @param order 输入框组的序号 26 | * @param data 初始化输入框组中的数据 27 | */ 28 | function addInputGroup(order) { 29 | 30 | //1.创建输入框组 31 | var inputGroup = $("
"); 32 | //2.输入框组的序号 33 | var inputGroupAddon1 = $(""); 34 | //3.设置输入框组的序号 35 | inputGroupAddon1.html(" " + order + " "); 36 | 37 | //4.创建输入框组中的输入控件(input或textarea) 38 | var widget = '', inputGroupAddon2; 39 | if(c.widget == 'textarea') { 40 | widget = $(""); 41 | widget.html(c.data[order - 1]); 42 | inputGroupAddon2 = $(""); 43 | } else if(c.widget == 'input') { 44 | widget = $(""); 45 | widget.val(c.data[order - 1]); 46 | inputGroupAddon2 = $(""); 47 | } 48 | 49 | //5.设置表单提交时的字段名 50 | if(c.field.length == 0) { 51 | widget.prop('name', c.widget + 'Data[]'); 52 | } else { 53 | widget.prop('name', c.field + '[]'); 54 | } 55 | 56 | 57 | //6.创建输入框组中最后面的操作按钮 58 | var addBtn = $(""); 59 | addBtn.appendTo(inputGroupAddon2).on('click', function() { 60 | //7.响应删除和添加操作按钮事件 61 | if($(this).html() == c.del) { 62 | $(this).parents('.input-group').remove(); 63 | } else if($(this).html() == c.add) { 64 | $(this).html(c.del); 65 | addInputGroup(order+1); 66 | } 67 | //8.重新排序输入框组的序号 68 | resort(); 69 | }); 70 | 71 | inputGroup.append(inputGroupAddon1).append(widget).append(inputGroupAddon2); 72 | 73 | _this.append(inputGroup); 74 | 75 | if(order + 1 > c.data.length) { 76 | return; 77 | } 78 | addBtn.trigger('click'); 79 | } 80 | 81 | function resort() { 82 | var child = _this.children(); 83 | $.each(child, function(i) { 84 | $(this).find(".input-group-addon").eq(0).html(' ' + (i + 1) + ' '); 85 | }); 86 | } 87 | } 88 | }); -------------------------------------------------------------------------------- /p4src/includes/parser.p4: -------------------------------------------------------------------------------- 1 | /********************************* 2 | FuZhou University, SDNLab 3 | Added by Chen, 2017.3.29 4 | *********************************/ 5 | 6 | /* Copyright 2017 FuZhou University SDNLab, Edu. 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. */ 16 | 17 | /********************************* 18 | template: parser.p4 19 | *********************************/ 20 | 21 | parser start { 22 | return parse_ethernet; 23 | } 24 | 25 | header ethernet_t ethernet; 26 | metadata intrinsic_metadata_t intrinsic_metadata; 27 | 28 | #define ETHERTYPE_BF_FABRIC 0x9000 29 | #define ETHERTYPE_VLAN 0x8100 30 | #define ETHERTYPE_QINQ 0x9100 31 | #define ETHERTYPE_MPLS 0x8847 32 | #define ETHERTYPE_IPV4 0x0800 33 | #define ETHERTYPE_IPV6 0x86dd 34 | #define ETHERTYPE_ARP 0x0806 35 | #define ETHERTYPE_RARP 0x8035 36 | #define ETHERTYPE_NSH 0x894f 37 | #define ETHERTYPE_ETHERNET 0x6558 38 | #define ETHERTYPE_ROCE 0x8915 39 | #define ETHERTYPE_FCOE 0x8906 40 | #define ETHERTYPE_TRILL 0x22f3 41 | #define ETHERTYPE_VNTAG 0x8926 42 | #define ETHERTYPE_LLDP 0x88cc 43 | #define ETHERTYPE_LACP 0x8809 44 | 45 | #define IPV4_MULTICAST_MAC 0x01005E 46 | #define IPV6_MULTICAST_MAC 0x3333 47 | 48 | parser parse_ethernet { 49 | extract(ethernet); 50 | return select(latest.etherType) { 51 | ETHERTYPE_IPV4 : parse_ipv4; 52 | default: ingress; 53 | } 54 | } 55 | 56 | #define IP_PROTOCOLS_ICMP 1 57 | #define IP_PROTOCOLS_IGMP 2 58 | #define IP_PROTOCOLS_IPV4 4 59 | #define IP_PROTOCOLS_TCP 6 60 | #define IP_PROTOCOLS_UDP 17 61 | #define IP_PROTOCOLS_IPV6 41 62 | #define IP_PROTOCOLS_GRE 47 63 | #define IP_PROTOCOLS_IPSEC_ESP 50 64 | #define IP_PROTOCOLS_IPSEC_AH 51 65 | #define IP_PROTOCOLS_ICMPV6 58 66 | #define IP_PROTOCOLS_EIGRP 88 67 | #define IP_PROTOCOLS_OSPF 89 68 | #define IP_PROTOCOLS_PIM 103 69 | #define IP_PROTOCOLS_VRRP 112 70 | 71 | #define IP_PROTOCOLS_IPHL_ICMP 0x501 72 | #define IP_PROTOCOLS_IPHL_IPV4 0x504 73 | #define IP_PROTOCOLS_IPHL_TCP 0x506 74 | #define IP_PROTOCOLS_IPHL_UDP 0x511 75 | #define IP_PROTOCOLS_IPHL_IPV6 0x529 76 | #define IP_PROTOCOLS_IPHL_GRE 0x52f 77 | 78 | header ipv4_t ipv4; 79 | 80 | parser parse_ipv4 { 81 | extract(ipv4); 82 | return select(latest.fragOffset, latest.ihl, latest.protocol) { 83 | IP_PROTOCOLS_IPHL_ICMP : parse_icmp; 84 | IP_PROTOCOLS_IPHL_TCP : parse_tcp; 85 | IP_PROTOCOLS_IPHL_UDP : parse_udp; 86 | default: ingress; 87 | } 88 | } 89 | 90 | #define UDP_PORT_BOOTPS 67 91 | #define UDP_PORT_BOOTPC 68 92 | #define UDP_PORT_RIP 520 93 | #define UDP_PORT_RIPNG 521 94 | #define UDP_PORT_DHCPV6_CLIENT 546 95 | #define UDP_PORT_DHCPV6_SERVER 547 96 | #define UDP_PORT_HSRP 1985 97 | #define UDP_PORT_BFD 3785 98 | #define UDP_PORT_LISP 4341 99 | #define UDP_PORT_VXLAN 4789 100 | #define UDP_PORT_VXLAN_GPE 4790 101 | #define UDP_PORT_ROCE_V2 4791 102 | #define UDP_PORT_GENV 6081 103 | #define UDP_PORT_SFLOW 6343 104 | 105 | header icmp_t icmp; 106 | 107 | parser parse_icmp { 108 | extract(icmp); 109 | return select(latest.typeCode) { 110 | default: ingress; 111 | } 112 | } 113 | 114 | header tcp_t tcp; 115 | 116 | parser parse_tcp { 117 | extract(tcp); 118 | return select(latest.dstPort) { 119 | default: ingress; 120 | } 121 | } 122 | 123 | header udp_t udp; 124 | 125 | parser parse_udp { 126 | extract(udp); 127 | return select(latest.dstPort) { 128 | default: ingress; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /script/topo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Copyright 2013-present Barefoot Networks, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | from mininet.net import Mininet 18 | from mininet.topo import Topo 19 | from mininet.log import setLogLevel, info 20 | from mininet.cli import CLI 21 | 22 | from p4_mininet import P4Switch, P4Host 23 | 24 | import argparse 25 | from time import sleep 26 | import os 27 | import subprocess 28 | 29 | _THIS_DIR = os.path.dirname(os.path.realpath(__file__)) 30 | _THRIFT_BASE_PORT = 22222 31 | 32 | parser = argparse.ArgumentParser(description='Mininet demo') 33 | parser.add_argument('--behavioral-exe', help='Path to behavioral executable', 34 | type=str, action="store", required=True) 35 | parser.add_argument('--json', help='Path to JSON config file', 36 | type=str, action="store", required=True) 37 | parser.add_argument('--cli', help='Path to BM CLI', 38 | type=str, action="store", required=True) 39 | parser.add_argument('--mode', choices=['l2', 'l3'], type=str, default='l3') 40 | 41 | args = parser.parse_args() 42 | 43 | sw_macs = [] 44 | sw_addrs = [] 45 | 46 | class MyTopo(Topo): 47 | def __init__(self, sw_path, json_path, nb_hosts, nb_switches, links, **opts): 48 | # Initialize topology and default options 49 | Topo.__init__(self, **opts) 50 | 51 | for i in xrange(nb_switches): 52 | self.addSwitch('s%d' % (i + 1), 53 | sw_path = sw_path, 54 | json_path = json_path, 55 | thrift_port = _THRIFT_BASE_PORT + i, 56 | pcap_dump = True, 57 | device_id = i) 58 | 59 | for h in xrange(nb_hosts): 60 | self.addHost('h%d' % (h + 1), ip="10.0.0.%d" % (h + 1), 61 | mac="00:00:00:00:00:0%d" % (h+1)) 62 | addrv = "10.0.0.%d" % (h+1) 63 | macv = "00:00:00:00:00:0%d" % (h+1) 64 | sw_addrs.append(addrv) 65 | sw_macs.append(macv) 66 | 67 | for a, b in links: 68 | self.addLink(a, b) 69 | 70 | def read_topo(): 71 | nb_hosts = 0 72 | nb_switches = 0 73 | links = [] 74 | with open("topo.txt", "r") as f: 75 | line = f.readline()[:-1] 76 | w, nb_switches = line.split() 77 | assert(w == "switches") 78 | line = f.readline()[:-1] 79 | w, nb_hosts = line.split() 80 | assert(w == "hosts") 81 | for line in f: 82 | if not f: break 83 | a, b = line.split() 84 | links.append( (a, b) ) 85 | return int(nb_hosts), int(nb_switches), links 86 | 87 | 88 | def main(): 89 | nb_hosts, nb_switches, links = read_topo() 90 | 91 | mode = args.mode 92 | 93 | topo = MyTopo(args.behavioral_exe, 94 | args.json, 95 | nb_hosts, nb_switches, links) 96 | 97 | net = Mininet(topo = topo, 98 | host = P4Host, 99 | switch = P4Switch, 100 | controller = None ) 101 | net.start() 102 | 103 | for n in xrange(nb_hosts): 104 | h = net.get('h%d' % (n + 1)) 105 | 106 | for off in ["rx", "tx", "sg"]: 107 | cmd = "/sbin/ethtool --offload eth0 %s off" % off 108 | print cmd 109 | h.cmd(cmd) 110 | 111 | print "disable ipv6" 112 | h.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") 113 | h.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") 114 | h.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") 115 | h.cmd("sysctl -w net.ipv4.tcp_congestion_control=reno") 116 | h.cmd("iptables -I OUTPUT -p icmp --icmp-type destination-unreachable -j DROP") 117 | 118 | if mode == "l2": 119 | h.setDefaultRoute("dev eth0") 120 | else: 121 | h.setARP(sw_addrs[n], sw_macs[n]) 122 | h.setDefaultRoute("dev eth0 via %s" % sw_addrs[n]) 123 | 124 | for n in xrange(nb_hosts): 125 | h = net.get('h%d' % (n + 1)) 126 | h.describe() 127 | 128 | sleep(1) 129 | 130 | print "Ready !" 131 | 132 | CLI( net ) 133 | net.stop() 134 | 135 | if __name__ == '__main__': 136 | setLogLevel( 'info' ) 137 | main() 138 | -------------------------------------------------------------------------------- /p4web/P4_tools/css/bootstrap-table.css: -------------------------------------------------------------------------------- 1 | .table { 2 | margin-bottom: 0 !important; 3 | border-bottom: 1px solid #dddddd; 4 | border-collapse: collapse !important; 5 | border-radius: 1px; 6 | } 7 | 8 | .fixed-table-container { 9 | position: relative; 10 | clear: both; 11 | border: 1px solid #dddddd; 12 | border-radius: 4px; 13 | -webkit-border-radius: 4px; 14 | -moz-border-radius: 4px; 15 | } 16 | 17 | .fixed-table-header { 18 | overflow: hidden; 19 | border-radius: 4px 4px 0 0; 20 | -webkit-border-radius: 4px 4px 0 0; 21 | -moz-border-radius: 4px 4px 0 0; 22 | } 23 | 24 | .fixed-table-body { 25 | overflow-x: auto; 26 | overflow-y: auto; 27 | height: 100%; 28 | } 29 | 30 | .fixed-table-container table { 31 | width: 100%; 32 | } 33 | 34 | .fixed-table-container thead th { 35 | height: 0; 36 | padding: 0; 37 | margin: 0; 38 | border-left: 1px solid #dddddd; 39 | } 40 | 41 | .fixed-table-container thead th:first-child { 42 | border-left: none; 43 | } 44 | 45 | .fixed-table-container thead th .th-inner { 46 | padding: 8px; 47 | line-height: 24px; 48 | vertical-align: top; 49 | overflow: hidden; 50 | text-overflow: ellipsis; 51 | white-space: nowrap; 52 | } 53 | 54 | .fixed-table-container thead th .sortable { 55 | cursor: pointer; 56 | } 57 | 58 | .fixed-table-container tbody td { 59 | border-left: 1px solid #dddddd; 60 | } 61 | 62 | .fixed-table-container tbody tr:first-child td { 63 | border-top: none; 64 | } 65 | 66 | .fixed-table-container tbody td:first-child { 67 | border-left: none; 68 | } 69 | 70 | /* the same color with .active */ 71 | .fixed-table-container tbody .selected td { 72 | background-color: #f5f5f5; 73 | } 74 | 75 | .fixed-table-container .bs-checkbox { 76 | text-align: center; 77 | } 78 | 79 | .fixed-table-container .bs-checkbox .th-inner { 80 | padding: 8px 0; 81 | } 82 | 83 | .fixed-table-container input[type="radio"], 84 | .fixed-table-container input[type="checkbox"] { 85 | margin: 0 auto !important; 86 | } 87 | 88 | .fixed-table-container .no-records-found { 89 | text-align: center; 90 | } 91 | 92 | 93 | .fixed-table-pagination .pagination, 94 | .fixed-table-pagination .pagination-detail { 95 | margin-top: 10px; 96 | margin-bottom: 10px; 97 | } 98 | 99 | .fixed-table-pagination .pagination a { 100 | padding: 6px 12px; 101 | line-height: 1.428571429; 102 | } 103 | 104 | .fixed-table-pagination .pagination-info { 105 | line-height: 34px; 106 | margin-right: 5px; 107 | } 108 | 109 | .fixed-table-pagination .btn-group { 110 | position: relative; 111 | display: inline-block; 112 | vertical-align: middle; 113 | } 114 | 115 | .fixed-table-pagination .dropup .dropdown-menu { 116 | margin-bottom: 0; 117 | } 118 | 119 | .fixed-table-pagination .page-list { 120 | display: inline-block; 121 | } 122 | 123 | .fixed-table-toolbar .columns { 124 | margin-left: 5px; 125 | } 126 | 127 | .fixed-table-toolbar .columns label { 128 | display: block; 129 | padding: 3px 20px; 130 | clear: both; 131 | font-weight: normal; 132 | line-height: 1.428571429; 133 | } 134 | 135 | .fixed-table-toolbar .bars, 136 | .fixed-table-toolbar .search, 137 | .fixed-table-toolbar .columns { 138 | position: relative; 139 | margin-top: 10px; 140 | margin-bottom: 10px; 141 | line-height: 34px; 142 | } 143 | 144 | .fixed-table-pagination li.disabled a { 145 | pointer-events: none; 146 | cursor: default; 147 | } 148 | 149 | .fixed-table-loading { 150 | display: none; 151 | position: absolute; 152 | top: 42px; 153 | right: 0; 154 | bottom: 0; 155 | left: 0; 156 | z-index: 99; 157 | background-color: #fff; 158 | text-align: center; 159 | } 160 | 161 | .fixed-table-body .card-view .title { 162 | font-weight: bold; 163 | display: inline-block; 164 | min-width: 30%; 165 | text-align: left !important; 166 | } 167 | 168 | /* support bootstrap 2 */ 169 | .fixed-table-body thead th .th-inner { 170 | box-sizing: border-box; 171 | } 172 | 173 | .table th, .table td { 174 | vertical-align: middle; 175 | box-sizing: border-box; 176 | } 177 | 178 | .fixed-table-toolbar .dropdown-menu { 179 | text-align: left; 180 | max-height: 300px; 181 | overflow: auto; 182 | } 183 | 184 | .fixed-table-toolbar .btn-group>.btn-group { 185 | display: inline-block; 186 | margin-left: -1px !important; 187 | } 188 | 189 | .fixed-table-toolbar .btn-group>.btn-group>.btn { 190 | border-radius: 0; 191 | } 192 | 193 | .fixed-table-toolbar .btn-group>.btn-group:first-child>.btn { 194 | border-top-left-radius: 4px; 195 | border-bottom-left-radius: 4px; 196 | } 197 | 198 | .fixed-table-toolbar .btn-group>.btn-group:last-child>.btn { 199 | border-top-right-radius: 4px; 200 | border-bottom-right-radius: 4px; 201 | } 202 | 203 | .table>thead>tr>th { 204 | vertical-align: bottom; 205 | border-bottom: 2px solid #ddd; 206 | } 207 | 208 | /* support bootstrap 3 */ 209 | .table thead>tr>th { 210 | padding: 0; 211 | margin: 0; 212 | } 213 | 214 | .pull-right .dropdown-menu { 215 | right: 0; 216 | left: auto; 217 | } 218 | 219 | /* calculate scrollbar width */ 220 | p.fixed-table-scroll-inner { 221 | width: 100%; 222 | height: 200px; 223 | } 224 | 225 | div.fixed-table-scroll-outer { 226 | top: 0; 227 | left: 0; 228 | visibility: hidden; 229 | width: 200px; 230 | height: 150px; 231 | overflow: hidden; 232 | } -------------------------------------------------------------------------------- /p4web/p4web.py: -------------------------------------------------------------------------------- 1 | # -- coding: utf-8 -- 2 | import os 3 | import commands 4 | import re 5 | from webob.static import DirectoryApp 6 | 7 | from ryu.app.wsgi import ControllerBase, WSGIApplication, route 8 | from ryu.base import app_manager 9 | import json 10 | 11 | 12 | 13 | PATH = os.path.dirname(__file__) 14 | 15 | 16 | # Serving static files 17 | class GUIServerApp(app_manager.RyuApp): 18 | _CONTEXTS = { 19 | 'wsgi': WSGIApplication, 20 | } 21 | 22 | def __init__(self, *args, **kwargs): 23 | super(GUIServerApp, self).__init__(*args, **kwargs) 24 | 25 | wsgi = kwargs['wsgi'] 26 | wsgi.register(GUI_P4_ServerController) 27 | 28 | class GUI_P4_ServerController(ControllerBase): 29 | def __init__(self, req, link, data, **config): 30 | super(GUI_P4_ServerController, self).__init__(req, link, data, **config) 31 | path = "%s/P4_tools" % PATH 32 | self.static_app = DirectoryApp(path) 33 | 34 | @route('topology', '/{filename:.*}', methods=['GET']) 35 | def static_handler(self, req, **kwargs): 36 | if kwargs['filename']: 37 | print kwargs['filename'] 38 | if kwargs['filename'] == "ok": 39 | 40 | # 用于创建拓扑文件 41 | switches = req.GET['switches'] 42 | hosts = req.GET['hosts'] 43 | 44 | switches = req.GET['switches'] 45 | switches = switches.encode('utf-8') 46 | switches = int(switches) 47 | 48 | hosts = req.GET['hosts'] 49 | hosts = hosts.encode('utf-8') 50 | hosts = int(hosts) 51 | 52 | linksnum = req.GET['linksnum'] 53 | linksnum = linksnum.encode('utf-8') 54 | linksnum = int(linksnum) 55 | 56 | link_information = req.GET['link_information'] 57 | link_information = link_information.encode('utf-8') 58 | 59 | #正则匹配得到对应的链路信息,从而写到topo.txt中去 60 | match_links = re.findall(r'(.*?),', link_information, re.M | re.I) 61 | links = match_links 62 | 63 | 64 | topo_file = open('topo.txt', 'w') 65 | topo_file.write('switches ' + str(switches) + '\n') 66 | topo_file.write('hosts ' + str(hosts) + '\n') 67 | for i in range(linksnum): 68 | topo_file.write(links[i] + '\n') 69 | topo_file.close() 70 | 71 | print 'switches=%d' % switches 72 | print "hosts=%d" % hosts 73 | status, output = commands.getstatusoutput('cp -f topo.txt /home/wpq/NSP4/init') 74 | 75 | elif kwargs['filename'] == "table": 76 | #显示流表专用 77 | switch_no = req.GET['switch_no'] 78 | switch_no = switch_no.encode('utf-8') 79 | 80 | #--------得到交换机内所有的【表名】(以------为该部分功能的结尾) 81 | 82 | cmd_str = 'python /home/wpq/NSP4/src/show_sw_tables.py --swname s' + switch_no 83 | status, output = commands.getstatusoutput(cmd_str) 84 | #正则匹配得到交换机内所有的【表名】 85 | matchObj = re.findall('(\S+)(?=[\s]*\[i.*\])', output, re.M | re.I) 86 | 87 | table_number = len(matchObj) 88 | # -----------得到交换机内所有的【表名】----------------# 89 | data_json = {} 90 | 91 | # print matchObj 92 | data_json['table-number'] = table_number 93 | table = [] 94 | for i in range(table_number): 95 | table_infor = {} 96 | table_name = matchObj[i] 97 | table_infor['table-name'] = table_name 98 | 99 | #查询对应表的匹配项以及动作 100 | cmd_str = 'python /home/wpq/NSP4/src/show_table_info.py --swname s' + switch_no + ' --table-name ' + table_name 101 | status, output = commands.getstatusoutput(cmd_str) 102 | 103 | #得到该表对应的匹配项 104 | match_table_key = re.findall('[=\t](\S*)(?=\(.*\,.*\))', output, re.M | re.I) 105 | #得到该表对应的动作 106 | match_action_key = re.findall('[\n](\S+)(?=[\s]*\[(.*)\])', output, re.M | re.I) 107 | 108 | 109 | match_key_num = len(match_table_key) 110 | key = [] 111 | for j in range(match_key_num): 112 | key.append(match_table_key[j]) 113 | 114 | table_infor['key-number'] = match_key_num 115 | table_infor['key'] = key 116 | 117 | match_action_num = len(match_action_key) 118 | action = [] 119 | for j in range(match_action_num): 120 | action.append(match_action_key[j][0]) 121 | 122 | table_infor['action-number'] = match_action_num 123 | table_infor['action'] = action 124 | 125 | #查询对应表,所对应的表项 126 | cmd_str = 'python /home/wpq/NSP4/src/show_table_entry.py --swname s' + switch_no + ' --table-name ' + table_name 127 | status, output = commands.getstatusoutput(cmd_str) 128 | 129 | print output 130 | 131 | #matchObj0 对应 表项 的 handle(唯一值) 132 | matchObj0 = re.findall('(0x[\S]+)', output, re.M | re.I) 133 | 134 | matchObj1 = re.findall('\*\s(\S+)(?=\s*:\s)', output, re.M | re.I) 135 | matchObj2 = re.findall('[\s] (\S+)(?=\n)', output, re.M | re.I) 136 | #matchObj3 对应的动作,以及动作参数 137 | matchObj3 = re.findall('(\S+)\s- ?([\S ]*)', output, re.M | re.I) 138 | 139 | table_entry = [ 140 | ] 141 | 142 | 143 | for j in range(len(matchObj0)): 144 | entry = {} 145 | entry['handle'] = int(matchObj0[j], 16) 146 | entry[matchObj1[j]] = matchObj2[j] 147 | entry["action"] = matchObj3[j][0] 148 | entry["action-parameter"] = matchObj3[j][1] 149 | table_entry.append(entry) 150 | 151 | table_infor['table-entry'] = table_entry 152 | table_infor['table-entry-number'] = len(table_entry) 153 | table.append(table_infor) 154 | 155 | data_json['table'] = table 156 | return json.dumps(data_json) 157 | 158 | elif kwargs['filename'] == 'add_entry': 159 | #添加表项 160 | 161 | switch_no = req.GET['switch-name'] 162 | table_name = req.GET['table-name'] 163 | action = req.GET['action'] 164 | action_parameter = req.GET['action_parameter'] 165 | 166 | switch_no = switch_no.encode('utf-8') 167 | switch_no = re.findall('([0-9]+)', switch_no, re.M | re.I) 168 | switch_no = switch_no[0] 169 | 170 | table_name = table_name.encode('utf-8') 171 | action = action.encode('utf-8') 172 | action_parameter = action_parameter.encode('utf-8') 173 | 174 | action_sum = action + ' ' + action_parameter 175 | 176 | cmd_str = 'python /home/wpq/NSP4/src/show_table_info.py --swname s' + switch_no + ' --table-name ' + table_name 177 | status, output = commands.getstatusoutput(cmd_str) 178 | 179 | match_table_key = re.findall('[=\t](\S*)(?=\(.*\,.*\))', output, re.M | re.I) 180 | 181 | match_key_num = len(match_table_key) 182 | 183 | key = [] 184 | match_key_value = '' 185 | for j in range(match_key_num): 186 | match_key_value += req.GET[match_table_key[j]].encode('utf-8') + ' ' 187 | key.append(match_table_key[j]) 188 | 189 | print "match_key_value" 190 | print match_key_value 191 | cmd_str = 'python /home/wpq/NSP4/src/table_add_entry.py --swname s' + switch_no + ' --table-name ' + table_name + ' --key ' + match_key_value + '--action ' + action_sum 192 | status, output = commands.getstatusoutput(cmd_str) 193 | 194 | print cmd_str 195 | 196 | output = re.findall('(Error|Invalid)', output, re.M | re.I) 197 | 198 | if len(output) < 1: 199 | return "add_entry success!" 200 | elif output[0] == 'Error': 201 | return "input Error!" 202 | elif output[0] == 'Invalid': 203 | return "input Invalid!" 204 | return json.dumps(output) 205 | 206 | elif kwargs['filename'] == 'del_entry': 207 | #删除表项 208 | handle = req.GET['handle'].encode('utf-8') 209 | table_name = req.GET['table-name'].encode('utf-8') 210 | switch_no = req.GET['switch-name'].encode('utf-8') 211 | 212 | switch_no = re.findall('([0-9]+)', switch_no, re.M | re.I) 213 | switch_no = switch_no[0] 214 | 215 | cmd_str = 'python /home/wpq/NSP4/src/table_delete_entry.py --swname s' + switch_no + ' --table-name ' + table_name + ' --handle ' + handle 216 | 217 | print cmd_str 218 | status, output = commands.getstatusoutput(cmd_str) 219 | 220 | return "delete success" 221 | 222 | req.path_info = kwargs['filename'] 223 | return self.static_app(req) 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /p4web/P4_tools/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | P4 tools 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 36 | 37 | 41 | 151 | 152 | 273 | 274 | 275 | 276 | 277 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /p4web/P4_tools/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | P4 tools 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 27 | 51 | 52 | 53 |
54 |
55 |
56 |
57 |
拓扑图
58 |
59 | 60 |
61 |
62 |
63 |
64 | 65 |
交换机流表信息
66 | 67 | 68 |
69 |
70 |
71 |
72 |
73 | 74 | 75 | 76 | 77 | 78 | 79 | 388 | 389 | 390 | -------------------------------------------------------------------------------- /p4web/P4_tools/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.2.0 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.2.0",d.prototype.close=function(b){function c(){f.detach().trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",c).emulateTransitionEnd(150):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.2.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),d[e](null==f[b]?this.options[b]:f[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b).on("keydown.bs.carousel",a.proxy(this.keydown,this)),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.2.0",c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.to=function(b){var c=this,d=this.getItemIndex(this.$active=this.$element.find(".item.active"));return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=e[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:g});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,f&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(e)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:g});return a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one("bsTransitionEnd",function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger(m)),f&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(b=!b),e||d.data("bs.collapse",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};c.VERSION="3.2.0",c.DEFAULTS={toggle:!0},c.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},c.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var c=a.Event("show.bs.collapse");if(this.$element.trigger(c),!c.isDefaultPrevented()){var d=this.$parent&&this.$parent.find("> .panel > .in");if(d&&d.length){var e=d.data("bs.collapse");if(e&&e.transitioning)return;b.call(d,"hide"),e||d.data("bs.collapse",null)}var f=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[f](0),this.transitioning=1;var g=function(){this.$element.removeClass("collapsing").addClass("collapse in")[f](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return g.call(this);var h=a.camelCase(["scroll",f].join("-"));this.$element.one("bsTransitionEnd",a.proxy(g,this)).emulateTransitionEnd(350)[f](this.$element[0][h])}}},c.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},c.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var d=a.fn.collapse;a.fn.collapse=b,a.fn.collapse.Constructor=c,a.fn.collapse.noConflict=function(){return a.fn.collapse=d,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(c){var d,e=a(this),f=e.attr("data-target")||c.preventDefault()||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""),g=a(f),h=g.data("bs.collapse"),i=h?"toggle":e.data(),j=e.attr("data-parent"),k=j&&a(j);h&&h.transitioning||(k&&k.find('[data-toggle="collapse"][data-parent="'+j+'"]').not(e).addClass("collapsed"),e[g.hasClass("in")?"addClass":"removeClass"]("collapsed")),b.call(g,i)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.2.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('