内核中TCP序列号的生成

一、分层

分层就是一个层尽量的承担比较小的功能,一个层只完成自己的功能,这样的通用性会比较好。当然,如果只是完成一个功能,那么可以同时把所有的功能都在一层实现,这样对某个功能或者协议来说是比较方便的,但是从维护和使用起来就比较混乱。

TCP提供的机制包括 可靠传输 和 流量控制。这两个功能是TCP的最为基本的功能。其中的可靠传输主要通过 确认 机制来实现,这样在两个主机之间不断发送报文的时候,就需要对发送到报文进行逐个确认,那么着报文到底是确认哪一个呢?着就需要对每个报文进行编号,我们在通讯的过程中对报文进行确认,从而可以完成可靠传输。从协议的实现来看,在协议的开始头部包含了自己的序列号seq和确认方的序列号ackseq

流量控制

由于不可避免的,一个老式的PC和一个大型的服务器之间还是有比较大的区别的。这样如果我们不断的向客户机发送数据,那么这些数据可能会使慢速PC疲于接受和处理报文,而对于真正的数据被丢失,整个网络的带宽被浪费。为了实现这个问题,在协议中增加了wnd的概念,也就是在其中要告诉对方我的能力现在可以接受和处理多少数据,从而让对方知道,避免浪费带宽和慢速设备被冲死。

二、序列号的选择

由于计算机中使用的大部分都是32bits的数据,所以序列号之后可能会发生回绕,这样我们就要尽可能的选择不同的序列号,避免可能的报文序号重复。

看一下内核中对这个报文序列号的选择方式:

tcp_v4_rcv-->>tcp_v4_do_rcv-->>tcp_rcv_state_process-->>icsk->icsk_af_ops->conn_request-->>tcp_v4_conn_request-->>tcp_v4_init_sequence-->>secure_tcp_sequence_number

__u32 secure_tcp_sequence_number(__be32 saddr, __be32 daddr,
     __be16 sport, __be16 dport)
{
 __u32 seq;
 __u32 hash[4];
 struct keydata *keyptr = get_keyptr();

 /*
  *  Pick a unique starting offset for each TCP connection endpoints
  *  (saddr, daddr, sport, dport).
  *  Note that the words are placed into the starting vector, which is
  *  then mixed with a partial MD4 over random data.
  */
 hash[0] = (__force u32)saddr;
 hash[1] = (__force u32)daddr;
 hash[2] = ((__force u16)sport << 16) + (__force u16)dport;
 hash[3] = keyptr->secret[11];

 seq = half_md4_transform(hash, keyptr->secret) & HASH_MASK;
 seq += keyptr->count;
 /*
  * As close as possible to RFC 793, which
  * suggests using a 250 kHz clock.
  * Further reading shows this assumes 2 Mb/s networks.
  * For 10 Mb/s Ethernet, a 1 MHz clock is appropriate.
  * For 10 Gb/s Ethernet, a 1 GHz clock should be ok, but
  * we also need to limit the resolution so that the u32 seq
  * overlaps less than one time per MSL (2 minutes).
  * Choosing a clock of 64 ns period is OK. (period of 274 s)
  */
 seq += ktime_to_ns(ktime_get_real()) >> 6;

 return seq;
}

三、具体的分析


/* This should not be decreased so low that ISNs wrap too fast. */这个地方设置了一个key生成的时间间隔,作者说明,这个值不应该太小,不然回绕出现的会比较快
#define REKEY_INTERVAL (300 * HZ)
/*
 * Bit layout of the tcp sequence numbers (before adding current time):
 * bit 24-31: increased after every key exchange
 * bit 0-23: hash(source,dest)  在加上当前时间之前的TCP序列号。
 *
 * The implementation is similar to the algorithm described
 * in the Appendix of RFC 1185, except that
 * - it uses a 1 MHz clock instead of a 250 kHz clock
 * - it performs a rekey every 5 minutes, which is equivalent
 *  to a (source,dest) tulple dependent forward jump of the
 *  clock by 0..2^(HASH_BITS+1)
 *
 * Thus the average ISN wraparound time is 68 minutes instead of
 * 4.55 hours
.
 *
 * SMP cleanup and lock avoidance with poor man's RCU.
 *    Manfred Spraul <manfred@colorfullife.com>
 *
 */
#define COUNT_BITS 8
#define COUNT_MASK ((1 << COUNT_BITS) - 1)
#define HASH_BITS 24
#define HASH_MASK ((1 << HASH_BITS) - 1)

真正的序列号更新操作

/*
 * Lock avoidance:
 * The ISN generation runs lockless - it's just a hash over random data.
 * State changes happen every 5 minutes when the random key is replaced.
 * Synchronization is performed by having two copies of the hash function
 * state and rekey_seq_generator always updates the inactive copy.
 * The copy is then activated by updating ip_cnt.
 * The implementation breaks down if someone blocks the thread
 * that processes SYN requests for more than 5 minutes. Should never
 * happen, and even if that happens only a not perfectly compliant
 * ISN is generated, nothing fatal.
 */
static void rekey_seq_generator(struct work_struct *work)
{
 struct keydata *keyptr = &ip_keydata[1 ^ (ip_cnt & 1)];

 get_random_bytes(keyptr->secret, sizeof(keyptr->secret));
 keyptr->count = (ip_cnt & COUNT_MASK) << HASH_BITS;
 smp_wmb();
 ip_cnt++;
 schedule_delayed_work(&rekey_work,
         round_jiffies_relative(REKEY_INTERVAL));
}

这里将在内核的工作队列中添加一个工作,这个工作就是每隔REKEY_INTERVAL时间之后,从新增加一下ip_cnt的值,注意,这个值和是否有报文发送没有关系。当前值是每隔五分钟更新一次。而且此时还好烤炉到其中的系统的熵的变化,这个应该真的是一个真实随机值,此时可能考虑了键盘敲击速率甚至是中断发生的次数和频率等不确定因素。结果放在secret中

void get_random_bytes(void *buf, int nbytes)
{
 extract_entropy(&nonblocking_pool, buf, nbytes, 0, 0);
}

由于这个值5分钟更新一次,所以在五分钟之内发送的序列号的这个部分是相同的,之后的部分就需要通过时间来进行一次新的冲突避免,只要我们避免在5分钟之内超时,这个就应该不会有问题。

四、连接请求的发送及其它初始化

tcp_v4_connect--->>secure_tcp_sequence_number

然后是相关的流量控制的窗口的大小

tcp_connect_init

原文地址:https://www.cnblogs.com/tsecer/p/10485717.html