Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Monitoring traffic inside network namespaces #1010

Open
bakamomi opened this issue Aug 8, 2023 · 8 comments
Open

[Feature Request] Monitoring traffic inside network namespaces #1010

bakamomi opened this issue Aug 8, 2023 · 8 comments
Labels
feature a whole new feature

Comments

@bakamomi
Copy link

bakamomi commented Aug 8, 2023

Some network namespaces forward to the host only encrypted traffic. In particular, wireguard supports this by pushing its interface into a netns https://www.wireguard.com/netns/

Because of this the current method of intercepting netns traffic at the Mangle table doesn't work. Nothing gets intercepted. OpenSnitch sees only encrypted wireguard traffic.

As a stopgap, I considered monitoring traffic inside netns via a new OpenSnitch node by launching an additional instance of the daemon inside this netns. But it seems the UI can connect to "remote" nodes only via tcp. My wireguard netns doesn't even have a bridge to the host. There is no direct tcp link. Instead, I wanted to connect to the node via a unix socket, but the UI supports only one socket connection.

Summary

Please add the ability to monitor traffic inside netns. One possible solution is by allowing "remote" nodes to connect via additional unix sockets like the local one.

@bakamomi bakamomi added the feature a whole new feature label Aug 8, 2023
@gustavo-iniguez-goya
Copy link
Collaborator

Hi @bakamomi ,

Try adding a rule to redirect forwarded packets to the daemon:

image

@bakamomi
Copy link
Author

bakamomi commented Aug 8, 2023

Hi @gustavo-iniguez-goya

I added this rule. I also tried enabling the built-in "Intercept forwarded connections (docker, etc)". It didn't work.

Correct me if I'm wrong, but the rule you suggested assumes that all traffic from a netns is sent unencrypted to the host where it's forwarded and sent out. But wireguard netns doesn't do this.
Traffic goes into the wg interface inside the netns, gets encrypted, then OpenSnitch only sees encrypted udp traffic. Unencrypted traffic exists only inside the wg netns. It should be intercepted before wg encrypts it.

@gustavo-iniguez-goya
Copy link
Collaborator

gustavo-iniguez-goya commented Aug 9, 2023

Alright, I keep making this mistake 0:) so let's start over.

Please, post the following information:

  • OpenSnitch version:
  • Post debug logs: Set LogLevel to DEBUG, empty the log file /var/log/opensnitchd.log, and reproduce the problem.
  • Post the IP of the wg interface so we can see if it appears in the log.
  • Post the output of: cat /sys/kernel/debug/tracing/kprobe_events
  • Did opensnitch prompt you to allow a "kernel connection"?

Usually there're firewall rules involved to forward traffic from namespaces to the host (docker for example), that's why I asked to add a rule for that. Do you have firewall rules to handle the wireguard connections?

Traffic goes into the wg interface inside the netns, gets encrypted, then OpenSnitch only sees encrypted udp traffic.

mmh, I think that this is the expected behaviour, but I need more details.
Are you launching applications also from that namespace? so when they establish the connection it goes through the wireguard tunnel of that namespace?

Debug logs will provide useful information, but maybe we'd need a firewall rule to intercept those connections before they're routed through the tunnel.

Interesting use case!probably I'll need to reproduce it locally.

@bakamomi
Copy link
Author

bakamomi commented Aug 9, 2023

The debug log turned out to be huge. I posted it on privatebin for convenience.
https://bin.urla.no/?d4aba4eb25251af2#AYXK1uH97bJfcoZAjPJmC8dAC5iyWaPeYfPexm8PPTwD

Regardless, the ip address of the wg interface (192.168.3.5) isn't recorded there.

These are the only lines directly related to wireguard:

[2023-08-09 17:23:38]  DBG  new connection udp => 20222:127.0.0.1 -> 127.0.0.1 ():47551 uid: 0, mark: 0
[2023-08-09 17:23:38]  DBG  [ebpf conn] not in cache, NOR in execEvents: udp20222127.0.0.1127.0.0.147551, 1794 -> /etc/wireguard/swgp-go
[2023-08-09 17:23:38]  DBG  [ebpf conn] adding item to cache: udp20222127.0.0.1127.0.0.147551
[2023-08-09 17:23:38]  DBG  ✔ /etc/wireguard/swgp-go -> 20222:127.0.0.1 => 127.0.0.1:47551, mark: 0 (001-allow-localhost)

I use a wireguard obfuscating proxy (swgp-go). It listens on 20222. Wireguard itself is on 47551. Further in the log you can find messages about trying to resolve discord.com. It happened exactly when I updated discord inside my wg netns. I have systemd-resolved resolving dns records for the wg netns. More importantly, the log didn't record any new connections even though they happened inside the netns.

For reference, I have somewhat complicated setup. The firewall on my baremetal system blocks all outgoing traffic aside from LAN and my remote wg server. When I want to give a program access to the internet, I put it inside one of a netns with internet access. Specifically, I run firejail --netns=wg <application>

Here is how my wg netns looks like:

# ip netns exec wg ip a l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host proto kernel_lo 
       valid_lft forever preferred_lft forever
2: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
9: wg1: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none 
    inet 192.168.3.5/24 scope global wg1
       valid_lft forever preferred_lft forever

My baremetal system has no interface connected to the wg netns. It's isolated.

# ip a l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute 
       valid_lft forever preferred_lft forever
2: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
3: wlp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 192.168.80.242/24 brd 192.168.80.255 scope global dynamic noprefixroute wlp1s0
       valid_lft 23098sec preferred_lft 23098sec
    inet6 xxxx:xxxx:xxxx::4fc/128 scope global dynamic noprefixroute 
       valid_lft 25495sec preferred_lft 25495sec
    inet6 xxxx:xxxx:xxxx:0:xxxx:xxxx:xxxx:16e6/64 scope global noprefixroute 
       valid_lft forever preferred_lft forever
    inet6 xxxx::xxxx:xxxx:xxxx:xxxx/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
4: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
6: virbr2: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 10.0.1.1/24 brd 10.0.1.255 scope global virbr2
       valid_lft forever preferred_lft forever
7: veth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff link-netns internet
    inet 10.200.0.1/24 scope global veth0
       valid_lft forever preferred_lft forever
    inet6 fd00::1/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::ccd:59ff:fe91:a809/64 scope link proto kernel_ll 
       valid_lft forever preferred_lft forever
8: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
       valid_lft forever preferred_lft forever

Here are kprobe_events:

# cat /sys/kernel/debug/tracing/kprobe_events
r32:kprobes/rtcp_v4_connect tcp_v4_connect
p:kprobes/ptcp_v6_connect tcp_v6_connect
r32:kprobes/rtcp_v6_connect tcp_v6_connect
p:kprobes/pudp_sendmsg udp_sendmsg
p:kprobes/pudpv6_sendmsg udpv6_sendmsg
p:kprobes/piptunnel_xmit iptunnel_xmit
p:kprobes/ptcp_v4_connect tcp_v4_connect

@gustavo-iniguez-goya
Copy link
Collaborator

Thank you @bakamomi !

The problem is that we create the netfilter queue and the interception rules in the root namespace.

After thinking a little bit about this, there're a couple of solutions (probably more and better):

  • Migrate to a eBPF based intercepting solution:

    We already intercept these connections via eBPF (you can use the script utils/scripts/debug-ebpf-maps.sh tcp to see all the intercepted connections).
    So maybe we could imitate what libnetfilter_queue does:

    1. kernel: a XDP program to block outbound connections by default (configurable),
    2. kernel: kprobes/tracepoints to intercept new connections, storing connections properties in a pinned map, shared with the XDP program,
    3. kernel: a perf ring buffer to send new connections to userspace
    4. userspace: on new connections get the process' properties -> check rules ,
    5. userspace: on allow/deny -> update pinned map with the verdict
    6. kernel: on new connection attempts to the given connection, apply a verdict.
  • Keep using libnetfilter_queue:

    In order to intercept connections from network namespaces:

    1. or they are routed through the host (root namespace), so we can intercept them with host's firewall (like docker and other containers work)
    2. or we create the nfqueues per network namespace.
      I've written a PoC to create nfqueues for the root namespace and another one created manually, and more or less work.
      But it needs more investigation.

@bakamomi
Copy link
Author

Thanks for looking into this! What way you choose to implement this feature is of course up to you, but here are my 2 cents:

they are routed through the host (root namespace), so we can intercept them with host's firewall (like docker and other containers work)

I think that this would break certain usecases, like when you want to isolate a netns from root completely, e.g., wg netns, or if you push a physical net interface into a netns.

we create the nfqueues per network namespace.

This does sound like the simplest solution, and it won't break anything.

eBPF might be the most robust choice, but I have no idea how hard it would be to implement in opensnitch.

@bakamomi
Copy link
Author

Hi, again!
Since you said you already have PoC with nfqueues, can you post it maybe as a pull request or in a separate tree? I wouldn't mind compiling opensntich with the patch applied until you merge it into the main repository.

@gustavo-iniguez-goya
Copy link
Collaborator

Sorry @bakamomi , I think the PoC is not practical. We should do it via ebpf.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature a whole new feature
Projects
None yet
Development

No branches or pull requests

2 participants