diff options
| author | user <user@clank> | 2026-09-22 21:54:03 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-22 21:54:03 +0200 |
| commit | dcd1f1320c102f9cc4cff7eee387ec538304f7d1 (patch) | |
| tree | e0b48e345c8d34a5f110378e795b8690120ebd55 | |
| parent | build: rebuild every object when include/*.inc changes (diff) | |
| download | gbos-dcd1f1320c102f9cc4cff7eee387ec538304f7d1.tar.gz gbos-dcd1f1320c102f9cc4cff7eee387ec538304f7d1.tar.xz gbos-dcd1f1320c102f9cc4cff7eee387ec538304f7d1.zip | |
net: one socket per client - four-tuple demux, spawning accept, bounded close
A server could only ever hold ONE connection, which makes an ircd pointless:
listen() turned the listener into the connection, and net_find_tcp demuxed on
local port alone, so two clients on 6667 were indistinguishable.
- net_find_tcp now matches an established socket on the full four-tuple
(local port + peer IP + peer port) and only falls back to a LISTENing
socket when none matches.
- A SYN at a listener no longer consumes it: tcp_spawn_conn clones a new
socket (local port + owning pid, so exit/kill still reclaims it) and the
listener keeps listening. accept() returns that socket; closing it leaves
the listener alone. A full table drops the SYN, and the client's retransmit
is taken once a slot frees.
- tcp_peer_is_current preserves BC: net_find_tcp scans with the socket index
there, and the compare needs BC for the rx buffer base. Without this the
scan died at the first mismatching socket, so a second client's handshake
ACK never reached its socket and it hung in SYNRCVD - tcpdump showed our
SYN+ACK going out and the client's NICK retransmitted five times into
silence.
- listen() only conflicts with another LISTENER on the port; connections
share it by design now.
- MAX_SOCKS 4 -> 6 (listener + 3 clients + 2 spare; 537 bytes of WRAM0 still
free), and SK_ACC marks a spawned connection as not yet accepted.
tcp_close's settle is also time-boxed to ~250 ms of real time. It counted
PUMPS (8 x 8000), which is ~30s of wall clock when the peer never answers -
invisible with well-behaved peers, but a daemon refusing a client that stayed
connected froze itself, and every other client with it, for half a minute.
httpd keeps its listener open across requests now, so the next client's SYN
is accepted immediately instead of being dropped between close and re-listen:
three parallel clients go 0.1/1.1/2.1s -> 0.4/1.0/0.1s.
| -rw-r--r-- | docs/internals.md | 36 | ||||
| -rw-r--r-- | include/gbos.inc | 2 | ||||
| -rw-r--r-- | src/socket.asm | 359 | ||||
| -rw-r--r-- | src/syscall.asm | 2 | ||||
| -rw-r--r-- | usr/httpd.c | 31 | ||||
| -rw-r--r-- | usr/sock.h | 47 |
6 files changed, 376 insertions, 101 deletions
diff --git a/docs/internals.md b/docs/internals.md index 62167f4..3e27bd2 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -149,16 +149,21 @@ Userland networking is plain C on top of that: `dhcp` (DISCOVER→ACK, then pump stdin and print the reply), `irc` (a full client: channels, /join /msg /me, nick colors, flood-safe rendering) and `chat`. -**Listening** (`net_op_listen` / `net_op_accept`): sockets demux on the local -port *alone*, so a listener and its connection are the same socket - the SYN -fills in the peer and the socket becomes the connection. That means one -connection at a time, re-armed by listening again after close; a second client -knocking is dropped in silence and its SYN retransmit is taken when we are -free again (~1s later), which is why `httpd` serves three parallel clients in -0.1/1.1/2.1s. `accept` never blocks: one RX pump per call, like `recv_nb`. -Since SYNs reach the same socket as the live connection, `tcp_peer_is_current` -is what tells a peer's retransmitted SYN apart from a newcomer's - without it -a second client silently *hijacks* the connection mid-request. +**Listening** (`net_op_listen` / `net_op_accept`): a listener stays a listener. +When a SYN arrives, `tcp_spawn_conn` clones a *new* socket from it (local port ++ owning pid), and `accept` hands that socket to the application - so a server +holds several clients at once, which is the whole point of `ircd`. Closing a +client socket leaves the listener untouched. `accept` never blocks: one RX pump +per call, like `recv_nb`. If the socket table is full the SYN is dropped, and +the client's retransmit is taken once a slot frees. + +That makes **demux a four-tuple problem**: one local port now hosts a listener +plus a socket per client. `net_find_tcp` scans for an established socket +matching (local port, peer IP, peer port) and only falls back to a LISTENing +socket if none matches. Matching on local port alone - what it used to do - +hands every client's segments to whichever socket sits earliest in the table. +`tcp_peer_is_current` does the peer comparison and **preserves BC**, because +the scan keeps its socket index there. **Receive-path invariants** (all three were bugs once, and each one is invisible until a peer sends more than a toy reply): @@ -177,10 +182,13 @@ invisible until a peer sends more than a toy reply): EOF at the hole — truncating the transfer just before the last segments. `tcp_close` FINs and then pumps for the peer's FIN/ACK, but **stops as soon as -it arrives** instead of burning its whole budget: the socket otherwise sits in -`TCP_FINWAIT` for about a second, and a server that closes and re-listens drops -the next client's SYN in that window, so every request after the first paid a -SYN retransmit (1.1s → 0.1s per request). +it arrives**, and in any case after **~250 ms of real time**. Both halves were +bugs. Lingering in `TCP_FINWAIT` made a server drop the next client's SYN, so +every request after the first paid a SYN retransmit (1.1s → 0.1s per request). +And the budget used to be counted in *pumps* (8 × 8000 ≈ 30s of wall clock when +the peer never answers): a well-behaved peer closes in milliseconds so nobody +noticed, until `ircd` refused a client that stayed connected and `close()` froze +the daemon - and every other client with it - for half a minute. A server must also **drain the request it was sent**. Reading only the request line leaves the rest unacknowledged: the client retransmits headers for a diff --git a/include/gbos.inc b/include/gbos.inc index 589c33c..7f4431b 100644 --- a/include/gbos.inc +++ b/include/gbos.inc @@ -194,7 +194,7 @@ DEF NET_RECVNB EQU 8 ; like NET_RECV but never blocks: pump once, then DEF SOCK_ICMP EQU 1 DEF SOCK_UDP EQU 2 DEF SOCK_TCP EQU 3 -DEF MAX_SOCKS EQU 4 +DEF MAX_SOCKS EQU 6 ; a server holds one listener + a socket per client ; SLIP framing (RFC 1055) DEF SLIP_END EQU $C0 DEF SLIP_ESC EQU $DB diff --git a/src/socket.asm b/src/socket.asm index c719948..9079b7e 100644 --- a/src/socket.asm +++ b/src/socket.asm @@ -31,8 +31,10 @@ DEF SK_STATE EQU 17 ; 1 TCP state (see TCP_* below) DEF SK_SND EQU 18 ; 4 snd_nxt (our next sequence number) DEF SK_RCV EQU 22 ; 4 rcv_nxt (next sequence we expect from peer) DEF SK_OWNER EQU 26 ; 1 pid that created this socket (freed when it dies) -DEF SK_RXBUF EQU 27 ; 288 payload (holds a DHCP reply, or a max segment) -DEF SK_SIZE EQU 315 +DEF SK_ACC EQU 27 ; 1 connection spawned by a listener, not yet handed + ; to the app by accept() +DEF SK_RXBUF EQU 28 ; 288 payload (holds a DHCP reply, or a max segment) +DEF SK_SIZE EQU 316 DEF TCP_MSS EQU 200 ; TCP connection states @@ -80,6 +82,12 @@ wNetTO:: DS 3 ; recv timeout counter (net_pump clobbers registers) wNetRxRem:: DS 1 ; recv: bytes left in RXBUF after a clamped delivery wNetRxOff:: DS 1 ; tcp_buffer_data: append offset (unread bytes) wNetTcpInOrd:: DS 1 ; tcp_in: did this segment start exactly at rcv_nxt? +wNetListen:: DS 2 ; net_find_tcp: listener seen while scanning for an + ; exact four-tuple match (used only if none is found) +wNetAccPort:: DS 2 ; net_op_accept: the listening socket's local port +wNetSpawnPort::DS 2 ; tcp_spawn_conn: listener's port, cloned to the child +wNetSpawnOwner::DS 1 ; tcp_spawn_conn: listener's owning pid +wNetCloseT0:: DS 2 ; tcp_close: tick when the settle wait started wNetTcpFlags:: DS 1 ; TCP flags for the segment being built wNetTcpDlen:: DS 1 ; TCP payload length for the segment being built wNetTcpIn:: DS 1 ; incoming TCP flags @@ -381,8 +389,9 @@ net_op_listen: xor a ret -; listen_port_free -> CF set if a socket OTHER than this one is already bound -; to the requested local port. +; listen_port_free -> CF set if another socket is already LISTENING on the +; requested local port. Established connections share that port by design (one +; per client), so only a second listener is a conflict. listen_port_free: ld c, 0 .l @@ -394,6 +403,17 @@ listen_port_free: ld a, [hl] or a jr z, .nx ; free slot + push hl + ld a, l + add SK_STATE + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl] + pop hl + cp TCP_LISTEN + jr nz, .nx ; a connection on this port, not a listener ld a, l add SK_LPORT ld l, a @@ -417,10 +437,15 @@ listen_port_free: or a ret -; ---- accept(sock) -> A = 0 connected / $FE not yet --------------------------- -; Never blocks: one pump per call, exactly like recv_nb. A server that owns its -; main loop (httpd) keeps polling the keyboard and stays killable; a kernel-side -; blocking accept would wedge the process forever on a quiet port. +; ---- accept(lsock) -> A = new socket id / $FE if nobody is waiting ---------- +; Never blocks: one pump per call, exactly like recv_nb, so a server owns its +; own main loop and stays killable. +; +; The listener KEEPS listening; each connection gets its own socket (spawned by +; tcp_in when the SYN arrives), which is what lets a server hold several +; clients at once - an ircd is pointless otherwise. The caller talks to the +; returned socket with the ordinary send/recv/close, and closing it leaves the +; listener untouched. net_op_accept: ld a, [wNetReq + NR_SOCK] call net_sock_ptr @@ -429,13 +454,60 @@ net_op_accept: ld a, h ld [wNetSockPtr+1], a call net_pump - ld a, [wNetReq + NR_SOCK] ; tcp_in moves wNetSockPtr; restore ours + ; hand over the first connection on our port that the app has not seen yet + ld a, [wNetReq + NR_SOCK] call net_sock_ptr ld a, l - ld [wNetSockPtr], a + add SK_LPORT + ld l, a ld a, h - ld [wNetSockPtr+1], a - call sock_ptr_hl + adc 0 + ld h, a + ld a, [hl+] + ld [wNetAccPort], a ; the listener's local port + ld a, [hl] + ld [wNetAccPort+1], a + ld c, 0 +.scan + ld a, c + cp MAX_SOCKS + jr nc, .none + ld a, [wNetReq + NR_SOCK] + cp c + jr z, .nx ; skip the listener itself + ld a, c + call net_sock_ptr ; HL = &sock[c] + ld a, [hl] + cp SOCK_TCP + jr nz, .nx + push hl + ld a, l + add SK_ACC + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl] + or a ; SK_ACC: spawned and not yet accepted? + pop hl + jr z, .nx + push hl + ld a, l + add SK_LPORT + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [wNetAccPort] + cp [hl] + jr nz, .nomatch + inc hl + ld a, [wNetAccPort+1] + cp [hl] +.nomatch + pop hl + jr nz, .nx + push hl ld a, l add SK_STATE ld l, a @@ -443,14 +515,27 @@ net_op_accept: adc 0 ld h, a ld a, [hl] + pop hl cp TCP_ESTAB - jr z, .up - cp TCP_DONE ; opened and closed between two polls: still a - jr z, .up ; connection - let the app read its buffer/EOF - ld a, $FE - ret -.up + jr z, .give + cp TCP_DONE ; connected and gone between two polls: still + jr nz, .nx ; a connection - let the app read its EOF +.give + ld a, l + add SK_ACC + ld l, a + ld a, h + adc 0 + ld h, a xor a + ld [hl], a ; handed over; never offered twice + ld a, c ; -> the new socket id + ret +.nx + inc c + jr .scan +.none + ld a, $FE ret ; ---- bind(sock, port) - set local port ----------------------------------- @@ -1664,7 +1749,19 @@ sk_set_rcv_from_seq: ret ; net_find_tcp -> HL = &TCP socket bound to wNetUdpPort, CF set if none +; net_find_tcp -> HL = &socket for the segment in wNetRxBuf, CF if none. +; +; Two passes, because one local port can now host a listener AND several +; connections at once (that is what lets a server take more than one client): +; 1. an established socket matching the FULL four-tuple (local port + the +; peer's IP and port) +; 2. failing that, a socket LISTENing on the local port - a new connection +; Matching on local port alone (what this used to do) hands every client's +; segments to whichever socket sits earliest in the table. net_find_tcp: + xor a + ld [wNetListen], a ; no listener seen yet this scan + ld [wNetListen+1], a ld c, 0 .l ld a, c @@ -1672,6 +1769,48 @@ net_find_tcp: ld a, [hl] cp SOCK_TCP jr nz, .nx + call tcp_lport_matches + jr nz, .nx + ; local port matches; is this the connection or the listener? + push hl + ld a, l + ld [wNetSockPtr], a + ld a, h + ld [wNetSockPtr+1], a + call sock_state_get + cp TCP_LISTEN + jr z, .islisten + call tcp_peer_is_current ; Z = the four-tuple matches + pop hl + jr nz, .nx + or a ; exact connection wins immediately + ret +.islisten + pop hl + ld a, l + ld [wNetListen], a ; remember it; keep scanning for an exact match + ld a, h + ld [wNetListen+1], a +.nx + inc c + ld a, c + cp MAX_SOCKS + jr c, .l + ld a, [wNetListen] ; no connection matched: use the listener + ld l, a + ld a, [wNetListen+1] + ld h, a + or l ; both bytes zero -> nothing for us + jr z, .none + or a ; CF = 0 + ret +.none + scf + ret + +; tcp_lport_matches(HL = &sock) -> Z if SK_LPORT == the segment's dest port. +; Preserves HL. +tcp_lport_matches: push hl ld a, l add SK_LPORT @@ -1681,29 +1820,118 @@ net_find_tcp: ld h, a ld a, [wNetUdpPort] cp [hl] - jr nz, .nomatch + jr nz, .no inc hl ld a, [wNetUdpPort+1] cp [hl] - jr nz, .nomatch +.no pop hl - or a ret -.nomatch - pop hl -.nx - inc c + +; sock_state_get -> A = SK_STATE of the current socket (wNetSockPtr) +sock_state_get: + call sock_ptr_hl + ld a, l + add SK_STATE + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl] + ret + +; tcp_spawn_conn - give an inbound connection its own socket, cloned from the +; listening socket in wNetSockPtr (local port + owning pid) and marked pending +; accept(). Points wNetSockPtr at the new socket. CF set if the table is full. +tcp_spawn_conn: + call sock_ptr_hl ; save the listener's local port + owner + ld a, l + add SK_LPORT + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl+] + ld [wNetSpawnPort], a + ld a, [hl] + ld [wNetSpawnPort+1], a + call sock_ptr_hl + ld a, l + add SK_OWNER + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [hl] + ld [wNetSpawnOwner], a + ld c, 0 +.find ld a, c cp MAX_SOCKS - jr c, .l + jr nc, .full + ld a, c + call net_sock_ptr + ld a, [hl] + or a + jr z, .got ; SK_TYPE == 0: free slot + inc c + jr .find +.full scf ret +.got + ld a, l + ld [wNetSockPtr], a ; everything below runs on the NEW socket + ld a, h + ld [wNetSockPtr+1], a + ld a, SOCK_TCP + ld [hl], a ; SK_TYPE + call sock_ptr_hl + ld a, l + add SK_LPORT + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [wNetSpawnPort] + ld [hl+], a + ld a, [wNetSpawnPort+1] + ld [hl], a + call sock_ptr_hl + ld a, l + add SK_HASRX + ld l, a + ld a, h + adc 0 + ld h, a + xor a + ld [hl+], a ; HASRX = 0 + ld [hl], a ; RXLEN = 0 (SK_RXLEN = SK_HASRX + 1) + call sock_ptr_hl + ld a, l + add SK_OWNER + ld l, a + ld a, h + adc 0 + ld h, a + ld a, [wNetSpawnOwner] + ld [hl+], a ; inherit the listener's owner, so exit/kill + ld a, 1 ; reclaims client sockets too + ld [hl], a ; SK_ACC = 1 (waiting for accept) + or a ; CF = 0 + ret ; tcp_peer_is_current -> Z if the segment in wNetRxBuf came from the peer this -; socket is already talking to (same source IP *and* source port). Sockets -; demux on local port alone, so a second client's SYN reaches the same socket -; as the connection in progress; only this tells them apart. +; socket is already talking to (same source IP *and* source port). One local +; port now hosts a listener plus several connections, so this is what tells a +; peer's own segment from another client's. tcp_peer_is_current: + push bc ; net_find_tcp scans with the socket index in + call .body ; C, and the compare below needs BC for the + pop bc ; rx buffer base - without this the scan dies + ret ; after the first mismatching socket. (pop + ; leaves the Z flag from .body alone.) +.body call sock_ptr_hl ld a, l add SK_RIP @@ -2034,32 +2262,42 @@ tcp_close: call sk_add_snd ld a, TCP_FINWAIT call sock_state_set - ; Settle: pump for the peer's FIN/ACK - but STOP as soon as it arrives. - ; Always burning the whole budget keeps the socket in FINWAIT for ~a - ; second, and a server that closes and re-listens (httpd) drops the next - ; client's SYN in that window, so every request after the first paid a - ; TCP SYN retransmit (~1s) before it was even accepted. - ld b, 8 -.settle - ld de, 8000 -.s2 - call net_pump - call sock_ptr_hl + ; Settle: pump for the peer's FIN/ACK, stopping the moment it arrives - + ; and in any case after ~250 ms of REAL time. + ; + ; Two lessons are baked in here. Stopping early matters because a server + ; that lingers in FINWAIT drops the next client's SYN, which cost httpd a + ; ~1s SYN retransmit per request. Time-boxing matters because the budget + ; used to be counted in PUMPS (8 x 8000) - about 30 seconds of wall clock + ; when the peer never answers. Well-behaved peers close in milliseconds so + ; it went unnoticed, until an ircd refused a client that stayed connected: + ; close() then froze the whole daemon, and every other client with it, for + ; half a minute. One rude peer must not hold a server hostage. + call ticks16 ld a, l - add SK_STATE - ld l, a + ld [wNetCloseT0], a ld a, h - adc 0 - ld h, a - ld a, [hl] + ld [wNetCloseT0+1], a +.settle + call net_pump + call sock_state_get cp TCP_DONE jr z, .free ; peer closed too: nothing left to wait for - dec de - ld a, d - or e - jr nz, .s2 - dec b - jr nz, .settle + call ticks16 ; HL = now, in 1/64 s ticks + ld a, [wNetCloseT0] + ld e, a + ld a, [wNetCloseT0+1] + ld d, a + ld a, l + sub e + ld b, a + ld a, h + sbc d ; B = elapsed ticks (low byte) + or a + jr nz, .free ; >= 256 ticks: long past + ld a, b + cp 16 ; ~250 ms + jr c, .settle .free call sock_ptr_hl xor a @@ -2216,13 +2454,20 @@ tcp_in: call sock_state_set ret .listen - ; A SYN arriving at a listening socket: learn the peer from the packet, - ; answer SYN+ACK. Reached again if the peer retransmits its SYN (our - ; SYN+ACK was lost), which is why SND is re-seeded here rather than - ; advanced - the reply must always carry the same ISN. + ; A SYN at a LISTENING socket: give the connection a socket of its own and + ; leave the listener listening, so the next client can arrive while this + ; one is being served. ld a, [wNetTcpIn] and TF_SYN ret z ; nothing else opens a connection + call tcp_spawn_conn + ret c ; table full: drop the SYN. The client + ; retransmits, and by then a slot may have + ; freed - better than evicting a live peer. +.handshake + ; Learn the peer from the packet and answer SYN+ACK. Re-entered when a + ; peer retransmits its SYN (our SYN+ACK was lost), which is why SND is + ; re-seeded here rather than advanced: the reply must carry the same ISN. call sock_ptr_hl ; peer IP <- the IP header's source address ld a, l add SK_RIP @@ -2286,7 +2531,9 @@ tcp_in: and TF_SYN jr z, .sr_ack call tcp_peer_is_current - jr z, .listen ; our peer resent its SYN: redo the SYN+ACK + jp z, .handshake ; our peer resent its SYN: redo the SYN+ACK on + ; THIS socket (spawning another would leak one + ; socket per retransmission) ret ; a *different* client knocking while we are ; mid-handshake: drop it. It retransmits, and ; we take it once we are listening again - diff --git a/src/syscall.asm b/src/syscall.asm index e0961bc..0ed9f85 100644 --- a/src/syscall.asm +++ b/src/syscall.asm @@ -282,7 +282,7 @@ ENDR ; (TimerISR can carry between the two byte reads, which would otherwise read ; $01FF out of a counter going $00FF -> $0100). ; ----------------------------------------------------------------------------- -ticks16: +ticks16:: ld a, [wTicks+1] ld h, a ld a, [wTicks] diff --git a/usr/httpd.c b/usr/httpd.c index 335e824..e704e8c 100644 --- a/usr/httpd.c +++ b/usr/httpd.c @@ -33,7 +33,8 @@ #define REQSZ 120 /* the request line is all we parse */ #define PATHSZ 48 -static unsigned char sock; +static unsigned char lsock; /* the listener, held for our lifetime */ +static unsigned char sock; /* the client being served right now */ static unsigned char rbuf[BUFSZ]; static char obuf[OBUFSZ]; static unsigned char olen; @@ -256,22 +257,22 @@ void main(void) { puts(" (run it with & and kill it to stop)"); nl(); + lsock = net_socket(SOCK_TCP); + if (lsock == 0xFF) { + puts("httpd: no socket"); + nl(); + sexit(1); + } + if (net_listen(lsock, port) == 0xFF) { + puts("httpd: listen failed (port in use?)"); + nl(); + net_close(lsock); + sexit(1); + } for (;;) { - sock = net_socket(SOCK_TCP); - if (sock == 0xFF) { - puts("httpd: no socket"); - nl(); - sexit(1); - } - if (net_listen(sock, port) == 0xFF) { - puts("httpd: listen failed"); - nl(); - net_close(sock); - sexit(1); - } - while (net_accept(sock) != 0) /* one RX pump per call */ + while ((sock = net_accept(lsock)) == 0xFE) /* one RX pump per call */ msleep(20); handle(); - net_close(sock); /* FIN: this is what ends the body */ + net_close(sock); /* FIN: this is what ends the body; listener stays */ } } @@ -21,35 +21,47 @@ static struct netreq _nr; /* -> socket id, or 0xFF if the table is full */ static unsigned char net_socket(unsigned char type) { - _nr.op = 0; _nr.sock = type; + _nr.op = 0; + _nr.sock = type; return sys_net(&_nr); } /* set the remote address/port for send()/recv() */ static unsigned char net_connect(unsigned char s, const unsigned char *ip, unsigned int port) { unsigned char i; - _nr.op = 1; _nr.sock = s; + _nr.op = 1; + _nr.sock = s; for (i = 0; i < 4; i++) _nr.ip[i] = ip[i]; - _nr.port_hi = (unsigned char)(port >> 8); _nr.port_lo = (unsigned char)port; + _nr.port_hi = (unsigned char)(port >> 8); + _nr.port_lo = (unsigned char)port; return sys_net(&_nr); } /* send 'len' payload bytes to the connected peer -> 0 ok / 0xFF error */ static unsigned char net_send(unsigned char s, void *buf, unsigned char len) { - _nr.op = 2; _nr.sock = s; _nr.buf = (unsigned char *)buf; _nr.len = len; + _nr.op = 2; + _nr.sock = s; + _nr.buf = (unsigned char *)buf; + _nr.len = len; return sys_net(&_nr); } /* receive up to 'max' payload bytes -> length, or 0xFF on timeout */ static unsigned char net_recv(unsigned char s, void *buf, unsigned char max) { - _nr.op = 3; _nr.sock = s; _nr.buf = (unsigned char *)buf; _nr.len = max; + _nr.op = 3; + _nr.sock = s; + _nr.buf = (unsigned char *)buf; + _nr.len = max; return sys_net(&_nr); } /* non-blocking recv: one RX pump -> length, 0xFE if nothing buffered yet, 0 on TCP EOF. Loop it with msleep() to own your timeout (see ping). */ static unsigned char net_recv_nb(unsigned char s, void *buf, unsigned char max) { - _nr.op = 8; _nr.sock = s; _nr.buf = (unsigned char *)buf; _nr.len = max; + _nr.op = 8; + _nr.sock = s; + _nr.buf = (unsigned char *)buf; + _nr.len = max; return sys_net(&_nr); } -/* passive open: take connections on a local port (TCP). The listener and the - connection are the same socket, so serve one, close, then listen again. */ +/* passive open: take connections on a local port (TCP). The listener stays a + listener - accept() hands back a separate socket for each client. */ static unsigned char net_listen(unsigned char s, unsigned int port) { _nr.op = 9; _nr.sock = s; @@ -57,8 +69,10 @@ static unsigned char net_listen(unsigned char s, unsigned int port) { _nr.port_lo = (unsigned char)port; return sys_net(&_nr); } -/* -> 0 once a peer has connected, 0xFE if none yet. Never blocks: one RX pump - per call, so loop it with yield()/msleep() and poll the keyboard too. */ +/* -> the new client's socket id, or 0xFE if nobody is waiting. Never blocks: + one RX pump per call, so loop it with yield()/msleep(). Talk to the returned + socket with the ordinary send/recv/close; closing it leaves the listener + alone, so a server can hold several clients at once (see usr/ircd.c). */ static unsigned char net_accept(unsigned char s) { _nr.op = 10; _nr.sock = s; @@ -66,16 +80,21 @@ static unsigned char net_accept(unsigned char s) { } /* set the local (source) port - needed so UDP replies demux back to us */ static unsigned char net_bind(unsigned char s, unsigned int port) { - _nr.op = 5; _nr.sock = s; - _nr.port_hi = (unsigned char)(port >> 8); _nr.port_lo = (unsigned char)port; + _nr.op = 5; + _nr.sock = s; + _nr.port_hi = (unsigned char)(port >> 8); + _nr.port_lo = (unsigned char)port; return sys_net(&_nr); } static void net_close(unsigned char s) { - _nr.op = 4; _nr.sock = s; sys_net(&_nr); + _nr.op = 4; + _nr.sock = s; + sys_net(&_nr); } /* pump the RX path once (answers inbound pings) without blocking */ static void net_poll(void) { - _nr.op = 6; sys_net(&_nr); + _nr.op = 6; + sys_net(&_nr); } /* set our IPv4 address (the DHCP client calls this once it has a lease) */ static void net_setip(const unsigned char *ip) { |
