Sunday, April 12, 2026

Re: Routing to locally connected off-subnet host address

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

No comments:

Post a Comment