tcp服务器监听任意地址的问题

晨曦之光 发布于 2012/04/10 14:59
阅读 792
收藏 0

如果服务器监听地址任意地址,客户端只要连接该服务器上配置的任何地址都能连接进来(前提是arp_ignore为0,否则最后一跳的arp解析可能会失败,详情参见前面的文章),
但是看linux协议栈的实现代码时发现内核是通过五元组的哈西来寻找套结字结构从而实现一个连接和套结字绑定的,如果一个服务器监听任意地址,而客户端连接一个确定的地址,因为连接的目的地址和监听地址不同,哈希后是不可能找到监听套结字的,这是怎么处理的呢?
实际上在__tcp_v4_lookup中有两类查询,第一类是首先进行的查询,查询已经建立的连接,就是利用五元组来查询,如果找不到的话,就会进行第二类查询,就是在所有的监听套结字上进行查询:
static struct sock *__tcp_v4_lookup_listener(struct hlist_head *head, u32 daddr,
                         unsigned short hnum, int dif)
{
    ...//head仅仅是和端口相关的链表
    sk_for_each(sk, node, head) {
        struct inet_opt *inet = inet_sk(sk);
        if (inet->num == hnum && !ipv6_only_sock(sk)) {
            __u32 rcv_saddr = inet->rcv_saddr;  //取到服务器监听的地址,如果监听任意地址的话则为0
            score = (sk->sk_family == PF_INET ? 1 : 0);
            if (rcv_saddr) {
                if (rcv_saddr != daddr) //如果监听确定的地址,则严格进行判断
                    continue;
                score+=2;
            }
            if (sk->sk_bound_dev_if) { //如果绑定了确定的设备,则严格进行判断
                if (sk->sk_bound_dev_if != dif)
                    continue;
                score+=2;
            }
            if (score == 5) //严格经过了上述两个约束条件的判断,直接返回
                return sk;
            if (score > hiscore) { //scope越大越精确
                hiscore = score;
                result = sk;
            }
        }
    }
    return result;  //返回一个任意的可用套结字
}
找到了监听套结字,接下来需要创建一个用户套结字,该用户套结字的源地址就是连接套结字的目的地址。tcp_v4_rcv后面要调用tcp_v4_do_rcv,当服务器收到第一个syn包的时候以及后续的握手包,是监听套结字进行处理的:
if (sk->sk_state == TCP_LISTEN) {
    struct sock *nsk = tcp_v4_hnd_req(sk, skb);
    if (!nsk)
        goto discard;
    if (nsk != sk) { //说明生成了新的套结字,接下来进入处理新的通信套结字
        if (tcp_child_process(sk, nsk, skb))
            goto reset;
        return 0;
    }
}
如果一切合理,则会进入tcp三次握手的状态机中。看一下tcp_v4_hnd_req函数,只有在真的创建了一个通信套接字的时候才会返回新的通信套结字:
static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
{
    ...//如果服务器收到了syn包,那么它会分配一个连接请求并且将之放入一个容器,所谓的syn-flood就是服务器收到了大量的没有下文的syn包,导致内存被大量占用.在服务器收到syn的时候会调用conn_request回调函数将请求加入容器
    struct open_request *req = tcp_v4_search_req(tp, &prev, th->source, iph->saddr, iph->daddr);
    if (req)
        return tcp_check_req(sk, skb, req, prev);//到了这一步说明syn已经收到了,这个函数中在state为syn-rcv情况下发送syn-ack,返回NULL,在ack收到后创建通信套结字并且返回它。
    ...
    return sk;
}
在创建通信套结字的时候会设置通信套结字的源地址,相关的回调函数中有以下的代码:
newinet->daddr          = req->af.v4_req.rmt_addr;
newinet->rcv_saddr    = req->af.v4_req.loc_addr;
newinet->saddr          = req->af.v4_req.loc_addr;
这就设置了通信套结字的地址信息,这些信息是在服务器收到syn的时候初始化的:
req->af.v4_req.loc_addr = daddr;
req->af.v4_req.rmt_addr = saddr;
根本不需要判断daddr是否是自己的ip就可以放心的进行设置,因为路由模块做完了这一切判断,如果不是自己ip地址,数据包那是根本不可能被投递到上层的。


原文链接:http://blog.csdn.net/dog250/article/details/5846457
加载中
返回顶部
顶部