Linux operatsioonisüsteemis töötab võrguühendus
Sissejuhatus
TODO
Tööpõhimõte
Teenuse pakkumise poolelt vaadates toimub seoses võrguühenduse teenindamisega selline sündmuste käik
SOCKET() -> BIND() -> LISTEN() -> ACCEPT() -> READ/WRITE() -> CLOSE()
kus erinevad tarkvaralised lahendused saavad jagada seda järgnevust erinevalt lõikudeks, nt
- üks netcat protsess tegeleb kõigi teemadega ise
- postgres isand protsess tegeleb kuni accept juurde, edasi moodustatakse uus protsess ning see töötab lõpuks 'active connected socket' olukorraga
- sshd/init (ubuntu 24.04) - esialgu kuulab võrgus init protsess port 22/tcp, st on listen olekus, kui saabub esimene pöördumine tuuakse juurde sshd protsess, listen antakse üle (sd_listen_fds tehnika abil)
Väited
- toodud nähtusi saab omavahel kombineerida (nt nginx-reuseport režiimis kasutab lisaks 'sshd/init' lähenemise elemente)
- kui toimub võrguühendus, siis serveri poolelt osaleb kaks erinevat socketit: 1. listening socket, 2. active connected socket
- socket esineb kerneli mälu struktuuri üksusena (sinna on üles kirjutatud metaandmed, nt port number, aga ka andmetega seotud queue'd)
Listening-socket ja active-connected-socket objektidega toimub on sarnane protsessiga toimuvale forkimise olukorras
- pidavalt on süsteemis olemas nö parent nähtus: vastavalt 1. listen socket, 2. parent protsess
- teatud olukorras toimub uue objekti tekkimine: 1. saabuva võrguühenduse teenindamisega moodustatakse listen socket alusel koopia; 2. xxx
TODO
Väited
- haproxy - kasutatakse vaikimisi
- nginx - vaikimisi ei kasutata, aga soovitatakse kasutada jõudluse suurendamise eesmärgil
- zabbix-agent2 - ei ole võimalik kasutada
Võrguühendus vaatlemine
Serveri poolel on lähtepunktiks ainult nn 'listening socket', peale netcat protsessi käivitamist ütleb protsess kernelile kolm syscall'i
- socket()
- bind()
- listen()
- accept()
tulemusena paistab (reaalset grep väljundis accept4 ei ole paista)
terminal 1 - server-01# strace netcat -l 6001 2>&1 | egrep "socket|setsockopt|bind|listen|accept"
..
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(6001), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 1) = 0
accept4(...
...
terminal 2 - server-02# netstat -anpt | grep netcat
tcp 0 0 0.0.0.0:6001 0.0.0.0:* LISTEN 21473/netcat
root@zabbix-pub-01:~# lsof -n -P -p 21473
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
netcat 21473 root cwd DIR 252,2 4096 8193 /root
netcat 21473 root rtd DIR 252,2 4096 2 /
netcat 21473 root txt REG 252,2 39560 841 /usr/bin/nc.openbsd
netcat 21473 root mem REG 252,2 2125328 7621 /usr/lib/x86_64-linux-gnu/libc.so.6
netcat 21473 root mem REG 252,2 55536 15411 /usr/lib/x86_64-linux-gnu/libmd.so.0.1.0
netcat 21473 root mem REG 252,2 68104 8515 /usr/lib/x86_64-linux-gnu/libresolv.so.2
netcat 21473 root mem REG 252,2 80888 15290 /usr/lib/x86_64-linux-gnu/libbsd.so.0.12.1
netcat 21473 root mem REG 252,2 236616 7618 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
netcat 21473 root 0u CHR 136,7 0t0 10 /dev/pts/7
netcat 21473 root 1u CHR 136,7 0t0 10 /dev/pts/7
netcat 21473 root 2u CHR 136,7 0t0 10 /dev/pts/7
netcat 21473 root 3u IPv4 1514112 0t0 TCP *:6001 (LISTEN)
kus
- lsof käsk esitab userspace protsessiga seotud avatud failide nimekirja (muu hulgas kuulub nimekirja võrgusoketiga seotud võrguühenduse inode 1514112
- 1514112 on operatsioonisüsteemis unikaalne globaalne võrgusoketi inode number (ino)
- 3u on protsessi kontekstis unikaalne file descriptor väärus (3r või 4w oleksid põhimõtteliselt read ja write, u on universal st mõlemad)
- tegu on listening soketiga, talle vastab mälueraldis jms
- soket asub ise kernel space'is, aga userspace'i protsessil on temaga kontakt fd abil (selle suhtes teeb protess nt read() ja write() syscall'isid
- accept() syscall on pooleldi täidetud, st ta ootab võrgust connect() syscall'ile vastavat pöördumist
Peale kliendi ühenduse alustamist st kui ühendus toimub, paistab kliendi arvutis syscallid
- socket
- setsockopt
- connect
terminal 1 - klient-01# # strace netcat 192.168.10.193 6001 2>&1 | egrep "socket|setsockopt|connect"
..
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
setsockopt(3, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(6001), sin_addr=inet_addr("192.168.10.193")}, 16) = 0
...
terminal 2 - klient-01# netstat -anpt | grep netcat
tcp 0 0 192.168.10.196:58384 192.168.10.193:6001 ESTABLISHED 345883/netcat
# lsof -n -P -p 345883
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
netcat 345883 root cwd DIR 253,2 4096 14 /root
netcat 345883 root rtd DIR 253,2 4096 2 /
netcat 345883 root txt REG 253,2 35032 419036 /usr/bin/nc.traditional
netcat 345883 root mem REG 253,2 1999312 403434 /usr/lib/x86_64-linux-gnu/libc.so.6
netcat 345883 root mem REG 253,2 225600 403431 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
netcat 345883 root 0u CHR 136,2 0t0 5 /dev/pts/2
netcat 345883 root 1u CHR 136,2 0t0 5 /dev/pts/2
netcat 345883 root 2u CHR 136,2 0t0 5 /dev/pts/2
netcat 345883 root 3u IPv4 29390854 0t0 TCP 192.168.10.196:58384->192.168.10.193:6001 (ESTABLISHED)
kus
- üks active connected socket oma mälueraldisega kernelis
- 29390854 on operatsioonisüsteemi jaoks globaalselt unikaalne inode väärtus (ino)
ja serveris kaks võrgusoketit nüüd, accept syscall sai valmis (tema tegevuse tulemusena ilmus file descriptor 4u
root@zabbix-pub-01:~# netstat -anpt | grep netcat tcp 0 0 0.0.0.0:6001 0.0.0.0:* LISTEN 21473/netcat tcp 0 0 192.168.10.193:6001 192.168.10.196:58384 ESTABLISHED 21473/netcat root@zabbix-pub-01:~# lsof -n -P -p 21473 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME netcat 21473 root cwd DIR 252,2 4096 8193 /root netcat 21473 root rtd DIR 252,2 4096 2 / netcat 21473 root txt REG 252,2 39560 841 /usr/bin/nc.openbsd netcat 21473 root mem REG 252,2 2125328 7621 /usr/lib/x86_64-linux-gnu/libc.so.6 netcat 21473 root mem REG 252,2 55536 15411 /usr/lib/x86_64-linux-gnu/libmd.so.0.1.0 netcat 21473 root mem REG 252,2 68104 8515 /usr/lib/x86_64-linux-gnu/libresolv.so.2 netcat 21473 root mem REG 252,2 80888 15290 /usr/lib/x86_64-linux-gnu/libbsd.so.0.12.1 netcat 21473 root mem REG 252,2 236616 7618 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 netcat 21473 root 0u CHR 136,7 0t0 10 /dev/pts/7 netcat 21473 root 1u CHR 136,7 0t0 10 /dev/pts/7 netcat 21473 root 2u CHR 136,7 0t0 10 /dev/pts/7 netcat 21473 root 3u IPv4 1514112 0t0 TCP *:6001 (LISTEN) netcat 21473 root 4u IPv4 1514113 0t0 TCP 192.168.10.193:6001->192.168.10.196:58384 (ESTABLISHED)
- listening socket oma mälueraldisega jms
- active connected socket oma mälueraldisega
- võrgusoketi inode numbrid on soketitel erinevad - 1514112 ja 1514113
- lisaks tekkinud file descriptor 4u on read() ja write() syscallide poolt kasutatav
- asjaolu, et võrguühendusega on seotud fd nii nagu tavalise failisüsteemi faili puhul kinnitab asjaolu, et in-unix-everything-is-a-file
Väited
- serveri poolel olev listen-socket ülesseadmine on
Reuseport
Osutub, et tcp/ip network stack Linux, Windows, BSD jt implementatsioonid võimaldavad teatud tingimustel mitmel protsessil korraga sama porti kasutada. Seda võimalust kontrollib 'so_reuseport' socket option ('so_').
Python skript
# cat socket-01-server.py
import socket
import os
import sys
PORT = 8083
# Fetch the Process ID so we can easily tell which instance answers the client
PID = os.getpid()
# 1. Create a standard TCP IPv4 Socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. THE MAGIC OPTIONS: Enable Reuse Address and Reuse Port
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
# 3. Bind and Listen (Standard blocking mode)
server_socket.bind(('0.0.0.0', PORT))
server_socket.listen(10)
print(f"[Worker PID: {PID}] Multi-core listening gate opened on port {PORT}...")
try:
while True:
# This call BLOCKS completely. The script sleeps right here in the kernel
# until the Linux 4-tuple hash decides to route a client to THIS specific socket instance.
client_socket, client_address = server_socket.accept()
# Read the raw incoming HTTP request payload (up to 4KB)
request = client_socket.recv(4096)
if request:
# Construct a basic HTTP response containing the answering process ID
body = f"Hello from Worker Process ID: {PID}\n"
response = (
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
f"Content-Length: {len(body)}\r\n"
"Connection: close\r\n"
"\r\n"
f"{body}"
).encode('utf-8')
# Fire the response back to the client
client_socket.sendall(response)
# Close the socket immediately to return the client line
client_socket.close()
except KeyboardInterrupt:
print(f"\n[Worker {PID}] Shutting down down gracefully.")
finally:
server_socket.close()
Nginx
TODO
haproxy
TODO
Kasulikud lisamaterjalid
- TODO