dnspr answered an A or AAAA query missing from the name cache with
udpresolve(), which waited for the name servers in place, and a name
server over TCP was waited for in place too, holding up every query of
the service. Now only the name cache is looked at in place; anything it
does not have goes to the name servers from the loop: over UDP the query
is sent on a socket the loop waits on, over TCP the socket is connected
without waiting, the query written once the loop sees it writable, and
the answer read in as many pieces as it comes. A name server silent for
DNS_TO is followed by the next one, as udpresolve() did; the last ends
the query with 821. An address in an answer is kept in the name cache,
unless -s says not to use it.
The UDP loop lets a session say what it waits for on its socket
(events) and a service handle a session's deadline itself (timeout).
Tests: an A query not in the cache answered by the name server and then
from the cache with the server gone, an A query waiting for a silent
name server while another is answered, a name server over TCP whose
answer comes in pieces, and the second name server asked when the
first is silent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A UDP service now runs in its own thread alone (udploop()): it reads the
service's socket and the sockets of its sessions towards their
destinations, keeps their deadlines, and answers every client from the
service's socket. A session is a struct clientparam as a client thread's
would be, with a slot of maxchild, among the service's children and
counted on its listener, but without a thread. The services supply
handlers (struct udpservice, childdef.udp): udppm keeps a session per
client address, forwarding its datagrams, behind the headers of a
parent chain, from a socket of its own and answering what comes back,
until STRING_L of silence or, with -s, the first answer; dnspr makes a
session of each query, answered at once from the fake address, the
name cache or the resolver, or sent on to the first name server over
UDP, whose answer is waited for in the loop up to DNS_TO (a name server
over TCP is still waited for in place). Authentication stays in the
loop for now; datagrams over a bandwidth limit, or of a client without
a free slot, are dropped.
On a reload the old service goes on serving the socket until it sees
its configuration replaced, then hands its sessions to the listener;
the newest service of its kind there, which until then does not read
the socket so that no client gets two sessions, adopts them: a slot of
its own, the old one given back, its filters and limits, and
re-authentication. A session is ended with 26 if the newest service is
of another kind, 27 without a slot, or with what the authentication
says; sessions nobody is left to adopt are ended by the last service to
stop on the listener.
On Windows a send on a socket waited while another thread polled it,
which delayed udppm's answers by up to a second, and old UDP services
did not exit after a reload; with one thread per socket neither
happens, and the second socket bound to the service's address
(NO_SHARE_UDP_SOCKET) is gone from the code and the build files.
udpinit, the global udp_table, udpnext(), udppmchild() and
dnsprchild() are gone; udpsockmap() serves SOCKS5 UDP ASSOCIATE only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A service keeps the configuration it was started with (srvparam.config)
and its threads and its clients' read it there: limits and counters
(alwaysauth, the limiter), RADIUS, dnspr's name servers, and the checks
for a reload in the accept loop and in sockmap(). The configuration is
still reloaded in place, so it is the current one for now; this is what
lets a reload build a new one while the old one still serves.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
conf is now a pointer to the current configuration rather than the
structure itself: at first confboot, which exists before main() runs, as
plugins and the standalone services use it too; later configurations
will start from confdefaults. Both are made from confinit.h, the one list
of initial values. The timeouts are an array of the configuration
instead of a pointer to a global one. Nothing behaves differently yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nservers, numservers and authnserver (resolve.c, common.c) and
radiuslist, nradservers and radiussecret (authradius.c) were globals of
their own; they are configuration, set by nserver, authnserver and
radius, and now live in struct extparam with the rest of it.
MAXNSERVERS, MAXRADIUS and struct radserver move to structures.h, which
plugins include without proxy.h.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- sockmapping rewritten from stratch to minimilse polling. poll() is now
only called if blocking is actually expected, splice pipes are now
polled if splice fails, buffers flushing is much more accurate.
- logging code moved to separate files
- signal masks added to client threads to prevent unneeded interruptions
- bandwidth limitation will not delay the thread after client or server
shutdown
nscache6 - new command for IPv6 addresses cache
nsrecord supports IPv6 addresses
dnsauth should work with IPv6 servers and clients
+ caching is now more accurate
Supports _real_ name resolution via parent proxy over TCP in nserver /
dnspr.
Can be used to create UDP->TCP name proxy, e.g.
nserver 127.0.0.1/tcp
nscache 65535
auth none
dnspr
auth iponly
tcppm 53 77.88.8.8 53
now, all names are resolved via TCP connection to 77.88.8.8
(dns.yandex.ru).