ウェルノウンポートとかエフェメラルポートとかの単語は 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 で確認できる。
たとえば 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
最後に
まあ何も気にせず、アプリケーションのデフォルトポートを使うんですけどね。