BitVisorのlwIP機能の解説

これはQiitaの下書きをふと確認したら発見した，昨年のAdvent Calendar用の記事になります．多分．
文章は結構前に書いたものだと思うので（記憶がない..)，もしかすると最新版のコードと差異があるかもしれません．BitVisor 1.4~2.0あたりのコードを参照していると思います．

---

[前回](https://qiita.com/mmi/items/6b2436ca0ca0400effbd)BitVisorのネットワークAPI(主にpassモジュール)とpro1000ドライバについて書きました．
ipあるいはippassモジュールを利用することでBitVisor内でlwIP機能が使えるようになります．
ここではlwIP概要と，BitVisorにおけるlwIP機能の処理の流れおよびechoサーバの実装について簡単に説明します．
lwIPに関連するコードは`ip/`以下に存在します．


## lwIPの概要
lwIPは軽量なTCP/IPスタックの実装であり，BSDライセンスであることもあって主に組み込み製品などで利用されています．
BitVisorはlwIPのnosysモードを利用しています．
これはsocket等のサポートがなく，システム全体で一つの実行スレッドしか持てませんが，その分移植が容易です．

nosysモードでは，lwIPを利用する流れはおおまかに以下のようになります．
基本はコールバック関数を設定しておいて，lwIPが必要に応じてそれを呼び出す流れになります．

- lwIPの初期化
    - インタフェース(`netif`) の設定
    - lwIPがパケットを送信するための関数の登録などをする
- サーバの追加 (listen)
    - `tcp_new()`でPCB (protocol control blockを追加)
    - `tcp_bind()`, `tcp_listen()`
    - `tcp_accept()`でacceptした際に呼ばれるコールバック関数を設定
- lwIPの実行スレッドから `netif->input()`を使ってTCP/IPスタックへパケットを送る
    - あとはlwIPがパケットを解釈し，必要に応じて`tcp_accept()`で指定したコールバック関数や，`tcp_accept()`のコールバック関数の中で設定されたコールバック関数を呼び出す．

lwIP側からコネクションを貼ることも可能です．

lwIPに関する詳細は[lwIPのドキュメント](http://www.nongnu.org/lwip/2_0_x/index.html)を参照してください．

## BitVisor内でのlwIP処理の流れ
ipあるいはippassモジュールがドライバによって開始されると，以下の`net_ip_start()`が呼ばれます．

```c
static void
net_ip_start (void *handle)
{
	struct net_ip_data *p = handle;

	p->phys_func->set_recv_callback (p->phys_handle, net_ip_phys_recv, p);
	if (p->virt_func)
		p->virt_func->set_recv_callback (p->virt_handle,
						 net_ip_virt_recv, p);
	thread_new (net_thread, p, VMM_STACKSIZE);
}
```

ここで，passモジュールなどと同様にコールバック関数の設定をおこなった後，`net_thread`という新しいスレッドを立ち上げています．
このスレッドでlwIPの処理をおこないます．

`net_thread()`は以下のようになっています．

```c
static void
net_thread (void *arg)
{
	struct net_ip_data *p = arg;
	struct ip_main_netif netif_arg[1];

	netif_arg[0].handle = p;
	netif_arg[0].use_as_default = 1;
	netif_arg[0].use_dhcp = config.ip.use_dhcp;
	netif_arg[0].ipaddr = config.ip.ipaddr;
	netif_arg[0].netmask = config.ip.netmask;
	netif_arg[0].gateway = config.ip.gateway;
	ip_main_init (netif_arg, 1);
	for (;;) {
		ip_main_task ();
		net_task_call ();
		schedule ();
	}
}
```

まず．`ip_main_init()`でlwIP機能の初期化をおこないます．

```c
void
ip_main_init (struct ip_main_netif *netif_arg, int netif_num)
{
	/* Initialize TCP/IP Stack. */
	lwip_init ();

	/* Allocate context. */
	tcpip_context = mem_malloc (sizeof *tcpip_context);
	LWIP_ASSERT ("tcpip_context", tcpip_context);
	memset (tcpip_context, 0, sizeof *tcpip_context);

	/* Configure and add network interface. */
	tcpip_netif_conf (netif_arg, netif_num);
}
```

`tcpip_netif_conf()`の中でlwIPのデータ構造である`netif`の初期化をおこなっています．

```c
static void
tcpip_netif_conf (struct ip_main_netif *netif_arg, int netif_num)
{
	struct netif *netif;
	int i;

	/* Allocate network interface. */
	netif = mem_malloc (sizeof *tcpip_context->netif * netif_num);
	LWIP_ASSERT ("netif", netif);
	tcpip_context->netif = netif;
	tcpip_context->netif_num = netif_num;
	tcpip_context->oldip_addr = mem_malloc (sizeof *tcpip_context->
						oldip_addr * netif_num);
	LWIP_ASSERT ("tcpip_context->oldip_addr", tcpip_context->oldip_addr);

	for (i = 0; i < netif_num; i++)
		tcpip_netif_conf_sub (&netif[i],
				      netif_arg[i].ipaddr,
				      netif_arg[i].netmask,
				      netif_arg[i].gateway,
				      netif_arg[i].use_as_default,
				      netif_arg[i].use_dhcp,
				      netif_arg[i].handle,
				      &tcpip_context->oldip_addr[i]);
}

static void
tcpip_netif_conf_sub (struct netif *netif, unsigned char *ipaddr_a,
		      unsigned char *netmask_a, unsigned char *gw_a,
		      int use_as_default, int use_dhcp, void *handle,
		      ip_addr_t *oldip_addr)
{
	ip_addr_t ipaddr, netmask, gw;

	/* Setup IP address etc. */
	IP4_ADDR (&ipaddr, ipaddr_a[0], ipaddr_a[1], ipaddr_a[2], ipaddr_a[3]);
	IP4_ADDR (&netmask, netmask_a[0], netmask_a[1], netmask_a[2],
		  netmask_a[3]);
	IP4_ADDR (&gw, gw_a[0], gw_a[1], gw_a[2], gw_a[3]);
	IP4_ADDR (oldip_addr, 0, 0, 0, 0);

	/* Register the network interface. */
	netif_add (netif, &ipaddr, &netmask, &gw, handle, ip_netif_init,
		   ethernet_input);

	/* Some additional configuration... */
	if (use_as_default) {
		/* Use the interface as default one. */
		netif_set_default (netif);
	}
	if (use_dhcp) {
		/* Dynamic IP assignment by DHCP. */
		dhcp_start (netif);
	} else {
		/* Static IP assignment. */
		netif_set_up (netif);
	}
}

static err_t
ip_netif_init (struct netif *netif)
{
	netif->name[0] = 'v';
	netif->name[1] = 'm';
	netif->output = etharp_output;
	netif->linkoutput = ip_netif_output;
	netif->hwaddr_len = ETHARP_HWADDR_LEN;
	net_main_get_mac_address (netif->state, netif->hwaddr);
	netif->mtu = 1500;
	netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP |
		NETIF_FLAG_LINK_UP;
	net_main_set_recv_arg (netif->state, netif);
	return ERR_OK;
}


void
net_main_set_recv_arg (void *handle, void *arg)
{
	struct net_ip_data *p = handle;

	p->input_arg = arg;
	p->input_ok = true;
}
```

`netif_add()`や`dhcp_start(), netif_set_up()` などはlwIPの関数です．
ちょっと処理が入り組んでいますが，`netif_add()`の中で`netif`をセットアップします．
`netif_add()`の引数として渡した`handle`は`netif->state`として設定されます．
`netif_add()`の中で`ip_netif_init()`が呼ばれ，その中の`net_main_set_recv_arg`によって`handle`に`netif`を登録しています．
この`handle`はもともと何だったかというと，`net_new_nic()`したときに作成した`net_ip_data`でした．
ネットワークAPIのコールバック関数のparamとしてこの`handle`が登録されているので，コールバック関数から`netif`が参照できます．

また，`netif->linkoutput`でlwIPがパケットを送信するための関数を登録しています．以下のように，最終的には`phys_func->send()`でパケットを送信します．

```c
static err_t
ip_netif_output (struct netif *netif, struct pbuf *p)
{
	char buf[1600];
	int offset = 0, len;

	if (p && !p->next) {
		/* Fast path */
		net_main_send (netif->state, p->payload, p->len);
		return ERR_OK;
	}
	for (; p; p = p->next) {
		len = p->len;
		if (offset + len > sizeof buf)
			len = sizeof buf - offset;
		memcpy (buf + offset, p->payload, len);
		offset += len;
	}
	net_main_send (netif->state, buf, offset);
	return ERR_OK;
}

void
net_main_send (void *handle, void *buf, unsigned int len)
{
	struct net_ip_data *p = handle;

	p->phys_func->send (p->phys_handle, 1, &buf, &len, true);
}
```

初期化を行なった後は無限ループで処理をおこないます．
ここで，`ip_main_task()`では`tcpip_netif_poll()`によってパケットの受信をおこないます．
これは実際にはドライバが設定したpoll関数を呼び出します．
ipモジュールを利用する場合，ゲストからはNICが見えず割り込みが発生しないため，明示的にこの関数を呼んでパケットを受信します．


```c
void
ip_main_task (void)
{
	/* Check IP address change of network interface. */
	tcpip_netif_ipaddr_check ();

	/* Handle packet reception. */
	tcpip_netif_poll ();

	/* Do timer related tasks. */
	sys_check_timeouts ();
}
```

前回の話を思い出すと，パケットを受信した際にコールバック関数が呼ばれます．ipあるいはippassモジュールの場合は以下が呼ばれます．

```c
static void
net_ip_phys_recv (void *handle, unsigned int num_packets, void **packets,
		  unsigned int *packet_sizes, void *param, long *premap)
{
	struct net_ip_data *p = param;

	if (p->pass)
		p->virt_func->send (p->virt_handle, num_packets, packets,
				    packet_sizes, true);
	if (p->input_ok)
		net_main_input_queue (p, packets, packet_sizes, num_packets);
}
```
ここで，`p->virt_func->send ()`はゲストOS側へパケットを渡す関数 (passモジュールと同様)です．
`net_main_input_queue()`で，BitVisorのlwIPスレッドが処理するために受信したパケットと，それを処理するための関数（`net_main_input_direct`をキューに追加します．

```c
static void
net_main_input_queue (struct net_ip_data *p, void **packets,
		      unsigned int *packet_sizes, unsigned int num_packets)
{
	struct net_ip_input_data *data;
	unsigned int i;

	for (i = 0; i < num_packets; i++) {
		/* Note: pbuf_alloc() must be called in the network
		 * thread, but this function is not. */
		/* FIXME: memcpy() is called twice for every input
		 * packet. Copying to pbuf directly is better. */
		data = alloc (sizeof *data);
		data->p = p;
		data->buf = alloc (packet_sizes[i]);
		data->len = packet_sizes[i];
		memcpy (data->buf, packets[i], packet_sizes[i]);
		net_main_task_add (net_main_input_direct, data);
	}
}

void
net_main_task_add (void (*func) (void *arg), void *arg)
{
	struct net_task *p;

	p = alloc (sizeof *p);
	p->func = func;
	p->arg = arg;
	spinlock_lock (&net_task_lock);
	LIST1_ADD (net_task_list, p);
	spinlock_unlock (&net_task_lock);
}
```

さて，ここまでが`net_thread()`のループ内でパケットを受信した際に実行される処理です．
この後，`net_task_call()`により実際に受信したパケットを処理していきます．

```c
static void
net_task_call (void)
{
	struct net_task *p;

	spinlock_lock (&net_task_lock);
	while ((p = LIST1_POP (net_task_list))) {
		spinlock_unlock (&net_task_lock);
		p->func (p->arg);
		free (p);
		spinlock_lock (&net_task_lock);
	}
	spinlock_unlock (&net_task_lock);
}
```

ここで，先ほど追加したデータなら`p->func`は`net_main_input_direct()`, `p->arg`は受信したパケットを含むデータ（`net_ip_input_data`構造体）です．

```c
static void
net_main_input_direct (void *arg)
{
	struct net_ip_input_data *data = arg;

	ip_main_input (data->p->input_arg, data->buf, data->len);
	free (data->buf);
	free (data);
}
```

で，結局`ip_main_input()`が呼ばれるわけですが，これは以下のようになっています．

```c
void
ip_main_input (void *arg, void *buf, unsigned int len)
{
	struct netif *netif = arg;
	struct eth_hdr *ethhdr;
	struct pbuf *p, *q;
	int offset;

	ethhdr = buf;
	switch (ntohs (ethhdr->type)) {
	case ETHTYPE_IP:
	case ETHTYPE_ARP:
		p = pbuf_alloc (PBUF_RAW, len, PBUF_POOL);
		offset = 0;
		LWIP_ASSERT ("pbuf_alloc", p);
		LWIP_ASSERT ("p->tot_len == len", p->tot_len == len);
		for (q = p; q; q = q->next) {
			LWIP_ASSERT ("q->payload", q->payload);
			LWIP_ASSERT ("offset + q->len <= len",
				     offset + q->len <= len);
			memcpy (q->payload, buf + offset, q->len);
			offset += q->len;
		}
		LWIP_ASSERT ("offset == len", offset == len);
		LWIP_ASSERT ("offset > 12 + 4", offset > 12 + 4);
		LWIP_ASSERT ("p->len > 12 + 4", p->len > 12 + 4);
		if (netif->input (p, netif) != ERR_OK)
			printf ("IP/ARP Input Error.\n");
		break;
	}
}
```

最終的に`netif->input()`で受信したパケットをTCP/IPスタックへ渡します．
ARPでパケットであればlwIPが応答を返します．

なお，BitVisorのスレッドは全てnon-preemptiveなため，ループ内で明示的にschedule()を呼んでいます．

## lwIPを利用したサーバ
ここまででlwIPのスレッドが動作し，ARP応答をlwIPが返してくれるようになりましたが，実際に何か有用なことをするにはサーバを立てる必要があります．
`ip/echo-server.c`にechoサーバの例があるので，この動作について簡単に説明します．

echoサーバを立てるために`echoctl`というメッセージハンドラが`ip/echoctl.c`で定義されており，`dbgsh`から`echoctl; server start 8888`とかやるとポート8888でechoサーバがlistneするようになります．
これはどうなっているのかというと，`echoctl.c`の中でサーバを立ち上げるために`tcpip_begin (echoctl_echo_server_start, a);`を呼んでいます．
この関数は`net_main_task_add()`で関数を登録するたけです．lwIPを実行するスレッドでサーバを立ち上げなければならないためこうしているわけです．

```c
void
tcpip_begin (tcpip_task_fn_t *func, void *arg)
{
	net_main_task_add (func, arg);
}
```

lwIPのスレッドの中で`echoctl_echo_server_start()`が実行されます．

```c
static void
echoctl_echo_server_start (void *arg)
{
	struct arg *a = arg;

	echo_server_init (a->port);
	free (a);
}

void
echo_server_init (int port)
{
  echo_pcb = tcp_new();
  if (echo_pcb != NULL)
  {
    err_t err;

    err = tcp_bind(echo_pcb, IP_ADDR_ANY, port);
    if (err == ERR_OK)
    {
      echo_pcb = tcp_listen(echo_pcb);
      tcp_accept(echo_pcb, echo_accept);
    }
    else 
    {
      /* abort? output diagnostic? */
    }
  }
  else
  {
    /* abort? output diagnostic? */
  }
}
```

最終的に呼ばれる`echo_server_init()`の中で，`tcp_bind()`, `tcp_listen()`によっていわゆるソケットにbindとlistenをし，さらに`tcp_accept()`で実際にacceptした際のコールバック関数を設定します．
`netif->input()`でlwIPのTCP/IPスタックにパケットを渡した際，条件にあうものがあればこの`tcp_accept()`で登録した関数を呼んでくれます．


もし外部からechoサーバへのアクセスがあれば`echo_accept()`が実行されます．

```c
static err_t
echo_accept(void *arg, struct tcp_pcb *newpcb, err_t err)
{
  err_t ret_err;
  struct echo_state *es;

  LWIP_UNUSED_ARG(arg);
  LWIP_UNUSED_ARG(err);

  /* commonly observed practive to call tcp_setprio(), why? */
  tcp_setprio(newpcb, TCP_PRIO_MIN);

  es = (struct echo_state *)mem_malloc(sizeof(struct echo_state));
  if (es != NULL)
  {
    es->state = ES_ACCEPTED;
    es->pcb = newpcb;
    es->retries = 0;
    es->p = NULL;
    /* pass newly allocated es to our callbacks */
    tcp_arg(newpcb, es);
    tcp_recv(newpcb, echo_recv);
    tcp_err(newpcb, echo_error);
    tcp_poll(newpcb, echo_poll, 0);
    ret_err = ERR_OK;
  }
  else
  {
    ret_err = ERR_MEM;
  }
  return ret_err;
```

この中の`tcp_recv()`でパケットを受信した際に呼ばれるコールバック関数を設定しています．

```c
static err_t
echo_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
  struct echo_state *es;
  err_t ret_err;

  LWIP_ASSERT("arg != NULL",arg != NULL);
  es = (struct echo_state *)arg;
  if (p == NULL)
  {
    /* remote host closed connection */
    es->state = ES_CLOSING;
    if(es->p == NULL)
    {
       /* we're done sending, close it */
       echo_close(tpcb, es);
    }
    else
    {
      /* we're not done yet */
      tcp_sent(tpcb, echo_sent);
      echo_send(tpcb, es);
    }
    ret_err = ERR_OK;
  }
  else if(err != ERR_OK)
  {
    /* cleanup, for unknown reason */
    if (p != NULL)
    {
      es->p = NULL;
      pbuf_free(p);
    }
    ret_err = err;
  }
  else if(es->state == ES_ACCEPTED)
  {
    /* first data chunk in p->payload */
    es->state = ES_RECEIVED;
    /* store reference to incoming pbuf (chain) */
    es->p = p;
    /* install send completion notifier */
    tcp_sent(tpcb, echo_sent);
    echo_send(tpcb, es);
    ret_err = ERR_OK;
  }
  else if (es->state == ES_RECEIVED)
  {
    /* read some more data */
    if(es->p == NULL)
    {
      es->p = p;
      tcp_sent(tpcb, echo_sent);
      echo_send(tpcb, es);
    }
    else
    {
      struct pbuf *ptr;

      /* chain pbufs to the end of what we recv'ed previously  */
      ptr = es->p;
      pbuf_chain(ptr,p);
    }
    ret_err = ERR_OK;
  }
  else if(es->state == ES_CLOSING)
  {
    /* odd case, remote side closing twice, trash data */
    tcp_recved(tpcb, p->tot_len);
    es->p = NULL;
    pbuf_free(p);
    ret_err = ERR_OK;
  }
  else
  {
    /* unknown es->state, trash data  */
    tcp_recved(tpcb, p->tot_len);
    es->p = NULL;
    pbuf_free(p);
    ret_err = ERR_OK;
  }
  return ret_err;
}
```


`echo_recv()`は少々長いですが，TCP/IPの状態によって場合分けされていて，`ES_RECEIVED`でデータを受信していたら`echo_send()`を使ってデータを送り返します．また`tcp_sent()`で送信後に呼ばれるコールバック関数を設定しています．コネクションがクローズされたらそれに応じた適切な処理をおこないます．

lwIPではこのようにコールバック関数を繋げて処理を書いていきます．

## まとめ
BitVisroのlwIP機能について簡単に説明しました．ipモジュールもしくはippassモジュールを使えばlwIP機能がセットアップされるので，後はechoサーバを参考にすれば比較的簡単にTCP/IPサーバが書けると思います．
一つ注意点としてはlwIPの機能はそれ専用のスレッドから呼ぶ必要があります．
サーバを立ち上げる際はechoサーバのように`tcpip_begin()`を使うか，ipモジュール開始時からサーバを立てたいのであれば`ip_main_init()`直後にサーバを立ち上げる処理を`net_thread()`の中に追加してしまってもよいと思います．
また現在複数のNICでipモジュールを使うことはできないようになっています(`net_new_ip_nic()`の時点で失敗する）．