lwIP 细节之三:errf 回调函数是何时调用的

2023-12-14 22:17:35

使用 lwIP 协议栈进行 TCP 裸机编程,其本质就是编写协议栈指定的各种回调函数。将你的应用逻辑封装成函数,注册到协议栈,在适当的时候,由协议栈自动调用,所以称为回调

注:除非特别说明,以下内容针对 lwIP 2.0.0 及以上版本。

向协议栈注册回调函数有专门的接口,如下所示:

tcp_err(pcb, errf);							//注册 TCP 接到 RST 标志或发生错误回调函数 errf
tcp_connect(pcb, ipaddr, port, connected);	//注册 TCP 建立连接成功回调函数 connecter
tcp_accept(pcb, accept);					//注册 TCP 处于 LISTEN 状态时,监听到有新的连接接入
tcp_recv(pcb, recv);						//注册 TCP 接收到数据回调函数 recv
tcp_sent(pcb, sent);						//注册 TCP 发送数据成功回调函数 sent
tcp_poll(pcb, poll, interval);				//注册 TCP 周期性执行回调函数 poll

本节讲述 errf 回调函数。

errf 回调函数

在 TCP 控制块中,函数指针 errf 指向用户实现的 TCP 错误处理函数,当 TCP 连接发送错误时,由协议栈调用此函数。
函数指针 errf 的类型为 tcp_err_fn ,该类型定义在 tcp.h 中:

/** Function prototype for tcp error callback functions. Called when the pcb
 * receives a RST or is unexpectedly closed for any other reason.
 *
 * @note The corresponding pcb is already freed when this callback is called!
 *
 * @param arg Additional argument to pass to the callback function (@see tcp_arg())
 * @param err Error code to indicate why the pcb has been closed
 *            ERR_ABRT: aborted through tcp_abort or by a TCP timer
 *            ERR_RST: the connection was reset by the remote host
 */
typedef void  (*tcp_err_fn)(void *arg, err_t err);

从注释得知,错误处理函数在接收到 RST 标志,或者连接意外关闭时,由协议栈调用。
注意,当这个函数调用的时候,TCP 控制块已经释放掉了。

协议栈通过宏 TCP_EVENT_ERR(last_state,errf,arg,err) 调用 errf 指向的错误处理函数,宏 TCP_EVENT_ERR 定义在 tcp_priv.h 中:

#define TCP_EVENT_ERR(last_state,errf,arg,err)                 \
  do {                                                         \
    LWIP_UNUSED_ARG(last_state);                               \
    if((errf) != NULL)                                         \
      (errf)((arg),(err));                                     \
  } while (0)

可以看到这个宏的第 4 个参数就是传递给错误处理函数的错误码
以关键字 TCP_EVENT_ERR 搜索源码,可以搜索到 4 处使用:

TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_RST);
TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_CLSD);
TCP_EVENT_ERR(last_state, errf, errf_arg, ERR_ABRT);
TCP_EVENT_ERR(last_state, err_fn, err_arg, ERR_ABRT);

用到了 3 个错误码:ERR_RSTERR_CLSDERR_ABRT ,分别表示连接复位、连接关闭和连接异常。

1.连接复位

当远端连接发送 RST 标志,并且报文序号正确是,调用错误类型为 ERR_RST 的错误处理回调函数,这一过程在 tcp_input 函数中执行。

void
tcp_input(struct pbuf *p, struct netif *inp)
{
  // 经过一系列检测,没有错误

  flags = TCPH_FLAGS(tcphdr);	// 这里获取数据包的 [标志] 字段

  /* 在本地找到有效的控制块 pcb */
  if (pcb != NULL) {

    tcp_input_pcb = pcb;
    err = tcp_process(pcb);		// [标志]中有 RST, 且报文序号正确:recv_flags |= TF_RESET
    /* A return value of ERR_ABRT means that tcp_abort() was called
       and that the pcb has been freed. If so, we don't do anything. */
    if (err != ERR_ABRT) {
      if (recv_flags & TF_RESET) {
        /* TF_RESET means that the connection was reset by the other
           end. We then call the error callback to inform the
           application that the connection is dead before we
           deallocate the PCB. */
        TCP_EVENT_ERR(pcb->state, pcb->errf, pcb->callback_arg, ERR_RST);
        tcp_pcb_remove(&tcp_active_pcbs, pcb);
        tcp_free(pcb);
      } 
    }
  } 
  return;
}

tcp_process 函数中关于 RST 标志的判断代码:

static err_t
tcp_process(struct tcp_pcb *pcb)
{
  /* Process incoming RST segments. */
  if (flags & TCP_RST) {		// flags 保存数据包的 [标志] 字段,在 tcp_input 函数中取得		
    /* First, determine if the reset is acceptable. */
    if (pcb->state == SYN_SENT) {
      /* "In the SYN-SENT state (a RST received in response to an initial SYN),
          the RST is acceptable if the ACK field acknowledges the SYN." */
      if (ackno == pcb->snd_nxt) {
        acceptable = 1;
      }
    } else {
      /* "In all states except SYN-SENT, all reset (RST) segments are validated
          by checking their SEQ-fields." */
      if (seqno == pcb->rcv_nxt) {
        acceptable = 1;
      } else  if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
                                  pcb->rcv_nxt + pcb->rcv_wnd)) {
        /* If the sequence number is inside the window, we send a challenge ACK
           and wait for a re-send with matching sequence number.
           This follows RFC 5961 section 3.2 and addresses CVE-2004-0230
           (RST spoofing attack), which is present in RFC 793 RST handling. */
        tcp_ack_now(pcb);
      }
    }

    if (acceptable) {
      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: Connection RESET\n"));
      recv_flags |= TF_RESET;
      tcp_clear_flags(pcb, TF_ACK_DELAY);
      return ERR_RST;
    }
  }
}

2.连接关闭

这是为了处理异常情况。

当接收到远端的 FIN 标志,表示远端要关闭 TCP 连接,此时协议栈会通知应用程序。通常情况下,应用程序应该调用 tcp_close 函数关闭连接。但是,若应用才程序没有执行这个步骤,则 TCP 状态处于 LADT_ACK 并且接收到远端的 ACK 标志后,调用错误类型为 ERR_CLSD 的错误回调函数,简化后的代码为:

void tcp_input(struct pbuf *p, struct netif *inp)
{
	err = tcp_process(pcb);
    /* A return value of ERR_ABRT means that tcp_abort() was called
       and that the pcb has been freed. If so, we don't do anything. */
    if (err != ERR_ABRT) {
       if (recv_flags & TF_CLOSED) {	/*处于 LAST_ACK 状态时收到 ACK 标识*/
			/* The connection has been closed and we will deallocate the PCB. */
			if (!(pcb->flags & TF_RXCLOSED)) {
				/* Connection closed although the application has only shut down the
					tx side: call the PCB's err callback and indicate the closure to
					ensure the application doesn't continue using the PCB. */
				TCP_EVENT_ERR(pcb->errf, pcb->callback_arg, ERR_CLSD);	// <--- 这里
			}
			tcp_pcb_remove(&tcp_active_pcbs, pcb);
			memp_free(MEMP_TCP_PCB, pcb);
		}
    }
}

3.连接异常

3.1 由 tcp_abandon 函数调用

tcp_abandon 函数会调用错误类型为 ERR_ABRT 的错误回调函数,简化后的代码为:

void
tcp_abandon(struct tcp_pcb *pcb, int reset)
{
  if (pcb->state == TIME_WAIT) {
    tcp_pcb_remove(&tcp_tw_pcbs, pcb);
    tcp_free(pcb);
  } else {
	// 从链表中移除 TCP_PCB
	// 按需释放[未应答]、[未发送]、[失序]报文内存
	// 按需发送 RST 标志
	// 释放 TCP_PCB :tcp_free(pcb)
    TCP_EVENT_ERR(last_state, errf, errf_arg, ERR_ABRT);	// <-- 这里
  }
}

tcp_abandon 函数又是谁在调用呢?

3.1.1 tcp_listen_input 函数中

tcp_listen_input 函数中,检测接收到 SYN 标志报文,则创建新的 TCP_PCB,然后发送 SYN|ACK 标志报文。在这一过程中,若发送 SYN|ACK 标志报文失败,则调用 tcp_abandon 函数放弃这个连接,在 tcp_abandon 函数内部会调用错误类型为 ERR_ABRT 的错误处理回调函数。简化后的代码为:

static void
tcp_listen_input(struct tcp_pcb_listen *pcb)
{
  /* In the LISTEN state, we check for incoming SYN segments,
     creates a new PCB, and responds with a SYN|ACK. */
  if (flags & TCP_SYN) {
    npcb = tcp_alloc(pcb->prio);
    
	/* 这里 TCP PCB 申请成功,初始化新的 PCB*/
	// ...
    npcb->state = SYN_RCVD;
    // ...

    /* Send a SYN|ACK together with the MSS option. */
    rc = tcp_enqueue_flags(npcb, TCP_SYN | TCP_ACK);
    if (rc != ERR_OK) {
      tcp_abandon(npcb, 0);		// <-- 这里
      return;
    }
    tcp_output(npcb);
  }
  return;
}

3.1.2 tcp_kill_state 函数中
《lwIP 细节之二:协议栈什么情况下发送 RST 标志》博文中,有提到 tcp_alloc 函数,tcp_alloc 函数设计原则是尽一切可能返回一个有效的 TCP_PCB 控制块,因此,当 TCP_PCB 不足时,函数可能 “杀死”(kill)正在使用的连接,以释放 TCP_PCB 控制块!
具体就是:

  1. 先调用 tcp_kill_timewait 函数,试图找到 TIME_WAIT 状态下生存时间最长的连接,如果找到符合条件的控制块 pcb ,则调用 tcp_abort(pcb) 函数 “杀” 掉这个连接,这会发送 RST 标志,以便通知远端释放连接;
  2. 如果第 1 步失败了,则调用 tcp_kill_state 函数,试图找到 LAST_ACKCLOSING 状态下生存时间最长的连接,如果找到符合条件的控制块 pcb ,则调用 tcp_abandon(pcb, 0) 函数 “杀” 掉这个连接,注意这个函数并不会发送 RST 标志,处于这两种状态的连接都是等到对方发送的 ACK 就会结束连接,不会有数据丢失;
  3. 如果第 2 步也失败了,则调用 tcp_kill_prio(prio) 函数,试图找到小于指定优先级(prio)的最低优先级且生存时间最长的有效(active)连接!如果找到符合条件的控制块 pcb ,则调用 tcp_abort(pcb) 函数 “杀” 掉这个连接,这会发送 RST 标志。

这里的第 2 步,调用 tcp_abandon(pcb, 0) 函数 “杀” 掉这个连接时,会调用 tcp_abandon 函数放弃这个连接,在 tcp_abandon 函数内部会调用错误类型为 ERR_ABRT 的错误处理回调函数。简化后的代码为:

static void
tcp_kill_state(enum tcp_state state)
{
  inactivity = 0;
  inactive = NULL;
  /* Go through the list of active pcbs and get the oldest pcb that is in state
     CLOSING/LAST_ACK. */
  for (pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) {
    if (pcb->state == state) {
      if ((u32_t)(tcp_ticks - pcb->tmr) >= inactivity) {
        inactivity = tcp_ticks - pcb->tmr;
        inactive = pcb;
      }
    }
  }
  if (inactive != NULL) {
    LWIP_DEBUGF(TCP_DEBUG, ("tcp_kill_closing: killing oldest %s PCB %p (%"S32_F")\n",
                            tcp_state_str[state], (void *)inactive, inactivity));
    /* Don't send a RST, since no data is lost. */
    tcp_abandon(inactive, 0);
  }
}

3.1.3 tcp_abort 函数中
tcp_abort 函数中止一个连接,会向远端主机发送一个 RST 标志。当从其中一个 TCP 回调函数中调用时,请确保一定返回 ERR_ABRT 错误码(其它情况绝不要返回 ERR_ABRT 错误码,否则可能会有内存泄漏的风险或者访问已经释放的内存!
tcp_abort 函数代码简单,原始无简化代码为:

void
tcp_abort(struct tcp_pcb *pcb)
{
  tcp_abandon(pcb, 1);
}
3.2 由 tcp_slowtmr 函数调用

tcp_slowtmr 函数中完成重传和超时处理,当重传达到设定次数,或者超时达到设定时间,则调用错误类型为 ERR_ABRT 的错误处理回调函数。

重传和超时事件有:

  • PCB 控制块处于 SYN_SENT 状态,重传次数达到 TCP_SYNMAXRTX 次(默认 6 次)
  • PCB 控制块处于其它状态,重传次数达到 TCP_MAXRTX 次(默认 12 次)
  • 坚持定时器探查窗口达到 TCP_MAXRTX 次(默认 12 次)
  • PCB 控制块处于 FIN_WAIT_2 状态,超时达到 TCP_FIN_WAIT_TIMEOUT 秒(默认 20 秒)
  • PCB 控制块处于 SYN_RCVD 状态,超时达到 TCP_SYN_RCVD_TIMEOUT 秒(默认 20 秒)
  • PCB 控制块处于 LAST_ACK 状态,超时达到 2 * TCP_MSL 秒(默认 120 秒)
  • 使能保活、PCB 控制块处于 ESTABLISHEDCLOSE_WAIT 状态,超时达到 pcb->keep_idle + TCP_KEEP_DUR(pcb) 秒(默认 2 小时 10 分 48 秒)

tcp_slowtmr 函数简化后的代码为:

/**
 * Called every 500 ms and implements the retransmission timer and the timer that
 * removes PCBs that have been in TIME-WAIT for enough time. It also increments
 * various timers such as the inactivity timer in each PCB.
 *
 * Automatically called from tcp_tmr().
 */
void
tcp_slowtmr(void)
{
  while (pcb != NULL) {
	/* 这里表明处于 CLOSED、LISTEN 和 TIME_WAIT 状态的连接不会有重传 */
    LWIP_ASSERT("tcp_slowtmr: active pcb->state != CLOSED\n", pcb->state != CLOSED);
    LWIP_ASSERT("tcp_slowtmr: active pcb->state != LISTEN\n", pcb->state != LISTEN);
    LWIP_ASSERT("tcp_slowtmr: active pcb->state != TIME-WAIT\n", pcb->state != TIME_WAIT);

    if (pcb->state == SYN_SENT && pcb->nrtx >= TCP_SYNMAXRTX) {
      ++pcb_remove;				// 处于SYN_SENT 状态,重传达到 6 次
      LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max SYN retries reached\n"));
    } else if (pcb->nrtx >= TCP_MAXRTX) {
      ++pcb_remove;				// 其它状态,重传达到 12 次
      LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max DATA retries reached\n"));
    } else {
      if (pcb->persist_backoff > 0) {
        if (pcb->persist_probe >= TCP_MAXRTX) {
          ++pcb_remove; 		// 探查次数达到 12 次 */
        }
    }

    if (pcb->state == FIN_WAIT_2) {
      if (pcb->flags & TF_RXCLOSED) {
        if ((u32_t)(tcp_ticks - pcb->tmr) >
            TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {
          ++pcb_remove;			// 处于 FIN_WAIT_2 状态,超时达到 20 秒
        }
      }
    }

    /* 注意只有 ESTABLISHED 和 CLOSE_WAIT 状态才会有 KEEPALIVE(保活) */
    if (ip_get_option(pcb, SOF_KEEPALIVE) &&
        ((pcb->state == ESTABLISHED) ||
         (pcb->state == CLOSE_WAIT))) {
      if ((u32_t)(tcp_ticks - pcb->tmr) >
          (pcb->keep_idle + TCP_KEEP_DUR(pcb)) / TCP_SLOW_INTERVAL) {
        ++pcb_remove;			// 使能保活,超时 2 小时 10 分钟 48 秒
        ++pcb_reset;
      } 
    }

    if (pcb->state == SYN_RCVD) {
      if ((u32_t)(tcp_ticks - pcb->tmr) >
          TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {
        ++pcb_remove;			// 处于 SYN_RCVD 状态,超时达到 20 秒
      }
    }

    if (pcb->state == LAST_ACK) {
      if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {
        ++pcb_remove;			// 处于 LAST_ACK 状态,超时达到 120 秒
      }
    }

    /* 需要移除 PCB 控制块 */
    if (pcb_remove) {
      tcp_pcb_purge(pcb);		// 释放 PCB 中的数据缓冲区(refused_data、unsent、unacked、ooseq)
      
      if (prev != NULL) {		// 从 tcp_active_pcbs 列表中移除 PCB
        prev->next = pcb->next;
      } else {
        tcp_active_pcbs = pcb->next;
      }

      if (pcb_reset) {			// 根据需要发送 RST 标志
        tcp_rst(pcb, pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,
                pcb->local_port, pcb->remote_port);
      }
      tcp_free(pcb2);			// 释放 PCB 控制块内存
	  
	  /* 调用错误回调函数 */
      TCP_EVENT_ERR(last_state, err_fn, err_arg, ERR_ABRT);
    } 
  }
}






读后有收获,资助博主养娃 - 千金难买知识,但可以买好多奶粉 (〃‘▽’〃)
千金难买知识,但可以买好多奶粉

文章来源:https://blog.csdn.net/zhzht19861011/article/details/134952286
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。