On Fri, 10 Apr 2026, Kenneth Gober wrote:
OpenBSD routes packets as it's told, but it takes its instructions from
the packets being routed. You want to give OpenBSD alternative
instructions. This is doable but you need to be clear about when OpenBSD
should override what was asked.
I'm not quite seeing how there's any overriding here. At the philosophical
level I'd have to disagree that the machine may take instructions from the
packets; that sounds like an inversion of authority (picture a traffic
signal taking its instructions from the cars rather than the other way
around).
Packets are labeled with destination addresses; the operator chooses where
next to direct those for any given address by constructing a routing
table; then the machine executes those instructions.
Thus, the problem here to my mind is the arbitrary imposition of some
unstated restriction that the destination or next hop must share a subnet
with the local interface's address. I imagine nobody much minded such
restriction back when globally routable IPs were handed out for the asking
like candy on Halloween, but now that their value is indisputable and even
readily measurable, economy is forcing norms to change.
If you give the host the public address then that host needs to be "on"
the Internet. In my experience this is normally done by setting up a
"dmz" network for hosts to attach to, which is in turn bridged to your
Internet uplink through some kind of firewall (e.g. OpenBSD configured
for bridge filtering).
Yeah, I'm not keen on bridging the broadcast domains though; part of the
point of a router is to keep those separate. I understand a bridge can be
filtered too but I lack experience with that; it seems like yet more
possible things to go wrong.
If you want to give a host multiple addresses, the general practice is
to use multiple interfaces. The host interface connected to your
internal network will have its private IP address, and the host
interface connected to the dmz network will have its public IP address.
You can have a router on the internal network that routes packets from
that host to the Internet, and you can have a bridging firewall that
bridges the dmz network to the actual Internet. You can even have a
single OpenBSD gateway do both, but it is most conveniently done by
having 4 interfaces on your OpenBSD gateway: two interfaces configured
for IP forwarding with NAT, and two interfaces set up for bridging. And
your PF rules would apply the appropriate rules based on whether the
bridging or forwarding interfaces were in use.
Four separate interfaces, yikes. Those aren't free either when it comes to
physical iron.
If the host is trying to use both IP addresses on the same interface,
then when you receive packets on the internal network from that host,
you will need to do something different for each packet depending on
whether the packet has its source IP set to the private or public
address. Effectively some packets will need to be routed while other
packets will need to be bridged.
In this case it's not important for public hosts to talk internally to
private ones; it's fine (and even preferable) if that flow has to bounce
through the router. So the end hosts only need a single address and
default route.
The key complication here is that your router needs to know how to
forward replies.
Correct, that's the only part not working.
When routing, that means your router needs to have an interface on the
correct network, which in this case means your router's internal
interface needs to have an external IP address so it can send packets to
other external IP addresses that happen to be held by internal hosts.
Why? It has a physical interface on the correct physical network, of
course. And it seems I'm quite able to send traffic to openbsd.org despite
its being outside the subnet of any of my router's interfaces.
Which in turn means your routing table has to know that some external
IPs get routed out the external interface, while other external IPs get
routed out the internal interface. This is not how routing is normally
set up, for good reasons, but you've been handling it with -iface.
What reasons?
-iface did not work, as detailed in my first message.
"Some IPs go here while other IPs go there" seems to me exactly what
routing normally does. When multiple routes match a given destination, the
more specific one (longer netmask) wins. That's why internal addresses go
out the internal interface despite their also technically matching the
default route (/0) on the external. The most specific possible route is a
host route (/32), and it works, except in this specific case where the
destination is directly connected.
In this specific case, when you see that an internal host is initiating
an outbound connection from its private address, you can NAT that
connection so that on the outside it appears to have come from the
public IP instead of the private IP. But instead of using the IP of your
NAT pool, use the dedicated public IP for that specific host instead:
Might work, but still a workaround rather than a solution, and also not a
free option. It would be slipping into the business of stateful packet
mangling rather than providing a free and clear connection.
I have made perhaps some progress; ignoring the ISP uplink for now, I can
create the desired internal route to public addresses:
# route add -net 1.2.3.0/24 -cloning -iface 10.0.0.1
This succeeds, with flags UCS. Then a "ping 1.2.3.4" succeeds, resolving
ARP and adding the temporary cloned host route (UHLc) with MAC address.
Behold, local delivery without holding an address on the target network.
The key is that it only works for a network route, not a host route (and
of course you can't make a -net with size /32; I tried).
But now the problem is that on the uplink, I don't even have a separate
public address for reaching the ISP router; it's all in this one block.
(I'm using published/proxy ARP entries on cnmac0 to bring the traffic from
upstream into the router.) So now there will be a true route collision
between internal and external interfaces, unless I delete it from the
external and then I'm back to the original problem of off-subnet delivery
to a specific host (the upstream router). AFAIK I can't use a /31 due to
misalignment, as they're on .1 and .0 would be disallowed as the "network
address" (another restriction of dubious value these days).
Perhaps it all reduces to how to do the equivalent of Linux "ifconfig
pointopoint"? It even looks possible:
ifconfig [-AaC] [interface] [address_family] [address [dest_address]]
[parameters]
dest_address
Specify the address of the correspondent on the other end of a
point-to-point link.
But no,
# ifconfig cnmac0 1.2.3.2 1.2.3.1 netmask 255.255.255.255
# route get 1.2.3.1
route: writing to routing socket: No such process
and there's no sign of 1.2.3.1 in the routing table or ifconfig output,
making me wonder what dest_address is even for or where it ends up.
Maybe I can set cnmac0 to 1.2.3.0/31 since upstream is on .1 and .0 isn't
otherwise usable... or somehow convince upstream to set up private IPs for
the routers.
Jacob