Skip to content

Add NetworkConfiguration::add_port_forwarding_rule - #12

Merged
edigaryev merged 1 commit into
edigaryev:mainfrom
simonisjp:network-configuration-port-forwarding
Oct 27, 2026
Merged

edigaryev merged 1 commit into
edigaryev:mainfrom
simonisjp:network-configuration-port-forwarding

Conversation

@simonisjp

@simonisjp simonisjp commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR

Adds NetworkConfiguration::add_port_forwarding_rule, a binding for macOS 26's vmnet_network_configuration_add_port_forwarding_rule. On a network created with Network::new, it's the only way I found to forward a host port to a guest: the interface-level forwarding calls fail there. softnet needs it to fix --expose (openai/softnet#222).

Details

The problem. For an interface attached to a network (Interface::with_network), Interface::port_forwarding_rule_add returns VmnetErrorStatus(Failure). On macOS 27.0.1 the same happens for port_forwarding_rules and port_forwarding_rule_remove, in both Shared and Host mode. That includes rules added through the configuration, even though Apple's header documents list and remove for that case. Forwards for these networks therefore have to be declared on the NetworkConfiguration before Network::new.

The change.

  • src/ffi/vmnet.rs: declares vmnet_network_configuration_add_port_forwarding_rule exactly as in the macOS 27 SDK's vmnet.h. This function takes internal_port before external_port, the opposite of the interface variant, so the declaration has a comment saying so.
  • src/network.rs: adds NetworkConfiguration::add_port_forwarding_rule. Its arguments and its AddressFamilyMismatch check match Interface::port_forwarding_rule_add, so it looks the same to callers. The doc covers what callers need: add rules before creating the network, and use an address inside the network's subnet.
  • Tests:
    • network::tests::port_forwarding_rule_address_family_mismatch checks both mismatch directions.
    • interface::tests::network_port_forwarding configures two rules, creates the network and attaches an interface. Because of the problem above, it can't read the rules back, so the forwarding itself was checked end to end (below).

Open question. The address-family match now appears in both Interface::port_forwarding_rule_add and here. I'm happy to move it into a shared helper, in this PR or a follow-up, if you'd prefer.

Verification

On an Apple silicon Mac mini running macOS 27.0.1 (Xcode 27 SDK):

  • Checks: cargo fmt --check and cargo clippy --all-targets -- -D warnings are clean, and cargo doc builds with RUSTDOCFLAGS="-D warnings".
  • Tests: cargo test -- --test-threads 1 passes (all 14 tests, including the two new ones), on top of 0.9.0.
  • End to end, through softnet (Put --expose port forwards on the vmnet network configuration openai/softnet#223):
    • --expose 2222:22 produces rdr on en0 inet proto tcp from any to (en0:0) port = 2222 -> <guest> port 22 in com.apple.internet-sharing/port-mapping.
    • The guest's sshd answers on the host's port 2222 from another network.
    • The rdr line also confirms the port order.

Happy to adjust naming or shape. Questions are welcome here.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread src/ffi/vmnet.rs
) -> VmnetReturnT;
// internal_port comes before external_port here, unlike in
// vmnet_interface_add_ip_port_forwarding_rule
pub fn vmnet_network_configuration_add_port_forwarding_rule(

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.

Declared exactly as in the macOS 27 SDK's vmnet.h (available since macOS 26): (config, uint8_t protocol, sa_family_t address_family, uint16_t internal_port, uint16_t external_port, void const *internal_address). sa_family_t is __uint8_t on macOS, hence u8.

The port order is internal-then-external, the reverse of vmnet_interface_add_ip_port_forwarding_rule. That's what the comment above is for.

Comment thread src/network.rs
Ok(())
}

/// Adds a port forwarding rule to the network.

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.

The doc only covers what a caller needs: add rules before Network::new, and target an address inside the network's subnet.

The macOS 27.0.1 observation that listing and removing through the interface also fail is in the PR description rather than here, because Apple documents those calls as working.

Comment thread src/network.rs
/// known address.
///
/// [`Interface::port_forwarding_rule_add`]: crate::Interface::port_forwarding_rule_add
pub fn add_port_forwarding_rule(

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.

Same arguments, order and AddressFamilyMismatch check as Interface::port_forwarding_rule_add, so the two read the same to callers. The call below maps them onto the C function's internal-before-external order.

Comment thread src/network.rs
}

#[test]
fn port_forwarding_rule_address_family_mismatch() {

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.

Covers both mismatch directions. The check runs before any FFI call, so this test doesn't create a network.

Comment thread src/interface.rs
.add_port_forwarding_rule(AddressFamily::Ipv4, Protocol::Tcp, 8080, addr, 80)
.unwrap();

// Create the network and attach an interface. The interface port

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.

This test can't read the rules back, because port_forwarding_rules returns Failure on an interface attached to a network. So it checks that configure → create network → attach interface succeeds with rules present.

The forwarding itself was verified end to end through softnet: an rdr rule appears for the port, and traffic reaches the guest.

@edigaryev
edigaryev merged commit 0bd08f2 into edigaryev:main Oct 27, 2026
3 checks passed
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