SOCKS proxy implementation
Upcoming. This page describes
master. The proxy is not part of a release yet, and details may change.
Part of SOCKS proxy. The protocol of the proxy is a Maven module of its own, proxy, which knows
nothing of QUIC, CDI or sporks; the application module adds the daemon glue, the chain packets and the services
in service/chain. The chains themselves, their entries and the traffic between nodes are on
SOCKS proxy chains.
Modules and packages
The Maven module hedgehog-proxy, in the directory proxy/, is the JPMS module org.unigrid.hedgehog.proxy. It
depends on no other Hedgehog module, only on Netty (with its SOCKS codec), BouncyCastle, SLF4J and Lombok.
| Package | Main classes |
|---|---|
proxy/socks |
SocksInitializer, SocksGreetingHandler, SocksPasswordHandler, Country, SocksCommandHandler, HandshakeTimeout, QuietClose, Tunnel, TunnelStream, UdpAssociation, UdpBinder, UdpHeaderCodec |
proxy/group |
ProxySettings, Epoch, ShardGroups, Rendezvous, ChainIds, GroupView, GroupChoice |
proxy/chain |
Entry, EntryHeader, EntryCodec, Tag, SizeClasses, FnvJ64, WriterKey, WriterId, Feed, FeedWriter, Chain, ChainLimits, Head, Link, Admission, HeadsDiff, Gap |
proxy/session |
ClientSession, Session, NoiseNk, Tags, SealingCipher, SealedCodec, ExitSelector, ExitCandidate, SocksAddress, DestinationPolicy |
proxy/exit |
ExitRelay, NettyExitConnector, NettyUdpConnector, AddressFamily, DatagramRelay, DatagramDestinations, Target |
application, server/socks |
SocksServer, ConnectionLimit |
application, service/chain |
ChainHost, HostedChain, ChainClient, ChainAttachment, ClientSessions, ChainTunnel, ChainExit, ChainOutbox, StemRouter, StemEmbargo, StemStalls, WriterPauses, StreamBudgets, ExitContexts |
application, model/geo |
CountryLookup, MmdbCountryLookup, HostLiteral |
application, model/gridnode |
SealingKeyFile, SealingKey, GridnodeSignature |
The rest of the glue in application: SocksOptions holds the four options, CommunicationSpork and
CommunicationFlag in model/spork the spork, ChainProducer turns a stored spork into the ProxySettings the
services read, and TunnelProducer builds the ChainTunnel that SocksServer hands its connections to and the
CountryLookup. PeerProtocol, StreamRouter, ChainStreamInitializer, ChainPipeline and ChainCodecs carry
the chain traffic between nodes. MmdbCountryLookup reads the database with the MaxMind DB reader
(com.maxmind.db:maxmind-db).
The communication spork on the wire
CommunicationSporkEncoder and CommunicationSporkDecoder put the
fields after the spork header, big-endian, 32 bytes in all.
| Offset | Size | Field |
|---|---|---|
| 0 | 2 | groupSize |
| 2 | 4 | reorganizePeriodSeconds |
| 6 | 2 | chainsPerGroup |
| 8 | 2 | clientsPerChain |
| 10 | 4 | chainCapacityBytes |
| 14 | 2 | retentionSeconds |
| 16 | 4 | maxEntryBytes |
| 20 | 2 | sessionIdleSeconds |
| 22 | 2 | communicationFlags |
| 24 | 8 | Reserved, zero |
CommunicationSpork.SporkData.validate enforces the bounds through SporkContent. cli checks the data before it
signs, and POST /gridspork/wire answers 400 with the message. A spork that comes in from a peer is not held to the
bounds, but ChainProducer treats a stored one that fails them as no spork, so the proxy stays off.
SporkRecipients sends the spork, from the periodic publish, a relay or the REST interface, only over a connection
for which PeerProtocol.speaksProxy holds. Without --preview a node takes no communication spork
(SporkRelease), and ChainProducer reads the stored one through SporkRelease.stored, so it ignores one that a
run with --preview left in spork.db.
SOCKS5 front end
SocksServer is an @Eager @Preview @ApplicationScoped bean, started with the container only with --preview,
like ChainHost and ChainExit, and it does nothing while --socks-port is 0. Otherwise it binds a
Netty server on two threads, and ConnectionLimit closes every connection beyond 512 at once. SocksInitializer
builds the pipeline of each connection: Socks5ServerEncoder, Socks5InitialRequestDecoder,
SocksGreetingHandler, Socks5PasswordAuthRequestDecoder, SocksPasswordHandler, Socks5CommandRequestDecoder,
HandshakeTimeout, SocksCommandHandler and QuietClose. Each stage removes itself or its decoder once its part
is done. The country of --socks-country, if any, is set on the channel first.
- Greeting. One that offers
USERNAME/PASSWORDgets0x02, even when it offersNO AUTHtoo; one that offers onlyNO AUTHgets0x00, and the two username stages leave the pipeline; any other gets0xFFand a close. - Username.
SocksPasswordHandlerreads the username throughCountry.of: two ASCII letters thatLocale.getISOCountries()holds in upper case set the country of the connection, an empty one leaves the default. Any other gets status0xFFand a close. The password is not read. - Deadline.
HandshakeTimeoutcloses a connection that has sent no command 10 seconds after it opened. - Read failures.
QuietClosecloses a connection whose read failed and logs only the class of the failure. - Command.
SocksCommandHandlerstops reads at the command. ForCONNECTit holds bytes the client sends ahead of the reply, up to 128 KiB, and closes the connection beyond that.BINDgets0x07. - Addresses.
SocksAddressaccepts a domain of 1 to 255 printable ASCII characters without a colon, bracket or percent sign, whose last label is not a number in decimal or hex (127.1,2130706433,0x7f.1), since resolvers read such names as addresses. Trailing dots do not hide the number. Anything else gets0x08.
The client gets one reply. Every reply but that of a UDP ASSOCIATE that succeeded names
0.0.0.0:0.
Tunnel and pacing
The front end talks to the network through Tunnel, which ChainTunnel implements over client sessions. It reads
the spork for every stream and association, refuses with 0x02 while the spork is absent or lacks the flag, and
gives each 120 seconds to be answered before it fails with 0x06. The two minutes cover 10 seconds per member asked
to join, 5 seconds for each of three exits and 8 addresses of 10 seconds at the exit.
Writes are cut into Data messages of at most maxEntryBytes less 181 bytes of entry overhead, 20 of seal and 1 of
type: 16182 bytes with the default spork. The front end reads its client only while the session’s writer has
nothing waiting in its ChainOutbox and no member has paused it; ClientSessions hands each outbox’s state to its
streams once a second. A client that reads slowly makes the stream hold what arrives, up to 1 MiB, and then drops
the stream, as the chain has no flow control that could slow the exit down. There is no half-close: Netty closes a
connection whose input shuts down, and with it the session.
UDP associations
UDP ASSOCIATE starts a session that carries Connect(UDP_ASSOCIATE, 0.0.0.0:0). When the exit answers,
UdpAssociation binds a datagram socket at the local address the client reached, replies with that address and
reads the control connection again, whose end ends the association. The socket serves only the control
connection’s IP address, with the port the request named or, for port 0, the port of the first valid datagram.
UdpHeaderCodec requires RSV and FRAG to be zero, since fragments are not reassembled.
A datagram is dropped, never queued, when it does not fit one entry, while the chain has no room, when it comes from another source, when 64 datagrams from the exit already wait for the event loop, or when it would exceed 65507 bytes with its header on the way to the client. Drops are counted, and the association logs one warning once it has dropped more than 1000.
Shard groups, epochs and exits
Every node derives the groups and chains from the spork, the active gridnodes and the clock, without coordinating with any other.
- Epoch. The epoch is ⌊t / 1000P⌋ for t, the time in milliseconds since 1970, and P,
reorganizePeriodSeconds; it is encoded as u64. - Groups.
ShardGroups.ofranks the gridnode ids by rendezvous hashing, as in the placement of Network storage: highest SHA-256(s followed by the UTF-8 id) first, equal scores by id, with s = SHA-256(shard-groupfollowed by the epoch). It deals them round robin into max(1, ⌊n /groupSize⌋) groups, so sizes differ by at most one; with no gridnodes there is no group. - Chains. Chain i of group g is SHA-256(
chainfollowed by the epoch as u64, g as u32 and i as u32). The members of a group host its chains.
Hosting. ChainHost refreshes once a second and rebuilds its hosting only when the settings, the epoch, the
active gridnodes or its own id change. A rebuilt hosting keeps a chain whose id and limits stay the same.
Countries. The client’s node looks each active gridnode up in its own database. HostLiteral takes the host of
the announced address, without port or brackets, when it is an IP literal, and resolves no name.
MmdbCountryLookup reads the whole file into memory, refuses one over MAX_BYTES by its size before reading it,
and takes the iso_code of a record’s country, where the network is, not of its registered_country. A node
without a readable database uses CountryLookup.NONE, which places no gridnode anywhere.
Joining. ChainClient.attachment finds the attachment for the country a session asks for, or for none.
GroupChoice.groupdraws a group at random with a weight of its members in the country, or of all its members for none, never the node’s own group nor one passed over. With no weight left the session fails at once, before any member is asked.- The node holds at most 4 attachments (
MAX_ATTACHMENTS), one for each group drawn. A country keeps the one it was given while that is under way or in use; past four, it takes the joined group with the most members there. - The members the node holds an open stream to are taken in a random order, and the first two are the pair it would
attach through, whatever their country. When they hold every candidate exit of the group in the country, the group
is passed over before either is asked, as two members asked and then left without a session would learn that the
country is theirs. When that passes over every group drawn, the attachment fails with a message that says so and
names no country, and the session closes
UNREACHABLE. - With a fresh
WriterKeythe node sendsJOIN_CHAINwithout a chain id to the first. AJOINEDreply must name a chain of the group, and the node follows that chain and asks the second member for it as a spare path. A refusal, or no reply within 10 seconds (PendingRequests.TIMEOUT), passes the group over for the next draw. How a member places the writer is on SOCKS proxy chains. - An attachment made for another country, or for none, that has no exit in the country is passed over for it, and the country is drawn again among the other groups.
All sessions of the node on one attachment share its writer and its outbox.
The exit. ChainAttachment.exit draws the exit of each attempt uniformly among the members of the group that
are active, announce a sealing key and are in the country, any for none, passing over those tried. The pair the
node asked to attach is never a candidate: those members see the node’s address, and as its exit one would see
both ends of a session.
Retiring and draining. Chains a rebuilt hosting no longer wants retire. They take no new clients but still take
entries, and they are kept while their epoch is at most 4 behind the current one, 1024 at most, the oldest epoch
going first. While a valid spork is in force, a retired chain stays until its grace, min(10 minutes, P), is over,
whether anything runs on it here or not, as an exit stems the entries of a draining session to any member of the
group. Every ChainDrain is then told, so that ChainExit closes its relays DONE while the chain still carries the
Close; a chain whose drains failed goes after two graces. Without a spork, a retired chain that no client and no
session of the node uses goes once it has been retired for retentionSeconds. A client closes the sessions on an
attachment that is no longer current DONE after the same grace.
The sealing key
A gridnode (-G) makes an X25519 key pair on its first start and keeps the private key in gridnode-sealing.key in
the data directory: one line of hex, readable by its owner only and never replaced, as a new key would leave every
opening sealed to the old one unanswered. GridnodeSetup.validate reads or makes it before the container starts, so
a file that cannot be read stops the daemon with a message. The public key goes into the gridnode’s signed
announcement, see Peer-to-peer network protocol.
A node that is no gridnode has no sealing key and opens no session as an exit.
Sessions
A session lives on one chain. The client writes to the writer of its attachment; the exit writes as a writer made for that session alone.
Handshake
NoiseNk implements the Noise pattern NK, Noise_NK_25519_AESGCM_SHA256 as revision 34 of the
Noise Protocol Framework defines it, on BouncyCastle: the client knows the
exit’s static sealing key. The prologue is the chain id followed by the client’s writer id, so a copy of an opening
under another writer opens nowhere. Both entries are padded with random bytes to the smallest size class.
| Entry | Noise | Payload before the padding |
|---|---|---|
| Opening | -> e, es |
The client’s ephemeral key (32 bytes) and the AEAD tag of an empty payload (16): 48 bytes |
| Answer | <- e, ee |
The exit’s ephemeral key (32), then the exit’s writer id sealed with its AEAD tag (48): 80 bytes |
The opening goes under a random entry tag and names no exit. The answer carries the answer tag, computed as the
session tags below with the answer tag key: HKDF-SHA256 with the chaining key after es as salt, no input and
hedgehog-proxy/answer as info, which both ends hold before the answer exists. Once the handshake is done, Split
gives the client-to-exit and the exit-to-client key, and HKDF-SHA256 with the final chaining key as salt, the
handshake hash as input and hedgehog-proxy/tags as info gives 64 bytes: the client’s tag key, then the exit’s.
The tag of message n in direction d, 1 from client to exit and 2 from exit to client, is the first 16 bytes of
HMAC-SHA256 under that direction’s tag key over d (u8) and n (u64). The answer is message 0 of direction 2 under the
answer tag key.
ChainExit tries every entry of the smallest class on a current or retired chain of its group as an opening, unless
a session of its own recognises it or one of its own writers wrote it: one X25519 agreement and one AES-GCM check.
It passes over an opening stamped more than 15 seconds ago (the 5 seconds a client waits and 10 of clock skew) or
more than 10 seconds ahead, and one it answered already: the record of answered openings keeps each for the
retention of its chain and at least 25 seconds, 4096 at most.
The client takes only an answer under the answer tag of one of its openings whose sealed writer id is the writer that
posted it, so a copy under another writer completes nothing, and passes over one that does not complete, so the
exit’s own can still. The first answer completes the session, and the client sends its Connect at once. An exit key
that is no X25519 key, or one of low order, is passed over without using an attempt. An attempt left unanswered for 5
seconds is followed by one towards the next exit, up to 3 attempts; then the session closes UNREACHABLE. Each
opening keeps its ephemeral key until it is answered or the session ends, so an answer the stem held up still
completes the session. An exit that answers after another one did is sent Close(DONE) and lets its relay go.
Sealing
SealingCipher is AES-256-GCM with the key of the direction. A plaintext is framed as its length (u32), its bytes
and zeros, and sealed to exactly the payload of the smallest size class that holds it with the 16-byte AEAD tag, so
the size tells nothing but the class. The associated data is the handshake hash. No counter travels: each end counts
the messages of a direction from 0.
| Nonce bytes | Content |
|---|---|
| 0 | d |
| 1 to 3 | Zero |
| 4 to 11 | Counter, big-endian |
An end looks out for the next 64 tags of its peer (Session.WINDOW) and opens only the peer’s entry under the next
one. An entry of the peer’s writer under a later tag, or one under the next tag that does not open, closes the
session BROKEN and tells the peer. An entry of another writer under a tag of the session, copied or forged, is
passed over and ends nothing.
Messages
SealedCodec puts one message in each sealed entry, starting with its type (u8). An address is a SOCKS5 type
byte (1 for IPv4 and 4 bytes, 3 for a domain with a u8 length and ASCII, 4 for IPv6 and 16 bytes) followed by a
u16 port.
| Type | After the type | Meaning |
|---|---|---|
1 Connect |
Command (u8), address | CONNECT (1) or UDP_ASSOCIATE (3) |
2 Connected |
Status (u8, 0 to 8), address | The exit’s SOCKS5 reply; the address is always 0.0.0.0:0, as where the exit connects from would point at it |
3 Data |
Bytes | TCP bytes, in order |
4 Datagram |
Address, bytes | The destination from the client, the source towards it |
5 Close |
Reason (u8) | DONE 0, IDLE 1, REFUSED 2, UNREACHABLE 3, BROKEN 4 |
A session carries one Connect, one Connected, Data or Datagram messages and a Close; anything else closes
it BROKEN at the exit. A Close ends a session even when it cannot be posted. A Close that arrives before the
reply fails the stream: REFUSED as 0x02, UNREACHABLE as 0x03, IDLE as 0x06, the others as 0x01.
States
Each end closes after sessionIdleSeconds without traffic either way, on a Close, on the drain of its chain and on
a gap in the peer’s feed: at the client any gap, at the exit one that names a tag the session looks out for.
ClientSession |
Left when |
|---|---|
NEW |
The opening is posted; or no exit fits, and it closes |
OPENING |
An answer to any of its openings completes the handshake; after 5 seconds a new attempt stays here, and after 3 attempts or with no exit left it closes |
OPEN |
A Close or the idle limit |
CLOSED |
Never |
ExitRelay |
Left when |
|---|---|
WAITING |
Connect(CONNECT) leads to CONNECTING, Connect(UDP_ASSOCIATE) to OPENING |
CONNECTING |
The destination is connected (RELAYING), or it was refused or failed (CLOSED) |
OPENING |
The datagram socket is bound (ASSOCIATED), or it failed (CLOSED) |
RELAYING, ASSOCIATED |
Either side closes (CLOSED) |
CLOSED |
Never |
ChainExit also closes IDLE a relay whose client has sent nothing within 30 seconds of the answer.
Exit side
ChainExit hands every entry a session of its own recognises to that session and its ExitRelay. Each session
writes as a writer of its own, made fresh, with an outbox of its own, so the feeds on a chain do not tell which
sessions one exit relays. The exit answers a client’s Close with one of its own, and its Close is the last entry
of that writer’s feed, with the end mark set. A chain the host rebuilt or dropped, or a feed whose outbox ended,
closes its relays BROKEN.
Limits. An exit answers an opening only while it holds fewer than 256 relays, fewer than 128 on that chain and
fewer than 64 for that client writer; while fewer than 256 writers of its sessions (MAX_WRITERS_PER_CHAIN, a
sixteenth of the 4096 a chain takes) hold a place on the chain; while the chain takes new writers; and while it has
a stem successor outside the members the client attached through. Beyond any of them it stays silent, as for an
opening that is not its own, and the client moves on after 5 seconds. The writers counted are those of its relays,
of the relays that drain and of the sessions that ended, until their Close is older than the retention window, or
for as long as the host holds the chain when that Close never got there.
Draining. A relay that ends before all it wrote is on the chain, as it waits in its outbox or on the stem, drains: it holds no place among the relays, and at most 256 drain at once, beyond which a relay that ends goes at once with what waited. A draining relay goes once all it wrote came back by flood, or after the retention of its chain, as what waited longer would be refused as too old.
Streams. On Connect(CONNECT) the relay checks the CONNECT flag of the spork it accepted the session under and
admits the destination. A name is looked up on a pool of 32 threads with a queue of 256, and a full pool answers
0x01; an IP literal skips the pool. NettyExitConnector tries up to 8 admitted addresses, one after the other, for
10 seconds each. A failure maps to 0x06 for a timeout, 0x04 for no route or no address left to try, 0x05 for a
refusal, 0x03 for any other socket error and 0x01 otherwise. Success answers Connected(SUCCESS, 0.0.0.0:0).
The relay reads the destination only while its outbox can write, which it learns once a second. It closes BROKEN
when more than 64 full Data messages wait for a slow destination, and answers the destination’s close with
Close(DONE). When the relay ends, the connection to the destination gets 5 seconds to take what is queued before
it is closed.
Associations. On Connect(UDP_ASSOCIATE) the relay checks the UDP flag, opens one datagram socket and
answers Connected(SUCCESS, 0.0.0.0:0). DatagramDestinations admits every destination before it sends to it, and
sends to the first checked address the socket can reach, never to the name. An association keeps 64 destinations,
4 lookups at once and 8 datagrams waiting for each lookup, and forgets a destination nothing was sent to for 60
seconds. A datagram to port 0 is dropped. A reply is taken only from an address and port the association sent to,
decided before its content is copied, and only when it fits one entry.
Local address. When the address a gridnode announces is an IP literal of one of its host’s interfaces
(ExitContexts.localAddressOf), the exit binds its TCP connections and datagram sockets to it, so the destination
sees the address the client chose the country by. Behind 1:1 NAT, as on many clouds, the announced address is no
interface’s: the exit binds nothing, and the datagram sockets take the wildcard address. Bound, the exit reaches
only addresses of its own family (AddressFamily) and never sends from another address instead: a name or literal
with none of that family gets 0x04, and a datagram to another family is dropped.
Refusals. A refused or failed Connect is answered Connected(status) and closed REFUSED for 0x02, 0x07
and 0x08, else UNREACHABLE; the client’s tunnel fails the stream with that status and closes the session.
The stem
What an exit writes travels a stem first, as in Dandelion++ (Fanti et al., 2018): it is passed on from member to
member in CHAIN_STEM packets before any member floods it, so the first member to flood an entry is seldom its
author. Clients do not stem, as the members they attach through know them anyway.
- Draws. Each member draws once for each writer, the first time it takes an entry of the writer into the stem,
and keeps the draw while the chain knows the writer (
StemRouter). A writer not its own is diffused or relayed with even odds: a diffuser floods a stem entry it receives, a relay passes it on. An exit relays its own writers always, so to a successor an author and a relay look alike. A draw that lasts keeps a member that watches who floods a writer’s entries first from narrowing down the members over time. - Successors. A relayed writer gets a primary and a secondary successor among the streams to the other members of the group, and every entry of it goes to the primary. A relay draws outside the members the writer’s entries came in from, the exit outside the two members the client of the session attached through: the first two member streams that flooded the client writer’s entries to it. Members count by their gridnode id, so one that comes back on a new stream is the same member, and none is drawn twice for one writer.
- Moves. A refusal or a round moves the writer to its secondary, once for its life, at the author and at a relay
alike. On
ENTRY_REFUSEDwithFULLfor an entry it stemmed, a member sends what it holds of that writer from the refused sequence number on, of what went to the refuser, to the secondary, unless one of those entries came from there; a refusal after the move only drops the route that refused. A successor that closes, leaves the group or, at the exit, turns out to be an attach member leaves the writer to the other one, which is no move. Only once both are gone is a member drawn in their place, at most twice for each writer. - Rounds. An entry that comes back from a stream other than one it was passed to or came in on has come round the stem. A relay floods it at once, which ends the round and tells nothing of the exit, as a relay is never the author; the author leaves it to the shorter timers of the relays. One that comes back from a member it was passed to is left to the timers and moves nothing, and one the member it came from sends again is dropped as a duplicate.
- Cap. A member keeps a draw for at most 4096 writers it relays, as many as a chain takes. A writer past them is routed by a keyed hash of its id, which gives its mode and its successor among the members other than the one it came from, and nothing moves it. The exit’s own writers count against no cap.
- Checks.
CHAIN_STEMis taken only from the member streams of a chain the node hosts, and counts against the same entry budget asCHAIN_ENTRY. Each entry is checked as a flooded one would be, signature included, without being appended; only one the chain would take, or hold past a gap, goes on. - Embargo. Every member that handles a stem entry, the author too, holds it in
StemEmbargountil it comes back by flood. A relay waits 2 seconds plus an exponential wait with a mean of 2 seconds, the author 4 plus one with a mean of 4, so a lost entry is almost always flooded by a relay first. At its deadline a relay floods what it passed on and did not get back, and what waits because its route is the stream it came in on. The author floods its own that it passed on and keeps them held under a new timer until they come back. - Stalls. A route whose stream takes no writes is skipped and stays the route: what it did not take waits and
goes to it as soon as it takes writes again (
StemStalls). At the deadline of an entry that never left, the writer moves off a route that stalled, one that took no writes for the whole wait or on which the entry missed two deadlines, its write there on its way or not. A stalled route counts as gone. A relay floods what never left only with no successor left, as for a writer routed by the hash once its route stalls. An exit whose writer has no successor left ends the outbox of the writer, and with it the session, rather than flood an entry of its own that never left, as the first to flood it would be its author. - Back-pressure. The exit appends its own entries to its replica, reports them in its heads and serves them in
reconcile only once they came back by flood. Before it stems one,
HostedChain.holdOwnchecks that the replica would take it, counting what the stem holds of its own; otherwise the outbox waits, as forFULL. - Relayed share. What a member holds for others may take max(
maxEntryBytes, capacity / 4), and at most half of the reserve beyond the capacity. The exit’s own entries leave that share free in the replica, so the two together never pass what the chain takes, and what is relayed never makes the exit’s own entriesFULL.
Destination policy
DestinationPolicy.publicOnly() is the only gate between a stranger’s request and a connection. Production gets
it from ExitContexts.production, and LoopbackPolicyIsTestOnlyTest asserts that only tests reach
allowLoopback(). For TCP and UDP alike it refuses these blocks, RFC 1918
among them:
| Family | Refused |
|---|---|
| IPv4 | 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.168.0.0/16, 198.18.0.0/15, 198.51.100.0/24, 203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4 |
| IPv6 | ::/128, ::1/128, fc00::/7, fe80::/10, fec0::/10, ff00::/8, 2001:db8::/32, 64:ff9b:1::/48 |
An IPv6 address that carries an IPv4 address is also held to the IPv4 rules: IPv4-mapped (::ffff:0:0/96),
IPv4-translated (::ffff:0:0:0/96), IPv4-compatible (::/96), NAT64 (64:ff9b::/96), 6to4 (2002::/16), Teredo
(2001::/32, both the server and the flipped client address) and ISATAP (interface identifier 0000:5efe or
0200:5efe under any prefix).
localhost and every name below it, in any case and with trailing dots, are refused without a lookup. A name is
looked up once and refused when any of its addresses is refused, so it cannot pair a public answer with a local one.
The exit connects only to the addresses it checked and never looks the name up again. A refusal answers 0x02, and
a name without addresses 0x04.
Tests
The module proxy has a unit test class per mechanism, in the same packages as the code, among them the SOCKS
pipeline on embedded channels, where HeldWrites holds writes back as a slow socket would, the Noise handshake
against a published test vector (NoiseNkTest) and the exit over loopback (ExitRelayLoopbackTest,
ExitRelayUdpLoopbackTest). In application, ChainFleet is an in-process fleet of gridnodes in one group, four
unless a test asks for more, that share one clock, topology and spork and exchange packets over embedded channels a
round at a time. ChainProxyEndToEndTest and ChainProxyUdpEndToEndTest run a SOCKS5 client through the front end,
a client node, the fleet and an exit to a TCP or UDP echo on loopback, covering countries, refusals, retries, idle
closes, fragments and strangers. ChainHostStemTest, StemRouterTest and StemEmbargoTest cover the stem, and
MmdbCountryLookupTest reads MaxMind’s GeoIP2-Country-Test.mmdb.
In the source
proxy/src/main/java/org/unigrid/hedgehog/proxy/: the packagessocks,group,chain,sessionandexitapplication/src/main/java/org/unigrid/hedgehog/:server/socks,model/option/SocksOptions.java,model/sporkfor the spork and its codecs,producerforChainProducerandTunnelProducer,model/geofor the countries,model/gridnodefor the sealing key, andservice/chainfor the client and exit sidesapplication/src/test/java/org/unigrid/hedgehog/service/chain/: the fleet and the end-to-end tests