|
| 1 | +## Exercise 7: Segment Routing v6 (SRv6) |
| 2 | + |
| 3 | +In this exercise, you will be implementing a simplified version of segment |
| 4 | +routing, a source routing method that steers traffic through a specified set of |
| 5 | +nodes. |
| 6 | + |
| 7 | +This exercise is based on an IETF draft specification called SRv6, which uses |
| 8 | +IPv6 packets to frame traffic that follows an SRv6 policy. SRv6 packets use the |
| 9 | +IPv6 routing header, and they can either encapsulate IPv6 (or IPv4) packets |
| 10 | +entirely or they can just inject an IPv6 routing header into an existing IPv6 |
| 11 | +packet. |
| 12 | + |
| 13 | +The IPv6 routing header looks as follows: |
| 14 | +``` |
| 15 | + 0 1 2 3 |
| 16 | + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 |
| 17 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 18 | + | Next Header | Hdr Ext Len | Routing Type | Segments Left | |
| 19 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 20 | + | Last Entry | Flags | Tag | |
| 21 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 22 | + | | |
| 23 | + | Segment List[0] (128 bits IPv6 address) | |
| 24 | + | | |
| 25 | + | | |
| 26 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 27 | + | | |
| 28 | + | | |
| 29 | + ... |
| 30 | + | | |
| 31 | + | | |
| 32 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 33 | + | | |
| 34 | + | Segment List[n] (128 bits IPv6 address) | |
| 35 | + | | |
| 36 | + | | |
| 37 | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
| 38 | +``` |
| 39 | + |
| 40 | +The **Next Header** field is the type of either the next IPv6 header or the |
| 41 | +payload. |
| 42 | + |
| 43 | +For SRv6, the **Routing Type** is 4. |
| 44 | + |
| 45 | +**Segments Left** points to the index of the current segment in the segment |
| 46 | +list. In properly formed SRv6 packets, the IPv6 destination address equals |
| 47 | +`Segment List[Segments Left]`. The original IPv6 address should be `Segment |
| 48 | +List[0]` in our exercise so that traffic is routed to the correct destination |
| 49 | +eventually. |
| 50 | + |
| 51 | +**Last Entry** is the index of the last entry in the segment list. |
| 52 | + |
| 53 | +Note: This means it should be one less than the length of the list. (In the |
| 54 | +example above, the list is `n+1` entries and last entry should be `n`.) |
| 55 | + |
| 56 | +Finally, the **Segment List** is a reverse-sorted list of IPv6 addresses to be |
| 57 | +traversed in a specific SRv6 policy. The last entry in the list is the first |
| 58 | +segment in the SRv6 policy. The list is not typically mutated; the entire header |
| 59 | +is inserted or removed as a whole. |
| 60 | + |
| 61 | +To keep things simple and because we are already using IPv6, your solution will |
| 62 | +just be adding the routing header to the existing IPv6 packet. (We won't be |
| 63 | +embedding entire packets inside of new IPv6 packets with an SRv6 policy, |
| 64 | +although the spec allows it and there are valid use cases for doing so.) |
| 65 | + |
| 66 | +As you may have already noticed, SRv6 uses IPv6 addresses to identify segments |
| 67 | +in a policy. While the format of the addresses is the same as IPv6, the address |
| 68 | +space is typically different from the space used for switch's internal IPv6 |
| 69 | +addresses. The format of the address also differs. A typical IPv6 unicast |
| 70 | +address is broken into a network prefix and host identifier pieces, and a subnet |
| 71 | +mask is used to delineate the boundary between the two. A typical SRv6 segment |
| 72 | +identifier (SID) is broken into a locator, a function identifier, and |
| 73 | +optionally, function arguments. The locator must be routable, which enables both |
| 74 | +SRv6-enable and unaware nodes to participate in forwarding. |
| 75 | + |
| 76 | +HINT: Due to optional arguments, longest prefix match on the 128-bit SID is |
| 77 | +preferred to exact match. |
| 78 | + |
| 79 | +There are three types of nodes of interest in a segment routed network: |
| 80 | + |
| 81 | +1. Source Node - the node (either host or switch) that injects the SRv6 policy. |
| 82 | +2. Transit Node - a node that forwards an SRv6 packet, but is not the |
| 83 | + destination for the traffic |
| 84 | +3. Endpoint Node - a participating waypoint in an SRv6 policy that will modify |
| 85 | + the SRv6 header and perform a specified function |
| 86 | + |
| 87 | +In our implementation, we simplify these types into two roles: |
| 88 | + |
| 89 | +* Endpoint Node - for traffic to the switch's SID, update the SRv6 header |
| 90 | + (decrement segments left), set the IPv6 destination address to the next |
| 91 | + segment, and forward the packets ("End" behavior). For simplicity, we will |
| 92 | + always remove the SRv6 header on the penultimate segment in the policy (called |
| 93 | + Penultimate Segment Pop or PSP in the spec). |
| 94 | + |
| 95 | +* Transit Node - by default, forward traffic normally if it is not destined for |
| 96 | + the switch's IP address or its SID ("T" behavior). Allow the control plane to |
| 97 | + add rules to inject SRv6 policy for traffic destined to specific IPv6 |
| 98 | + addresses ("T.Insert" behavior). |
| 99 | + |
| 100 | +For more details, you can read the draft specification here: |
| 101 | +https://tools.ietf.org/id/draft-filsfils-spring-srv6-network-programming-06.html |
| 102 | + |
| 103 | +## Exercise steps |
| 104 | + |
| 105 | +### 1. Adding tables for SRv6 |
| 106 | + |
| 107 | +We have already defined the SRv6 header as well as included the logic for |
| 108 | +parsing the header in `main.p4`. |
| 109 | + |
| 110 | +The next step is to add two for each of the two roles specified above. |
| 111 | +In addition to the tables, you will also need to write the action for the |
| 112 | +endpoint node table (otherwise called the "My SID" table); in `snippets.p4`, we |
| 113 | +have provided the `t_insert` actions for policies of length 2 and 3, which |
| 114 | +should be sufficient to get you started. |
| 115 | + |
| 116 | +Once you've finished that, you will need to apply the tables in the `apply` |
| 117 | +block at the bottom of your `EgressPipeImpl` section. You will want to apply the |
| 118 | +tables after checking that the L2 destination address matches the switch's, and |
| 119 | +before the L3 table is applied (because you'll want to use the same routing |
| 120 | +entries to forward traffic after the SRv6 policy is applied). You can also apply |
| 121 | +the PSP behavior as part of your `apply` logic because we will always be |
| 122 | +applying it if we are the penultimate SID. |
| 123 | + |
| 124 | +### 2. Testing the pipeline with Packet Test Framework (PTF) |
| 125 | + |
| 126 | +In this exercise, you will be modifying tests in [srv6.py](ptf/tests/srv6.py) to |
| 127 | +verify the SRv6 behavior of the pipeline. |
| 128 | + |
| 129 | +There are four tests in `srv6.py`: |
| 130 | + |
| 131 | +* Srv6InsertTest: Tests SRv6 insert behavior, where the switch receives an IPv6 |
| 132 | + packet and inserts the SRv6 header. |
| 133 | + |
| 134 | +* Srv6TransitTest: Tests SRv6 transit behavior, where the switch ignores the |
| 135 | + SRv6 header and routes the packet normally, without applying any SRv6-related |
| 136 | + modifications. |
| 137 | + |
| 138 | +* Srv6EndTest: Tests SRv6 end behavior (without pop), where the switch forwards |
| 139 | + the packet to the next SID found in the SRv6 header. |
| 140 | + |
| 141 | +* Srv6EndPspTest: Tests SRv6 End with Penultimate Segment Pop (PSP) behavior, |
| 142 | + where the switch SID is the penultimate in the SID list and the switch removes |
| 143 | + the SRv6 header before routing the packet to it's final destination (last SID |
| 144 | + in the list). |
| 145 | + |
| 146 | +You should be able to find `TODO EXERCISE 7` in [srv6.py](ptf/tests/srv6.py) |
| 147 | +with some hints. |
| 148 | + |
| 149 | +To run all the tests for this exercise: |
| 150 | + |
| 151 | + make p4-test TEST=srv6 |
| 152 | + |
| 153 | +This command will run all tests in the `srv6` group (i.e. the content of |
| 154 | +`ptf/tests/srv6.py`). To run a specific test case you can use: |
| 155 | + |
| 156 | + make p4-test TEST=<PYTHON MODULE>.<TEST CLASS NAME> |
| 157 | + |
| 158 | +For example: |
| 159 | + |
| 160 | + make p4-test TEST=srv6.Srv6InsertTest |
| 161 | + |
| 162 | +**Check for regressions** |
| 163 | + |
| 164 | +At this point, our P4 program should be complete. We can check to make sure that |
| 165 | +we haven't broken anything from the previous exercises by running all tests from |
| 166 | +the `ptf/tests` directory: |
| 167 | + |
| 168 | +``` |
| 169 | +$ make p4-test |
| 170 | +``` |
| 171 | + |
| 172 | +Now we have shown that we can install basic rules and pass SRv6 traffic using BMv2. |
| 173 | + |
| 174 | +### 3. Building the ONOS App |
| 175 | + |
| 176 | +For the ONOS application, you will need to update `Srv6Component.java` in the |
| 177 | +following ways: |
| 178 | + |
| 179 | +* Complete the `setUpMySidTable` method which will insert an entry into the M |
| 180 | + SID table that matches the specified device's SID and performs the `end` |
| 181 | + action. This function is called whenever a new device is connected. |
| 182 | + |
| 183 | +* Complete the `insertSrv6InsertRule` function, which creates a `t_insert` rule |
| 184 | + along for the provided SRv6 policy. This function is called by the |
| 185 | + `srv6-insert` CLI command. |
| 186 | + |
| 187 | +* Complete the `clearSrv6InsertRules`, which is called by the `srv6-clear` CLI |
| 188 | + command. |
| 189 | + |
| 190 | +Once you are finished, you should rebuild and reload your app. This will also |
| 191 | +rebuild and republish any changes to your P4 code and the ONOS pipeconf. Don't |
| 192 | +forget to enable your Srv6Component at the top of the file. |
| 193 | + |
| 194 | +As with previous exercises, you can use the following command to build and |
| 195 | +reload the app: |
| 196 | + |
| 197 | +``` |
| 198 | +$ make app-build app-reload |
| 199 | +``` |
| 200 | + |
| 201 | +### 4. Inserting SRv6 policies |
| 202 | + |
| 203 | +The next step is to show that traffic can be steered using an SRv6 policy. |
| 204 | + |
| 205 | +You should start a ping between `h2` and `h4`: |
| 206 | +``` |
| 207 | +mininet> h2 ping h4 |
| 208 | +``` |
| 209 | + |
| 210 | +Using the ONOS UI, you can observe which paths are being used for the ping |
| 211 | +packets. |
| 212 | + |
| 213 | +- Press `a` until you see "Port stats (packets/second)" |
| 214 | +- Press `l` to show device labels |
| 215 | + |
| 216 | +<img src="img/srv6-ping-1.png" alt="Ping Test" width="344"/> |
| 217 | + |
| 218 | +Once you determine which of the spines your packets are being hashed to (and it |
| 219 | +could be both, with requests and replies taking different paths), you should |
| 220 | +insert a set of SRv6 policies that sends the ping packets via the other spine |
| 221 | +(or the spine of your choice). |
| 222 | + |
| 223 | +To add new SRv6 policies, you should use the `srv6-insert` command. |
| 224 | + |
| 225 | +``` |
| 226 | +onos> srv6-insert <device ID> <segment list> |
| 227 | +``` |
| 228 | + |
| 229 | +Note: In our topology, the SID for spine1 is `3:201:2::` and the SID for spine |
| 230 | +is `3:202:2::`. |
| 231 | + |
| 232 | +For example, to add a policy that forwards traffic between h2 and h4 though |
| 233 | +spine1 and leaf2, you can use the following command: |
| 234 | + |
| 235 | +* Insert the SRv6 policy from h2 to h4 on leaf1 (through spine1 and leaf2) |
| 236 | +``` |
| 237 | +onos> srv6-insert device:leaf1 3:201:2:: 3:102:2:: 2001:1:4::1 |
| 238 | +Installing path on device device:leaf1: 3:201:2::, 3:102:2::, 2001:1:4::1 |
| 239 | +``` |
| 240 | +* Insert the SRv6 policy from h4 to h2 on leaf2 (through spine1 and leaf1) |
| 241 | +``` |
| 242 | +onos> srv6-insert device:leaf2 3:201:2:: 3:101:2:: 2001:1:2::1 |
| 243 | +Installing path on device device:leaf2: 3:201:2::, 3:101:2::, 2001:1:2::1 |
| 244 | +``` |
| 245 | + |
| 246 | +These commands will match on traffic to the last segment on the specified device |
| 247 | +(e.g. match `2001:1:4::1` on `leaf1`). You can update the command to allow for |
| 248 | +more specific match criteria as extra credit. |
| 249 | + |
| 250 | +You can confirm that your rule has been added using a variant of the following: |
| 251 | + |
| 252 | +(HINT: Make sure to update the tableId to match the one in your P4 program.) |
| 253 | + |
| 254 | +``` |
| 255 | +onos> flows any device:leaf1 | grep tableId=IngressPipeImpl.srv6_transit |
| 256 | + id=c000006d73f05e, state=ADDED, bytes=0, packets=0, duration=871, liveType=UNKNOWN, priority=10, |
| 257 | + tableId=IngressPipeImpl.srv6_transit, |
| 258 | + appId=org.p4.srv6-tutorial, |
| 259 | + selector=[hdr.ipv6.dst_addr=0x20010001000400000000000000000001/128], |
| 260 | + treatment=DefaultTrafficTreatment{immediate=[ |
| 261 | + IngressPipeImpl.srv6_t_insert_3( |
| 262 | + s3=0x20010001000400000000000000000001, |
| 263 | + s1=0x30201000200000000000000000000, |
| 264 | + s2=0x30102000200000000000000000000)], |
| 265 | + deferred=[], transition=None, meter=[], cleared=false, StatTrigger=null, metadata=null} |
| 266 | +``` |
| 267 | + |
| 268 | +You should now return to the ONOS UI to confirm that traffic is flowing through |
| 269 | +the specified spine. |
| 270 | + |
| 271 | +<img src="img/srv6-ping-2.png" alt="SRv6 Ping Test" width="335"/> |
| 272 | + |
| 273 | +### Debugging and Clean Up |
| 274 | + |
| 275 | +If you need to remove your SRv6 policies, you can use the `srv6-clear` command |
| 276 | +to clear all SRv6 policies from a specific device. For example to remove flows |
| 277 | +from `leaf1`, use this command: |
| 278 | + |
| 279 | +``` |
| 280 | +onos> srv6-clear device:leaf1 |
| 281 | +``` |
| 282 | + |
| 283 | +To verify that the device inserts the correct SRv6 header, you can use |
| 284 | +**Wireshark** to capture packet from each device port. |
| 285 | + |
| 286 | +For example, if you want to capture packet from port 1 of spine1, capture |
| 287 | +packets from interface `spine1-eth1`. |
| 288 | + |
| 289 | +NOTE: `spine1-eth1` is connected to leaf1, and `spine1-eth2` is connected to |
| 290 | +leaf2; spine two follows the same pattern. |
| 291 | + |
| 292 | +## Congratulations! |
| 293 | + |
| 294 | +You have completed the seventh exercise! Now your fabric is capable of steering |
| 295 | +traffic using SRv6. |
0 commit comments