Skip to content

NAT: use a reserved source port for forwards to rsh/rlogin#56

Open
tenox7 wants to merge 2 commits into
techomancer:mainfrom
tenox7:nat-rsh-reserved-source-port
Open

NAT: use a reserved source port for forwards to rsh/rlogin#56
tenox7 wants to merge 2 commits into
techomancer:mainfrom
tenox7:nat-rsh-reserved-source-port

Conversation

@tenox7

@tenox7 tenox7 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Problem

rsh into the guest through a [[port_forward]] to guest port 514 fails every
time, while telnet through the same mechanism works. rshd closes the
connection without writing a byte, and /var/adm/SYSLOG on the guest shows why:

rshd[123]: Connection from 192.168.0.1 on illegal port 49152

poll_tcp_fwd_listeners synthesizes the SYN it injects into the guest and took
the source port from fwd_ephemeral_next, which starts at 49152. BSD
r-services authenticate via .rhosts/hosts.equiv trust, which only means
anything if the client proved it was root by binding a reserved port, so rshd
rejects anything outside 512..1023 before reading the request:

if (fromp->sin_port >= IPPORT_RESERVED || fromp->sin_port < IPPORT_RESERVED/2)
        exit(1);        /* "Connection from %s on illegal port" */

The host client's own source port never reaches the guest, so a client that
correctly binds a reserved port — or is run under sudo — behaves identically.
That's what makes this one hard to spot from the client side.

Fix

Allocate the injected source port from a separate 512..1023 counter when the
forward targets 513/514. 512 ports is ample; the NAT key
(guest_ip, guest_port, sport) stays unique.

Verified

IRIX 5.3, NAT mode, forward 2514 → 514:

$ rsh -p 2514 root@127.0.0.1 uname -a
IRIX IRIS 5.3 12200159 IP22 mips

Notes

Guest-side trust must be granted to the gateway, since that's who the
connection appears to come from (documented in HELP.md and the new rules note):

echo '192.168.0.1 gateway' >> /etc/hosts
echo 'gateway yourname'     > /.rhosts
chmod 600 /.rhosts

The rsh stderr channel still won't work, and this PR doesn't try to fix it.
The reverse connection already lands in the right place — nfs_remap_dst maps
guest→192.168.0.1:N onto host 127.0.0.1:N — but the NAT rewrites its source
port to an OS-chosen ephemeral, and rcmd(3) clients check that port is
reserved too. Supporting it would mean binding a reserved port on the outbound
NAT connect, i.e. root on the host. Clients that send 0 as the stderr port
(rcp, and most modern implementations) are unaffected.

Also worth knowing: use 127.0.0.1, not localhostbind = "localhost"
binds IPv4 only, so a client resolving localhost to ::1 gets ECONNREFUSED,
which looks exactly like the forward not existing.

Inbound port-forwards synthesize the SYN injected into the guest and took the
source port from fwd_ephemeral_next (49152+). BSD r-services reject any client
whose source port falls outside 512..1023, and they do it before reading the
request, so rshd hung up on every forwarded connection while telnet through the
same mechanism worked fine.

The host client's own source port never reaches the guest, so a client binding a
reserved port (or being run as root) made no difference -- which made the
failure look unrelated to ports.

Allocate from a separate 512..1023 counter when the forward targets 513/514.
512 ports is ample; the NAT key (guest_ip, guest_port, sport) stays unique.

Verified on IRIX 5.3 with a 2514 -> 514 forward:

    $ rsh -p 2514 root@127.0.0.1 uname -a
    IRIX IRIS 5.3 12200159 IP22 mips
Copilot AI review requested due to automatic review settings July 17, 2026 11:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adjusts the NAT inbound TCP port-forward path so that forwards targeting BSD r-services (rsh/rlogin) use a reserved source port, matching the guest-side security expectations of rshd/rlogind.

Changes:

  • Add a dedicated reserved-port allocator (512..=1023) for inbound forwards targeting guest ports 513/514.
  • Use the reserved allocator when synthesizing the forwarded SYN in poll_tcp_fwd_listeners.
  • Document the behavior and guest-side setup in HELP.md and add a dedicated rules note.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/net.rs Adds fwd_reserved_next and uses reserved source ports for forwards to guest 513/514 when synthesizing inbound SYNs.
rules/irix/rsh-forward-needs-reserved-source-port.md New troubleshooting/design note documenting why reserved source ports are required for rsh/rlogin forwards.
HELP.md Adds user-facing documentation for rsh/rlogin forwarding requirements and limitations (stderr channel).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/net.rs Outdated
Comment on lines +2400 to +2405
// rshd/rlogind reject a client whose source port isn't reserved, and the
// guest only ever sees the port synthesized here, not the host client's.
let ephemeral = if matches!(guest_port, 513 | 514) {
let p = self.fwd_reserved_next;
self.fwd_reserved_next = if p >= 1023 { 512 } else { p + 1 };
p

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in b3b9c2d.

The port is now allocated by alloc_fwd_sport(), which probes the range and returns the first candidate with no live entry in tcp_fwd_pending, tcp_nat, or tcp_tw for that guest port, or None if the whole range is occupied (the caller then drops the accept). This covers the ephemeral range too, since it had the same latent bug with a larger range. I also reordered so an accept dropped for an unknown guest MAC no longer burns a port.

Verified on IRIX 5.3: 8 concurrent rsh sessions each get a distinct reserved source port with no collision or overwrite.

The reserved (512..=1023) and ephemeral (49152..) source-port counters for
inbound forwards advanced blindly. Since the chosen port is part of the
tcp_fwd_pending / tcp_nat / tcp_tw keys, a wrapped counter could reuse a port
still live for the same guest port and silently overwrite that entry via
HashMap::insert, breaking the active connection. Only 512 reserved ports exist,
so rsh/rlogin is where this bites first.

Add alloc_fwd_sport(), which probes the range and returns the first port with
no live entry in any of the three maps, or None if the range is exhausted (the
caller then drops the accept). Also reorder so an accept dropped for an unknown
guest MAC no longer consumes a port.

Verified on IRIX 5.3: 8 concurrent rsh sessions each get a distinct reserved
source port with no collision.
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.

2 participants