iovisor / bcc

BCC - Tools for BPF-based Linux IO analysis, networking, monitoring, and more
Apache License 2.0
20.59k stars 3.88k forks source link

tail-calls from namespace fail #1026

Closed ghost closed 7 years ago

ghost commented 7 years ago

You can find my python script below. I am working with a virtual machine running Ubuntu 16.04 and Kernel 4.4.

My program creates a network namespace "sw1" and interfaces that connect the new namespace to the default namespace. A simple hello world ebpf program is attatched to the ingress path of the new namespace. It prints "Hello, World!", executes a tail-call to a second ebpf program and finally prints "Tail-call not executed!". The second print should not be visible if the tail-call was successful. The result is:

    avahi-daemon-741   [000] ..s1 37135.708497: : Hello, World!
    avahi-daemon-741   [000] ..s1 37135.708501: : Tail-call not executed!
    avahi-daemon-741   [000] ..s1 37151.722406: : Hello, World!
    avahi-daemon-741   [000] ..s1 37151.722410: : Tail-call not executed!
    ...

If I modify the program to work without namespaces the tail-call works correctly and the output is:

    avahi-daemon-741   [000] ..s1 40830.997562: : Hello, World!
    avahi-daemon-741   [000] ..s1 40830.997572: : Ciao, World!
    avahi-daemon-741   [000] ..s. 40830.997627: : Hello, World!
    avahi-daemon-741   [000] ..s. 40830.997627: : Ciao, World!
     ...

Thus, it seems to be a problem with my namespace, but I have no idea where the problem is. Unfortunately its impossible to lookup the prog_array, but if I write the fd of the second program into a hash map and look this up in the .c code the fd is displayed correctly. Hence, I assume the correct fd is inside the prog_array, so why it is not called?

import subprocess
import ctypes
import sys
from bcc import BPF
from pyroute2 import IPRoute, NSPopen, NetNS

class SimulatedNetwork():
    def setup_switch(self):
        # This method is run in the sw1 namespace.
        ipr = IPRoute()

        prog = """
          BPF_TABLE("prog", int, int, prog_array, 5);

          int hello(struct __sk_buff* ebpf_packet) {
            bpf_trace_printk("Hello, World!\\n");
            prog_array.call(ebpf_packet, 1);
            bpf_trace_printk("Tail-call not executed!\\n");
            return 1;
          }
          """ 

        prog1 = """
            int ciao(struct __sk_buff* ebpf_packet) {
                bpf_trace_printk("Ciao, World!\\n");
                return 1;
            }
            """ 

        #create and load bpf programs
        hw = BPF(text=prog)
        hello_fn = hw.load_func("hello", BPF.SCHED_CLS)

        ciao = BPF(text=prog1)
        ciao_fn = ciao.load_func("ciao", BPF.SCHED_CLS)

        #assign ciao program fd to the prog_array map
        prog_array = hw.get_table("prog_array")
        prog_array[ctypes.c_int(1)] = ctypes.c_int(ciao_fn.fd)  

        #add ebpf program to ingress 
        sw1_node1_idx = ipr.link_lookup(ifname="veth-sw1-node1")[0]
        ipr.tc("add", "ingress", sw1_node1_idx, "ffff:")
        ipr.tc("add-filter", "bpf", sw1_node1_idx, ":1", fd=hello_fn.fd,
                    name=hello_fn.name, parent="ffff:", action="ok", classid=1)

def start_simulation():
    NetNS("sw1")
    ipr = IPRoute()

    #create links and push sw1 into its namespaces
    ipr.link_create(ifname="veth-node1-sw1", kind="veth", peer="veth-sw1-node1")

    ix = ipr.link_lookup(ifname="veth-node1-sw1")[0]
    ipr.link("set", index=ix, state="up")

    ix = ipr.link_lookup(ifname="veth-sw1-node1")[0]
    ipr.link("set", index=ix, net_ns_fd="sw1", state="up")

    #execute "setup_switch" function in namespace of the switch
    NSPopen("sw1", (["python", __file__, "setup_switch"]))

    #check result 
    subprocess.call(["cat", "/sys/kernel/debug/tracing/trace_pipe"])

def main(argv):
    if len(argv) == 1:
        start_simulation()
    else:
        # run "setup_switch" in namespace
        network = SimulatedNetwork()
        methodname = argv[1]
        method = getattr(network, methodname)
        method()

if __name__ == '__main__':
    main(sys.argv)
ghost commented 7 years ago

Okay the problem is solved, it was very easy but I didn't recognized it:

A second instance of my program is created to execute "setup_switch" in the namespace. This program instance loads the bpf programs and attatches the hello program to tc. Then the program instance stops, and thus the unattatched ciao program is deleted. If I add a sleep to avoid that the second instance stops everything works as expected.