图1
直接上图,再分别简单介绍各部分:
PC:笔记本电脑。
gw1:使用一台微型服务器模拟企业用户网关,同时添加静态路由使具备简单路由功能。
gw2:模拟ISP,同时添加静态路由使具备简单路由功能。
TPB:使用linux bridge桥接eth2、eth3。这里有个特殊功能,修改linux内核网络协议栈源码,使内核在不使用GRE模块情况下,具有对IP数据报的加封装和解封装GRE包头功能,同时具备GRE数据包的分片功能。
POP:模拟网络服务(如VPN服务等)提供点,这里省略了很多的网络功能,然后为了能够访问外网,对应于TPB具有加封装和解封装GRE包头功能,也是修改的linux内核网络协议栈源码。
GW3:pfsense(软防火墙和路由器),连接公司网关通往外网。
- iptables 对数据包打标记
iptables -t mangle -A FORWARD -d x.x.x.x/x -j MARK --set-mark 47
这样代码中可以识别出数据包在内核缓存中是否被标记:
if(skb->mark == 47)
... ...
然后对这些被识别出的数据包添加GRE包头, 这里的GRE包头是数据包包头的最外层IP层和GRE层的总称。
- 加封装和解封装GRE包头(GRE over IPv4)
这里以内核版本版本2.6.32-431.el6.x86_64为例。
下面是ip_gre模块中从加封装GRE包头到发送的代码流程:
netif_receive_skb --> deliver_skb --> ip_rcv --> ip_rcv_finish --> dst_input --> ip_forward --> ip_forward_finish --> dst_output --> ip_output --> ip_finish_output --> ip_finish_output2 --> arp_constructor --> dev_queue_xmit --> dev_hard_start_xmit --> ipgre_xmit --> __gre_xmit --> gre_build_header --> ip_tunnel_xmit --> ip_local_out --> dst_output --> dev_queue_xmit
流程比较复杂,这里附上最简版的添加GRE包头的代码,下面代码是经过测试的,但不是在内核源码任何地方都可以用,使用前必须保证skb->data初始位置在MAC层位置上:
int TPB_xmit(struct sk_buff *skb)
{
struct gre_base_hdr *greh;
struct iphdr *iph, *inner_iph;
int needed_headroom = 38;
int tunnel_hlen = 4;
unsigned char eth_addr[ETH_ALEN];
unsigned char hh_data[14];
struct ethhdr *eth1, *eth2;
const u8 *p1;
u8 eth_daddr[ETH_ALEN];
skb->encapsulation = 1;
skb_reset_network_header(skb);
if (skb_cow_head(skb, needed_headroom)) //扩展包头,以容得下新添加的IP层和GRE层
return 0;
skb_pull(skb, 14); //将skb->data向数据方向后移14个字节(即mac层长度)的位置,IP头的位置
eth1 = eth_hdr(skb);
memcpy(eth_addr, eth1->h_source, ETH_ALEN);
memcpy(eth_daddr, eth1->h_dest, ETH_ALEN);
inner_iph = (struct iphdr*)skb->data;
skb_push(skb, tunnel_hlen); //将skb->data向数据方向反向移动4个字节(GRE包头长度)
greh = (struct gre_base_hdr *)skb->data;
greh->flags = 0;
greh->protocol = skb->protocol;
skb_push(skb, sizeof(struct iphdr)); //将skb->data向数据方向反向移动sizeof(struct iphdr)个字节(即IP层长度)的位置,设置为最外层IP头的位置
skb_reset_network_header(skb);
iph = ip_hdr(skb);
iph->version = 4;
iph->ihl = sizeof(struct iphdr) >> 2;
iph->frag_off = 0;
iph->protocol = IPPROTO_GRE;
iph->tot_len = inner_iph->tot_len + htons(24);
iph->tos = 0;
iph->daddr = ip_array_TPB[1];
iph->saddr = ip_array_TPB[0];
iph->ttl = 64;
iph->id = inner_iph->id;
iph->check = 0;
iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
eth2 = (struct ethhdr*) (((u8 *) hh_data) );
eth2->h_proto = skb->protocol;
memcpy(eth2->h_source, eth_addr, ETH_ALEN);
memcpy(eth2->h_dest, eth_daddr, ETH_ALEN);
p1= (const u8*)hh_data;
skb_push(skb, 14); //将skb->data向数据方向反向移动14个字节(即MAC层长度),设置为MAC头的位置
skb_reset_mac_header(skb);
memcpy(skb->data, hh_data, 14);
return 0;
}
接下来的解封装GRE包头介绍,以下ip_gre模块中解封装GRE包头的代码流程:
netif_receive_skb --> deliver_skb --> ip_rcv --> ip_rcv_finish --> dst_input --> ip_local_deliver --> ip_local_deliver_finish --> gre_rcv --> gre_cisco_rcv --> ipgre_rcv --> ip_tunnel_lookup --> ip_tunnel_rcv --> gro_cells_receive --> napi_schedule --> __napi_schedule --> ____napi_schedule --> netif_receive_skb --> deliver_skb --> ip_rcv --> ip_rcv_finish --> dst_input --> ip_forward --> ip_forward_finish --> dst_output --> dev_queue_xmit
同样,流程比较复杂,这里附上最简版解封装GRE包头代码:
struct sk_buff* TPB_trim_gre_header(struct sk_buff *skb)
{
unsigned char hh_data[14];
unsigned char* skb_data_ptr;
skb->data = skb_mac_header(skb);
skb_data_ptr = skb->data;
memcpy(hh_data, skb_data_ptr, 14);
skb_pull(skb, 24);
skb_reset_mac_header(skb);
memcpy(skb->data, hh_data, 14);
skb_pull(skb, 14);
skb_reset_network_header(skb);
skb_push(skb, 14);
return skb;
}
- GRE包头分片
目前硬件还不支持GRE分片,只能在代码层对GRE包头进行分片。看下整个协议栈的GSO处理逻辑:
图2
详情参考博客:
这里在图中tcp_tso_segment处做更改,如下:
dev_hard_start_xmit --> dev_gso_segment --> skb_gso_segment --> skb_mac_gso_segment --> inet_gso_segment
--> gre_gso_segment --> skb_mac_gso_segment --> inet_gso_segment --> tcp_tso_segment
static const struct net_offload gre_offload = {
.gso_send_check = gre_gso_send_check,
.gso_segment = gre_gso_segment,
};
inet_add_offload(&gre_offload, IPPROTO_GRE);
static int gre_gso_send_check(struct sk_buff *skb)
{
if (!skb->encapsulation)
return -EINVAL;
return 0;
}
static struct sk_buff *gre_gso_segment(struct sk_buff *skb,
int features)
{
... ...
}
注意:
在图1中,TPB节点和POP节点都具有加封装和解封装GRE包头功能,在POP节点接收来自TPB节点的加封装的数据包,并且进行GRE分片,如果没有关闭网卡eth3的generic-receive-offload 特性,这里分片的数据包会合并,由于数据包长超过MTU值,会被丢弃, 这里可以暂时将该特性关闭,执行命令 ethtool -K eth3 gro off。