On Thu, Dec 12, 2024 at 06:01:37PM -0400, Christopher Sean Hilton wrote:
> Hi,
> 
> I'm trying to setup a pair of OpenBSD machines to handle their respective 
> home networks and
> create a IKEv2 VPN tunnel between them. If I call one side _home_ and one 
> side _remote_ I
> think that defines things. The main function of the tunnel is to allow stuff 
> on the _remote_
> network to access services in the _home_ network. As a second function, I 
> want a handful of
> hosts in the _remote_ network to consume the internet via the _home_ 
> network's ISP. My
> `iked.conf` files look like this:
> 
>     ```
>     ## Home: (responder)
>     
>     home_network="192.168.1.0/24"
>     remote_network="192.168.2.0/24" 
>     
>     ikev2 passive esp \
>         from any to dynamic \
>         from $home_network to $remote_network \
>         ...
>         config address 192.168.128.16/32 \
>         config access-server 192.168.128.1
>         
> 
>     ## ## Remote: (Initiator)
>     ## ikev2 passive esp \
>     ##     from dynamic to any\
>     ##     from $remote_network to $home_network \
>     ##     ...
>     ##     request address any \
>     ##     iface enc0
>     ```
> 
> I've shown both configs here. The _remote_ config is commented out. The 
> otherside
> `iked.conf` is vice-versa.
> 
> This gets the tunnel up and running. All works as I expect it to and when I 
> do this:
> 
>     ```
>     # traceroute -s 192.168.128.16 8.8.8.8
>     ...
>     ```
>     
> The traceroute goes over the VPN tunner first as I expect it to. I figured, 
> **incorrectly**
> that at this point it would be just a matter of some _pf_ magic to get a host 
> on the remote
> side NATted to tunnel address such that it's packets would traverse the 
> tunnel and then
> shuffle off to their designed destination. I've tried this:
> 
>     ```
>     ## pf.conf
>     
>     ext_if=em0
>     vpn_if=enc0
> 
>     match out on $ext_if from !($ext_if) to any tag "USE-PLAIN-NAT"
>     match out on $vpn_if from <full-vpn> to any tag "USE-FULL-VPN"
>     
>     match out on $ext_if tagged "USE-PLAIN-NAT" nat-to ($ext_if)
>     
>     ...
>     
>     match out on $vpn_if tagged "USE-FULL-VPN" nat-to ($vpn_if)
>     
>     ```
> 
> But I get no joy. At best, the packets which should be tagged "USE-FULL-VPN" 
> get natted and
> emitted out of my "$ext_if". I'm clearly missing something.

the thing i think you're missing is that enc0 is not a real interface.
it largely exists so you can see what the ipsec stack is doing with
things like tcpdump via bpf. however, assigning an IP to it and
expecting to be able to route over it is not supported, even if some of
that appears to work.

> I'm referencing these links in the web:
> 
> * https://www.openbsd.org/faq/faq17.html
> * https://man.openbsd.org/iked.conf
> 
> As my gotos but I'm clearly missing some which may be really obvious. As an 
> aside, In a VPN
> situation like this, how does the kernel make decisions about where the 
> packets pass
> through? 

ok. ive written this up before, so i'll paste it and tweak it here:

For a packet going through an OpenBSD router, these are the main
steps:

1. Packet is received by the incoming network interface
2. Packet is shown to BPF
3. PF processing for incoming packets
4. IP routing/stack processing
5. PF processing for outgoing packets
6. Packet is shown to BPF on outgoing interface
7. Transmission on the outgoing interface

There are a couple of interesting things to note here.

PF is run twice for packets going through a router/firewall. Once when
the packet is received by a network interface and before the IP stack,
and again when the packet leaves the IP stack and goes out to a network
interface.

The outgoing network interface for a packet is decided by the IP stack,
and that decision is based entirely on the destination IP address in the
packet. This means if you want an IP packet to go out a particular
network interface, you add a route for those packets. You can see what
route decision will be made for an address by using route -n get $IP.

Just to be clear, the source IP or the network interface a packet was
received on does not affect the route lookup performed by the IP stack,
it is only the destination IP address that is used. Also, packets in
each direction of a connection are routed independently, meaning replies
need to be routed correctly too.

Generally, by the time pf gets to see a packet going out an interface,
it is too late to affect where it's going because that decision has
already been made by the route lookup in the IP stack.

BPF is used by programs like tcpdump, and allows you to observe the
packets as they have been received by or about to be transmitted by the
network interface. This is useful for verifying what packets are handled
where, and what addresses are involved before and after the IP stack and
PF processing is done.


these steps ignore ipsec processing though. the ipsec policy database
(SPD) is consulted between steps 4 and 5 above. if a packet matches the
SPD, it's taken away from the stack processing, encrypted (and shown to
bpf on enc0) and then injected back into the stack at step 4 so it can
figure out where the encrypted packet is supposed to be routed to.

the stuff above also ignores what pf can do to a packet. if pf rewrites
or reroutes a packet in step 5, the packet is basically taken back
to step 4 for a new route lookup, and then skips step 5 again.

so what does this mean for what you're trying to achieve?

firstly, if you want to send packets from hosts in the <full-vpn> table
over the vpn, you need to do more than just change the source ip. as
described above, the routing table sends packets somewhere based
entirely on the destination address, which nat-to doesn't affect at all.

it is possible that you could write ipsec config that will generate SPD
entries that will take these packets and move them over the ipsec link.
that config might look like this:

     home_network="192.168.1.0/24"
     remote_network="192.168.2.0/24" 
     
     ikev2 passive esp \
         from any to dynamic \
         from $home_network to $remote_network \
         from any to $full_vpn_1 \
         from any to $full_vpn_2 \
         from any to $full_vpn_n \
         ...

and on the remote side:

     ikev2 active esp \
         from dynamic to any \
         from $remote_network to $home_network \
         from $full_vpn_1 to any \
         from $full_vpn_2 to any \
         from $full_vpn_n to any \
         ...

alternatively, you could use sec(4) (or wg(4) or openvpn or something),
which would let you use routes and pf to control which packets get sent
where. if you went that way you would need to use a separate routing
table/domain for the <full-vpn> hosts, or route-to in pf to control
where those packets go.

tl;dr: either use ipsec policy to control where packets go with vanilla
ipsec peers, or use routes/pf to control where packets go over a tunnel
like sec(4). don't use routes/pf to try and control ipsec policy.

> 
> 
> Thanks!
> 
> -- 
> Chris
> 
>       __o          "All I was trying to do was get home from work."
>     _`\<,_           -Rosa Parks
> ___(*)/_(*)____.___o____..___..o...________ooO..._____________________
> Christopher Sean Hilton                    [chris/at/vindaloo/dot/com]
> 

Reply via email to