免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1437 | 回复: 0
打印 上一主题 下一主题

内核中的TCP的追踪分析-20-TCP(IPV4)的socket请求连接 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-01-19 08:52 |只看该作者 |倒序浏览

在前面的章节中我们围绕着服务器端来进行了分析,回忆一下我们先是从练习程序开始围绕着服务器端的socket的创建、绑定、监听、接收过程对内核的tcp/ip源码进行了分析,下面我们要看一下客户端的练习程序,我们将要围绕着这个客户端的练习程序来继续我们的tcp/ip分析,首先请朋友们看一下[从一个练习开始]那节中的“地图”我们说过,这是我们游览的地图,尽管前面我们也在一些“风景”上驻足了很久,尤其是路由的设置过程,我们用了大量的篇幅对这个重要的“景点”进行了分析和探讨,这里还是让我们在地图的导游下先开始客户端的练习程序吧,这必竟是下面几篇文章的线索

#include sys/types.h>
#include sys/socket.h>
#include stdio.h>
#include netinet/in.h>
#include arpa/inet.h>
#include unistd.h>
#include stdlib.h>
int main()
{
    int client_fd;
    int client_len;
    struct sockaddr_in server_address;
    int result;
    char *temp = "HELLO,WUMINGXIAOZU";
同样要创建一个客户端的socket,然后用这个socket去向服务器的socket连接去
    client_fd= socket(AF_INET, SOCK_STREAM, 0);
为建立的socket设定电话号码,这个电话号码要与服务器端的socket号码一致,准备连接使用
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("192.168.1.1");
    server_address.sin_port = 9266;
    client_len= sizeof(server_address);
使用电话号码向服务器端拨号,请求服务器的socket连接
    result = connect(client_fd, (struct sockaddr *)&server_address, client_len);
    if(result == -1) {
        perror("error: client failed");
        exit(1);
    }
到这里我们连接成功了,就对建立的socket发送一个字符然后接收服务器端返回的一个字符并打印了到控制台
    write(client_fd, temp, 20);
    read(client_fd, temp, 20);
    printf("char from server = %s\n", temp);
    close(client_fd);
    exit(0);
}
这里我们运行一下程序,看一下与第一节的服务器端的练习运行后的效果
[root@localhost wumingxiaozu]# ./server2 &
[root@localhost wumingxiaozu]# ./client2
char from server = HELLO,WUMINGXIAOZU
我们看到执行的效果了,与我们程序预测的是一样的,那么上面的客户端的练习程序socket()函数我们在前面的章节中已经分析过了,按照地图中左边的服务器端的过程也已经分析到与客户端接收连接请求的“景点”了,只是地图右边的客户端的connect()函数我们还没有看到,这是我们要重点进行的过程,我们还是先从练习中的代码看“界面”,有朋友说为什么称为界面,不称为接口,这主要因为接口是对于库来说的,而我们的界面是指应用程序的代码语句,无论称之为界面还是接口,只要能让大家理解到这是应用程序(我们的练习程序)的语句内容
connect(client_fd, (struct sockaddr *)&server_address, client_len)
我们看到第二个参数是从应用程序中(有时也称为练习程序)初始化设置的
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr("192.168.1.1");
    server_address.sin_port = 9266;
sockaddr_in 和sockadd结构我们之前说过了,这里我们直接进入系统调用总入口sys_socketcall()看一下
case SYS_CONNECT:
err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);
break;
上面应用程序界面的参数全部传递到了这里,我们追踪sys_connect()

sys_socketcall()-->sys_connect()
asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr,
             int addrlen)
{
    struct socket *sock;
    char address[MAX_SOCK_ADDR];
    int err, fput_needed;
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;
    err = move_addr_to_kernel(uservaddr, addrlen, address);
    if (err  0)
        goto out_put;
    err =
     security_socket_connect(sock, (struct sockaddr *)address, addrlen);
    if (err)
        goto out_put;
    err = sock->ops->connect(sock, (struct sockaddr *)address, addrlen,
                 sock->file->f_flags);
out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
}
象在[TCP/IP的socket地址设置]那节中讲述的一样,这里我们还是从err = sock->ops->connect(sock, (struct sockaddr *)address, addrlen,sock->file->f_flags)看起,其余部分请朋友们参阅以前内容,那些函数都在前面分析过了,在地址设备那节中我们看到是将socket的ops通过answer结构变量转接入了inet_stream_ops,所以这里会跳入这个钩子结构去执行,我们再看一下这个结构中相关的钩子函数
const struct proto_ops inet_stream_ops = {
......
    .connect     = inet_stream_connect,
......
    };
结构中其他的部分我们暂且不要关心,也就是说会执行钩子函数inet_stream_connect。

sys_socketcall()-->sys_connect()-->inet_stream_connect()
int inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,
            int addr_len, int flags)
{
    struct sock *sk = sock->sk;
    int err;
    long timeo;
    lock_sock(sk);
    if (uaddr->sa_family == AF_UNSPEC) {
        err = sk->sk_prot->disconnect(sk, flags);
        sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
        goto out;
    }
    switch (sock->state) {
    default:
        err = -EINVAL;
        goto out;
    case SS_CONNECTED:
        err = -EISCONN;
        goto out;
    case SS_CONNECTING:
        err = -EALREADY;
        /* Fall out of switch with err, set for this state */
        break;
    case SS_UNCONNECTED:
        err = -EISCONN;
        if (sk->sk_state != TCP_CLOSE)
            goto out;
        err = sk->sk_prot->connect(sk, uaddr, addr_len);
        if (err  0)
            goto out;
        sock->state = SS_CONNECTING;
        /* Just entered SS_CONNECTING state; the only
         * difference is that return value in non-blocking
         * case is EINPROGRESS, rather than EALREADY.
         */
        err = -EINPROGRESS;
        break;
    }
    timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
    if ((1  sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
        /* Error code is set above */
        if (!timeo || !inet_wait_for_connect(sk, timeo))
            goto out;
        err = sock_intr_errno(timeo);
        if (signal_pending(current))
            goto out;
    }
    /* Connection was closed by RST, timeout, ICMP error
     * or another process disconnected us.
     */
    if (sk->sk_state == TCP_CLOSE)
        goto sock_error;
    /* sk->sk_err may be not zero now, if RECVERR was ordered by user
     * and error was received after socket entered established state.
     * Hence, it is handled normally after connect() return successfully.
     */
    sock->state = SS_CONNECTED;
    err = 0;
out:
    release_sock(sk);
    return err;
sock_error:
    err = sock_error(sk) ? : -ECONNABORTED;
    sock->state = SS_UNCONNECTED;
    if (sk->sk_prot->disconnect(sk, flags))
        sock->state = SS_DISCONNECTING;
    goto out;
}
这个函数先是对sock结构加锁,然后判断我们在练习中设置的地址是否符合要求,接下来要根据sock的状态来执行switch语句代码,如果回忆一下在创建socket的过程中,朋友们可以看[TCP/IP的socket创建]那篇文章也可以参阅我的博客中的
http://blog.chinaunix.net/u2/64681/showart.php?id=1685658
,在那里我们看到会通过inet_create()函数将新创建的sock结构的state状态标志设为了SS_UNCONNECTED,意思是还没有连接,显然,会执行SS_UNCONNECTED相关的语句,我们看到使用sock中的sk_prot钩子结构来调用connect,而sk_prot则在创建socket时我们说到了他是如何挂钩的,这里直接进入挂入的tcp_prot结构变量中的钩子函数,它是运输层的结构体
struct proto tcp_prot = {
    。。。。。。
    .connect        = tcp_v4_connect,
    。。。。。。
};
显然是进入tcp_v4_connect()函数中,我们分段来看

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
    struct inet_sock *inet = inet_sk(sk);
    struct tcp_sock *tp = tcp_sk(sk);
    struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
    struct rtable *rt;
    __be32 daddr, nexthop;
    int tmp;
    int err;
    if (addr_len  sizeof(struct sockaddr_in))
        return -EINVAL;
    if (usin->sin_family != AF_INET)
        return -EAFNOSUPPORT;
    nexthop = daddr = usin->sin_addr.s_addr;
    if (inet->opt && inet->opt->srr) {
        if (!daddr)
            return -EINVAL;
        nexthop = inet->opt->faddr;
    }
    tmp = ip_route_connect(&rt, nexthop, inet->saddr,
             RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
             IPPROTO_TCP,
             inet->sport, usin->sin_port, sk, 1);
    if (tmp  0) {
        if (tmp == -ENETUNREACH)
            IP_INC_STATS_BH(IPSTATS_MIB_OUTNOROUTES);
        return tmp;
    }
    if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
        ip_rt_put(rt);
        return -ENETUNREACH;
    }
    if (!inet->opt || !inet->opt->srr)
        daddr = rt->rt_dst;
    if (!inet->saddr)
        inet->saddr = rt->rt_src;
    inet->rcv_saddr = inet->saddr;
    if (tp->rx_opt.ts_recent_stamp && inet->daddr != daddr) {
        /* Reset inherited state */
        tp->rx_opt.ts_recent     = 0;
        tp->rx_opt.ts_recent_stamp = 0;
        tp->write_seq         = 0;
    }
我们知道参数uaddr是从用户空间传递过来的地址结构变量,这里转变成IP地址结构struct sockaddr_in,这种结构我们已经在以前看过了,首先是对这个地址结构的检测是否是IP协议,此后是检测地址类型,接着进入ip_route_connect()函数这是与路由相关的函数

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()
static inline int ip_route_connect(struct rtable **rp, __be32 dst,
                 __be32 src, u32 tos, int oif, u8 protocol,
                 __be16 sport, __be16 dport, struct sock *sk,
                 int flags)
{
    struct flowi fl = { .oif = oif,
             .mark = sk->sk_mark,
             .nl_u = { .ip4_u = { .daddr = dst,
                         .saddr = src,
                         .tos = tos } },
             .proto = protocol,
             .uli_u = { .ports =
                 { .sport = sport,
                     .dport = dport } } };
    int err;
    struct net *net = sock_net(sk);
    if (!dst || !src) {
        err = __ip_route_output_key(net, rp, &fl);
        if (err)
            return err;
        fl.fl4_dst = (*rp)->rt_dst;
        fl.fl4_src = (*rp)->rt_src;
        ip_rt_put(*rp);
        *rp = NULL;
    }
    security_sk_classify_flow(sk, &fl);
    return ip_route_output_flow(net, rp, &fl, sk, flags);
}
我们在函数头部看到一个数据结构struct flowi这是个专门用于路由的键值,我们在前面的路由分析过程中提到过这个数据结构,我们把它列在下面让游客们“欣赏”,在使用的过程中可以对照参考
struct flowi {
    int    oif;
    int    iif;
    __u32    mark;
    union {
        struct {
            __be32            daddr;
            __be32            saddr;
            __u8            tos;
            __u8            scope;
        } ip4_u;
        
        struct {
            struct in6_addr        daddr;
            struct in6_addr        saddr;
            __be32            flowlabel;
        } ip6_u;
        struct {
            __le16            daddr;
            __le16            saddr;
            __u8            scope;
        } dn_u;
    } nl_u;
#define fld_dst        nl_u.dn_u.daddr
#define fld_src        nl_u.dn_u.saddr
#define fld_scope    nl_u.dn_u.scope
#define fl6_dst        nl_u.ip6_u.daddr
#define fl6_src        nl_u.ip6_u.saddr
#define fl6_flowlabel    nl_u.ip6_u.flowlabel
#define fl4_dst        nl_u.ip4_u.daddr
#define fl4_src        nl_u.ip4_u.saddr
#define fl4_tos        nl_u.ip4_u.tos
#define fl4_scope    nl_u.ip4_u.scope
    __u8    proto;
    __u8    flags;
    union {
        struct {
            __be16    sport;
            __be16    dport;
        } ports;
        struct {
            __u8    type;
            __u8    code;
        } icmpt;
        struct {
            __le16    sport;
            __le16    dport;
        } dnports;
        __be32        spi;
        struct {
            __u8    type;
        } mht;
    } uli_u;
#define fl_ip_sport    uli_u.ports.sport
#define fl_ip_dport    uli_u.ports.dport
#define fl_icmp_type    uli_u.icmpt.type
#define fl_icmp_code    uli_u.icmpt.code
#define fl_ipsec_spi    uli_u.spi
#define fl_mh_type    uli_u.mht.type
    __u32 secid;    /* used by xfrm; see secid.txt */
} __attribute__((__aligned__(BITS_PER_LONG/8)));
这里很显然我们需要先看参数才能搞明白路由函数的过程,从tcp_v4_connect函数传递的参数来看

tmp = ip_route_connect(&rt, nexthop, inet->saddr,
             RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
             IPPROTO_TCP,
             inet->sport, usin->sin_port, sk, 1);
分别是rt,它是一个查询路由表使用的结构,这个结构是为了封装在内存的高速缓存的路由表而使用的,我们把他的结构贴在下面
struct rtable
{
    union
    {
        struct dst_entry    dst;
    } u;
    /* Cache lookup keys */
    struct flowi        fl;
    struct in_device    *idev;
   
    int            rt_genid;
    unsigned        rt_flags;
    __u16            rt_type;
    __be32            rt_dst;    /* Path destination    */
    __be32            rt_src;    /* Path source        */
    int            rt_iif;
    /* Info on neighbour */
    __be32            rt_gateway;
    /* Miscellaneous cached information */
    __be32            rt_spec_dst; /* RFC1122 specific destination */
    struct inet_peer    *peer; /* long-living peer info */
};
然后nexthop是服务器的地址,即目标地址192.168.1.1,inet->saddr是我们客户端的IP地址,这是在客户端处设置的,朋友们可以参考服务器的IP设置的过程想一下客户端应该是与我们前面看到过的绑定过程一样,RT_CONN_FLAGS宏是根据sock中的tos和SOCK_LOCALROUTE状态位来确定最终的tos,tos就是type of service的意思,即服务类型。sk->sk_bound_dev_if是socket绑定到的设备,IPPROTO_TCP宏是确定使用的传输控制协议,inet->sport是客户端的socket的端口,而usin->sin_port则是服务器端口即目标端口。上面这些参数一一对应ip_route_connect()函数的参数。我们看到将这些参数在ip_route_connect()函数中设置到了路由的键值结构变量fl中了。然后取得网络命名空间结构,在我们这里取得的是全局变量init_net,如果地址类型是多播或者是广播的话那么我们这里源地址就是0,这个是在绑定地址章节中看到过的,所以会进入__ip_route_output_key()函数中,这个函数是根据我们上面设置的路由键值查找适用的路由表

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> __ip_route_output_key()
int __ip_route_output_key(struct net *net, struct rtable **rp,
             const struct flowi *flp)
{
    unsigned hash;
    struct rtable *rth;
    hash = rt_hash(flp->fl4_dst, flp->fl4_src, flp->oif);
    rcu_read_lock_bh();
    for (rth = rcu_dereference(rt_hash_table[hash].chain); rth;
        rth = rcu_dereference(rth->u.dst.rt_next)) {
        if (rth->fl.fl4_dst == flp->fl4_dst &&
         rth->fl.fl4_src == flp->fl4_src &&
         rth->fl.iif == 0 &&
         rth->fl.oif == flp->oif &&
         rth->fl.mark == flp->mark &&
         !((rth->fl.fl4_tos ^ flp->fl4_tos) &
             (IPTOS_RT_MASK | RTO_ONLINK)) &&
         net_eq(dev_net(rth->u.dst.dev), net) &&
         rth->rt_genid == atomic_read(&rt_genid)) {
            dst_use(&rth->u.dst, jiffies);
            RT_CACHE_STAT_INC(out_hit);
            rcu_read_unlock_bh();
            *rp = rth;
            return 0;
        }
        RT_CACHE_STAT_INC(out_hlist_search);
    }
    rcu_read_unlock_bh();
    return ip_route_output_slow(net, rp, flp);
}
函数首先根据路由键值确定一个hash值,然后在rt_hash_table的杂凑表中找到适用的路由表,朋友们可以自已阅读这段代码,如果找到了就会返回0,如果没有找到就会进入ip_route_output_slow()函数,这个函数总的来说就是再建立一个新的路由键值,然后根据我们这里的键值进行一系列的初始化操作,最后根据这个键值进入fib_lookup()函数在我们全局的网络空间依次查找适用的路由表,最后找到路由表并建立缓存后通过rp参数得到了路由表。我们来分析这个ip_route_output_slow()函数,函数比较大我们逐段分析

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> __ip_route_output_key()-->ip_route_output_slow()
static int ip_route_output_slow(struct net *net, struct rtable **rp,
                const struct flowi *oldflp)
{
    u32 tos    = RT_FL_TOS(oldflp);
    struct flowi fl = { .nl_u = { .ip4_u =
                 { .daddr = oldflp->fl4_dst,
                    .saddr = oldflp->fl4_src,
                    .tos = tos & IPTOS_RT_MASK,
                    .scope = ((tos & RTO_ONLINK) ?
                         RT_SCOPE_LINK :
                         RT_SCOPE_UNIVERSE),
                 } },
             .mark = oldflp->mark,
             .iif = net->loopback_dev->ifindex,
             .oif = oldflp->oif };
    struct fib_result res;
    unsigned flags = 0;
    struct net_device *dev_out = NULL;
    int free_res = 0;
    int err;
    res.fi        = NULL;
#ifdef CONFIG_IP_MULTIPLE_TABLES
    res.r        = NULL;
#endif
我们先看一下参数,net我们知道是init_net全局的网络空间结构,而rp是我们要找到的路由表的地址,所以他用了双指针。而oldflp则是传递下来的ip_route_connect()函数中准备的路由键值结构变量的指针。我们看到上面函数开始再次初始化了一个新的路由键值fl变量。它是根据传递下来的键值来设置的。我们上面也已经分别对这些字段进行了说明这些都是关于服务器的地址以及客户端的地址、服务类型值等内容。还声明了一个路由查找结构变量res,这个结构我们以前讲述过了。我们接着往下看
if (oldflp->fl4_src) {
        err = -EINVAL;
        if (ipv4_is_multicast(oldflp->fl4_src) ||
         ipv4_is_lbcast(oldflp->fl4_src) ||
         ipv4_is_zeronet(oldflp->fl4_src))
            goto out;
        /* It is equivalent to inet_addr_type(saddr) == RTN_LOCAL */
        dev_out = ip_dev_find(net, oldflp->fl4_src);
        if (dev_out == NULL)
            goto out;
        /* I removed check for oif == dev_out->oif here.
         It was wrong for two reasons:
         1. ip_dev_find(net, saddr) can return wrong iface, if saddr
         is assigned to multiple interfaces.
         2. Moreover, we are allowed to send packets with saddr
         of another iface. --ANK
         */
        if (oldflp->oif == 0
         && (ipv4_is_multicast(oldflp->fl4_dst) ||
            oldflp->fl4_dst == htonl(0xFFFFFFFF))) {
            /* Special hack: user can direct multicasts
             and limited broadcast via necessary interface
             without fiddling with IP_MULTICAST_IF or IP_PKTINFO.
             This hack is not just for fun, it allows
             vic,vat and friends to work.
             They bind socket to loopback, set ttl to zero
             and expect that it will work.
             From the viewpoint of routing cache they are broken,
             because we are not allowed to build multicast path
             with loopback source addr (look, routing cache
             cannot know, that ttl is zero, so that packet
             will not leave this host and route is valid).
             Luckily, this hack is good workaround.
             */
            fl.oif = dev_out->ifindex;
            goto make_route;
        }
        if (dev_out)
            dev_put(dev_out);
        dev_out = NULL;
    }
注意fl4_src是键值结构flowi中的一个宏
#define fl4_src  nl_u.ip4_u.saddr
而nl_u是其中的一个联合
union {
  struct {
   __be32   daddr;
   __be32   saddr;
   __u8   tos;
   __u8   scope;
  } ip4_u;
  
  struct {
   struct in6_addr  daddr;
   struct in6_addr  saddr;
   __be32   flowlabel;
  } ip6_u;
  struct {
   __le16   daddr;
   __le16   saddr;
   __u8   scope;
  } dn_u;
} nl_u;
这些我们在上面的flowi结构中已经看到了,这种通过指针宏的方式确实不太多见,足以可见linux源代码的精华之处。上面代码开始的if判断语句要首先回忆一下我们在前边ip_route_connect()函数时对路由键值的初始化代码
             .nl_u = { .ip4_u = { .daddr = dst,
                         .saddr = src,
                         .tos = tos } },
很显然上边是将saddr设置为了客户端的地址。
也就是说如果我们的客户端的地址已经存在了,就会进入上面的代码中
那么也象地址绑定那节中看到的一样,先是对客户端的地址进行检查,如果检查通过了就会进入ip_dev_find()函数查找网络设备的net_device结构。

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> __ip_route_output_key()-->ip_route_output_slow()-->ip_dev_find()
struct net_device * ip_dev_find(struct net *net, __be32 addr)
{
    struct flowi fl = { .nl_u = { .ip4_u = { .daddr = addr } } };
    struct fib_result res;
    struct net_device *dev = NULL;
    struct fib_table *local_table;
#ifdef CONFIG_IP_MULTIPLE_TABLES
    res.r = NULL;
#endif
    local_table = fib_get_table(net, RT_TABLE_LOCAL);
    if (!local_table || local_table->tb_lookup(local_table, &fl, &res))
        return NULL;
    if (res.type != RTN_LOCAL)
        goto out;
    dev = FIB_RES_DEV(res);
    if (dev)
        dev_hold(dev);
out:
    fib_res_put(&res);
    return dev;
}
毫无疑问,这个函数我们看到他象以前在我的博客
http://blog.chinaunix.net/u2/64681/showart.php?id=1724837
或者朋友可以看一下本书中的[TCP/IP的路由初始化]那节中的fn_hash_lookup() 函数,这里又再次执行这个函数根据我们客户端的地址进行非常复杂的查找过程,返回结果存储在res中,详细的查找过程请朋友们回过头去看一下那里的fn_hash_lookup()函数,我们假定查找成功了,最后执行到dev = FIB_RES_DEV(res);通过FIB_RES_DEV取得了网络设备的net_device结构。然后增加它的计数,避免被内核释放。我们返回到ip_route_output_slow()函数中继续往下看,首先是看到了oif这是根据传递下来的sk->sk_bound_dev_if,它保存着绑定设备的索引编号

sys_socketcall()-->sys_connect()-->inet_stream_connect()-->tcp_v4_connect()-->ip_route_connect()--> __ip_route_output_key()-->ip_route_output_slow()
if (oldflp->oif) {
        dev_out = dev_get_by_index(net, oldflp->oif);
        err = -ENODEV;
        if (dev_out == NULL)
            goto out;
        /* RACE: Check return value of inet_select_addr instead. */
        if (__in_dev_get_rtnl(dev_out) == NULL) {
            dev_put(dev_out);
            goto out;    /* Wrong error code */
        }
        if (ipv4_is_local_multicast(oldflp->fl4_dst) ||
         oldflp->fl4_dst == htonl(0xFFFFFFFF)) {
            if (!fl.fl4_src)
                fl.fl4_src = inet_select_addr(dev_out, 0,
                             RT_SCOPE_LINK);
            goto make_route;
        }
        if (!fl.fl4_src) {
            if (ipv4_is_multicast(oldflp->fl4_dst))
                fl.fl4_src = inet_select_addr(dev_out, 0,
                             fl.fl4_scope);
            else if (!oldflp->fl4_dst)
                fl.fl4_src = inet_select_addr(dev_out, 0,
                             RT_SCOPE_HOST);
        }
    }
如果已经指定的要绑定的设备,那就通过dev_get_by_index()函数根据索引编号找到这个绑定设备的net_device结构,上面其余的代码因为我们已经假设设定了客户端的地址所以都会通过检查。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u2/64681/showart_1803486.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP