内核中的TCP的追踪分析-4-TCP(IPV4)的socket的地址绑定-续

接着昨天的继续分析,我们接着从上一节最后部分继续开始今天的内容 :
 
我是无名小卒,转载的朋友请注明出处,请不要抄袭做为它用,谢谢!
 

在内核中CONFIG_NET_NS配置选项是为了让用户自定义自己的网络空间结构,即上面的net结构,可以看出2.6.26内核的灵活性,但是我们一般在内核中不会配置该项,所以这里应该是取得init_net,这个结构是在前一节分析的那样在do_one_initcall()机制中调用了从pure_initcall(net_ns_init)注册的net_ns_init()初始化的,这个net_ns_init函数进一步调用setup_net()来对init_net结构进行详细的设置。我们这里不细细分析了他的过程类似于我们前边那节socket的初始化的流程,只不过在setup_net对init_net初始化中有一个非常重要的循环

    list_for_each_entry(ops, &pernet_list, list) {
        if (ops->init) {
            error = ops->init(net);
            if (error < 0)
                goto out_undo;
        }
    }

在这个循环中会从已经注册到pernet_list队列中依次通过其钩子结构pernet_operations,调用结构中的钩子函数init对这个net网络命名空间结构进行初始设置。而这里的pernet_list是一个队列头,它在/net/core/Net_namespace.c文件中的16行处这个队列专门用于person net 用户自定义的pernet_operations结构的登记,也就是我们上面提过的CONFIG_NET_NS情形时才可以将用户自定义的pernet_operations结构链入队列。

 

static LIST_HEAD(pernet_list);

而这里的LIST_HEAD宏在include/linux/list.h

 

#define LIST_HEAD(name) \
    struct list_head name = LIST_HEAD_INIT(name)
#define LIST_HEAD_INIT(name) { &(name), &(name) }
struct list_head {
    struct list_head *next, *prev;
};

朋友们可能注意到我经常将调用在前,说明在后的排列数据结构说明,这种习惯个人感觉有助于理解和记忆。“带着问题学习”远比摸黑效率高。那么这个结构是什么时候被初始化的呢?我们结合本类的第一节关于socket的初始化中提到的initcall机制会看到在/net/core/dev.c中有一句

subsys_initcall(net_dev_init);

他引用了include/linux/init.h中的

#define subsys_initcall(fn)      __define_initcall("4",fn,4)

所以按照第一节中提到的initcall机制那部分内容,net_dev_init函数将会在开机时得到执行,在这个函数内部有这样的代码:

 

if (register_pernet_subsys(&netdev_net_ops))
        goto out;

    if (register_pernet_device(&default_device_ops))
        goto out;

上面代码中调用的二个函数都会调用register_pernet_operations函数

int register_pernet_subsys(struct pernet_operations *ops)
{
    int error;
    mutex_lock(&net_mutex);
    error = register_pernet_operations(first_device, ops);
    mutex_unlock(&net_mutex);
    return error;
}
int register_pernet_device(struct pernet_operations *ops)
{
    int error;
    mutex_lock(&net_mutex);
    error = register_pernet_operations(&pernet_list, ops);
    if (!error && (first_device == &pernet_list))
        first_device = &ops->list;
    mutex_unlock(&net_mutex);
    return error;
}

我们在上面第一个函数register_pernet_subsys中看到了first_device这是在Net_namespace.c中17行处声明的

static struct list_head *first_device = &pernet_list;
进入register_pernet_operations我们看一下

static int register_pernet_operations(struct list_head *list,
                 struct pernet_operations *ops)
{
    if (ops->init == NULL)
        return 0;
    return ops->init(&init_net);
}

在Net_namespace.c中还有另一个同名的函数但是那必须在支持NET_NS用户自定义网络的选择项打开情况下,我们上面提到过了,所以这里会进入net_dev_init函数中层层传递下来的netdev_net_ops钩子结构,执行这个结构中的钩子函数init,我们看一下这个结构

static struct pernet_operations __net_initdata netdev_net_ops = {
    .init = netdev_init,
    .exit = netdev_exit,
};

显然是进入了netdev_init函数,在进入函数之前我们看到它是对系统默认的网络空间结构变量init_net进行的操作

/* Initialize per network namespace state */
static int __net_init netdev_init(struct net *net)
{
    INIT_LIST_HEAD(&net->dev_base_head);

    net->dev_name_head = netdev_create_hash();
    if (net->dev_name_head == NULL)
        goto err_name;

    net->dev_index_head = netdev_create_hash();
    if (net->dev_index_head == NULL)
        goto err_idx;

    return 0;

err_idx:
    kfree(net->dev_name_head);
err_name:
    return -ENOMEM;
}

我是无名小卒本文是我原创完成,尽管083月份才开始写文章但是已经得到了朋友们认可,文章也得到了转载,所以请转载的朋友注明出处http://qinjiana07876.cublog.cn,我们在上面的函数中看到初始化了init_net的几个队列头,其中有二个是哈希队列。

static inline void INIT_LIST_HEAD(struct list_head *list)
{
    list->next = list;
    list->prev = list;
}

static struct hlist_head *netdev_create_hash(void)
{
    int i;
    struct hlist_head *hash;

    hash = kmalloc(sizeof(*hash) * NETDEV_HASHENTRIES, GFP_KERNEL);
    if (hash != NULL)
        for (= 0; i < NETDEV_HASHENTRIES; i++)
            INIT_HLIST_HEAD(&hash[i]);

    return hash;
}

这二个函数对队列头进行了初始化,上面函数是对初始化普通的链表头,下面函数是初始化hash队列头。函数很简单。这二个函数分别在include/linux/list.h和/net/core/dev.c中。接下来我们再看上面已经贴出的register_pernet_device函数调用register_pernet_operations时传递的是default_device_ops钩子结构,会执行这个结构中的init钩子函

static struct pernet_operations __net_initdata default_device_ops = {
    .exit = default_device_exit,
};

但是我们上面看到没有设置init钩子函数,那么看一下上面的register_pernet_operations函数,它在直接返回0了,然后在register_pernet_device函数中就会执行

    if (!error && (first_device == &pernet_list))
        first_device = &ops->list;

但是我们看到first_device在这里将会指向default_device_ops中的队列头list。到这里我们只是看到了对init_net的几个队列头的初始化,那么还有没有其他重要的初始化呢,如何对init_net的网络结构进行的设置的。我们后边随着代码的逐渐深入,将会看到这个重要的数据结构在多个地方进行了设置和调整,我是无名小卒,请转载的朋友注明出处,http://qinjiana0786.cublog.cn,我们如果联合想一下内核的设置就能明白了,当你在make内核之前,在对内核进行设置选择使用何种网络时根据你对内核的网络选项,内核会有选择的使用ipv4还是使用ipv6的互联网协议。显然我们练习中使用的是ipv4,所以我们在内核编译时会执行下面几个初始化过程,这些过程是根据你对内核的配置而定。

 

1、首先是IP协议的初始化安装ipip_init()函数,这个函数在/net/ipv4/ipip.c839行处,其次是GRE通用路由协议的初始化安装ipgre_init()函数(/net/ipv4/ip_gre.c),以及IEEE的802.1Q VLAN协议的初始化安装vlan_proto_init()(/net/8021q/vlan.c)还有通用的TUN/TAP设备驱动程序中的tun_init()初始化函数(/driversnet/tun.c)。

 

2、如果朋友们使用的是ipv6的话就会执行ipv6的通道设备初始化ip6_tunnel_init()函数(/net/ipv6/ip6_tunnel.c)、SIT互联网协议的sit_init()(同上目录中的sit.c)、以及上面与ipv4一样的通用的TUN/TAP设备驱动程序中的tun_init()初始化函数(目录同上)和IEEE的802.1Q VLAN协议的初始化安装vlan_proto_init()初始化函数(目录同上)。

我们这里只针对ipv4所以,只看第一项列出的几个初始化函数,那些初始化函数都非常重要,他们都会在函数内部调用register_pernet_gen_device()函数来初始化全局的init_net网络空间结构。我们先看ipip_init()我们只关心这里重要的对其函数其他部分暂且不做分析

static int __init ipip_init(void)
{
。。。。。。
    err = register_pernet_gen_device(&ipip_net_id, &ipip_net_ops);
。。。。。。
}

可以看到他向register_pernet_gen_device传递了二个参数结构ipip_net_idipip_net_ops

static int ipip_net_id;
static struct pernet_operations ipip_net_ops = {
    .init = ipip_init_net,
    .exit = ipip_exit_net,
};

我们进入register_pernet_gen_device函数中看是如何初始化的

int register_pernet_gen_device(int *id, struct pernet_operations *ops)
{
    int error;
    mutex_lock(&net_mutex);
again:
    error = ida_get_new_above(&net_generic_ids, 1, id);
    if (error) {
        if (error == -EAGAIN) {
            ida_pre_get(&net_generic_ids, GFP_KERNEL);
            goto again;
        }
        goto out;
    }
    error = register_pernet_operations(&pernet_list, ops);
    if (error)
        ida_remove(&net_generic_ids, *id);
    else if (first_device == &pernet_list)
        first_device = &ops->list;
out:
    mutex_unlock(&net_mutex);
    return error;

我们暂时只关心与init_net相关的函数,上面代码中也是调用了register_pernet_operations函数只不过这次传递进去的opsipip_net_ops,我们在上面看过了register_pernet_operations,他是首先判断ipip_net_opsinit钩子是否链入了,我们看到上面他的结构中是ipip_init_net函数,那么就要进一步调用这个函数初始化我们这里的init_net网络命名空间。

 

static int ipip_init_net(struct net *net)
{
    int err;
    struct ipip_net *ipn;

    err = -ENOMEM;
    ipn = kzalloc(sizeof(struct ipip_net), GFP_KERNEL);
    if (ipn == NULL)
        goto err_alloc;

    err = net_assign_generic(net, ipip_net_id, ipn);
    if (err < 0)
        goto err_assign;

    ipn->tunnels[0] = ipn->tunnels_wc;
    ipn->tunnels[1] = ipn->tunnels_l;
    ipn->tunnels[2] = ipn->tunnels_r;
    ipn->tunnels[3] = ipn->tunnels_r_l;

    ipn->fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel),
                     "tunl0",
                     ipip_tunnel_setup);
    if (!ipn->fb_tunnel_dev) {
        err = -ENOMEM;
        goto err_alloc_dev;
    }

    ipn->fb_tunnel_dev->init = ipip_fb_tunnel_init;
    dev_net_set(ipn->fb_tunnel_dev, net);

    if ((err = register_netdev(ipn->fb_tunnel_dev)))
        goto err_reg_dev;

    return 0;

err_reg_dev:
    free_netdev(ipn->fb_tunnel_dev);
err_alloc_dev:
    /* nothing */
err_assign:
    kfree(ipn);
err_alloc:
    return err;
}

这里有了一个新的结构struct ipip_net,是专门用于ip协议专用的数据结构

struct ipip_net {
    struct ip_tunnel *tunnels_r_l[HASH_SIZE];
    struct ip_tunnel *tunnels_r[HASH_SIZE];
    struct ip_tunnel *tunnels_l[HASH_SIZE];
    struct ip_tunnel *tunnels_wc[1];
    struct ip_tunnel **tunnels[4];

    struct net_device *fb_tunnel_dev;
};

其结构内部还包含IP通道的结构变量

struct ip_tunnel
{
    struct ip_tunnel    *next;
    struct net_device    *dev;
    struct net_device_stats    stat;

    int            recursion;    /* Depth of hard_start_xmit recursion */
    int            err_count;    /* Number of arrived ICMP errors */
    unsigned long        err_time;    /* Time when the last ICMP error arrived */

    /* These four fields used only by GRE */
    __u32            i_seqno;    /* The last seen seqno    */
    __u32            o_seqno;    /* The last output seqno */
    int            hlen;        /* Precalculated GRE header length */
    int            mlink;

    struct ip_tunnel_parm    parms;

    struct ip_tunnel_prl_entry    *prl;        /* potential router list */
    unsigned int            prl_count;    /* # of entries in PRL */
};

我们先不解释他的作用了,具体的作用会在使用过程中了解的,接着为进入net_assign_generic函数为init_net->gen也就是struct net_generic结构变量分配空间,并将参数ipip_net_id和参数结构ipn赋值到gen中,注意ipn是我们刚刚上面分配的struct ipip_net,我们可以从 init_net->gen->ptr数组的以ipip_net_id为下标找到这里分配的IP协议结构struct ipip_net。所以struct net_generic结构是一个通用的数据结构

struct net_generic {
    unsigned int len;
    struct rcu_head rcu;

    void *ptr[0];
};

这个结构的定义在/net/netns/generic.h中,正象注释说的那样他的作用是减少了一些专用数据结构的声明。很多数据可以共用这个能用的数据结构。我是无名小卒,http://qinjiana0786.cublog.cn,转载请注明此出处。这也是他的结构名称的由来。

回到ipip_init_net函数中我们继续往下看

ipn->tunnels[0] = ipn->tunnels_wc;
    ipn->tunnels[1] = ipn->tunnels_l;
    ipn->tunnels[2] = ipn->tunnels_r;
    ipn->tunnels[3] = ipn->tunnels_r_l;

这段代码是对ipip_net结构中的ip通道数组进行设置,使双重指针数组中的元素,也就是队列头指针分别与ipip_net中其他四个ip通道的队列数组挂上钩。我们上面看到了可以通过init_net全局的网络命名空间结构找到这个ipip_net结构变量ipn,请朋友们注意这里。在struct ipip_net结构中还有一个网络设备的结构体变量struct net_device*fb_tunnel_dev,函数中,struct net_device结构体很大,我们不贴了,随着代码的阅读会越来越清楚他的作用。在这里调用了alloc_netdev()函数

这是个宏声明在/incluce/linux/netdevice.h

#define alloc_netdev(sizeof_priv, name, setup) \
    alloc_netdev_mq(sizeof_priv, name, setup, 1)

然后他转向了/net/core/dev.c中的函数

 

 

struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
        void (*setup)(struct net_device *), unsigned int queue_count)
{
    void *p;
    struct net_device *dev;
    int alloc_size;

    BUG_ON(strlen(name) >= sizeof(dev->name));

    alloc_size = sizeof(struct net_device) +
         sizeof(struct net_device_subqueue) * (queue_count - 1);
    if (sizeof_priv) {
        /* ensure 32-byte alignment of private area */
        alloc_size = (alloc_size + NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST;
        alloc_size += sizeof_priv;
    }
    /* ensure 32-byte alignment of whole construct */
    alloc_size += NETDEV_ALIGN_CONST;

    p = kzalloc(alloc_size, GFP_KERNEL);
    if (!p) {
        printk(KERN_ERR "alloc_netdev: Unable to allocate device.\n");
        return NULL;
    }

    dev = (struct net_device *)
        (((long)+ NETDEV_ALIGN_CONST) & ~NETDEV_ALIGN_CONST);
    dev->padded = (char *)dev - (char *)p;
    dev_net_set(dev, &init_net);

    if (sizeof_priv) {
        dev->priv = ((char *)dev +
             ((sizeof(struct net_device) +
             (sizeof(struct net_device_subqueue) *
                (queue_count - 1)) + NETDEV_ALIGN_CONST)
             & ~NETDEV_ALIGN_CONST));
    }

    dev->egress_subqueue_count = queue_count;
    dev->gso_max_size = GSO_MAX_SIZE;

    dev->get_stats = internal_stats;
    netpoll_netdev_init(dev);
    setup(dev);
    strcpy(dev->name, name);
    return dev;
}

这个函数的主要使用就是分配网络设备结构,并初始化它,函数首先确定要分配一块内存的大小,然后分配内存用于struct net_device结构变量dev使用,我们看到他调用了一个与init_net网络空间结构相关的操作dev_net_set(),但是我们追踪进去发现他是在CONFIG_NET_NS配置情况下才会得到执行,在这里也只是一个空操作,函数最后调用ipip_tunnel_setup()对dev进一步的初始化。并且为dev复制了一个名称“tunl0”。函数返回后,我们在ipip_init_net()函数中的ipn结构变量其中的fb_tunnel_dev也就自此有了具体的指向。最后再进一步对这个网络设备结构初始化和“登记”工作。

ipn->fb_tunnel_dev->init = ipip_fb_tunnel_init;
    dev_net_set(ipn->fb_tunnel_dev, net);

    if ((err = register_netdev(ipn->fb_tunnel_dev)))
        goto err_reg_dev;

注意上面的init钩子函数的挂入了ipip_fb_tunnel_init,一会我们会用到。我们进入register_netdev()函数

int register_netdev(struct net_device *dev)
{
    int err;

    rtnl_lock();

    /*
     * If the name is a format string the caller wants us to do a
     * name allocation.
     */

    if (strchr(dev->name, '%')) {
        err = dev_alloc_name(dev, dev->name);
        if (err < 0)
            goto out;
    }

    err = register_netdevice(dev);
out:
    rtnl_unlock();
    return err;
}

这个函数的主要做用就是注册网络设备到内核,我们先跳过锁的操作,如果设备名称中含有“%”号就代表着让内核自动分配一个名称。我们知道上面已经有一个名称“tunl0”了。所以不用分配,接着函数进入了register_netdevice(),这是个比较大的函数,我们分段来看

int register_netdevice(struct net_device *dev)
{
    struct hlist_head *head;
    struct hlist_node *p;
    int ret;
    struct net *net;

    BUG_ON(dev_boot_phase);
    ASSERT_RTNL();

    might_sleep();

    /* When net_device's are persistent, this will be fatal. */
    BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
    BUG_ON(!dev_net(dev));
    net = dev_net(dev);

    spin_lock_init(&dev->queue_lock);
    spin_lock_init(&dev->_xmit_lock);
    netdev_set_lockdep_class(&dev->_xmit_lock, dev->type);
    dev->xmit_lock_owner = -1;
    spin_lock_init(&dev->ingress_lock);

    dev->iflink = -1;

    /* Init, if this function is available */
    if (dev->init) {
        ret = dev->init(dev);
        if (ret) {
            if (ret > 0)
                ret = -EIO;
            goto out;
        }
    }

    if (!dev_valid_name(dev->name)) {
        ret = -EINVAL;
        goto err_uninit;
    }

    dev->ifindex = dev_new_index(net);
    if (dev->iflink == -1)
        dev->iflink = dev->ifindex;
上面代码中,首先取得了init_net网络空间的结构指针
static inline
struct net *dev_net(const struct net_device *dev)
{
#ifdef CONFIG_NET_NS
    return dev->nd_net;
#else
    return &init_net;
#endif
}

接着要调用dev中设置初始化函数,我们在前面提醒过朋友们,在那里间接设置了ipip_fb_tunnel_init(),所以会进入这个函数,它在/net/ipv4/ipip.c中的736

static int ipip_fb_tunnel_init(struct net_device *dev)
{
    struct ip_tunnel *tunnel = netdev_priv(dev);
    struct iphdr *iph = &tunnel->parms.iph;
    struct ipip_net *ipn = net_generic(dev_net(dev), ipip_net_id);

    tunnel->dev = dev;
    strcpy(tunnel->parms.name, dev->name);

    iph->version        = 4;
    iph->protocol        = IPPROTO_IPIP;
    iph->ihl        = 5;

    dev_hold(dev);
    ipn->tunnels_wc[0]    = tunnel;
    return 0;
}

这个函数首先是取得IP通道
static inline void *netdev_priv(const struct net_device *dev)
{
    return dev->priv;
}

对照一下之前的设置,在前面分配struct net_devicedev调用alloc_netdev_mq()函数时,我们再贴一下代码

struct net_device *alloc_netdev_mq(int sizeof_priv, const char *name,
        void (*setup)(struct net_device *), unsigned int queue_count)
{
。。。。。。
    if (sizeof_priv) {
        dev->priv = ((char *)dev +
             ((sizeof(struct net_device) +
             (sizeof(struct net_device_subqueue) *
                (queue_count - 1)) + NETDEV_ALIGN_CONST)
             & ~NETDEV_ALIGN_CONST));
    }
。。。。。。

我们看到一个结构struct net_device_subqueue

struct net_device_subqueue
{
    /* Give a control state for each queue. This struct may contain
     * per-queue locks in the future.
     */

    unsigned long state;
};

他只包含一变量,所以这个结构实际是网络设备结构的一个辅助结构,从字面上看是设备队列使用的,但是没有看到其包含任何的队列头。queue_count在参数传递下为是1,而NETDEV_ALIGN_CONST是个宏定义

 

#define    NETDEV_ALIGN           32

#define    NETDEV_ALIGN_CONST     (NETDEV_ALIGN - 1)

所以上面的宏是为了保持地址对齐使用的,这样就能看懂了dev->priv所指向的位置是与dev地址相差一个struct net_device结构大小的地址处。所以回到上面ipip_fb_tunnel_init()函数中调用netdev_priv()就取得了这个地址,然后将这段地址做struct ip_tunnelIP通道结构起始地址,在ip通道结构中有一个代表其参数的数据结构

struct ip_tunnel_parm
{
    char            name[IFNAMSIZ];
    int            link;
    __be16            i_flags;
    __be16            o_flags;
    __be32            i_key;
    __be32            o_key;
    struct iphdr        iph;
};
参数中最后一个是IP头部结构变量。
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u8    ihl:4,
        version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
    __u8    version:4,
          ihl:4;
#else
#error    "Please fix <asm/byteorder.h>"
#endif
    __u8    tos;
    __be16    tot_len;
    __be16    id;
    __be16    frag_off;
    __u8    ttl;
    __u8    protocol;
    __sum16    check;
    __be32    saddr;
    __be32    daddr;
    /*The options start here. */
};

上面IP头部结构中变量根据应用我们自然会明白其作用和含义。

所以函数中的struct iphdr *iph指了IP头部。接着我们看到

struct ipip_net *ipn = net_generic(dev_net(dev), ipip_net_id);

这是取得我们在ipip_init_net()函数中已经初始化的ipn结构变量。函数首先是让靠在dev后边的ip通道与dev挂上关系,紧接着复制网络设备名称到ip通道的参数结构ip_tunnel中。接着对ip头部结构进行了设置

 

       iph->version           = 4;//表示ip版本是ipv4

       iph->protocol         = IPPROTO_IPIP;//使用IPIP通道

       iph->ihl           = 5;//ip头部的长度是5

然后让ipnip结构中的通道指针数组指向这里的ip通道

ipn->tunnels_wc[0] = tunnel;

初始化dev结构以后,我们回到register_netdevice()函数代码中,下面是检查init_net中的hash队列头是否已经存在该网络设备。

其具体过程请朋友们自已追踪了。我们为了不离了主题,层层返回到ipip_init()函数中,也就完成了IP协议的初始化过程。我们已经看到了其对init_net的初始化部分。很明显init_net的重要性不言而喻。


转自:http://blog.chinaunix.net/uid-7960587-id-2035550.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值