Linux kernel v2.6.20 network stackを読んでみる - UDP受信編

#はじめに

[Linux kernel v2.6.20 network stackを読んでみる - IP受信編](https://qiita.com/IK_PE/items/4d868e8940885f46e0da) の続きです。
今度はUDP層の処理です。

#UDP層

UDPヘッダのフォーマット
<img width="674" alt="39CDD778-AE4A-4276-87DC-8B5FEDBEF6A0.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/272771/fb1a1f2b-fb4c-a7fc-d645-3fcb98ca576b.jpeg">

UDPヘッダ構造体の定義は以下です。
https://elixir.bootlin.com/linux/v2.6.20/source/include/linux/udp.h#L22

```c:
struct udphdr {
	__be16	source;
	__be16	dest;
	__be16	len;
	__sum16	check;
};
```

UDP層のcallgraph

<img width="1019" alt="D748F45C-CF22-4ADC-BBD7-32FF2628BA5D.jpeg" src="https://qiita-image-store.s3.amazonaws.com/0/272771/742e02da-c789-c43e-1f30-4c3272a5ce2f.jpeg">

IP層の説明であったように、受信時のUDP層のエントリーポイントは　udp_rcv()　となります。
UDP自体は特有の処理があまりないシンプルなものなので、ユーザが待ち受けている socket を見つけてデータを紐づけ、ユーザ側を起動させることが仕事になります。

##udp_rcv()

https://elixir.bootlin.com/linux/v2.6.20/source/net/ipv4/udp.c#L1290

```c:
//#define UDP_HTABLE_SIZE		128
struct hlist_head udp_hash[UDP_HTABLE_SIZE];

__inline__ int udp_rcv(struct sk_buff *skb)
{
	return __udp4_lib_rcv(skb, udp_hash, 0);
}

/*
 *	All we need to do is get the socket, and then do a checksum. 
 */
int __udp4_lib_rcv(struct sk_buff *skb, struct hlist_head udptable[], int is_udplite)
{
  	struct sock *sk;
  	struct udphdr *uh = skb->h.uh;
	unsigned short ulen;
	struct rtable *rt = (struct rtable*)skb->dst;
	__be32 saddr = skb->nh.iph->saddr;
	__be32 daddr = skb->nh.iph->daddr;

	// UDPヘッダに書かれているlengthと実際のパケットデータ大きさに矛盾がないか調べます。
	(省略)

	// 受信したパケットと対応するsocketのインスタンスを探す.socketはユーザとやり取りするインターフェース.
	sk = __udp4_lib_lookup(saddr, uh->source, daddr, uh->dest, skb->dev->ifindex, udptable);

	if (sk != NULL) {
		// socketとsk_buffを紐づけ、ユーザがsocketを通じデータにアクセスできるようにします。この中でユーザコンテキストの起動も行います.
		int ret = udp_queue_rcv_skb(sk, skb);
		sock_put(sk);

		/* a return value > 0 means to resubmit the input, but
		 * it wants the return to be -protocol, or 0
		 */
		if (ret > 0)
			return -ret;
		return 0;
	}

	// ユーザがlistenしていないポートへの通信なので本パケットを破棄します。
	kfree_skb(skb);
	return(0);
}
```

##__udp4_lib_lookup()
https://elixir.bootlin.com/linux/v2.6.20/source/net/ipv4/udp.c#L237

kernel内部で使用している、受信したパケットを管理する sk_buff と、ユーザが使用するsocketはデータ構造が異なります。

socketのデータ型は以下を参照のこと。
[struct sock](https://elixir.bootlin.com/linux/v2.6.20/source/include/net/sock.h#L183)

ここでは受信したUDPパケットを待ち受けているsocketのインスタンスを探すことが関数の目的となります。

```c:net/ipv4/udp.c
/* UDP is nearly always wildcards out the wazoo, it makes no sense to try
 * harder than this. -DaveM
 */
static struct sock *__udp4_lib_lookup(__be32 saddr, __be16 sport,
				      __be32 daddr, __be16 dport,
				      int dif, struct hlist_head udptable[])
{
	struct sock *sk, *result = NULL;
	struct hlist_node *node;
	unsigned short hnum = ntohs(dport); // Destination Port
	int badness = -1;

	read_lock(&udp_hash_lock);
	// Destination Portの下位7bitが等しいものを探す.
	sk_for_each(sk, node, &udptable[hnum & (UDP_HTABLE_SIZE - 1)]) {
		struct inet_sock *inet = inet_sk(sk);

		// 探してきたsocketはまだ今受信しているパケットと同じ種類のものか不明なので、一致するものを探す。
		// 通信の起点はクライアント側なので、socketのsourceとdestinationと受信したパケットのそれは逆に対応している。
		// つまりsocketのsourceとパケットのdestinationが対応するということに注意。
		// 探すものは Source/Destinationのアドレスとポート。
		if (inet->num == hnum && !ipv6_only_sock(sk)) {
			int score = (sk->sk_family == PF_INET ? 1 : 0);
			if (inet->rcv_saddr) {
				if (inet->rcv_saddr != daddr)
					continue;
				score+=2;
			}
			if (inet->daddr) {
				if (inet->daddr != saddr)
					continue;
				score+=2;
			}
			if (inet->dport) {
				if (inet->dport != sport)
					continue;
				score+=2;
			}
			if (sk->sk_bound_dev_if) {
				if (sk->sk_bound_dev_if != dif)
					continue;
				score+=2;
			}
			if(score == 9) {
				// 対応するsocketを見つけたので、そのインスタンスを返す.
				result = sk;
				break;
			} else if(score > badness) {
				result = sk;
				badness = score;
			}
		}
	}
	if (result)
		sock_hold(result);
	read_unlock(&udp_hash_lock);
	return result;
}
```

##udp_queue_rcv_skb
https://elixir.bootlin.com/linux/v2.6.20/source/net/ipv4/udp.c#L1022

対応するsocketのインスタンスを入手したので処理が進みます。

```c:net/ipv4/udp.c
/* returns:
 *  -1: error
 *   0: success
 *  >0: "udp encap" protocol resubmission
 *
 * Note that in the success and error cases, the skb is assumed to
 * have either been requeued or freed.
 */
int udp_queue_rcv_skb(struct sock * sk, struct sk_buff *skb)
{
	struct udp_sock *up = udp_sk(sk);
	int rc;

	// socketがカプセル化されている場合
	// (参考) https://wa3.i-3-i.info/word12035.html
	if (up->encap_type) {
		省略
	}

	/*
	 * 	UDP-Lite specific tests, ignored on UDP sockets
	 */
	// https://ja.wikipedia.org/wiki/UDP-Lite
	// 主にインターネットで使用されるプロトコルの1つで音声や動画の伝送を目的にしている。
	// 従来のUDPパケットでは1ビットでもデータが損傷すれば破棄されるのに対して、UDP-Liteはそのまま伝送される。
	if ((up->pcflag & UDPLITE_RECV_CC)  &&  UDP_SKB_CB(skb)->partial_cov) {
		省略
	}

	// checksumの確認
	if (sk->sk_filter && skb->ip_summed != CHECKSUM_UNNECESSARY) {
		省略
	}

	// いよいよsocketとsk_buffを結びつけます.
	if ((rc = sock_queue_rcv_skb(sk,skb)) < 0) {
		/* Note that an ENOMEM error is charged twice */
		goto drop;
	}

	return 0;
}
```

##sock_queue_rcv_skb
https://elixir.bootlin.com/linux/v2.6.20/source/net/core/sock.c#L237

```c:
int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
	int err = 0;
	int skb_len;

	skb->dev = NULL;
	skb_set_owner_r(skb, sk); // skb->sk = sk; とsk_buffにsocketを結びつける.

	/* Cache the SKB length before we tack it onto the receive
	 * queue.  Once it is added it no longer belongs to us and
	 * may be freed by other threads of control pulling packets
	 * from the queue.
	 */
	skb_len = skb->len;

	// sockのインスタンスが管理するsk_buffのlistに本パケットのsk_buffのインスタンスをつなぐ。
	skb_queue_tail(&sk->sk_receive_queue, skb);

	if (!sock_flag(sk, SOCK_DEAD)) {
		// socketの準備が整ったのでユーザ側を起動する.
		sk->sk_data_ready(sk, skb_len);
	}
out:
	return err;
}
```

最後に
`sk->sk_data_ready()`
ですが、関数ポインタ自体は [sock_init_data()](https://elixir.bootlin.com/linux/v2.6.20/source/net/core/sock.c#L1464)の中で
`sk->sk_data_ready	=	sock_def_readable;`
 とセットされています。


##sock_def_readable()
https://elixir.bootlin.com/linux/v2.6.20/source/net/core/sock.c#L1407

```c:net/core/sock.c
static void sock_def_readable(struct sock *sk, int len)
{
	read_lock(&sk->sk_callback_lock);
	// socketが起こされ、ユーザに制御が行く.
	if (sk->sk_sleep && waitqueue_active(sk->sk_sleep))
		wake_up_interruptible(sk->sk_sleep);
	sk_wake_async(sk,1,POLL_IN);
	read_unlock(&sk->sk_callback_lock);
}
```

#感想
UDPは簡単だろうと思いましたが、複雑ですね。。

次にTCPを読み、IPのデフラグに戻り、今回調査のきっかけとなった脆弱性の解説をしていきたいですが、なかなか骨が折れますね。。linux kernelのnetwork stackは、、

#参考

[【書籍】Professional Linux Kernel Architecture](https://www.amazon.co.jp/dp/B0711DCCBJ/)

[RFC3828 The Lightweight User Datagram Protocol (UDP-Lite)](https://tools.ietf.org/html/rfc3828)

[callback function in socket as sk_data_ready()](https://stackoverflow.com/questions/14040328/callback-function-in-socket-as-sk-data-ready)

#履歴
2018/08/19 初版