Per il CED di un'amministrazione comunale ho installato un doppio nodo di server Linux con un meccanismo di High Availabity.
Si tratta di un sistema composto da due server Linux, identici dal punto di vista hardware e software, in cui però i servizi realmente attivi sono suddivisi tra le due macchine.
In condizioni normali, una delle due funziona come web server (pubblicato esternamente), application server ed SQL server; l'altra come controller primario di dominio (in sostituzione di un precedente Windows NT), come file server per le condivisioni e come mail server (sia locale che pubblico).
Tra le due macchine è attivo un sistema che monitorizza costantemente lo stato dei servizi su entrambi i nodi. In caso di caduta di uno dei server, il sistema provvede a lanciare tutti i servizi inattivi sull'altra macchina, assumendone anche il nome e l'IP, in modo che entrambi i nodi siano sempre visti come un unico nodo dall'esterno.
I servizi attivati sono:
- Server 1
- Apache HTTPD web server, per la pubblicazione all'esterno del sito;
- PHP per un'eventuale contenuto dinamico del sito;
- ProFTPD ftp server, per la manutenzione del sito stesso da remoto;
- Apache Tomcat servlet container, come application server per i gestionali da utilizzare internamente;
- MySQL database server, come base dati per Tomcat.
Server 2
- Server mail Postfix, per la gestione della posta interna ed esterna;
- Server imap/pop3 Imap2000, per le caselle e-mail degli utenti;
- Filtro antispam Spamassassin associato al mail server;
- Samba server, per la condivisione di cartelle comuni e come controller primario di dominio, in sostituzione di un precedente Windows NT
Ovviamente, tutti i servizi sono installati in entrambe le macchine, ma solo quelli indicati sono normalmente attivi.
Sincronizzazione dei dati
Entrambe le macchine hanno i dischi suddivisi in tre partizioni (oltre alla swap):
- /dev/hda1 con il sistema di base, gli applicativi e le necessarie librerie;
- /dev/hda2 con tutte le cartelle necessarie agli applicativi funzionanti sul server 1, dove per cartelle necessarie si intendono quelle in cui può risiedere qualsiasi dato modificabile relativo a quegli applicativi (pagine del sito, basi di dati, files di configurazione specifici, logs ecc.);
- /dev/hda3, analogamente, con tutte le cartelle necessarie agli applicativi funzionanti sul server 2 (account utente, home directories, shares, cartelle di posta, logs ecc.).
Le partizioni dati /dev/hda2 e /dev/hda3 sono esportate via NFS tra le due macchine, e vengono montate in rispettive sottocartelle di /mnt all'avvio in entrambi i sistemi. Tramite questa condivisione, i dati modificati dalle applicazioni di un server vengono replicati sulle corrispondenti partizioni dell'altro server. Si viene così a creare un mirror virtuale, in modo che i due sistemi risultino costantemente sincronizzati sia come dati che come configurazioni. La replica viene effettuata con uno script in cron che esegue una copia incrementale ogni 5 minuti tra i due nodi.
Alternativa: mirror virtuale con drbd
Un'alternativa alla soluzione precedente avrebbe potuto essere quella di utilizzare drbd (Distributed Replicated Block Device), un sistema che consente la replica istantanea tra un nodo master ed uno slave; in pratica, l'equivalente di un RAID 1 (mirror), ma con i dischi separati su due macchine diverse collegate in rete. Questo non è stato fatto per precise scelte progettuali: in drbd si prevede che vi sia un nodo master ed uno slave, in cui i servizi sono effettivamente attivi solo sul master; lo slave entra in funzione solo quando il master cade.
Nel caso in questione, invece, la richiesta precisa dell'amministrazione comunale era di bilanciare il carico di lavoro tra i due nodi, seperando i servizi attivi; la soluzione drbd non sarebbe quindi stata valida. E' comunque una via praticabile e molto efficace quando si vuole che uno dei due nodi sia effettivamente solo un server di backup.
CONTROLLO DI FAILOVER
In entrambe le macchine è installato il servizio Heartbeat. Questo demone comunica tra le due macchine tramite una sottorete dedicata, come nello schema sottostante,
In questo infobox verranno illustrati alcuni concetti sulla configurazione di un cluster load balancing, ovvero un cluster costituito da una serie di nodi posti dietro ad un bilanciatore, utilizzando il progetto Linux Virtual Server. Questo infobox ha solo lo scopo di introdurre i concetti di base di LVS, mentre i dettagli della configurazione verranno illustrati in seguito.
In una configurazione di questo tipo sono presenti diversi nodi dove e' in esecuzione il servizio che si vuole fornire, ed almeno un sistema con il ruolo di bilanciatore, che ha il compito di distribuire le richieste dei client ai diversi nodi secondo il metodo di instradamento e l'algoritmo di bilanciamento prescelto.
In particolare, la configurazione che verra' presa come riferimento e' costituita da un piccolo cluster che ho configurato qualche settimana fa ed attualmente in produzione, costituito da un bilanciatore e due nodi che svolgono funzionalita' di proxy http.
Per quanto riguarda i metodi di instradamento (forwarding) delle richieste che e' possibile utilizzare con LVS esistono tre possibilita':
- LVS-NAT (Network Address Translation): le request dai client vengono ricevute dall director sull'ip virtuale, il quale le inoltra all'IP reale del nodo. A sua volta il nodo risponde alle request inviando i pacchetti al director, che effettuera' un source nat degli stessi al fine di cambiare l'ip sorgente dall'ip del nodo fisico a quello virtuale.
In pratica il Director fa da gateway per i nodi del cluster, e tutti i pacchetti sia in entrata che in uscita passano da esso.
- LVS-DR (Direct Routing): in questo caso il Director inoltra tutte le request ricevute ai nodi del cluster, che a differenza del precedente metodo rispondono direttamente ai client. Le resposte alla request ricevute. dunque, non passano piu' per il Director che si limita ad inoltrare il traffico in entrata.
- LVS-TUN (IP Tunneling): questo metodo puo' essere utilizzato quando il director ed i nodi del cluster si trovino su reti differenti. Le request vengono ricevute dal director, incapsulate ed inoltrate ai nodi del cluster, i quali a loro volta rispondono direttamente al client.
Poiche' la configurazione presa come esempio utilizza il metodo di forwarding LVS-DR si illustra in maggiore dettaglio il suo funzionamento:
- Il client invia la request all'indirizzo IP virtuale assegnato al bilanciatore. Il pacchetto inviato avra' quindi come destinazione l'indirizzo IP virtuale ed il mac address della scheda di rete del director.
- Il director inoltra i pacchetti ricevuti ai nodi del cluster, lasciando inalterato l'indirizzo IP di destinazione e modificando il MAC address di destinazione con quello del nodo.
- Il nodo del cluster riceve quindi i pacchetti (poiche' destinati al proprio mac-address) aventi un indirizzo IP di destinazione non corrispondente a quello della propria scheda di rete. I nodi hanno infatti associato un indirizzo ip differente dall'indirizzo virtuale assegnato al director.
Servira' quindi un meccanismo per fare in modo che le request ricevute dal nodo fisico vengano effettivamente passate al servizio interessato. La documentazione disponibile propone alcuni metodi che e' possibile adottare dall'utilizzo di un alias con l'indirizzo virtuale sull'interfaccia di loopback del nodo ed un rotta statica per inoltrarvi il traffico fino all'utilizzo di iproute.
Poiche' i nodi del cluster nel mio caso erano basati su una distribuzione linux parecchio customizzata che si voleve modificare il meno possibile si e' optato per un soluzione differente, ovvero un destination nat tramite iptables: una volta ricevuto sul nodo un pacchetto con ip di destinazione uguale all'ip virtuale del director, si effettua un destination nat modificando l'ip di destinazione con l'ip reale del nodo, dove il servizio e' effettivamente in ascolto.
- Ricevuta la request il servizio risponde direttamente ai client, senza piu' passare per il director. Le risposte in oggetto avranno comunque come indirizzo IP sorgente l'IP virtuale del director: esso viene infatti modificato automaticamente nelle risposte da netfilter, poiche' ricondotte alla connessione per la quale e' stato precendentemente applicato il destination nat.
Una volta stabilito il metodo di forwarding, altro aspetto da considerare e' il paradigma di bilanciamento. In particolare il progetto linux LVS supporta i seguenti algoritmi:
- Non dinamici
Round Robin (RR)
Weighted Round Robin (WRR)
Destination Hashing
Source Hashing
- Dinamici
Least-connection (LC)
Weighted least-connection (WLC)
Shortest expected delay (SED)
Never queue (NQ)
Locality-based least-connection (LBLC)
Locality-based least-connection with replication scheduling (LBLCR)
Vista l'ampiezza dell'argomento non verranno illustrati tutti i paradigmi disponibili, ma solo quello adottato per la configurazione descritta.
In teoria, dovendo bilanciare dei proxy http il paradigma piu' indicato dovrebbe essere LBLC (Locality-Based Least-Connection).
L'algoritmo LBLC cerca di inviare tutte le request per un certo indirizzo IP di destinazione (nel caso un determinato sito web) al medesimo nodo del cluster, perche' avendo quest'ultimo funzionalita' di proxy si presume che gia' abbia in cache le risorse relative al sito in oggetto.
La prima connessione viene bilanciata con un criterio simile ad un Weighted Least Connection, mentre le successive request verso quell'indirizzo IP continueranno ad essere inoltrate allo stesso nodo finche' un altro nodo del cluster non abbia un numero di connessioni (pesate) minore della meta' di quelle del nodo in uso.
Nel mio caso l'utilizzo di questo algoritmo di scheduling, abbinato alla persistenza ed al fatto che molti client arrivavano al bilanciatore nattati da un apparato, portava ad un cluster decisamente sbilanciato con frequenti situazioni di Denial of Service sul proxy piu' carico.
Ho invece verificato un cluster piu' bilanciato ed un miglior feedback da parte degli utenti utilizzando l'algoritmo LC (Least Connection). Alla ricezione di una nuova request il bilanciatore verifica il numero di connessioni attive ed inattive sui diversi nodi al fine di determinare a chi di essi inoltrarla. In particolare, per ogni nodo, il bilanciatore calcola un valore di overhead moltiplicando le connessioni attive per 256 ed aggiungendo il numero di connessioni inattive.
Una volta ricevuta la request viene quindi inoltrata al nodo con valore di overhead piu' basso.
Un aspetto ulteriore da tenere in considerazione e' quello della persistenza, ovvero fare in modo che un client che abbia iniziato ad essere bilanciato su un nodo continui ad esserlo sul medesimo. Questo aspetto e' particolarmente importante nel caso della configurazione che ho dovuto realizzare, poiche' indispensabile per il meccanismo di content filter adottato dai proxy. Il meccanismo di content filter in oggetto prevedeva che ogni qualvolta un utente cercasse di scaricare un file di grosse dimensioni, questo venisse copiato su un sito temporaneo, scansionato tramite antivirus, e fornito all'utente un nuovo url sul sito temporaneo da cui scaricare il file controllato.
Poiche' il file da scaricare veniva riconosciuto tramite un id di sessione, il meccanismo non potrebbe funzionare se l'utente tentasse di scaricarlo dal sito temporaneo utilizzando un proxy diverso da quello che ha effettuato la richiesta originaria. Da qui la necessita' di ricorrere ad un meccanismo di persistenza, per fare in modo che i client continuino a contattare il medesimo nodo durante la sua sessione.
Per ottenere questo comportamento e' possibile impostare un timeout, il persistence timeout appunto, che indica l'arco di tempo per il quale, un client debba essere matenuto bilanciato sul medesimo nodo.
Alla prima request di un client il director impostera' quindi un timer per tenere traccia di questo timeout.
Una volta che il timer abbia raggiunto il valore zero, se la connessione e' ancora attiva esso verra' resettato ad un valore di default pari a 2 minuti. Finche' il timer in questione sara' attivo il client verra' bilanciato sul medesimo nodo.
L'utilizzo della persistenza ha pero' un effetto collaterale: forzando il bilanciamento dei client sui nodi dove e'stata instanziata la loro prima connessione aumentano le possibilita' di avere un cluster sbilanciato. Ad esempio, nonostante l'utiilizzo di paradigmi di bilanciamento dinamici si potrebbe avere uno dei nodi sovraccarico semplicemente perche' tutti i client che stiano facendo piu' traffico continuino ad essere bilanciati sul nodo in oggetto a causa della persistenza, anche se gli altri nodi dovessero comunque avere maggiori risorse a disposizione.
Questo infobox e' la prosecuzione della parte introduttiva ad LVS e descrive i vari passaggi della configurazione di un piccolo cluster che ho realizzato costituito da bilanciatore e due nodi con funzionalita' di proxy http. Per maggiori dettagli sulle scelte effettuate (instradamento, algoritmo di bilanciamento, persistenza) si rimanda all'infobox introduttivo.
Innanzitutto, non avendo avuto la possibilita' di ridondare il bilanciatore facendo due macchine in cluster ha tramite heartbeat si e' cercato di garantire quantomeno la ridondanza delle interfacc di rete, configurandole in bonding secondo la modalita' Active-Backup. Avendo gia' trattato l'argomento in un altro infobox si rimanda allo stesso per i dettagli della configurazione. Quindi eventuali riferimenti alla scheda di rete nell'infobox riguarderanno bond0 anziche' ethx.
Per prima cosa e' necessario installare i seguenti pacchetti:
apt-get install ldirectord
apt-get install ipvsadm
Ipvsadm consente di modificare la configurazione di LVS aggiungendo e rimuovendo servizi, modificando la modalita' di forwarding, paradigmi di bilanciamento, ecc. Ldirectord e' invece un demone che consente di monitorare i servizi erogati dai nodi fisici ed in base allo stato rilevato aggiungerli o rimuoverli dinamicamente al bilanciamento.
Se ad esempio uno dei due proxy dovesse spegnersi o il relativo servizio crashare, ldirectord dovrebbe accorgersi del cambio di stato (ad esempio perche' impossibilitato a connettersi alla porta dove il servizio e' in ascolto) e togliere a caldo il nodo dal bilanciamento.
Ldirectord e' personalizzabile tramite file di configurazione, nel mio caso /etc/ldirectord.conf:
# /etc/ldirectord.conf
# --------------------
# Ldirectord configuration file to configure various virtual services.
#
checktimeout=20
checkinterval=10
autoreload=yes
logfile="/var/log/ldirectord.log"
quiescent=no
virtual=10.62.1.164:8080
real=10.62.1.166:8080 gate 1
real=10.62.1.165:8080 gate 1
scheduler=lc
persistent=90
protocol=tcp
checktype=connect
checkport=8080
Le opzioni definite nel file ldirectord.conf sono le seguenti:
- checktimeout: specifica il numero di secondi che ldirectord deve attendere per completare il check di un servizio. Se il controllo non dovesse essere completato entro il numero di secondi indicato, ldirectord rimuovera' il nodo dal bilanciamento.
- checkinterval: indica l'intervallo di tempo in secondi tra i check eseguiti da ldirectord.
- autoreaload: permette di abilitare l'opzione autoreaload. In pratica ldirectord calcola un checksum md5 del file di configurazione in modo da applicare automaticamente tramite ipvsadm eventuali modifiche in caso cambi il checksum del file in oggetto.
- logfile: il file di log di ldirectord, dove verra' ad esempio registrato quando un nodo viene aggiunto o rimosso dal bilanciamento.
- quiescent: un nodo viene considerato in stato quiescent nel caso non abbia riposto entro l'arco di tempo specificato dal parametro checktimeout.
Se non si imposta questa opzione al valore no, se uno dei nodi dovesse crashare il cluster potrebbe apparire down per quei client che fossero bilanciati sul nodo in oggetto prima che esso diventasse irraggiungibile.
- virtual: specifica l'indirizzo virtuale e la porta sulla quale LVS deve accettare le connessioni per poi inoltrarle ai nodi fisici, ovvero l'IP al quale i client devono puntare.
- real: specifica l'indirizzo IP e la porta dei singoli nodi fisici, il metodo di forwarding e l'eventuale priorita' del nodo in oggetto. Questo parametro in particolare ha un sintesi simile alla seguente:
real=indirizzo_ip:porta gate|masq|ipip [weight]
Ovviamente dovremo avere un'entry di questo tipo per ogni nodo fisico presente.
Le parole chiavi gate,masq e ipip permettono di selezionare il metodo di forwarding, rispettivamente LVS-DR, LVS-NAT e LVS-TUN.
Il peso del nodo, invece, viene utilizzato solo in presenza di algoritmi di scheduling pesati (ad esempio un weighted round robin) in modo da assegnare un peso maggiore a nodi con maggiore capacita' hardware.
Nel caso specifico illustrato abbiamo due entry real che indicano la presenza di due nodi fisici 10.62.1.166 e 10.62.165, in ascolto sulla porta 8080, aventi il medesimo peso e gestiti tramite il metodo di forwarding direct routing (LVS-DR).
Infine, si ricorda che il parametro real supporta anche ulteriori opzioni come ad esempio l'url di una pagina a cui fare una request e la risposta attesa, ma non essendo utilizzati nella mia configurazione non mi soffermo su di essi.
- scheduler: specifica un algoritmo di bilanciamento da utilizzare, tra quelli elencati nell'infobox introduttivo. Nel mio caso ho adotatato un algoritmo di tipo Least Connection.
- persistent: specifica il timeout per la persistenza delle connessioni. Per maggiori dettagli sulla persistenza si rimanda sempre all'infobox introduttivo.
- protocol: il tipo di protocollo del servizio da bilanciare. Nel mio caso tcp, avendo i proxy in ascolto sulla porta tcp 8080.
- checktype: questo parametro specifica la modalita' con la quale ldirectord dovra' verificare la disponibilita' del servizio sui nodi fisici, al fine di aggiungerli o rimuoverli dinamicamente al bilanciamento. Esistono diverse possibilita', per le quali si rimanda alla documentazione disponibile. Nel caso specifico e' stato scelta la modalita' connect, tramite la quale viene semplicemente effettuata una connessione alla porta utilizzata dal servizio per verificare se esso sia ancora in ascolto.
- checkport: specifica la porta alla quale connettersi per verificare la disponibilita' del servizio sui nodi fisici.
Una volta preparato l file di configurazionem per avviare il demone e' sufficiente dare il comando:
ldirectord /etc/ldirectord.conf start
Mentre per arrestarlo:
ldirectord /etc/ldirectord.conf stop
In alternativa e' possibile usare uno script di init in /etc/init.d/ ed avviare o stoppare il servizio con i comandi /etc/init.d/ldirectord start
e /etc/init.d/ldirectord stop
:
#!/bin/sh
NAME=ldirectord
DAEMON="/usr/sbin/$NAME"
CONFIG="/etc/ldirectord.conf"
test -x $DAEMON || exit 0
case "$1" in
start)
$DAEMON $CONFIG start
;;
stop)
$DAEMON $CONFIG stop
;;
restart)
$DAEMON $CONFIG stop
$DAEMON $CONFIG start
;;
status|reload|force-reload)
$DAEMON $CONFIG $1
;;
*)
echo "Usage: /etc/init.d/$NAME" \
"{start|stop|restart|status|reload|force-reload}" >&2
exit 1
;;
esac
Infine, sui nodi fisici e' necessario configurare netfilter in modo da effettuare un destination nat delle richieste ricevute e destinate all'indirizzo IP virtuale assegnato al bilanciatore al fine di modificare l'indirizzo di destinazione (10.62.1.164) con quello del nodo (10.62.1.165 e 10.62.1.166).
A tal fine e' necessario eseguire i seguenti comandi:
- nodo 10.62.1.165:
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -I PREROUTING -i bond0 -d 10.62.1.164 -p tcp --dport 8080 -j DNAT --to-dest 10.62.1.165
- nodo 10.62.1.166:
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -t nat -I PREROUTING -i bond0 -d 10.62.1.164 -p tcp --dport 8080 -j DNAT --to-dest 10.62.1.166
In caso si desideri testare il funzionamento di LVS senza la necessita' di monitorare i servizi e possibile aggiungere e rimuovere nodi con il comando ipvsadm
:
ipvsadm -C
ipvsadm -A -t 10.62.1.164:8080 -s lc
ipvsadm -a -t 10.62.1.164:8080 -r 10.62.1.166 -g
ipvsadm -a -t 10.62.1.164:8080 -r 10.62.1.165 -g
Le opzioni utilizzate nelle linee di comando di ipvsadm per l'esempio riportato sono le seguenti:
- -C, --clear: cancella la tabella del virtual server.
- -A, --add-service: crea un servizio virtuale.
- -a, --add-server: aggiunge un nodo ad un servizio virtuale.
- -t, --tcp-service: specifica indirizzo ip e numero di porta tcp del servizio virtuale.
- -s, --scheduler: specifica l'algoritmo di bilanciamento
- -r, --real-server: specifica l'indirizzo ip del nodo reale
- -g, --gatewaying: indica il metodo di forwarding direct routing (LVS-DR)
Per maggior dettagli sulle opzioni supportate da ipvsadm e' possibile fare riferimento alla relativa manpage.
Il comando ipvsadm, inoltre, puo' essere utilizzato anche per visualizzare la configurazione del bilanciatore, indipendentemente dal fatto che venga gestito direttamente tramite ipvsadm o con ldirectord.
In particolare lo stato del bilanciamento puo' essere visualizzato con il comando ipvsadm -L -n
:
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.62.1.164:8080 lc persistent 90
-> 10.62.1.165:8080 Route 1 52 1359
-> 10.62.1.166:8080 Route 1 32 1296
Maggiori dettagli possono essere visualizzati con i seguenti comandi:
- ipvsadm -L -n --connection
L'opzione connnection permette di vedere le connessioni attualmente in essere sul bilanciatore, e lo stato delle stesse. In realta' l'output di questo comando non e' da giudicarsi particolarmente attendibile nella configurazione adottata. Transitando dal bilanciatore solo i pacchetti destinati ai nodi fisici e non le relative risposte molte connessioni appariranno in stato FIN_WAIT anche se in realta' potrebbero essere state correttamente terminate.
Non vedendo il bilanciatore le risposte ai segmenti FIN dei client, per ognuno di essi mostrera' la connessione in stato FIN_WAIT, come nell'esempio seguente:
IPVS connection entries
pro expire state source virtual destination
TCP 00:08 FIN_WAIT 10.65.49.39:3494 10.62.1.164:8080 10.62.1.165:8080
TCP 01:56 FIN_WAIT 10.80.49.38:1510 10.62.1.164:8080 10.62.1.166:8080
TCP 00:06 CLOSE 10.66.49.37:1704 10.62.1.164:8080 10.62.1.165:8080
TCP 00:02 NONE 10.222.198.135:0 10.62.1.164:8080 10.62.1.165:8080
TCP 14:59 ESTABLISHED 10.65.49.41:4465 10.62.1.164:8080 10.62.1.165:8080
TCP 00:06 CLOSE 10.144.49.41:4377 10.62.1.164:8080 10.62.1.165:8080
[...]
TCP 00:15 FIN_WAIT 10.144.49.38:4164 10.62.1.164:8080 10.62.1.165:8080
TCP 00:00 CLOSE 10.129.49.14:1301 10.62.1.164:8080 10.62.1.166:8080
TCP 00:08 CLOSE 10.66.49.37:1712 10.62.1.164:8080 10.62.1.165:8080
TCP 00:03 FIN_WAIT 10.145.49.35:1087 10.62.1.164:8080 10.62.1.166:8080
TCP 01:00 FIN_WAIT 10.153.14.24:2806 10.62.1.164:8080 10.62.1.166:8080
- ipvsadm -L -n --stats
L'opzione stats mostra il numero di connessioni, pacchett e bye scambiati delle statistiche generiche relative al numero di connessioni, pacchetti e byte ricevuti:
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Conns InPkts OutPkts InBytes OutBytes
-> RemoteAddress:Port
TCP 10.62.1.164:8080 54242260 457441K 0 73629M 0
-> 10.62.1.165:8080 28469454 239225K 0 38122M 0
-> 10.62.1.166:8080 398255 3335312 0 537157K 0
- ipvsadm -L -n --rate
L'opzione rate, infine, mostra la velocita' con la quale i pacchetti vengono inoltrati ai nodi fisici:
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port CPS InPPS OutPPS InBPS OutBPS
-> RemoteAddress:Port
TCP 10.62.1.164:8080 39 320 0 45171 0
-> 10.62.1.165:8080 19 132 0 21225 0
-> 10.62.1.166:8080 21 188 0 23946 0