#はじめに
Linux kernel v2.6.20 network stackを読んでみる - IP受信編 の続きです。
今度はUDP層の処理です。
#UDP層
UDPヘッダ構造体の定義は以下です。
https://elixir.bootlin.com/linux/v2.6.20/source/include/linux/udp.h#L22
struct udphdr {
__be16 source;
__be16 dest;
__be16 len;
__sum16 check;
};
UDP層のcallgraph
IP層の説明であったように、受信時のUDP層のエントリーポイントは udp_rcv() となります。
UDP自体は特有の処理があまりないシンプルなものなので、ユーザが待ち受けている socket を見つけてデータを紐づけ、ユーザ側を起動させることが仕事になります。
##udp_rcv()
//#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
ここでは受信したUDPパケットを待ち受けているsocketのインスタンスを探すことが関数の目的となります。
/* 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のインスタンスを入手したので処理が進みます。
/* 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
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()の中で
sk->sk_data_ready = sock_def_readable;
とセットされています。
##sock_def_readable()
https://elixir.bootlin.com/linux/v2.6.20/source/net/core/sock.c#L1407
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
RFC3828 The Lightweight User Datagram Protocol (UDP-Lite)
callback function in socket as sk_data_ready()
#履歴
2018/08/19 初版