实验背景
报文转发流程
采用示例说明SRv6的报文转发流程。
如图所示,假设有报文需要从主机1转发到主机2,主机1将报文发送给节点A处理。节点A、B、D、E均支持SRv6,节点C不支持SRv6,只支持IPv6。我们在源节点A上进行网络编程,希望报文经过B-C、C-D链路,送达节点E,由E节点送达主机2。
报文转发流程分为以下几步:
- 源节点A将SRv6路径信息封装在SRH中,指定B-C,C-D链路的SID,另外封装E点发布的SID A5::10(此SID对应于节点E的一个IPv4 VPN),共3个SID,按照逆序形式压入SID序列。此时SL(Segment Left)=2,将Segment List[2]值复制到目的地址DA字段,按照最长匹配原则查找IPv6路由表,将其转发到节点B。
- 报文到达节点B,B节点查找本地SID表(存储本节点生成的SRv6 SID信息),命中自身的SID(End.X SID),执行SID对应的指令动作。SL值减1,并将Segment List[1]值复制到DA字段,同时将报文从SID绑定的链路(B-C)发送出去。
报文到达节点C,C无SRv6能力,无法识别SRH,按照正常IPv6报文处理流程,按照最长匹配原则查找IPv6路由表,将其转发到当前目的地址所代表的节点D。 - 节点D收报文后根据目的地址A4::45查找本地SID表,命中自身的SID(End.X SID)。同节点B,SL值减1,将A5::10作为DA,并将报文发送出去。
- 节点E收到报文后根据A5::10查找本地SID表,命中自身SID(End.DT4 SID),执行对应的指令动作,解封装报文,去除IPv6报文头,并将内层IPv4报文在SID绑定的VPN实例的IPv4路由表中进程查表转发,最终将报文发送给主机2。
srv6简化实验
本实验进行了简化,仅使用两个交换机作为源节点和end节点
实验实现流程:当h1向h2通信时,s1作为源节点,插入路径s1、s2。当s2接收到来在s1的报文,检查发现自己为end节点,执行srv6_pop操作,去掉srv6报文头并将安装ipv6地址进行转发到达h2
p4文件
#include <core.p4>
#include <v1model.p4>
/************************************************************************
************************** Defines **************************************
*************************************************************************/
#define ETH_TYPE_IPV4 0x0800
#define ETH_TYPE_IPV6 0x86dd
#define IP_PROTO_TCP 8w6
#define IP_PROTO_UDP 8w17
#define IP_PROTO_SRV6 8w43
#define IP_VERSION_4 4w4
#define SRV6_MAX_HOPS 6
typedef bit<48> mac_addr_t;
typedef bit<32> ipv4_addr_t;
typedef bit<128> ipv6_addr_t;
typedef bit<9> port_t;
const port_t CPU_PORT = 255;
/************************************************************************
************************** Headers **************************************
*************************************************************************/
@controller_header("packet_in")
header packet_in_header_t {
bit<9> ingress_port;
bit<7> _padding;
}
@controller_header("packet_out")
header packet_out_header_t {
bit<9> egress_port;
bit<7> _padding;
}
header ethernet_t {
bit<48> dst_addr;
bit<48> src_addr;
bit<16> ether_type;
}
header ipv4_t {
bit<4> version;
bit<4> ihl;
bit<6> dscp;
bit<2> ecn;
bit<16> len;
bit<16> identification;
bit<3> flags;
bit<13> frag_offset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdr_checksum;
bit<32> src_addr;
bit<32> dst_addr;
}
header ipv6_t {
bit<4> version;
bit<8> traffic_class;
bit<20> flow_label;
bit<16> payload_len;
bit<8> next_hdr;
bit<8> hop_limit;
bit<128> src_addr;
bit<128> dst_addr;
}
header srv6_header_t {
bit<8> next_hdr;
bit<8> hdr_ext_len;
bit<8> routing_type;
bit<8> segment_left;
bit<8> last_entry;
bit<8> flags;
bit<16> tag;
}
header srv6_segment_list_t{
bit<128> sid;
}
header tcp_t {
bit<16> src_port;
bit<16> dst_port;
bit<32> seq_no;
bit<32> ack_no;
bit<4> data_offset;
bit<3> res;
bit<3> ecn;
bit<6> ctrl;
bit<16> window;
bit<16> checksum;
bit<16> urgent_ptr;
}
header udp_t {
bit<16> src_port;
bit<16> dst_port;
bit<16> length_;
bit<16> checksum;
}
/************************************************************************
*********************** Custom Headers *********************************
*************************************************************************/
struct headers_t {
packet_out_header_t packet_out;
packet_in_header_t packet_in;
ethernet_t ethernet;
ipv4_t ipv4;
ipv6_t ipv6;
srv6_header_t srh;
srv6_segment_list_t[SRV6_MAX_HOPS] segment_list;
tcp_t tcp;
udp_t udp;
}
struct local_metadata_t {
bit<16> l4_src_port;
bit<16> l4_dst_port;
ipv6_addr_t next_sid;
bit<8> ip_proto;
}
/************************************************************************
**************************** Parser *************************************
*************************************************************************/
parser parser_impl(packet_in packet,
out headers_t hdr,
inout local_metadata_t local_metadata,
inout standard_metadata_t standard_metadata) {
state start {
transition select(standard_metadata.ingress_port) {
CPU_PORT: parse_packet_out;
default: parse_ethernet;
}
}
state parse_packet_out {
packet.extract(hdr.packet_out);
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.ether_type) {
ETH_TYPE_IPV4: parse_ipv4;
ETH_TYPE_IPV6: parse_ipv6;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition select(hdr.ipv4.protocol) {
IP_PROTO_TCP: parse_tcp;
IP_PROTO_UDP: parse_udp;
default: accept;
}
}
state parse_ipv6 {
packet.extract(hdr.ipv6);
local_metadata.ip_proto = hdr.ipv6.next_hdr;
transition select(hdr.ipv6.next_hdr) {
IP_PROTO_TCP: parse_tcp;
IP_PROTO_UDP: parse_udp;
IP_PROTO_SRV6: parse_srv6;
default: accept;
}
}
state parse_tcp {
packet.extract(hdr.tcp);
local_metadata.l4_src_port = hdr.tcp.src_port;
local_metadata.l4_dst_port = hdr.tcp.dst_port;
transition accept;
}
state parse_udp {
packet.extract(hdr.udp);
local_metadata.l4_src_port = hdr.udp.src_port;
local_metadata.l4_dst_port = hdr.udp.dst_port;
transition accept;
}
state parse_srv6 {
packet.extract(hdr.srh);
transition parse_segment_list;
}
state parse_segment_list {
packet.extract(hdr.segment_list.next);
bool next_sid = (bit<32>)hdr.srh.segment_left - 1 == (bit<32>)hdr.segment_list.lastIndex;
transition select(next_sid){
true: mark_next_sid;
default: check_last_sid;
}
}
state mark_next_sid{
local_metadata.next_sid = hdr.segment_list.last.sid;
transition check_last_sid;
}
state check_last_sid {
bool last_sid = (bit<32>)hdr.srh.last_entry == (bit<32>)hdr.segment_list.lastIndex;
transition select(last_sid){
true: parse_srv6_next_hdr;
false: parse_segment_list;
}
}
state parse_srv6_next_hdr{
transition select(hdr.srh.next_hdr){
IP_PROTO_TCP: parse_tcp;
IP_PROTO_UDP: parse_udp;
default: accept;
}
}
}
/************************************************************************
*********************** Verify Checksum *********************************
*************************************************************************/
control verify_checksum_control(inout headers_t hdr,
inout local_metadata_t local_metadata) {
apply {
// Assume checksum is always correct.
}
}
/************************************************************************
*********************** Ingress Pipeline*********************************
*************************************************************************/
control ingress(inout headers_t hdr,
inout local_metadata_t local_metadata,
inout standard_metadata_t standard_metadata) {
action drop(){
mark_to_drop(standard_metadata);
}
table local_mac_table {
key = {
hdr.ethernet.dst_addr: exact;
}
actions = {
NoAction;
}
}
action set_next_hop(mac_addr_t dmac, port_t port){
hdr.ethernet.src_addr = hdr.ethernet.dst_addr;
hdr.ethernet.dst_addr = dmac;
hdr.ipv6.hop_limit = hdr.ipv6.hop_limit - 1;
standard_metadata.egress_spec = port;
}
table routing_v6_table {
key = {
hdr.ipv6.dst_addr: lpm;
}
actions = {
set_next_hop;
}
}
action end(){
hdr.srh.segment_left = hdr.srh.segment_left - 1;
hdr.ipv6.dst_addr = local_metadata.next_sid;
}
table local_sid_table {
key = {
hdr.ipv6.dst_addr: lpm;
}
actions = {
end;
}
}
action insert_srh(bit<8> num_segments){
hdr.srh.setValid();
hdr.srh.next_hdr = hdr.ipv6.next_hdr;
hdr.srh.hdr_ext_len = num_segments * 2;
hdr.srh.routing_type = 4;
hdr.srh.segment_left = num_segments - 1;
hdr.srh.last_entry = num_segments - 1;
hdr.srh.flags = 0;
hdr.srh.tag = 0;
hdr.ipv6.next_hdr = IP_PROTO_SRV6;
}
action insert_segment_list_2(ipv6_addr_t s1, ipv6_addr_t s2){
hdr.ipv6.dst_addr = s1;
hdr.ipv6.payload_len = hdr.ipv6.payload_len + 40;
insert_srh(2);
hdr.segment_list[0].setValid();
hdr.segment_list[0].sid = s2;
hdr.segment_list[1].setValid();
hdr.segment_list[1].sid = s1;
}
action insert_segment_list_3(ipv6_addr_t s1, ipv6_addr_t s2, ipv6_addr_t s3){
hdr.ipv6.dst_addr = s1;
hdr.ipv6.payload_len = hdr.ipv6.payload_len + 56;
insert_srh(3);
hdr.segment_list[0].setValid();
hdr.segment_list[0].sid = s3;
hdr.segment_list[1].setValid();
hdr.segment_list[1].sid = s2;
hdr.segment_list[2].setValid();
hdr.segment_list[2].sid = s1;
}
table transit_table{
key={
hdr.ipv6.dst_addr: lpm;
}
actions = {
insert_segment_list_2;
insert_segment_list_3;
}
}
action srv6_pop(){
hdr.ipv6.next_hdr = hdr.srh.next_hdr;
bit<16> srh_size = (((bit<16>)hdr.srh.last_entry + 1) << 4) + 8;
hdr.ipv6.payload_len = hdr.ipv6.payload_len - srh_size;
hdr.srh.setInvalid();
hdr.segment_list[0].setInvalid();
hdr.segment_list[1].setInvalid();
hdr.segment_list[2].setInvalid();
hdr.segment_list[3].setInvalid();
hdr.segment_list[4].setInvalid();
hdr.segment_list[5].setInvalid();
}
apply {
if (standard_metadata.ingress_port == CPU_PORT) {
// Receive packets from controller, namely packet_out message.
// Directly tell switch where the port packets sent to.
standard_metadata.egress_spec = hdr.packet_out.egress_port;
// pop the header of packet-out packet
hdr.packet_out.setInvalid();
exit;
}
// The logic of how to handle srv6 header
// simple version, only considering ipv6 packets and srv6 packets
//当交换机接收到到达自己的报文时,检查其mac地址、检查是否是end节点和源节点,进行相应的操作,然后进行ipv6的转发
if(local_mac_table.apply().hit){
if(hdr.ipv6.isValid()){
if(local_sid_table.apply().hit){
if(hdr.srh.isValid() && hdr.srh.segment_left == 0){
srv6_pop();
}
}else{
transit_table.apply();
}
routing_v6_table.apply();
if(hdr.ipv6.hop_limit == 0){
drop();
}
}
}
}
}
/************************************************************************
*********************** Egress Pipeline**********************************
*************************************************************************/
control egress(inout headers_t hdr,
inout local_metadata_t local_metadata,
inout standard_metadata_t standard_metadata) {
apply {
if (standard_metadata.egress_port == CPU_PORT) {
// Handle packet-in packet, if egress_port is cpu port, which means this packet is sent to controller
// Add the packet-in header
hdr.packet_in.setValid();
hdr.packet_in.ingress_port = standard_metadata.ingress_port;
}
}
}
/************************************************************************
*********************** Compute Checksum ********************************
*************************************************************************/
control compute_checksum_control(inout headers_t hdr,
inout local_metadata_t local_metadata) {
apply {
update_checksum(hdr.ipv4.isValid(),
{
hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.dscp,
hdr.ipv4.ecn,
hdr.ipv4.len,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.frag_offset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.src_addr,
hdr.ipv4.dst_addr
},
hdr.ipv4.hdr_checksum,
HashAlgorithm.csum16
);
}
}
/************************************************************************
**************************** Deparser ***********************************
*************************************************************************/
control deparser(packet_out packet, in headers_t hdr) {
apply {
packet.emit(hdr.packet_in);
packet.emit(hdr.ethernet);
packet.emit(hdr.ipv4);
packet.emit(hdr.ipv6);
packet.emit(hdr.srh);
packet.emit(hdr.segment_list);
packet.emit(hdr.tcp);
packet.emit(hdr.udp);
}
}
/************************************************************************
**************************** Switch *************************************
*************************************************************************/
V1Switch(parser_impl(),
verify_checksum_control(),
ingress(),
egress(),
compute_checksum_control(),
deparser()) main;
拓扑文件
{
"program": "srv6.p4",
"switch": "simple_switch",
"compiler": "p4c",
"options": "--target bmv2 --arch v1model --std p4-16",
"switch_cli": "simple_switch_CLI",
"cli": true,
"pcap_dump": true,
"enable_log": true,
"topo_module": {
"file_path": "",
"module_name": "p4utils.mininetlib.apptopo",
"object_name": "AppTopoStrategies"
},
"controller_module": null,
"topodb_module": {
"file_path": "",
"module_name": "p4utils.utils.topology",
"object_name": "Topology"
},
"mininet_module": {
"file_path": "",
"module_name": "p4utils.mininetlib.p4net",
"object_name": "P4Mininet"
},
"topology": {
"assignment_strategy": "mixed",
"auto_arp_tables": "true",
"auto_gw_arp": "true",
"links": [["h1", "s1"], ["h2", "s2"], ["s1", "s2"]],
"hosts": {
"h1": {
"ip": "10.0.1.1/24",
"mac" : "08:00:00:00:01:11",
"gw" : "10.0.1.10"
},
"h2": {
"ip": "10.0.2.2/24",
"mac": "08:00:00:00:02:22",
"gw": "10.0.2.20"
}
},
"switches": {
"s1": {
"cli_input": "cmd_s1.txt",
"program": "srv6.p4"
},
"s2": {
"cli_input": "cmd_s2.txt",
"program": "srv6.p4"
}
}
}
}
控制表
第一行是为了确认目的mac为本交换机,第二行是为了验证本交换机为end交换机,第三行根据目的ipv6地址确定插入的路径有哪些
s1
table_add local_mac_table NoAction 08:00:00:00:11:00 =>
table_add local_sid_table end A1:11::11/128 =>
table_add transit_table insert_segment_list_2 2::2/128 => A2:22::22 2::2
table_add routing_v6_table set_next_hop A2:22::22/128 => 08:00:00:00:22:00 2
table_add routing_v6_table set_next_hop 1::1/128 => 08:00:00:00:01:00 1
s2
table_add local_mac_table NoAction 08:00:00:00:22:00 =>
table_add local_sid_table end A2:22::22/128 =>
table_add transit_table insert_segment_list_2 1::1/128 => A1:11::11 1::1
table_add routing_v6_table set_next_hop 2::2/128 => 08:00:00:00:02:00 1
table_add routing_v6_table set_next_hop A1:11::11/128 => 08:00:00:00:11:00 2
ipv6报文发送
#!/usr/bin/env python
import argparse
import sys
import socket
import random
import struct
from scapy.all import sendp, send, get_if_list, get_if_hwaddr
from scapy.all import Packet
from scapy.all import Ether, IPv6, UDP
def get_if():
ifs=get_if_list()
iface=None # "h1-eth0"
for i in get_if_list():
if "eth0" in i:
iface=i
break;
if not iface:
print "Cannot find eth0 interface"
exit(1)
return iface
def main():
if len(sys.argv)<3:
print 'pass 2 arguments: <destination> "<message>"'
exit(1)
saddr = sys.argv[1]
addr = sys.argv[2]
iface = get_if()
print "sending on interface %s to %s" % (iface, str(addr))
#注意,这里的目的地址是转发给s1的目的mac地址
pkt = Ether(src=get_if_hwaddr(iface), dst='08:00:00:00:11:00') / IPv6(src=saddr,dst=addr) / UDP(dport=4321, sport=1234) / sys.argv[3]
pkt.show2()
sendp(pkt, iface=iface, verbose=False)
if __name__ == '__main__':
main()