Skip to content

leodido/traffico

Repository files navigation

                  _   _
                 | | | | o
 _|_  ,_    __,  | | | |     __   __
  |  /  |  /  |  |/  |/  |  /    /  \_
  |_/   |_/\_/|_/|__/|__/|_/\___/\__/
                 |\  |\
                 |/  |/

README

    traffico is a collection of tools to shape traffic on a network using traffic control tc(8).
    It can be used via a CLI tool (traffico) or as a CNI plugin (traffico-cni).
    For a list of the available programs and what they do see the BUILT-IN PROGRAMS section.

    Intent mode adds network intent guardrails for agent workloads. It can
    express intent-based egress quarantine policies such as "allow ARP, allow
    DNS to this resolver, allow TCP to this service, but forbid SSH to it".

    The BUILT-IN PROGRAMS are very opinionated and made for the needs of the authors but the framework
    is flexible enough to be used for other purposes. You can add programs to the bpf/ directory
    to extend it to other use cases.

CONTACT

    If you have problems, question, ideas or suggestions, please contact us by
    posting to https://github.com/leodido/traffico/issues.

DOWNLOAD

    To download the very latest source do this:

    git clone https://github.com/leodido/traffico.git

AUTHORS

    Leonardo Di Donato
    Lorenzo Fontana

USAGE

    Traffico can be either used standalone or as a CNI plugin.

    traffico
        traffico is a CLI tool that can be used to load and unload the programs.
        You can choose an interface and choose whether the program will be loaded in
        "INGRESS" or "EGRESS".

        Start with a standalone block program when the policy targets specific
        traffic and should leave everything else alone:
            traffico --ifname=eth0 --at=INGRESS block_private_ipv4

        Programs that accept runtime input (marked [input] in --help) take it
        as a second positional argument:
            traffico --ifname=eth0 block_ipv4 10.0.0.1
            traffico --ifname=eth0 block_port 443

        Use standalone allow programs when the interface should admit only the
        traffic described by that one program:
            traffico --ifname=eth0 allow_ipv4 10.0.0.10
            traffico --ifname=eth0 allow_proto tcp+udp
            traffico --ifname=eth0 allow_ethertype ipv4+arp

        Use --chain when the policy needs multiple ordered stages. Chains that
        contain L3/L4 programs must start with the L2 gate allow_ethertype:
            traffico --ifname=eth0 --chain \
                "allow_ethertype:ipv4+arp,allow_port:443"

        Chains compose checks linearly: each stage narrows the traffic that
        reaches the next stage. That works well for single-path policies such
        as HTTPS to one service:
            traffico --ifname=eth0 --at=EGRESS --chain \
                "allow_ethertype:ipv4+arp,allow_ipv4:10.0.0.10,allow_proto:tcp,allow_port:443"

        Or DNS to one resolver:
            traffico --ifname=eth0 --at=EGRESS --chain \
                "allow_ethertype:ipv4+arp,allow_dns:10.0.0.53"

        These examples are alternative linear policies, not an additive
        multi-flow allowlist. Full policies such as "DNS to resolver OR HTTPS
        to service" need Intent mode.

        Chain order is validated before attach. If a chain contains any L3/L4
        program, slot 0 must be allow_ethertype, and the layer order must be
        L2 -> L3 -> L4.

        Intent mode
            Use Intent mode for network intent guardrails and intent-based
            egress quarantine. It is selected by repeating --allow / --permit
            and optionally --forbid / --block. --permit is an alias for
            --allow. --block is an alias for --forbid.

            Intent mode uses deny-overrides semantics:
                malformed or unclassifiable packet -> DROP
                matching forbid                    -> DROP
                matching permit                    -> ALLOW
                no matching permit                 -> DROP

            Supported permit selectors:
                arp
                dns/IPv4
                tcp/IPv4
                udp/IPv4
                tcp/IPv4:PORT
                udp/IPv4:PORT

            Supported forbid selectors:
                arp
                dns/IPv4
                tcp/IPv4:PORT
                udp/IPv4:PORT

            dns/IPv4 matches DNS to that IPv4 resolver over TCP or UDP
            destination port 53. tcp/IPv4 and udp/IPv4 permit any destination
            port on that IPv4 host. The tcp/IPv4:PORT and udp/IPv4:PORT
            selectors match one destination endpoint. Any traffic not matching
            a permit is dropped, and packets that cannot be safely classified
            are dropped. In v0.6, Intent mode drops subsequent TCP/UDP
            fragments because the TCP/UDP header is not present in those
            packets.

            What v0.6 can do today:
                Put a workload behind a dedicated Linux egress interface,
                container veth, or network namespace veth.
                Attach Intent mode to that interface at egress.
                Permit the DNS, ARP, TCP, and UDP traffic the workload needs.
                Add narrow forbids for denied ports, DNS, or ARP carve-outs.
                Treat the policy as interface-scoped. It is not process-,
                agent-, container-, or CNI-scoped by itself.

            This allows broad service access with narrow carve-outs:

            traffico --ifname=eth0 --at=EGRESS \
                --allow  arp \
                --allow  dns/10.0.0.53 \
                --allow  tcp/10.0.0.10 \
                --block  tcp/10.0.0.10:22

            For a DNS carve-out inside broader TCP and UDP access to one host:

            traffico --ifname=eth0 --at=EGRESS \
                --allow  arp \
                --allow  tcp/10.0.0.53 \
                --allow  udp/10.0.0.53 \
                --block  dns/10.0.0.53

            Use --dry-run to compile and validate without attaching:

            traffico --ifname=eth0 --at=EGRESS \
                --allow  arp \
                --allow  tcp/10.0.0.10 \
                --block  tcp/10.0.0.10:22 \
                --dry-run

            Use --explain to print the normalized Intent before validation
            succeeds or fails:

            traffico --ifname=eth0 --at=EGRESS \
                --allow  arp \
                --allow  dns/10.0.0.53 \
                --allow  tcp/10.0.0.10 \
                --block  tcp/10.0.0.10:22 \
                --dry-run --explain

            Intent mode is mutually exclusive with --chain and with positional
            PROGRAM [INPUT]. The first Intent backend is the Linux BPF egress
            adapter. --at=INGRESS is parsed and can be explained, but backend
            validation rejects it until an ingress backend is implemented.

            Current Intent boundaries:
                Intent is CLI-only; traffico-cni still uses built-in programs.
                Intent supports Linux TC BPF egress only.
                Host-wide tcp/IPv4 and udp/IPv4 are permits only.
                Subsequent TCP/UDP fragments are dropped, even under host-wide
                permits.
                Host-wide TCP/UDP forbids are not supported yet.
                CIDR, port ranges, source predicates, labels, DNS names, ICMP
                selectors, policy files, and --explain=dag are reserved for
                future releases.

            Current Intent limits:
                Up to 32 permits and 32 forbids.
                The Linux TC BPF backend enforces up to 64 action-bearing rows.
                Duplicate permits and duplicate forbids are rejected.
                The 33rd permit or forbid is rejected before attach.

    traffico-cni
        traffico-cni is a meta CNI plugin that allows the traffico programs to be used in CNI.

        Meta means that traffic-cni does not create any interface for you,
        it is intended to be used as a chained CNI plugin.

        The plugin block to use traffico-cni is very similar to how traffico is
        used as a CLI tool.

        {
            "type": "traffico-cni",
            "program": "block_private_ipv4",
            "attachPoint": "ingress"
        }

        Programs that accept runtime input use the "input" field:

        {
            "type": "traffico-cni",
            "program": "block_ipv4",
            "input": "10.0.0.1",
            "attachPoint": "egress"
        }

        Here's an example CNI config file featuring traffico-cni.

        {
            "name": "mynetwork",
            "cniVersion": "0.4.0",
            "plugins": [
                {
                    "type": "ptp",
                    "ipMasq": true,
                    "ipam": {
                        "type": "host-local",
                        "subnet": "10.10.10.0/24",
                        "resolvConf": "/etc/resolv.conf",
                        "routes": [
                            { "dst": "0.0.0.0/0" }
                        ]
                    },
                    "dns": {
                        "nameservers": ["1.1.1.1", "1.0.0.1"]
                    }
                },
                {
                    "type": "firewall"
                },
                {
                    "type": "traffico-cni",
                    "program": "block_private_ipv4",
                    "attachPoint": "ingress"
                },
                {
                    "type": "tc-redirect-tap"
                }
            ]
        }

DESIGN PRINCIPLES

    +-----------------------+------------------------------------------------+
    | Principle             | Description                                    |
    +-----------------------+------------------------------------------------+
    | Standalone vs chained | Standalone programs are the only filter on     |
    |                       | the interface and handle all traffic types     |
    |                       | themselves. Chained programs pass traffic      |
    |                       | they do not handle to the next program,        |
    |                       | trusting that an upstream filter (typically    |
    |                       | allow_ethertype) already constrained it.       |
    +-----------------------+------------------------------------------------+
    | Boundary failures     | block_* programs fail open (TC_ACT_OK) on      |
    |                       | truncated headers, unsupported protocols,      |
    |                       | and subsequent fragments because they target   |
    |                       | specific traffic. allow_* programs fail closed |
    |                       | (TC_ACT_SHOT) on the same failures because     |
    |                       | they define permitted traffic.                 |
    +-----------------------+------------------------------------------------+
    | L2 -> L3 -> L4        | Chains run cheapest and broadest checks        |
    | ordering              | first: allow_ethertype (L2), then allow_proto  |
    |                       | (L3), then allow_port or allow_dns (L4).       |
    |                       | allow_ipv4 fits after L2 and alongside or      |
    |                       | after L3 protocol filtering.                   |
    +-----------------------+------------------------------------------------+
    | Non-IPv4 passthrough  | In chains, L3 and L4 programs pass non-IPv4    |
    | in chains             | traffic to the next program via                |
    |                       | tail_call_next(). L2 filtering is              |
    |                       | allow_ethertype's job; ARP, IPv6, and other    |
    |                       | non-IPv4 traffic allowed by L2 must not be     |
    |                       | silently dropped downstream.                   |
    +-----------------------+------------------------------------------------+

BUILT-IN PROGRAMS

    allow_dns
        allow_dns allows IPv4 DNS traffic (TCP/UDP port 53) to the
        input resolver address. Other IPv4 traffic passes through.
        Standalone mode blocks non-IPv4 traffic. Chain mode passes
        non-IPv4 traffic to the next stage.

    allow_ethertype
        allow_ethertype is an L2 gatekeeper that drops Ethernet frames
        whose EtherType is not in the allowed set. Multiple EtherTypes
        can be specified by joining them with +. Symbolic names (ipv4,
        ipv6, arp, vlan, qinq) and hex values (0x0800) are both
        supported.
        Example: allow_ethertype ipv4+arp

        If a chain contains any L3/L4 program, allow_ethertype must be
        first. Chain order must be non-decreasing by layer:
        L2 -> L3 -> L4. Same-layer programs are allowed, and chains may
        skip L3 after the L2 gate.

        Standalone allow_ethertype compares the outer Ethernet header
        EtherType only; it does not unwrap VLAN tags or match the inner
        payload EtherType.

        Symbolic names vlan (0x8100) and qinq (0x88A8) are available.
        VLAN TPIDs are only supported in standalone mode. In chains,
        they are rejected because VLAN-aware parsing is not uniform
        across downstream programs.

    allow_proto
        allow_proto is an L3 gatekeeper that drops IPv4 packets whose IP
        protocol is not in the allowed set. Multiple protocols can be
        specified by joining them with +. Symbolic names (tcp, udp,
        icmp, sctp) and decimal numbers (6, 17) are both supported.
        Example: allow_proto tcp+udp

        VLAN/QinQ-tagged IPv4 is unwrapped before checking the IP
        protocol; truncated VLAN headers and unsupported additional
        VLAN nesting fail closed. Standalone mode blocks non-IPv4
        traffic after VLAN unwrap. Chain mode passes non-IPv4 traffic to
        the next stage.

    allow_ipv4
        allow_ipv4 allows IPv4 traffic to the input address, drops other
        IPv4 destinations, and always allows localhost (127.0.0.0/8).
        Standalone mode blocks non-IPv4 traffic. Chain mode passes
        non-IPv4 traffic to the next stage.

    allow_port
        allow_port allows IPv4 TCP/UDP traffic to the input destination
        port. Other IPv4 protocols (ICMP, GRE, etc.) pass through.
        Standalone mode blocks non-IPv4 traffic. Chain mode passes
        non-IPv4 traffic to the next stage. TCP/UDP subsequent fragments
        are blocked.

    block_private_ipv4
        block_private_ipv4 is a program that can be used to block
        private IPv4 addresses subnets allowing only SSH access on port 22.

    block_ipv4
        block_ipv4 is a program that drops packets with destination equal to the
        input IPv4 address.

    block_port
        block_port is a program that drops packets with the destination port
        equal to the input port number.

    nop
        nop is a simple program that does nothing.

BUILD

    To compile traffico from source you either provide your `vmlinux.h` in the
    `vmlinux/` directory (default option) or you configure the project to
    generate one from your current Linux kernel:

    xmake f --generate-vmlinux=y

    Now you will be able to build traffico from source by running:

    xmake

    In case you only want to compile the BPF programs you can do this:

    xmake -b bpf

    In case you want to compile in debug mode:

    xmake f -m debug

    You will be able to read from the trace_pipe the logs of the BPF programs
    and you will obtain the logs of libbpf into the stderr only if you compile
    in debug mode.

TEST

    To run the test suite you can do this:

    xmake run test

About

Shape your traffic the BPF way

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Contributors