0

With wireguard, is it possible to create a tunnel that will only allow traffic in one direction? For example heres the following scenario: Given PC-s: A, B, C. A should be able to reach (ping, telnet, etc) B and C, B should be able to reach C, but not backwards. I figured the AllowedIPs will let me do that but I was mistaken, as it needs to be added on both sides, and it will be bidirectional

The current solution I've found is to set up iptables on each PC and defining every rule about every connection, what to drop and what to accept, but I figured there might be a better solution for this.

Update: With iptables, I was able to figure it out following @grawity_u1686's comment:

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)


pkts bytes target     prot opt in     out     source               destination         
   11   924 ACCEPT     0    --  wg0    *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
   12  1008 DROP       0    --  wg0    *       0.0.0.0/0            0.0.0.0/0           

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    3   252 ACCEPT     0    --  wg0    *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    9   756 DROP       0    --  wg0    *       10.0.0.3             10.0.0.2            
    0     0 ACCEPT     0    --  *      wg0     10.0.0.1             10.0.0.2            
    0     0 ACCEPT     0    --  *      wg0     10.0.0.1             10.0.0.3            
   26  2184 ACCEPT     0    --  *      wg0     10.0.0.2             10.0.0.3            
    0     0 ACCEPT     0    --  wg0    *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Update, with nftables it's also working:

table inet filter {
    chain INPUT {
        type filter hook input priority 0; policy accept;
        iifname "wg0" ct state related,established accept
        iifname "wg0" drop
    }

    chain FORWARD {
        type filter hook forward priority 0; policy accept;
        iifname "wg0" ct state related,established accept
        iifname "wg0" ip saddr 10.0.0.3 ip daddr 10.0.0.2 drop
        oifname "wg0" ip saddr 10.0.0.1 ip daddr 10.0.0.2 accept
        oifname "wg0" ip saddr 10.0.0.1 ip daddr 10.0.0.3 accept
        oifname "wg0" ip saddr 10.0.0.2 ip daddr 10.0.0.3 accept
        iifname "wg0" ct state related,established accept
    }

    chain OUTPUT {
        type filter hook output priority 0; policy accept;
    }
}

2 Answers 2

4

Given PC-s: A, B, C. A should be able to reach (ping, telnet, etc) B and C, B should be able to reach C, but not backwards.

That is not one-way traffic. To make a telnet connection, you have to send packets and receive packets, otherwise you won't even establish the TCP connection. So any stateless mechanism (specifically including AllowedIPs in WireGuard) must be able to pass traffic in both directions.

You're asking for the filter to work in terms of connections or flows (i.e. only be able to initiate a connection one way), so the general solution to that will necessarily involve stateful filtering – on Linux this always means iptables/nftables with the conntrack subsystem, which is meant precisely for that purpose.

It's the job of conntrack to keep track of each active flow, so it can tell which packets are "first" and which ones are replies; you typically use it by allowing everything in one direction but only 'ESTABLISHED' packets in the other direction:

chain forward {
    type filter hook forward priority filter;

    # Allow traffic in the direction you describe
    ip saddr $A ip daddr {$B, $C} accept
    ip saddr $B ip daddr $C accept

    # And allow only replies in the other direction
    ct state established,related accept

    # Block the rest
    counter drop
}

(Note: While I wrote the rules top-down for clarity, usually you'd put the state rule first for better performance – conntrack table has already been checked anyway, so you can rely on that for avoiding redundant checks all of the other IP/port-based rules.)

There are ways to filter e.g. TCP statelessly by allowing all packets with the ACK flag in the reverse direction (only replies have ACK set), and similarly ICMP by filtering by type (Echo vs Echo Reply), but this doesn't work for UDP or other "stateless" streams, while conntrack does – as well as making sure ICMP error packets are allowed through.

5
  • Following this, with iptables I was able to figure it out, although it might be redundant, please check the updated question. However with nftables it's either neither can ping neither, or everyone can ping everyone. Commented Sep 10 at 8:54
  • Doesn't matter which one you decide to use, both will have the same kind of conntrack-state match. I didn't include the hook definition for the forward chain though (those implicit in iptables but needs to be specified explicitly in nftables).
    – grawity
    Commented Sep 10 at 8:57
  • I updated the question. I will have a look at what I did wrong with nftables Commented Sep 10 at 8:59
  • nevermind, I have figured it out. I will accept your answer and include the solution in the question Commented Sep 10 at 9:01
  • Do not edit answers into questions. Post your own answer instead.
    – grawity
    Commented Sep 10 at 9:02
1

The solution based on grawity_u1686's insights:

Iptables:

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)


pkts bytes target     prot opt in     out     source               destination         
   11   924 ACCEPT     0    --  wg0    *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
   12  1008 DROP       0    --  wg0    *       0.0.0.0/0            0.0.0.0/0           

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    3   252 ACCEPT     0    --  wg0    *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED
    9   756 DROP       0    --  wg0    *       10.0.0.3             10.0.0.2            
    0     0 ACCEPT     0    --  *      wg0     10.0.0.1             10.0.0.2            
    0     0 ACCEPT     0    --  *      wg0     10.0.0.1             10.0.0.3            
   26  2184 ACCEPT     0    --  *      wg0     10.0.0.2             10.0.0.3            
    0     0 ACCEPT     0    --  wg0    *       0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination   

  

With nftables:

table inet filter {
    chain INPUT {
        type filter hook input priority 0; policy accept;
        iifname "wg0" ct state related,established accept
        iifname "wg0" drop
    }

    chain FORWARD {
        type filter hook forward priority 0; policy accept;
        iifname "wg0" ct state related,established accept
        iifname "wg0" ip saddr 10.0.0.3 ip daddr 10.0.0.2 drop
        oifname "wg0" ip saddr 10.0.0.1 ip daddr 10.0.0.2 accept
        oifname "wg0" ip saddr 10.0.0.1 ip daddr 10.0.0.3 accept
        oifname "wg0" ip saddr 10.0.0.2 ip daddr 10.0.0.3 accept
        iifname "wg0" ct state related,established accept
    }

    chain OUTPUT {
        type filter hook output priority 0; policy accept;
    }
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .