Skip to content

EC: skip ZLIB compression on local peers - #728

Merged
mrjimenez merged 3 commits into
amule-project:masterfrom
got3nks:fix/ec-zlib-auto-disable-local
May 26, 2026
Merged

EC: skip ZLIB compression on local peers#728
mrjimenez merged 3 commits into
amule-project:masterfrom
got3nks:fix/ec-zlib-auto-disable-local

Conversation

@got3nks

@got3nks got3nks commented May 26, 2026

Copy link
Copy Markdown
Contributor

Summary

amuled currently negotiates EC zlib compression on every connection where the client advertises EC_TAG_CAN_ZLIB, and the server then compresses every outbound packet larger than 1 KB regardless of who the peer is. On loopback / LAN / link-local connections that's pure overhead — no transit time on lo, and on a gigabit LAN line rate (~125 MB/s) is already several times higher than the ~37 MB/s ceiling a streaming zlib pipeline hits on a typical CPU core.

This patch keeps zlib negotiation as-is and instead makes the per-packet send-time decision local-peer-aware:

  • peer is local (0.0.0.0, 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16) and packet ≤ 256 MB → send uncompressed (the perf win)
  • peer is local and packet > 256 MB → fall back to zlib so the wire size stays inside the receiver's ReadHeader gate
  • peer is remote → zlib as before

ReadHeader's historical 16 MB packet-size cap is also raised to 256 MB post-auth. Pre-auth keeps the historical 16 MB cap so an unauthenticated peer still can't make us allocate large buffers from one bogus header — the existing IsAuthorized() resize guard at ECSocket.cpp:581 is the actual security-relevant gate; 16 MB was just a sanity check that becomes too tight once a class of responses goes uncompressed.

SetLocalPeer(bool) on CECSocket records the locality decision the server makes at auth time. Client-side code is unchanged — the wire flag bits on each packet already control inflate-vs-raw on the receiving side.

Companion to #725 and #727 for issue #713 (slow EC responses on big libraries).

Test plan

  • amulegui on the same host as amuled — verify debug log shows EC peer 127.0.0.1 is local (loopback/LAN/link-local) — bypassing ZLIB for small/medium packets
  • amulegui on a LAN host (192.168.x.y) — same log line, with the LAN IP
  • amulecmd show shared on big libraries — confirmed by @Stoatwblr on his 91k-file shareset: uncompressed multi-MB response now passes the (raised) receiver gate and completes faster than the previous zlib-on-loopback path

amuled unconditionally negotiates EC_FLAG_ZLIB whenever the client
advertises EC_TAG_CAN_ZLIB. On loopback or LAN links this is pure
overhead: the EC payload (full library tag tree) deflates only ~2-3x
but adds a ~37 MB/s ceiling per direction on a typical CPU, well
below gigabit LAN line rate and meaningless on lo where there is no
transit cost at all.

Server-side gate: when the peer IP is 0.0.0.0, loopback (127/8), an
RFC1918 LAN range (10/8, 172.16/12, 192.168/16), or RFC3927
link-local (169.254/16), don't set EC_FLAG_ZLIB on the response side
even if the client offered it. This covers all client versions
(amulegui, amuleweb, amulecmd, third-party) without an EC pref
roundtrip and without a client-side rebuild.

Adds IsLoopbackIP() and IsLinkLocalIP() helpers next to IsLanIP() in
NetworkFunctions; same anti-host order convention.

Public WAN clients continue to negotiate ZLIB as before.
got3nks added 2 commits May 26, 2026 15:11
…56 MB

Initial commit gated ZLIB at auth time: if the peer is on loopback /
RFC1918 LAN / RFC3927 link-local, the server cleared EC_FLAG_ZLIB
from m_my_flags and every subsequent response went uncompressed.

That broke `amulecmd "show shared"` (and the initial amulegui /
amuleweb INC_UPDATE) on big libraries (issue amule-project#713 → Stoatwblr's 91k-
file shareset): the uncompressed response is 18-45 MB, and
CECSocket::ReadHeader has a hard 16 MB cap that closes the socket on
"oversize" packets. amulecmd would connect, send `show shared`, then
the daemon's response would arrive with `m_curr_packet_len` > 16 MB,
the client's ReadHeader would CloseSocket(), and the shell loop would
exit with "Ok, exiting…" — the symptom Stoatwblr reported.

Two complementary changes:

1. `ReadHeader` raises its cap to 256 MB post-auth. The pre-auth
   path keeps the historical 16 MB limit so a single bogus header
   from a not-yet-authorized peer can still only allocate a small
   bounded buffer (the existing IsAuthorized() guard on resize
   already enforces this). Post-auth we trust the peer enough to
   accept a larger announced size.

2. The local-peer ZLIB bypass moves from auth-time on m_my_flags to
   per-packet in CECSocket::WritePacket. The server now always
   negotiates ZLIB normally with the client (whatever the client
   advertised), then on each outbound packet:
     * peer is local AND packet <= 256 MB -> send uncompressed
     * peer is local AND packet  > 256 MB -> fall back to ZLIB so
       wire size stays under the receiver gate
     * peer is remote -> ZLIB as before
   This preserves the perf win (no deflate/inflate on loopback or
   LAN for the vast majority of responses) without ever producing a
   wire packet the receiver will reject.

SetLocalPeer(bool) on CECSocket records the locality decision the
server makes at auth time; ExternalConn's Authenticate calls it
unconditionally with the loopback/LAN/link-local result. Client-side
code is unchanged — the wire flag bits on each packet already
control inflate vs raw-read.
@got3nks
got3nks marked this pull request as ready for review May 26, 2026 22:12
@mrjimenez
mrjimenez merged commit 975f233 into amule-project:master May 26, 2026
7 checks passed
@got3nks
got3nks deleted the fix/ec-zlib-auto-disable-local branch May 27, 2026 15:15
@danim7

danim7 commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

I'm a bit late for this PR, but I think this approach is not optimal:

IMHO, the assumption that all LAN IP are high speed physical networks is too optimistic.

EC protocol is not a secure protocol: it has no authentication and data travel as plaintext, only the password is hashed, but without authentication that security is very weak.

The logical security mechanism when accessing from a remote location will be to use a VPN like WireGuard. This remote access through the public Internet may be quite slow.

Also, when using a VPN, the client will look like a LAN IP to the server, thus automatically disabling zlib. Actually, since EC is an insecure protocol, I don't expect anyone to connect using a public IP, hence being identified as "remote" by amuled.

Please note that WireGuard provides no compression! If compression is required, that must be provided by others protocol layers.

In my opinion, a better solution will keep zlib disabled for local peers as default, as this PR did. But it will give users the option to activate it at will. Ideally, the compression option will be chosen by the client application, not by the server, since the client knows better how is it connecting to the server. A possible approach could move the local peer decision to the client, and advertise or not zlib capability from the client side depending on the server IP. A command line parameter on the client would allow to bypass the local peers default. But if zlib is deactivated, that could break the >256MB packets that are currently always compressed, so this needs further thinking (maybe split the packet, or compress even if zlib support not advertised?)

Opinions?

@got3nks

got3nks commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Two reframings that change the right fix here:

  1. Zlib isn't a security mechanism. EC's security boundary is the auth handshake (EC_OP_AUTH_REQ / EC_OP_AUTH_PASSWD with the salted-hash exchange), not the compression layer that runs on top of an already-authenticated session. Whether zlib is on or off has no bearing on confidentiality, integrity, or auth strength. So removing it as a default doesn't weaken anything — it just removes a perf trade-off that we may be getting wrong in some topologies (your WireGuard-as-LAN case).

  2. Move the locality decision to the client. Keep the local-peer perf win, but flip who makes the call: the client knows the IP it dialed, the server only knows what came down the socket. Three pieces:

    a. EC_TAG_CAN_ZLIB stays always-advertised by every client. That's the capability tag — "I can inflate if you send me deflate". Since every amule build links zlib unconditionally, there's no real choice here, and keeping it always-on is what preserves the existing oversize fallback at ECSocket.cpp:797-803 — a packet larger than EC_MAX_UNCOMPRESSED still gets compressed regardless of preference, so receiver-gate-busting payloads keep working.

    b. New preference tag — EC_TAG_PREFER_NO_ZLIB — sent by the client at auth time. Client sets it when the dialed server IP is loopback / RFC1918 / RFC3927 link-local and /EC/ZLIB isn't explicitly forcing compression on. Users keep an explicit /EC/ZLIB=1 override (force compression — useful for WireGuard-as-LAN) and =0 (suppress everywhere).

    c. Server side: m_isLocalPeer gets driven by the presence of EC_TAG_PREFER_NO_ZLIB in the auth request instead of from peer-IP inspection. The per-packet send logic at ECSocket.cpp:785-803 stays identical — the only thing changing is who decides "is this peer local", and that side now actually knows whether the path between us is fast.

Net result: WireGuard-as-LAN works correctly (client knows its dialed IP is the VPN endpoint, not really "local" in transit terms; user can also explicitly /EC/ZLIB=1 to override). Plain loopback / LAN keeps its win. Oversize payloads keep compressing. No protocol break for existing clients — old clients that don't send the new tag look like "no preference = use zlib as before".

@got3nks

got3nks commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

Filed #840 — the client-side preference-tag design we discussed above, with the --force-zlib / GUI checkbox override for the WireGuard-as-LAN case. Both code paths verified end-to-end on a Debug build today: server logs EC peer 127.0.0.1 asked to skip ZLIB on default loopback connect, and the line is absent when --force-zlib is passed (server then compresses as remote). EC_TAG_CAN_ZLIB stays always-advertised so the oversize-payload fallback keeps working in both modes.

mrjimenez pushed a commit that referenced this pull request Jun 4, 2026
#728 made the daemon skip ZLIB on per-packet basis when the EC peer's
IP looked "local" (loopback / RFC1918 / link-local). The check lived on
the server side and was based on the peer-IP it saw on the socket,
which misclassifies common topologies — most importantly WireGuard /
Tailscale tunnel endpoints that resolve to RFC1918 addresses but whose
underlying transit is the public Internet. Those connections lost ZLIB
compression silently and paid the wire-size cost.

Move the locality decision to the client, which is the only side that
knows the IP it actually dialed:

  - New tag EC_TAG_PREFER_NO_ZLIB (0x0014) — a *preference*, not a
    capability. EC_TAG_CAN_ZLIB stays as the always-on capability so
    the oversize-payload fallback (>EC_MAX_UNCOMPRESSED forces ZLIB
    regardless of preference, see ECSocket.cpp:797) still kicks in
    when the receiver-gate would otherwise reject the packet.

  - CRemoteConnect::ConnectToCore resolves the dialed host and sets
    m_preferNoZlib when it points at a loopback / RFC1918 / link-local
    address. CECLoginPacket takes a new preferNoZlib param and emits
    the tag when set.

  - ExternalConn drops the peer-IP block in CECServerSocket::
    Authenticate; SetLocalPeer(true) now fires only when the client
    sent EC_TAG_PREFER_NO_ZLIB. Old clients omit the tag and get the
    pre-#728 always-ZLIB behaviour. Old servers ignore the unknown
    tag and treat the connection as remote (also pre-#728).

  - User override: amulegui dialog gets a "Force ZLIB compression"
    checkbox (persists under "Remember those settings" to
    /EC/ForceZLIB); amulecmd / amuleweb get --force-zlib and the
    same config key in remote.conf. When set, the locality check is
    skipped and the tag is never sent, so the server compresses as
    if we were remote. Use case: VPN-as-LAN topologies where the
    dialed IP looks local but transit isn't.

ECSocket.cpp's per-packet send logic (m_isLocalPeer / EC_FLAG_ZLIB /
oversize fallback) is unchanged; only the meaning of m_isLocalPeer's
input changes.

Closes the discussion thread on #728 (danim7 raised the WireGuard case).
got3nks added a commit to got3nks/amule that referenced this pull request Jun 4, 2026
…ndex

Adds 55+ merged PRs to the 3.0.0 changelog since the last update
(amule-project#747, 2026-05-27). Narrative additions cover:

- Packaging: expanded the top list to include the macOS per-arch .app
  bundles and the Windows NSIS installer alongside the existing
  AppImage / Flatpak / .dmg / .zip entries. New bullets for amule-project#785
  (alc/alcc/cas/wxcas everywhere + Windows amuleweb), amule-project#794 (.dmg
  amuleweb path), amule-project#789 (<OS>-<arch> artifact naming), amule-project#780 / amule-project#796
  (Windows DPI + comctl32 manifest), amule-project#784 (FHS share/amule paths).

- Bug Fixes & Stability: post-amule-project#744 fixes including EC notification
  leak (amule-project#797), big-library scaling (amule-project#736, amule-project#840 superseding amule-project#728),
  amulegui ghost entries (amule-project#810, amule-project#819, amule-project#841, amule-project#824, amule-project#830, amule-project#760),
  PartFile early hash (amule-project#762), server protocol fixes (amule-project#835, amule-project#788,
  amule-project#721, amule-project#787), crypto stream UB (amule-project#779), UAF prevention (amule-project#756),
  Kad rotation (amule-project#795, amule-project#799/amule-project#805), GTK warning silencing (amule-project#833,
  amule-project#826/amule-project#836), and the clang-tidy worklist (amule-project#770, amule-project#772-amule-project#774).

- Translations: late-cycle wave covering French/Turkish manpages
  (amule-project#753/amule-project#754/amule-project#776), Galician (amule-project#763), Slovenian (amule-project#771), pt-BR
  (amule-project#768/amule-project#775/amule-project#812), French (amule-project#811), plus man-page tooling for
  date+version drift (amule-project#802).

- Contributors: added ngosang for UX feedback on the late-3.0
  cycle (amule-project#817/amule-project#818/amule-project#821/amule-project#828/amule-project#844) and ongoing work on the
  user-facing manual at amule-org.github.io.

- Merged PRs flat index: extended with amule-project#746-amule-project#845 + amule-project#841.
got3nks added a commit to got3nks/amule that referenced this pull request Jun 4, 2026
…ndex

Adds 55+ merged PRs to the 3.0.0 changelog since the last update
(amule-project#747, 2026-05-27). Narrative additions cover:

- Packaging: expanded the top list to include the macOS per-arch .app
  bundles and the Windows NSIS installer alongside the existing
  AppImage / Flatpak / .dmg / .zip entries. New bullets for amule-project#785
  (alc/alcc/cas/wxcas everywhere + Windows amuleweb), amule-project#794 (.dmg
  amuleweb path), amule-project#789 (<OS>-<arch> artifact naming), amule-project#780 / amule-project#796
  (Windows DPI + comctl32 manifest), amule-project#784 (FHS share/amule paths).

- Bug Fixes & Stability: post-amule-project#744 fixes including EC notification
  leak (amule-project#797), big-library scaling (amule-project#736, amule-project#840 superseding amule-project#728),
  amulegui ghost entries (amule-project#810, amule-project#819, amule-project#841, amule-project#824, amule-project#830, amule-project#760),
  PartFile early hash (amule-project#762), server protocol fixes (amule-project#835, amule-project#788,
  amule-project#721, amule-project#787), crypto stream UB (amule-project#779), UAF prevention (amule-project#756),
  Kad rotation (amule-project#795, amule-project#799/amule-project#805), GTK warning silencing (amule-project#833,
  amule-project#826/amule-project#836), and the clang-tidy worklist (amule-project#770, amule-project#772-amule-project#774).

- Translations: late-cycle wave covering French/Turkish manpages
  (amule-project#753/amule-project#754/amule-project#776), Galician (amule-project#763), Slovenian (amule-project#771), pt-BR
  (amule-project#768/amule-project#775/amule-project#812), French (amule-project#811), plus man-page tooling for
  date+version drift (amule-project#802).

- Contributors: added ngosang for UX feedback on the late-3.0
  cycle (amule-project#817/amule-project#818/amule-project#821/amule-project#828/amule-project#844) and ongoing work on the
  user-facing manual at amule-org.github.io.

- Merged PRs flat index: extended with amule-project#746-amule-project#845 + amule-project#841.
mrjimenez pushed a commit that referenced this pull request Jun 4, 2026
Adds 55+ merged PRs to the 3.0.0 changelog since the last update
(#747, 2026-05-27). Narrative additions cover:

- Packaging: expanded the top list to include the macOS per-arch .app
  bundles and the Windows NSIS installer alongside the existing
  AppImage / Flatpak / .dmg / .zip entries. New bullets for #785
  (alc/alcc/cas/wxcas everywhere + Windows amuleweb), #794 (.dmg
  amuleweb path), #789 (<OS>-<arch> artifact naming), #780 / #796
  (Windows DPI + comctl32 manifest), #784 (FHS share/amule paths).

- Bug Fixes & Stability: post-#744 fixes including EC notification
  leak (#797), big-library scaling (#736, #840 superseding #728),
  amulegui ghost entries (#810, #819, #841, #824, #830, #760),
  PartFile early hash (#762), server protocol fixes (#835, #788,
  #721, #787), crypto stream UB (#779), UAF prevention (#756),
  Kad rotation (#795, #799/#805), GTK warning silencing (#833,
  #826/#836), and the clang-tidy worklist (#770, #772-#774).

- Translations: late-cycle wave covering French/Turkish manpages
  (#753/#754/#776), Galician (#763), Slovenian (#771), pt-BR
  (#768/#775/#812), French (#811), plus man-page tooling for
  date+version drift (#802).

- Contributors: added ngosang for UX feedback on the late-3.0
  cycle (#817/#818/#821/#828/#844) and ongoing work on the
  user-facing manual at amule-org.github.io.

- Merged PRs flat index: extended with #746-#845 + #841.
ngosang pushed a commit to ngosang/amule that referenced this pull request Jul 30, 2026
amule-project#728)

CUpDownClient had two accessors disagreeing about what a missing credits
object means. GetCurrentIdentState() treats it as IS_NOTAVAILABLE; the
five boolean predicates tested `credits &&` instead, so with credits
still NULL every one of them returned false -- including
SUINotSupported(), which is exactly the state the accessor was
reporting. credits is NULL from construction until the peer's user hash
resolves, so this is reachable, not theoretical.

The visible effect was in the Client Details dialog.
CClientRef::GetSecureIdentTextStatus() is an if/else-if chain over those
five predicates with no final else, and its result is assigned straight
into the label. All five false left the string empty, blanking the
field and overwriting the _("N/A") placeholder it was built with. The
same dialog reads "Not supported" in amulegui, whose predicates compare
m_identState directly and whose credits object is always allocated --
so the monolithic GUI was the odd one out, disagreeing with both
amulegui and the EC/Web API view, all of which go through
GetCurrentIdentState().

Define the predicates in terms of that accessor. The contradiction then
cannot recur: they are the accessor's answer rather than a second
opinion about it.

Only SUINotSupported() changes behaviour, and it has exactly one caller
in the tree -- the chain being fixed. For the other four the guard was
redundant: a NULL credits yields IS_NOTAVAILABLE, which already compares
unequal to the state each of them asks about, so their 6/4/1/1 call
sites are unaffected.

Give the chain a final else as well. It is unreachable while the five
predicates cover every EIdentState, but a sixth state would otherwise
silently blank the field again rather than fail visibly.

No new catalog string: the fallback reuses the _("N/A") the field is
already built with. The catalogs still move, because that msgid's
reference list gains src/ClientRef.cpp.

Not unit-tested: nothing currently links BaseClient/ClientRef, and
CUpDownClient's construction pulls a 49-include closure while the chain
needs theApp for CryptoAvailable(), so a harness would dwarf the fix.
Routing the predicates through the accessor makes the disagreement
unrepresentable instead, which a test could only sample for.
got3nks pushed a commit to got3nks/amule that referenced this pull request Jul 31, 2026
…r.cpp (amule-project#675) (amule-project#725)

* chore(gui): delete unreachable bitmap functions/entries from muuli_wdr.cpp

First slice of the icon-system cleanup scoped in amule-project#675: remove code with
zero call sites anywhere in the tree, before any wxArtProvider migration
work starts.

- muleToolbar(): whole function unused -- superseded by the main
  wxToolBar setup in amuleDlg.cpp; nothing calls it.
- moreImages(): whole function unused, both of its two icon entries.
- amuleDlgImages(): 21 of 35 index blocks have no caller anywhere
  (0-13, 16, 17, 19, 27, 28, 31, 34). The 14 live ones are untouched --
  10 of those (20-26, 29, 32, 33) are already the fallback path inside
  amuleDlg.cpp's Add_Skin_Icon, which prefers a wxArtProvider/SVG lookup
  first; the other 4 (14, 15, 18, 30) are still called directly.
- amuleSpecial(): 6 of 26 index blocks have no caller (6, 7, 8, 9, 18,
  20) -- checked both direct call sites and the PrefsUnifiedDlg.cpp
  fallback table (pages[].m_imageidx), which uses neither.

convert_xpm in PartFileConvertDlg.cpp was on the same "no literal
grep hits" list initially but is not actually dead -- SetIcon(wxICON
(convert)) reaches it via the wxICON macro's token-pasting
(X##_xpm), invisible to a plain identifier search. Caught by a full
build failing on the undeclared identifier, not by inspection; left
untouched.

Deletes 1373 lines (~16% of the file). No behavior change: every
touched entry was unreachable code. clang-format v18 clean; full
amule build verified (macOS, CLIENT_GUI unaffected since neither
touched symbol is CLIENT_GUI-only).

* chore(po): regenerate catalogs after muuli_wdr.cpp dead-code removal

muleToolbar() duplicated several msgid source-location references
(Networks, Searches, Downloads Window, etc.) already present via
amuleDlg.cpp's own toolbar setup. Deleting it drops those now-stale
#: comments; no msgid added, removed, or retranslated -- verified via
unchanged msgid count (1903 before/after) and a diff limited to
source-location comments and POT-Creation-Date.

Regenerated after rebasing onto upstream/master to pick up po/
changes from amule-project#723/amule-project#724/amule-project#726/amule-project#728/amule-project#730/amule-project#731, which had drifted our
prior regeneration out of sync.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants