TC-PEDIT(8)

๐Ÿ“ฆ
version 7.3.0 ยท 2 versions
  • 7.3.0 ยท alpine 3.22, alpine 3.23, alpine 3.24, arch rolling, centos-stream 10, centos-stream 9, debian bookworm, debian trixie, fedora 43, fedora 44, nixos 26.05, nixpkgs unstable, opensuse-leap 16.0, ubuntu noble, ubuntu questing, ubuntu resolute, ubuntu stonking (latest)
  • 5.15.0 ยท ubuntu jammy
, in alpine 3.22, alpine 3.23, alpine 3.24, arch rolling, centos-stream 10, centos-stream 9, debian bookworm, debian trixie, fedora 43, fedora 44, nixos 26.05, nixpkgs unstable, opensuse-leap 16.0, ubuntu noble, ubuntu questing, ubuntu resolute, ubuntu stonking

โœจ TC-PEDIT(8) โ€” the pedit action, bestie ๐Ÿ’–

okay so pedit is the generic packet editor action ๐Ÿฆ‹ It lets you change arbitrary packet data, like literally rewriting bytes mid-flight. ngl it's giving main character energy ๐Ÿ’…


๐Ÿ“œ NAME

pedit โ€” generic packet editor action ๐ŸŽ€


๐Ÿงฉ SYNOPSIS

tc ... action pedit [ex] munge { RAW_OP | LAYERED_OP |
        EXTENDED_LAYERED_OP } [ CONTROL ]

RAW_OP := offset OFFSET { u8 | u16 | u32 } [ AT_SPEC ] CMD_SPEC

AT_SPEC := at AT offmask MASK shift SHIFT

LAYERED_OP := { ip IPHDR_FIELD | ip BEYOND_IPHDR_FIELD } CMD_SPEC

EXTENDED_LAYERED_OP := { eth ETHHDR_FIELD | ip IPHDR_FIELD | ip
        EX_IPHDR_FIELD | ip6 IP6HDR_FIELD | tcp TCPHDR_FIELD | udp
        UDPHDR_FIELD } CMD_SPEC

ETHHDR_FIELD := { src | dst | type }

IPHDR_FIELD := { src | dst | tos | dsfield | ihl | protocol | precedence
        | nofrag | firstfrag | ce | df }

BEYOND_IPHDR_FIELD := { dport | sport | icmp_type | icmp_code }

EX_IPHDR_FIELD := { ttl }

IP6HDR_FIELD := { src | dst | traffic_class | flow_lbl | payload_len |
        nexthdr | hoplimit }

TCPHDR_FIELD := { sport | dport | flags }

UDPHDR_FIELD := { sport | dport }

CMD_SPEC := { clear | invert | set VAL | add VAL | decrement | preserve
        } [ retain RVAL ]

CONTROL := { reclassify | pipe | drop | shot | continue | pass | goto
        chain CHAIN_INDEX }

๐Ÿ’ฌ DESCRIPTION

okay so the pedit action is how you change arbitrary packet data โœจ You can point at the data in one of two ways. Either you give an offset and size (that's the RAW_OP way), or you name a header and field you want to edit ๐Ÿซถ For header values the size gets picked for you automatically, based on how big that header field is. Easy peasy ๐ŸŒธ


๐ŸŽ€ OPTIONS

  • ex โ€” this turns on extended pedit ๐Ÿ”ฅ EXTENDED_LAYERED_OP and the add and decrement CMD_SPECs only work in this mode, so don't forget it!
  • offset OFFSET { u32 | u16 | u8 } โ€” tell it where to change data ๐Ÿ“ OFFSET is a signed integer, and its base gets chosen automatically (so hex if you prefix it with 0x, octal if you prefix it with 0). The second part is how much data to change: four bytes (u32), two bytes (u16), or a single byte (u8).
  • at AT offmask MASK shift SHIFT โ€” this is an optional bit of RAW_OP that makes the OFFSET wiggle ๐Ÿฆ‹ It depends on the packet data at offset AT. That data gets binary ANDed with MASK, right-shifted by SHIFT, and then added to OFFSET.
  • eth ETHHDR_FIELD โ€” change an ETH header field ๐Ÿ’… The keywords you can use for ETHHDR_FIELD are:
    • src and dst โ€” source or destination MAC address, written in the standard format XX:XX:XX:XX:XX:XX ๐Ÿ“
    • type โ€” the ether-type, as a numeric value
  • ip IPHDR_FIELD โ€” change an IPv4 header field ๐ŸŒˆ The keywords for IPHDR_FIELD are:
    • src and dst โ€” source or destination IP address, a four-byte value
    • tos, dsfield and precedence โ€” the Type Of Service field, an eight-bit value
    • ihl โ€” change the IP Header Length field, a four-bit value
    • protocol โ€” the next-layer Protocol field, an eight-bit value
    • nofrag, firstfrag, ce, df and mf โ€” change the IP header flags ๐Ÿšฉ Heads up: the value you pass to set isn't just a single bit, it's the full byte including the flags field. Only the relevant bits of that value get respected, and the rest get ignored.
  • ip BEYOND_IPHDR_FIELD โ€” this one is supported only for non-extended layered ops ๐Ÿ™ˆ It gets passed to the kernel as offsets relative to the start of the IP header, and it assumes the IP header is the minimum size (20 bytes). The keywords for BEYOND_IPHDR_FIELD are:
    • dport and sport โ€” destination or source port numbers, a 16-bit value. Real talk: IPv4 headers don't actually contain this info ๐Ÿ’€ Instead, this sets an offset that suits at least TCP and UDP, if the IP header is minimum size (20 bytes). If it isn't, this will do unexpected things.
    • icmp_type and icmp_code โ€” again, this lets you change data past the IP header itself. It assumes an ICMP header sits right after the (minimum-sized) IP header. If it doesn't, or the IP header is bigger than the 20-byte minimum, this will do unexpected things. Both fields are eight-bit values.
  • ip EX_IPHDR_FIELD โ€” only supported when ex is used โœจ The keyword for EX_IPHDR_FIELD is:
    • ttl
  • ip6 IP6HDR_FIELD โ€” the keywords for IP6HDR_FIELD are:
    • src, dst, traffic_class, flow_lbl, payload_len, nexthdr and hoplimit ๐Ÿงš
  • tcp TCPHDR_FIELD โ€” the keywords for TCPHDR_FIELD are:
    • sport and dport โ€” source or destination TCP port number, a 16-bit value
    • flags ๐Ÿ’œ
  • udp UDPHDR_FIELD โ€” the keywords for UDPHDR_FIELD are:
    • sport and dport โ€” source or destination UDP port number, a 16-bit value ๐Ÿฆ‹

(The original page calls these "TCP port number" here, but it's a UDP field, so I'm keeping the meaning straight for you bestie ๐Ÿซถ)

  • clear โ€” clear the addressed data, aka set it to zero ๐Ÿงผ
  • invert โ€” swap every bit in the addressed data ๐Ÿ™ƒ
  • set VAL โ€” set the addressed data to a specific value ๐ŸŽฏ The size of VAL comes from either u32, u16 or u8 in RAW_OP, or from the size of the addressed header field in LAYERED_OP.
  • add VAL โ€” add a specific value to the addressed data โž• The size of VAL comes from the size of the addressed header field in EXTENDED_LAYERED_OP. This is supported only for extended layered ops.
  • decrement โ€” decrease the addressed data by one โฌ‡๏ธ This is supported only for ip ttl and ip6 hoplimit.
  • preserve โ€” keep the addressed data as it is ๐Ÿซง
  • retain RVAL โ€” this optional extra part of CMD_SPEC lets you exclude bits from being changed ๐Ÿ”’ It's supported only for fields that are 32 bits or smaller.
  • CONTROL โ€” these keywords control how the tree of qdisc, classes, filters and actions gets traversed after this action ๐Ÿšฆ
    • reclassify โ€” restart with the first filter in the current list
    • pipe โ€” continue with the next action attached to the same filter
    • drop and shot โ€” drop the packet ๐Ÿ’€
    • continue โ€” continue classification with the next filter in line
    • pass โ€” finish classification and return to the calling qdisc for further packet processing. This is the default โœจ

๐ŸŒธ EXAMPLES

Being able to edit packet data means you could do all kinds of things ๐Ÿคญ Like, implementing port redirection. Certainly not the most useful app, but as an example it slaps:

First, qdiscs need to be set up so you can attach filters to them. For the receive path a simple ingress qdisc will do, and for the transmit path you need a classful qdisc (HTB in this case) ๐Ÿซถ

tc qdisc replace dev eth0 root handle 1: htb
tc qdisc add dev eth0 ingress handle ffff:

Finally, add a filter with a pedit action for each direction. Here u32 matches on the port number you want to redirect from, and then pedit does the actual rewriting ๐Ÿ”ฅ

tc filter add dev eth0 parent 1: u32 \
     match ip dport 23 0xffff \
     action pedit pedit munge ip dport set 22
tc filter add dev eth0 parent ffff: u32 \
     match ip sport 22 0xffff \
     action pedit pedit munge ip sport set 23
tc filter add dev eth0 parent ffff: u32 \
     match ip sport 22 0xffff \
     action pedit ex munge ip dst set 192.168.1.199
tc filter add dev eth0 parent ffff: u32 \
     match ip sport 22 0xffff \
     action pedit ex munge ip6 dst set fe80::dacb:8aff:fec7:320e
tc filter add dev eth0 parent ffff: u32 \
     match ip sport 22 0xffff \
     action pedit ex munge eth dst set 11:22:33:44:55:66
tc filter add dev eth0 parent ffff: u32 \
     match ip dport 23 0xffff \
     action pedit ex munge tcp dport set 22

Want to rewrite just part of a field? Use the retain directive ๐ŸŽ€ For example, to overwrite the DSCP part of a dsfield with $DSCP without touching ECN:

tc filter add dev eth0 ingress flower ... \
     action pedit ex munge ip dsfield set $((DSCP << 2)) retain 0xfc

And vice versa, to set ECN to e.g. 1 without impacting DSCP:

tc filter add dev eth0 ingress flower ... \
     action pedit ex munge ip dsfield set 1 retain 0x3

๐Ÿฆ‹ SEE ALSO

tc(8), tc-htb(8), tc-u32(8)


iproute2 ยท 12 Jan 20 ๐Ÿ’– that's the tea, bestie, periodt ๐Ÿซถโœจ

๐Ÿ“ฆ where this page comes from