-
Notifications
You must be signed in to change notification settings - Fork 10
NAT: use a reserved source port for forwards to rsh/rlogin #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
techomancer
merged 2 commits into
techomancer:main
from
tenox7:nat-rsh-reserved-source-port
Jul 21, 2026
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Inbound port-forwards to rsh/rlogin need a reserved source port | ||
|
|
||
| ## Symptom | ||
|
|
||
| `rsh` into the guest through a `[[port_forward]]` to guest port 514 always | ||
| fails, while telnet through the same mechanism works fine. `rshd` closes the | ||
| connection without writing a single byte, so a client that reports the server's | ||
| own framing has nothing to show: | ||
|
|
||
| ``` | ||
| rsh: server closed connection without response | ||
| ``` | ||
|
|
||
| `/var/adm/SYSLOG` on the guest has the real reason: | ||
|
|
||
| ``` | ||
| rshd[123]: Connection from 192.168.0.1 on illegal port 49152 | ||
| ``` | ||
|
|
||
| ## Cause | ||
|
|
||
| `poll_tcp_fwd_listeners` synthesizes the SYN it injects into the guest, and it | ||
| allocated the source port from `fwd_ephemeral_next`, which starts at 49152. The | ||
| guest never sees the host client's real source port — only the one we make up. | ||
|
|
||
| BSD r-services authenticate with `.rhosts`/`hosts.equiv` trust, which is only | ||
| meaningful if the client proved it was root by binding a reserved port. So | ||
| `rshd` rejects anything outside 512..1023 *before reading the request*: | ||
|
|
||
| ```c | ||
| if (fromp->sin_port >= IPPORT_RESERVED || fromp->sin_port < IPPORT_RESERVED/2) | ||
| exit(1); /* "Connection from %s on illegal port" */ | ||
| ``` | ||
|
|
||
| A client that binds a reserved port on the host — as a correct rsh does — makes | ||
| no difference, because the NAT discards it. This is also why running the client | ||
| under `sudo` changes nothing, which makes the failure look unrelated to ports. | ||
|
|
||
| ## 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. | ||
|
|
||
| ## Guest-side setup this still needs | ||
|
|
||
| The forward makes the connection appear to come from the gateway | ||
| (192.168.0.1), and reverse DNS for it fails (queries go to the upstream | ||
| resolver, which knows nothing about RFC1918 space). Trust must be granted to | ||
| the gateway: | ||
|
|
||
| - `/etc/inetd.conf`: `shell stream tcp nowait root /usr/etc/rshd rshd` | ||
| - `/etc/hosts`: `192.168.0.1 gateway` | ||
| - `~/.rhosts`, mode 600 — `/.rhosts` for root, since `hosts.equiv` never | ||
| applies to root: `gateway <local-username>` | ||
|
|
||
| ## The stderr channel | ||
|
|
||
| The rsh protocol's second connection is a reverse one: the client passes a port | ||
| number and `rshd` dials *back* to it from a `rresvport()`. That direction | ||
| already lands correctly — `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 the reverse connection's source port is | ||
| reserved too. Clients that send `0` as the stderr port (`rcp` does, as do most | ||
| modern implementations) sidestep this entirely. Supporting the stderr channel | ||
| would require the outbound NAT connect to bind a reserved port, i.e. root on | ||
| the host. | ||
|
|
||
| ## 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 | ||
| ``` | ||
|
|
||
| Use `127.0.0.1`, not `localhost`: `bind = "localhost"` binds IPv4 only, so a | ||
| client that resolves `localhost` to `::1` gets ECONNREFUSED — a failure that | ||
| looks exactly like the forward not being there. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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 intcp_fwd_pending,tcp_nat, ortcp_twfor that guest port, orNoneif 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.