openswan发送状态机分析
阅读原文时间:2023年07月11日阅读:2

1. 函数调用关系

2. 函数说明

如果按用户空间、内核空间划分的话,此部分代码更多是运行在内核空间的。

2.1 ipsec_tunnel_init_devices()

该函数主要用来初始化网络设备信息。

int
ipsec_tunnel_init_devices(void)
{
    int i;
    int error;
    /*打印调试信息*/
    KLIPS_PRINT(debug_tunnel & DB_TN_INIT,
            "klips_debug:ipsec_tunnel_init_devices: "
            "creating and registering IPSEC_NUM_IF=%u devices, allocating %lu per device, IFNAMSIZ=%u.\n",
            IPSEC_NUM_IF,
            (unsigned long) (sizeof(struct net_device) + IFNAMSIZ),
            IFNAMSIZ);
    /*依次创建接口信息*/
    for(i = 0; i < IPSEC_NUM_IF; i++) {
        error = ipsec_tunnel_createnum(i);

        if(error) break;
    }
    return 0;
}

2.2 ipsec_tunnel_createnum()

该函数主要的功能为:实现一个网络设备。 主要包含如下流程:

  • 申请网络设备资源

  • 绑定网络设备的操作方法集(驱动层的常见用法)

  • 注册网络设备到内核

    /实现一个网络设备:申请资源、绑定操作方法集、注册网络设备/
    int
    ipsec_tunnel_createnum(int ifnum)
    {
    char name[IFNAMSIZ];
    struct net_device *dev_ipsec;
    int vifentry;

    /*不得超过最大接口数*/
    if(ifnum >= IPSEC_NUM_IFMAX) {
        return -ENOENT;
    }
    /*网络设备是否已经存在,如果存在则错误退出*/
    if(ipsecdevices[ifnum]!=NULL) {
        return -EEXIST;
    }
    
    /* no identical device *//*记录当前设备接口数*/
    if(ifnum > ipsecdevices_max) {
        ipsecdevices_max=ifnum;
    }
    vifentry = ifnum;
    
    KLIPS_PRINT(debug_tunnel & DB_TN_INIT,
            "klips_debug:ipsec_tunnel_init_devices: "
            "creating and registering IPSEC_NUM_IF=%u device\n",
            ifnum);
    
    sprintf(name, IPSEC_DEV_FORMAT, ifnum);
    /*根据内核版本来获取网络设备结构体*/
    #ifdef ALLOC_NETDEV4
        dev_ipsec = alloc_netdev(sizeof(struct ipsecpriv),name,
                                     NET_NAME_UNKNOWN,ipsec_tunnel_netdev_setup);
    #elif defined(alloc_netdev)
    dev_ipsec = alloc_netdev(sizeof(struct ipsecpriv), name, ipsec_tunnel_netdev_setup);
    #else/*最低档次的版本,使用kmalloc分配空间*/
        dev_ipsec = (struct net_device*)kmalloc(sizeof(struct net_device), GFP_KERNEL);
    #endif
    
    if (dev_ipsec == NULL) {
        printk(KERN_ERR "klips_debug:ipsec_tunnel_init_devices: "
               "failed to allocate memory for device %s, quitting device init.\n",
               name);
        return -ENOMEM;
    }
    /*如果内核版本太低,则需要手动将申请的内存清空*/
    #ifndef alloc_netdev
        memset((caddr_t)dev_ipsec, 0, sizeof(struct net_device));
        strncpy(dev_ipsec->name, name, sizeof(dev_ipsec->name));
        #ifdef PAUL_FIXME
            dev_ipsec->next = NULL;
        #endif
    #endif /* alloc_netdev */

    /由于我选用的内核是4.19.y, 因此USE_NETDEV_OPS是已经定义了,我们走else流程/
    /用来绑定操作方法集/
    #ifndef USE_NETDEV_OPS
    dev_ipsec->init = &ipsec_tunnel_probe;
    #else
    dev_ipsec->netdev_ops = &klips_device_ops;/网络设备操作方法集/
    #endif
    KLIPS_PRINT(debug_tunnel & DB_TN_INIT,
    "klips_debug:ipsec_tunnel_init_devices: "
    "registering device %s\n",
    dev_ipsec->name);

    /* reference and hold the device reference */
    ipsec_dev_hold(dev_ipsec);
    ipsecdevices[vifentry]=dev_ipsec;
    
    /*注册网络设备*/
    if (register_netdev(dev_ipsec) != 0) {
        KLIPS_PRINT(1 || debug_tunnel & DB_TN_INIT,
                "klips_debug:ipsec_tunnel_init_devices: "
                "registering device %s failed, quitting device init.\n",
                dev_ipsec->name);
        return -EIO;
    } else {
        KLIPS_PRINT(debug_tunnel & DB_TN_INIT,
                "klips_debug:ipsec_tunnel_init_devices: "
                "registering device %s succeeded, continuing...\n",
                dev_ipsec->name);
    }
    return 0;

    }

2.3 ipsec_tunnel_probe()

这个函数,如果再较高的内核版中(2.6.30以后)是在驱动安装加载过程中最先执行的(作为net_device_ops的回调函数执行的)。

主要功能:

完成网络设备的初始化。 实际上网络设备的初始化最主要的工作就是:实现若干个函数指针。这也是其他驱动设备、网络设备的都需要的操作。

int
ipsec_tunnel_probe(struct net_device *dev)
{
    ipsec_tunnel_init(dev);//隧道初始化
    return 0;
}
int
ipsec_tunnel_init(struct net_device *dev)
{
    int i;
    struct ipsecpriv *iprv;

    ... ...
    #ifdef alloc_netdev
        dev->destructor         = free_netdev;
    #endif
    ... ...

    #ifdef HAVE_NETDEV_HEADER_OPS
        dev->header_ops        = NULL;
    #else
        dev->hard_header    = NULL;
        dev->rebuild_header     = NULL;
        dev->header_cache_update= NULL;
    #endif

    #ifdef HAVE_NET_DEVICE_OPS
        dev->netdev_ops         = &klips_device_ops;  /**/
    #else
        dev->open               = ipsec_tunnel_open;
        dev->stop               = ipsec_tunnel_close;
        dev->hard_start_xmit    = ipsec_tunnel_start_xmit;/*发送数据时的函数接口*/
        dev->get_stats          = ipsec_tunnel_get_stats;

        #ifdef HAVE_SET_MAC_ADDR
                dev->set_mac_address    = NULL;
        #endif
        dev->do_ioctl           = ipsec_tunnel_ioctl;
        dev->neigh_setup        = ipsec_tunnel_neigh_setup_dev;
    #endif

        dev->hard_header_len     = 0;
        dev->mtu                = 0;
        dev->addr_len            = 0;
        dev->type        = ARPHRD_VOID; /* ARPHRD_TUNNEL; */ /* ARPHRD_ETHER; */
        dev->tx_queue_len        = 10;       /* Small queue */
    #ifdef IFF_XMIT_DST_RELEASE
        dev->priv_flags           &= ~IFF_XMIT_DST_RELEASE;
    #endif
    memset((caddr_t)(dev->broadcast),0xFF, ETH_ALEN);   /* what if this is not attached to ethernet? */

    /* New-style flags. */
    dev->flags      = IFF_NOARP /* 0 */ /* Petr Novak */;

    /* We're done.  Have I forgotten anything? */
    return 0;
}

这里可能有疑问:为什么只实现了发送函数指针,而没有接口函数指针。这是因为数据的接收采用的硬件中断的方式(最原始的时候)进行统一的接收处理,而不再单独实现。


以上为IPSec模块在驱动加载过程中的部分处理流程。它实现了一个操作方法集,其中就包括网络设备的方法函数hard_start_xmit, 接下来主要说明发送数据时的处理过程。


3.0 网络设备发送报文处理流程

3.1 ipsec_tunnel_start_xmit()

在2.3节已经知道,在申请网络设备时,会注册实现多个函数指针,其中一个便是dev->hard_start_xmit,这个函数指针是注册到驱动中的,经由该网口转发的报文都会调用到该函数指针。IPSec数据报文便是在这个函数中进行的封装。

/*
 *    This function assumes it is being called from dev_queue_xmit()
 *    and that skb is filled properly by that function.
 */
int
ipsec_tunnel_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct ipsec_xmit_state *ixs = NULL;
    enum ipsec_xmit_value stat;

    KLIPS_PRINT(debug_tunnel & DB_TN_XMIT,
            "\n\nipsec_tunnel_start_xmit: STARTING");

    stat = IPSEC_XMIT_ERRMEMALLOC;
    /*创建一个新的发送状态对象*/
    ixs = ipsec_xmit_state_new(dev);
    if(ixs == NULL)
        return NETDEV_TX_BUSY;

    ixs->dev = dev;
    ixs->skb = skb;

    /*网络设备正确性检查*/
    stat = ipsec_xmit_sanity_check_ipsec_dev(ixs);
    if(stat != IPSEC_XMIT_OK) {
        goto cleanup;
    }
    /*网络数据包(skb)的正确性检查:1.是否支持V6, 是否需要支持IP选项字段*/
    stat = ipsec_xmit_sanity_check_skb(ixs);
    if(stat != IPSEC_XMIT_OK) {
        goto cleanup;
    }
    /*检测并标识报文中是否包含二层头部*/
    stat = ipsec_tunnel_strip_hard_header(ixs);
    if(stat != IPSEC_XMIT_OK) {
        goto cleanup;
    }
    /*查询IPSec SA*/
    stat = ipsec_tunnel_SAlookup(ixs);
    if(stat != IPSEC_XMIT_OK) {
        KLIPS_PRINT(debug_tunnel & DB_TN_XMIT,
                "klips_debug:ipsec_tunnel_start_xmit: SAlookup failed: %d\n",
                stat);
        goto cleanup;
    }
    /*报文封装完毕后(ipsec_xsm状态机),将报文发送出去*/
    ixs->xsm_complete = ipsec_tunnel_xsm_complete;

    ipsec_xsm(ixs);/*发送状态机*/
    return 0;

 cleanup:
    ipsec_xmit_cleanup(ixs);
    ipsec_xmit_state_delete(ixs);
    return 0;
}

3.2 ipsec_tunnel_xsm_complete()

void
ipsec_tunnel_xsm_complete(
    struct ipsec_xmit_state *ixs,
    enum ipsec_xmit_value stat)
{
    unsigned char nexthdr;
    int nexthdroff;
    if(stat != IPSEC_XMIT_OK) {
        if(stat == IPSEC_XMIT_PASS) {
            goto bypass;
        }

        KLIPS_PRINT(debug_tunnel & DB_TN_XMIT,
                "klips_debug:ipsec_tunnel_start_xmit: encap_bundle failed: %d\n",
                stat);
        goto cleanup;
    }
    ... ...
    {
        nexthdr = osw_ip4_hdr(ixs)->protocol;
        nexthdroff = 0;
        if ((ntohs(osw_ip4_hdr(ixs)->frag_off) & IP_OFFSET) == 0)
            nexthdroff = (ixs->iph + (osw_ip4_hdr(ixs)->ihl<<2)) -
                (void *)ixs->skb->data;
        ixs->matcher.sen_type = SENT_IP4;
        ixs->matcher.sen_ip_src.s_addr = osw_ip4_hdr(ixs)->saddr;
        ixs->matcher.sen_ip_dst.s_addr = osw_ip4_hdr(ixs)->daddr;
        ixs->matcher.sen_proto = nexthdr;
    }
    /*提取封装后的端口信息*/
    ipsec_extract_ports(ixs->skb, nexthdr, nexthdroff, &ixs->matcher);

    spin_lock_bh(&eroute_lock);
    /*重新查找eroute路由表*/
    ixs->eroute = ipsec_findroute(&ixs->matcher);
    if(ixs->eroute) {
        ixs->outgoing_said = ixs->eroute->er_said;
        ixs->eroute_pid = ixs->eroute->er_pid;
        ixs->eroute->er_count++;
        ixs->eroute->er_lasttime = jiffies/HZ;
    }
    spin_unlock_bh(&eroute_lock);

    /*ipsec_xmit_init1中实现ixs->orgeds的初始化*/
    if (/*((ixs->orgdst != ixs->newdst) || (ixs->orgsrc != ixs->newsrc))*/
            ip_address_cmp(&ixs->orgedst, &ixs->outgoing_said.dst) != 0 &&
            !ip_address_isany(&ixs->outgoing_said.dst) &&
            ixs->eroute) {
        KLIPS_PRINT(debug_tunnel & DB_TN_XMIT,
            "klips_debug:ipsec_tunnel_start_xmit: "
            "We are recursing here.\n");
        ipsec_xsm(ixs);/*如果需要(再次匹配到其他的eroute),再次进入状态机进行封装*/
        return;
    }

#ifdef NAT_TRAVERSAL
    stat = ipsec_nat_encap(ixs);/*如果中间经过了NAT,需要NAT-T封装*/
    if(stat != IPSEC_XMIT_OK) {
        goto cleanup;
    }
#endif

    stat = ipsec_tunnel_restore_hard_header(ixs);/*添加二层头*/
    if(stat != IPSEC_XMIT_OK) {
        goto cleanup;
    }

bypass:
    stat = ipsec_tunnel_send(ixs);/*发送报文*/

cleanup:
    ipsec_xmit_cleanup(ixs);
    ipsec_xmit_state_delete(ixs);
}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章