gre封装是第几层_gre封装是第几层

                                                                          图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处理逻辑:

      

gre封装是第几层_gre封装是第几层_02

                                                   图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。