免费注册 查看新帖 |

Chinaunix

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

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

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-11-12 10:08 |只看该作者 |倒序浏览
我们继续探讨socket的连接,同样象在unix的socket章节一样,我们还是先从练习中的例子看起
connect(sockfd, (struct sockaddr *)&address, len)
在这个练习很明显是客户端的socket发起的,中间的过程我们不详细叙述了,需要了解的朋友请看我在博客中的关于unix的socket连接那篇文章
这里我们还是从err = sock->ops->connect(sock, (struct sockaddr *)address, addrlen,sock->file->f_flags); 看起
struct proto tcp_prot = {
    。。。。。。
    .connect        = tcp_v4_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()函数这是与路由相关的函数
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函数传递的参数来看分别是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是要服务器的地址,即目标地址,inet->saddr是我们绑定地址那节中看到的绑定的127.0.0.1这个地址,然后根据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()函数的参数。我们看到将这些参数设置到了路由的键值结构变量fl中了。然后取得网络命名空间结构,在我们这里取得的是全局变量init_net,如果地址类型是多播或者是广播的话那么我们这里源地址就是0,这个是在绑定地址章节中看到过的,
http://blog.chinaunix.net/u2/64681/showart_1387214.html
所以会进入__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_connect()函数中得到了这个rp路由表后则根据路由表中提供的目标地址和源地址调整这里的路由键值fl,最后函数进入ip_route_output_flow()返回
int ip_route_output_flow(struct net *net, struct rtable **rp, struct flowi *flp,
             struct sock *sk, int flags)
{
    int err;
    if ((err = __ip_route_output_key(net, rp, flp)) != 0)
        return err;
    if (flp->proto) {
        if (!flp->fl4_src)
            flp->fl4_src = (*rp)->rt_src;
        if (!flp->fl4_dst)
            flp->fl4_dst = (*rp)->rt_dst;
        err = __xfrm_lookup((struct dst_entry **)rp, flp, sk,
                 flags ? XFRM_LOOKUP_WAIT : 0);
        if (err == -EREMOTE)
            err = ipv4_dst_blackhole(rp, flp);
        return err;
    }
    return 0;
}
我们看到在这个函数中再次通过__ip_route_output_key()函数确定一下是否能在路由的杂凑缓存表中找到我们的路由表,然后通过__xfrm_lookup()函数查找策略,这个函数__xfrm_lookup()非常大主要是ipsec的过程,我们也暂时放一放。这里暂时用网上的一句简短的描述概括一下函数的主要过程:下面摘取linuxforum论坛中的一段文字
事实上无论是本机的包还是转发的包,都必须经过xfrm_lookup这个函数的处理。
而每个skbuff在xfrm_lookup处理前dst_output 已经有了一个默认的设置,比如说是ip_output
而在经过xfrm_lookup处理过程中首先要检查的是策略,也就是policy,策略无非就是绕过、处理和丢弃。如果是绕过,则不作任何的处理,直接返回,然后根据原有的dst_output将数据包发出。这个过程和Linux-2.4内核的处理过程基本相同。关键区别是在加入ipsec处理上
如果策略决定数据包需要经过ipsec处理,那在xfrm_lookup中就有一个复杂的处理过程了,大概顺序是
1、查策略
2、找sa(xfrm_state),xfrm_find_bundle
3、没有找到就创建一个xfrm_bundle_create
4、创建完以后还要添加sa(xfrm_state)
5、最后关键的一步是在dst_output的修改上,经过处理后的skbuff的dst_output已经不再是原有的ip_output,而是根据查找的xfrm_state设置成具体的ah_output或者是esp_output。
而这些所有的dst_output都连成了一个链,往往需要进行ipsec处理的都放在链头最先获得处理,处理完后又被修改为ip_output这样的正常处理,又重新挂到链上处理。
这样需要ipsec处理的包可以得到不同的处理。
到此我们返回到tcp_v4_connect()函数中继续往下看,我们看到除了一些对函数返回值的检查外还对inet的地址进行了调整。由于时间关系我们明天继续。


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

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP