├── .gitignore ├── LICENSE ├── README.md ├── client ├── nc_socket.py ├── receiver.py └── set_arp.sh ├── controller ├── config │ ├── commands.txt │ ├── commands_1.txt │ ├── commands_2.txt │ ├── commands_3.txt │ ├── config.xml │ ├── topo.txt │ └── vring.txt ├── exe_cmd.py ├── fail_recovery.py ├── nc_config.py ├── p4_mininet.py ├── plot.py ├── run_test.py └── topo.py ├── logs └── .gitignore └── p4src ├── includes ├── checksum.p4 ├── defines.p4 ├── headers.p4 └── parsers.p4 ├── netchain.p4 └── routing.p4 /.gitignore: -------------------------------------------------------------------------------- 1 | *.pcap 2 | *.bak 3 | *.pyc 4 | -------------------------------------------------------------------------------- /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 | 203 | -------------------------------------------------------------------------------- 204 | 205 | Code in python/ray/rllib/{evolution_strategies, dqn} adapted from 206 | https://github.com/openai (MIT License) 207 | 208 | Copyright (c) 2016 OpenAI (http://openai.com) 209 | 210 | Permission is hereby granted, free of charge, to any person obtaining a copy 211 | of this software and associated documentation files (the "Software"), to deal 212 | in the Software without restriction, including without limitation the rights 213 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 214 | copies of the Software, and to permit persons to whom the Software is 215 | furnished to do so, subject to the following conditions: 216 | 217 | The above copyright notice and this permission notice shall be included in 218 | all copies or substantial portions of the Software. 219 | 220 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 221 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 222 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 223 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 224 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 225 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 226 | THE SOFTWARE. 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NetChain 2 | ## 0. Introduction
3 | 4 | BMV2-based implementation of NetChain for paper ["NetChain: Scale-Free Sub-RTT Coordination"](https://www.usenix.org/conference/nsdi18/presentation/jin) published in NSDI 2018. 5 | 6 | ## 1. Obtain required software
7 | - Firstly, you need to get the p4 compiler from Github, and install required dependencies.
8 | > git clone https://github.com/p4lang/p4c-bm.git p4c-bmv2
9 | > cd p4c-bmv2
10 | > sudo pip install -r requirements.txt
11 | - Secondly, you need to get the behavior model of p4 from github, install dependencies and compile the behavior model.
12 | > git clone https://github.com/p4lang/behavioral-model.git bmv2
13 | > cd bmv2
14 | > install_deps.sh
15 | > ./autogen.sh
16 | > ./configure
17 | > make
18 | - Finally, you need to install some other tools which are used in this simulation.
19 | > sudo apt-get install mininet python-ipaddr
20 | > sudo pip install scapy thrift networkx
21 | 22 | ## 2. Content
23 | - client
24 | - client/nc_socket.py
25 | - client/receiver.py
26 | - clinet/set_arp.sh
27 | - controller
28 | - controller/config
29 | - controller/config/topo.txt
30 | - controller/config/vring.txt
31 | - controller/config/config.xml
32 | - controller/config/commands[_1,_2,_3]*.txt
33 | - controller/nc_config.py
34 | - controller/p4_mininet.py
35 | - controller/run_test.py
36 | - controller/exe_cmd.py
37 | - controller/fail_recovery.py
38 | - controller/plot.py
39 | - p4src
40 | - p4src/includes
41 | - p4src/includes/checksum.p4
42 | - p4src/includes/defines.p4
43 | - p4src/includes/headers.p4
44 | - p4src/includes/parsers.p4
45 | - p4src/netchain.p4
46 | - p4src/routing.p4
47 | - logs
48 | - .gitignore
49 | - README.md
50 | 51 | ## 3. How to run
52 | - Install required software and dependency for P4 (P4-14).
53 | - Make sure the directory informations are correct in 54 | > controller/config/config.xml.
55 | - Go to directory controller, you can simply run program in normal mode: 56 | > sudo python run_test.py normal. 57 | - Or you can run program in failure recovery mode: 58 | > sudo python run_test.py failure. 59 | - The program produces results for failure recovery at logs/. The format of the files is 60 | > [number of virtual groups].tmp_[read/write]_[send/receive].log. 61 | - You can draw figures by running: 62 | > python plot.py [number of virtual groups]. 63 | -------------------------------------------------------------------------------- /client/nc_socket.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | import time 4 | import thread 5 | import sys 6 | import os 7 | lib_path = os.path.abspath(os.path.join('../controller')) 8 | sys.path.append(lib_path) 9 | from nc_config import * 10 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 11 | 12 | counter = 0 13 | def counting(): 14 | last_counter = 0 15 | while True: 16 | print (counter - last_counter), counter 17 | sys.stdout.flush() 18 | last_counter = counter 19 | time.sleep(1) 20 | thread.start_new_thread(counting, ()) 21 | 22 | def get_chain_of_key(key, chains): 23 | for i in chains: 24 | if key in ENTRY[i]: 25 | return chains[i] 26 | return [] 27 | 28 | def read_req(chains, num_kv, size_vgroup): 29 | global counter 30 | value = 0 31 | request_type = NC_READ_REQUEST 32 | 33 | op_field = struct.pack("B", request_type) 34 | sc = 4 35 | sc_field = struct.pack("B", sc) 36 | seq = 0 37 | seq_field = struct.pack("H", seq) 38 | 39 | index = 0 40 | while (1): 41 | key = ENTRY[1][index] 42 | value = 0 43 | 44 | vgroup = (key - 2018) / (num_kv/size_vgroup) 45 | vgroup_field = struct.pack(">H", vgroup) 46 | 47 | chain = get_chain_of_key(key, chains) 48 | ipx = [[] for i in range(len(chain))] 49 | for i in range(len(chain)): 50 | ipx[i] = [int(j) for j in chain[i].strip().split('.')] 51 | 52 | s0_field = struct.pack("BBBB", ipx[0][0], ipx[0][1], ipx[0][2], ipx[0][3]) 53 | 54 | s1_field = struct.pack("BBBB", ipx[1][0], ipx[1][1], ipx[1][2], ipx[1][3]) 55 | 56 | s2_field = struct.pack("BBBB", ipx[2][0], ipx[2][1], ipx[2][2], ipx[2][3]) 57 | 58 | end_field = struct.pack("BBBB", 0,0,0,0) 59 | 60 | key_highBytes = key >> 64 61 | key_lowBytes = key ^ (key_highBytes << 64) 62 | key_field = struct.pack(">Q", key_highBytes) + struct.pack(">Q", key_lowBytes) 63 | 64 | value_highBytes = value >> 64 65 | value_lowBytes = value ^ (value_highBytes << 64) 66 | value_field = struct.pack(">Q", value_highBytes) + struct.pack(">Q", value_lowBytes) 67 | 68 | packet_to_send = s2_field + s1_field + s0_field + end_field + op_field + sc_field + seq_field + key_field + value_field + vgroup_field 69 | 70 | read_ip = chain[len(chain) - 1] 71 | #print "send out." 72 | s.sendto(packet_to_send, (read_ip, NC_PORT)) 73 | counter = counter + 1 74 | time.sleep(0.01) 75 | index = (index + 1) % num_kv 76 | def write_req(chains, num_kv, size_vgroup): 77 | global counter 78 | request_type = NC_WRITE_REQUEST 79 | op_field = struct.pack("B", request_type) 80 | sc = 4 81 | sc_field = struct.pack("B", sc) 82 | seq = 0 83 | seq_field = struct.pack("H", seq) 84 | 85 | index = 0 86 | while (1): 87 | key = ENTRY[1][index] 88 | value = 222 89 | 90 | vgroup = (key - 2018) / (num_kv/size_vgroup) 91 | vgroup_field = struct.pack(">H", vgroup) 92 | 93 | chain = get_chain_of_key(key, chains) 94 | ipx = [[] for i in range(len(chain))] 95 | for i in range(len(chain)): 96 | ipx[i] = [int(j) for j in chain[i].strip().split('.')] 97 | 98 | s0_field = struct.pack("BBBB", ipx[0][0], ipx[0][1], ipx[0][2], ipx[0][3]) 99 | 100 | s1_field = struct.pack("BBBB", ipx[1][0], ipx[1][1], ipx[1][2], ipx[1][3]) 101 | 102 | s2_field = struct.pack("BBBB", ipx[2][0], ipx[2][1], ipx[2][2], ipx[2][3]) 103 | 104 | end_field = struct.pack("BBBB", 0,0,0,0) 105 | 106 | key_highBytes = key >> 64 107 | key_lowBytes = key ^ (key_highBytes << 64) 108 | key_field = struct.pack(">Q", key_highBytes) + struct.pack(">Q", key_lowBytes) 109 | 110 | value_highBytes = value >> 64 111 | value_lowBytes = value ^ (value_highBytes << 64) 112 | value_field = struct.pack(">Q", value_highBytes) + struct.pack(">Q", value_lowBytes) 113 | 114 | packet = s0_field + s1_field + s2_field + end_field + op_field + sc_field + seq_field + key_field + value_field + vgroup_field 115 | write_ip = chain[0] 116 | 117 | s.sendto(packet, (write_ip, NC_PORT)) 118 | counter = counter + 1 119 | time.sleep(0.01) 120 | index = (index + 1) % num_kv 121 | 122 | if __name__ == "__main__": 123 | op = sys.argv[1] 124 | if (op == "read"): 125 | vnode_num = int(sys.argv[2]) 126 | vring_file = sys.argv[3] 127 | elif (op == "write"): 128 | vnode_num = int(sys.argv[2]) 129 | vring_file = sys.argv[3] 130 | num_kv = int(sys.argv[4]) 131 | size_vgroup = int(sys.argv[5]) 132 | ## Set vring... 133 | chains = {} 134 | with open(vring_file, "r") as f: 135 | for i in range(vnode_num): 136 | line = f.readline().split() 137 | chains[int(line[0])] = line[1:] 138 | 139 | if (op == "read"): 140 | read_req(chains, num_kv, size_vgroup) 141 | elif (op == "write"): 142 | write_req(chains, num_kv, size_vgroup) -------------------------------------------------------------------------------- /client/receiver.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | import time 4 | import thread 5 | import sys 6 | import os 7 | lib_path = os.path.abspath(os.path.join('../controller')) 8 | sys.path.append(lib_path) 9 | from nc_config import * 10 | 11 | rs = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 12 | rs.bind((sys.argv[1], REPLY_PORT)) 13 | 14 | counter = 0 15 | def counting(): 16 | last_counter = 0 17 | while True: 18 | print (counter - last_counter), counter 19 | sys.stdout.flush() 20 | last_counter = counter 21 | time.sleep(1) 22 | thread.start_new_thread(counting, ()) 23 | 24 | def main(): 25 | global counter 26 | last_count = 0 27 | current_time = time.time() 28 | while (1): 29 | packet_received, addr = rs.recvfrom(2048) 30 | op_field = int(struct.unpack("B", packet_received[0])[0]) 31 | counter = counter + 1 32 | continue 33 | 34 | if __name__ == "__main__": 35 | main() -------------------------------------------------------------------------------- /client/set_arp.sh: -------------------------------------------------------------------------------- 1 | arp -s 10.0.100.3 aa:bb:cc:dd:ee:31 2 | arp -s 10.0.100.2 aa:bb:cc:dd:ee:21 3 | arp -s 10.0.100.1 aa:bb:cc:dd:ee:11 4 | arp -s 10.0.100.4 aa:bb:cc:dd:ee:41 5 | echo "ARP set." -------------------------------------------------------------------------------- /controller/config/commands.txt: -------------------------------------------------------------------------------- 1 | table_add ipv4_route set_egress 10.0.0.1 => 1 2 | table_add ipv4_route set_egress 10.0.0.2 => 1 3 | table_add ipv4_route set_egress 10.0.100.2 => 2 4 | table_add ipv4_route set_egress 10.0.100.3 => 1 5 | table_add ipv4_route set_egress 10.0.100.4 => 1 6 | table_add ethernet_set_mac ethernet_set_mac_act 1 => aa:bb:cc:dd:ee:11 aa:bb:cc:dd:ee:43 7 | table_add ethernet_set_mac ethernet_set_mac_act 2 => aa:bb:cc:dd:ee:12 aa:bb:cc:dd:ee:22 8 | table_add gen_reply gen_reply_act 10 => 11 9 | table_add gen_reply gen_reply_act 12 => 13 10 | table_set_default get_sequence get_sequence_act 11 | table_set_default maintain_sequence maintain_sequence_act 12 | table_set_default read_value read_value_act 13 | table_set_default assign_value assign_value_act 14 | table_set_default pop_chain pop_chain_act 15 | table_set_default drop_packet drop_packet_act 16 | table_set_default get_next_hop get_next_hop_act 17 | table_set_default failure_recovery nop -------------------------------------------------------------------------------- /controller/config/commands_1.txt: -------------------------------------------------------------------------------- 1 | table_add ipv4_route set_egress 10.0.0.1 => 1 2 | table_add ipv4_route set_egress 10.0.0.2 => 1 3 | table_add ipv4_route set_egress 10.0.100.1 => 2 4 | table_add ipv4_route set_egress 10.0.100.3 => 3 5 | table_add ipv4_route set_egress 10.0.100.4 => 1 6 | table_add ethernet_set_mac ethernet_set_mac_act 1 => aa:bb:cc:dd:ee:21 aa:bb:cc:dd:ee:44 7 | table_add ethernet_set_mac ethernet_set_mac_act 2 => aa:bb:cc:dd:ee:22 aa:bb:cc:dd:ee:12 8 | table_add ethernet_set_mac ethernet_set_mac_act 3 => aa:bb:cc:dd:ee:23 aa:bb:cc:dd:ee:32 9 | table_add gen_reply gen_reply_act 10 => 11 10 | table_add gen_reply gen_reply_act 12 => 13 11 | table_set_default get_sequence get_sequence_act 12 | table_set_default maintain_sequence maintain_sequence_act 13 | table_set_default read_value read_value_act 14 | table_set_default assign_value assign_value_act 15 | table_set_default pop_chain pop_chain_act 16 | table_set_default drop_packet drop_packet_act 17 | table_set_default get_next_hop get_next_hop_act 18 | table_set_default failure_recovery nop -------------------------------------------------------------------------------- /controller/config/commands_2.txt: -------------------------------------------------------------------------------- 1 | table_add ipv4_route set_egress 10.0.0.1 => 1 2 | table_add ipv4_route set_egress 10.0.0.2 => 1 3 | table_add ipv4_route set_egress 10.0.100.1 => 1 4 | table_add ipv4_route set_egress 10.0.100.2 => 2 5 | table_add ipv4_route set_egress 10.0.100.4 => 1 6 | table_add ethernet_set_mac ethernet_set_mac_act 1 => aa:bb:cc:dd:ee:31 aa:bb:cc:dd:ee:45 7 | table_add ethernet_set_mac ethernet_set_mac_act 2 => aa:bb:cc:dd:ee:32 aa:bb:cc:dd:ee:23 8 | table_add gen_reply gen_reply_act 10 => 11 9 | table_add gen_reply gen_reply_act 12 => 13 10 | table_set_default get_sequence get_sequence_act 11 | table_set_default maintain_sequence maintain_sequence_act 12 | table_set_default read_value read_value_act 13 | table_set_default assign_value assign_value_act 14 | table_set_default pop_chain pop_chain_act 15 | table_set_default drop_packet drop_packet_act 16 | table_set_default get_next_hop get_next_hop_act 17 | table_set_default pop_chain_again pop_chain_act 18 | table_set_default failure_recovery nop -------------------------------------------------------------------------------- /controller/config/commands_3.txt: -------------------------------------------------------------------------------- 1 | table_add ipv4_route set_egress 10.0.0.1 => 1 2 | table_add ipv4_route set_egress 10.0.0.2 => 2 3 | table_add ipv4_route set_egress 10.0.100.1 => 3 4 | table_add ipv4_route set_egress 10.0.100.2 => 4 5 | table_add ipv4_route set_egress 10.0.100.3 => 5 6 | table_add ipv4_route set_egress 10.0.100.4 => 4 7 | table_add ethernet_set_mac ethernet_set_mac_act 1 => aa:bb:cc:dd:ee:41 aa:bb:cc:dd:ee:01 8 | table_add ethernet_set_mac ethernet_set_mac_act 2 => aa:bb:cc:dd:ee:42 aa:bb:cc:dd:ee:02 9 | table_add ethernet_set_mac ethernet_set_mac_act 3 => aa:bb:cc:dd:ee:43 aa:bb:cc:dd:ee:11 10 | table_add ethernet_set_mac ethernet_set_mac_act 4 => aa:bb:cc:dd:ee:44 aa:bb:cc:dd:ee:21 11 | table_add ethernet_set_mac ethernet_set_mac_act 5 => aa:bb:cc:dd:ee:45 aa:bb:cc:dd:ee:31 12 | table_add gen_reply gen_reply_act 10 => 11 13 | table_add gen_reply gen_reply_act 12 => 13 14 | table_set_default get_sequence get_sequence_act 15 | table_set_default maintain_sequence maintain_sequence_act 16 | table_set_default read_value read_value_act 17 | table_set_default assign_value assign_value_act 18 | table_set_default pop_chain pop_chain_act 19 | table_set_default drop_packet drop_packet_act 20 | table_set_default get_next_hop get_next_hop_act 21 | table_set_default failure_recovery nop -------------------------------------------------------------------------------- /controller/config/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | /home/user/zhuolong/p4/netchain 4 | /home/user/zhuolong/p4/bmv2 5 | /home/user/zhuolong/p4/p4c-bmv2 6 | 7 | controller/config/topo.txt 8 | controller/config/vring.txt 9 | 10 | 1 11 | 3 12 | 120 13 | 20 14 | 22222 15 | controller/config/switch.json 16 | p4src/netchain.p4 17 | targets/simple_switch/sswitch_CLI 18 | 10 19 | -------------------------------------------------------------------------------- /controller/config/topo.txt: -------------------------------------------------------------------------------- 1 | switches 4 2 | hosts 2 3 | h1 s4 4 | h2 s4 5 | s1 s4 6 | s1 s2 7 | s2 s4 8 | s3 s4 9 | s2 s3 -------------------------------------------------------------------------------- /controller/config/vring.txt: -------------------------------------------------------------------------------- 1 | 1 10.0.100.1 10.0.100.2 10.0.100.3 2 | 2 10.0.100.1 10.0.100.4 10.0.100.4 -------------------------------------------------------------------------------- /controller/exe_cmd.py: -------------------------------------------------------------------------------- 1 | import subprocess, os 2 | 3 | def exe_cmd(cmd): 4 | subprocess.call(cmd, shell=True) 5 | return 6 | 7 | def send_cmd_to_port(parameters, cmd_to_send, dst_port, extra_cmd, interprete = False): 8 | file_to_send = "tmp_send_cmd.txt" 9 | fout = open(file_to_send, "w") 10 | fout.write(cmd_to_send) 11 | fout.close() 12 | 13 | st = "%s --thrift-port " % parameters.runtime_CLI + str(dst_port) + " < tmp_send_cmd.txt" + extra_cmd 14 | return_value = os.popen(st) 15 | return return_value 16 | 17 | def send_cmd_to_port_noreply(parameters, cmd_to_send, dst_port): 18 | file_to_send = "tmp_send_cmd_noreply.txt" 19 | fout = open(file_to_send, "w") 20 | fout.write(cmd_to_send) 21 | fout.close() 22 | 23 | cmd = [parameters.runtime_CLI, parameters.switch_json, str(dst_port)] 24 | with open(file_to_send, "r") as f: 25 | try: 26 | output = subprocess.check_output(cmd, stdin = f) 27 | except subprocess.CalledProcessError as e: 28 | print e 29 | print e.output -------------------------------------------------------------------------------- /controller/fail_recovery.py: -------------------------------------------------------------------------------- 1 | from nc_config import * 2 | from exe_cmd import * 3 | 4 | def failover(parameters, fail_switch_id, chains): 5 | fail_ip = IP_PREFIX + str(fail_switch_id + 1) 6 | fail_sw_port = THRIFT_PORT_OF_SWITCH[fail_switch_id] 7 | for i in chains: 8 | chain = chains[i] 9 | pos = chain.index(fail_ip) 10 | if (pos == 0): ## if the failed switch is a head switch in the chain, get a new head 11 | newhead_ip = chain[pos + 1] 12 | newhead_sw_id = int(newhead_ip.strip().split('.')[3]) - 1 13 | newhead_sw_port = THRIFT_PORT_OF_SWITCH[newhead_sw_id] 14 | 15 | table_dump_getAddress_cmd = "table_dump get_my_address" 16 | extra_process_cmd_1 = " | grep 'Action entry' | awk -F ['-']+ '{print $2}'" 17 | action_pm = send_cmd_to_port(parameters, table_dump_getAddress_cmd, fail_sw_port, extra_process_cmd_1) 18 | 19 | extra_process_cmd_2 = " | grep 'Dumping entry' | awk -F [' ']+ '{print $3}'" 20 | table_entry = send_cmd_to_port(parameters, table_dump_getAddress_cmd, fail_sw_port, extra_process_cmd_2) 21 | 22 | extra_process_cmd_3 = " | grep 'nc_hdr' | awk -F [' ']+ '{print $5}'" 23 | match_key = send_cmd_to_port(parameters, table_dump_getAddress_cmd, fail_sw_port, extra_process_cmd_3) 24 | 25 | for j in range(len(match_key)): 26 | if match_key[j] in ENTRY[i]: 27 | 28 | table_modify_getAddress_cmd = "table_modify get_my_address get_my_address_act " + str(int(table_entry[j].strip, 16)) + " " + newhead_ip + " " + str(HEAD_NODE) 29 | extra_process_cmd = "" 30 | send_cmd_to_port(parameters, table_modify_getAddress_cmd, newhead_sw_port, extra_process_cmd) 31 | elif (pos == len(chain) - 1): ## if the failed switch is a tail switch in the chain 32 | neighbor_ip = chain[pos - 1] 33 | neighbor_sw_id = int(neighbor_ip.strip().split('.')[3]) - 1 34 | neighbor_sw_port = THRIFT_PORT_OF_SWITCH[neighbor_sw_id] 35 | 36 | table_add_failover_cmd = "table_add failure_recovery failover_write_reply_act %s&&&255.255.255.255 0.0.0.0&&&0.0.0.0 0&&&0 => 3" % fail_ip 37 | extra_process_cmd = "" 38 | send_cmd_to_port_noreply(parameters, table_add_failover_cmd, neighbor_sw_port) 39 | else: ## if the failed switch is a middle switch in the chain 40 | neighbor_ip = chain[pos - 1] 41 | neighbor_sw_id = int(neighbor_ip.strip().split('.')[3]) - 1 42 | neighbor_sw_port = THRIFT_PORT_OF_SWITCH[neighbor_sw_id] 43 | 44 | table_add_failover_cmd = "table_add failure_recovery failover_act %s&&&255.255.255.255 0.0.0.0&&&0.0.0.0 0&&&0 => 3" % fail_ip 45 | extra_process_cmd = "" 46 | send_cmd_to_port_noreply(parameters, table_add_failover_cmd, neighbor_sw_port) 47 | 48 | return 49 | 50 | def disable_read_write(parameters, neighbor_sw_port, fail_ip, v_group_id): 51 | table_add_failrecovery_cmd = "table_add failure_recovery drop_packet_act %s&&&255.255.255.255 0.0.0.0&&&0.0.0.0 %d&&&255 => 2" % (fail_ip, v_group_id) 52 | extra_process_cmd = "" 53 | send_cmd_to_port_noreply(parameters, table_add_failrecovery_cmd, neighbor_sw_port) 54 | return 55 | 56 | 57 | def enable_read_write(parameters, neighbor_sw_port, fail_ip, backup_ip, v_group_id): 58 | 59 | table_add_failrecovery_cmd = "table_add failure_recovery failure_recovery_act %s&&&255.255.255.255 0.0.0.0&&&0.0.0.0 %d&&&255 => %s 1" % (fail_ip, v_group_id, backup_ip) 60 | extra_process_cmd = "" 61 | send_cmd_to_port_noreply(parameters, table_add_failrecovery_cmd, neighbor_sw_port) 62 | return 63 | 64 | def failure_recovery(parameters, fail_switch_id, chains): 65 | fail_ip = IP_PREFIX + str(fail_switch_id + 1) 66 | backup_sw_id = BACKUP_SW_ID 67 | backup_ip = IP_PREFIX + str(backup_sw_id + 1) 68 | backup_sw_port = THRIFT_PORT_OF_SWITCH[backup_sw_id] 69 | 70 | vgroup = [[] for v_group_id in range(parameters.size_vgroup)] 71 | s_key = 2018 72 | for v_group_id in range(parameters.size_vgroup): 73 | count_key = 0 74 | while count_key " + backup_ip + " " + str(role) 123 | vgroup_cmd = vgroup_cmd + table_add_getAddress_cmd + "\n" 124 | 125 | 126 | # copy table find_index 127 | tmp_index_group = [] 128 | for j in range(len(match_key_findindex)): 129 | match_key_j = int(match_key_findindex[j], 16) 130 | action_j = int(action_pm_findindex[j], 16) 131 | 132 | if (match_key_j in vgroup[v_group_id]): 133 | tmp_index_group.append(action_j) 134 | table_add_findindex_cmd = "table_add find_index find_index_act " + str(match_key_j) + " => " + str(action_j) 135 | vgroup_cmd = vgroup_cmd + table_add_findindex_cmd + "\n" 136 | # copy sequence register 137 | for j in tmp_index_group: 138 | register_read_sequence_cmd = "register_read sequence_reg " + str(j) 139 | extra_process_cmd = " | grep 'sequence_reg' | awk -F [' ']+ '{print $3}'" 140 | register_sequence = send_cmd_to_port(parameters, register_read_sequence_cmd, target_sw_port, extra_process_cmd).readlines() 141 | if (register_sequence): 142 | register_write_sequence_cmd = "register_write sequence_reg " + str(j) + " " + register_sequence[0].strip('\n') 143 | vgroup_cmd = vgroup_cmd + register_write_sequence_cmd + "\n" 144 | #copy value register 145 | for j in tmp_index_group: 146 | register_read_value_cmd = "register_read value_reg " + str(j) 147 | extra_process_cmd = " | grep 'value_reg' | awk -F [' ']+ '{print $3}'" 148 | register_value = send_cmd_to_port(parameters, register_read_value_cmd, target_sw_port, extra_process_cmd).readlines() 149 | if (register_value): 150 | register_write_value_cmd = "register_write value_reg " + str(j) + " " + register_value[0].strip('\n') 151 | vgroup_cmd = vgroup_cmd + register_write_value_cmd + "\n" 152 | send_cmd_to_port_noreply(parameters, vgroup_cmd, backup_sw_port) 153 | enable_read_write(parameters, neighbor_sw_port, fail_ip, backup_ip, v_group_id) 154 | return 155 | -------------------------------------------------------------------------------- /controller/nc_config.py: -------------------------------------------------------------------------------- 1 | NC_READ_REQUEST = 10 2 | NC_READ_REPLY = 11 3 | NC_WRITE_REQUEST = 12 4 | NC_WRITE_REPLY = 13 5 | NC_PORT = 8888 6 | REPLY_PORT = 8889 7 | HEAD_NODE = 100 8 | REPLICA_NODE = 101 9 | TAIL_NODE = 102 10 | TENSECONDS = 10 11 | IP_PREFIX = "10.0.100." 12 | BACKUP_SW_ID = 3 13 | FAIL_SW_ID = 1 14 | ENTRY = {1: [(2018+i) for i in range(200)]} 15 | THRIFT_PORT_OF_SWITCH = [22222, 22223, 22224, 22225] -------------------------------------------------------------------------------- /controller/p4_mininet.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013-present Barefoot Networks, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | 16 | from mininet.net import Mininet 17 | from mininet.node import Switch, Host 18 | from mininet.log import setLogLevel, info, error, debug 19 | from mininet.moduledeps import pathCheck 20 | from sys import exit 21 | import os 22 | import tempfile 23 | import socket 24 | 25 | class P4Host(Host): 26 | def config(self, **params): 27 | r = super(Host, self).config(**params) 28 | 29 | self.defaultIntf().rename("eth0") 30 | 31 | for off in ["rx", "tx", "sg"]: 32 | cmd = "/sbin/ethtool --offload eth0 %s off" % off 33 | self.cmd(cmd) 34 | 35 | # disable IPv6 36 | self.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") 37 | self.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") 38 | self.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") 39 | 40 | return r 41 | 42 | def describe(self): 43 | print "**********" 44 | print self.name 45 | print "default interface: %s\t%s\t%s" %( 46 | self.defaultIntf().name, 47 | self.defaultIntf().IP(), 48 | self.defaultIntf().MAC() 49 | ) 50 | print "**********" 51 | 52 | class P4Switch(Switch): 53 | """P4 virtual switch""" 54 | device_id = 0 55 | 56 | def __init__(self, name, sw_path = None, json_path = None, 57 | thrift_port = None, 58 | pcap_dump = False, 59 | log_console = True, 60 | verbose = False, 61 | device_id = None, 62 | enable_debugger = False, 63 | **kwargs): 64 | Switch.__init__(self, name, **kwargs) 65 | assert(sw_path) 66 | assert(json_path) 67 | # make sure that the provided sw_path is valid 68 | pathCheck(sw_path) 69 | # make sure that the provided JSON file exists 70 | if not os.path.isfile(json_path): 71 | error("Invalid JSON file.\n") 72 | exit(1) 73 | self.sw_path = sw_path 74 | self.json_path = json_path 75 | self.verbose = verbose 76 | logfile = "/tmp/p4s.{}.log".format(self.name) 77 | self.output = open(logfile, 'w') 78 | self.thrift_port = thrift_port 79 | self.pcap_dump = pcap_dump 80 | self.enable_debugger = enable_debugger 81 | self.log_console = log_console 82 | if device_id is not None: 83 | self.device_id = device_id 84 | P4Switch.device_id = max(P4Switch.device_id, device_id) 85 | else: 86 | self.device_id = P4Switch.device_id 87 | P4Switch.device_id += 1 88 | self.nanomsg = "ipc:///tmp/bm-{}-log.ipc".format(self.device_id) 89 | 90 | @classmethod 91 | def setup(cls): 92 | pass 93 | 94 | def check_switch_started(self, pid): 95 | """While the process is running (pid exists), we check if the Thrift 96 | server has been started. If the Thrift server is ready, we assume that 97 | the switch was started successfully. This is only reliable if the Thrift 98 | server is started at the end of the init process""" 99 | while True: 100 | if not os.path.exists(os.path.join("/proc", str(pid))): 101 | return False 102 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 103 | sock.settimeout(0.5) 104 | result = sock.connect_ex(("localhost", self.thrift_port)) 105 | if result == 0: 106 | return True 107 | 108 | def start(self, controllers): 109 | "Start up a new P4 switch" 110 | info("Starting P4 switch {}.\n".format(self.name)) 111 | args = [self.sw_path] 112 | for port, intf in self.intfs.items(): 113 | if not intf.IP(): 114 | args.extend(['-i', str(port) + "@" + intf.name]) 115 | if self.pcap_dump: 116 | args.append("--pcap") 117 | # args.append("--useFiles") 118 | if self.thrift_port: 119 | args.extend(['--thrift-port', str(self.thrift_port)]) 120 | if self.nanomsg: 121 | args.extend(['--nanolog', self.nanomsg]) 122 | args.extend(['--device-id', str(self.device_id)]) 123 | P4Switch.device_id += 1 124 | args.append(self.json_path) 125 | if self.enable_debugger: 126 | args.append("--debugger") 127 | 128 | logfile = "p4s.{}.log".format(self.name) 129 | if self.log_console: 130 | #args.append("--log-console") 131 | args.append("--log-file "+logfile) 132 | info(' '.join(args) + "\n") 133 | 134 | pid = None 135 | with tempfile.NamedTemporaryFile() as f: 136 | # self.cmd(' '.join(args) + ' > /dev/null 2>&1 &') 137 | self.cmd(' '.join(args) + ' 2>&1 & echo $! >> ' + f.name) 138 | pid = int(f.read()) 139 | debug("P4 switch {} PID is {}.\n".format(self.name, pid)) 140 | if not self.check_switch_started(pid): 141 | error("P4 switch {} did not start correctly.\n".format(self.name)) 142 | exit(1) 143 | info("P4 switch {} has been started.\n".format(self.name)) 144 | 145 | def stop(self): 146 | "Terminate P4 switch." 147 | self.output.flush() 148 | self.cmd('kill %' + self.sw_path) 149 | self.cmd('wait') 150 | self.deleteIntfs() 151 | 152 | def attach(self, intf): 153 | "Connect a data port" 154 | assert(0) 155 | 156 | def detach(self, intf): 157 | "Disconnect a data port" 158 | assert(0) 159 | -------------------------------------------------------------------------------- /controller/plot.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | def draw(num_vgroup, plot_id): 6 | plt.figure(plot_id) 7 | plt.ylim(0.0, 210.0) 8 | 9 | fin = open("../logs/"+num_vgroup+".tmp_write_receive.log", "r") 10 | count = 0 11 | y = [] 12 | for line in fin.readlines(): 13 | s_line = line.strip('\n').split() 14 | count = count + 1 15 | y.append(int(s_line[0])) 16 | fin.close 17 | fin = open("../logs/"+num_vgroup+".tmp_read_receive.log", "r") 18 | count = 0 19 | for line in fin.readlines(): 20 | s_line = line.strip('\n').split() 21 | if (count < len(y)): 22 | y[count] = y[count] + int(s_line[0]) 23 | count = count + 1 24 | 25 | 26 | x = range(1,len(y)-1) 27 | y = y[1:len(y)-1] 28 | print x 29 | print y 30 | plt.plot(x,y) 31 | plt.title(str(num_vgroup)+" Virtual Group") 32 | plt.xlabel('Time (s)') 33 | plt.ylabel('Throughput (QPS)') 34 | 35 | def usage(): 36 | print "Usage:" 37 | print " python plot.py [number of virtual groups] (e.g. python plot.py 20)" 38 | return 39 | 40 | if __name__ == "__main__": 41 | if (len(sys.argv) != 2): 42 | usage() 43 | quit() 44 | draw(sys.argv[1], 1) 45 | plt.show() -------------------------------------------------------------------------------- /controller/run_test.py: -------------------------------------------------------------------------------- 1 | import os, logging, sys, subprocess, argparse, time 2 | import xml.etree.ElementTree as xmlparse 3 | from nc_config import * 4 | from exe_cmd import * 5 | from topo import * 6 | from fail_recovery import * 7 | 8 | 9 | ########################################### 10 | ## get parameters 11 | ########################################### 12 | class Parameters: 13 | def __init__(self, config_file): 14 | root_dir = xmlparse.parse(config_file).getroot() 15 | self.config_file = config_file 16 | self.project_dir = root_dir.find("projectDir").text 17 | self.topology_file = self.project_dir + "/" + root_dir.find("topology").find("topologyFileName").text 18 | self.thrift_base_port = int(root_dir.find("thriftBasePort").text) 19 | self.bmv2 = root_dir.find("bmv2").text 20 | self.p4c_bmv2 = root_dir.find("p4cBmv2").text 21 | self.switch_json = self.project_dir + "/" + root_dir.find("switchJson").text 22 | self.num_vnode = int(root_dir.find("numVirtualNode").text) 23 | self.num_replica = int(root_dir.find("numReplica").text) 24 | self.num_kv = int(root_dir.find("numKeyValue").text) 25 | self.switch_p4 = self.project_dir + "/" + root_dir.find("p4src").text 26 | self.runtime_CLI = root_dir.find("bmv2").text + "/" + root_dir.find("runtimeCLI").text 27 | self.vring_file = self.project_dir + "/" + root_dir.find("topology").find("vringFileName").text 28 | self.register_size = int(root_dir.find("registerSize").text) 29 | self.size_vgroup = int(root_dir.find("sizeVirtualGroup").text) 30 | 31 | def compile_p4_switch(parameters): 32 | logging.info("Generate switch json...") 33 | exe_cmd("%s/p4c_bm/__main__.py %s --json %s" % (parameters.p4c_bmv2, parameters.switch_p4, parameters.switch_json)) 34 | return 35 | 36 | def compile_all(parameters): 37 | compile_p4_switch(parameters) 38 | return 39 | 40 | 41 | def run(parameters): 42 | compile_all(parameters) 43 | logging.info("Warm up...") 44 | exe_cmd("%s/targets/simple_switch/simple_switch > /dev/null 2>&1" % parameters.bmv2) 45 | exe_cmd("mkdir -p %s/logs/switches" % parameters.project_dir) 46 | 47 | logging.info("Start mininet...") 48 | (switches, hosts, net) = config_mininet(parameters) 49 | 50 | logging.info("Get chain informations...") 51 | chains = {} 52 | with open(parameters.vring_file, "r") as f: 53 | for i in range(parameters.num_vnode): 54 | line = f.readline().split() 55 | chains[int(line[0])] = line[1:] 56 | 57 | return (switches, hosts, net, chains) 58 | 59 | def clean(): 60 | print "Clean environment..." 61 | exe_cmd("ps -ef | grep nc_socket.py | grep -v grep | awk '{print $2}' | xargs kill -9") 62 | exe_cmd("ps -ef | grep NetKVController.jar | grep -v grep | awk '{print $2}' | xargs kill -9") 63 | exe_cmd("ps -ef | grep tcpdump | grep -v grep | awk '{print $2}' | xargs kill -9") 64 | exe_cmd("ps -ef | grep dist_txn | grep -v grep | awk '{print $2}' | xargs kill -9") 65 | exe_cmd("rm -f *.pcap") 66 | exe_cmd("rm -f *.out *.pyc") 67 | exe_cmd("rm -f *.log.txt *.log.*.txt") 68 | exe_cmd("rm -f tmp_send_cmd_noreply.txt tmp_send_cmd.txt") 69 | exe_cmd("killall lt-simple_switch >/dev/null 2>&1") 70 | exe_cmd("mn -c >/dev/null 2>&1") 71 | #exe_cmd("ps -ef | grep run.py | grep -v grep | awk '{print $2}' | xargs kill -9") 72 | exe_cmd("killall -9 redis-server > /dev/null 2>&1") 73 | exe_cmd("killall -9 redis_proxy > /dev/null 2>&1") 74 | exe_cmd("killall -9 cr_backend > /dev/null 2>&1") 75 | 76 | def init_flowtable(parameters): 77 | role = [100, 101, 102] 78 | for switch_id in range(3): 79 | switch_ip = IP_PREFIX + str(switch_id + 1) 80 | switch_port = THRIFT_PORT_OF_SWITCH[switch_id] 81 | logging.info(switch_port) 82 | init_cmd = "" 83 | for i in range(parameters.num_kv): 84 | key = ENTRY[1][i] 85 | table_add_getAddress_cmd = "table_add get_my_address get_my_address_act " + str(key) + " => " + switch_ip + " " + str(role[switch_id]) 86 | 87 | table_add_findindex_cmd = "table_add find_index find_index_act " + str(key) + " => " + str(i) 88 | 89 | register_write_value_cmd = "register_write value_reg " + str(i) + " " + str(i) 90 | init_cmd = init_cmd + table_add_getAddress_cmd + "\n" + table_add_findindex_cmd + "\n" + register_write_value_cmd + "\n" 91 | send_cmd_to_port_noreply(parameters, init_cmd, switch_port) 92 | 93 | 94 | def send_traffic(parameters, switches, hosts, net): 95 | read_host_id = 0 96 | read_host = net.get('h%d' % (read_host_id + 1)) 97 | read_host.sendCmd("sh %s/client/set_arp.sh" % (parameters.project_dir)) 98 | print read_host.waitOutput() 99 | read_host.sendCmd("python %s/client/receiver.py 10.0.0.1 > %s/logs/%d.tmp_read_receive.log & python %s/client/nc_socket.py read %d %s %d %d > %s/logs/tmp_read_send.log &" 100 | % (parameters.project_dir, parameters.project_dir, parameters.size_vgroup, parameters.project_dir, parameters.num_vnode, parameters.vring_file, parameters.num_kv, parameters.size_vgroup, parameters.project_dir)) 101 | write_host_id = 1 102 | write_host = net.get('h%d' % (write_host_id + 1)) 103 | write_host.sendCmd("sh %s/client/set_arp.sh" % (parameters.project_dir)) 104 | print write_host.waitOutput() 105 | write_host.sendCmd("python %s/client/receiver.py 10.0.0.2 > %s/logs/%d.tmp_write_receive.log & python %s/client/nc_socket.py write %d %s %d %d > %s/logs/tmp_write_send.log &" 106 | % (parameters.project_dir, parameters.project_dir, parameters.size_vgroup, parameters.project_dir, parameters.num_vnode, parameters.vring_file, parameters.num_kv, parameters.size_vgroup, parameters.project_dir)) 107 | 108 | return (read_host,write_host) 109 | 110 | def stop_switch(fail_switch_id, switches, hosts, net): 111 | fail_switch = net.get('s%d' % (fail_switch_id + 1)) 112 | fail_switch.stop() 113 | return 114 | 115 | 116 | ########################################### 117 | ## run test in normal case 118 | ########################################### 119 | def test_normal(parameters): 120 | (switches, hosts, net, chains) = run(parameters) 121 | init_flowtable(parameters) 122 | logging.info("Run for 60 seconds...") 123 | time.sleep(60) 124 | net.stop() 125 | clean() 126 | return 127 | 128 | ########################################### 129 | ## run test in failure case 130 | ########################################### 131 | def test_failure(parameters, fail_switch_id): 132 | ### install initial rules 133 | (switches, hosts, net, chains) = run(parameters) 134 | init_flowtable(parameters) 135 | ### start sending traffic on host0 136 | logging.info("Sending traffic...") 137 | (read_host, write_host) = send_traffic(parameters, switches, hosts, net) 138 | 139 | ### wait for 10 seconds 140 | logging.info("Wait for 10 seconds...") 141 | time.sleep(TENSECONDS) 142 | 143 | ### stop one switch 144 | logging.info("Stop a switch...") 145 | stop_switch(fail_switch_id, switches, hosts, net) 146 | 147 | ### wait for 10 seconds 148 | logging.info("Assume the failure is discovered in 0.5s...") 149 | time.sleep(0.5) 150 | 151 | ### update rules for fast failover 152 | logging.info("Start failover...") 153 | failover(parameters, fail_switch_id, chains) 154 | 155 | ### wait for 10 seconds 156 | logging.info("Wait for 10 seconds...") 157 | time.sleep(TENSECONDS) 158 | 159 | ### update rules for failure recovery 160 | logging.info("Start failrecovering...") 161 | failure_recovery(parameters, fail_switch_id, chains) 162 | 163 | ### wait for 10 seconds 164 | logging.info("Wait for 10 seconds...") 165 | time.sleep(TENSECONDS) 166 | 167 | ### clean environment 168 | read_host.monitor() 169 | write_host.monitor() 170 | net.stop() 171 | clean() 172 | return 173 | 174 | def usage(): 175 | print "Usage:" 176 | print " To run test in normal case: python run_test.py normal" 177 | print " To run test in failure case: python run_test.py failure" 178 | return 179 | 180 | if __name__ == "__main__": 181 | exe_cmd("rm -f *.log.txt") 182 | logging.basicConfig(level=logging.INFO) 183 | if (len(sys.argv) != 2): 184 | usage() 185 | quit() 186 | config_file = "config/config.xml" 187 | parameters = Parameters(config_file) 188 | if sys.argv[1] == "normal": 189 | test_normal(parameters) 190 | elif sys.argv[1] == "failure": 191 | test_failure(parameters, 1) 192 | else: 193 | usage() 194 | quit() 195 | -------------------------------------------------------------------------------- /controller/topo.py: -------------------------------------------------------------------------------- 1 | import os, logging, sys, subprocess, argparse, time 2 | 3 | from mininet.net import Mininet 4 | from mininet.topo import Topo 5 | from mininet.log import setLogLevel, info 6 | from mininet.cli import CLI 7 | from mininet.node import CPULimitedHost 8 | from mininet.link import TCLink 9 | 10 | from p4_mininet import P4Switch, P4Host 11 | from nc_config import * 12 | from exe_cmd import * 13 | 14 | 15 | ########################################### 16 | ## get parameters 17 | ########################################### 18 | class MyTopo(Topo): 19 | def __init__(self, sw_path, json_path, switches, thrift_base_port, 20 | pcap_dump_flag, log_dir, hosts, links, **opts): 21 | Topo.__init__(self, **opts) 22 | 23 | for i in xrange(switches): 24 | self.addSwitch("s%d" % (i+1), 25 | sw_path = sw_path, 26 | json_path = json_path, 27 | thrift_port = thrift_base_port+i, 28 | pcap_dump = pcap_dump_flag, 29 | device_id = i, 30 | verbose = True, 31 | log_dir = log_dir) 32 | 33 | for i in xrange(hosts): 34 | self.addHost("h%d" % (i+1)) 35 | 36 | for a,b in links: 37 | self.addLink(a, b) 38 | 39 | def read_topo(topology_file): 40 | nb_hosts = 0 41 | nb_switches = 0 42 | links = [] 43 | with open(topology_file, "r") as f: 44 | line = f.readline()[:-1] 45 | w, nb_switches = line.split() 46 | assert(w == "switches") 47 | line = f.readline()[:-1] 48 | w, nb_hosts = line.split() 49 | assert(w == "hosts") 50 | for line in f: 51 | if not f: break 52 | a, b = line.split() 53 | links.append( (a, b) ) 54 | return int(nb_switches), int(nb_hosts), links 55 | 56 | def config_mininet(parameters): 57 | switches, hosts, links = read_topo(parameters.topology_file) 58 | topo = MyTopo("%s/targets/simple_switch/simple_switch" % parameters.bmv2, 59 | parameters.switch_json, 60 | switches, 61 | parameters.thrift_base_port, 62 | False, 63 | parameters.project_dir + '/logs/switches', 64 | hosts, 65 | links) 66 | 67 | net = Mininet(topo = topo, 68 | host = P4Host, 69 | switch = P4Switch, 70 | controller = None, 71 | autoStaticArp=True ) 72 | 73 | net.start() 74 | 75 | for n in range(hosts): 76 | h = net.get('h%d' % (n + 1)) 77 | for off in ["rx", "tx", "sg"]: 78 | cmd = "/sbin/ethtool --offload eth0 %s off" % off 79 | print cmd 80 | h.cmd(cmd) 81 | print "disable ipv6" 82 | h.cmd("sysctl -w net.ipv6.conf.all.disable_ipv6=1") 83 | h.cmd("sysctl -w net.ipv6.conf.default.disable_ipv6=1") 84 | h.cmd("sysctl -w net.ipv6.conf.lo.disable_ipv6=1") 85 | h.cmd("sysctl -w net.ipv4.tcp_congestion_control=reno") 86 | h.cmd("iptables -I OUTPUT -p icmp --icmp-type destination-unreachable -j DROP") 87 | h.setIP("10.0.0.%d" % (n + 1)) 88 | h.setMAC("aa:bb:cc:dd:ee:0%d" % (n + 1)) 89 | for i in range(hosts): 90 | if (i != n): 91 | h.setARP("10.0.0.%d" % (i + 1), "aa:bb:cc:dd:ee:0%d" % (i + 1)) 92 | net.get('s1').setMAC("aa:bb:cc:dd:ee:11","s1-eth1") 93 | net.get('s1').setMAC("aa:bb:cc:dd:ee:12","s1-eth2") 94 | 95 | net.get('s2').setMAC("aa:bb:cc:dd:ee:21","s2-eth1") 96 | net.get('s2').setMAC("aa:bb:cc:dd:ee:22","s2-eth2") 97 | net.get('s2').setMAC("aa:bb:cc:dd:ee:23","s2-eth3") 98 | 99 | net.get('s3').setMAC("aa:bb:cc:dd:ee:31","s3-eth1") 100 | net.get('s3').setMAC("aa:bb:cc:dd:ee:32","s3-eth2") 101 | 102 | net.get('s4').setMAC("aa:bb:cc:dd:ee:41","s4-eth1") 103 | net.get('s4').setMAC("aa:bb:cc:dd:ee:42","s4-eth2") 104 | net.get('s4').setMAC("aa:bb:cc:dd:ee:43","s4-eth3") 105 | net.get('s4').setMAC("aa:bb:cc:dd:ee:44","s4-eth4") 106 | net.get('s4').setMAC("aa:bb:cc:dd:ee:45","s4-eth5") 107 | 108 | time.sleep(1) 109 | commands_list = ["config/commands.txt", "config/commands_1.txt", "config/commands_2.txt", "config/commands_3.txt"] 110 | file_index = 0 111 | for i in range(switches): 112 | cmd = [parameters.runtime_CLI, parameters.switch_json, str(parameters.thrift_base_port + i)] 113 | with open(commands_list[file_index], "r") as f: 114 | file_index = file_index + 1 115 | print " ".join(cmd) 116 | try: 117 | output = subprocess.check_output(cmd, stdin = f) 118 | print output 119 | except subprocess.CalledProcessError as e: 120 | print e 121 | print e.output 122 | 123 | time.sleep(1) 124 | 125 | logging.info("Ready !") 126 | 127 | return (switches, hosts, net) 128 | -------------------------------------------------------------------------------- /logs/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /p4src/includes/checksum.p4: -------------------------------------------------------------------------------- 1 | field_list ipv4_field_list { 2 | ipv4.version; 3 | ipv4.ihl; 4 | ipv4.diffserv; 5 | ipv4.totalLen; 6 | ipv4.identification; 7 | ipv4.flags; 8 | ipv4.fragOffset; 9 | ipv4.ttl; 10 | ipv4.protocol; 11 | ipv4.srcAddr; 12 | ipv4.dstAddr; 13 | } 14 | field_list_calculation ipv4_chksum_calc { 15 | input { 16 | ipv4_field_list; 17 | } 18 | algorithm : csum16; 19 | output_width: 16; 20 | } 21 | calculated_field ipv4.hdrChecksum { 22 | update ipv4_chksum_calc; 23 | } 24 | 25 | field_list udp_checksum_list { 26 | // IPv4 Pseudo Header Format. Must modify for IPv6 support. 27 | ipv4.srcAddr; 28 | ipv4.dstAddr; 29 | 8'0; 30 | ipv4.protocol; 31 | udp.len; 32 | udp.srcPort; 33 | udp.dstPort; 34 | udp.len; 35 | // udp.checksum; 36 | payload; 37 | } 38 | field_list_calculation udp_checksum { 39 | input { 40 | udp_checksum_list; 41 | } 42 | algorithm : csum16; 43 | output_width : 16; 44 | } 45 | calculated_field udp.checksum { 46 | update udp_checksum; 47 | } 48 | -------------------------------------------------------------------------------- /p4src/includes/defines.p4: -------------------------------------------------------------------------------- 1 | #define SEQUENCE_REG_SIZE 4096 2 | #define VALUE_REG_SIZE 4096 3 | 4 | #define NUM_CACHE 128 5 | #define NC_PORT 8888 6 | #define REPLY_PORT 8889 7 | #define NC_READ_REQUEST 10 8 | #define NC_READ_REPLY 11 9 | #define NC_WRITE_REQUEST 12 10 | #define NC_WRITE_REPLY 13 11 | #define DROP_PORT 9999 12 | #define END_OF_CHAIN 0 13 | #define MAX_LENGTH_OF_CHAIN 10 14 | #define HEAD_NODE 100 15 | #define REPLICA_NODE 101 16 | #define TAIL_NODE 102 17 | #define DROPPED 100 18 | -------------------------------------------------------------------------------- /p4src/includes/headers.p4: -------------------------------------------------------------------------------- 1 | header_type ethernet_t { 2 | fields { 3 | dstAddr : 48; 4 | srcAddr : 48; 5 | etherType : 16; 6 | } 7 | } 8 | header ethernet_t ethernet; 9 | 10 | header_type ipv4_t { 11 | fields { 12 | version : 4; 13 | ihl : 4; 14 | diffserv : 8; 15 | totalLen : 16; 16 | identification : 16; 17 | flags : 3; 18 | fragOffset : 13; 19 | ttl : 8; 20 | protocol : 8; 21 | hdrChecksum : 16; 22 | srcAddr : 32; 23 | dstAddr: 32; 24 | } 25 | } 26 | header ipv4_t ipv4; 27 | 28 | header_type tcp_t { 29 | fields { 30 | srcPort : 16; 31 | dstPort : 16; 32 | seqNo : 32; 33 | ackNo : 32; 34 | dataOffset : 4; 35 | res : 3; 36 | ecn : 3; 37 | ctrl : 6; 38 | window : 16; 39 | checksum : 16; 40 | urgentPtr : 16; 41 | } 42 | } 43 | header tcp_t tcp; 44 | 45 | header_type udp_t { 46 | fields { 47 | srcPort : 16; 48 | dstPort : 16; 49 | len : 16; 50 | checksum : 16; 51 | } 52 | } 53 | header udp_t udp; 54 | 55 | header_type overlay_t { 56 | fields { 57 | swip: 32; 58 | } 59 | } 60 | header overlay_t overlay [MAX_LENGTH_OF_CHAIN]; 61 | 62 | header_type nc_hdr_t { 63 | fields { 64 | op: 8; 65 | sc: 8; 66 | seq: 16; 67 | key: 128; 68 | value: 128; 69 | vgroup: 16; 70 | } 71 | 72 | } 73 | header nc_hdr_t nc_hdr; -------------------------------------------------------------------------------- /p4src/includes/parsers.p4: -------------------------------------------------------------------------------- 1 | parser start { 2 | return parse_ethernet; 3 | } 4 | 5 | #define ETHER_TYPE_IPV4 0x0800 6 | parser parse_ethernet { 7 | extract (ethernet); 8 | return select (latest.etherType) { 9 | ETHER_TYPE_IPV4: parse_ipv4; 10 | default: ingress; 11 | } 12 | } 13 | 14 | #define IPV4_PROTOCOL_TCP 6 15 | #define IPV4_PROTOCOL_UDP 17 16 | parser parse_ipv4 { 17 | extract(ipv4); 18 | return select (latest.protocol) { 19 | IPV4_PROTOCOL_TCP: parse_tcp; 20 | IPV4_PROTOCOL_UDP: parse_udp; 21 | default: ingress; 22 | } 23 | } 24 | 25 | parser parse_tcp { 26 | extract (tcp); 27 | return ingress; 28 | } 29 | 30 | parser parse_udp { 31 | extract (udp); 32 | return select (latest.dstPort) { 33 | NC_PORT: parse_overlay; 34 | REPLY_PORT: parse_overlay; 35 | default: ingress; 36 | } 37 | } 38 | 39 | parser parse_overlay { 40 | extract (overlay[next]); 41 | return select (latest.swip) { 42 | END_OF_CHAIN: parse_nc_hdr; 43 | default: parse_overlay; 44 | } 45 | } 46 | 47 | parser parse_nc_hdr { 48 | extract (nc_hdr); 49 | return select(latest.op) { 50 | NC_READ_REQUEST: ingress; 51 | NC_WRITE_REQUEST: ingress; 52 | default: ingress; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /p4src/netchain.p4: -------------------------------------------------------------------------------- 1 | #include "includes/defines.p4" 2 | #include "includes/headers.p4" 3 | #include "includes/parsers.p4" 4 | #include "includes/checksum.p4" 5 | #include "routing.p4" 6 | 7 | 8 | 9 | register sequence_reg { 10 | width: 16; 11 | instance_count: SEQUENCE_REG_SIZE; 12 | } 13 | 14 | register value_reg { 15 | width: 128; 16 | instance_count: VALUE_REG_SIZE; 17 | } 18 | 19 | 20 | header_type location_t { 21 | fields { 22 | index: 16; 23 | } 24 | } 25 | metadata location_t location; 26 | 27 | header_type sequence_md_t { 28 | fields { 29 | seq: 16; 30 | tmp: 16; 31 | } 32 | } 33 | metadata sequence_md_t sequence_md; 34 | 35 | header_type my_md_t { 36 | fields { 37 | ipaddress: 32; 38 | role: 16; 39 | failed: 16; 40 | } 41 | } 42 | metadata my_md_t my_md; 43 | 44 | header_type reply_addr_t { 45 | fields { 46 | ipv4_srcAddr: 32; 47 | ipv4_dstAddr: 32; 48 | } 49 | } 50 | metadata reply_addr_t reply_to_client_md; 51 | 52 | field_list rec_fl { 53 | standard_metadata; 54 | location; 55 | sequence_md; 56 | my_md; 57 | reply_to_client_md; 58 | } 59 | 60 | 61 | action find_index_act(index) { 62 | modify_field(location.index, index); 63 | } 64 | 65 | table find_index { 66 | reads { 67 | nc_hdr.key: exact; 68 | } 69 | actions { 70 | find_index_act; 71 | } 72 | } 73 | 74 | 75 | action get_sequence_act() { 76 | register_read(sequence_md.seq, sequence_reg, location.index); 77 | } 78 | 79 | table get_sequence { 80 | actions { 81 | get_sequence_act; 82 | } 83 | } 84 | 85 | action maintain_sequence_act() { 86 | add_to_field(sequence_md.seq, 1); 87 | register_write(sequence_reg, location.index, sequence_md.seq); 88 | register_read(nc_hdr.seq, sequence_reg, location.index); 89 | } 90 | 91 | table maintain_sequence { 92 | actions { 93 | maintain_sequence_act; 94 | } 95 | } 96 | 97 | action read_value_act() { 98 | register_read (nc_hdr.value, value_reg, location.index); 99 | } 100 | 101 | table read_value { 102 | actions { 103 | read_value_act; 104 | } 105 | } 106 | 107 | action assign_value_act() { 108 | register_write(sequence_reg, location.index, nc_hdr.seq); 109 | register_write(value_reg, location.index, nc_hdr.value); 110 | } 111 | 112 | table assign_value { 113 | actions { 114 | assign_value_act; 115 | } 116 | } 117 | 118 | action pop_chain_act() { 119 | add_to_field(nc_hdr.sc, -1); 120 | pop(overlay, 1); 121 | add_to_field(udp.len, -4); 122 | add_to_field(ipv4.totalLen, -4); 123 | } 124 | 125 | table pop_chain { 126 | actions { 127 | pop_chain_act; 128 | } 129 | } 130 | 131 | table pop_chain_again { 132 | actions { 133 | pop_chain_act; 134 | } 135 | } 136 | 137 | action gen_reply_act(message_type) { 138 | modify_field(reply_to_client_md.ipv4_srcAddr, ipv4.dstAddr); 139 | modify_field(reply_to_client_md.ipv4_dstAddr, ipv4.srcAddr); 140 | modify_field(ipv4.srcAddr, reply_to_client_md.ipv4_srcAddr); 141 | modify_field(ipv4.dstAddr, reply_to_client_md.ipv4_dstAddr); 142 | modify_field(nc_hdr.op, message_type); 143 | modify_field(udp.dstPort, REPLY_PORT); 144 | } 145 | 146 | table gen_reply { 147 | reads { 148 | nc_hdr.op: exact; 149 | } 150 | actions { 151 | gen_reply_act; 152 | } 153 | } 154 | 155 | action drop_packet_act() { 156 | drop(); 157 | } 158 | 159 | table drop_packet { 160 | actions { 161 | drop_packet_act; 162 | } 163 | } 164 | 165 | action get_my_address_act(sw_ip, sw_role) { 166 | modify_field(my_md.ipaddress, sw_ip); 167 | modify_field(my_md.role, sw_role); 168 | } 169 | 170 | table get_my_address { 171 | reads { 172 | nc_hdr.key: exact; 173 | } 174 | actions { 175 | get_my_address_act; 176 | } 177 | } 178 | 179 | action get_next_hop_act() { 180 | modify_field(ipv4.dstAddr, overlay[0].swip); 181 | } 182 | 183 | table get_next_hop { 184 | actions { 185 | get_next_hop_act; 186 | } 187 | } 188 | 189 | action failover_act() { 190 | modify_field(ipv4.dstAddr, overlay[1].swip); 191 | pop_chain_act(); 192 | } 193 | 194 | action failover_write_reply_act() { 195 | gen_reply_act(NC_WRITE_REPLY); 196 | } 197 | 198 | action failure_recovery_act(nexthop) { 199 | modify_field(overlay[0].swip, nexthop); 200 | modify_field(ipv4.dstAddr, nexthop); 201 | } 202 | 203 | action nop() { 204 | no_op(); 205 | } 206 | 207 | table failure_recovery { 208 | reads { 209 | ipv4.dstAddr: ternary; 210 | overlay[1].swip: ternary; 211 | nc_hdr.vgroup: ternary; 212 | } 213 | actions { 214 | failover_act; 215 | failover_write_reply_act; 216 | failure_recovery_act; 217 | nop; 218 | drop_packet_act; 219 | } 220 | } 221 | 222 | control ingress { 223 | if (valid(nc_hdr)) { 224 | apply (get_my_address); 225 | if (ipv4.dstAddr == my_md.ipaddress) { 226 | apply (find_index); 227 | apply (get_sequence); 228 | if (nc_hdr.op == NC_READ_REQUEST) { 229 | apply (read_value); 230 | } 231 | else if (nc_hdr.op == NC_WRITE_REQUEST) { 232 | if (my_md.role == HEAD_NODE) { 233 | apply (maintain_sequence); 234 | } 235 | if ((my_md.role == HEAD_NODE) or (nc_hdr.seq > sequence_md.seq)) { 236 | apply (assign_value); 237 | apply (pop_chain); 238 | } 239 | else { 240 | apply (drop_packet); 241 | } 242 | 243 | } 244 | if (my_md.role == TAIL_NODE) { 245 | apply (pop_chain_again); 246 | apply (gen_reply); 247 | } 248 | else { 249 | apply (get_next_hop); 250 | } 251 | } 252 | } 253 | if (valid(nc_hdr)) { 254 | apply (failure_recovery); 255 | 256 | } 257 | if (valid(tcp) or valid(udp)) { 258 | apply (ipv4_route); 259 | } 260 | } 261 | 262 | control egress { 263 | apply (ethernet_set_mac); 264 | } 265 | 266 | -------------------------------------------------------------------------------- /p4src/routing.p4: -------------------------------------------------------------------------------- 1 | action set_egress(egress_spec) { 2 | modify_field(standard_metadata.egress_spec, egress_spec); 3 | add_to_field(ipv4.ttl, -1); 4 | } 5 | 6 | @pragma stage 11 7 | table ipv4_route { 8 | reads { 9 | ipv4.dstAddr : exact; 10 | } 11 | actions { 12 | set_egress; 13 | } 14 | size : 8192; 15 | } 16 | 17 | action ethernet_set_mac_act (smac, dmac) { 18 | modify_field (ethernet.srcAddr, smac); 19 | modify_field (ethernet.dstAddr, dmac); 20 | } 21 | table ethernet_set_mac { 22 | reads { 23 | standard_metadata.egress_port: exact; 24 | } 25 | actions { 26 | ethernet_set_mac_act; 27 | } 28 | } 29 | --------------------------------------------------------------------------------