Tunneling encapsulates one communication protocol within another. Selection begins by answering "what exactly needs to be transported": an unmodifiable Ethernet frame, an entire subnet's IP packets, or a single TCP connection for a specific service. These requirements differ in broadcast scope, routing behavior, and permission boundaries; they cannot be ranked solely by product performance.
Requirement
Primary Capability to Consider
Responsibilities Often Introduced
Transporting IP packets between two trusted networks
Encapsulation like IPIP, GRE
Reachability, filtering, MTU; add encryption if necessary
Connecting subnets over untrusted networks
Secure tunnels like WireGuard, IPsec
Keys, identities, routing, and return paths
Auto-managing device identities and changing networks
Overlay networks like Tailscale
Control plane, policy propagation, relay dependency
Building a Layer 2 network over a Layer 3 network
VXLAN, Geneve
MAC/neighbor learning, BUM traffic, control plane
Accessing a few internal services from outside
Reverse proxy or port forwarding
Public entry permissions, proxy trust, service recovery
"Deployability" also considers whether existing devices support it, whether operations can manage keys and routing, and whether the egress allows the required transport. Device support for IPsec offload does not prove that specific algorithms, encapsulation, and traffic are actually using the offload path.
IPIP and GRE: The Simplest IP Encapsulation Is Not the Same Protocol
IPv4-in-IPv4 directly adds a new IPv4 header before the original IPv4 packet, with the outer Protocol field set to 4; without options, this adds 20 B. GRE uses protocol number 47, with an additional 4 B base GRE header, where the Protocol Type field indicates the encapsulated protocol. Therefore, GRE is more versatile, but it cannot be called "IPIP without extra headers." IP-in-IP RFC 2003, GRE RFC 2784
GRE can carry fields such as Key, Sequence Number, or Checksum via extensions; its length should not be fixed at 4 B. The Key can distinguish logical tunnels but cannot serve as an authentication credential; an attacker can construct the same Key without knowing any encryption private key. Basic IPIP and GRE do not provide confidentiality for the payload.
Assuming an inner 1400 B IPv4 packet: IPv4-in-IPv4 without options is 1420 B, and basic GRE over IPv4 is 1424 B. This excludes the outer Ethernet header and does not include encryption. When comparing this number to "bytes occupied online," you must align the measurement scope to the same layer.
VXLAN and Geneve: The Tunnel Itself Is Not a Complete Virtual Network Control Plane
VXLAN encapsulates inner Ethernet frames into UDP. The base VXLAN header is 8 B, and the VNI is 24 bits. The VNI represents a logical network, not an encryption key; mis-joining with the same VNI still requires underlying isolation and ingress validation. The UDP source port can provide traffic hashing information for outer ECMP, but this does not mean every packet should randomly change paths. VXLAN RFC 7348
Geneve's base header is also 8 B, supporting variable-length options, making it suitable for carrying network virtualization metadata; computational overhead must include the actual option length. When encountering critical options that cannot be understood, processing rules affect whether the packet can be received; options cannot be treated as transparent decorations for all endpoints. Geneve RFC 8926
Encapsulation only explains how packets are transported, not where the remote MAC is, who unknown unicast should be sent to, or how broadcast is replicated. Learning, static configuration, or control methods like EVPN handle these issues. Layer 2 extension spreads broadcast and failure impact to more locations; if the application only requires unicast IP communication, first determine if it truly relies on the same broadcast domain.
Why VXLAN Does Not Require MTU to Be Exactly 9000
Taking the example of no VLAN, inner frames without FCS, and outer IPv4 without options: a 1500 B inner IP packet plus 14 B inner Ethernet, 8 B VXLAN, 8 B UDP, and 20 B outer IP forms a 1550 B outer IP packet. The path needs to support this size; 9000 is just one possible network configuration. You can also lower the inner MTU, but you cannot treat arbitrary fragmentation as a cost-free remedy.
inner_ip=1500encapsulation={'IPIP/IPv4':20,'GRE-basic/IPv4':20+4,'VXLAN/IPv4':20+8+8+14,'VXLAN/IPv6':40+8+8+14,'Geneve/IPv4+16B-options':20+8+8+16+14,}assertinner_ip+encapsulation['VXLAN/IPv4']==1550assert1500-encapsulation['VXLAN/IPv4']==1450assert1500-encapsulation['VXLAN/IPv6']==1430assert1500-encapsulation['Geneve/IPv4+16B-options']==1434# 1550 B outer IP packet plus outer Ethernet header and FCS; preamble and frame gap not yet calculated.
assert1550+14+4==1568print('Encapsulation budget calculated uniformly by outer IP length passed')
Inner VLANs, additional encryption, or second-layer tunnels continue to consume budget. TCP MSS only affects TCP data segments; lowering MSS does not automatically solve large UDP datagrams, nor does it replace PMTU handling for all paths.
Can a 1500-byte Inner Packet Fit Inside?
First compare VXLAN's IPv4 and IPv6 outer layers; the inner limit differs by 20 B. Then select ESP; increasing the inner length from 1438 to 1439 does not increase the outer by just 1 B, but jumps from 1500 to 1504 due to alignment. The budget must account for such discrete changes.
Preparing the visual
Can a 1500-byte Inner Packet Fit Inside?
Layer headers must be deducted sequentially from the same budget. VXLAN also includes the inner Ethernet header; ESP alignment causes total length to grow in steps.
{"id":"tunnel-layer-budget","title":"Can a 1500-byte Inner Packet Fit Inside?","summary":"Layer headers must be deducted sequentially from the same budget. VXLAN also includes the inner Ethernet header; ESP alignment causes total length to grow in steps.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"mode\"><button data-mode=\"gre\" data-i18n=\"gre\"></button><button data-mode=\"vx\" data-i18n=\"vx\"></button><button data-mode=\"esp\" data-i18n=\"esp\"></button></div><label class=\"form-field\"><span data-i18n=\"ip\"></span><select id=\"ip\"><option value=\"20\">IPv4 (20 B)</option><option value=\"40\">IPv6 (40 B)</option></select></label><div class=\"control\"><label for=\"inner\" data-i18n=\"inner\"></label><output for=\"inner\" id=\"inner-value\"></output><input id=\"inner\" type=\"range\" min=\"40\" max=\"1600\" value=\"1500\" step=\"1\"></div><div class=\"control\"><label for=\"mtu\" data-i18n=\"mtu\"></label><output for=\"mtu\" id=\"mtu-value\"></output><input id=\"mtu\" type=\"range\" min=\"1280\" max=\"1600\" value=\"1500\" step=\"1\"></div><div id=\"packet\" class=\"budget\"></div><div class=\"metrics\"><div><span data-i18n=\"total\"></span><strong id=\"total\"></strong></div><div><span data-i18n=\"ceiling\"></span><strong id=\"ceiling\"></strong></div></div><p id=\"equation\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let mode='vx';function size(n){const ip=+$('#ip').value;return mode==='gre'?n+ip+4:mode==='vx'?n+ip+30:n+ip+8+8+8+2+16+((4-(n+2)%4)%4)}function draw(){const n=+$('#inner').value,mtu=+$('#mtu').value,total=size(n);let ceiling=0;for(let i=0;i<=mtu;i++)if(size(i)<=mtu)ceiling=i;set('inner-value',n);set('mtu-value',mtu);band('packet',[{name:'inner IP',value:n},{name:'encapsulation',value:total-n}],Math.max(total,mtu));set('total',total+(total<=mtu?' ✓':' ✗'));set('ceiling',ceiling);set('equation','inner '+n+' + overhead '+(total-n)+' = '+total+'\\n'+total+' ≤ MTU '+mtu+' : '+String(total<=mtu));$('#total').dataset.value=total;$('#ceiling').dataset.value=ceiling;pressed(mode);}document.querySelectorAll('input,select').forEach(e=>e.oninput=draw);document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{mode=b.dataset.mode;draw()});draw();","audio":false,"strings":{"ceiling":"Max Usable Inner IP (B)","esp":"ESP-GCM NAT-T","gre":"GRE","heading":"Can a 1500-byte Inner Packet Fit Inside?","inner":"Inner IP Total Length (B)","intro":"When choosing encapsulation, outer IP version, and path MTU, the chart uniformly uses outer IP total length as the metric.","ip":"Outer IP Version","mode":"Encapsulation","mtu":"Path MTU (B)","reset":"Reset","scope":"No VLAN/FCS/IP options; GRE base 4 B; VXLAN 8+UDP 8+inner Ethernet 14; ESP AES-GCM/UDP includes 8 B IV, 16 B tag, 4-byte alignment. Based on single-packet budget, no fragmentation simulation.","scopeLabel":"Model Scope and Assumptions","total":"Outer IP Total Length (B)","vx":"VXLAN"}}
WireGuard, IPsec, and Platform Capabilities Should Be Compared Separately
WireGuard combines peer public keys, AllowedIPs, and the UDP data plane; the protocol itself lacks a complete user directory or universal automatic hole-punching service. IPsec/IKEv2 protects IP traffic via policies and SAs, offering a richer configuration space for authentication, algorithm negotiation, and device interoperability. Performance is determined by implementation, algorithm acceleration, packet length, core count, and link; there is no fixed champion across environments.
Tailscale adds identity, policy distribution, discovery, and relaying on top of WireGuard. The difference between it and bare WireGuard is not just a single throughput test; when comparing deployment costs, you must account for the workload of manually maintaining peers, public keys, and routes, as well as the dependency on the platform control plane.
The endpoints of "end-to-end encryption" must be clearly defined: two business hosts terminating the tunnel themselves are different from a tunnel between two gateways. The latter can continue over plaintext networks after leaving the gateway; application TLS can provide protection one layer further.
Reverse Tunnel: Internal Client Initiates Outbound, Public End Receives Access
In standard public port forwarding schemes, the internal proxy C actively connects to the public proxy S, S forwards the external connection to C, and then C connects to the local service. This avoids the requirement for direct inbound access to the internal network, but the public S still needs to be reachable, and C's outbound must also be permitted. There is no need to successfully establish a UDP hole-punch from the visitor to the internal network here.
Common deployments of rathole and frp both have server/client roles; it is inaccurate to write that the former is naturally symmetric and decentralized while the latter is centralized. The current rathole repository configuration supports TCP and UDP services and provides transport options like TLS and Noise; claims of "TCP only, encryption relies on Caddy" are inaccurate. rathole project, Transport Configuration
Comparison Item
rathole
frp
Common Deployment
Public server and internal client
frps and frpc
Service Capability
Configure TCP/UDP forwarding per service
TCP/UDP, HTTP/HTTPS, and other proxy types
Transport Protection
Configurable TLS or Noise
Supports TLS and other transport configurations
Connection Reuse/Pool
Check based on version and implementation
Documentation explicitly supports TCP reuse and connection pooling
Resource Assessment
Measure using own concurrency, packet length, and buffers
Measure using same load and version metrics
Product support for UDP services is different from "proxy-to-proxy necessarily uses native UDP transport." If UDP data is carried over a reliable stream, packet loss and head-of-line blocking behaviors will change; if multiple service connections share a single TCP transport, outer packet loss recovery may affect other multiplexed connections. frp Network Configuration
Why Connection Pools Cannot Eliminate All Handshakes
A pool moves some connection preparation work to before the business request arrives, reducing wait time on hit; reuse allows multiple logical connections to share an existing transport. frp supports both mechanisms, so "rathole pre-builds, frp always creates new TCP per request" cannot be used as the fundamental difference between the two.
Even if the S—C channel is ready, the connection from the visitor to S, the connection from C to the local service, and the application TLS/login steps may still exist. When pooled connections become invalid, detection and replenishment are needed; early eof cannot be uniquely attributed to NAT idle timeout; it may also come from peer closure, process exit, or protocol mismatch.
For example, if a request requires establishing a channel on demand, adding an extra wait of 40 ms, and the prepared channel hit rate is 90%, the average wait for this item alone is 4 ms. The remaining 10% of requests still have a 40 ms wait; the average cannot be used to claim tail latency has disappeared. Larger pools also consume more FDs, memory, and gateway state.
Ingress TLS and Tunnel Encryption: Two Different Termination Points
If Caddy terminates browser TLS at S, S can see HTTP plaintext; enabling TLS/Noise for S—C can continue to protect the transport, but it does not turn S back into a relay that cannot see business content. If application TLS is passed through from the visitor to the origin, S only forwards the corresponding ciphertext, and the origin is responsible for certificates and application authentication.
The proxy registration token is used to prove that C has the right to register a specific forwarding service; it does not automatically verify every user accessing the public listening port. When exposing public management pages, database ports, or file services, access control needs to be implemented at the actual entry or application; providing a single token to both tunnel sides does not mean the service is privatized.
Making a Choice with a Complete Scenario
Assume you need to allow three colleagues to access the management page of a home NAS. Visitors can install clients, and public access is not required. First, compare the member authorization, client support, and recovery costs of device overlay network solutions; there is no need to first open the management page to the public network and then add extra ingress authentication.
If the goal changes to a public demo site that requires no client installation, a reverse HTTP proxy fits the ingress form better; next, determine the TLS endpoint, domain name, origin identity, and request size/timeout. If the goal changes to connecting two subnets with many IP services, then compare gateway VPN, routing, and return paths. If the application must rely on Layer 2 discovery or specific Ethernet semantics, then continue to evaluate Layer 2 overlay networks and broadcast boundaries.
These judgments are not a permanent product ranking. An evaluation report should at least provide: who the data actually passes through, who can decrypt it, what is allowed to be accessed, the maximum effective packet length, memory usage and tail latency under full load, and how new and old services behave after control connection interruption. Writing only "lightweight, low latency, enterprise-grade" does not allow for reproduction or trade-off decisions.