发新帖我要提问
123
返回列表
打印

狗拿耗子LWIP

[复制链接]
楼主: zheng522
手机看帖
扫描二维码
随时随地手机跟帖
41
zheng522|  楼主 | 2015-5-28 08:18 | 只看该作者 回帖奖励 |倒序浏览
if ((flags & TCP_SYN) || (flags & TCP_FIN)) {
++len;
}
pcb->snd_lbb += len; /* 更新下一个 segment 的 sequence number */
pcb->snd_buf -= len; /* 更新 buffer 的大小 */
/* update number of segments on the queues */ /* 更新 buffer 中 segment 的个数 */
pcb->snd_queuelen = queuelen;
......
/* Set the PSH flag in the last segment that we enqueued, but only
if the segment has data (indicated by seglen > 0). */
if (seg != NULL && seglen > 0 && seg->tcphdr != NULL) {
TCPH_SET_FLAG(seg->tcphdr, TCP_PSH);
}
return ERR_OK;
memerr:
TCP_STATS_INC(tcp.memerr);
if (queue != NULL) {
tcp_segs_free(queue);
}
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_enqueue: valid queue length", pcb->unacked != NULL ||
pcb->unsent != NULL);
}
......
return ERR_MEM;
}

使用特权

评论回复
42
zheng522|  楼主 | 2015-5-28 08:19 | 只看该作者
12.1 发送的地方
z tcp_listen_input, 在 LISTEN 状态收到 SYN 后, 通过 tcp_enqueue 发送 SYN、 ACK, 并转到 SYN_RCVD
状态
z tcp_rst,通过 ip_output 直接发送 RST、 ACK
z tcp_output,下面详细介绍
12.2 tcp_out 中的 ACK 发送
err_t tcp_output(struct tcp_pcb *pcb)
{
......
/* 1) If the TF_ACK_NOW flag is set and no data will be sent (either
* because the ->unsent queue is empty or because the window does
* not allow it), construct an empty ACK segment and send it.
*
*2) If data is to be sent, we will just piggyback the ACK (see below). */
if (pcb->flags & TF_ACK_NOW &&
(seg == NULL ||
ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd)) {
p = pbuf_alloc(PBUF_IP, TCP_HLEN, PBUF_RAM);
......
/* remove ACK flags from the PCB, as we send an empty ACK now */
pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
tcphdr = p->payload;
tcphdr->src = htons(pcb->local_port);
tcphdr->dest = htons(pcb->remote_port);
tcphdr->seqno = htonl(pcb->snd_nxt);
tcphdr->ackno = htonl(pcb->rcv_nxt);
TCPH_FLAGS_SET(tcphdr, TCP_ACK);
tcphdr->wnd = htons(pcb->rcv_wnd);
tcphdr->urgp = 0;
TCPH_HDRLEN_SET(tcphdr, 5);
......
ip_output(p, &(pcb->local_ip), &(pcb->remote_ip), pcb->ttl, pcb->tos,
IP_PROTO_TCP);
pbuf_free(p);
return ERR_OK;
}
......
/* data available and window allows it to be sent? */
while (seg != NULL &&
ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len <= wnd) {
......
pcb->unsent = seg->next;
if (pcb->state != SYN_SENT) {
TCPH_SET_FLAG(seg->tcphdr, TCP_ACK);
pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
}
tcp_output_segment(seg, pcb);
......
}
return ERR_OK;
}
1) TF_ACK_NOW 被掩上且发送数据的条件不满足,则立刻发送 ACK(不带数据) ,最后清除
TF_ACK_DELAY 与 TF_ACK_NOW。
2)当满足发送数据的条件时,只要 pcb 不处于 SYN_SENT,则在发送数据的同时发送 ACK,最后清除
TF_ACK_DELAY 与 TF_ACK_NOW。

使用特权

评论回复
43
zheng522|  楼主 | 2015-5-28 08:19 | 只看该作者
12.3 宏 tcp_ack_now
#define tcp_ack_now(pcb) (pcb)->flags |= TF_ACK_NOW; \
tcp_output(pcb)
立刻发送 ACK:如果当前满足发送数据的条件则随数据发送 ACK,否则单独发送 ACK。对于希望立刻发
送 ACK 的模块,应使用该宏来发送 ACK。

使用特权

评论回复
44
zheng522|  楼主 | 2015-5-28 08:20 | 只看该作者
12.4 宏 tcp_ack
#define tcp_ack(pcb) if((pcb)->flags & TF_ACK_DELAY) { \
(pcb)->flags &= ~TF_ACK_DELAY; \
(pcb)->flags |= TF_ACK_NOW; \
tcp_output(pcb); \
} else { \
(pcb)->flags |= TF_ACK_DELAY; \
}
延时发送 ACK:如果不存在延时发送的 ACK,则延时当前待发送的 ACK,否则复位延时发送的标识立刻
发送 ACK。 lwip 开启了一个 250ms 的定时器,该定时器扫描所有处于非 listion、非 timewait 状态的 pcb,
一旦发现延时发送的标识被掩上了,则引用宏 tcp_ack_now 立刻发送 ACK。

使用特权

评论回复
45
zheng522|  楼主 | 2015-5-28 08:20 | 只看该作者
13、发送 segment
static void tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb)
{
u16_t len;
struct netif *netif;
......
/* The TCP header has already been constructed, but the ackno and
wnd fields remain. */
seg->tcphdr->ackno = htonl(pcb->rcv_nxt);
/* silly window avoidance */
if (pcb->rcv_wnd < pcb->mss) {
seg->tcphdr->wnd = 0;
} else {
/* advertise our receive window size in this TCP segment */
seg->tcphdr->wnd = htons(pcb->rcv_wnd);
}
/* If we don't have a local IP address, we get one by
calling ip_route(). */
if (ip_addr_isany(&(pcb->local_ip))) {
netif = ip_route(&(pcb->remote_ip));
if (netif == NULL) {
return;
}
ip_addr_set(&(pcb->local_ip), &(netif->ip_addr));
}
pcb->rtime = 0;
if (pcb->rttest == 0) {
pcb->rttest = tcp_ticks;
pcb->rtseq = ntohl(seg->tcphdr->seqno);
LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_output_segment: rtseq %"U32_F"\n", pcb->rtseq));
}
......
len = (u16_t)((u8_t *)seg->tcphdr - (u8_t *)seg->p->payload);
seg->p->len -= len;
seg->p->tot_len -= len;
seg->p->payload = seg->tcphdr;
seg->tcphdr->chksum = 0;
#if CHECKSUM_GEN_TCP
seg->tcphdr->chksum = inet_chksum_pseudo(seg->p,
&(pcb->local_ip),
&(pcb->remote_ip),
IP_PROTO_TCP, seg->p->tot_len);
#endif
TCP_STATS_INC(tcp.xmit);
ip_output(seg->p, &(pcb->local_ip), &(pcb->remote_ip), pcb->ttl, pcb->tos,
IP_PROTO_TCP);
}
填写待发送 segment 的 ack sequnce number 与通告窗口,启动 rtt 估计,最后调用 ip_output 发送报文。不过
无法理解的是上面红色字体的那段,在 tcp_enqueue 中 p->payload 被赋给了 seg->tcphdr,什么时候发生了
改动呢?

使用特权

评论回复
46
zheng522|  楼主 | 2015-5-28 08:20 | 只看该作者
14、糊涂窗口综合症的避免
z 由 tcp_output_segment()可知,当 lwip 发现 receive buffer 大小小于 mss 时,即通告窗口为 0。这样避免
了在 receive buffer 较小的时候,收到对方发送过来的较小的 segment,这避免了触发 sws。
z 由 tcp_enqueue()可知, lwip 总是尽量按照 mss 分割待发送数据,这样可以避免发送小的 segment。当
对方的 receive buffer 较小,即对方通告的窗口较小时,由于 lwip 的 send buffer 中的 segment 较大(尽
量被分割成 mss 大小),大于对方通告的窗口从而不满足发送条件。于是此时 lwip 不发送 segment,
而是等待对方通告一个足够大的窗口。
可以说 lwip 简化了分割待发送数据的流程和 receive buffer 的通告流程,缺点是降低了传输速度,优点是避
免了 sws。

使用特权

评论回复
47
zheng522|  楼主 | 2015-5-28 08:21 | 只看该作者
15、 tcp_output 与重传
15.1 发送 segment 有关的两个队列:
z pcb->unsent,待发送 segment, tcp_enqueue()将待发送数据分割乘 segment 并放入该队列
z pcb->unacked,已发送待确认 segment, tcp_output()从该队列中取 segment,如果满足发送条件则调用
tcp_output_segment()发送该 segment。
15.2 这两个队列的其他用户
z tcp_rexmit_rto,超时重传时将 pcb->unacked 中所有的 segment 移动到到 pcb->unsent 的前面,最后调
用 tcp_output。
z tcp_rexmit,快速重传时将 pcb->unacked 第一个 segment 移动到到 pcb->unsent 的前面,最后调用
tcp_output。
15.3 tcp_output 的流程
err_t tcp_output(struct tcp_pcb *pcb)
{
struct pbuf *p;
struct tcp_hdr *tcphdr;
struct tcp_seg *seg, *useg;
u32_t wnd;
#if TCP_CWND_DEBUG
s16_t i = 0;
#endif /* TCP_CWND_DEBUG */
/* First, check if we are invoked by the TCP input processing
code. If so, we do not output anything. Instead, we rely on the
input processing code to call us when input processing is done
with. */
if (tcp_input_pcb == pcb) {
return ERR_OK;
}
wnd = LWIP_MIN(pcb->snd_wnd, pcb->cwnd); /* 当前窗口取拥塞窗口与对方的通告窗口的最小值 */
seg = pcb->unsent;
/* useg should point to last segment on unacked queue */
useg = pcb->unacked;
if (useg != NULL) {
for (; useg->next != NULL; useg = useg->next);
}
......
if (pcb->flags & TF_ACK_NOW &&
(seg == NULL ||
ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd)) {
/* 前面已经叙述过:立刻发送 ACK */
}
......
/* 在窗口允许的前提下,尽可能地发送 segment */
while (seg != NULL &&
ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len <= wnd) {
pcb->unsent = seg->next;
if (pcb->state != SYN_SENT) { /* 前面已经叙述过: ACK 与数据一起发送 */
TCPH_SET_FLAG(seg->tcphdr, TCP_ACK);
pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
}
tcp_output_segment(seg, pcb); /* 发送 segment */
pcb->snd_nxt = ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg); /* next seqno to be sent */
if (TCP_SEQ_LT(pcb->snd_max, pcb->snd_nxt)) {
pcb->snd_max = pcb->snd_nxt; /* Highest seqno sent. */
}
  /* put segment on unacknowledged list if length > 0 */
if (TCP_TCPLEN(seg) > 0) {
seg->next = NULL;
/* unacked list is empty? */
if (pcb->unacked == NULL) {
pcb->unacked = seg;
useg = seg;
/* unacked list is not empty? */
} else {
  /* In the case of fast retransmit, the packet should not go to the tail
* of the unacked queue, but rather at the head. We need to check for
* this case. -STJ Jul 27, 2004 */
if (TCP_SEQ_LT(ntohl(seg->tcphdr->seqno), ntohl(useg->tcphdr->seqno))){
  /* add segment to head of unacked list */
seg->next = pcb->unacked;
pcb->unacked = seg;
} else {
  /* add segment to tail of unacked list */
useg->next = seg;
useg = useg->next;
}
}
/* do not queue empty segments on the unacked list */
} else {
tcp_seg_free(seg);
}
seg = pcb->unsent;
}
return ERR_OK;
}
16、内核函数 do_connect
static void do_connect(struct api_msg_msg *msg)
{
if (msg->conn->pcb.tcp == NULL) {
switch (msg->conn->type) {
......
case NETCONN_TCP:
msg->conn->pcb.tcp = tcp_new();
if (msg->conn->pcb.tcp == NULL) {
msg->conn->err = ERR_MEM;
sys_mbox_post(msg->conn->mbox, NULL);
return;
}
default:
break;
}
}
switch (msg->conn->type) {
........
case NETCONN_TCP:
setup_tcp(msg->conn);
tcp_connect(msg->conn->pcb.tcp, msg->msg.bc.ipaddr, msg->msg.bc.port,
do_connected);
default:
break;
}
}

使用特权

评论回复
48
zheng522|  楼主 | 2015-5-28 08:22 | 只看该作者
16.1 tcp_connect
err_t tcp_connect(struct tcp_pcb *pcb, struct ip_addr *ipaddr, u16_t port,
err_t (* connected)(void *arg, struct tcp_pcb *tpcb, err_t err))
{
/* 当链接请求被服务器接收后 connected 被调用,内核用它来通知用户任务 */
u32_t optdata;
err_t ret;
u32_t iss;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_connect to port %"U16_F"\n", port));
if (ipaddr != NULL) {
pcb->remote_ip = *ipaddr;
} else {
return ERR_VAL;
}
pcb->remote_port = port;
if (pcb->local_port == 0) {
pcb->local_port = tcp_new_port();
}
iss = tcp_next_iss();
pcb->rcv_nxt = 0;
pcb->snd_nxt = iss;
pcb->lastack = iss - 1;
pcb->snd_lbb = iss - 1;
pcb->rcv_wnd = TCP_WND; /*自己的通告窗口  */
pcb->snd_wnd = TCP_WND; /*对方的通告窗口  */
pcb->mss = TCP_MSS;
pcb->cwnd = 1;
pcb->ssthresh = pcb->mss * 10;
pcb->state = SYN_SENT; /* 被置为 SYN_SENT 状态 */
#if LWIP_CALLBACK_API
pcb->connected = connected; /* 注册回调函数 */
#endif /* LWIP_CALLBACK_API */
TCP_REG(&tcp_active_pcbs, pcb); /* 将 pcb 放入 tcp_active_pcbs */
snmp_inc_tcpactiveopens();
/* Build an MSS option */
optdata = htonl(((u32_t)2 << 24) |
((u32_t)4 << 16) |
(((u32_t)pcb->mss / 256) << 8) |
(pcb->mss & 255));
ret = tcp_enqueue(pcb, NULL, 0, TCP_SYN, 0, (u8_t *)&optdata, 4); /* 发送 SYN,并通告 mss */
if (ret == ERR_OK) {
tcp_output(pcb);
}
return ret;
}

使用特权

评论回复
49
zheng522|  楼主 | 2015-5-28 08:22 | 只看该作者
16.2 do_connected
static err_t do_connected(void *arg, struct tcp_pcb *pcb, err_t err)
{
struct netconn *conn;
conn = arg;
if (conn == NULL) {
return ERR_VAL;
}
conn->err = err;
if (conn->type == NETCONN_TCP && err == ERR_OK) {
setup_tcp(conn); /* 注册链接相关的 event handler */
}
sys_mbox_post(conn->mbox, NULL); /* 通知用户任务继续执行 */
return ERR_OK;
}

使用特权

评论回复
50
zheng522|  楼主 | 2015-5-28 08:22 | 只看该作者
17、系统调用 netconn_accept
struct netconn *netconn_accept(struct netconn *conn)
{
struct netconn *newconn;
......
sys_mbox_fetch(conn->acceptmbox, (void *)&newconn);
/* Register event with callback */
if (conn->callback)
(*conn->callback)(conn, NETCONN_EVT_RCVMINUS, 0);
return newconn;
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则