本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
前回を読んでいない方はこちらからどうぞ.
Linuxカーネルの記事一覧はこちらからどうぞ.
LinuxカーネルはC言語で書かれています.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
今回のテーマはソケット通信(TCP/UDP/IP)です.
本記事では,以下を習得できます.
- Linuxカーネルのネットワークアーキテクチャ
- パケットフィルタやファイアウォールを用いたIPパケット管理の実践的な技術
- カーネル空間でのソケットの使用方法
- TCPアクセラレーション(TCP高速化機能)
- UDPアクセラレーション(UDP高速化機能)
本記事は,以下のユーザ空間でソケット通信によるネットワークプログラミングを理解していることを前提とします.
目次
ソケット通信
インターネットの発展により,ネットワークアプリケーションは飛躍的に増加しています.
結果として,OSのネットワークサブシステムに求められる速度や生産性も向上しています.
ネットワーキングサブシステムは,OSカーネルの必須コンポーネントではありません(Linuxカーネルはネットワーキングのコンフィグなしでコンパイルできます).
しかし,クラウドやIoTの普及に伴い,コンピュータシステム(あるいは組込みシステム)における接続性の必要性から,ネットワークに対応しているOSの搭載はほぼ必須です.
最近のOSは,TCP/IPスタックを使用しています.
そのカーネルはトランスポート層までのプロトコルを実装し,アプリケーション層のプロトコルは通常ユーザ空間で実装されます(HTTP,FTP,SSH等).
ユーザ空間では,ネットワーク通信を抽象化したものがソケットです.
ソケットは通信チャネルを抽象化したもので,カーネルベースのTCP/IPスタックのインタラクションインタフェースです.
IPソケットは,IPアドレス,使用するトランスポート層プロトコル(TCP,UDP等),ポートに関連付けられます.
ソケットを使用する一般的な関数呼び出しは,作成(socket関数),初期化(bind関数),接続(connect関数),接続待ち(listen/accept関数),ソケットの終了(close関数)です.
ネットワーク通信は,TCPソケットの場合はread/write関数またはrecv/send関数,UDPソケットの場合はrecvfrom/sendto関数の呼び出しで実現されます.
送受信の操作はアプリケーションに対して透過的であり,カプセル化やネットワーク上での送信はカーネルの判断に委ねられます.
ただし,rawソケットを用いたユーザ空間でのTCP/IPスタックの実装(ソケット作成時のPF_PACKETオプション)や,カーネルでのアプリケーション層プロトコルの実装(TUX Webサーバ)は可能です.
ソケットを使ったユーザ空間プログラミングの詳細については,「Beej's Guide to Network Programming Using Internet Sockets」を参照してください.
Linuxカーネルのソケット通信
Linuxカーネルは,ネットワークパケットを扱うための3つの基本構造体,struct socket,struct sock,struct sk_buffを提供します.
struct socket構造体とstruct sock構造体は,ソケットを抽象化したものです.
struct socket構造体はユーザ空間に近い抽象化であり,例えばBSDのソケットはネットワークアプリケーションのプログラミングに使用されます.
struct sock構造体(Linux用語ではINET socket)は,ソケットをネットワークで表現したものです.
これらの2つの構造体は関連しており,struct socket構造体はINETソケットフィールドを持ち,struct sock構造体はそれを保持するBSDソケットを持っています.
struct sk_buff構造体は,ネットワークパケットとそのステータスを表現するもので,ユーザ空間またはネットワークインタフェースからカーネルパケットを受信したときに作成されます.
ソケット通信を管理する構造体
ソケット通信を管理する構造体を紹介します.
socket構造体
linux/include/linux/net.hにあるsocket構造体は一般的なBSDソケットのことです.
重要なメンバ変数は以下になります.
- ops:プロトコル固有のソケット操作
- sk:内部ネットワークプロトコルに依存しないソケット表現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/** * struct socket - general BSD socket * @state: socket state (%SS_CONNECTED, etc) * @type: socket type (%SOCK_STREAM, etc) * @flags: socket flags (%SOCK_NOSPACE, etc) * @ops: protocol specific socket operations * @file: File back pointer for gc * @sk: internal networking protocol agnostic socket representation * @wq: wait queue for several uses */ struct socket { socket_state state; short type; unsigned long flags; struct file *file; struct sock *sk; const struct proto_ops *ops; struct socket_wq wq; }; |
proto_ops構造体
linux/include/linux/net.hにあるproto_ops構造体では,特定のプロトコルを実装するための関数ポインタを管理します.
具体的には,ソケット構造体を介して汎用関数から呼び出される(sock_release,sock_sendmsg等)の関数ポインタです.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
struct proto_ops { int family; struct module *owner; int (*release) (struct socket *sock); int (*bind) (struct socket *sock, struct sockaddr *myaddr, int sockaddr_len); int (*connect) (struct socket *sock, struct sockaddr *vaddr, int sockaddr_len, int flags); int (*socketpair)(struct socket *sock1, struct socket *sock2); int (*accept) (struct socket *sock, struct socket *newsock, int flags, bool kern); int (*getname) (struct socket *sock, struct sockaddr *addr, int peer); __poll_t (*poll) (struct file *file, struct socket *sock, struct poll_table_struct *wait); int (*ioctl) (struct socket *sock, unsigned int cmd, unsigned long arg); #ifdef CONFIG_COMPAT int (*compat_ioctl) (struct socket *sock, unsigned int cmd, unsigned long arg); #endif int (*gettstamp) (struct socket *sock, void __user *userstamp, bool timeval, bool time32); int (*listen) (struct socket *sock, int len); int (*shutdown) (struct socket *sock, int flags); int (*setsockopt)(struct socket *sock, int level, int optname, sockptr_t optval, unsigned int optlen); int (*getsockopt)(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen); void (*show_fdinfo)(struct seq_file *m, struct socket *sock); int (*sendmsg) (struct socket *sock, struct msghdr *m, size_t total_len); /* Notes for implementing recvmsg: * =============================== * msg->msg_namelen should get updated by the recvmsg handlers * iff msg_name != NULL. It is by default 0 to prevent * returning uninitialized memory to user space. The recvfrom * handlers can assume that msg.msg_name is either NULL or has * a minimum size of sizeof(struct sockaddr_storage). */ int (*recvmsg) (struct socket *sock, struct msghdr *m, size_t total_len, int flags); int (*mmap) (struct file *file, struct socket *sock, struct vm_area_struct * vma); ssize_t (*sendpage) (struct socket *sock, struct page *page, int offset, size_t size, int flags); ssize_t (*splice_read)(struct socket *sock, loff_t *ppos, struct pipe_inode_info *pipe, size_t len, unsigned int flags); int (*set_peek_off)(struct sock *sk, int val); int (*peek_len)(struct socket *sock); /* The following functions are called internally by kernel with * sock lock already held. */ int (*read_sock)(struct sock *sk, read_descriptor_t *desc, sk_read_actor_t recv_actor); int (*sendpage_locked)(struct sock *sk, struct page *page, int offset, size_t size, int flags); int (*sendmsg_locked)(struct sock *sk, struct msghdr *msg, size_t size); int (*set_rcvlowat)(struct sock *sk, int val); }; |
sock構造体
linux/include/net/sock.hのsock構造体は,ソケットのネットワーク層(INETソケット)を表現します.
sock構造体は,ユーザ空間のソケットと暗黙のうちにsocket 構造体と関連付けられます.
sock構造体は,接続の状態に関する情報を格納するために使用されます.
構造体のメンバ変数と関連する操作は,通常,sk_という文字列で始まります.
sock構造体の重要なメンバ変数は以下になります.
- sk_protocol:ソケットが使用するプロトコルのタイプ
- sk_type:ソケットのタイプ(SOCK_STREAM,SOCK_DGRAM等)
- sk_socket:socket構造体を保持するBSDソケット
- sk_send_head:送信用のsk_buff構造体のリスト
※末尾の関数ポインタは,様々な状況に対応するためのコールバックです.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
/** * struct sock - network layer representation of sockets * @__sk_common: shared layout with inet_timewait_sock * @sk_shutdown: mask of %SEND_SHUTDOWN and/or %RCV_SHUTDOWN * @sk_userlocks: %SO_SNDBUF and %SO_RCVBUF settings * @sk_lock: synchronizer * @sk_kern_sock: True if sock is using kernel lock classes * @sk_rcvbuf: size of receive buffer in bytes * @sk_wq: sock wait queue and async head * @sk_rx_dst: receive input route used by early demux * @sk_dst_cache: destination cache * @sk_dst_pending_confirm: need to confirm neighbour * @sk_policy: flow policy * @sk_rx_skb_cache: cache copy of recently accessed RX skb * @sk_receive_queue: incoming packets * @sk_wmem_alloc: transmit queue bytes committed * @sk_tsq_flags: TCP Small Queues flags * @sk_write_queue: Packet sending queue * @sk_omem_alloc: "o" is "option" or "other" * @sk_wmem_queued: persistent queue size * @sk_forward_alloc: space allocated forward * @sk_napi_id: id of the last napi context to receive data for sk * @sk_ll_usec: usecs to busypoll when there is no data * @sk_allocation: allocation mode * @sk_pacing_rate: Pacing rate (if supported by transport/packet scheduler) * @sk_pacing_status: Pacing status (requested, handled by sch_fq) * @sk_max_pacing_rate: Maximum pacing rate (%SO_MAX_PACING_RATE) * @sk_sndbuf: size of send buffer in bytes * @__sk_flags_offset: empty field used to determine location of bitfield * @sk_padding: unused element for alignment * @sk_no_check_tx: %SO_NO_CHECK setting, set checksum in TX packets * @sk_no_check_rx: allow zero checksum in RX packets * @sk_route_caps: route capabilities (e.g. %NETIF_F_TSO) * @sk_route_nocaps: forbidden route capabilities (e.g NETIF_F_GSO_MASK) * @sk_route_forced_caps: static, forced route capabilities * (set in tcp_init_sock()) * @sk_gso_type: GSO type (e.g. %SKB_GSO_TCPV4) * @sk_gso_max_size: Maximum GSO segment size to build * @sk_gso_max_segs: Maximum number of GSO segments * @sk_pacing_shift: scaling factor for TCP Small Queues * @sk_lingertime: %SO_LINGER l_linger setting * @sk_backlog: always used with the per-socket spinlock held * @sk_callback_lock: used with the callbacks in the end of this struct * @sk_error_queue: rarely used * @sk_prot_creator: sk_prot of original sock creator (see ipv6_setsockopt, * IPV6_ADDRFORM for instance) * @sk_err: last error * @sk_err_soft: errors that don't cause failure but are the cause of a * persistent failure not just 'timed out' * @sk_drops: raw/udp drops counter * @sk_ack_backlog: current listen backlog * @sk_max_ack_backlog: listen backlog set in listen() * @sk_uid: user id of owner * @sk_prefer_busy_poll: prefer busypolling over softirq processing * @sk_busy_poll_budget: napi processing budget when busypolling * @sk_priority: %SO_PRIORITY setting * @sk_type: socket type (%SOCK_STREAM, etc) * @sk_protocol: which protocol this socket belongs in this network family * @sk_peer_lock: lock protecting @sk_peer_pid and @sk_peer_cred * @sk_peer_pid: &struct pid for this socket's peer * @sk_peer_cred: %SO_PEERCRED setting * @sk_rcvlowat: %SO_RCVLOWAT setting * @sk_rcvtimeo: %SO_RCVTIMEO setting * @sk_sndtimeo: %SO_SNDTIMEO setting * @sk_txhash: computed flow hash for use on transmit * @sk_filter: socket filtering instructions * @sk_timer: sock cleanup timer * @sk_stamp: time stamp of last packet received * @sk_stamp_seq: lock for accessing sk_stamp on 32 bit architectures only * @sk_tsflags: SO_TIMESTAMPING flags * @sk_bind_phc: SO_TIMESTAMPING bind PHC index of PTP virtual clock * for timestamping * @sk_tskey: counter to disambiguate concurrent tstamp requests * @sk_zckey: counter to order MSG_ZEROCOPY notifications * @sk_socket: Identd and reporting IO signals * @sk_user_data: RPC layer private data * @sk_frag: cached page frag * @sk_peek_off: current peek_offset value * @sk_send_head: front of stuff to transmit * @tcp_rtx_queue: TCP re-transmit queue [union with @sk_send_head] * @sk_tx_skb_cache: cache copy of recently accessed TX skb * @sk_security: used by security modules * @sk_mark: generic packet mark * @sk_cgrp_data: cgroup data for this cgroup * @sk_memcg: this socket's memory cgroup association * @sk_write_pending: a write to stream socket waits to start * @sk_state_change: callback to indicate change in the state of the sock * @sk_data_ready: callback to indicate there is data to be processed * @sk_write_space: callback to indicate there is bf sending space available * @sk_error_report: callback to indicate errors (e.g. %MSG_ERRQUEUE) * @sk_backlog_rcv: callback to process the backlog * @sk_validate_xmit_skb: ptr to an optional validate function * @sk_destruct: called at sock freeing time, i.e. when all refcnt == 0 * @sk_reuseport_cb: reuseport group container * @sk_bpf_storage: ptr to cache and control for bpf_sk_storage * @sk_rcu: used during RCU grace period * @sk_clockid: clockid used by time-based scheduling (SO_TXTIME) * @sk_txtime_deadline_mode: set deadline mode for SO_TXTIME * @sk_txtime_report_errors: set report errors mode for SO_TXTIME * @sk_txtime_unused: unused txtime flags */ struct sock { /* * Now struct inet_timewait_sock also uses sock_common, so please just * don't add nothing before this first member (__sk_common) --acme */ struct sock_common __sk_common; #define sk_node __sk_common.skc_node #define sk_nulls_node __sk_common.skc_nulls_node #define sk_refcnt __sk_common.skc_refcnt #define sk_tx_queue_mapping __sk_common.skc_tx_queue_mapping #ifdef CONFIG_SOCK_RX_QUEUE_MAPPING #define sk_rx_queue_mapping __sk_common.skc_rx_queue_mapping #endif #define sk_dontcopy_begin __sk_common.skc_dontcopy_begin #define sk_dontcopy_end __sk_common.skc_dontcopy_end #define sk_hash __sk_common.skc_hash #define sk_portpair __sk_common.skc_portpair #define sk_num __sk_common.skc_num #define sk_dport __sk_common.skc_dport #define sk_addrpair __sk_common.skc_addrpair #define sk_daddr __sk_common.skc_daddr #define sk_rcv_saddr __sk_common.skc_rcv_saddr #define sk_family __sk_common.skc_family #define sk_state __sk_common.skc_state #define sk_reuse __sk_common.skc_reuse #define sk_reuseport __sk_common.skc_reuseport #define sk_ipv6only __sk_common.skc_ipv6only #define sk_net_refcnt __sk_common.skc_net_refcnt #define sk_bound_dev_if __sk_common.skc_bound_dev_if #define sk_bind_node __sk_common.skc_bind_node #define sk_prot __sk_common.skc_prot #define sk_net __sk_common.skc_net #define sk_v6_daddr __sk_common.skc_v6_daddr #define sk_v6_rcv_saddr __sk_common.skc_v6_rcv_saddr #define sk_cookie __sk_common.skc_cookie #define sk_incoming_cpu __sk_common.skc_incoming_cpu #define sk_flags __sk_common.skc_flags #define sk_rxhash __sk_common.skc_rxhash socket_lock_t sk_lock; atomic_t sk_drops; int sk_rcvlowat; struct sk_buff_head sk_error_queue; struct sk_buff *sk_rx_skb_cache; struct sk_buff_head sk_receive_queue; /* * The backlog queue is special, it is always used with * the per-socket spinlock held and requires low latency * access. Therefore we special case it's implementation. * Note : rmem_alloc is in this structure to fill a hole * on 64bit arches, not because its logically part of * backlog. */ struct { atomic_t rmem_alloc; int len; struct sk_buff *head; struct sk_buff *tail; } sk_backlog; #define sk_rmem_alloc sk_backlog.rmem_alloc int sk_forward_alloc; #ifdef CONFIG_NET_RX_BUSY_POLL unsigned int sk_ll_usec; /* ===== mostly read cache line ===== */ unsigned int sk_napi_id; #endif int sk_rcvbuf; struct sk_filter __rcu *sk_filter; union { struct socket_wq __rcu *sk_wq; /* private: */ struct socket_wq *sk_wq_raw; /* public: */ }; #ifdef CONFIG_XFRM struct xfrm_policy __rcu *sk_policy[2]; #endif struct dst_entry *sk_rx_dst; struct dst_entry __rcu *sk_dst_cache; atomic_t sk_omem_alloc; int sk_sndbuf; /* ===== cache line for TX ===== */ int sk_wmem_queued; refcount_t sk_wmem_alloc; unsigned long sk_tsq_flags; union { struct sk_buff *sk_send_head; struct rb_root tcp_rtx_queue; }; struct sk_buff *sk_tx_skb_cache; struct sk_buff_head sk_write_queue; __s32 sk_peek_off; int sk_write_pending; __u32 sk_dst_pending_confirm; u32 sk_pacing_status; /* see enum sk_pacing */ long sk_sndtimeo; struct timer_list sk_timer; __u32 sk_priority; __u32 sk_mark; unsigned long sk_pacing_rate; /* bytes per second */ unsigned long sk_max_pacing_rate; struct page_frag sk_frag; netdev_features_t sk_route_caps; netdev_features_t sk_route_nocaps; netdev_features_t sk_route_forced_caps; int sk_gso_type; unsigned int sk_gso_max_size; gfp_t sk_allocation; __u32 sk_txhash; /* * Because of non atomicity rules, all * changes are protected by socket lock. */ u8 sk_padding : 1, sk_kern_sock : 1, sk_no_check_tx : 1, sk_no_check_rx : 1, sk_userlocks : 4; u8 sk_pacing_shift; u16 sk_type; u16 sk_protocol; u16 sk_gso_max_segs; unsigned long sk_lingertime; struct proto *sk_prot_creator; rwlock_t sk_callback_lock; int sk_err, sk_err_soft; u32 sk_ack_backlog; u32 sk_max_ack_backlog; kuid_t sk_uid; #ifdef CONFIG_NET_RX_BUSY_POLL u8 sk_prefer_busy_poll; u16 sk_busy_poll_budget; #endif spinlock_t sk_peer_lock; struct pid *sk_peer_pid; const struct cred *sk_peer_cred; long sk_rcvtimeo; ktime_t sk_stamp; #if BITS_PER_LONG==32 seqlock_t sk_stamp_seq; #endif u16 sk_tsflags; int sk_bind_phc; u8 sk_shutdown; u32 sk_tskey; atomic_t sk_zckey; u8 sk_clockid; u8 sk_txtime_deadline_mode : 1, sk_txtime_report_errors : 1, sk_txtime_unused : 6; struct socket *sk_socket; void *sk_user_data; #ifdef CONFIG_SECURITY void *sk_security; #endif struct sock_cgroup_data sk_cgrp_data; struct mem_cgroup *sk_memcg; void (*sk_state_change)(struct sock *sk); void (*sk_data_ready)(struct sock *sk); void (*sk_write_space)(struct sock *sk); void (*sk_error_report)(struct sock *sk); int (*sk_backlog_rcv)(struct sock *sk, struct sk_buff *skb); #ifdef CONFIG_SOCK_VALIDATE_XMIT struct sk_buff* (*sk_validate_xmit_skb)(struct sock *sk, struct net_device *dev, struct sk_buff *skb); #endif void (*sk_destruct)(struct sock *sk); struct sock_reuseport __rcu *sk_reuseport_cb; #ifdef CONFIG_BPF_SYSCALL struct bpf_local_storage __rcu *sk_bpf_storage; #endif struct rcu_head sk_rcu; }; |
INETソケットを作成するinet_create関数でsock構造体を利用しています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
/* * Create an inet socket. */ static int inet_create(struct net *net, struct socket *sock, int protocol, int kern) { struct sock *sk; struct inet_protosw *answer; struct inet_sock *inet; struct proto *answer_prot; unsigned char answer_flags; int try_loading_module = 0; int err; if (protocol < 0 || protocol >= IPPROTO_MAX) return -EINVAL; sock->state = SS_UNCONNECTED; /* Look for the requested type/protocol pair. */ lookup_protocol: err = -ESOCKTNOSUPPORT; rcu_read_lock(); list_for_each_entry_rcu(answer, &inetsw[sock->type], list) { err = 0; /* Check the non-wild match. */ if (protocol == answer->protocol) { if (protocol != IPPROTO_IP) break; } else { /* Check for the two wild cases. */ if (IPPROTO_IP == protocol) { protocol = answer->protocol; break; } if (IPPROTO_IP == answer->protocol) break; } err = -EPROTONOSUPPORT; } if (unlikely(err)) { if (try_loading_module < 2) { rcu_read_unlock(); /* * Be more specific, e.g. net-pf-2-proto-132-type-1 * (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM) */ if (++try_loading_module == 1) request_module("net-pf-%d-proto-%d-type-%d", PF_INET, protocol, sock->type); /* * Fall back to generic, e.g. net-pf-2-proto-132 * (net-pf-PF_INET-proto-IPPROTO_SCTP) */ else request_module("net-pf-%d-proto-%d", PF_INET, protocol); goto lookup_protocol; } else goto out_rcu_unlock; } err = -EPERM; if (sock->type == SOCK_RAW && !kern && !ns_capable(net->user_ns, CAP_NET_RAW)) goto out_rcu_unlock; sock->ops = answer->ops; answer_prot = answer->prot; answer_flags = answer->flags; rcu_read_unlock(); WARN_ON(!answer_prot->slab); err = -ENOMEM; sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern); if (!sk) goto out; err = 0; if (INET_PROTOSW_REUSE & answer_flags) sk->sk_reuse = SK_CAN_REUSE; inet = inet_sk(sk); inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0; inet->nodefrag = 0; if (SOCK_RAW == sock->type) { inet->inet_num = protocol; if (IPPROTO_RAW == protocol) inet->hdrincl = 1; } if (net->ipv4.sysctl_ip_no_pmtu_disc) inet->pmtudisc = IP_PMTUDISC_DONT; else inet->pmtudisc = IP_PMTUDISC_WANT; inet->inet_id = 0; sock_init_data(sock, sk); sk->sk_destruct = inet_sock_destruct; sk->sk_protocol = protocol; sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv; inet->uc_ttl = -1; inet->mc_loop = 1; inet->mc_ttl = 1; inet->mc_all = 1; inet->mc_index = 0; inet->mc_list = NULL; inet->rcv_tos = 0; sk_refcnt_debug_inc(sk); if (inet->inet_num) { /* It assumes that any protocol which allows * the user to assign a number at socket * creation time automatically * shares. */ inet->inet_sport = htons(inet->inet_num); /* Add to protocol hash chains. */ err = sk->sk_prot->hash(sk); if (err) { sk_common_release(sk); goto out; } } if (sk->sk_prot->init) { err = sk->sk_prot->init(sk); if (err) { sk_common_release(sk); goto out; } } if (!kern) { err = BPF_CGROUP_RUN_PROG_INET_SOCK(sk); if (err) { sk_common_release(sk); goto out; } } out: return err; out_rcu_unlock: rcu_read_unlock(); goto out; } |
sk_buff構造体
linux/include/linux/skbuff.hのsk_buff構造体は,ソケットのバッファ(ネットワークパケット)を管理する構造体です.
構造体のメンバ変数には,ヘッダとパケットの内容,使用されたプロトコル,使用されたネットワークデバイス,他の構造体 sk_buffへのポインタに関する情報が含まれます.
重要なメンバ変数は以下になります.
- next,prev:バッファリスト中の次と前の要素へのポインタ
- dev:バッファを送信または受信するデバイス
- sk:バッファに関連付けられたソケット
- destructor:バッファの割り当てを解除するコールバック
- transport_header,network_header,mac_header:パケットの先頭とパケット内の様々なヘッダの先頭の間のオフセットである.これらは,パケットが通過する様々な処理層によって内部的に維持されています.ヘッダへのポインタを得るには,tcp_hdr/udp_hdr/ip_hdr等の関数のいずれかを使用します.原則として,各プロトコルは受信したパケット内のそのプロトコルのヘッダへの参照を取得する関数を提供します.network_headerメンバ変数はパケットがネットワーク層に到達するまで設定されず,transport_headerメンバ変数はパケットがトランスポート層に到達するまで設定されないことに注意してください.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 |
/** * struct sk_buff - socket buffer * @next: Next buffer in list * @prev: Previous buffer in list * @tstamp: Time we arrived/left * @skb_mstamp_ns: (aka @tstamp) earliest departure time; start point * for retransmit timer * @rbnode: RB tree node, alternative to next/prev for netem/tcp * @list: queue head * @sk: Socket we are owned by * @ip_defrag_offset: (aka @sk) alternate use of @sk, used in * fragmentation management * @dev: Device we arrived on/are leaving by * @dev_scratch: (aka @dev) alternate use of @dev when @dev would be %NULL * @cb: Control buffer. Free for use by every layer. Put private vars here * @_skb_refdst: destination entry (with norefcount bit) * @sp: the security path, used for xfrm * @len: Length of actual data * @data_len: Data length * @mac_len: Length of link layer header * @hdr_len: writable header length of cloned skb * @csum: Checksum (must include start/offset pair) * @csum_start: Offset from skb->head where checksumming should start * @csum_offset: Offset from csum_start where checksum should be stored * @priority: Packet queueing priority * @ignore_df: allow local fragmentation * @cloned: Head may be cloned (check refcnt to be sure) * @ip_summed: Driver fed us an IP checksum * @nohdr: Payload reference only, must not modify header * @pkt_type: Packet class * @fclone: skbuff clone status * @ipvs_property: skbuff is owned by ipvs * @inner_protocol_type: whether the inner protocol is * ENCAP_TYPE_ETHER or ENCAP_TYPE_IPPROTO * @remcsum_offload: remote checksum offload is enabled * @offload_fwd_mark: Packet was L2-forwarded in hardware * @offload_l3_fwd_mark: Packet was L3-forwarded in hardware * @tc_skip_classify: do not classify packet. set by IFB device * @tc_at_ingress: used within tc_classify to distinguish in/egress * @redirected: packet was redirected by packet classifier * @from_ingress: packet was redirected from the ingress path * @peeked: this packet has been seen already, so stats have been * done for it, don't do them again * @nf_trace: netfilter packet trace flag * @protocol: Packet protocol from driver * @destructor: Destruct function * @tcp_tsorted_anchor: list structure for TCP (tp->tsorted_sent_queue) * @_sk_redir: socket redirection information for skmsg * @_nfct: Associated connection, if any (with nfctinfo bits) * @nf_bridge: Saved data about a bridged frame - see br_netfilter.c * @skb_iif: ifindex of device we arrived on * @tc_index: Traffic control index * @hash: the packet hash * @queue_mapping: Queue mapping for multiqueue devices * @head_frag: skb was allocated from page fragments, * not allocated by kmalloc() or vmalloc(). * @pfmemalloc: skbuff was allocated from PFMEMALLOC reserves * @pp_recycle: mark the packet for recycling instead of freeing (implies * page_pool support on driver) * @active_extensions: active extensions (skb_ext_id types) * @ndisc_nodetype: router type (from link layer) * @ooo_okay: allow the mapping of a socket to a queue to be changed * @l4_hash: indicate hash is a canonical 4-tuple hash over transport * ports. * @sw_hash: indicates hash was computed in software stack * @wifi_acked_valid: wifi_acked was set * @wifi_acked: whether frame was acked on wifi or not * @no_fcs: Request NIC to treat last 4 bytes as Ethernet FCS * @encapsulation: indicates the inner headers in the skbuff are valid * @encap_hdr_csum: software checksum is needed * @csum_valid: checksum is already valid * @csum_not_inet: use CRC32c to resolve CHECKSUM_PARTIAL * @csum_complete_sw: checksum was completed by software * @csum_level: indicates the number of consecutive checksums found in * the packet minus one that have been verified as * CHECKSUM_UNNECESSARY (max 3) * @dst_pending_confirm: need to confirm neighbour * @decrypted: Decrypted SKB * @slow_gro: state present at GRO time, slower prepare step required * @napi_id: id of the NAPI struct this skb came from * @sender_cpu: (aka @napi_id) source CPU in XPS * @secmark: security marking * @mark: Generic packet mark * @reserved_tailroom: (aka @mark) number of bytes of free space available * at the tail of an sk_buff * @vlan_present: VLAN tag is present * @vlan_proto: vlan encapsulation protocol * @vlan_tci: vlan tag control information * @inner_protocol: Protocol (encapsulation) * @inner_ipproto: (aka @inner_protocol) stores ipproto when * skb->inner_protocol_type == ENCAP_TYPE_IPPROTO; * @inner_transport_header: Inner transport layer header (encapsulation) * @inner_network_header: Network layer header (encapsulation) * @inner_mac_header: Link layer header (encapsulation) * @transport_header: Transport layer header * @network_header: Network layer header * @mac_header: Link layer header * @kcov_handle: KCOV remote handle for remote coverage collection * @tail: Tail pointer * @end: End pointer * @head: Head of buffer * @data: Data head pointer * @truesize: Buffer size * @users: User count - see {datagram,tcp}.c * @extensions: allocated extensions, valid if active_extensions is nonzero */ struct sk_buff { union { struct { /* These two members must be first. */ struct sk_buff *next; struct sk_buff *prev; union { struct net_device *dev; /* Some protocols might use this space to store information, * while device pointer would be NULL. * UDP receive path is one user. */ unsigned long dev_scratch; }; }; struct rb_node rbnode; /* used in netem, ip4 defrag, and tcp stack */ struct list_head list; }; union { struct sock *sk; int ip_defrag_offset; }; union { ktime_t tstamp; u64 skb_mstamp_ns; /* earliest departure time */ }; /* * This is the control buffer. It is free to use for every * layer. Please put your private variables there. If you * want to keep them across layers you have to do a skb_clone() * first. This is owned by whoever has the skb queued ATM. */ char cb[48] __aligned(8); union { struct { unsigned long _skb_refdst; void (*destructor)(struct sk_buff *skb); }; struct list_head tcp_tsorted_anchor; #ifdef CONFIG_NET_SOCK_MSG unsigned long _sk_redir; #endif }; #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) unsigned long _nfct; #endif unsigned int len, data_len; __u16 mac_len, hdr_len; /* Following fields are _not_ copied in __copy_skb_header() * Note that queue_mapping is here mostly to fill a hole. */ __u16 queue_mapping; /* if you move cloned around you also must adapt those constants */ #ifdef __BIG_ENDIAN_BITFIELD #define CLONED_MASK (1 << 7) #else #define CLONED_MASK 1 #endif #define CLONED_OFFSET() offsetof(struct sk_buff, __cloned_offset) /* private: */ __u8 __cloned_offset[0]; /* public: */ __u8 cloned:1, nohdr:1, fclone:2, peeked:1, head_frag:1, pfmemalloc:1, pp_recycle:1; /* page_pool recycle indicator */ #ifdef CONFIG_SKB_EXTENSIONS __u8 active_extensions; #endif /* fields enclosed in headers_start/headers_end are copied * using a single memcpy() in __copy_skb_header() */ /* private: */ __u32 headers_start[0]; /* public: */ /* if you move pkt_type around you also must adapt those constants */ #ifdef __BIG_ENDIAN_BITFIELD #define PKT_TYPE_MAX (7 << 5) #else #define PKT_TYPE_MAX 7 #endif #define PKT_TYPE_OFFSET() offsetof(struct sk_buff, __pkt_type_offset) /* private: */ __u8 __pkt_type_offset[0]; /* public: */ __u8 pkt_type:3; __u8 ignore_df:1; __u8 nf_trace:1; __u8 ip_summed:2; __u8 ooo_okay:1; __u8 l4_hash:1; __u8 sw_hash:1; __u8 wifi_acked_valid:1; __u8 wifi_acked:1; __u8 no_fcs:1; /* Indicates the inner headers are valid in the skbuff. */ __u8 encapsulation:1; __u8 encap_hdr_csum:1; __u8 csum_valid:1; #ifdef __BIG_ENDIAN_BITFIELD #define PKT_VLAN_PRESENT_BIT 7 #else #define PKT_VLAN_PRESENT_BIT 0 #endif #define PKT_VLAN_PRESENT_OFFSET() offsetof(struct sk_buff, __pkt_vlan_present_offset) /* private: */ __u8 __pkt_vlan_present_offset[0]; /* public: */ __u8 vlan_present:1; __u8 csum_complete_sw:1; __u8 csum_level:2; __u8 csum_not_inet:1; __u8 dst_pending_confirm:1; #ifdef CONFIG_IPV6_NDISC_NODETYPE __u8 ndisc_nodetype:2; #endif __u8 ipvs_property:1; __u8 inner_protocol_type:1; __u8 remcsum_offload:1; #ifdef CONFIG_NET_SWITCHDEV __u8 offload_fwd_mark:1; __u8 offload_l3_fwd_mark:1; #endif #ifdef CONFIG_NET_CLS_ACT __u8 tc_skip_classify:1; __u8 tc_at_ingress:1; #endif __u8 redirected:1; #ifdef CONFIG_NET_REDIRECT __u8 from_ingress:1; #endif #ifdef CONFIG_TLS_DEVICE __u8 decrypted:1; #endif __u8 slow_gro:1; #ifdef CONFIG_NET_SCHED __u16 tc_index; /* traffic control index */ #endif union { __wsum csum; struct { __u16 csum_start; __u16 csum_offset; }; }; __u32 priority; int skb_iif; __u32 hash; __be16 vlan_proto; __u16 vlan_tci; #if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS) union { unsigned int napi_id; unsigned int sender_cpu; }; #endif #ifdef CONFIG_NETWORK_SECMARK __u32 secmark; #endif union { __u32 mark; __u32 reserved_tailroom; }; union { __be16 inner_protocol; __u8 inner_ipproto; }; __u16 inner_transport_header; __u16 inner_network_header; __u16 inner_mac_header; __be16 protocol; __u16 transport_header; __u16 network_header; __u16 mac_header; #ifdef CONFIG_KCOV u64 kcov_handle; #endif /* private: */ __u32 headers_end[0]; /* public: */ /* These elements must be at the end, see alloc_skb() for details. */ sk_buff_data_t tail; sk_buff_data_t end; unsigned char *head, *data; unsigned int truesize; refcount_t users; #ifdef CONFIG_SKB_EXTENSIONS /* only useable after checking ->active_extensions != 0 */ struct skb_ext *extensions; #endif }; |
tcphdr/udphdr/iphdr構造体
linux/include/uapi/linux/tcp.hのtcphdr構造体はTCPヘッダを管理します.
tcphdr構造体の主なメンバ変数は以下になります.
- source:送信元ポート
- dest:宛先ポート
- ack,syn,fin:使用するTCPフラグ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
struct tcphdr { __be16 source; __be16 dest; __be32 seq; __be32 ack_seq; #if defined(__LITTLE_ENDIAN_BITFIELD) __u16 res1:4, doff:4, fin:1, syn:1, rst:1, psh:1, ack:1, urg:1, ece:1, cwr:1; #elif defined(__BIG_ENDIAN_BITFIELD) __u16 doff:4, res1:4, cwr:1, ece:1, urg:1, ack:1, psh:1, rst:1, syn:1, fin:1; #else #error "Adjust your <asm/byteorder.h> defines" #endif __be16 window; __sum16 check; __be16 urg_ptr; }; |
linux/include/uapi/linux/udp.hのudphdr構造体はUDPヘッダを管理します.
udphdr構造体の主なメンバ変数は以下になります.
- source:送信元ポート
- dest:宛先ポート
1 2 3 4 5 6 |
struct udphdr { __be16 source; __be16 dest; __be16 len; __sum16 check; }; |
linux/include/uapi/linux/ip.hのiphdr構造体はIPヘッダを管理します.
iphdr構造体の主なメンバ変数は以下になります.
- protocol:使用されるトランスポート層プロトコル
- saddr:送信元IPアドレス
- daddr:宛先IPアドレス
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
struct iphdr { #if defined(__LITTLE_ENDIAN_BITFIELD) __u8 ihl:4, version:4; #elif defined (__BIG_ENDIAN_BITFIELD) __u8 version:4, ihl:4; #else #error "Please fix <asm/byteorder.h>" #endif __u8 tos; __be16 tot_len; __be16 id; __be16 frag_off; __u8 ttl; __u8 protocol; __sum16 check; __be32 saddr; __be32 daddr; /*The options start here. */ }; |
ソケットの作成
ソケットの作成は,ユーザ空間でsocket関数を呼び出すのと似ていますが,作成されたstruct socket構造体はresパラメータに格納されます.
linux/include/linux/net.hに以下のソケットの作成関数があります.
- sock_create関数:socketシステムコールの後にソケットを作成する.
- sock_create_kern関数:カーネルソケットを新規に作成する.
- sock_create_lite関数:パラメータのサニティチェックなしでカーネルソケットを作成する.
1 2 3 |
int sock_create(int family, int type, int proto, struct socket **res); int sock_create_kern(struct net *net, int family, int type, int proto, struct socket **res); int sock_create_lite(int family, int type, int proto, struct socket **res); |
これらの呼び出しのパラメータは以下の通りです.
- net:もし存在する場合,使用するネットワーク名前空間への参照として使用され,通常init_netで初期化する.
- family:情報転送に使用するプロトコルファミリーを表す.通常,PF_(Protocol Family)という文字列で始まる.使用するプロトコルファミリーを表す定数はlinux/include/linux/socket.hにあり,TCP/IPプロトコルではPF_INETが最もよく使用される.
- type:ソケットのタイプを表し,このパラメータに使用される定数(enum sock_type列挙型)はlinux/include/linux/net.hにある.最もよく使われるのは,コネクションベースの送信元と送信先間通信のためのSOCK_STREAM とコネクションレス型通信のためのSOCK_DGRAMである.
- proto:使用するプロトコルを表し,typeパラメータと密接な関係がある.このパラメータに使用される定数(enum列挙型)はlinux/include/uapi/linux/in.hにあり,最も使用されるのはTCPのIPPROTO_TCPとUDPのIPPROTO_UDPである.
socketシステムコールはlinux/net/socket.cにあるsys_socket関数(28行目の「SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)」)を呼び出します.
sys_socket関数は,__sys_socket関数(1行目)を呼び出します.
__sys_socket関数では,21行目でsock_create関数を呼び出していることがわかります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
int __sys_socket(int family, int type, int protocol) { int retval; struct socket *sock; int flags; /* Check the SOCK_* constants for consistency. */ BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC); BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK); BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK); BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK); flags = type & ~SOCK_TYPE_MASK; if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK)) return -EINVAL; type &= SOCK_TYPE_MASK; if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK)) flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK; retval = sock_create(family, type, protocol, &sock); if (retval < 0) return retval; return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK)); } SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) { return __sys_socket(family, type, protocol); } |
ソケットの終了
ソケットを終了する場合(コネクションを使用するソケットの場合),および関連リソースを解放する場合は,以下のlinux/net/socket.cのsock_release関数(34行目)を呼び出します.
sock_release関数は__sock_release関数(1行目)を呼び出します.
__sock_release関数は,ソケット構造体のopsメンバ変数にあるrelease関数ポインタを呼び出します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
static void __sock_release(struct socket *sock, struct inode *inode) { if (sock->ops) { struct module *owner = sock->ops->owner; if (inode) inode_lock(inode); sock->ops->release(sock); sock->sk = NULL; if (inode) inode_unlock(inode); sock->ops = NULL; module_put(owner); } if (sock->wq.fasync_list) pr_err("%s: fasync list not empty!\n", __func__); if (!sock->file) { iput(SOCK_INODE(sock)); return; } sock->file = NULL; } /** * sock_release - close a socket * @sock: socket to close * * The socket is released from the protocol stack if it has a release * callback, and the inode is then released if the socket is bound to * an inode not a file. */ void sock_release(struct socket *sock) { __sock_release(sock, NULL); } EXPORT_SYMBOL(sock_release); |
ソケット通信によるメッセージの送受信
ソケット通信によるメッセージの送受信は以下の関数で行います.
- sock_sendmsg関数:ソケットによりメッセージを送信する.
- kernel_sendmsg関数:カーネル空間のソケットによりメッセージを送信する.
- sock_recvmsg関数:ソケットによりメッセージを受信する.
- kernel_recvmsg関数:カーネル空間のソケットによりメッセージを受信する.
メッセージの送受信関数は,ソケットのopsメンバ変数にあるsendmsg/recvmsg関数を呼び出します.
kernel_をプレフィックスに持つ関数は,ソケットがカーネルで使用される場合に使用されます.
- msg:msghdr構造体であり,送受信するメッセージを格納しています.msghdr構造体の重要な構成要素はmsg_nameとmsg_namelenで,UDPソケットの場合はメッセージの送信先アドレス(sockaddr_in構造体)を記入する必要があります.
- vec:そのデータとサイズを含むバッファへのポインタを含むstruct kvec構造体です.iovec構造体と同様の構造を持っています(iovec構造体はユーザ空間のデータに対応し,kvec構造体はカーネル空間のデータに対応します).
linux/net/socket.cのsys_sendtoシステムコールハンドラ関数(45行目)は,__sys_sendto関数(1行目)を呼び出しています.
__sys_sendto関数では,sock_sendmsg関数(37行目)を利用しています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
/* * Send a datagram to a given address. We move the address into kernel * space and check the user space data area is readable before invoking * the protocol. */ int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags, struct sockaddr __user *addr, int addr_len) { struct socket *sock; struct sockaddr_storage address; int err; struct msghdr msg; struct iovec iov; int fput_needed; err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter); if (unlikely(err)) return err; sock = sockfd_lookup_light(fd, &err, &fput_needed); if (!sock) goto out; msg.msg_name = NULL; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_namelen = 0; if (addr) { err = move_addr_to_kernel(addr, addr_len, &address); if (err < 0) goto out_put; msg.msg_name = (struct sockaddr *)&address; msg.msg_namelen = addr_len; } if (sock->file->f_flags & O_NONBLOCK) flags |= MSG_DONTWAIT; msg.msg_flags = flags; err = sock_sendmsg(sock, &msg); out_put: fput_light(sock->file, fput_needed); out: return err; } SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len, unsigned int, flags, struct sockaddr __user *, addr, int, addr_len) { return __sys_sendto(fd, buff, len, flags, addr, addr_len); } |
バイトオーダーの変換
異なるシステムにおいて,ワード内のバイトオーダー(エンディアン)には,ビッグエンディアン(最上位バイトが先),リトルエンディアン(最下位バイトが先)などがあります.
ネットワークは異なるプラットフォームのシステムを相互接続するため,インターネットではネットワークバイトオーダーと呼ばれる数値データの格納順序の標準が定められています.
これに対して,ホストコンピュータ上で数値データを表現するためのバイト列をホストバイトオーダーと呼びます.
ネットワークから送受信されるデータはネットワークバイトオーダーの形式であり,この形式とホストバイトオーダーとの間で変換する必要があります.
変換には以下のマクロを使用します.
- u16 htons(u16 x)マクロ(host to network short):16ビット整数をホストバイトオーダーからネットワークバイトオーダーに変換します.
- u32 htonl(u32 x)マクロ(host to network long):32ビット整数をホストバイトオーダーからネットワークバイトオーダーへ変換します.
- u16 ntohs(u16 x)マクロ(network to host short):16ビット整数をネットワークバイトオーダーからホストバイトオーダーに変換します.
- u32 ntohl(u32 x)マクロ(network to host long):32ビット整数をネットワークバイトオーダーからホストバイトオーダーに変換します.
netfilter
netfilterは,ネットワークパケットをキャプチャして変更・分析(フィルタリング,NAT等)するためのカーネルインターフェースの名前です.
ユーザ空間ではiptablesがnetfilterのインタフェースを使用します.
Linuxカーネルでは,netfilterを使ったパケットキャプチャは,フックを付けることによって行われます.
フックは,カーネルのネットワークパケットがたどる経路のさまざまな場所に,必要に応じて指定することができます.
パッケージがたどる経路とフックに指定可能な場所の構成図はこちらです.
netfilterはLinuxカーネル固有の機能ですが,様々なOSでnetfilterと同様の機能を実行できるC言語で開発されたネットワークアナライザ「Wireshark」を知りたいあなたはこちらからどうぞ.
netfilterで利用する構造体と関数
linux/include/linux/netfilter.hのstruct nf_hook_ops構造体でフックを定義します.
1 2 3 4 5 6 7 8 9 10 11 |
struct nf_hook_ops { /* User fills in from here down. */ nf_hookfn *hook; struct net_device *dev; void *priv; u8 pf; enum nf_hook_ops_type hook_ops_type:8; unsigned int hooknum; /* Hooks are ordered in ascending priority. */ int priority; }; |
- pf:パッケージの種類(PF_INET等)
- priority:優先度
priorityは,linux/include/uapi/linux/netfilter_ipv4.hのenum nf_ip_hook_prioritiesで定義されています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
enum nf_ip_hook_priorities { NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_RAW_BEFORE_DEFRAG = -450, NF_IP_PRI_CONNTRACK_DEFRAG = -400, NF_IP_PRI_RAW = -300, NF_IP_PRI_SELINUX_FIRST = -225, NF_IP_PRI_CONNTRACK = -200, NF_IP_PRI_MANGLE = -150, NF_IP_PRI_NAT_DST = -100, NF_IP_PRI_FILTER = 0, NF_IP_PRI_SECURITY = 50, NF_IP_PRI_NAT_SRC = 100, NF_IP_PRI_SELINUX_LAST = 225, NF_IP_PRI_CONNTRACK_HELPER = 300, NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX, NF_IP_PRI_LAST = INT_MAX, }; |
- dev:キャプチャの対象となるデバイス(ネットワークインタフェース)
- hooknum:使用されるフックのタイプ.パケットがキャプチャされるとき,処理モードはhooknumとhookメンバ変数によって定義される.
hooknumは,IPの場合,フックの種類はlinux/include/uapi/linux/netfilter.hのenum nf_inet_hooksで定義されています.
1 2 3 4 5 6 7 8 9 |
enum nf_inet_hooks { NF_INET_PRE_ROUTING, NF_INET_LOCAL_IN, NF_INET_FORWARD, NF_INET_LOCAL_OUT, NF_INET_POST_ROUTING, NF_INET_NUMHOOKS, NF_INET_INGRESS = NF_INET_NUMHOOKS, }; |
- hook:ネットワークパケット(パケットはsk_buff構造体として送られる)をキャプチャする際に呼び出されるハンドラ関数である.
- priv:ハンドラ関数に渡されるプライベートな情報である.
hookキャプチャハンドラ関数のプロトタイプはlinux/include/linux/netfilter.hのnf_hookfn関数型で定義される.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct nf_hook_state { u8 hook; u8 pf; struct net_device *in; struct net_device *out; struct sock *sk; struct net *net; int (*okfn)(struct net *, struct sock *, struct sk_buff *); }; typedef unsigned int nf_hookfn(void *priv, struct sk_buff *skb, const struct nf_hook_state *state); |
nf_hookfn関数型では,privパラメータはnf_hook_ops構造体を初期化したプライベート情報,skbはキャプチャしたネットワークパケットへのポインタです.
skbの情報に基づいて,パケットフィルタリングの判断が行われます.
この関数のstateパラメータは,入力インタフェース,出力インタフェース,優先度,フック番号など,パケットキャプチャに関連する状態情報です.
優先度とフック番号は,同じ関数を複数のフックから呼び出せるようにするために有用です.
キャプチャハンドラ関数は,linux/include/uapi/linux/netfilter.hで定義されている定数NF_*のいずれかを返すことができます.
NF_DROPはパケットをフィルタリング(無視)するために使用し,NF_ACCEPTはパケットを受理して転送するために使用します.
1 2 3 4 5 6 7 8 |
/* Responses from hook functions. */ #define NF_DROP 0 #define NF_ACCEPT 1 #define NF_STOLEN 2 #define NF_QUEUE 3 #define NF_REPEAT 4 #define NF_STOP 5 /* Deprecated, for userspace nf_queue compatibility. */ #define NF_MAX_VERDICT NF_STOP |
フックの登録・解除関数は以下になります.
- nf_register_net_hook関数:netfilterを利用したフックを登録する.
- nf_unregister_net_hook関数:netfilterを利用したフックを解除する.
- nf_register_net_hooks関数:n個のnetfilterを利用したフックを登録する.
- nf_unregister_net_hooks関数:n個のnetfilterを利用したフックを解除する.
1 2 3 4 5 6 7 |
/* Function to register/unregister hook points. */ int nf_register_net_hook(struct net *net, const struct nf_hook_ops *ops); void nf_unregister_net_hook(struct net *net, const struct nf_hook_ops *ops); int nf_register_net_hooks(struct net *net, const struct nf_hook_ops *reg, unsigned int n); void nf_unregister_net_hooks(struct net *net, const struct nf_hook_ops *reg, unsigned int n); |
netfilterフックのパラメータとして設定されたsk_buff構造体からのヘッダ抽出関数の使用に関して,いくつかの制約があります.
IPヘッダはip_hdr関数でその都度取得できますが,TCP/UDPヘッダはtcp_hdr/udp_hdr関数で取得できるのは,システム外から受信したパッケージではなく,システム内部から受信したパッケージに対してだけです.
システム外から受信したパッケージの場合,以下のようにパッケージのヘッダオフセットを手動で計算する必要があります.
1 2 3 4 |
// for TCP packets (iph->protocol == IPPROTO_TCP) tcph = (struct tcphdr*)((__u32*)iph + iph->ihl); // for UDP packets (iph->protocol == IPPROTO_UDP) udph = (struct udphdr*)((__u32*)iph + iph->ihl); |
上記のコードは,すべてのフィルタリングの状況で動作するので,ヘッダアクセス関数の代わりに使用することをおすすめします!
netfilterでパケットの送受信を制御するカーネルモジュール
netfilterでパケットの送受信を制御するカーネルモジュールのコード一式はこちらからダウンロードして下さい.
netfilterのコードnetfilter_kernel_module.cは以下になります.
netfilter_hook_func関数で宛先のIPアドレスが1.1.1.1,通信プロトコルがICMP(いわゆるpingで利用する通信プロトコル)をフィルタリングします.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/net.h> #include <linux/in.h> #include <linux/skbuff.h> #include <linux/ip.h> #include <linux/tcp.h> #define DROP_IP_ADDR 0x01010101 // IP Address: 1.1.1.1 static unsigned int netfilter_hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct iphdr *iph = ip_hdr(skb); if ((be32_to_cpu(iph->daddr) & 0xffffffff) == DROP_IP_ADDR && iph->protocol == IPPROTO_ICMP) { return NF_DROP; } return NF_ACCEPT; } static struct nf_hook_ops mynfho = { .hook = netfilter_hook_func, .hooknum = NF_INET_LOCAL_OUT, .pf = PF_INET, .priority = NF_IP_PRI_FIRST }; static int __init netfilter_kernel_module_init(void) { return nf_register_net_hook(&init_net, &mynfho); } static void __exit netfilter_kernel_module_exit(void) { nf_unregister_net_hook(&init_net, &mynfho); } module_init(netfilter_kernel_module_init); module_exit(netfilter_kernel_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Hiroyuki Chishiro"); MODULE_DESCRIPTION("Hello Kernel Module"); |
まずは「ping 1.1.1.1」を実行して正常に動作するか確認しましょう.
1 2 3 4 5 6 7 |
$ ping 1.1.1.1 PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data. 64 bytes from 1.1.1.1: icmp_seq=1 ttl=128 time=4.73 ms 64 bytes from 1.1.1.1: icmp_seq=2 ttl=128 time=4.72 ms 64 bytes from 1.1.1.1: icmp_seq=3 ttl=128 time=5.54 ms 64 bytes from 1.1.1.1: icmp_seq=4 ttl=128 time=6.10 ms 64 bytes from 1.1.1.1: icmp_seq=5 ttl=128 time=5.31 ms |
次に,ビルドしてカーネルモジュールをロードしましょう.
1 2 3 4 5 6 7 8 9 10 11 |
$ make make -C /lib/modules/5.15.0-50-generic/build M=/home/chishiro/c-language/netfilter_kernel_module modules make[1]: Entering directory '/usr/src/linux-headers-5.15.0-50-generic' CC [M] /home/chishiro/c-language/netfilter_kernel_module/netfilter_kernel_module.o MODPOST /home/chishiro/c-language/netfilter_kernel_module/Module.symvers CC [M] /home/chishiro/c-language/netfilter_kernel_module/netfilter_kernel_module.mod.o LD [M] /home/chishiro/c-language/netfilter_kernel_module/netfilter_kernel_module.ko BTF [M] /home/chishiro/c-language/netfilter_kernel_module/netfilter_kernel_module.ko Skipping BTF generation for /home/chishiro/c-language/netfilter_kernel_module/netfilter_kernel_module.ko due to unavailability of vmlinux make[1]: Leaving directory '/usr/src/linux-headers-5.15.0-50-generic' $ sudo insmod netfilter_kernel_module.ko |
「ping 1.1.1.1」を実行するとnetfilterによるパケットフィルタリングにより正常に動作しないことがわかります.
1 2 |
$ ping 1.1.1.1 PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data. |
カーネルモジュールをアンロードすると「ping 1.1.1.1」は正常に動作します.
1 2 3 4 5 6 7 8 |
$ sudo rmmod netfilter_kernel_module.ko $ ping 1.1.1.1 PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data. 64 bytes from 1.1.1.1: icmp_seq=1 ttl=128 time=4.57 ms 64 bytes from 1.1.1.1: icmp_seq=2 ttl=128 time=30.6 ms 64 bytes from 1.1.1.1: icmp_seq=3 ttl=128 time=4.85 ms 64 bytes from 1.1.1.1: icmp_seq=4 ttl=128 time=5.94 ms 64 bytes from 1.1.1.1: icmp_seq=5 ttl=128 time=4.42 ms |
参考:netcat
netcatは,ネットワークコードを含むアプリケーションを開発する時によく利用されるツールです.
また,netcatは,「Swiss-Army Knife for TCP/IP」(TCP/IP用スイス軍用ナイフ)というニックネームも持っています.
※netcatはスイス軍用ナイフのように便利という意味です.
netcatでできることは以下になります.
- TCP接続を開始する.
- TCP接続を待つ.
- UDPパケットを送受信する.
- トラフィックをhexdumpフォーマットで表示する.
- 接続を確立した後のプログラムを実行する(例:シェル).
- 送信パッケージの特別なオプションを設定する.
netcatの使い方を知りたいあなたは,以下の記事がおすすめです!
TCPアクセラレーション(TCP高速化機能)
TCPアクセラレーション(TCP高速化機能)は,TCPにおいてアプリケーションに手を加えることなく,ネットワーク接続において標準的なTCPよりも優れたスループットを実現する一連の技術のことです.
本記事では,LinuxカーネルにマージされているTCPアクセラレーションを紹介します.
基本的には,TCPアクセラレーションは,Bufferbloat(バッファブロート)と呼ばれるパケット交換型ネットワークにおける送信パケットの過剰なバッファリングにより生じる大きな遅延に関する課題を解決するために開発されています.
※後述するUDPアクセラレーションも含みます.
Bufferbloatの解説動画は以下がわかりやすいです.
TCP Small Queues(TSQ),CoDel,Byte Queue Limits(BQL)
TCP Small Queues(TSQ)は,データがどこにキューイングされているかに関係なく,任意のソケットによって送信のためにキューイングされるデータの量を制限することです.
TSQの目的は,伝送キューに入るTCPパケットを減らし,ラウンドトリップタイム(RTT)や輻輳ウインドウサイズ(cwnd)の偏り,bufferbloatの一部を減らすことです.
TSQの開発の動機にはCoDelと呼ばれるキュー管理アルゴリズムが挙げられます.
CoDelは,パケットが時間とともにルータのキューに蓄積されるのを防ぐために動作します.
より低いレベルでは,バイトキュー制限は,特定のネットワークインターフェースに出力されるのを待つことができるデータ量に上限を設定します.
しかし,バイトキュー制限はデバイスキューのレベルでしか機能しません.
一方,ネットワーキングスタックは,キューイング規則(Queueing Discipline)レベルなど,他の場所でバッファリングを行うことができます.
※キューイング規則はキューを取り出す方法のことでFirst In First Out(FIFO)等のことを表します.
また,Byte Queue Limits(BQL)と呼ばれる適正な上限値を設定することで,NICの不要なキューイングを削減する手法もあります.
TSQの特徴は,デバイスキューより上のレベルでバッファリングを制限できることです.
TSQは,データがどこにキューイングされているかに関係なく,任意のソケットによって送信のためにキューイングされるデータの量を制限します.
つまり,TSQは,キューイング,トラフィック制御,またはネットフィルタのコードに潜むバッファに惑わされません.
TSQによるIPv4のTCPの伝送制限は,/proc/sys/net/ipv4/tcp_limit_output_bytesで設定できます.
私のLinux環境では,1MB(= 1,024KB = 1,048,576Bytes)に設定されています.
1 2 |
$ cat /proc/sys/net/ipv4/tcp_limit_output_bytes 1048576 |
TSQのIPv4のコードは,linux/net/ipv4/tcp_output.cにあります.
- tsq_tasklet構造体:TSQを管理する.
- tcp_tsq_write関数:TCPのパケットをTSQで伝送する.
- tcp_tsq_handler関数:TSQ用のハンドラ関数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
/* TCP SMALL QUEUES (TSQ) * * TSQ goal is to keep small amount of skbs per tcp flow in tx queues (qdisc+dev) * to reduce RTT and bufferbloat. * We do this using a special skb destructor (tcp_wfree). * * Its important tcp_wfree() can be replaced by sock_wfree() in the event skb * needs to be reallocated in a driver. * The invariant being skb->truesize subtracted from sk->sk_wmem_alloc * * Since transmit from skb destructor is forbidden, we use a tasklet * to process all sockets that eventually need to send more skbs. * We use one tasklet per cpu, with its own queue of sockets. */ struct tsq_tasklet { struct tasklet_struct tasklet; struct list_head head; /* queue of tcp sockets */ }; static DEFINE_PER_CPU(struct tsq_tasklet, tsq_tasklet); static void tcp_tsq_write(struct sock *sk) { if ((1 << sk->sk_state) & (TCPF_ESTABLISHED | TCPF_FIN_WAIT1 | TCPF_CLOSING | TCPF_CLOSE_WAIT | TCPF_LAST_ACK)) { struct tcp_sock *tp = tcp_sk(sk); if (tp->lost_out > tp->retrans_out && tp->snd_cwnd > tcp_packets_in_flight(tp)) { tcp_mstamp_refresh(tp); tcp_xmit_retransmit_queue(sk); } tcp_write_xmit(sk, tcp_current_mss(sk), tp->nonagle, 0, GFP_ATOMIC); } } static void tcp_tsq_handler(struct sock *sk) { bh_lock_sock(sk); if (!sock_owned_by_user(sk)) tcp_tsq_write(sk); else if (!test_and_set_bit(TCP_TSQ_DEFERRED, &sk->sk_tsq_flags)) sock_hold(sk); bh_unlock_sock(sk); } |
TCP Fast Open(TFO)
TCP Fast Open(TFO)は,2つのエンドポイント間の連続したTCPコネクションのオープンを高速化する拡張機能です.
TFO cookie(TCPオプション)は,クライアント側に保存され,サーバとの最初の接続時に設定される暗号化cookieです.
クライアントが後で再接続するとき,最初のSYNパケットとTFO cookieデータを送信し,自身を認証します.
成功すれば,サーバは3ウェイハンドシェイクの最終ACKパケットを受信する前にクライアントへのデータ送信を開始できるため,ラウンドトリップ遅延をスキップしてデータ送信開始の待ち時間を短縮することができます.
TFOのIPv4サポートは,Linuxカーネルのバージョン3.6(クライアントのサポート)と3.7(2012年12月)(サーバのサポート)でLinuxカーネルのメインラインにマージされました.
2014年1月のLinuxカーネルのバージョン3.13でデフォルトで有効になりました.
IPv6サーバのTFOサポートはLinuxカーネルのバージョン3.16でマージされました.
TFOのIPv4のコードは,linux/net/ipv4/tcp_fastopen.cにあるので興味があるあなたは是非読みましょう!
TCP Fast Openの解説動画はこちらです.
TCPの輻輳制御
TCPでは,インターネットにアクセスが集中することで輻輳が発生し,性能が低下する主な原因になっています.
輻輳を回避するために,多くのTCPの輻輳制御アルゴリズムが提案されました.
TCPの輻輳制御アルゴリズムのタイプは以下になります.
- ロスベース(Loss-based)輻輳制御
- 遅延ベース(Delay-based)輻輳制御
- Hybrid輻輳制御
本記事では代表的なTCPの輻輳制御アルゴリズムである以下を紹介します.
まずはTCPの輻輳制御を学びたいあなたは「基本から学ぶ TCPと輻輳制御 ……押さえておきたい輻輳制御アルゴリズム 記事一覧」を読みましょう!
以下の動画もおすすめです!
CUBIC
CUBICは,Loss-based輻輳回避アルゴリズムです.
CUBICの特徴は,従来のアルゴリズムに比べて,高遅延に直面しても,より迅速かつ確実にネットワーク上の広帯域接続を達成することができることです.
CUBICは,長距離ネットワークを最適化するのに有用です.
2006年に最初のCUBIC実装がLinuxカーネルのバージョン2.6.13でリリースされたました.
Linuxカーネルのバージョン2.6.19以降,CUBICはLinuxカーネルのデフォルトTCP輻輳制御アルゴリズムになりました.
LinuxでCUBICを利用しているかどうかは,sysctlコマンドで確認できます.
私のLinuxカーネルのバージョン5.15ではCUBICであることがわかります.
1 2 |
$ sysctl net.ipv4.tcp_congestion_control net.ipv4.tcp_congestion_control = cubic |
CUBICのIPv4のコードは,linux/net/ipv4/tcp_cubic.cにあるので興味があるあなたは是非読みましょう!
CUBICの解説動画はこちらです.
BBR
BBRは,2016年にGoogleで開発されたDelay-based輻輳制御アルゴリズムです.
※BBRはBottleneck Bandwidth and Round-trip propagation timeの略です.
ほとんどのTCPの輻輳制御アルゴリズムは,パケットロスに依存して輻輳や送信率の低下を検出するという意味でLoss-basedです.
BBRは,ネットワークが送信データパケットの直近のフライトを配信した際の最大帯域幅と往復時間を使用して,ネットワークのモデルを構築します.
パケット配信の累積的または選択的な確認応答ごとに,データパケットの送信とそのパケットの確認応答との間の時間間隔にわたって配信されたデータ量を記録するレートサンプルが生成されます.
ネットワーク・インターフェース・コントローラがMbit/sからGbit/sの性能に進化するにつれ,パケットロスではなくBufferbloatに関連する遅延が最大スループットの実現に対して支配的になっています.
そこで,BBRなどの高スループットと低遅延を実現するDelay-based輻輳制御アルゴリズムが,CUBIC等の一般的なLoss-based輻輳制御アルゴリズムより優れてきています.
BBRはLinuxカーネルのバージョン4.9にマージされました.
また,後述するQUICでも利用可能です.
BBRのIPv4のコードは,linux/net/ipv4/tcp_bbr.cにあるので興味があるあなたは是非読みましょう!
BBRの解説動画はこちらです.
参考:TCP Protective Load Balancing(PLB),Data Center TCP(DCTCP)
TCP Protective Load Balancing(PLB)は,グーグルが開発したLinuxカーネルのTCPの輻輳制御「Data Center TCP(DCTCP)」のコードにフックしてロードバランシングする手法です.
PLBは,Linuxカーネルのバージョン6.2でマージ予定です.
PLBの論文は「PLB: Congestion Signals are Simple and Effective for Network Load Balancing」ですので,興味があるあなたは是非読みましょう!
UDPアクセラレーション(UDP高速化機能)
UDPアクセラレーション(UDP高速化機能)は,UDPにおいてアプリケーションに手を加えることなく,ネットワーク接続において標準的なUDPよりも優れたスループットを実現する一連の技術のことです.
つまり,TCPアクセラレーションのUDP版です.
本記事では,LinuxカーネルにマージされているUDPアクセラレーションを紹介します.
UDP-Lite
UDP-Liteは,コネクションレス型のプロトコルで,破損の可能性があるデータペイロードを受信側で破棄せず,アプリケーションに配信することを可能にします.
従来のUDPパケットでは1ビットでもデータが損傷すれば破棄されるのに対して,UDP-Liteはそのまま伝送されます.
UDP-Liteにより,データの完全性に関する決定をアプリケーション層(アプリケーションまたはコーデック)で行えるため,音声や動画の伝送に有用です.
UDP-LiteのIPv4のコードは,linux/net/ipv4/udplite.cにあるので興味があるあなたは是非読みましょう!
UDP-LiteはLinuxカーネルでは必要最低限の処理で,ほとんど何もしないことが高速化に貢献しているということです!
UDP-Liteの解説動画はこちらです.
参考:Quick UDP Internet Connections(QUIC)
Quick UDP Internet Connections(QUIC)は,グーグルが開発したUDPにコネクション指向を追加したトランスポート層の通信プロトコルです.
QUICはLinuxカーネルのメインラインにはマージされていませんので注意して下さい.
QUICはユーザ空間のライブラリとして実装されています.
QUICの詳細を知りたいあなたはこちらからどうぞ.
まとめ
今回はソケット通信(TCP/UDP/IP)を紹介しました.
Linuxカーネルでソケット通信がどのように実装されているかがわかりました.
ソケット通信(TCP/UDP/IP)を深掘りしたいあなたは,以下を読みましょう!
- Linux Networking Documentation
- Networking
- Linux IP Networking
- The TUX Web Server
- Beej's Guide to Network Programming Using Internet Sockets
- Kernel Korner - Network Programming in the Kernel
- Hacking the wholism of GNU/Linux net*
- Kernel bypass
- The netfilter.org project
- A Deep Dive Into Iptables and Netfilter Architecture
- LF Networking
- TCP Fast Open
- CUBIC: A New TCP-Friendly High-Speed TCP Variant
- BBR: Congestion-Based Congestion Control
- netfilterでパケットの送受信を制御する
- 高度なトラフィック制御
以下の動画もおすすめです!
LinuxカーネルはC言語で書かれています.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!