written by @aw on 2016-12-29

LWIP TCP/IP协议栈讲解

[TOC]

写在前面

在嵌入式开发中,由于很多性价比较高的平台没有TCP/IP协议栈,导致很多项目开发中想通过网络编程实现某些应用时较为困难,自己临时实现的简单TCP/IP协议,由于测试少和时间原因不能够面面俱到,难免存在bug。这时,这款开源的TCP/IP协议栈LWIP,闪亮登场了。经过了多年的迭代和广大程序员的验证,LWIP已经成为开源TCP/IP协议栈的首选。

注册网卡

网卡是与协议栈数据交互的媒介,想了解协议栈的工作原理,第一步就需要搞清楚网卡是如何注册到协议栈的。这里有一个重要的结构体,

/** Generic data structure used for all lwIP network interfaces.
 *  The following fields should be filled in by the initialization
 *  function for the device driver: hwaddr_len, hwaddr[], mtu, flags */
struct netif {
  /** pointer to next in linked list */
  struct netif *next;

  /** IP address configuration in network byte order */
  ip_addr_t ip_addr;
  ip_addr_t netmask;
  ip_addr_t gw;

  /** This function is called by the network device driver
   *  to pass a packet up the TCP/IP stack. */
  netif_input_fn input;
  /** This function is called by the IP module when it wants
   *  to send a packet on the interface. This function typically
   *  first resolves the hardware address, then sends the packet. */
  netif_output_fn output;
  /** This function is called by the ARP module when it wants
   *  to send a packet on the interface. This function outputs
   *  the pbuf as-is on the link medium. */
  netif_linkoutput_fn linkoutput;
#if LWIP_NETIF_STATUS_CALLBACK
  /** This function is called when the netif state is set to up or down
   */
  netif_status_callback_fn status_callback;
#endif /* LWIP_NETIF_STATUS_CALLBACK */
#if LWIP_NETIF_LINK_CALLBACK
  /** This function is called when the netif link is set to up or down
   */
  netif_status_callback_fn link_callback;
#endif /* LWIP_NETIF_LINK_CALLBACK */
#if LWIP_NETIF_REMOVE_CALLBACK
  /** This function is called when the netif has been removed */
  netif_status_callback_fn remove_callback;
#endif /* LWIP_NETIF_REMOVE_CALLBACK */
  /** This field can be set by the device driver and could point
   *  to state information for the device. */
  void *state;
#if LWIP_DHCP
  /** the DHCP client state information for this netif */
  struct dhcp *dhcp;
#endif /* LWIP_DHCP */
#if LWIP_AUTOIP
  /** the AutoIP client state information for this netif */
  struct autoip *autoip;
#endif
#if LWIP_NETIF_HOSTNAME
  /* the hostname for this netif, NULL is a valid value */
  char*  hostname;
#endif /* LWIP_NETIF_HOSTNAME */
  /** maximum transfer unit (in bytes) */
  u16_t mtu;
  /** number of bytes used in hwaddr */
  u8_t hwaddr_len;
  /** link level hardware address of this interface */
  u8_t hwaddr[NETIF_MAX_HWADDR_LEN];
  /** flags (see NETIF_FLAG_ above) */
  u8_t flags;
  /** descriptive abbreviation */
  char name[2];
  /** number of this interface */
  u8_t num;
#if LWIP_SNMP
  /** link type (from "snmp_ifType" enum from snmp.h) */
  u8_t link_type;
  /** (estimate) link speed */
  u32_t link_speed;
  /** timestamp at last change made (up/down) */
  u32_t ts;
  /** counters */
  u32_t ifinoctets;
  u32_t ifinucastpkts;
  u32_t ifinnucastpkts;
  u32_t ifindiscards;
  u32_t ifoutoctets;
  u32_t ifoutucastpkts;
  u32_t ifoutnucastpkts;
  u32_t ifoutdiscards;
#endif /* LWIP_SNMP */
#if LWIP_IGMP
  /** This function could be called to add or delete a entry in the multicast
      filter table of the ethernet MAC.*/
  netif_igmp_mac_filter_fn igmp_mac_filter;
#endif /* LWIP_IGMP */
#if LWIP_NETIF_HWADDRHINT
  u8_t *addr_hint;
#endif /* LWIP_NETIF_HWADDRHINT */
#if ENABLE_LOOPBACK
  /* List of packets to be queued for ourselves. */
  struct pbuf *loop_first;
  struct pbuf *loop_last;
#if LWIP_LOOPBACK_MAX_PBUFS
  u16_t loop_cnt_current;
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
#endif /* ENABLE_LOOPBACK */
};

下边我们一个一个来分析这个结构体,

/** pointer to next in linked list */
  struct netif *next;

很明显,这是组成链表的首要元素,也就是说如果平台上有多个网卡,那么我们可以一一注册,形成链表。

/** IP address configuration in network byte order */
  ip_addr_t ip_addr;
  ip_addr_t netmask;
  ip_addr_t gw;

分别对应网卡的IP地址,子网掩码和网关。

/** This function is called by the network device driver
   *  to pass a packet up the TCP/IP stack. */
  netif_input_fn input;

实际上这是一个回调函数,用于传送上行数据,在你的网卡驱动中调用这个回调函数即可把数据包从网卡上传到协议栈内。此回调函数的定义为

/** Function prototype for netif->input functions. This function is saved as 'input'
 * callback function in the netif struct. Call it when a packet has been received.
 *
 * @param p The received packet, copied into a pbuf
 * @param inp The netif which received the packet
 */
typedef err_t (*netif_input_fn)(struct pbuf *p, struct 		netif *inp);

这一看函数参数,@param p 就是协议栈内的一个buffer, 如果底层接收到一个数据,把他拷贝到 p 内,协议栈就接收到这个数据包了。再来看struct pbuf的定义,

struct pbuf {
  /** next pbuf in singly linked pbuf chain */
  struct pbuf *next;

  /** pointer to the actual data in the buffer */
  void *payload;

  /**
   * total length of this buffer and all next buffers in chain
   * belonging to the same packet.
   *
   * For non-queue packet chains this is the invariant:
   * p->tot_len == p->len + (p->next? p->next->tot_len: 0)
   */
  u16_t tot_len;

  /** length of this buffer */
  u16_t len;

  /** pbuf_type as u8_t instead of enum to save space */
  u8_t /*pbuf_type*/ type;

  /** misc flags */
  u8_t flags;

  /**
   * the reference count always equals the number of pointers
   * that refer to this pbuf. This can be pointers from an application,
   * the stack itself, or pbuf->next pointers from a chain.
   */
  u16_t ref;
};

这也是一个链表,如果有数据,协议栈有一个现成始终在轮询此链表,如果发现有数据,就把packet拿走。(详细参数定义以后再说)。

咱们回到input 回调函数,看到他的第二个参数就是网卡信息 netif, 协议栈用这个参数来判别我的第一个参数 p 里边的数据是从哪个netif发过来的,常用的一个原型就是 tcpip_input 函数。

OK, 我们继续看 netif 结构体的下一个成员,

/** This function is called by the IP module when it wants
   *  to send a packet on the interface. This function typically
   *  first resolves the hardware address, then sends the packet. */
  netif_output_fn output;

这个函数是用来传送下行数据的,也就是从协议栈到网卡的数据。我们来看函数定义,

/** Function prototype for netif->output functions. Called by lwIP when a packet
 * shall be sent. For ethernet netif, set this to 'etharp_output' and set
 * 'linkoutput'.
 *
 * @param netif The netif which shall send a packet
 * @param p The packet to send (p->payload points to IP header)
 * @param ipaddr The IP address to which the packet shall be sent
 */
typedef err_t (*netif_output_fn)(struct netif *netif, struct pbuf *p, ip_addr_t *ipaddr);

netif 经过哪张网卡,这里就需要路由选择了, p 要发送的数据包, ipaddr, 发送给谁? 具体的细节我们在以后专门讲到这里再描述。

/** This function is called by the ARP module when it wants
   *  to send a packet on the interface. This function outputs
   *  the pbuf as-is on the link medium. */
  netif_linkoutput_fn linkoutput;

这也是一个回调函数,发给某网卡的,被 ARP 模块调用。 函数定义,

/** Function prototype for netif->linkoutput functions. Only used for ethernet
 * netifs. This function is called by ARP when a packet shall be sent.
 *
 * @param netif The netif which shall send a packet
 * @param p The packet to send (raw ethernet packet)
 */
typedef err_t (*netif_linkoutput_fn)(struct netif *netif, struct pbuf *p);

可以看到,参数列表分别为 netif 网卡, p 数据包。

#if LWIP_NETIF_STATUS_CALLBACK
  /** This function is called when the netif state is set to up or down
   */
  netif_status_callback_fn status_callback;
#endif /* LWIP_NETIF_STATUS_CALLBACK */

也是一个回调函数,当网卡的状态发生变化时,调用该函数。函数定义为,

/** Function prototype for netif status- or link-callback functions. */
typedef void (*netif_status_callback_fn)(struct netif *netif);

参数列表只有 netif.

下边的成员也是在同样条件下被调用,

#if LWIP_NETIF_LINK_CALLBACK
  /** This function is called when the netif link is set to up or down
   */
  netif_status_callback_fn link_callback;
#endif /* LWIP_NETIF_LINK_CALLBACK */

下一个成员,

#if LWIP_NETIF_REMOVE_CALLBACK
  /** This function is called when the netif has been removed */
  netif_status_callback_fn remove_callback;
#endif /* LWIP_NETIF_REMOVE_CALLBACK */

这三个函数的区别我们以后再介绍。下边继续介绍 netif,

  /** This field can be set by the device driver and could point
   *  to state information for the device. */
  void *state;

网卡驱动中配置此参数,用来表达网卡的状态。

#if LWIP_DHCP
  /** the DHCP client state information for this netif */
  struct dhcp *dhcp;
#endif /* LWIP_DHCP */

这个网卡的DHCP状态信息。比如之前没有配置IP地址,或者之前配置过了IP地址。上次谁给他配置的IP地址。详细定义当我们介绍DHCP的时候再一一分解。

#if LWIP_AUTOIP
  /** the AutoIP client state information for this netif */
  struct autoip *autoip;
#endif

这个网卡的AutoIP状态信息,当我们介绍AUTOIP的时候再一一分解。

#if LWIP_NETIF_HOSTNAME
  /* the hostname for this netif, NULL is a valid value */
  char*  hostname;
#endif /* LWIP_NETIF_HOSTNAME */

这张网卡的名字,可以为NULL.

/** maximum transfer unit (in bytes) */
  u16_t mtu;

最大传输单元,以字节为单位。

/** number of bytes used in hwaddr */
  u8_t hwaddr_len;

MAC地址的长度,这个一般情况为定值 6.

/** link level hardware address of this interface */
  u8_t hwaddr[NETIF_MAX_HWADDR_LEN];

MAC地址, NETIF_MAX_HWADDR_LEN的值即为6.

/** flags (see NETIF_FLAG_ above) */
  u8_t flags;
/** Whether the network interface is 'up'. This is
 * a software flag used to control whether this network
 * interface is enabled and processes traffic.
 * It is set by the startup code (for static IP configuration) or
 * by dhcp/autoip when an address has been assigned.
 */
#define NETIF_FLAG_UP           0x01U
/** If set, the netif has broadcast capability.
 * Set by the netif driver in its init function. */
#define NETIF_FLAG_BROADCAST    0x02U
/** If set, the netif is one end of a point-to-point connection.
 * Set by the netif driver in its init function. */
#define NETIF_FLAG_POINTTOPOINT 0x04U
/** If set, the interface is configured using DHCP.
 * Set by the DHCP code when starting or stopping DHCP. */
#define NETIF_FLAG_DHCP         0x08U
/** If set, the interface has an active link
 *  (set by the network interface driver).
 * Either set by the netif driver in its init function (if the link
 * is up at that time) or at a later point once the link comes up
 * (if link detection is supported by the hardware). */
#define NETIF_FLAG_LINK_UP      0x10U
/** If set, the netif is an ethernet device using ARP.
 * Set by the netif driver in its init function.
 * Used to check input packet types and use of DHCP. */
#define NETIF_FLAG_ETHARP       0x20U
/** If set, the netif is an ethernet device. It might not use
 * ARP or TCP/IP if it is used for PPPoE only.
 */
#define NETIF_FLAG_ETHERNET     0x40U
/** If set, the netif has IGMP capability.
 * Set by the netif driver in its init function. */
#define NETIF_FLAG_IGMP         0x80U

可以设置这些值。

/** descriptive abbreviation */
  char name[2];

网卡的名字缩写。

/** number of this interface */
  u8_t num;

这个网卡的编号。

#if LWIP_SNMP
  /** link type (from "snmp_ifType" enum from snmp.h) */
  u8_t link_type;
  /** (estimate) link speed */
  u32_t link_speed;
  /** timestamp at last change made (up/down) */
  u32_t ts;
  /** counters */
  u32_t ifinoctets;
  u32_t ifinucastpkts;
  u32_t ifinnucastpkts;
  u32_t ifindiscards;
  u32_t ifoutoctets;
  u32_t ifoutucastpkts;
  u32_t ifoutnucastpkts;
  u32_t ifoutdiscards;
#endif /* LWIP_SNMP */

SNMP协议系相关的一些参数,我们介绍SNMP协议的时候再详细讲。

#if LWIP_IGMP
  /** This function could be called to add or delete a entry in the multicast
      filter table of the ethernet MAC.*/
  netif_igmp_mac_filter_fn igmp_mac_filter;
#endif /* LWIP_IGMP */

IGMP协议相关的一些参数,我们介绍IGMP协议的时候再详细讲。

#if LWIP_NETIF_HWADDRHINT
  u8_t *addr_hint;
#endif /* LWIP_NETIF_HWADDRHINT */

IP地址的一个提示。

#if ENABLE_LOOPBACK
  /* List of packets to be queued for ourselves. */
  struct pbuf *loop_first;
  struct pbuf *loop_last;
#if LWIP_LOOPBACK_MAX_PBUFS
  u16_t loop_cnt_current;
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
#endif /* ENABLE_LOOPBACK */

支持本地回传时需要的参数。

上边介绍了描述网卡的结构体,下边介绍如何去注册这个网卡。

netifapi.h 中定义了几个API函数,如果要用这些API,请先定义宏

/**
 * LWIP_NETIF_API==1: Support netif api (in netifapi.c)
 */
#ifndef LWIP_NETIF_API
#define LWIP_NETIF_API                  1
#endif

如果你的函数LWIP中没有定义,先改过来吧。 因为如果不定义这个宏的话,在主循环中会无法调用do_function 函数。详情参见 TCPIP_MSG_NETIFAPI .

/* API for application */
err_t netifapi_netif_add       ( struct netif *netif,
                                 ip_addr_t *ipaddr,
                                 ip_addr_t *netmask,
                                 ip_addr_t *gw,
                                 void *state,
                                 netif_init_fn init,
                                 netif_input_fn input);

这个函数从命名就能看出是往协议栈添加一个网卡 netif, 并且给这个网卡配置ipaddr, netmask, gw, state, 以及input, ``output回调函数。基本上单独调用这一个函数就搞定了注册网卡的事情。

除此之外,我们还可以通过调用下边的函数实现修改网卡的ipaddr, netmask, gw.

err_t netifapi_netif_set_addr  ( struct netif *netif,
                                 ip_addr_t *ipaddr,
                                 ip_addr_t *netmask,
                                 ip_addr_t *gw );

除此之外,下边的几个宏定义函数用来配置,卸载,开启服务等应用。

#define netifapi_netif_remove(n)      netifapi_netif_common(n, netif_remove, NULL)
#define netifapi_netif_set_up(n)      netifapi_netif_common(n, netif_set_up, NULL)
#define netifapi_netif_set_down(n)    netifapi_netif_common(n, netif_set_down, NULL)
#define netifapi_netif_set_default(n) netifapi_netif_common(n, netif_set_default, NULL)
#define netifapi_dhcp_start(n)        netifapi_netif_common(n, NULL, dhcp_start)
#define netifapi_dhcp_stop(n)         netifapi_netif_common(n, dhcp_stop, NULL)
#define netifapi_autoip_start(n)      netifapi_netif_common(n, NULL, autoip_start)
#define netifapi_autoip_stop(n)       netifapi_netif_common(n, NULL, autoip_stop)

注册网卡的流程

如果我们调用函数

/* API for application */
err_t netifapi_netif_add       ( struct netif *netif,
                                 ip_addr_t *ipaddr,
                                 ip_addr_t *netmask,
                                 ip_addr_t *gw,
                                 void *state,
                                 netif_init_fn init,
                                 netif_input_fn input);

协议栈是如何操作的呢?我们来看函数定义:

/**
 * Call netif_add() in a thread-safe way by running that function inside the
 * tcpip_thread context.
 *
 * @note for params @see netif_add()
 */
err_t
netifapi_netif_add(struct netif *netif,
                   ip_addr_t *ipaddr,
                   ip_addr_t *netmask,
                   ip_addr_t *gw,
                   void *state,
                   netif_init_fn init,
                   netif_input_fn input)
{
  struct netifapi_msg msg;
  msg.function = do_netifapi_netif_add;
  msg.msg.netif = netif;
  msg.msg.msg.add.ipaddr  = ipaddr;
  msg.msg.msg.add.netmask = netmask;
  msg.msg.msg.add.gw      = gw;
  msg.msg.msg.add.state   = state;
  msg.msg.msg.add.init    = init;
  msg.msg.msg.add.input   = input;
  TCPIP_NETIFAPI(&msg);
  return msg.msg.err;
}

WTF, 难道只是发个消息,不能,慢慢看,首先确实是先填充了结构体

struct netifapi_msg_msg {
#if !LWIP_TCPIP_CORE_LOCKING
  sys_sem_t sem;
#endif /* !LWIP_TCPIP_CORE_LOCKING */
  err_t err;
  struct netif *netif;
  union {
    struct {
      ip_addr_t *ipaddr;
      ip_addr_t *netmask;
      ip_addr_t *gw;
      void *state;
      netif_init_fn init;
      netif_input_fn input;
    } add;
    struct {
      netifapi_void_fn voidfunc;
      netifapi_errt_fn errtfunc;
    } common;
  } msg;
};
struct netifapi_msg
 {
  void (* function)(struct netifapi_msg_msg *msg);
  struct netifapi_msg_msg msg;
};

然后调用了 TCPIP_NETIFAPI(&msg); 我们看看这里边做了神马,

#define TCPIP_NETIFAPI(m)     tcpip_netifapi_lock(m)

我擦,这开源软件真是够烦的,一层又一层,你封装的这么深我就找不到你了?

/**
 * Call the lower part of a netifapi_* function
 * This function has exclusive access to lwIP core code by locking it
 * before the function is called.
 *
 * @param netifapimsg a struct containing the function to call and its parameters
 * @return ERR_OK (only for compatibility fo tcpip_netifapi())
 */
err_t
tcpip_netifapi_lock(struct netifapi_msg* netifapimsg)
{
  LOCK_TCPIP_CORE();  
  netifapimsg->function(&(netifapimsg->msg));
  UNLOCK_TCPIP_CORE();
  return netifapimsg->msg.err;
}

哈哈,终于抓到你的小尾巴了,回调函数

netifapimsg->function(&(netifapimsg->msg));

/**
 * Call netif_add() inside the tcpip_thread context.
 */
void
do_netifapi_netif_add(struct netifapi_msg_msg *msg)
{
  if (!netif_add( msg->netif,
                  msg->msg.add.ipaddr,
                  msg->msg.add.netmask,
                  msg->msg.add.gw,
                  msg->msg.add.state,
                  msg->msg.add.init,
                  msg->msg.add.input)) {
    msg->err = ERR_IF;
  } else {
    msg->err = ERR_OK;
  }
  TCPIP_NETIFAPI_ACK(msg);
}

继续跟到你的深处,

/**
 * Add a network interface to the list of lwIP netifs.
 *
 * @param netif a pre-allocated netif structure
 * @param ipaddr IP address for the new netif
 * @param netmask network mask for the new netif
 * @param gw default gateway IP address for the new netif
 * @param state opaque data passed to the new netif
 * @param init callback function that initializes the interface
 * @param input callback function that is called to pass
 * ingress packets up in the protocol layer stack.
 *
 * @return netif, or NULL if failed.
 */
struct netif *
netif_add(struct netif *netif, ip_addr_t *ipaddr, ip_addr_t *netmask,
  ip_addr_t *gw, void *state, netif_init_fn init, netif_input_fn input)
{

  LWIP_ASSERT("No init function given", init != NULL);

  /* reset new interface configuration state */
  ip_addr_set_zero(&netif->ip_addr);
  ip_addr_set_zero(&netif->netmask);
  ip_addr_set_zero(&netif->gw);
  netif->flags = 0;
#if LWIP_DHCP
  /* netif not under DHCP control by default */
  netif->dhcp = NULL;
#endif /* LWIP_DHCP */
#if LWIP_AUTOIP
  /* netif not under AutoIP control by default */
  netif->autoip = NULL;
#endif /* LWIP_AUTOIP */
#if LWIP_NETIF_STATUS_CALLBACK
  netif->status_callback = NULL;
#endif /* LWIP_NETIF_STATUS_CALLBACK */
#if LWIP_NETIF_LINK_CALLBACK
  netif->link_callback = NULL;
#endif /* LWIP_NETIF_LINK_CALLBACK */
#if LWIP_IGMP
  netif->igmp_mac_filter = NULL;
#endif /* LWIP_IGMP */
#if ENABLE_LOOPBACK
  netif->loop_first = NULL;
  netif->loop_last = NULL;
#endif /* ENABLE_LOOPBACK */

  /* remember netif specific state information data */
  netif->state = state;
  netif->num = netif_num++;
  netif->input = input;
  NETIF_SET_HWADDRHINT(netif, NULL);
#if ENABLE_LOOPBACK && LWIP_LOOPBACK_MAX_PBUFS
  netif->loop_cnt_current = 0;
#endif /* ENABLE_LOOPBACK && LWIP_LOOPBACK_MAX_PBUFS */

  netif_set_addr(netif, ipaddr, netmask, gw);

  /* call user specified initialization function for netif */
  if (init(netif) != ERR_OK) {
    return NULL;
  }

  /* add this netif to the list */
  netif->next = netif_list;
  netif_list = netif;
  snmp_inc_iflist();

#if LWIP_IGMP
  /* start IGMP processing */
  if (netif->flags & NETIF_FLAG_IGMP) {
    igmp_start(netif);
  }
#endif /* LWIP_IGMP */

  LWIP_DEBUGF(NETIF_DEBUG, ("netif: added interface %c%c IP addr ",
    netif->name[0], netif->name[1]));
  ip_addr_debug_print(NETIF_DEBUG, ipaddr);
  LWIP_DEBUGF(NETIF_DEBUG, (" netmask "));
  ip_addr_debug_print(NETIF_DEBUG, netmask);
  LWIP_DEBUGF(NETIF_DEBUG, (" gw "));
  ip_addr_debug_print(NETIF_DEBUG, gw);
  LWIP_DEBUGF(NETIF_DEBUG, ("\n"));
  return netif;
}

我们来看这个函数内都做了什么活,

  • 各种清场
/* reset new interface configuration state */
  ip_addr_set_zero(&netif->ip_addr);
  ip_addr_set_zero(&netif->netmask);
  ip_addr_set_zero(&netif->gw);
  netif->flags = 0;
#if LWIP_DHCP
  /* netif not under DHCP control by default */
  netif->dhcp = NULL;
#endif /* LWIP_DHCP */
#if LWIP_AUTOIP
  /* netif not under AutoIP control by default */
  netif->autoip = NULL;
#endif /* LWIP_AUTOIP */
#if LWIP_NETIF_STATUS_CALLBACK
  netif->status_callback = NULL;
#endif /* LWIP_NETIF_STATUS_CALLBACK */
#if LWIP_NETIF_LINK_CALLBACK
  netif->link_callback = NULL;
#endif /* LWIP_NETIF_LINK_CALLBACK */
#if LWIP_IGMP
  netif->igmp_mac_filter = NULL;
#endif /* LWIP_IGMP */
#if ENABLE_LOOPBACK
  netif->loop_first = NULL;
  netif->loop_last = NULL;
#endif /* ENABLE_LOOPBACK */
  • 把我们开始的时候传进来的参数一一赋给netif,
/* remember netif specific state information data */
  netif->state = state;
  netif->num = netif_num++;
  netif->input = input;
  NETIF_SET_HWADDRHINT(netif, NULL);
#if ENABLE_LOOPBACK && LWIP_LOOPBACK_MAX_PBUFS
  netif->loop_cnt_current = 0;
#endif /* ENABLE_LOOPBACK && LWIP_LOOPBACK_MAX_PBUFS */

  netif_set_addr(netif, ipaddr, netmask, gw);

初始化动作,

/* call user specified initialization function for netif */
  if (init(netif) != ERR_OK) {
    return NULL;
  }

好了,下边重头戏, 好了,我们的新网卡终于加入了协议栈的链表中,庆祝一下:)

/* add this netif to the list */
  netif->next = netif_list;
  netif_list = netif;
  snmp_inc_iflist();

到这里,我们就可以把我们的网卡加入到协议栈了,以后收发数据包都是通过我们定义的两个回调函数input, output来进行。

主循环

LWIP中有一个主循环来处理所有的网络交互事物,然后交给相应的协议栈来做进一步的处理。

这就是tcpip_thread.

首先,我们看初始化函数

/**
 * Initialize this module:
 * - initialize all sub modules
 * - start the tcpip_thread
 *
 * @param initfunc a function to call when tcpip_thread is running and finished initializing
 * @param arg argument to pass to initfunc
 */
void
tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
  lwip_init();
//   print_msg("Wayne Tsai%s %d\n", __FUNCTION__, __LINE__);

  tcpip_init_done = initfunc;
  tcpip_init_done_arg = arg;
  if(sys_mbox_new(&mbox, TCPIP_MBOX_SIZE) != ERR_OK) {
    LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
  }
#if LWIP_TCPIP_CORE_LOCKING
  if(sys_mutex_new(&lock_tcpip_core) != ERR_OK) {
    LWIP_ASSERT("failed to create lock_tcpip_core", 0);
  }
#endif /* LWIP_TCPIP_CORE_LOCKING */

  sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, PRIORITY_TASK_MID_LWIP);
}
  • lwip_init() ,初始化所有的协议栈,分配内存。
  • 把初始化函数注册进LWIP。
tcpip_init_done = initfunc;
  tcpip_init_done_arg = arg;
  • 启动主线程
sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL, TCPIP_THREAD_STACKSIZE, PRIORITY_TASK_MID_LWIP);

我们来看主线程的定义

/**
 * The main lwIP thread. This thread has exclusive access to lwIP core functions
 * (unless access to them is not locked). Other threads communicate with this
 * thread using message boxes.
 *
 * It also starts all the timers to make sure they are running in the right
 * thread context.
 *
 * @param arg unused argument
 */
static void
tcpip_thread(void *arg)
{
  struct tcpip_msg *msg;
  LWIP_UNUSED_ARG(arg);
//  print_msg("Wayne Tsai%s %d\n", __FUNCTION__, __LINE__);

  if (tcpip_init_done != NULL) {
    tcpip_init_done(tcpip_init_done_arg);
  }

  LOCK_TCPIP_CORE();
  while (1) {                          /* MAIN Loop */
    UNLOCK_TCPIP_CORE();
    LWIP_TCPIP_THREAD_ALIVE();
    /* wait for a message, timeouts are processed while waiting */
    sys_timeouts_mbox_fetch(&mbox, (void **)&msg);
    LOCK_TCPIP_CORE();
    switch (msg->type) {
#if LWIP_NETCONN
    case TCPIP_MSG_API:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: API message %p\n", (void *)msg));
      msg->msg.apimsg->function(&(msg->msg.apimsg->msg));
      break;
#endif /* LWIP_NETCONN */

#if !LWIP_TCPIP_CORE_LOCKING_INPUT
    case TCPIP_MSG_INPKT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: PACKET %p\n", (void *)msg));
#if LWIP_ETHERNET
      if (msg->msg.inp.netif->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
        ethernet_input(msg->msg.inp.p, msg->msg.inp.netif);
      } else
#endif /* LWIP_ETHERNET */
      {
        ip_input(msg->msg.inp.p, msg->msg.inp.netif);
      }
      memp_free(MEMP_TCPIP_MSG_INPKT, msg);
      break;
#endif /* LWIP_TCPIP_CORE_LOCKING_INPUT */

#if LWIP_NETIF_API
    case TCPIP_MSG_NETIFAPI:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: Netif API message %p\n", (void *)msg));
      msg->msg.netifapimsg->function(&(msg->msg.netifapimsg->msg));
      break;
#endif /* LWIP_NETIF_API */

#if LWIP_TCPIP_TIMEOUT
    case TCPIP_MSG_TIMEOUT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: TIMEOUT %p\n", (void *)msg));
      sys_timeout(msg->msg.tmo.msecs, msg->msg.tmo.h, msg->msg.tmo.arg);
      memp_free(MEMP_TCPIP_MSG_API, msg);
      break;
    case TCPIP_MSG_UNTIMEOUT:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: UNTIMEOUT %p\n", (void *)msg));
      sys_untimeout(msg->msg.tmo.h, msg->msg.tmo.arg);
      memp_free(MEMP_TCPIP_MSG_API, msg);
      break;
#endif /* LWIP_TCPIP_TIMEOUT */

    case TCPIP_MSG_CALLBACK:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK %p\n", (void *)msg));
      msg->msg.cb.function(msg->msg.cb.ctx);
      memp_free(MEMP_TCPIP_MSG_API, msg);
      break;

    case TCPIP_MSG_CALLBACK_STATIC:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: CALLBACK_STATIC %p\n", (void *)msg));
      msg->msg.cb.function(msg->msg.cb.ctx);
      break;

    default:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: invalid message: %d\n", msg->type));
      LWIP_ASSERT("tcpip_thread: invalid message", 0);
      break;
    }
  }
}

在主线程中,一直在监听自己的消息队列,如果有消息就去处理,否则等在哪里。

UNLOCK_TCPIP_CORE();
LWIP_TCPIP_THREAD_ALIVE();
 /* wait for a message, timeouts are processed while waiting */
sys_timeouts_mbox_fetch(&mbox, (void **)&msg);
LOCK_TCPIP_CORE();

我们也需要注意协议栈的核心锁, 进入循环后开锁,得到一个数据包后,上锁然后做处理,处理完了 再开锁等待新的数据包。

下边一一过一下各种消息,

TCPIP_MSG_API

这类消息是跟连接有关的消息,与之密切相关的一类函数就是netconn_, 所有的跟连接相关的函数都会调用宏。

#define TCPIP_APIMSG(m)       tcpip_apimsg(m)

并且实现如下,

/**
 * Call the lower part of a netconn_* function
 * This function is then running in the thread context
 * of tcpip_thread and has exclusive access to lwIP core code.
 *
 * @param apimsg a struct containing the function to call and its parameters
 * @return ERR_OK if the function was called, another err_t if not
 */
err_t
tcpip_apimsg(struct api_msg *apimsg)
{
  struct tcpip_msg msg;
#ifdef LWIP_DEBUG
  /* catch functions that don't set err */
  apimsg->msg.err = ERR_VAL;
#endif
  
  if (sys_mbox_valid(&mbox)) {
    msg.type = TCPIP_MSG_API;
    msg.msg.apimsg = apimsg;
    sys_mbox_post(&mbox, &msg);
    sys_arch_sem_wait(&apimsg->msg.conn->op_completed, 0);
    return apimsg->msg.err;
  }
  return ERR_VAL;
}

我们可以看到所有的msg.type 都赋值为 TCPIP_MSG_API.

在主循环中,我们看到,如果msg.typeTCPIP_MSG_API, 那么会直接回调msg.function.

也就是说这个消息如何处置,是早有定义的。

举个例子,

/**
 * Connect a netconn to a specific remote IP address and port.
 *
 * @param conn the netconn to connect
 * @param addr the remote IP address to connect to
 * @param port the remote port to connect to (no used for RAW)
 * @return ERR_OK if connected, return value of tcp_/udp_/raw_connect otherwise
 */
err_t
netconn_connect(struct netconn *conn, ip_addr_t *addr, u16_t port)
{
  struct api_msg msg;
  err_t err;

  LWIP_ERROR("netconn_connect: invalid conn", (conn != NULL), return ERR_ARG;);

  msg.function = do_connect;
  msg.msg.conn = conn;
  msg.msg.msg.bc.ipaddr = addr;
  msg.msg.msg.bc.port = port;
  /* This is the only function which need to not block tcpip_thread */
  err = tcpip_apimsg(&msg);

  NETCONN_SET_SAFE_ERR(conn, err);
  return err;
}

如果我们调用这个函数来连接其他的服务,消息传到主循环后,会回调do_connect.

TCPIP_MSG_INPKT

当从底层网卡接受到一个数据包后,底层网卡需要调用这个函数,调用后就把这个数据包发送到主循环进行处理。底层常用的函数定义即:

/**
 * Pass a received packet to tcpip_thread for input processing
 *
 * @param p the received packet, p->payload pointing to the Ethernet header or
 *          to an IP header (if inp doesn't have NETIF_FLAG_ETHARP or
 *          NETIF_FLAG_ETHERNET flags)
 * @param inp the network interface on which the packet was received
 */
err_t
tcpip_input(struct pbuf *p, struct netif *inp)
{
#if LWIP_TCPIP_CORE_LOCKING_INPUT
  err_t ret;
  LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_input: PACKET %p/%p\n", (void *)p, (void *)inp));
  LOCK_TCPIP_CORE();
#if LWIP_ETHERNET
  if (inp->flags & (NETIF_FLAG_ETHARP | NETIF_FLAG_ETHERNET)) {
    ret = ethernet_input(p, inp);
  } else
#endif /* LWIP_ETHERNET */
  {
    ret = ip_input(p, inp);
  }
  UNLOCK_TCPIP_CORE();
  return ret;
#else /* LWIP_TCPIP_CORE_LOCKING_INPUT */
  struct tcpip_msg *msg;

  if (!sys_mbox_valid(&mbox)) {
    return ERR_VAL;
  }
  msg = (struct tcpip_msg *)memp_malloc(MEMP_TCPIP_MSG_INPKT);
  if (msg == NULL) {
    return ERR_MEM;
  }

  msg->type = TCPIP_MSG_INPKT;
  msg->msg.inp.p = p;
  msg->msg.inp.netif = inp;
  if (sys_mbox_trypost(&mbox, msg) != ERR_OK) {
    memp_free(MEMP_TCPIP_MSG_INPKT, msg);
    return ERR_MEM;
  }
  return ERR_OK;
#endif /* LWIP_TCPIP_CORE_LOCKING_INPUT */
}

我们看到这个函数分为两种情况,一种是当定义了LWIP_TCPIP_CORE_LOCKING_INPUT 时,立即处理该数据包,否则把该数据包发送给发到消息队列里边,交由主循环处理。无论哪种情况,真正的处理方式是一致的。

TCPIP_MSG_NETIFAPI { #TCPIP_MSG_NETIFAPI }

我们可以看到在主循环中此类消息的处理方式,就是直接回调当时发送这个消息时定义的function 函数。

#if LWIP_NETIF_API
    case TCPIP_MSG_NETIFAPI:
      LWIP_DEBUGF(TCPIP_DEBUG, ("tcpip_thread: Netif API message %p\n", (void *)msg));
      msg->msg.netifapimsg->function(&(msg->msg.netifapimsg->msg));
      break;
#endif /* LWIP_NETIF_API */

看到没,定义了宏LWIP_NETIF_API 没?如果没有,你的回调函数是没法被调用的。

TCPIP_MSG_TIMEOUT

这个消息没啥意义。在协议栈中没发现有任何的使用。

TCPIP_MSG_UNTIMEOUT

这个消息没啥意义。在协议栈中没发现有任何的使用。

TCPIP_MSG_CALLBACK

这类消息适用于要执行某个操作,但是时机不对,需要主循环来回调的场合。

TCPIP_MSG_CALLBACK_STATIC

这类消息实际在源代码中并没有使用。

DHCP

如果host不用静态IP而选择DHCP的话,在LWIP中的DHCP service是怎么工作的呢?

DHCP是一个Server-Client架构,具体的协议请参考 RFC 1531. 这个协议很简单,只有几个命令交互,通过UDP协议传输。

下边我们看在LWIP的DHCP Server,定义在 dhcps.c

初始化代码在函数

void dhcps_init(void)
{
	err_t err=0;

	dhcps_config_init();

	/* Init dhcp server clint info table */
	memset(g_clients, 0, sizeof(g_clients));

    pcb_dhcps = udp_new();

	if(!pcb_dhcps){
		DHCPS_PRINT_QUEUE(" dhcps pcb new failed.\n");
		DHCPS_PRINT_QUEUE(" Dhcp server init failed\n");
		return;
	}

    err = udp_bind(pcb_dhcps, IP_ADDR_ANY, DHCPS_SERVER_PORT);

	if(err != ERR_OK){
		DHCPS_PRINT_QUEUE(" dhcps pcb bind failed. err = %d\n", err);
		DHCPS_PRINT_QUEUE(" Dhcp server init failed\n");
		udp_remove(pcb_dhcps);
		return;
	}

    udp_recv(pcb_dhcps, handle_dhcp, NULL);
}

dhcps_config_init(), 主要是初始化DHCP服务器的IP地址,端口号,支持多少IP,起始IP和截止IP分别是多少等等。详细的代码你可以去源码中去看。

pcb_dhcps = udp_new(), 申请到DHCP服务器的socket句柄。

err = udp_bind(pcb_dhcps, IP_ADDR_ANY, DHCPS_SERVER_PORT) 把socket和端口号绑定起来。

udp_recv(pcb_dhcps, handle_dhcp, NULL), 然后就是接受所有来自客户端的数据包,然后再handle_dhcp 里处理。

下边我们重点看处理函数,

static void handle_dhcp(void *arg, struct udp_pcb *pcb, struct pbuf *p, struct ip_addr *addr, uint16_t port)
{
	struct dhcp_msg *msg=NULL;
	uint8_t *msg_type = NULL;
	struct client_info * client = NULL;
	uint32_t requested_ip = 0;
	uint8_t	*requested_ip_opt = NULL;


    if (p == NULL)
        return;

    if (p->next != NULL)
    {
		DHCPS_PRINT_QUEUE("Multiple pbuf get in dhcp server\n");
		pbuf_free(p);
		return;
    }

	msg = p->payload;

	msg_type = dhcps_option_get(msg, DHCP_OPTION_MSG_TYPE);

	DHCPS_PRINT_QUEUE("Get dhcp message type = 0x%x (%02X-%02X-%02X-%02X-%02X-%02X)\n", *msg_type, msg->chaddr[0], msg->chaddr[1], msg->chaddr[2], msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]);

	switch(*msg_type){
		case DHCPDISCOVER:
			send_offer(msg);
			break;
		case DHCPREQUEST:
			client = find_client_by_mac(msg->chaddr);
			requested_ip_opt = dhcps_option_get(msg, DHCP_OPTION_REQ_IPADDR);
			if(requested_ip_opt){
				/* Get ip from request option */
				memcpy(&requested_ip, requested_ip_opt, 4);
				DHCPS_PRINT_QUEUE("Get requested ip %s from option(%02X-%02X-%02X-%02X-%02X-%02X)\n", inet_ntoa(requested_ip),
					msg->chaddr[0], msg->chaddr[1], msg->chaddr[2], msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]);
			}else{
				/* Get ip from client address */
				requested_ip = msg->ciaddr;
				DHCPS_PRINT_QUEUE("Get requested ip %s from ciaddr(%02X-%02X-%02X-%02X-%02X-%02X)\n", inet_ntoa(requested_ip),
					msg->chaddr[0], msg->chaddr[1], msg->chaddr[2], msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]);
			}

			if(!requested_ip){
				DHCPS_PRINT_QUEUE("Ignore the DHCP REQUEST, (%02X-%02X-%02X-%02X-%02X-%02X)\n",
					msg->chaddr[0], msg->chaddr[1], msg->chaddr[2], msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]);
				break;
			}

			if(client && requested_ip == client->ip){
				send_ack(msg, requested_ip);
				break;
			}
			send_nak(msg, requested_ip);
			break;
		case DHCPDECLINE:
			break;
		case DHCPRELEASE:
			client = find_client_by_mac(msg->chaddr);
			if(client&&(msg->ciaddr == client->ip)){
				client->expires = get_current_time();
				DHCPS_PRINT_QUEUE("Release IP %s (%02X-%02X-%02X-%02X-%02X-%02X)\n", inet_ntoa(client->ip), 
					msg->chaddr[0], msg->chaddr[1], msg->chaddr[2], msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]);
			}

			break;
		case DHCPINFORM:
			send_ack(msg, msg->yiaddr);
			break;
		default:
			break;
	}
    pbuf_free(p);
}

我们可以看到这个函数主要处理从客户端发送来的数据包,不多,分别是 DHCPDISCOVER, DHCPREQUEST, DHCPDECLINE, DHCPRELEASE, DHCPINFORM.

下边我们一个一个来看。

DHCPDISCOVER

这个消息是客户端往网络上发送的广播包,寻找网络上的DHCP服务器,如果网络上的DHCP服务器接收到改网络包,需要返回一个数据包,表明“嘿,哥们,我在这”。 顺便把

服务器的这个招呼是怎么用代码打的呢?

static void send_offer( struct dhcp_msg *msg){
	struct pbuf *p;
	struct client_info *client = NULL;
	struct dhcp_msg *new_msg = NULL;
	uint8_t *optptr_end = NULL;

	p = pbuf_alloc(PBUF_TRANSPORT,sizeof(struct dhcp_msg),PBUF_RAM);
	new_msg = p->payload;
	create_msg(new_msg, msg);

	client = find_client_by_mac(msg->chaddr);

	if(client){
		new_msg->yiaddr = client->ip;
		DHCPS_PRINT_QUEUE("Find exist entry with %s (%02X-%02X-%02X-%02X-%02X-%02X)\n",
		inet_ntoa(new_msg->yiaddr), new_msg->chaddr[0], new_msg->chaddr[1], new_msg->chaddr[2], new_msg->chaddr[3], new_msg->chaddr[4], new_msg->chaddr[5]);
	}else{
		new_msg->yiaddr = search_free_ip(msg->chaddr);
		if(new_msg->yiaddr){
			DHCPS_PRINT_QUEUE("Find free entry with IP %s (%02X-%02X-%02X-%02X-%02X-%02X)\n",
					inet_ntoa(new_msg->yiaddr), new_msg->chaddr[0], new_msg->chaddr[1], new_msg->chaddr[2], new_msg->chaddr[3], new_msg->chaddr[4], new_msg->chaddr[5]);
		}
	}
	/* No ip can use and do nothing */
	if(!new_msg->yiaddr){
		DHCPS_PRINT_QUEUE("NO Free IP can use\n");
		goto offer_end;
	}


	client = add_client(new_msg->chaddr, new_msg->yiaddr, svrconf.offer_time);

	if(!client){
		DHCPS_PRINT_QUEUE("Add new client netry in table failed (%02X-%02X-%02X-%02X-%02X-%02X)\n",
		new_msg->chaddr[0], new_msg->chaddr[1], new_msg->chaddr[2], new_msg->chaddr[3], new_msg->chaddr[4], new_msg->chaddr[5]);
		goto offer_end;
	}

	DHCPS_PRINT_QUEUE("Sending OFFER with IP %s (%02X-%02X-%02X-%02X-%02X-%02X)\n", 
		inet_ntoa(new_msg->yiaddr), new_msg->chaddr[0], new_msg->chaddr[1], new_msg->chaddr[2], new_msg->chaddr[3], new_msg->chaddr[4], new_msg->chaddr[5]);

	/* Add dhcp options */
	optptr_end = add_msg_type(new_msg->options, DHCPOFFER);
	optptr_end = add_server_options(optptr_end);
	add_end(optptr_end);
	udp_sendto(pcb_dhcps, p, IP_ADDR_BROADCAST, DHCPS_CLIENT_PORT);

offer_end :
	pbuf_free(p);
	return;
}

首先,进来之后申请pbuf, 然后创建返回用的msg. client = find_client_by_mac(msg->chaddr), 查查看之前这个客户端有没有访问过该DHCP服务器。 如果有的话,那么肯定之前也给他分配过IP地址。再次如果之前分配给他的IP地址空闲的话,那么直接把该IP地址分配给他,否则,从IP地址资源池中找到空闲的IP地址分配给他。如果资源池里没有空闲的IP地址的话,直接不理会客户端的请求了,客户端就会认为,“我擦,网络上竟然没有DHCP服务器,太low了。还其他方式试试”。

当服务器发现有可用的IP服务器给该客户端使用时,要把该IP给这个客户端使用,就需要先把这个IP地址绑定的客户端删除,换成当前的客户端。这个过程体现在 client = add_client(new_msg->chaddr, new_msg->yiaddr, svrconf.offer_time) 中。

然后最终再次表明一下数据包的身份,贴上通行证之后发送出去,注意,仍然是采用广播的形式。

/* Add dhcp options */
optptr_end = add_msg_type(new_msg->options, DHCPOFFER);
optptr_end = add_server_options(optptr_end);
add_end(optptr_end);
udp_sendto(pcb_dhcps, p, IP_ADDR_BROADCAST, DHCPS_CLIENT_PORT);

DHCPREQUEST

客户端之前已经通过DHCP分配到了IP地址,但是他用了一段时间后,关机了,这段时间内有可能其他的客户端使用了这个IP地址。这就是为什么我们电脑开机时,有时候会看到IP地址冲突,然后又没事了。

客户端发送这个消息来确认一件事,“老大,这个IP地址我还能用吗?不能用的话,你给我个新的吧,嘿嘿”。

服务器收到这个消息后会做一下的事情。

  1. 验证这个客户端之前有没有在访问过我。 如果没有访问过,直接拒绝,“我好像跟你不熟啊”。 如果访问过,“老朋友,你等等,我查一下啊”。
client = find_client_by_mac(msg->chaddr);
requested_ip_opt = dhcps_option_get(msg,    						DHCP_OPTION_REQ_IPADDR);
if(requested_ip_opt){
	/* Get ip from request option */
	memcpy(&requested_ip, requested_ip_opt, 4);
	DHCPS_PRINT_QUEUE("Get requested ip %s from option(%02X-%02X-%02X-%02X-%02X-%02X)\n", inet_ntoa(requested_ip),
		msg->chaddr[0], msg->chaddr[1], msg->chaddr[2], msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]);
}else{
	/* Get ip from client address */
	requested_ip = msg->ciaddr;
	DHCPS_PRINT_QUEUE("Get requested ip %s from ciaddr(%02X-%02X-%02X-%02X-%02X-%02X)\n", inet_ntoa(requested_ip),
		msg->chaddr[0], msg->chaddr[1], msg->chaddr[2], msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]);
}

if(!requested_ip){
	DHCPS_PRINT_QUEUE("Ignore the DHCP REQUEST, (%02X-%02X-%02X-%02X-%02X-%02X)\n",
		msg->chaddr[0], msg->chaddr[1], msg->chaddr[2], msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]);
	break;
}
  1. 从客户端发来的消息中得到客户端之前使用的IP地址。如果从消息中根本没有找到IP地址的话,直接不理他。
  2. 拒绝或接受。
if(client && requested_ip == client->ip){
				send_ack(msg, requested_ip);
				break;
}
send_nak(msg, requested_ip);

我们看看send_ack 干啥了。

static void send_ack( struct dhcp_msg *msg, uint32_t requested_ip){
	struct pbuf *p;
	struct dhcp_msg *new_msg = NULL;
	uint8_t *optptr_end = NULL;
	uint8_t *msg_type = NULL;

	p = pbuf_alloc(PBUF_TRANSPORT,sizeof(struct dhcp_msg),PBUF_RAM);
	msg_type = dhcps_option_get(msg, DHCP_OPTION_MSG_TYPE);
	new_msg = p->payload;
	create_msg(new_msg, msg);

	new_msg->yiaddr = requested_ip;

	optptr_end = add_msg_type(new_msg->options, DHCPACK);
	optptr_end = add_server_options(optptr_end);
	add_end(optptr_end);

	udp_sendto(pcb_dhcps, p, IP_ADDR_BROADCAST, DHCPS_CLIENT_PORT);
	switch(*msg_type){
		case DHCPREQUEST:
			add_client(new_msg->chaddr, new_msg->yiaddr, svrconf.lease_time);
			DHCPS_PRINT_QUEUE("Sending ACK with IP %s (%02X-%02X-%02X-%02X-%02X-%02X)\n", inet_ntoa(new_msg->yiaddr),
					new_msg->chaddr[0], new_msg->chaddr[1], new_msg->chaddr[2], new_msg->chaddr[3], new_msg->chaddr[4], new_msg->chaddr[5]);
			break;
		case DHCPINFORM:
			DHCPS_PRINT_QUEUE("Sending ACK with IP %s (%02X-%02X-%02X-%02X-%02X-%02X)\n", inet_ntoa(new_msg->ciaddr),
					new_msg->chaddr[0], new_msg->chaddr[1], new_msg->chaddr[2], new_msg->chaddr[3], new_msg->chaddr[4], new_msg->chaddr[5]);
			break;
		default:
			break;
	}
	pbuf_free(p);
}

里边干的活跟send_offer 差不多,就是通行证不一样,表明返回的消息是 DHCPACK.

我们看服务器是怎么无情的拒绝的,

static void send_nak( struct dhcp_msg *msg, uint32_t requested_ip){
	struct pbuf *p;
	struct dhcp_msg *new_msg = NULL;
	uint8_t *optptr_end = NULL;

	p = pbuf_alloc(PBUF_TRANSPORT,sizeof(struct dhcp_msg),PBUF_RAM);
	new_msg = p->payload;
	create_msg(new_msg, msg);

	new_msg->yiaddr = requested_ip;
	DHCPS_PRINT_QUEUE("Sending NAK with IP %s (%02X-%02X-%02X-%02X-%02X-%02X)\n", 
		inet_ntoa(new_msg->yiaddr), new_msg->chaddr[0], new_msg->chaddr[1], new_msg->chaddr[2], new_msg->chaddr[3], new_msg->chaddr[4], new_msg->chaddr[5]);

	optptr_end = add_msg_type(new_msg->options, DHCPNAK);
	add_end(optptr_end);

	udp_sendto(pcb_dhcps, p, IP_ADDR_BROADCAST, DHCPS_CLIENT_PORT);
	pbuf_free(p);
}

看到没,看到没,连对不起都没说,直接说不要。

optptr_end = add_msg_type(new_msg->options, DHCPNAK)

DHCPDECLINE

这个消息,在LWIP中没做处理。

DHCPRELEASE

协议规定,如果客户端是正常关机的话,在关机之前需要通知服务器”好了,我用完了,谢谢”。 这个意思通过DHCPRELEASE来表达。

服务器收到这个命令后,把这个客户端设置为过期。注意,服务器不需要任何返回。

client = find_client_by_mac(msg->chaddr);
if(client&&(msg->ciaddr == client->ip)){
	client->expires = get_current_time();
	DHCPS_PRINT_QUEUE("Release IP %s (%02X-%02X-%02X-%02X-            %02X-%02X)\n", inet_ntoa(client->ip), 
		msg->chaddr[0], msg->chaddr[1], msg->chaddr[2], msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]);
			}

DHCPINFORM

这个消息很少用到,在我看来简直是浪费资源。当时定义它的时候是想分配给客户端IP后,客户端还想要更多详细的信息,那么就发送这个消息,在LWIP中,服务器收到这个消息,还是返回同样的内容send_ack.