Przejdź do treści
Lekcja 72 z 117

Kurs › Moduł 8 › Lekcja 72

Porty: kto słucha i dlaczego

⏱ ok. 10 min czytania· moduł 8: Sieć

Po tej lekcji: kursant sprawdza, które porty nasłuchują na serwerze, jaki proces je trzyma i na jakim adresie, używając ss -tulpn, lsof -i i fuser, i rozumie różnicę między 127.0.0.1 a 0.0.0.0.

Wstęp

Instalujesz Apache'a, uruchamiasz i dostajesz to:

(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80
no listening sockets available, shutting down

Adres zajęty. Ktoś już siedzi przy okienku 80. Kto? Nie wiesz. Kolega przysięga, że nic nie instalował. Kolega zawsze przysięga.

A druga scena z tego samego dnia: baza danych działa, systemctl status świeci się na zielono, a aplikacja z drugiego serwera dostaje Connection refused. Baza działa, ale nie dla niego.

Oba problemy rozwiążesz jedną komendą. Po tej lekcji będziesz ją wpisywał odruchowo, jak ls.

Kto siedzi przy okienku

Urząd, okienka, numery. To już znasz. Dziś nowe słowo: nasłuchiwanie. Program, który chce obsługiwać klientów, siada przy okienku z numerem i czeka. Serwer WWW przy 80 i 443. SSH przy 22. Baza danych MariaDB przy 3306. Po angielsku: program słucha na porcie, listen.

Jedno okienko, jeden urzędnik. Jeśli przy 80 już ktoś siedzi, drugi program nie usiądzie. Stąd Address already in use z haka.

I drugi szczegół, który rozwiązuje zagadkę bazy danych. Urzędnik siada nie tylko przy okienku o numerze, ale i przy konkretnych drzwiach budynku. Budynek ma wejście główne od ulicy, czyli kartę sieciową z adresem 192.168.1.50. I ma wejście od zaplecza, dla pracowników, czyli 127.0.0.1. Okienko na zapleczu obsługuje tylko swoich.

ss -tulpn — lista okienek

ss to następca netstat, z tego samego pakietu iproute2, co ip. Pokazuje gniazda sieciowe, po angielsku sockets. Najważniejsze zaklęcie admina sieciowego brzmi:

sudo ss -tulpn

Pięć liter, łatwo zapamiętać. Niektórzy mówią „tulpan”, jak kwiatek. -t TCP, -u UDP, -l tylko nasłuchujące, -p z procesem, -n liczby zamiast nazw. Na serwerze z mojego laboratorium:

Netid State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
udp   UNCONN 0      0            0.0.0.0:5514      0.0.0.0:*    users:(("nc",pid=20755,fd=3))
tcp   LISTEN 0      5            0.0.0.0:80        0.0.0.0:*    users:(("python3",pid=31332,fd=3))
tcp   LISTEN 0      128          0.0.0.0:22        0.0.0.0:*    users:(("sshd",pid=20752,fd=3))
tcp   LISTEN 0      5          127.0.0.1:8080      0.0.0.0:*    users:(("python3",pid=31333,fd=3))

Czytamy kolumny:

  • Netid — TCP czy UDP. Rozmowa telefoniczna czy pocztówka.
  • State — dla TCP LISTEN, czyli „czekam na klientów”. Dla UDP UNCONN, bo pocztówki nie mają połączeń. UDP po prostu leży przy skrzynce i czeka, co wpadnie.
  • Local Address:Port — przy których drzwiach i którym okienku. To jest najważniejsza kolumna dzisiejszej lekcji.
  • Peer Address:Port — z kim rozmawia. Przy nasłuchu gwiazdka, bo z każdym, kto przyjdzie.
  • Process — kto siedzi przy okienku: nazwa programu i numer PID z modułu o procesach.
  • Send-Q przy LISTEN — jak długa może być kolejka czekających na obsługę. Recv-Q — ile osób stoi w niej teraz.

Czyli: SSH słucha na 22, serwer WWW na 80, coś na UDP 5514, a na 8080 drugi serwer WWW. Ale tylko na 127.0.0.1. Zapamiętaj, za chwilę to będzie ważne.

Ważne: bez sudo kolumna Process będzie pusta przy cudzych procesach. Tak wygląda to samo jako zwykły użytkownik:

State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      5            0.0.0.0:80        0.0.0.0:*
LISTEN 0      128          0.0.0.0:22        0.0.0.0:*
LISTEN 0      5          127.0.0.1:8080      0.0.0.0:*

Porty widać, urzędników nie. Nie panikuj, że „nikt nie słucha”. Dodaj sudo.

Sam port cię interesuje? Filtr:

sudo ss -tlpn 'sport = :22'
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      128          0.0.0.0:22        0.0.0.0:*    users:(("sshd",pid=20752,fd=3))

A bez -l widzisz rozmowy, które trwają teraz. Ktoś z klienta jest podłączony do strony:

sudo ss -tnp state established
Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
0      0      192.168.50.10:80   192.168.50.20:51438 users:(("python3",pid=31332,fd=4))

Nasz port 80, klient 192.168.50.20 ze swojego losowego okienka 51438. A ss -tan pokaże wszystkie stany naraz, w tym TIME-WAIT, czyli połączenia, które się skończyły i chwilę jeszcze sprzątają. Jak kelner, który po wyjściu gościa jeszcze zbiera talerze.

127.0.0.1 kontra 0.0.0.0

Teraz najważniejsza lekcja lekcji.

0.0.0.0 w kolumnie Local Address znaczy: „słucham na wszystkich adresach tego komputera”. Przy wszystkich drzwiach naraz. Ktoś przyjdzie od ulicy, obsłużę. Ktoś od zaplecza, też obsłużę.

127.0.0.1 znaczy: „słucham tylko na zapleczu”. Obsługuję programy z tego samego komputera. Z zewnątrz nikt mnie nie zobaczy, nawet jeśli zapora jest szeroko otwarta.

Sprawdzam z drugiej maszyny, klienta:

nc -zv 192.168.50.10 80
nc -zv 192.168.50.10 8080
curl http://192.168.50.10:8080/
Connection to 192.168.50.10 80 port [tcp/http] succeeded!
nc: connect to 192.168.50.10 port 8080 (tcp) failed: Connection refused
curl: (7) Failed to connect to 192.168.50.10 port 8080 after 0 ms: Couldn't connect to server

A na samym serwerze:

curl http://127.0.0.1:8080/
Witaj w labie

Od środka działa, z zewnątrz Connection refused. To jest zagadka bazy danych z haka. Na Ubuntu w pakiecie MariaDB plik /etc/mysql/mariadb.conf.d/50-server.cnf ma domyślnie bind-address = 127.0.0.1. Baza działa, ale tylko dla swoich. I bardzo dobrze. Bazy danych, panele administracyjne, pamięci podręczne w stylu Redisa nie powinny wystawiać się na świat bez potrzeby. Okienko na zapleczu to najlepsza ochrona, jaka istnieje: nie da się zaatakować okienka, którego z ulicy nie widać.

Chcesz jednak, żeby baza była widoczna z sieci? Zmieniasz adres nasłuchu w konfiguracji programu. W MariaDB to bind-address. W SSH ListenAddress w /etc/ssh/sshd_config. W serwerach WWW dyrektywa Listen. Potem restart usługi i sprawdzenie ss -tlpn. I od razu zastanów się, kto powinien mieć dostęp, bo to już robota dla zapory z lekcji 73.

Na prawdziwych serwerach zobaczysz jeszcze dwa zapisy:

  • [::]:22 — to IPv6-owe „wszystkie adresy”, odpowiednik 0.0.0.0.
  • *:80 — gwiazdka, też „wszystkie adresy”. Tak często pokazuje się Apache, który słucha naraz na IPv4 i IPv6.

I jeszcze trzeci wariant: konkretny adres karty, na przykład 192.168.1.50:3306. Wtedy program słucha tylko przy tych jednych drzwiach od ulicy.

lsof i fuser — drugie zdanie

lsof znaczy „list open files”. Pamiętasz „wszystko jest plikiem”? Gniazdo sieciowe też. Więc lsof z flagą -i pokazuje otwarte gniazda:

sudo lsof -i :80
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
python3 31332 root    3u  IPv4 264945      0t0  TCP *:http (LISTEN)

Zauważ *:http. lsof sam zamienił 80 na nazwę z /etc/services i adres na gwiazdkę. Wszystkie nasłuchujące TCP, z liczbami zamiast nazw:

sudo lsof -nP -iTCP -sTCP:LISTEN
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
sshd    20752 root    3u  IPv4  53187      0t0  TCP *:22 (LISTEN)
python3 31332 root    3u  IPv4 264945      0t0  TCP *:80 (LISTEN)
python3 31333 root    3u  IPv4 264948      0t0  TCP 127.0.0.1:8080 (LISTEN)

-n bez nazw komputerów, -P bez nazw portów. Kolumna USER to właściciel procesu, czego ss wprost nie pokazuje.

fuser odpowiada na pytanie „kto używa tego portu” najkrócej:

sudo fuser -v 80/tcp
                     USER        PID ACCESS COMMAND
80/tcp:              root      31332 F.... python3

I ma flagę, która wygląda niewinnie: -k, kill. sudo fuser -k 80/tcp zabija wszystko, co trzyma port 80. Bez pytania. To jak wywalenie z okienka urzędnika razem z krzesłem. Przydatne, ale najpierw -v, żeby wiedzieć, kogo wyrzucasz. Bo jeśli to był sshd, to właśnie wyrzuciłeś siebie.

[Ubuntu] [RHEL] ss jest wszędzie, bo to iproute2. lsof i fuser czasem trzeba doinstalować: lsof to pakiet lsof na obu rodzinach, fuser pochodzi z pakietu psmisc.

/etc/services — tablica w holu, nie kontrola dostępu

Skąd lsof wiedział, że 80 to http? Z pliku /etc/services z lekcji 68.

getent services 8080
http-alt              8080/tcp webcache

To tylko tłumaczenie numerów na nazwy. Wpis w /etc/services nie otwiera portu, nie zamyka go i nie mówi, że cokolwiek tam działa. To tablica w holu z napisem „okienko 22: SSH”. Może wisieć przez lata, chociaż przy okienku od dawna nikt nie siedzi. Prawdę mówi tylko ss.

Jak to działa pod spodem

Program, który chce słuchać, robi trzy rzeczy, prosząc o nie jądro:

  1. socket() — „daj mi gniazdo”. Dostaje deskryptor, numerek jak plik. To to fd=3 w ss.
  2. bind() — „przypisz mi adres i port”. Tu wybiera 0.0.0.0 albo 127.0.0.1. Tu też pada Address already in use, po angielsku EADDRINUSE, błąd numer 98. Ta liczba z haka.
  3. listen() — „zaczynam czekać na klientów”. Od tej chwili gniazdo ma stan LISTEN.

Całą tę listę trzyma jądro. ss pyta je o nią przez netlink, tak jak ip. Stary netstat czytał pliki w /proc/net/, na przykład /proc/net/tcp:

  sl  local_address rem_address   st tx_queue rx_queue ...
   0: 00000000:0016 00000000:0000 0A 00000000:00000000 ...

Adresy szesnastkowo. 0016 to 22, 0A to stan LISTEN. Da się czytać, ale tylko jeśli ktoś bardzo lubi szesnastkowe zagadki. ss przy dużej liczbie połączeń jest też dużo szybszy.

Jeszcze jedno: porty od 0 do 1023 to porty uprzywilejowane. Zwykły użytkownik nie usiądzie przy okienku 80, to wymaga roota albo specjalnego uprawnienia. Granicę widać w ustawieniu jądra:

sysctl net.ipv4.ip_unprivileged_port_start
net.ipv4.ip_unprivileged_port_start = 1024

Dlatego programy deweloperskie lubią porty typu 3000, 5000 i 8080. Nie trzeba roota.

Ściąga flag

KomendaFlagaCo robi
ss-t / -utylko TCP / tylko UDP
ss-ltylko nasłuchujące
ss-ppokaż proces (z sudo dla cudzych)
ss-nliczby zamiast nazw portów
ss-awszystkie stany naraz
ss'sport = :22'filtr po porcie lokalnym
ssstate establishedtylko trwające połączenia
ss-spodsumowanie, ile czego
lsof-i :PORTkto trzyma port
lsof-iTCP -sTCP:LISTENwszystkie nasłuchujące TCP
lsof-n / -Pbez nazw komputerów / bez nazw portów
fuser-v PORT/tcpkto używa portu, z użytkownikiem
fuser-k PORT/tcpzabija procesy na porcie (ostrożnie!)
getentservices PORTnazwa usługi z /etc/services

⚠ Typowy błąd

Wracamy do Apache'a z haka:

(98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80

Pierwszy odruch nowicjusza: restartować Apache'a w kółko. Albo restartować serwer. Pięć razy. Jakby okienko miało się zwolnić od tego, że będziesz częściej pytać.

Odruch admina:

sudo ss -tlpn 'sport = :80'
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      5            0.0.0.0:80        0.0.0.0:*    users:(("python3",pid=31332,fd=3))

Jest winowajca: python3, PID 31332. Ktoś odpalił testowy serwer na porcie 80 i zapomniał. Najczęściej winowajcą jest jednak drugi serwer WWW: nginx zainstalowany obok Apache'a albo stara usługa, która startuje sama. Dalej masz wybór: zatrzymujesz tamtą usługę przez systemctl stop (i disable, żeby nie wróciła po restarcie), albo przenosisz jedną z usług na inny port. Nie zabijasz w ciemno przez fuser -k, dopóki nie wiesz, co zabijasz.

Drugi wariant tego samego błędu: usługa działa, a z zewnątrz Connection refused. Zanim zaczniesz grzebać w zaporze, sprawdź ss -tlpn. Jeśli widzisz 127.0.0.1:PORT, to zapora nie ma tu nic do rzeczy. Urzędnik siedzi na zapleczu i problem jest w konfiguracji programu.

✎ Zadanie

Sprawdź, które porty nasłuchują na Twoim serwerze, jakie procesy je trzymają i czy słuchają na wszystkich adresach, czy tylko lokalnie. Z drugiej maszyny sprawdź nc -zv, czy port 22 serwera jest osiągalny.

Pokaż rozwiązanie

Zadanie było takie: sprawdzić, kto słucha na serwerze, czy na wszystkich adresach, czy tylko lokalnie, a potem z drugiej maszyny zapukać w port 22.

sudo ss -tulpn
Netid State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
tcp   LISTEN 0      5          127.0.0.1:8080      0.0.0.0:*    users:(("python3",pid=5959,fd=3))
tcp   LISTEN 0      128          0.0.0.0:22        0.0.0.0:*    users:(("sshd",pid=20210,fd=4))
tcp   LISTEN 0      5            0.0.0.0:80        0.0.0.0:*    users:(("python3",pid=6067,fd=3))

Czytamy kolumnę Local Address:Port. 0.0.0.0:22 to SSH, który odbiera rozmowy z każdego adresu, czyli okienko otwarte na ulicę. 127.0.0.1:8080 słucha tylko lokalnie: okienko na zapleczu, do którego z zewnątrz nie dojdziesz, choćbyś stawał na głowie. Kolumna Process mówi, kto siedzi w okienku. To samo innym narzędziem:

sudo lsof -i -P -n | grep LISTEN
python3  5959 root    3u  IPv4  20179      0t0  TCP 127.0.0.1:8080 (LISTEN)
python3  6067 root    3u  IPv4  21453      0t0  TCP *:80 (LISTEN)
sshd    20210 root    4u  IPv4 124179      0t0  TCP *:22 (LISTEN)

Gwiazdka u lsof to to samo co 0.0.0.0 u ss. A teraz z drugiej maszyny:

nc -zv 10.0.0.10 22
nc -zv -w2 10.0.0.10 8080
Connection to 10.0.0.10 22 port [tcp/ssh] succeeded!
nc: connect to 10.0.0.10 port 8080 (tcp) failed: Connection refused

Port 22 odpowiada. Port 8080 odmawia, bo program słucha tylko na 127.0.0.1, więc dla świata go nie ma. Jeśli u Ciebie wyszło odwrotnie i baza danych słucha na 0.0.0.0, to gratuluję, właśnie znalazłeś pierwszy temat na dzisiaj.

Co dalej

Wiesz już, kto siedzi przy którym okienku. Teraz czas na ochroniarza, który decyduje, kogo w ogóle wpuścić do budynku. W następnej lekcji zapory: ufw na Ubuntu, firewalld ze strefami na RHEL i parę słów o nftables i iptables, które działają pod spodem.