Linux operatsioonisüsteemis töötab võrguühendus: erinevus redaktsioonide vahel

Allikas: Imre kasutab arvutit
Mine navigeerimisribaleMine otsikasti
899. rida: 899. rida:
 
CGroup: /system.slice/system-raw\x2dwebserver\x2dtcp.slice/raw-webserver-tcp@6-192.168.10.193:1044-192.168.10.194:36804.service
 
CGroup: /system.slice/system-raw\x2dwebserver\x2dtcp.slice/raw-webserver-tcp@6-192.168.10.193:1044-192.168.10.194:36804.service
 
└─13900 python3 /usr/local/bin/raw_webserver.py
 
└─13900 python3 /usr/local/bin/raw_webserver.py
  +
</pre>
   
  +
Klient pöördub
  +
  +
<pre>
  +
# curl -v http://192.168.10.193:1044/
  +
</pre>
  +
  +
Statistika
  +
  +
<pre>
 
May 31 19:46:42 zabbix-pub-01 systemd[1]: Starting raw-webserver-tcp@6-192.168.10.193:1044-192.168.10.194:36804.service - Protocol-Blind Web Server Instance (Per-Connection) (192.168.10.194:36804)...
 
May 31 19:46:42 zabbix-pub-01 systemd[1]: Starting raw-webserver-tcp@6-192.168.10.193:1044-192.168.10.194:36804.service - Protocol-Blind Web Server Instance (Per-Connection) (192.168.10.194:36804)...
 
May 31 19:46:42 zabbix-pub-01 raw_webserver.py[13900]: Received Request: GET / HTTP/1.1
 
May 31 19:46:42 zabbix-pub-01 raw_webserver.py[13900]: Received Request: GET / HTTP/1.1

Redaktsioon: 31. mai 2026, kell 21:20

Sissejuhatus

Käesolev tekst

  • ei juhenda arvuti ip aadressi ja ruutingu seadistamist
  • mõtiskleb, mis asi on võrguühendus
  • mõtiskleb, kuidas on arvutis töötav protsess seotud võrguühendusega (ja vastupidi)

Mõisted

  • berkeley sockets api - käesoleva teksti peamini sisu
  • streams api - berkeley sockets api peamini alternatiiv - solaris ja co kasutavad

Tööpõhimõte

Teenuse pakkumise poolelt vaadates toimub seoses võrguühenduse teenindamisega selline sündmuste käik (välja on kirjutatud nn syscall'id)

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õrguühenduse töötamine on seotud tavaliselt user-space protsessi töötamisega

Kliendi poolt vaadates

SOCKET() -> CONNECT() -> READ/WRITE() -> CLOSE()

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)

Selleks, et protsess saaks kasutada võrku ületab ta nö user-space -> kernel-space horisondi syscallide abil, ja neid ei ole palju, nt (mõni on ühine nii kliendile kui serverile, nt 'socket()', mõni iseloomulik serverile (nt listen()), mõni iseloomulik kliendile (nt connect())

  • socket()
  • bind()
  • listen()
  • accept()
  • connect()
  • read()
  • write()
  • epoll()
  • poll()
  • close()

Listening-socket ja active-connected-socket objektidega toimuv on sarnane protsessiga toimuvale forkimise (clone()) 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. uue protsessi vajadusel olemasolev protsess moodustab endast koopia ja täidab koodi osa mälus uue tarkvara st programmi sisuga)

Võrguühenduse vaatlemine - netcat

Serveri poolel on lähtepunktiks ainult nn 'listening socket', peale netcat protsessi käivitamist ütleb protsess kernelile kolm syscall'i

  • socket() - nö üksuse loomine
  • bind() - üksuse omaduste täpsustamine
  • listen() - täiendav üksuse omaduste täpsustamine
  • accept() - kliendi ootamine, selle tulemusena minnakse moodustama uut nn socket üksust, ja vana jääb ka alles

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-01# netstat -anpt | grep netcat
tcp        0      0 0.0.0.0:6001            0.0.0.0:*               LISTEN      21473/netcat

terminal 2 - server-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)
  • fd 0u, 1u ja 2u on standardse tähendusega, vastavalt - stdin, stdout ja stderr
  • 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

terminal 2 - klient-01# 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

terminal 2 - server-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

terminal 2 - server-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 valmisoleku tekitamine sisenevate võrguühenduste teenindamiseks
  • kui accept syscall lõpetab, siis listen-socket on nagu ta ikka on edasi, lisaks on tekkinud juurde teine ühendusega tegelev soket
  • listen-socket ja active-connection-soket suhe on võrreldab protsessidega operatsioonisüsteemis: parent ja child kuid sellise täpsustusega, et parent sünnitab ainult nö poegi st kes edasi ise ei suuda sünnitada; parent protsessi poolt tekitatud child protsess reeglina saab omakorda tekitada child protsessi, st protsess tekitab tütreid
  • šovinist võiks võrrelda võrguühenduse klienti nö poeglapsega ja serverit tütarlapsega

Võrguühenduse variatsioonid

Väited

  • võrguühendusega tegeleb kõige lihtsamal juhtumil üks ja sama protsess (non-threaded, netcat juhtum)
  • võrguühendusega tegeleb üks threaded protsess
  • võrguühendusega tegeleb protsesside komplekt (nt reuseport, sd_listen_fds)

Võrguühenduse vaatlemine - python skript

TODO

sd_listen_fds - sshd

sd_listen_fds (sd nagu systemd) nähtus võimaldab nt sellist asjakorraldust, mis toimub Ubuntu v. 24.04 operatsioonisüsteemis vaikimisi sshd serveriga

  • kui keegi pole sshd abil süsteemi sisse loginud, siis port 22 peal kuulab systemd komplekti kuuluva programm 'init' (analoogne kunagise xinitd vms nn internet superserveriga) - ssh.socket unit juhtimisel
  • kui keegi pole sshd kaudu süsteemi sisse loginud, siis sshd protsesse süsteemis ei töötagi veel (arvutusressusi kokkuhoiu eesmärgil)
  • kui toimub esimene pöördumine, siis systemd käivitab ssh.socket unit abil teise system unit'i ssh.service unit abil sshd protsessi enda kusjuures andes üle listen tegevusega seotud failideskriptori (see 9999 port nähtus ei puutu otseselt asjasse, see on üks nö trikk mille abil saab süsteemi eemalt sisse logida minnes mööda tavalisest, st vastasel korral ei saaks süsteemi hästi vaadelda)
root@zabbix-pub-01:/# netstat -anpt
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
..
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1/init
tcp6       0      0 :::22                   :::*                    LISTEN      1/init
...

tcp6       0      0 192.168.10.193:9999     192.168.10.156:59522    ESTABLISHED 1/init


root@zabbix-pub-01:/# ps -ef | egrep "init|ssh"
UID           PID    PPID  C STIME  TTY          TIME CMD
root           1       0   8 17:06  ?        00:00:01 /sbin/init

kus

  • nn bootshell viguriga (vt TODO) on nö kõrvalt neutraalselt sisse logitud

Peale sisselogimist

root@zabbix-pub-01:/# netstat -anpt
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1/init
tcp        0      0 192.168.10.193:22       192.168.10.156:60079    ESTABLISHED 1037/sshd: root@pts
tcp6       0      0 :::22                   :::*                    LISTEN      1/init
tcp6       0      0 :::9999                 :::*                    LISTEN      1/init
tcp6       0      0 192.168.10.193:9999     192.168.10.156:59522    ESTABLISHED 1/init


root@zabbix-pub-01:/# ps -ef | egrep "init|ssh"
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 17:06 ?        00:00:01 /sbin/init
root        1035       1  0 17:08 ?        00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups
root        1037    1035  0 17:08 ?        00:00:00 sshd: root@pts/0

kus

  • init protsess on "sshd -D ..." parent
  • "sshd -D ..." on "sshd: root@pts/0" parent
  • sisselogitud oleks on seotud protsessiga "sshd: root@pts/0"
  • kui toimub veel sisselogimisi, siis moodustub "sshd: root@pts/0" kõrvale nt "sshd: root@pts/1"
  • netstat väljund selles mõttes petab, et ta ütleb, et tema kuulab port 22, aga selleks ajaks pigem tegeleb võrgus kuulamisega juhba 'sshd -D ...' protsess

seejuures

root@zabbix-pub-01:~# lsof -n -P -p 1 | grep "TCP \*:22" | grep LISTE
COMMAND PID    USER   FD      TYPE             DEVICE     SIZE/OFF    NODE  NAME
systemd   1    root   94u     IPv4               6904      0t0        TCP   *:22 (LISTEN)
systemd   1    root   95u     IPv6               4076      0t0        TCP   *:22 (LISTEN)

root@zabbix-pub-01:~# lsof -n -P -p 1035 | grep TCP
COMMAND PID    USER   FD    TYPE         DEVICE     SIZE/OFF    NODE  NAME
sshd    1035   root   3u    IPv4           6904      0t0        TCP   *:22 (LISTEN)
sshd    1035   root   4u    IPv6           4076      0t0        TCP   *:22 (LISTEN)

st

  • init protsess ja "sshd -D .." protsess jagavad omavahel port 22 osas võrgusoketi globaalset inode'i - inet -> "sshd -D .." suunal toimus sd_listen_fds

systemd unitite poolelt töötavad selle nähtusega kaks unitit

  • ssh.socket
  • ssh.service

kus

root@zabbix-pub-01:~# systemctl status ssh.socket
● ssh.socket - OpenBSD Secure Shell server socket
     Loaded: loaded (/usr/lib/systemd/system/ssh.socket; enabled; preset: enabled)
    Drop-In: /etc/systemd/system/ssh.socket.d
             └─override.conf
     Active: active (running) since Thu 2026-05-28 17:06:29 EEST; 10min ago
   Triggers: ● ssh.service
     Listen: 0.0.0.0:22 (Stream)
             [::]:22 (Stream)
      Tasks: 0 (limit: 2115)
     Memory: 12.0K (peak: 512.0K)
        CPU: 1ms
     CGroup: /system.slice/ssh.socket

May 28 17:06:29 zabbix-pub-01 systemd[1]: Listening on ssh.socket - OpenBSD Secure Shell server socket.

root@zabbix-pub-01:~# cat /lib/systemd/system/ssh.socket
[Unit]
Description=OpenBSD Secure Shell server socket
Before=sockets.target ssh.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Socket]
ListenStream=0.0.0.0:22
ListenStream=[::]:22
BindIPv6Only=ipv6-only
Accept=no
FreeBind=yes

[Install]
WantedBy=sockets.target
RequiredBy=ssh.service
root@zabbix-pub-01:~#

kus

  • BindIPv6Only=ipv6-only - port 22 jaoks on eraldatud kaks listen soketit st tehtud kaks mälueraldist: 1. ipv4 jaoks, 2. ipv6 jaoks

ning

root@zabbix-pub-01:~# systemctl status ssh.service
● ssh.service - OpenBSD Secure Shell server
     Loaded: loaded (/usr/lib/systemd/system/ssh.service; disabled; preset: enabled)
    Drop-In: /etc/systemd/system/ssh.service.d
             └─override.conf
     Active: active (running) since Thu 2026-05-28 17:08:45 EEST; 9min ago
TriggeredBy: ● ssh.socket
       Docs: man:sshd(8)
             man:sshd_config(5)
    Process: 1034 ExecStartPre=/usr/sbin/sshd -t (code=exited, status=0/SUCCESS)
   Main PID: 1035 (sshd)
      Tasks: 1 (limit: 2115)
     Memory: 5.3M (peak: 5.9M)
        CPU: 39ms
     CGroup: /system.slice/ssh.service
             └─1035 "sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups"

May 28 17:08:45 zabbix-pub-01 systemd[1]: Starting ssh.service - OpenBSD Secure Shell server...
May 28 17:08:45 zabbix-pub-01 sshd[1035]: Server listening on 0.0.0.0 port 22.
May 28 17:08:45 zabbix-pub-01 sshd[1035]: Server listening on :: port 22.
May 28 17:08:45 zabbix-pub-01 systemd[1]: Started ssh.service - OpenBSD Secure Shell server.
May 28 17:08:45 zabbix-pub-01 sshd[1037]: Accepted publickey for root from 192.168.10.156 port 60079 ssh2: ED25519 SHA256:3cj7QCk4leNOSQJlfeUeHr6Y>
May 28 17:08:45 zabbix-pub-01 sshd[1037]: pam_unix(sshd:session): session opened for user root(uid=0) by root(uid=0)


root@zabbix-pub-01:~# cat /lib/systemd/system/ssh.service
[Unit]
Description=OpenBSD Secure Shell server
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
RuntimeDirectory=sshd
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target
Alias=sshd.service

Väited

  • ssh.socket sisaldab rida 'Accept=no' - accept syscall juures annab systemd tegevused üle init protsessilt "sshd -D ..." protsessile
  • üleandmine toimub systemd poolt vastava xxx.service abil, st antud juhul ssh.service käivitamisega
  • ssh.socket on enabletud, ssh.service iseensest ei ole enabletud - seda triggerdab ssh.socket
  • selleks, et selline nähtus toimuks, peab nägema omajagu vaeva systemd osakond, ja sshd tarkvara peab olema ka omalt poolt ettevalmistatud sd_listen_fds abil tegevuse üleandmiseks
  • tundub, et kui ssh.socket ja ssh.service on mõlemad enabletud, siis midagi hulle sellest ei juhtu, st süsteem on algkäivitumise järgselt sshd protsesside koosseisu seisukohalt samasuguses olekus nagu oleks esimene kasutaja sisse logimise järel; st init on näiliselt seotud port :22 osakonnaga ja 'sshd -D ...' sisuliselt

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_').

Väited

  • haproxy - kasutatakse vaikimisi
  • nginx - vaikimisi ei kasutata, aga soovitatakse kasutada jõudluse suurendamise eesmärgil
  • zabbix-agent2 - ei ole võimalik kasutada

Python skript

Illustratsiooniks reuseport omadust kasutav 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()

kus

  • TODO

Väited

  • sellist skripti saab käivitada mitu eksemplari ühes arvutis - kõik kuulavad ja teenindavad samal pordil

Nginx ja haproxy

reuseport nähtust saab illustreerida käivitades samal pordil nginx ja haproxy protsessid, haproxy seadistus

root@zabbix-pub-01:~# cat /etc/haproxy/haproxy.cfg
global
    log /dev/log local0
    chroot /var/lib/haproxy
    user haproxy
    group haproxy
    daemon
#    no-reuseport
    nbthread 1

    # Modern HAProxy thread configuration:
    # This automatically spawns workers matching your CPU count.
    # To strictly witness the kernel routing traffic across different internal
    # buckets, HAProxy pairs this natively with SO_REUSEPORT by default.
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners

defaults
    log     global
    mode    http
    option  httplog
    option  dontlognull
    timeout connect 5000
    timeout client  50000
    timeout server  50000

# =====================================================================
# THE EXPERIMENTAL REUSEPORT FRONTEND
# =====================================================================
frontend reuseport_experiment
    bind 0.0.0.0:80

    # We will use HAProxy's internal sample fetches to append the
    # handling Thread ID directly into a custom HTTP response header!
    http-after-response set-header X-Handling-Thread "%[thread]"

    default_backend echo_back

backend echo_back
    # A simple trick: We use HAProxy's internal stats app to act as an
    # instant mock HTTP responder, so you don't need a backend server running.
    http-request return status 200 content-type "text/plain" string "Hello from HAProxy! Connection processed successfully.\n"

ning nginx seadistus

root@zabbix-pub-01:~# cat /etc/nginx/sites-enabled/default
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
# https://www.nginx.com/resources/wiki/start/
# https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
# https://wiki.debian.org/Nginx/DirectoryStructure
#
# In most cases, administrators will remove this file from sites-enabled/ and
# leave it as reference inside of sites-available where it will continue to be
# updated by the nginx packaging team.
#
# This file will automatically load configuration files provided by other
# applications, such as Drupal or Wordpress. These applications will be made
# available underneath a path with that package name, such as /drupal8.
#
# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples.
##

# Default server configuration
#
server {
	listen 80 reuseport;
#	listen [::]:8082 reuseport;

	root /var/www/html;

	# Add index.php to the list if you are using PHP
	index index.html index.htm index.nginx-debian.html;

	server_name _;

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}
}

tulemus paistab serveris välja

root@zabbix-pub-01:~# netstat -lnpt | grep :80
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1520/nginx: master
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1513/haproxy

root@zabbix-pub-01:~# lsof -n -P -p 1513 | grep TCP
haproxy 1513 haproxy    9u     IPv4              12976      0t0    TCP *:80 (LISTEN)

root@zabbix-pub-01:~# lsof -n -P -p 1520 | grep TCP
nginx   1520 root    5u  IPv4              16719      0t0    TCP *:80 (LISTEN)
  • erinevad listen socket inode numbrid
  • ei ole üksteisest põlvnevat suhet

ja kliendi poolt vaadates

root@zabbix-01:~# curl http://192.168.10.193/
Hello from HAProxy! Connection processed successfully.
root@zabbix-01:~# curl http://192.168.10.193/
Hello from HAProxy! Connection processed successfully.
root@zabbix-01:~# curl http://192.168.10.193/
Welcome to nginx!
root@zabbix-01:~# curl http://192.168.10.193/
Hello from HAProxy! Connection processed successfully.
root@zabbix-01:~# curl http://192.168.10.193/
Welcome to nginx!
root@zabbix-01:~# curl http://192.168.10.193/
Welcome to nginx!

Väited

  • nö load balancing toimub kerneli poolt tehtud hash'ing vms abil (seda võiks saada üle kirjutada eBPF programmiga tõenäoliselt)

Turvalisus

Väidetavalt ei ole reuseport realistlik ründevektor kuigi tal võiks teoreetiliselt olla potentsiaali

  • ta on populaarne interneti ühendatud infosüsteemi komponendi puhul (nginx, haproxy, apache, nsd, unbound)
  • ta võimaldab nö ausa teenust pakkuva pordi külge sokutada midagi, ja teenuse ees olev tulemüür laseb liikluse ilmselt läbi

Ruuter

Väited

  • ruuter puhul ei ole tegevused seotud arvutis töötava protsessiga
  • ruuteris võrgus liikuvad andmed ei lahku kernel-space'ist

af_packet kasutamise näide

af_packet kihis töötamine on kõige madalam nö ausal viisil võrgu kasutamine st user-space'ist (edasi tuleks otse kernelis toimetada, nt kerneli mooduli abil, ebpf programmiga vms). Looduses esinevad näited

  • dhcp
  • synology assistant
  • mikrotik winbox ilma ip aadressita kliendiga st wifi router vms seadmega

Väited

  • töötab madalamas kihis kui nö tavalised paketifiltri tulemüürid (iptables, nft), st nendega ei saa takistada af_packet viisil koheldavat liiklust (ei kliendi ega serveri poolel)

Server

root@ph-minio-01:~# cat packet_server.py
import socket
import struct

def run_server():
    # 0x0003 tells the kernel to capture ALL incoming protocol frames
    ETH_P_ALL = 0x0003

    # Create the Layer 2 AF_PACKET socket
    # socket.htons converts the protocol integer to network byte order
    server_sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))

    # Bind the socket strictly to the loopback interface
    server_sock.bind(("lo", 0))

    print("Layer 2 AF_PACKET Server listening on interface 'lo'...")

    while True:
        # Receive the raw Ethernet frame (buffer size 65535 bytes)
        raw_frame, addr = server_sock.recvfrom(65535)

        # An Ethernet header is exactly 14 bytes:
        # Destination MAC (6 bytes) + Source MAC (6 bytes) + EtherType (2 bytes)
        eth_header = raw_frame[:14]
        payload = raw_frame[14:]

        # Unpack the 14-byte header using the struct module:
        # !6s6sH means: Network order, 6 bytes, 6 bytes, unsigned short (2 bytes)
        dest_mac, src_mac, eth_type = struct.unpack("!6s6sH", eth_header)

        # Custom EtherType check (We'll use 0x7A7A for our custom test app)
        if eth_type == 0x7A7A:
            print("\n--- [New Raw Frame Captured] ---")
            # Convert MAC address bytes to human-readable hex strings
            print(f"Destination MAC : {dest_mac.hex(':')}")
            print(f"Source MAC      : {src_mac.hex(':')}")
            print(f"EtherType (Hex) : {hex(eth_type)}")
            try:
                print(f"Payload Data    : {payload.decode('utf-8')}")
            except UnicodeDecodeError:
                print(f"Payload Data    : {payload}")

if __name__ == "__main__":
    run_server()

klient

root@ph-minio-01:~# cat packet_client.py
import socket
import struct

def run_client():
    # Create the Layer 2 AF_PACKET socket
    client_sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)

    # Bind to the loopback interface
    client_sock.bind(("lo", 0))

    # Define our raw addresses
    # Because it's loopback ('lo'), the MAC addresses are always 6 bytes of 0x00
    LOOPBACK_MAC = b'\x00\x00\x00\x00\x00\x00'

    dest_mac = LOOPBACK_MAC
    src_mac = LOOPBACK_MAC

    # Define a custom EtherType protocol ID (0x7A7A).
    # Standard values are things like 0x0800 (IPv4) or 0x86DD (IPv6).
    # Using a custom ID guarantees the Linux kernel's internal TCP/IP stack
    # will ignore our packet, leaving it entirely for our server script to read!
    eth_type = 0x7A7A

    # Craft the payload text
    message = "Hello from the raw AF_PACKET layer!"
    payload = message.encode('utf-8')

    # Manually assemble the 14-byte Ethernet Header
    # !6s6sH = Network byte order, 6-byte string, 6-byte string, unsigned short integer
    eth_header = struct.pack("!6s6sH", dest_mac, src_mac, eth_type)

    # Combine the header and payload into one single raw binary frame
    full_frame = eth_header + payload

    print(f"Sending raw frame ({len(full_frame)} bytes) directly onto 'lo'...")

    # Inject the frame onto the wire
    client_sock.send(full_frame)
    print("Frame injected successfully.")

if __name__ == "__main__":
    run_client()

Kasutamine

root@ph-minio-01:~# python3 packet_client.py
Sending raw frame (49 bytes) directly onto 'lo'...
Frame injected successfully.
root@ph-minio-01:~#

root@ph-minio-01:~# tcpdump -ni lo
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
18:08:12.071780 00:00:00:00:00:00 > 00:00:00:00:00:00, ethertype Unknown (0x7a7a), length 49:
	0x0000:  4865 6c6c 6f20 6672 6f6d 2074 6865 2072  Hello.from.the.r
	0x0010:  6177 2041 465f 5041 434b 4554 206c 6179  aw.AF_PACKET.lay
	0x0020:  6572 21                                  er!

Süsteemist sellisel viisil töötavad server programmi on keeruline avastada, nt tavaline 'netstat -lnp' ei näita, aga nt ss programm näitab

Netid       State        Recv-Q       Send-Q              Local Address:Port               Peer Address:Port       Process
p_raw       UNCONN       0            0                               *:lo                             *            users:(("python3",pid=2283,fd=3))

ning

root@ph-minio-01:~# lsof -n -P -p 2283
COMMAND  PID USER  FD   TYPE DEVICE SIZE/OFF   NODE NAME
python3 2283 root cwd    DIR  253,1     4096 130524 /root
python3 2283 root rtd    DIR  253,1     4096      2 /
python3 2283 root txt    REG  253,1  6828688  21266 /usr/bin/python3.13
python3 2283 root mem    REG  253,1  3063024   8431 /usr/lib/locale/locale-archive
python3 2283 root mem    REG  253,1    27028  33448 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
python3 2283 root mem    REG  253,1  1995216  33459 /usr/lib/x86_64-linux-gnu/libc.so.6
python3 2283 root mem    REG  253,1   178272  20782 /usr/lib/x86_64-linux-gnu/libexpat.so.1.10.2
python3 2283 root mem    REG  253,1   125376   2298 /usr/lib/x86_64-linux-gnu/libz.so.1.3.1
python3 2283 root mem    REG  253,1   977112  33462 /usr/lib/x86_64-linux-gnu/libm.so.6
python3 2283 root mem    REG  253,1   225672  33456 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
python3 2283 root   0u   CHR  136,1      0t0      4 /dev/pts/1
python3 2283 root   1u   CHR  136,1      0t0      4 /dev/pts/1
python3 2283 root   2u   CHR  136,1      0t0      4 /dev/pts/1
python3 2283 root   3u  pack  72032      0t0    ALL type=SOCK_RAW

systemd kasutamine network-proxy rollis

Tööpõhimõte

  • kasutatakse ühte võrgu-neutraalset python skripti, mis ei tea ise võrgust mitte midagi, tema töötab read-write abstraktsiooni tasemel
  • võrk tuuakse kohale inet, unix socket abil - võrguklient ühendatakse kokku skripti read-write omadustega stdin, stdout abil

Võrgu-neutraalne python skript

Kasutada on üks pyhthon nö võrgu-neutraalne skript /usr/local/bin/raw_webserver.py, mis teeb read ja write tegevusi, tavalisel viisil

root@zabbix-pub-01:~# cat /usr/local/bin/raw_webserver.py
#!/usr/bin/env python3
import sys
import time

def main():
    # Keep an active buffer array for incoming chunks
    request_bytes = b""

    # Read the stream line-by-line from stdin
    while True:
        line = sys.stdin.buffer.readline()
        if not line:
            break  # Client disconnected

        request_bytes += line

        # An empty HTTP line (\r\n or \n) marks the absolute end of HTTP headers.
        # The moment we see this, we MUST break out of reading, or we deadlock!
        if line == b"\r\n" or line == b"\n":
            break

    if not request_bytes:
        sys.exit(0)

    # Log the first line to systemd journal for debugging
    request_text = request_bytes.decode('utf-8', errors='ignore')
    lines = request_text.split('\r\n')
    if lines:
        print(f"Received Request: {lines[0]}", file=sys.stderr)

    # Manually build the HTML response payload
    html_content = (
        "<html><body>"
        "<h1>Hello from the Strict Read-Write Border!</h1>"
        "<p>Systemd Socket Activation is fully online and functioning.</p>"
        "</body></html>"
    )

    http_response = (
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=utf-8\r\n"
        f"Content-Length: {len(html_content)}\r\n"
        "Connection: close\r\n"
        "\r\n"
        f"{html_content}"
    )

    # Write back to stdout (the socket) and flush the buffers out to the wire
    time.sleep(40)
    sys.stdout.buffer.write(http_response.encode('utf-8'))
    sys.stdout.buffer.flush()

if __name__ == "__main__":
    main()

ning lokaalselt käivitades

root@zabbix-pub-01:~# echo "sisend" | /usr/local/bin/raw_webserver.py
Received Request: sisend

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 140
Connection: close

<html><body><h1>Hello from the Strict Read-Write Border!</h1><p>Systemd Socket Activation is fully online and functioning.</p></body></html>
root@zabbix-pub-01:~#

The Shared Systemd Service Unit

Shared systemd service unit põhimõtteliselt võiks olla kõigi variatsioonide peale üks st ühe universaalse nimega, millegipärast sedasi ei õnnestunud praktiliselt

  • /etc/systemd/system/raw-webserver-tcp@.service
  • /etc/systemd/system/raw-webserver-unix@.service
  • /etc/systemd/system/raw-webserver-vsock@.service
root@zabbix-pub-01:~# cat /etc/systemd/system/raw-webserver-tcp@.service
[Unit]
Description=Protocol-Blind Web Server Instance (Per-Connection)
After=network.target

[Service]
ExecStart=/usr/local/bin/raw_webserver.py
StandardInput=socket
StandardOutput=socket
StandardError=journal
# Automatically terminate the process when the read/write loop completes
Type=oneshot

raw-webserver-tcp

Esitatud võrgu-neutraane üldine pyhton skript ühendatakse systemd

root@zabbix-pub-01:~# cat /etc/systemd/system/raw-webserver-tcp.socket
[Unit]
Description=TCP/IP Socket for Border Web Server

[Socket]
# Bind to port 1044 on all available network cards (IPv4 and IPv6)
ListenStream=1044
# Accept=yes spawns a unique instance of our @.service file per network connection
Accept=yes

[Install]
WantedBy=sockets.target

Aktiveerimine

root@zabbix-pub-01:~# systemctl enable --now raw-webserver-tcp.socket

ja kasutamine

root@zabbix-pub-01:~# systemctl | grep raw-webser
  raw-webserver-tcp@6-192.168.10.193:1044-192.168.10.194:36804.service                                         loaded activating start     start Protocol-Blind Web Server Instance (Per-Connection) (192.168.10.194:36804)
  system-raw\x2dwebserver.slice                                                                                loaded active     active          Slice /system/raw-webserver
  system-raw\x2dwebserver\x2dtcp.slice                                                                         loaded active     active          Slice /system/raw-webserver-tcp
  raw-webserver-tcp.socket                                                                                     loaded active     listening       TCP/IP Socket for Border Web Server

root@zabbix-pub-01:~# systemctl status raw-webserver-tcp@6-192.168.10.193:1044-192.168.10.194:36804.service
● raw-webserver-tcp@6-192.168.10.193:1044-192.168.10.194:36804.service - Protocol-Blind Web Server Instance (Per-Connection) (192.168.10.194:36804)
     Loaded: loaded (/etc/systemd/system/raw-webserver-tcp@.service; static)
     Active: activating (start) since Sun 2026-05-31 19:46:42 EEST; 15s ago
TriggeredBy: ● raw-webserver-tcp.socket
   Main PID: 13900 (python3)
      Tasks: 1 (limit: 2115)
     Memory: 4.2M (peak: 4.4M)
        CPU: 17ms
     CGroup: /system.slice/system-raw\x2dwebserver\x2dtcp.slice/raw-webserver-tcp@6-192.168.10.193:1044-192.168.10.194:36804.service
             └─13900 python3 /usr/local/bin/raw_webserver.py

Klient pöördub

# curl -v http://192.168.10.193:1044/

Statistika

May 31 19:46:42 zabbix-pub-01 systemd[1]: Starting raw-webserver-tcp@6-192.168.10.193:1044-192.168.10.194:36804.service - Protocol-Blind Web Server Instance (Per-Connection) (192.168.10.194:36804)...
May 31 19:46:42 zabbix-pub-01 raw_webserver.py[13900]: Received Request: GET / HTTP/1.1

root@zabbix-pub-01:~# netstat -anpt | grep 1044
tcp6       0      0 :::1044                 :::*                    LISTEN      1/init
tcp6       0      0 192.168.10.193:1044     192.168.10.194:36804    ESTABLISHED 1/init

root@zabbix-pub-01:~# lsof -n -P -p 1 | grep :1044
systemd   1 root   72u     IPv6            1678876      0t0        TCP 192.168.10.193:1044->192.168.10.194:36804 (ESTABLISHED)
systemd   1 root   80u     IPv6            1668028      0t0        TCP *:1044 (LISTEN)

kus

  • kasutaja pöördumisel systemd tekitab 'listening socket' eeskujul 'active connection socket'-i ('establised')
  • kui mitu klienti pöörduvad, siis tekib süsteemi mitu .py skripti töötavas olekus

raw-webserver-unix

Tekitada universaalne service shared fail (sama /etc/systemd/system/raw-webserver-tcp.socket sisuga)

/etc/systemd/system/raw-webserver-unix@.service

ning

# cat /etc/systemd/system/raw-webserver-unix.socket
[Unit]
Description=Unix Domain Socket for Border Web Server

[Socket]
# ListenStream with an absolute path instructs systemd to create an AF_UNIX socket file
ListenStream=/run/raw_webserver.sock
# Accept=yes spawns a unique instance of our @.service file per local file connection
Accept=yes

[Install]
WantedBy=sockets.target

Aktiveerimiseks

# systemctl daemon-reload
# systemctl enable --now raw-webserver-unix.socket

ja pöördumiseks samas arvutis

root@zabbix-pub-01:~# time curl --unix-socket /run/raw_webserver.sock http://localhost/
<html><body><h1>Hello from the Strict Read-Write Border!</h1><p>Systemd Socket Activation is fully online and functioning.</p></body></html>

Soket paistab nii, eelmise variandi oma on samuti nimekirjas

root@zabbix-pub-01:~# systemctl list-sockets | grep raw
[::]:1044                             raw-webserver-tcp.socket        -
/run/raw_webserver.sock               raw-webserver-unix.socket       -

Päringu tegemise ajal

root@zabbix-pub-01:~# ss -a -p -f unix | grep raw
u_str LISTEN 0      4096                                  /run/raw_webserver.sock 1727463            * 0       users:(("systemd",pid=1,fd=79))
u_str ESTAB  0      0                                     /run/raw_webserver.sock 1736013            * 1679109 users:(("python3",pid=14454,fd=1),("python3",pid=14454,fd=0),("systemd",pid=1,fd=10))

kus

  • unix soketil on ka listen ja established

raw-webserver-vsock

Tekitada universaalne service shared fail (sama /etc/systemd/system/raw-webserver-tcp.socket sisuga)

/etc/systemd/system/raw-webserver-vsock@.service

ning

# cat /etc/systemd/system/raw-webserver-vsock.socket
[Unit]
Description=VSOCK Memory Socket for Border Web Server

[Socket]
# Bind strictly to the AF_VSOCK address family on port 1044
ListenStream=vsock::1044
Accept=yes

[Install]
WantedBy=sockets.target

Aktiveerimiseks

# systemctl daemon-reload
# systemctl enable --now raw-webserver-vsock.socket

Kliendina pöördumiseks sobib nt socat programmi kasutada, küllap curl saab ka

root@pve-svc-02:~# echo "GET /" | socat - VSOCK-CONNECT:50194:1044
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 140
Connection: close

<html><body><h1>Hello from the Strict Read-Write Border!</h1><p>Systemd Socket Activation is fully online and functioning.</p></body></html>

Paistab

root@zabbix-pub-01:~# systemctl --type=socket | grep raw-w
  raw-webserver-tcp.socket        loaded active listening TCP/IP Socket for Border Web Server
  raw-webserver-unix.socket       loaded active listening Unix Domain Socket for Border Web Server
  raw-webserver-vsock.socket      loaded active listening VSOCK Memory Socket for Border Web Server


Misc

Ebaõnnestumiste eemaldamine

root@zabbix-pub-01:~# systemctl | grep raw
● raw-webserver-vsock@0-50194:1044-2:3887152079.service  loaded failed failed    Protocol-Blind Web Server Instance (Per-Connection) (vsock:2:3887152079)
● raw-webserver-vsock@1-50194:1044-2:3887152080.service  loaded failed failed    Protocol-Blind Web Server Instance (Per-Connection) (vsock:2:3887152080)
● raw-webserver-vsock@2-50194:1044-2:3887152081.service  loaded failed failed    Protocol-Blind Web Server Instance (Per-Connection) (vsock:2:3887152081)
● raw-webserver-vsock@3-50194:1044-2:3887152082.service  loaded failed failed    Protocol-Blind Web Server Instance (Per-Connection) (vsock:2:3887152082)

root@zabbix-pub-01:~# systemctl reset-failed 'raw-webserver-vsock@*'

Kasulikud lisamaterjalid