Linux ポート番号と種類

ウェルノウンポートとかエフェメラルポートとかの単語は IPA の NW を勉強したときの知識としては知っているものの、正直よくわからん状態だったので調べました。

ポート番号の定義

ポート番号とその利用用途は IANA が管理している。Internet Assigned Numbers Authority (IANA) Procedures for the Management of the Service Name and Transport Protocol Port Number Registryによると、次の種類に分けられる。

名前 ポート番号 用途 アサイン
Well Known Ports 0-1023 the System Ports assigned by IANA
Registered Ports 1024-49151 the User Ports assigned by IANA
Ephemeral Ports 49152-65535 the Dynamic Ports never assigned

IANA でアサイン済みのポート番号は Service Name and Transport Protocol Port Number Registry で確認できる。

f:id:kimulla:20200723213009p:plain

たとえば WEB アプリケーション開発中によく利用するであろう 8080 ポートも HTTP Alternate (see port 80) として定義されている。

Linux 上の実装の話

/etc/services

Linux には IANA の定義に基づいてポート番号と用途を記述した /etc/services というファイルが存在する。glibc はこのファイルを読み込み、getservent(3), getservbyname(3), getservbyport(3), setservent(3), and endservent(3)といった関数で利用する。これらの関数を利用する代表的な仕組みとしては xinetd や tcpdump がある。

]# nm -D /usr/sbin/xinetd | grep getserv
                 U getservbyname

]#  nm -D /usr/sbin/tcpdump | grep getserv
                 U getservent

しかしこのファイルを尊重するかどうかはあくまでアプリケーションの実装に依存する。Linux カーネルとして適切なサービスにのみポート番号の利用を許可する、といった制限はしない。

例えば、http サーバを好きなポート番号で起動できる。

]# python3 -m http.server 2000
Serving HTTP on 0.0.0.0 port 2000 (http://0.0.0.0:2000/) ...
^C
Keyboard interrupt received, exiting.

Well Known Ports

Well Known Ports は root 権限を持ったユーザのみしか利用できない(メジャーなサービスが利用するポートなので、信用できないユーザが利用するのを防ぐ)。

]$ id
uid=1002(kimullaa) gid=1002(kimullaa) groups=1002(kimullaa) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

]$ python3 -m http.server 80
Traceback (most recent call last):
  File "/usr/lib64/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib64/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/usr/lib64/python3.6/http/server.py", line 1211, in <module>
    test(HandlerClass=handler_class, port=args.port, bind=args.bind)
  File "/usr/lib64/python3.6/http/server.py", line 1185, in test
    with ServerClass(server_address, HandlerClass) as httpd:
  File "/usr/lib64/python3.6/socketserver.py", line 456, in __init__
    self.server_bind()
  File "/usr/lib64/python3.6/http/server.py", line 136, in server_bind
    socketserver.TCPServer.server_bind(self)
  File "/usr/lib64/python3.6/socketserver.py", line 470, in server_bind
    self.socket.bind(self.server_address)
PermissionError: [Errno 13] Permission denied

root 権限を持っていれば Well Known Ports を利用できる。

]# id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

]# python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
^C
Keyboard interrupt received, exiting.

Registered Ports

Registered Ports はどのユーザでも利用できる。

]# python3 -m http.server 1024
Serving HTTP on 0.0.0.0 port 1024 (http://0.0.0.0:1024/) ...

Ephemeral Ports

Ephemeral Ports の範囲はカーネルパラメータで設定できる。CentOS 8 のデフォルトだと 32768-60999 に設定されている。

]# sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768    60999

なお、この 32768-60999 の範囲は RFC の 49152-65535 と異なる。LinuxがIANA Ephemeralポート範囲を使用しないのはなぜですか?によると、過去にはこの範囲でも IANA の定義を満たしていたのと、 変更しようとしたが IANA のポートレンジだと小さすぎると判断されたためらしい。
参考 LKML Re: [patch] ip_local_port_range sysctl has annoying default

ポートの割り当て

TCP サーバを例にして説明すると、bind(2) 時にポート番号を指定しなければ Ephemeral Port から割り当てられる。

#include <sys/fcntl.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
    int sfd, cfd;
    struct sockaddr_in my_addr, peer_addr;
    socklen_t peer_addr_size;

    if ((sfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
            perror("sock");
            exit(1);
    }

    bzero((char *) &my_addr, sizeof(my_addr));
    my_addr.sin_family = PF_INET;
    my_addr.sin_port = 0;

    if (bind(sfd, (struct sockaddr *) &my_addr, sizeof(my_addr)) == -1) {
            perror("bind");
            exit(1);
    }

    if (listen(sfd, 50) == -1) {
            perror("listen");
            exit(1);
    }

    cfd = accept(sfd, (struct sockaddr *) &peer_addr,&peer_addr_size);
    if (cfd == -1) {
            perror("accept");
            exit(1);
    }

}

このプログラムを起動すると、Ephemeral Port の範囲から割り当てられているのがわかる。また起動ごとに Ephemeral Ports から割り当てられるので、複数起動できる。

]# gcc -g -O0 main.c -o main
]# ./main &
]# ./main &
]# netstat -tulpn | grep main
tcp        0      0 0.0.0.0:32768           0.0.0.0:*               LISTEN      13768/./main
tcp        0      0 0.0.0.0:32769           0.0.0.0:*               LISTEN      13767/./main

my_addr.sin_port = htons(8080); という風にポート番号を指定すると、固定のポート番号を利用する。

]# netstat -tulpn | grep main
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN      13735/./main

この場合、複数は起動できない。

]# ./main
bind: Address already in use

ポート枯渇

Ephemeral Port からポートを割り当てる場合、利用可能なポートが尽きることがある。この場合、bind に失敗する。

// 一時的に Ephemeral Ports の範囲を絞る
]# sysctl -w  net.ipv4.ip_local_port_range="32768 32769"
net.ipv4.ip_local_port_range = 32768 32769

]# ./main &
[1] 13612
]# ./main &
[2] 13614
]# ./main
bind: Address already in use

ip_local_reserved_ports

net.ipv4.ip_local_reserved_ports を利用すると、Ephemeral Port を予約できる。こうすると、ポート番号を明示的に指定した場合のみ利用できるようになる。

]# sysctl -w net.ipv4.ip_local_reserved_ports=32768
net.ipv4.ip_local_reserved_ports = 32768

最後に

まあ何も気にせず、アプリケーションのデフォルトポートを使うんですけどね。