├── .gitignore ├── linux_tools ├── README ├── Makefile ├── bpf_asm.c ├── bpf_exp.l ├── linux │ ├── filter.h │ └── if_packet.h ├── bpf_jit_disasm.c ├── COPYING ├── bpf_exp.y └── bpf_dbg.c ├── Makefile ├── .github └── workflows │ └── semgrep.yml ├── bpftools ├── gen_p0f.py ├── __init__.py ├── gen_suffix.py ├── linktypes.py ├── utilsdns.py ├── gen_tcpdump.py ├── gen_dns_validate.py ├── utils.py ├── gen_dns.py └── p0f.py ├── setup.py ├── LICENSE-BSD-CloudFlare ├── hex2pcap ├── bpfgen ├── README.md ├── offset ├── filter ├── pcap2hex ├── iptables_bpf ├── iptables_bpf_chain ├── parsedns └── tests └── test_p0f.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.yacc.[ch] 3 | linux_tools/bpf_asm 4 | linux_tools/bpf_dbg 5 | linux_tools/bpf_jit_disasm 6 | *.pyc 7 | venv/ 8 | build/ 9 | bpftools.egg-info 10 | -------------------------------------------------------------------------------- /linux_tools/README: -------------------------------------------------------------------------------- 1 | Linux net tools copied from linux/tools/net. Code on GPLv2 license. 2 | 3 | Original source: 4 | 5 | https://github.com/torvalds/linux/tree/master/tools/net 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: all 3 | all: linux_tools/bpf_dbg linux_tools/bpf_asm 4 | python -c "import pcappy" 5 | 6 | 7 | linux_tools/bpf_dbg: linux_tools/*.[ch] 8 | make -C linux_tools bpf_dbg 9 | 10 | linux_tools/bpf_asm: linux_tools/*.[ch] 11 | make -C linux_tools bpf_asm 12 | 13 | test: 14 | python -m unittest discover tests/ 15 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | name: Semgrep config 10 | jobs: 11 | semgrep: 12 | name: semgrep/ci 13 | runs-on: ubuntu-20.04 14 | env: 15 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 16 | SEMGREP_URL: https://cloudflare.semgrep.dev 17 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 18 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 19 | container: 20 | image: returntocorp/semgrep 21 | steps: 22 | - uses: actions/checkout@v3 23 | - run: semgrep ci 24 | -------------------------------------------------------------------------------- /bpftools/gen_p0f.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | 4 | from . import p0f 5 | from . import gen_tcpdump 6 | 7 | def gen(params, l3_off=0, ipversion=4, negate=False): 8 | parser = argparse.ArgumentParser( 9 | formatter_class=argparse.RawDescriptionHelpFormatter, 10 | prog="%s p0f --" % (sys.argv[0]), 11 | description=r''' 12 | 13 | Generate bpf using a p0f signature string. 14 | 15 | ''') 16 | parser.add_argument('p0f', nargs=1, help='p0f signature') 17 | args = parser.parse_args(args=params) 18 | 19 | p = p0f.P0fBPF(args.p0f[0]) 20 | for l in p.doc_bpf_str.split("\n"): 21 | print "; " + l 22 | print ";" 23 | gen_tcpdump.gen([p.bpf_str], l3_off, ipversion, negate) 24 | 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import setuptools 3 | import shutil 4 | import sys 5 | 6 | 7 | if not os.path.exists('bpftools/bpf_asm'): 8 | os.system("make -C linux_tools") 9 | if not os.path.exists('linux_tools/bpf_asm'): 10 | print >> sys.stderr, "Type 'make' before packaging" 11 | sys.exit(-1) 12 | shutil.copy('linux_tools/bpf_asm', 'bpftools') 13 | 14 | 15 | setuptools.setup( 16 | name='bpftools', 17 | version='1.0', 18 | description='BPF Tools - packet analyst toolkit', 19 | url='https://github.com/cloudflare/bpftools', 20 | packages=['bpftools'], 21 | maintainer="Marek Majkowski", 22 | maintainer_email="marek@cloudflare.com", 23 | package_data = { 24 | '': ['bpf_asm'], 25 | }, 26 | zip_safe = False, 27 | ) 28 | -------------------------------------------------------------------------------- /linux_tools/Makefile: -------------------------------------------------------------------------------- 1 | prefix = /usr 2 | 3 | CC = gcc 4 | LEX = flex 5 | YACC = bison 6 | 7 | %.yacc.c: %.y 8 | $(YACC) -o $@ -d $< 9 | 10 | %.lex.c: %.l 11 | $(LEX) -o $@ $< 12 | 13 | all : bpf_jit_disasm bpf_dbg bpf_asm 14 | 15 | bpf_jit_disasm : CFLAGS = -Wall -O2 -DPACKAGE='bpf_jit_disasm' -I. 16 | bpf_jit_disasm : LDLIBS = -lopcodes -lbfd -ldl 17 | bpf_jit_disasm : bpf_jit_disasm.o 18 | 19 | bpf_dbg : CFLAGS = -Wall -O2 -I. 20 | bpf_dbg : LDLIBS = -lreadline 21 | bpf_dbg : bpf_dbg.o 22 | 23 | bpf_asm : CFLAGS = -Wall -O2 -I. 24 | bpf_asm : LDLIBS = 25 | bpf_asm : bpf_asm.o bpf_exp.yacc.o bpf_exp.lex.o 26 | bpf_exp.lex.o : bpf_exp.yacc.c 27 | 28 | clean : 29 | rm -rf *.o bpf_jit_disasm bpf_dbg bpf_asm bpf_exp.yacc.* bpf_exp.lex.* 30 | 31 | install : 32 | install bpf_jit_disasm $(prefix)/bin/bpf_jit_disasm 33 | install bpf_dbg $(prefix)/bin/bpf_dbg 34 | install bpf_asm $(prefix)/bin/bpf_asm 35 | -------------------------------------------------------------------------------- /bpftools/__init__.py: -------------------------------------------------------------------------------- 1 | import StringIO as stringio 2 | import os 3 | import sys 4 | 5 | from . import gen_dns 6 | from . import gen_dns_validate 7 | from . import gen_p0f 8 | from . import gen_suffix 9 | from . import utils 10 | from . import gen_tcpdump 11 | 12 | name_to_gen = { 13 | 'dns': gen_dns.gen, 14 | 'dns_validate': gen_dns_validate.gen, 15 | 'p0f': gen_p0f.gen, 16 | 'suffix': gen_suffix.gen, 17 | 'tcpdump': gen_tcpdump.gen, 18 | } 19 | 20 | generator_names = name_to_gen.keys() 21 | 22 | 23 | def gen(typename, params, **kwargs): 24 | gentype = name_to_gen[typename] 25 | 26 | assembly = kwargs.get('assembly', False) 27 | del kwargs['assembly'] 28 | 29 | sys.stdout, saved_stdout = stringio.StringIO(), sys.stdout 30 | def new_exit(s): 31 | sys.stdout.seek(0) 32 | data = sys.stdout.read() 33 | sys.stdout = saved_stdout 34 | print data 35 | os._exit(s) 36 | sys.exit, saved_exit = new_exit, sys.exit 37 | 38 | name = gentype(params, **kwargs) 39 | 40 | data = sys.stdout.seek(0) 41 | data = sys.stdout.read() 42 | sys.stdout = saved_stdout 43 | sys.exit = saved_exit 44 | 45 | if assembly: 46 | return name, data 47 | return name, utils.bpf_compile(data) 48 | -------------------------------------------------------------------------------- /linux_tools/bpf_asm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Minimal BPF assembler 3 | * 4 | * Instead of libpcap high-level filter expressions, it can be quite 5 | * useful to define filters in low-level BPF assembler (that is kept 6 | * close to Steven McCanne and Van Jacobson's original BPF paper). 7 | * In particular for BPF JIT implementors, JIT security auditors, or 8 | * just for defining BPF expressions that contain extensions which are 9 | * not supported by compilers. 10 | * 11 | * How to get into it: 12 | * 13 | * 1) read Documentation/networking/filter.txt 14 | * 2) Run `bpf_asm [-c] ` to translate into binary 15 | * blob that is loadable with xt_bpf, cls_bpf et al. Note: -c will 16 | * pretty print a C-like construct. 17 | * 18 | * Copyright 2013 Daniel Borkmann 19 | * Licensed under the GNU General Public License, version 2.0 (GPLv2) 20 | */ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | extern void bpf_asm_compile(FILE *fp, bool cstyle); 27 | 28 | int main(int argc, char **argv) 29 | { 30 | FILE *fp = stdin; 31 | bool cstyle = false; 32 | int i; 33 | 34 | for (i = 1; i < argc; i++) { 35 | if (!strncmp("-c", argv[i], 2)) { 36 | cstyle = true; 37 | continue; 38 | } 39 | 40 | fp = fopen(argv[i], "r"); 41 | if (!fp) { 42 | fp = stdin; 43 | continue; 44 | } 45 | 46 | break; 47 | } 48 | 49 | bpf_asm_compile(fp, cstyle); 50 | 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /LICENSE-BSD-CloudFlare: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 CloudFlare, Inc. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of the CloudFlare, Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /hex2pcap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import getopt 4 | import os 5 | import pcappy 6 | import sys 7 | 8 | 9 | def usage(): 10 | print """ 11 | hex2pcap.py [ OPTIONS ] [ hex file... ] 12 | 13 | Read hex packets from stdin or given files and print a pcap file to 14 | stdout. Input lines starting with hash or semicolon are ignored, only 15 | part of a line before tab character is parsed as hex. 16 | 17 | Options are: 18 | -h, --help print this message 19 | """.lstrip() 20 | sys.exit(2) 21 | 22 | 23 | def main(): 24 | try: 25 | opts, args = getopt.getopt(sys.argv[1:], "h", ["help"]) 26 | except getopt.GetoptError as err: 27 | print str(err) 28 | usage() 29 | 30 | for o, a in opts: 31 | if o in ("-h", "--help"): 32 | usage() 33 | else: 34 | assert False, "unhandled option" 35 | 36 | if not args: 37 | readfds = [sys.stdin] 38 | else: 39 | readfds = [open(fname, 'rb') for fname in args] 40 | 41 | dump = pcappy.PcapPyDead(snaplen=65536).dump_open(sys.stdout) 42 | hdr = {'ts':{'tv_sec':0, 'tv_usec':0}} 43 | 44 | for fd in readfds: 45 | for line in fd: 46 | line = line.lstrip() 47 | if not line or line[0] in '#;': 48 | continue 49 | h, _, _ = line.partition("\t") 50 | if not h: 51 | continue 52 | raw = h.rstrip().decode('hex') 53 | 54 | hdr['caplen'] = hdr['len'] = len(raw) 55 | dump.write(hdr, raw) 56 | 57 | sys.stdout.flush() 58 | dump.flush() 59 | 60 | # normal exit crashes due to a double free error in pcappy 61 | os._exit(0) 62 | 63 | 64 | if __name__ == "__main__": 65 | try: 66 | main() 67 | except IOError, e: 68 | if e.errno == 32: 69 | os._exit(-1) 70 | raise 71 | except KeyboardInterrupt: 72 | os._exit(-1) 73 | -------------------------------------------------------------------------------- /bpftools/gen_suffix.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import string 3 | import struct 4 | import sys 5 | 6 | 7 | def gen(args, l3_off=0, ipversion=4, negate=False): 8 | parser = argparse.ArgumentParser( 9 | formatter_class=argparse.RawDescriptionHelpFormatter, 10 | prog="%s suffix --" % (sys.argv[0]), 11 | description=r''' 12 | 13 | Generate a BPF rule that will match packets based on a given hex 14 | encoded suffix. For example to match packets ending with 010203: 15 | 16 | %(prog)s 010203 17 | 18 | This is the same as: 19 | 20 | %(prog)s 01 02 03 21 | ''') 22 | 23 | parser.add_argument('suffix', nargs='*', 24 | help='hex encoded suffix (spaces are ignored)') 25 | 26 | args = parser.parse_args(args) 27 | 28 | suffix_hex = ''.join(args.suffix) 29 | 30 | if not suffix_hex: 31 | parser.print_help() 32 | sys.exit(-1) 33 | 34 | assert len(suffix_hex) % 2 == 0 35 | 36 | print " ld #len" 37 | print " sub #%i" % (len(suffix_hex) / 2,) 38 | print " tax" 39 | print 40 | 41 | suffix_hex = suffix_hex.rstrip('x') 42 | s = suffix_hex.decode('hex') 43 | 44 | off = 0 45 | print " ; matching %s" % (s.encode('hex'),) 46 | print " ; %r" % (s,) 47 | while s: 48 | if len(s) >= 4: 49 | m, s = s[:4], s[4:] 50 | m, = struct.unpack('!I', m) 51 | print " ld [x + %i]" % off 52 | print " jneq #0x%08x, nomatch" % (m, ) 53 | off += 4 54 | elif len(s) >= 2: 55 | m, s = s[:2], s[2:] 56 | m, = struct.unpack('!H', m) 57 | print " ldh [x + %i]" % off 58 | print " jneq #0x%04x, nomatch" % (m, ) 59 | off += 2 60 | else: 61 | m, s = s[:1], s[1:] 62 | m, = struct.unpack('!B', m) 63 | print " ldb [x + %i]" % off 64 | print " jneq #0x%02x, nomatch" % (m, ) 65 | off += 1 66 | print " ret #%i" % (65535 if not negate else 0) 67 | print "" 68 | print "nomatch:" 69 | print " ret #%i" % (0 if not negate else 65535) 70 | 71 | return suffix_hex 72 | -------------------------------------------------------------------------------- /bpftools/linktypes.py: -------------------------------------------------------------------------------- 1 | LINKTYPE_NULL=0 2 | LINKTYPE_ETHERNET=1 3 | LINKTYPE_AX25=3 4 | LINKTYPE_IEEE802_5=6 5 | LINKTYPE_ARCNET_BSD=7 6 | LINKTYPE_SLIP=8 7 | LINKTYPE_PPP=9 8 | LINKTYPE_FDDI=10 9 | LINKTYPE_PPP_HDLC=50 10 | LINKTYPE_PPP_ETHER=51 11 | LINKTYPE_ATM_RFC1483=100 12 | LINKTYPE_RAW=101 13 | LINKTYPE_C_HDLC=104 14 | LINKTYPE_IEEE802_11=105 15 | LINKTYPE_FRELAY=107 16 | LINKTYPE_LOOP=108 17 | LINKTYPE_LINUX_SLL=113 18 | LINKTYPE_LTALK=114 19 | LINKTYPE_PFLOG=117 20 | LINKTYPE_IEEE802_11_PRISM=119 21 | LINKTYPE_IP_OVER_FC=122 22 | LINKTYPE_SUNATM=123 23 | LINKTYPE_IEEE802_11_RADIOTAP=127 24 | LINKTYPE_ARCNET_LINUX=129 25 | LINKTYPE_APPLE_IP_OVER_IEEE1394=138 26 | LINKTYPE_MTP2_WITH_PHDR=139 27 | LINKTYPE_MTP2=140 28 | LINKTYPE_MTP3=141 29 | LINKTYPE_SCCP=142 30 | LINKTYPE_DOCSIS=143 31 | LINKTYPE_LINUX_IRDA=144 32 | LINKTYPE_USER0=147 33 | LINKTYPE_USER1=148 34 | LINKTYPE_USER2=149 35 | LINKTYPE_USER3=150 36 | LINKTYPE_USER4=151 37 | LINKTYPE_USER5=152 38 | LINKTYPE_USER61=53 39 | LINKTYPE_USER7=154 40 | LINKTYPE_USER8=155 41 | LINKTYPE_USER9=156 42 | LINKTYPE_USER10=157 43 | LINKTYPE_USER11=158 44 | LINKTYPE_USER12=159 45 | LINKTYPE_USER13=160 46 | LINKTYPE_USER14=161 47 | LINKTYPE_USER15=162 48 | LINKTYPE_IEEE802_11_AVS=163 49 | LINKTYPE_BACNET_MS_TP=165 50 | LINKTYPE_PPP_PPPD=166 51 | LINKTYPE_GPRS_LLC=169 52 | LINKTYPE_LINUX_LAPD=177 53 | LINKTYPE_BLUETOOTH_HCI_H4=187 54 | LINKTYPE_USB_LINUX=189 55 | LINKTYPE_PPI=192 56 | LINKTYPE_IEEE802_15_4=195 57 | LINKTYPE_SITA=196 58 | LINKTYPE_ERF=197 59 | LINKTYPE_BLUETOOTH_HCI_H4_WITH_PHDR=201 60 | LINKTYPE_AX25_KISS=202 61 | LINKTYPE_LAPD=203 62 | LINKTYPE_PPP_WITH_DIR=204 63 | LINKTYPE_C_HDLC_WITH_DIR=205 64 | LINKTYPE_FRELAY_WITH_DIR=206 65 | LINKTYPE_IPMB_LINUX=209 66 | LINKTYPE_IEEE802_15_4_NONASK_PHY=215 67 | LINKTYPE_USB_LINUX_MMAPPED=220 68 | LINKTYPE_FC_2=224 69 | LINKTYPE_FC_2_WITH_FRAME_DELIMS=225 70 | LINKTYPE_IPNET=226 71 | LINKTYPE_CAN_SOCKETCAN=227 72 | LINKTYPE_IPV4=228 73 | LINKTYPE_IPV6=229 74 | LINKTYPE_IEEE802_15_4_NOFCS=230 75 | LINKTYPE_DBUS=231 76 | LINKTYPE_DVB_CI=235 77 | LINKTYPE_MUX27010=236 78 | LINKTYPE_STANAG_5066_D_PDU=237 79 | LINKTYPE_NFLOG=239 80 | LINKTYPE_NETANALYZER=240 81 | LINKTYPE_NETANALYZER_TRANSPARENT=241 82 | LINKTYPE_IPOIB=242 83 | LINKTYPE_MPEG_2_TS=243 84 | LINKTYPE_NG40=244 85 | LINKTYPE_NFC_LLCP=245 86 | -------------------------------------------------------------------------------- /bpfgen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import os 5 | import stat 6 | import string 7 | import sys 8 | 9 | import bpftools 10 | 11 | 12 | def main(): 13 | parser = argparse.ArgumentParser( 14 | formatter_class=argparse.RawDescriptionHelpFormatter, 15 | description=r''' 16 | 17 | This tool creates a Berkeley Packet Filter (BPF) bytecode that will 18 | match packets based on given criteria. Right now we support the 19 | following generators: 20 | 21 | dns - matches dns queries for given domains 22 | dns_validate - matches dns malformed requests 23 | suffix - matches packets with given suffix 24 | 25 | Generators can take arbitrary parameters and command line options. To 26 | read more on their usage pass '--help' option to the genrator (not to 27 | this wrapper), for example: 28 | 29 | %(prog)s dns -- --help 30 | 31 | Example of use: 32 | 33 | %(prog)s dns -- -i *.example.com 34 | %(prog)s dns -- -i example.com *.example.com *.*.example.com 35 | %(prog)s dns_validate 36 | %(prog)s dns_validate -- --strict 37 | %(prog)s suffix -- 010203 38 | 39 | Note that some common options are accepted by this wrapper, not by the 40 | BPF generators, for example: 41 | 42 | %(prog)s -s suffix -- 010203 43 | %(prog)s -s -n suffix -- 010203 44 | %(prog)s -s -n -o 14 suffix -- 010203 45 | %(prog)s -s -n -o 14 -6 suffix -- 010203 46 | ''') 47 | 48 | parser.add_argument('-6', '--inet6', action='store_true', 49 | help='generate script for IPv6') 50 | parser.add_argument('-n', '--negate', action='store_true', 51 | help='negate the logic') 52 | parser.add_argument('-o', '--offset', type=int, default=0, 53 | help='offset of an L3 header') 54 | parser.add_argument('-s', '--assembly', action='store_true', 55 | help='print readable assembly, not numeric bytecode') 56 | parser.add_argument('type', nargs=1, choices=bpftools.generator_names, 57 | help='BPF generator type') 58 | parser.add_argument('parameters', nargs='*', 59 | help='parameters passed to the BPF generator') 60 | 61 | args = parser.parse_args() 62 | 63 | if len(args.type) != 1: 64 | parser.print_help() 65 | sys.exit(-1) 66 | 67 | name, ret = bpftools.gen(args.type[0], 68 | args.parameters, 69 | assembly=args.assembly, 70 | l3_off=args.offset, 71 | ipversion=4 if not args.inet6 else 6, 72 | negate=args.negate, 73 | ) 74 | print ret 75 | 76 | 77 | if __name__ == "__main__": 78 | main() 79 | -------------------------------------------------------------------------------- /bpftools/utilsdns.py: -------------------------------------------------------------------------------- 1 | import struct 2 | 3 | 4 | _dns_type = map(lambda (a,b): (a, int(b)), map(lambda line: line.split(), ''' 5 | A 1 6 | NS 2 7 | MD 3 8 | MF 4 9 | CNAME 5 10 | SOA 6 11 | MB 7 12 | MG 8 13 | MR 9 14 | NULL 10 15 | WKS 11 16 | PTR 12 17 | HINFO 13 18 | MINFO 14 19 | MX 15 20 | TXT 16 21 | RP 17 22 | AFSDB 18 23 | X25 19 24 | ISDN 20 25 | RT 21 26 | NSAP 22 27 | NSAPPTR 23 28 | SIG 24 29 | KEY 25 30 | PX 26 31 | GPOS 27 32 | AAAA 28 33 | LOC 29 34 | NXT 30 35 | EID 31 36 | NIMLOC 32 37 | SRV 33 38 | ATMA 34 39 | NAPTR 35 40 | KX 36 41 | CERT 37 42 | DNAME 39 43 | OPT 41 44 | DS 43 45 | SSHFP 44 46 | IPSECKEY 45 47 | RRSIG 46 48 | NSEC 47 49 | DNSKEY 48 50 | DHCID 49 51 | NSEC3 50 52 | NSEC3PARAM 51 53 | TLSA 52 54 | HIP 55 55 | NINFO 56 56 | RKEY 57 57 | TALINK 58 58 | CDS 59 59 | SPF 99 60 | UINFO 100 61 | UID 101 62 | GID 102 63 | UNSPEC 103 64 | NID 104 65 | L32 105 66 | L64 106 67 | LP 107 68 | EUI48 108 69 | EUI64 109 70 | TKEY 249 71 | TSIG 250 72 | IXFR 251 73 | AXFR 252 74 | MAILB 253 75 | MAILA 254 76 | ANY 255 77 | URI 256 78 | CAA 257 79 | TA 32768 80 | DLV 32769 81 | '''.strip().split('\n'))) 82 | 83 | qtype2str = dict( (v, n) for n, v in _dns_type) 84 | str2qtype = dict( (n, v) for n, v in _dns_type) 85 | 86 | _dns_class = map(lambda (a,b): (a, int(b)), map(lambda line: line.split(), ''' 87 | INET 1 88 | CSNET 2 89 | CHAOS 3 90 | HESIOD 4 91 | NONE 254 92 | ANY 255 93 | '''.strip().split('\n'))) 94 | 95 | qclass2str = dict( (v, n) for n, v in _dns_class) 96 | str2qclass = dict( (n, v) for n, v in _dns_class) 97 | 98 | 99 | def unpack_domain(data, off): 100 | off1 = None 101 | 102 | parts = [] 103 | while True: 104 | c, = struct.unpack_from('!B', data, off) 105 | if c == 0x00: 106 | off += 1 107 | break 108 | elif (c & 0xC0): 109 | c, = struct.unpack_from('!H', data, off) 110 | ptr = c ^ 0xc000 111 | off += 2 112 | if off1 == None: 113 | off1 = off 114 | off = ptr 115 | else: 116 | parts.append( data[off+1:off+1+c] ) 117 | off += c + 1 118 | 119 | qtype, qclass = struct.unpack_from('!HH', data, off) 120 | return '.'.join(parts), qtype, qclass 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BPF Tools 2 | ========= 3 | 4 | Introductory blog posts: 5 | 6 | - http://blog.cloudflare.com/bpf-the-forgotten-bytecode/ 7 | - http://blog.cloudflare.com/introducing-the-bpf-tools/ 8 | 9 | 10 | Here you can find a set of tool for analyzing and processing of pcap 11 | traffic dumps. The aim of this tool is to help creating BPF rules that 12 | will match (and drop) malicious traffic. 13 | 14 | To run these scripts you will need: 15 | 16 | - Kernel headers (ideally from a 3.10+ kernel): 17 | 18 | $ sudo apt-get install linux-headers-generic 19 | 20 | - Installed dependencies: 21 | 22 | $ sudo apt-get install python-setuptools libpcap-dev \ 23 | libreadline-dev binutils-dev bison flex 24 | $ sudo easy_install pcappy 25 | 26 | - Build the binary tools in `linux_tools` directory: 27 | 28 | $ make 29 | 30 | 31 | BPF Tools repository contains a number simple Python scripts, some of 32 | them focus on analyzing pcap files, others focus more on the BPF: 33 | 34 | - `pcap2hex`, `hex2pcap` 35 | - `parsedns` 36 | - `bpfgen` 37 | - `filter` 38 | - `iptables_bpf`, `iptables_bpf_chain` 39 | 40 | 41 | bpfgen 42 | ====== 43 | 44 | The core script is `bpfgen` which generates the BPF bytecode. For more 45 | information please read: 46 | 47 | $ ./bpfgen --help 48 | $ ./bpfgen dns -- --help 49 | $ ./bpfgen dns_validate -- --help 50 | $ ./bpfgen suffix -- --help 51 | 52 | 53 | iptables_bpf 54 | ============ 55 | 56 | This script generates a simple bash script that contains iptables 57 | rules that drop traffic based on selected parameters. 58 | 59 | For example, to generate a script dropping packets exactly to a domain 60 | "example.com" you can run: 61 | 62 | $ ./iptables_bpf dns -- example.com 63 | Generated file 'bpf_dns_ip4_example_com.sh' 64 | 65 | If you want commands for IPv6 use `-6` flag: 66 | 67 | $ ./iptables_bpf -6 dns -- example.com 68 | Generated file 'bpf_dns_ip6_example_com.sh' 69 | 70 | The rule can match any from a number listed domains: 71 | 72 | $ ./iptables_bpf dns -- example.com example1.com example2.com 73 | Generated file 'bpf_dns_ip4_example_com_example1_com_example2_com.sh' 74 | 75 | If you want to match any subdomain you can use a star '*'. This will 76 | only work if star is the only character in a domain part. Valid 77 | examples: 78 | 79 | $ ./iptables_bpf dns -- *.example.com 80 | Generated file 'bpf_dns_ip4_any_example_com.sh' 81 | 82 | $ ./iptables_bpf dns -- *.example.*.gov.de 83 | Generated file 'bpf_dns_ip4_any_example_any_gov_de.sh' 84 | 85 | 86 | You can run the generated script to apply the rule and match it 87 | against one or more flooded ip addresses: 88 | 89 | $ sudo ./bpf_dns_ip4_example_com.sh 1.2.3.4/32 90 | 91 | To remove the iptable rule simply specify `--delete`: 92 | 93 | $ sudo ./bpf_dns_ip4_example_com.sh --delete 94 | -------------------------------------------------------------------------------- /bpftools/gen_tcpdump.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import re 4 | import subprocess 5 | import sys 6 | 7 | from . import linktypes 8 | 9 | FAKE_PCAP="d4 c3 b2 a1 02 00 04 00 00 00 00 00 00 00 00 00 00 00 01 00 %02x 00 00 00".replace(' ','') 10 | 11 | 12 | def bpf_from_expr(expr, linktype): 13 | fake_pcap = (FAKE_PCAP % (linktype,)).decode('hex') 14 | 15 | for path in ['/bin', '/sbin', '/usr/bin', '/usr/sbin', '/usr/local/bin', '/usr/local/sbin']: 16 | tcpdump = os.path.join(path, 'tcpdump') 17 | if os.path.exists(tcpdump): 18 | break 19 | else: 20 | raise ValueError("Can't find tcpdump executable! run: apt-get install tcpdump") 21 | 22 | if expr.strip().startswith('-'): 23 | raise ValueError("") 24 | 25 | p = subprocess.Popen([tcpdump, '-r-', '-d', expr], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 26 | stdout, stderr = p.communicate(fake_pcap) 27 | if p.returncode != 0: 28 | raise ValueError("tcpdump exited with %d %r" % (p.returncode, stderr)) 29 | return stdout.strip() 30 | 31 | 32 | def gen(params, l3_off=0, ipversion=4, negate=False): 33 | parser = argparse.ArgumentParser( 34 | formatter_class=argparse.RawDescriptionHelpFormatter, 35 | prog="%s tcpdump --" % (sys.argv[0]), 36 | description=r''' 37 | 38 | Generate bpf using tcpdump pcap compiler. Sadly for now it's done by 39 | shelling out to tcpdump. 40 | 41 | ''') 42 | parser.add_argument('expr', nargs='+', 43 | help='tcpdump expression') 44 | args = parser.parse_args(args=params) 45 | 46 | 47 | # map offset to linktype heuristics 48 | vlan = False 49 | if l3_off == 0: 50 | if ipversion == 4: 51 | lt = linktypes.LINKTYPE_IPV4 52 | if ipversion == 6: 53 | lt = linktypes.LINKTYPE_IPV6 54 | elif l3_off == 14: 55 | lt = linktypes.LINKTYPE_ETHERNET 56 | elif l3_off == 16: 57 | lt = linktypes.LINKTYPE_LINUX_SLL 58 | elif l3_off == 18: 59 | vlan = True 60 | lt = linktypes.LINKTYPE_ETHERNET 61 | else: 62 | assert False, 'l3_off of %d not supported' % (l3_off,) 63 | 64 | expr = ' '.join(args.expr) 65 | if negate: 66 | expr = "not (%s)" % (expr,) 67 | if vlan: 68 | expr = "vlan and (%s)" % (expr,) 69 | 70 | code = bpf_from_expr(expr, lt) 71 | print "; ipver=%s" % (ipversion,) 72 | print "; %s" % (expr,) 73 | print 74 | for line in code.split('\n'): 75 | lno, _ , rest = line.partition(" ") 76 | rest = rest.replace("#pktlen", "#len") 77 | lno = int(lno[1:-1]) 78 | print "l%03d:" % (lno,) 79 | m = re.match("^(?P.*)\s+jt (?P\d+)\s+jf (?P\d+)$", rest) 80 | if m: 81 | d = m.groupdict() 82 | print " %s, l%03d, l%03d" % (d['prefix'].strip(), int(d['jt']), int(d['jf'])) 83 | else: 84 | print " %s" % (rest,) 85 | -------------------------------------------------------------------------------- /offset: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import getopt 4 | import os 5 | import pcappy 6 | import string 7 | import sys 8 | 9 | 10 | def usage(): 11 | print r""" 12 | offset.py [ OPTIONS ] string [ pcap file... ] 13 | 14 | Find an offset of a string in packet. Packets are read from given pcap 15 | files or from stdin. The string is decoded so might contain things 16 | like '\x00' or '\t'. 17 | 18 | Options are: 19 | -h, --help print this message 20 | -r, --reverse the offset from the end, not the start of the packet 21 | -p, --prefix print hex encoded bytes up to the matched string 22 | -s, --suffix print hex encoded bytes from the matched string onwards 23 | -i, --ignore ignore lines that don't contain the string 24 | """.lstrip() 25 | sys.exit(2) 26 | 27 | def main(): 28 | reverse = prefix = suffix = ignore = False 29 | 30 | try: 31 | opts, args = getopt.getopt(sys.argv[1:], "hrpsi", 32 | ["help", "reverse", "prefix", "suffix", 33 | "ignore"]) 34 | except getopt.GetoptError as err: 35 | print str(err) 36 | usage() 37 | 38 | for o, a in opts: 39 | if o in ("-h", "--help"): 40 | usage() 41 | sys.exit() 42 | elif o in ("-r", "--reverse"): 43 | reverse = True 44 | elif o in ("-p", "--prefix"): 45 | prefix = True 46 | elif o in ("-s", "--suffix"): 47 | suffix = True 48 | elif o in ("-i", "--ignore"): 49 | ignore = True 50 | else: 51 | assert False, "unhandled option" 52 | 53 | if not args: 54 | assert False, "first argument must be a string to match" 55 | 56 | match, args = args[0], args[1:] 57 | if not args: 58 | readfds = [sys.stdin] 59 | else: 60 | readfds = [open(fname, 'rb') for fname in args] 61 | 62 | # Important. The first parameter may be encoded like '\x00' 63 | match = match.decode("string_escape") 64 | 65 | for fd in readfds: 66 | p = pcappy.open_offline(fd) 67 | while True: 68 | r = p.next_ex() 69 | if r is None: 70 | break 71 | hdr, data = r 72 | 73 | o = string.find(data, match) 74 | if ignore and o == -1: 75 | continue 76 | 77 | l = [str(o if not reverse else o - len(data))] 78 | if o != -1: 79 | if prefix: 80 | l.append( data[:o].encode('hex') ) 81 | if suffix: 82 | l.append( data[o:].encode('hex') ) 83 | else: 84 | if prefix or suffix: 85 | l.append( data.encode('hex') ) 86 | print '\t'.join(l) 87 | 88 | sys.stdout.flush() 89 | 90 | # normal exit crashes due to a double free error in pcappy 91 | os._exit(0) 92 | 93 | 94 | if __name__ == "__main__": 95 | try: 96 | main() 97 | except IOError, e: 98 | if e.errno == 32: 99 | os._exit(-1) 100 | raise 101 | except KeyboardInterrupt: 102 | os._exit(-1) 103 | -------------------------------------------------------------------------------- /bpftools/gen_dns_validate.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import string 3 | import sys 4 | 5 | 6 | def gen(args, l3_off=0, ipversion=4, negate=False): 7 | parser = argparse.ArgumentParser( 8 | formatter_class=argparse.RawDescriptionHelpFormatter, 9 | prog="%s dns_validate --" % (sys.argv[0]), 10 | description=r''' 11 | 12 | Generate raw BPF rules that match malformed DNS requests. This 13 | generator does not inspect the packet query name (queried domain), but 14 | instead looks at general packet sanity. To be more precise it checks: 15 | 16 | - if the qdcount is exactly 1 17 | - if ancount and nscounts fields are exactly 0 18 | - if arcount is either 0 or 1 19 | - if the length of the packet is greater than 12 + 9 bytes (that is 20 | at least long enough to have query for a two parts domain like "a.b") 21 | - if the flags field is sane 22 | 23 | The last check is important. During normal operation we want to accept 24 | DNS flags: 25 | 26 | - "Recursion Desired" 27 | - "Recursion Available" (due to a bug in RIPE Atlas probes) 28 | - "Checking Disabled" 29 | 30 | That said, legitimate traffic should only ever have "Checking 31 | Disabled" flag set. To be more strict and ensure "RD" and "RA" flags 32 | are clear supply "--strict" flag. 33 | ''') 34 | 35 | parser.add_argument('-s', '--strict', action='store_true', 36 | help='be more strict, also drop Recursion Desired and Recursion Available flags') 37 | args = parser.parse_args(args=args) 38 | 39 | if ipversion == 4: 40 | print " ld #%i" % (l3_off) 41 | print " ldx 4*([%i]&0xf)" % (l3_off,) 42 | print " add x" 43 | elif ipversion == 6: 44 | # assuming first "next header" is UDP 45 | print " ld #%i" % (l3_off + 40) # 40B of ipv6 46 | print " tax" 47 | print " ; x points to start of UDP header" 48 | print " ; load udp header + payload length" 49 | print " ldh [x + 4]" 50 | print " ; valid udp payload length must be > 12 + 9 (assuming a.b query)" 51 | print " jlt #%i, match" % (8 + 12 + 9) 52 | print 53 | print " txa" 54 | print " add #8" 55 | print " tax" 56 | print " ; x points to start of DNS" 57 | print 58 | 59 | print " ; allow only flags:" 60 | # rfc6840 says CD should always be set 61 | print " ; 4: checking_disabled" 62 | # and AD might be set on queries: 63 | # http://tools.ietf.org/html/rfc6840#page-10 64 | print " ; 5: authenticated_data" 65 | print " ; 6: zflag" 66 | flags = 0xffff 67 | flags &= ~(1 << 4) 68 | flags &= ~(1 << 5) 69 | flags &= ~(1 << 6) 70 | if not args.strict: 71 | print " ; 8: recursion_desired" 72 | print " ; 9: recursion_available" 73 | # there is some background noise of RD 74 | flags &= ~(1 << 8) 75 | # RIPE Atlas probes set RA flag in requests. Ugly. 76 | flags &= ~(1 << 9) 77 | print " ldh [x + 2] ; opcode = 0, rdcode = 0 etc" 78 | print " and #0x%04x" % flags 79 | print " jne #0, match" 80 | print 81 | print " ldh [x + 4] ; qdcount must be 1" 82 | print " jneq #0x1, match" 83 | print " ld [x + 6] ; ancount and nscount must be 0" 84 | print " jneq #0x0, match" 85 | print " ldh [x + 10] ; arcount must be 0 or 1" 86 | print " jgt #0x1, match" 87 | print " ret #%i" % (0 if not negate else 65535) 88 | print 89 | print "match:" 90 | print " ret #%i" % (65535 if not negate else 0) 91 | 92 | if args.strict: 93 | return 'strict' 94 | return '' 95 | -------------------------------------------------------------------------------- /filter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import getopt 4 | import itertools 5 | import os 6 | import pcappy 7 | import pcappy.types 8 | import sys 9 | 10 | import bpftools.utils 11 | 12 | 13 | def usage(): 14 | print r""" 15 | filter [ OPTIONS ] [ pcap file... ] 16 | 17 | Read pcap data from stdin or given files, run it through a BPF filter 18 | and write matching packets to stdout as pcap. 19 | 20 | Options are: 21 | -h, --help print this message 22 | -b, --bytecode filter with given BPF bytecode 23 | -e, --expr fitler with given BPF expression 24 | -c, --compile FILE compile given bpf file and use as filter 25 | 26 | For example to select only IP packets you can use: 27 | 28 | filter -e "ip" 29 | filter -b "4,40 0 0 12,21 0 1 2048,6 0 0 65535,6 0 0 0," 30 | 31 | Where the bytecode might have been generated by tcpdump: 32 | 33 | sudo tcpdump -p -n -ddd -i eth0 "ip" |tr "\n" "," 34 | """.lstrip() 35 | sys.exit(2) 36 | 37 | 38 | def bpf_from_bytecode(bytecode): 39 | instructions = bytecode.strip(",").split(',')[1:] 40 | 41 | class X: pass 42 | bpf = X() 43 | bpf._bpf = pcappy.types.bpf_program() 44 | bpf._bpf.bf_len = len(instructions) 45 | bpf._bpf.bf_insns = (pcappy.types.bpf_insn * (len(instructions)+10))() 46 | for i, ins_txt in enumerate(instructions): 47 | r = bpf._bpf.bf_insns[i] 48 | r.code, r.jt, r.jf, r.k = map(int, itertools.chain(ins_txt.split(), (0,0,0)))[:4] 49 | return bpf 50 | 51 | 52 | def bpf_from_expr(expr): 53 | return pcappy.PcapPyBpfProgram(expr, 0, '0.0.0.0', 54 | linktype=pcappy.LINKTYPE_ETHERNET, 55 | snaplen=65536) 56 | 57 | 58 | def main(): 59 | bpf = None 60 | 61 | try: 62 | opts, args = getopt.getopt(sys.argv[1:], "he:b:c:", 63 | ["help", "expr=", "bytecode=", "compile="]) 64 | except getopt.GetoptError as err: 65 | print str(err) 66 | usage() 67 | 68 | for o, a in opts: 69 | if o in ("-h", "--help"): 70 | usage() 71 | sys.exit() 72 | elif o in ("-e", "--expr"): 73 | bpf = bpf_from_expr(a) 74 | elif o in ("-b", "--bytecode"): 75 | bpf = bpf_from_bytecode(a) 76 | elif o in ("-c", "--compile"): 77 | x = open(a, 'rb') # check if can open 78 | bpf = bpf_from_bytecode(bpftools.utils.bpf_compile(x.read())) 79 | else: 80 | assert False, "unhandled option" 81 | 82 | if not args: 83 | readfds = [sys.stdin] 84 | else: 85 | readfds = [open(fname, 'rb') for fname in args] 86 | 87 | dump = pcappy.PcapPyDead(snaplen=65536).dump_open(sys.stdout) 88 | 89 | for fd in readfds: 90 | p = pcappy.open_offline(fd) 91 | if bpf: 92 | p.filter = bpf 93 | 94 | while True: 95 | try: 96 | r = p.next_ex() 97 | except pcappy.PcapPyException: 98 | break 99 | if r is None: 100 | break 101 | 102 | hdr, data = r 103 | dump.write(hdr, data) 104 | 105 | sys.stdout.flush() 106 | dump.flush() 107 | 108 | # normal exit crashes due to a double free error in pcappy 109 | os._exit(0) 110 | 111 | 112 | if __name__ == "__main__": 113 | try: 114 | main() 115 | except IOError, e: 116 | if e.errno == 32: 117 | os._exit(-1) 118 | raise 119 | except KeyboardInterrupt: 120 | os._exit(-1) 121 | -------------------------------------------------------------------------------- /pcap2hex: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import getopt 4 | import os 5 | import sys 6 | import string 7 | import struct 8 | import pcappy 9 | 10 | import bpftools.utils 11 | 12 | 13 | def usage(): 14 | print """ 15 | pcap2hex.py [ OPTIONS ] [ pcap file... ] 16 | 17 | Read pcap from stdin or given pcap files and print hex-encoded 18 | packets. Additionally pcap2hex can normalize DLT_LINUX_SLL l2 header 19 | (used by "tcpdump -i any") to something that looks more like 20 | DLT_EN10MB (by "tcpdump -i eth0"). Pcap2hex can also scrub (anonymize) 21 | packets by overwriting l2 MAC addresses and l3 IPv4 and IPv6 addresses 22 | with zeros. 23 | 24 | Options are: 25 | -h, --help print this message 26 | -s, --scrub scrub/anonymize MAC and IP addresses 27 | -n, --no-normalize don't normalize L2 headers from 16 to 28 | 14 bytes (from DLT_LINUX_SLL to DLT_EN10MB) 29 | -a, --ascii print printable asci characters 30 | """.lstrip() 31 | sys.exit(2) 32 | 33 | 34 | def main(): 35 | scrub = ascii = False 36 | normalize = True 37 | 38 | try: 39 | opts, args = getopt.getopt(sys.argv[1:], "hsna", 40 | ["help", "scrub", "no-normalize", "ascii"]) 41 | except getopt.GetoptError as err: 42 | print str(err) 43 | usage() 44 | 45 | for o, a in opts: 46 | if o in ("-h", "--help"): 47 | usage() 48 | elif o in ("-s", "--scrub"): 49 | scrub = True 50 | elif o in ("-n", "--no-normalize"): 51 | normalize = False 52 | elif o in ("-a", "--ascii"): 53 | ascii = True 54 | else: 55 | assert False, "unhandled option" 56 | 57 | if not args: 58 | readfds = [sys.stdin] 59 | else: 60 | readfds = [open(fname, 'rb') for fname in args] 61 | 62 | for fd in readfds: 63 | p = pcappy.open_offline(fd) 64 | 65 | l3_off = None 66 | l3_off_bad = 0 67 | 68 | while True: 69 | try: 70 | r = p.next_ex() 71 | except pcappy.PcapPyException: 72 | break 73 | if r is None: 74 | break 75 | hdr, data = r 76 | 77 | if l3_off is None: 78 | l3_off = bpftools.utils.find_ip_offset(data) 79 | if l3_off is None: 80 | l3_off_bad += 1 81 | if l3_off_bad > 5: 82 | raise Exception("Can't find IP offset") 83 | continue 84 | 85 | if scrub: 86 | data = bpftools.utils.do_scrub(data, l3_off) 87 | 88 | if normalize and l3_off in (16,): 89 | data = data[2:] 90 | if normalize and l3_off in (18,): 91 | data = data[0:12] + data[16:] 92 | 93 | h = data.encode('hex') 94 | if not ascii: 95 | print h 96 | else: 97 | s = ''.join([c if (c in string.printable and 98 | c not in string.whitespace) else '.' 99 | for c in data]) 100 | print "%s\t%s" % (h, s) 101 | 102 | sys.stdout.flush() 103 | 104 | # normal exit crashes due to a double free error in pcappy 105 | os._exit(0) 106 | 107 | 108 | if __name__ == "__main__": 109 | try: 110 | main() 111 | except IOError, e: 112 | if e.errno == 32: 113 | os._exit(-1) 114 | raise 115 | except KeyboardInterrupt: 116 | os._exit(-1) 117 | -------------------------------------------------------------------------------- /linux_tools/bpf_exp.l: -------------------------------------------------------------------------------- 1 | /* 2 | * BPF asm code lexer 3 | * 4 | * This program is free software; you can distribute it and/or modify 5 | * it under the terms of the GNU General Public License as published 6 | * by the Free Software Foundation; either version 2 of the License, 7 | * or (at your option) any later version. 8 | * 9 | * Syntax kept close to: 10 | * 11 | * Steven McCanne and Van Jacobson. 1993. The BSD packet filter: a new 12 | * architecture for user-level packet capture. In Proceedings of the 13 | * USENIX Winter 1993 Conference Proceedings on USENIX Winter 1993 14 | * Conference Proceedings (USENIX'93). USENIX Association, Berkeley, 15 | * CA, USA, 2-2. 16 | * 17 | * Copyright 2013 Daniel Borkmann 18 | * Licensed under the GNU General Public License, version 2.0 (GPLv2) 19 | */ 20 | 21 | %{ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "bpf_exp.yacc.h" 28 | 29 | extern void yyerror(const char *str); 30 | 31 | %} 32 | 33 | %option align 34 | %option ecs 35 | 36 | %option nounput 37 | %option noreject 38 | %option noinput 39 | %option noyywrap 40 | 41 | %option 8bit 42 | %option caseless 43 | %option yylineno 44 | 45 | %% 46 | 47 | "ldb" { return OP_LDB; } 48 | "ldh" { return OP_LDH; } 49 | "ld" { return OP_LD; } 50 | "ldi" { return OP_LDI; } 51 | "ldx" { return OP_LDX; } 52 | "ldxi" { return OP_LDXI; } 53 | "ldxb" { return OP_LDXB; } 54 | "st" { return OP_ST; } 55 | "stx" { return OP_STX; } 56 | "jmp" { return OP_JMP; } 57 | "ja" { return OP_JMP; } 58 | "jeq" { return OP_JEQ; } 59 | "jneq" { return OP_JNEQ; } 60 | "jne" { return OP_JNEQ; } 61 | "jlt" { return OP_JLT; } 62 | "jle" { return OP_JLE; } 63 | "jgt" { return OP_JGT; } 64 | "jge" { return OP_JGE; } 65 | "jset" { return OP_JSET; } 66 | "add" { return OP_ADD; } 67 | "sub" { return OP_SUB; } 68 | "mul" { return OP_MUL; } 69 | "div" { return OP_DIV; } 70 | "mod" { return OP_MOD; } 71 | "neg" { return OP_NEG; } 72 | "and" { return OP_AND; } 73 | "xor" { return OP_XOR; } 74 | "or" { return OP_OR; } 75 | "lsh" { return OP_LSH; } 76 | "rsh" { return OP_RSH; } 77 | "ret" { return OP_RET; } 78 | "tax" { return OP_TAX; } 79 | "txa" { return OP_TXA; } 80 | 81 | "#"?("len") { return K_PKT_LEN; } 82 | "#"?("proto") { return K_PROTO; } 83 | "#"?("type") { return K_TYPE; } 84 | "#"?("poff") { return K_POFF; } 85 | "#"?("ifidx") { return K_IFIDX; } 86 | "#"?("nla") { return K_NLATTR; } 87 | "#"?("nlan") { return K_NLATTR_NEST; } 88 | "#"?("mark") { return K_MARK; } 89 | "#"?("queue") { return K_QUEUE; } 90 | "#"?("hatype") { return K_HATYPE; } 91 | "#"?("rxhash") { return K_RXHASH; } 92 | "#"?("cpu") { return K_CPU; } 93 | "#"?("vlan_tci") { return K_VLANT; } 94 | "#"?("vlan_pr") { return K_VLANP; } 95 | 96 | ":" { return ':'; } 97 | "," { return ','; } 98 | "#" { return '#'; } 99 | "%" { return '%'; } 100 | "[" { return '['; } 101 | "]" { return ']'; } 102 | "(" { return '('; } 103 | ")" { return ')'; } 104 | "x" { return 'x'; } 105 | "a" { return 'a'; } 106 | "+" { return '+'; } 107 | "M" { return 'M'; } 108 | "*" { return '*'; } 109 | "&" { return '&'; } 110 | 111 | ([0][x][a-fA-F0-9]+) { 112 | yylval.number = strtoul(yytext, NULL, 16); 113 | return number; 114 | } 115 | ([0][b][0-1]+) { 116 | yylval.number = strtol(yytext + 2, NULL, 2); 117 | return number; 118 | } 119 | (([0])|([-+]?[1-9][0-9]*)) { 120 | yylval.number = strtol(yytext, NULL, 10); 121 | return number; 122 | } 123 | ([0][0-9]+) { 124 | yylval.number = strtol(yytext + 1, NULL, 8); 125 | return number; 126 | } 127 | [a-zA-Z_][a-zA-Z0-9_]+ { 128 | yylval.label = strdup(yytext); 129 | return label; 130 | } 131 | 132 | "/*"([^\*]|\*[^/])*"*/" { /* NOP */ } 133 | ";"[^\n]* { /* NOP */ } 134 | ^#.* { /* NOP */ } 135 | [ \t]+ { /* NOP */ } 136 | [ \n]+ { /* NOP */ } 137 | 138 | . { 139 | printf("unknown character \'%s\'", yytext); 140 | yyerror("lex unknown character"); 141 | } 142 | 143 | %% 144 | -------------------------------------------------------------------------------- /linux_tools/linux/filter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Linux Socket Filter Data Structures 3 | */ 4 | 5 | #ifndef __LINUX_FILTER_H__ 6 | #define __LINUX_FILTER_H__ 7 | 8 | 9 | #include 10 | 11 | 12 | /* 13 | * Current version of the filter code architecture. 14 | */ 15 | #define BPF_MAJOR_VERSION 1 16 | #define BPF_MINOR_VERSION 1 17 | 18 | /* 19 | * Try and keep these values and structures similar to BSD, especially 20 | * the BPF code definitions which need to match so you can share filters 21 | */ 22 | 23 | struct sock_filter { /* Filter block */ 24 | __u16 code; /* Actual filter code */ 25 | __u8 jt; /* Jump true */ 26 | __u8 jf; /* Jump false */ 27 | __u32 k; /* Generic multiuse field */ 28 | }; 29 | 30 | struct sock_fprog { /* Required for SO_ATTACH_FILTER. */ 31 | unsigned short len; /* Number of filter blocks */ 32 | struct sock_filter *filter; 33 | }; 34 | 35 | /* 36 | * Instruction classes 37 | */ 38 | 39 | #define BPF_CLASS(code) ((code) & 0x07) 40 | #define BPF_LD 0x00 41 | #define BPF_LDX 0x01 42 | #define BPF_ST 0x02 43 | #define BPF_STX 0x03 44 | #define BPF_ALU 0x04 45 | #define BPF_JMP 0x05 46 | #define BPF_RET 0x06 47 | #define BPF_MISC 0x07 48 | 49 | /* ld/ldx fields */ 50 | #define BPF_SIZE(code) ((code) & 0x18) 51 | #define BPF_W 0x00 52 | #define BPF_H 0x08 53 | #define BPF_B 0x10 54 | #define BPF_MODE(code) ((code) & 0xe0) 55 | #define BPF_IMM 0x00 56 | #define BPF_ABS 0x20 57 | #define BPF_IND 0x40 58 | #define BPF_MEM 0x60 59 | #define BPF_LEN 0x80 60 | #define BPF_MSH 0xa0 61 | 62 | /* alu/jmp fields */ 63 | #define BPF_OP(code) ((code) & 0xf0) 64 | #define BPF_ADD 0x00 65 | #define BPF_SUB 0x10 66 | #define BPF_MUL 0x20 67 | #define BPF_DIV 0x30 68 | #define BPF_OR 0x40 69 | #define BPF_AND 0x50 70 | #define BPF_LSH 0x60 71 | #define BPF_RSH 0x70 72 | #define BPF_NEG 0x80 73 | #define BPF_MOD 0x90 74 | #define BPF_XOR 0xa0 75 | 76 | #define BPF_JA 0x00 77 | #define BPF_JEQ 0x10 78 | #define BPF_JGT 0x20 79 | #define BPF_JGE 0x30 80 | #define BPF_JSET 0x40 81 | #define BPF_SRC(code) ((code) & 0x08) 82 | #define BPF_K 0x00 83 | #define BPF_X 0x08 84 | 85 | /* ret - BPF_K and BPF_X also apply */ 86 | #define BPF_RVAL(code) ((code) & 0x18) 87 | #define BPF_A 0x10 88 | 89 | /* misc */ 90 | #define BPF_MISCOP(code) ((code) & 0xf8) 91 | #define BPF_TAX 0x00 92 | #define BPF_TXA 0x80 93 | 94 | #ifndef BPF_MAXINSNS 95 | #define BPF_MAXINSNS 4096 96 | #endif 97 | 98 | /* 99 | * Macros for filter block array initializers. 100 | */ 101 | #ifndef BPF_STMT 102 | #define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k } 103 | #endif 104 | #ifndef BPF_JUMP 105 | #define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k } 106 | #endif 107 | 108 | /* 109 | * Number of scratch memory words for: BPF_ST and BPF_STX 110 | */ 111 | #define BPF_MEMWORDS 16 112 | 113 | /* RATIONALE. Negative offsets are invalid in BPF. 114 | We use them to reference ancillary data. 115 | Unlike introduction new instructions, it does not break 116 | existing compilers/optimizers. 117 | */ 118 | #define SKF_AD_OFF (-0x1000) 119 | #define SKF_AD_PROTOCOL 0 120 | #define SKF_AD_PKTTYPE 4 121 | #define SKF_AD_IFINDEX 8 122 | #define SKF_AD_NLATTR 12 123 | #define SKF_AD_NLATTR_NEST 16 124 | #define SKF_AD_MARK 20 125 | #define SKF_AD_QUEUE 24 126 | #define SKF_AD_HATYPE 28 127 | #define SKF_AD_RXHASH 32 128 | #define SKF_AD_CPU 36 129 | #define SKF_AD_ALU_XOR_X 40 130 | #define SKF_AD_VLAN_TAG 44 131 | #define SKF_AD_VLAN_TAG_PRESENT 48 132 | #define SKF_AD_PAY_OFFSET 52 133 | #define SKF_AD_MAX 56 134 | #define SKF_NET_OFF (-0x100000) 135 | #define SKF_LL_OFF (-0x200000) 136 | 137 | 138 | #endif /* __LINUX_FILTER_H__ */ 139 | -------------------------------------------------------------------------------- /linux_tools/bpf_jit_disasm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Minimal BPF JIT image disassembler 3 | * 4 | * Disassembles BPF JIT compiler emitted opcodes back to asm insn's for 5 | * debugging or verification purposes. 6 | * 7 | * To get the disassembly of the JIT code, do the following: 8 | * 9 | * 1) `echo 2 > /proc/sys/net/core/bpf_jit_enable` 10 | * 2) Load a BPF filter (e.g. `tcpdump -p -n -s 0 -i eth1 host 192.168.20.0/24`) 11 | * 3) Run e.g. `bpf_jit_disasm -o` to read out the last JIT code 12 | * 13 | * Copyright 2013 Daniel Borkmann 14 | * Licensed under the GNU General Public License, version 2.0 (GPLv2) 15 | */ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | static void get_exec_path(char *tpath, size_t size) 30 | { 31 | char *path; 32 | ssize_t len; 33 | 34 | snprintf(tpath, size, "/proc/%d/exe", (int) getpid()); 35 | tpath[size - 1] = 0; 36 | 37 | path = strdup(tpath); 38 | assert(path); 39 | 40 | len = readlink(path, tpath, size); 41 | tpath[len] = 0; 42 | 43 | free(path); 44 | } 45 | 46 | static void get_asm_insns(uint8_t *image, size_t len, unsigned long base, 47 | int opcodes) 48 | { 49 | int count, i, pc = 0; 50 | char tpath[256]; 51 | struct disassemble_info info; 52 | disassembler_ftype disassemble; 53 | bfd *bfdf; 54 | 55 | memset(tpath, 0, sizeof(tpath)); 56 | get_exec_path(tpath, sizeof(tpath)); 57 | 58 | bfdf = bfd_openr(tpath, NULL); 59 | assert(bfdf); 60 | assert(bfd_check_format(bfdf, bfd_object)); 61 | 62 | init_disassemble_info(&info, stdout, (fprintf_ftype) fprintf); 63 | info.arch = bfd_get_arch(bfdf); 64 | info.mach = bfd_get_mach(bfdf); 65 | info.buffer = image; 66 | info.buffer_length = len; 67 | 68 | disassemble_init_for_target(&info); 69 | 70 | disassemble = disassembler(bfdf); 71 | assert(disassemble); 72 | 73 | do { 74 | printf("%4x:\t", pc); 75 | 76 | count = disassemble(pc, &info); 77 | 78 | if (opcodes) { 79 | printf("\n\t"); 80 | for (i = 0; i < count; ++i) 81 | printf("%02x ", (uint8_t) image[pc + i]); 82 | } 83 | printf("\n"); 84 | 85 | pc += count; 86 | } while(count > 0 && pc < len); 87 | 88 | bfd_close(bfdf); 89 | } 90 | 91 | static char *get_klog_buff(int *klen) 92 | { 93 | int ret, len = klogctl(10, NULL, 0); 94 | char *buff = malloc(len); 95 | 96 | assert(buff && klen); 97 | ret = klogctl(3, buff, len); 98 | assert(ret >= 0); 99 | *klen = ret; 100 | 101 | return buff; 102 | } 103 | 104 | static void put_klog_buff(char *buff) 105 | { 106 | free(buff); 107 | } 108 | 109 | static int get_last_jit_image(char *haystack, size_t hlen, 110 | uint8_t *image, size_t ilen, 111 | unsigned long *base) 112 | { 113 | char *ptr, *pptr, *tmp; 114 | off_t off = 0; 115 | int ret, flen, proglen, pass, ulen = 0; 116 | regmatch_t pmatch[1]; 117 | regex_t regex; 118 | 119 | if (hlen == 0) 120 | return 0; 121 | 122 | ret = regcomp(®ex, "flen=[[:alnum:]]+ proglen=[[:digit:]]+ " 123 | "pass=[[:digit:]]+ image=[[:xdigit:]]+", REG_EXTENDED); 124 | assert(ret == 0); 125 | 126 | ptr = haystack; 127 | while (1) { 128 | ret = regexec(®ex, ptr, 1, pmatch, 0); 129 | if (ret == 0) { 130 | ptr += pmatch[0].rm_eo; 131 | off += pmatch[0].rm_eo; 132 | assert(off < hlen); 133 | } else 134 | break; 135 | } 136 | 137 | ptr = haystack + off - (pmatch[0].rm_eo - pmatch[0].rm_so); 138 | ret = sscanf(ptr, "flen=%d proglen=%d pass=%d image=%lx", 139 | &flen, &proglen, &pass, base); 140 | if (ret != 4) 141 | return 0; 142 | 143 | tmp = ptr = haystack + off; 144 | while ((ptr = strtok(tmp, "\n")) != NULL && ulen < ilen) { 145 | tmp = NULL; 146 | if (!strstr(ptr, "JIT code")) 147 | continue; 148 | pptr = ptr; 149 | while ((ptr = strstr(pptr, ":"))) 150 | pptr = ptr + 1; 151 | ptr = pptr; 152 | do { 153 | image[ulen++] = (uint8_t) strtoul(pptr, &pptr, 16); 154 | if (ptr == pptr || ulen >= ilen) { 155 | ulen--; 156 | break; 157 | } 158 | ptr = pptr; 159 | } while (1); 160 | } 161 | 162 | assert(ulen == proglen); 163 | printf("%d bytes emitted from JIT compiler (pass:%d, flen:%d)\n", 164 | proglen, pass, flen); 165 | printf("%lx + :\n", *base); 166 | 167 | regfree(®ex); 168 | return ulen; 169 | } 170 | 171 | int main(int argc, char **argv) 172 | { 173 | int len, klen, opcodes = 0; 174 | char *kbuff; 175 | unsigned long base; 176 | uint8_t image[4096]; 177 | 178 | if (argc > 1) { 179 | if (!strncmp("-o", argv[argc - 1], 2)) { 180 | opcodes = 1; 181 | } else { 182 | printf("usage: bpf_jit_disasm [-o: show opcodes]\n"); 183 | exit(0); 184 | } 185 | } 186 | 187 | bfd_init(); 188 | memset(image, 0, sizeof(image)); 189 | 190 | kbuff = get_klog_buff(&klen); 191 | 192 | len = get_last_jit_image(kbuff, klen, image, sizeof(image), &base); 193 | if (len > 0 && base > 0) 194 | get_asm_insns(image, len, base, opcodes); 195 | 196 | put_klog_buff(kbuff); 197 | 198 | return 0; 199 | } 200 | -------------------------------------------------------------------------------- /iptables_bpf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | template = r''' 4 | #!/bin/bash 5 | # 6 | # This script is ***AUTOGENERATED*** 7 | # 8 | # This is a script for applying and removing xt_bpf iptable rule. This 9 | # particular rule was created with: 10 | # 11 | # %(bpf_cmd)s 12 | # 13 | # To apply the iptables BPF rule against listed destination IP's run 14 | # this script with the flooded IP addresses as parameters: 15 | # 16 | # ./%(fname)s %(sampleips)s 17 | # 18 | # This script creates an ipset "%(ipsetname)s". You can manage it 19 | # manually: 20 | # 21 | # ipset add %(ipsetname)s %(sampleips)s 22 | # 23 | # 24 | # To clean the iptables rule and ipset run: 25 | # 26 | # ./%(fname)s --delete 27 | # 28 | # 29 | # For the record, here's the BPF assembly: 30 | # 31 | %(assembly)s 32 | # 33 | 34 | set -o noclobber 35 | set -o errexit 36 | set -o nounset 37 | set -o pipefail 38 | 39 | : ${IPTABLES:="%(iptables)s"} 40 | : ${IPSET:="ipset"} 41 | : ${INPUTPLACE:="1"} 42 | : ${DEFAULTINT:=`awk 'BEGIN {n=0} $2 == "00000000" {n=1; print $1; exit} END {if (n=0) {print "eth0"}}' /proc/net/route`} 43 | 44 | iptablesrule () { 45 | ${IPTABLES} \ 46 | --wait \ 47 | ${*} \ 48 | -i ${DEFAULTINT} \ 49 | -p udp --dport 53 \ 50 | -m set --match-set %(ipsetname)s dst \ 51 | -m bpf --bytecode "%(bytecode)s" \ 52 | -m comment --comment "%(bpf_cmd_s)s" \ 53 | -j DROP 54 | } 55 | 56 | if [ "$*" == "--delete" ]; then 57 | 58 | A=`(iptablesrule -C INPUT || echo "error") 2>/dev/null` 59 | if [ "${A}" != "error" ]; then 60 | iptablesrule -D INPUT 61 | fi 62 | ${IPSET} -exist destroy %(ipsetname)s 2>/dev/null 63 | 64 | else 65 | 66 | ${IPSET} -exist create %(ipsetname)s hash:net family %(ipsetfamily)s 67 | for IP in %(ips)s $@; do 68 | ${IPSET} -exist add %(ipsetname)s "$IP" 69 | done 70 | 71 | A=`(iptablesrule -C INPUT || echo "error") 2>/dev/null` 72 | if [ "${A}" == "error" ]; then 73 | iptablesrule -I INPUT ${INPUTPLACE} 74 | fi 75 | 76 | fi 77 | '''.lstrip() 78 | 79 | import argparse 80 | import os 81 | import stat 82 | import string 83 | import sys 84 | 85 | import bpftools 86 | 87 | parser = argparse.ArgumentParser( 88 | formatter_class=argparse.RawDescriptionHelpFormatter, 89 | description=r''' 90 | 91 | This program generates a bash script. The script when run will insert 92 | (or remove) an iptable rule and ipset. The iptable rule drops traffic 93 | that matches the BPF rule, which in turn is generated from given 94 | parameters. 95 | 96 | See "./bpfgen --help" for more information on BPF generators. 97 | 98 | Usage example: 99 | 100 | %(prog)s dns -- *.example.com 101 | 102 | %(prog)s dns -- --ignorecase *.example.com 103 | 104 | %(prog)s -6 dns -- *.example.com 105 | 106 | %(prog)s dns_validate -- --strict 107 | 108 | '''.strip()) 109 | parser.add_argument('-6', '--inet6', action='store_true', 110 | help='generate script for IPv6') 111 | parser.add_argument('-c', '--comment', 112 | help='Add a comment to better identify this rule in iptables -nvL'), 113 | parser.add_argument('-i', '--ip', metavar='ip', action='append', 114 | help='preset IP in the set') 115 | parser.add_argument('-n', '--negate', action='store_true', 116 | help='negate the logic') 117 | parser.add_argument('-w', '--write', metavar='file', 118 | help='name the generated script') 119 | parser.add_argument('type', nargs=1, choices=bpftools.generator_names, 120 | help='BPF generator') 121 | parser.add_argument('parameters', nargs='*', 122 | help='parameters passed to the BPF generator') 123 | 124 | args = parser.parse_args() 125 | if len(args.type) != 1: 126 | parser.print_help() 127 | sys.exit(-1) 128 | 129 | inet = 4 if not args.inet6 else 6 130 | 131 | a = [] 132 | for assembly in [False, True]: 133 | name, ret = bpftools.gen(args.type[0], 134 | args.parameters, 135 | assembly=assembly, 136 | l3_off=0, 137 | ipversion=inet, 138 | negate=args.negate, 139 | ) 140 | a.append(ret) 141 | bytecode, assembly = a 142 | 143 | if int(bytecode.split(',')[0]) > 63: 144 | raise Exception("bytecode too long!") 145 | 146 | name = 'bpf_%s_ip%s%s%s' % (args.type[0], inet, '_' if name else '', name) 147 | 148 | fname = args.write or name + '.sh' 149 | 150 | if fname == '-': 151 | f = sys.stdout 152 | else: 153 | f = open(fname, 'wb') 154 | 155 | cmd = sys.argv[1:] 156 | 157 | bpf_cmd_s = args.comment if args.comment else ' '.join(cmd).replace('"', "").replace("$", "") 158 | 159 | ctx = { 160 | 'bpf_cmd': cmd, 161 | 'bpf_cmd_s': bpf_cmd_s, 162 | 'bytecode': bytecode, 163 | 'assembly': '# ' + '\n# '.join(assembly.split('\n')), 164 | 'fname': fname if fname != '-' else name + '.sh', 165 | 'ipsetname': name[:31], 166 | 'ips': ' '.join(repr(s) for s in (args.ip or [])), 167 | } 168 | 169 | if inet == 4: 170 | ctx.update({ 171 | 'iptables': 'iptables', 172 | 'ipsetfamily': 'inet', 173 | 'sampleips': '1.1.1.1/32', 174 | }) 175 | else: 176 | ctx.update({ 177 | 'iptables': 'ip6tables', 178 | 'ipsetfamily': 'inet6', 179 | 'sampleips': '2a00:1450:4009:803::1008/128', 180 | }) 181 | 182 | f.write(template % ctx) 183 | f.flush() 184 | 185 | if f != sys.stdout: 186 | print "Generated file %r" % (fname,) 187 | os.chmod(fname, 0750) 188 | -------------------------------------------------------------------------------- /iptables_bpf_chain: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | template = r''' 4 | #!/bin/bash 5 | # 6 | # This script is ***AUTOGENERATED*** 7 | # 8 | # To apply the iptables BPF rule run this script: 9 | # 10 | # ./%(fname)s 11 | # 12 | # This script creates an ipset "%(ipsetname)s". You can manage it 13 | # manually: 14 | # 15 | # ipset add %(ipsetname)s %(sampleips)s 16 | # 17 | # To clean the iptables rule and ipset run: 18 | # 19 | # ./%(fname)s --delete 20 | # 21 | # 22 | 23 | set -o noclobber 24 | set -o errexit 25 | set -o nounset 26 | set -o pipefail 27 | 28 | : ${IPTABLES:="%(iptables)s"} 29 | : ${IPSET:="ipset"} 30 | : ${INPUTPLACE:="1"} 31 | : ${DEFAULTINT:=`awk 'BEGIN {n=0} $2 == "00000000" {n=1; print $1; exit} END {if (n=0) {print "eth0"}}' /proc/net/route`} 32 | 33 | main_match () { 34 | ${IPTABLES} \ 35 | --wait \ 36 | ${*} \ 37 | -i ${DEFAULTINT} \ 38 | -p udp --dport 53 \ 39 | -m set --match-set %(ipsetname)s dst \ 40 | -j %(chain)s 41 | } 42 | 43 | chain_create() { 44 | ${IPTABLES} --wait -N %(chain)s 45 | %(accept_cmds)s 46 | %(drop_cmds)s 47 | ${IPTABLES} --wait -A %(chain)s -j RETURN 48 | } 49 | 50 | chain_delete() { 51 | ${IPTABLES} --wait -F %(chain)s 52 | ${IPTABLES} --wait -X %(chain)s 53 | } 54 | 55 | if [ "$*" == "--delete" ]; then 56 | 57 | A=`(main_match -C INPUT || echo "error") 2>/dev/null` 58 | if [ "${A}" != "error" ]; then 59 | main_match -D INPUT 60 | chain_delete 61 | fi 62 | ${IPSET} -exist destroy %(ipsetname)s 2>/dev/null 63 | 64 | else 65 | 66 | ${IPSET} -exist create %(ipsetname)s hash:net family %(ipsetfamily)s 67 | for IP in %(ips)s $@; do 68 | ${IPSET} -exist add %(ipsetname)s "$IP" 69 | done 70 | 71 | A=`(main_match -C INPUT || echo "error") 2>/dev/null` 72 | if [ "${A}" == "error" ]; then 73 | chain_create 74 | main_match -I INPUT ${INPUTPLACE} 75 | fi 76 | 77 | fi 78 | '''.lstrip() 79 | 80 | import argparse 81 | import os 82 | import stat 83 | import string 84 | import sys 85 | 86 | import bpftools 87 | 88 | 89 | parser = argparse.ArgumentParser( 90 | formatter_class=argparse.RawDescriptionHelpFormatter, 91 | description=r''' 92 | 93 | This program generates a bash script. The script when run will insert 94 | (or remove) an iptables chain and ipset. The iptable chain ACCEPTs 95 | traffic that matches requests with domains given with "--accept" 96 | options and DROPs packets matching domains listed with "--drop" 97 | option. Example: 98 | 99 | %(prog)s -a www.example.com -a ns1.example.com -d *.example.com -w example_com 100 | 101 | 102 | '''.strip()) 103 | parser.add_argument('-6', '--inet6', action='store_true', 104 | help='generate script for IPv6') 105 | parser.add_argument('-i', '--ip', metavar='ip', action='append', 106 | help='preset IP in the set') 107 | parser.add_argument('-w', '--write', metavar='name', 108 | help='name the generated script') 109 | parser.add_argument('-a', '--accept', metavar='accept', action='append', 110 | help='accept domains') 111 | parser.add_argument('-d', '--drop', metavar='drop', action='append', 112 | help='drop domains') 113 | 114 | args = parser.parse_args() 115 | 116 | if not args.write: 117 | print "set name with -w" 118 | sys.exit(-1) 119 | 120 | inet = 4 if not args.inet6 else 6 121 | 122 | fname = args.write +'_ip'+str(inet)+'.sh' 123 | 124 | meta = [] 125 | for action, list_of_patterns in [('ACCEPT', args.accept), ('DROP', args.drop)]: 126 | cmds = [] 127 | for domain in list_of_patterns: 128 | if domain != 'any': 129 | _, bytecode = bpftools.gen('dns', 130 | ['-i', domain], 131 | assembly=False, 132 | l3_off=0, 133 | ipversion=inet, 134 | ) 135 | 136 | if int(bytecode.split(',')[0]) > 63: 137 | raise Exception("bytecode too long!") 138 | 139 | cmd = r''' 140 | ${IPTABLES} \ 141 | --wait \ 142 | -A %s \ 143 | -m bpf --bytecode "%s" \ 144 | -m comment --comment "%s" \ 145 | -j %s 146 | ''' % (args.write.upper(), bytecode, "dns -- -i " + domain, action) 147 | cmds.append(cmd.strip('\n')) 148 | else: 149 | cmd = r''' 150 | ${IPTABLES} \ 151 | --wait \ 152 | -A %s \ 153 | -m comment --comment "%s" \ 154 | -j %s 155 | ''' % (args.write.upper(), "dns -- -i " + domain, action) 156 | cmds.append(cmd.strip('\n')) 157 | 158 | meta.append(cmds) 159 | 160 | accept_cmds = meta[0] 161 | drop_cmds = meta[1] 162 | 163 | ctx = { 164 | 'accept_cmds': '\n'.join(accept_cmds), 165 | 'drop_cmds': '\n'.join(drop_cmds), 166 | 'fname': fname, 167 | 'ipsetname': args.write + '_ip' + str(inet), 168 | 'chain': args.write.upper(), 169 | 'ips': ' '.join(repr(s) for s in (args.ip or [])), 170 | } 171 | 172 | if inet == 4: 173 | ctx.update({ 174 | 'iptables': 'iptables', 175 | 'ipsetfamily': 'inet', 176 | 'sampleips': '1.1.1.1/32', 177 | }) 178 | else: 179 | ctx.update({ 180 | 'iptables': 'ip6tables', 181 | 'ipsetfamily': 'inet6', 182 | 'sampleips': '2a00:1450:4009:803::1008/128', 183 | }) 184 | 185 | f = open(fname, 'wb') 186 | f.write(template % ctx) 187 | f.flush() 188 | print "Generated file %r" % (fname,) 189 | os.chmod(fname, 0750) 190 | -------------------------------------------------------------------------------- /bpftools/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import struct 3 | import subprocess 4 | import sys 5 | 6 | from pkg_resources import resource_filename 7 | from binascii import hexlify 8 | from binascii import unhexlify 9 | 10 | def find_binary(prefixes, name, args): 11 | for prefix in prefixes: 12 | try: 13 | subprocess.call([os.path.join(prefix, name)] + args) 14 | except OSError, e: 15 | continue 16 | return prefix 17 | print >> sys.stderr, prefix, "%r not found in your PATH nor LINUX_TOOLS_PATH" % (name,) 18 | os._exit(-2) 19 | 20 | 21 | def bpf_compile(assembly): 22 | prefixes = ["", 23 | resource_filename(__name__, "."), 24 | resource_filename(__name__, os.path.join("..","linux_tools")), 25 | resource_filename(__name__, "linux_tools"), 26 | ".", 27 | "linux_tools", 28 | os.path.dirname(sys.argv[0]), 29 | os.path.realpath(os.path.dirname(sys.argv[0])), 30 | os.getenv("LINUX_TOOLS_PATH", "."), 31 | ] 32 | prefix = find_binary(prefixes, "bpf_asm", ['/dev/null']) 33 | 34 | out, err = subprocess.Popen([os.path.join(prefix, "bpf_asm")], 35 | stdin=subprocess.PIPE, stdout=subprocess.PIPE, 36 | stderr=subprocess.PIPE).communicate(assembly) 37 | 38 | if set(out) - set(" ,0123456789\n") or not out: 39 | print >> sys.stderr, "Compiling failed with:\n%s\n" % (out.strip() + err.strip()) 40 | os._exit(-3) 41 | return out.strip() 42 | 43 | 44 | def _looks_like_ip(l2, off): 45 | if len(l2) - off >= 20: 46 | ipver, _, total_length = struct.unpack_from('!BBH', l2, off) 47 | if (ipver & 0xF0 == 0x40 and (ipver & 0x0f) >= 5): 48 | return 4 49 | 50 | if len(l2) - off >= 40: 51 | vertos, _, _, pay_len, proto, ttl = struct.unpack_from('!BBHHBB', l2, off) 52 | if (vertos & 0xF0 == 0x60 and pay_len + off + 40 == len(l2) 53 | and ttl > 0): 54 | return 6 55 | return None 56 | 57 | 58 | def find_ip_offset(l2, max_off=40): 59 | # first look for both ethernet and ip header 60 | for off in xrange(2, max_off+2, 2): 61 | if l2[off-2:off] == '\x08\x00' and _looks_like_ip(l2, off) == 4: 62 | return off 63 | if l2[off-2:off] == '\x86\xdd' and _looks_like_ip(l2, off) == 6: 64 | return off 65 | 66 | # okay, just look for ip header 67 | for off in xrange(0, max_off, 2): 68 | if _looks_like_ip(l2, off): 69 | return off 70 | 71 | return None 72 | 73 | def scrub_byte(data, minval, maxval, ip_constant): 74 | if not (ord(data) >= minval and ord(data) < maxval): 75 | return data 76 | ip_byte_str = hexlify(data) 77 | ip_byte_int = int(ip_byte_str, 16) 78 | ip_byte_int = ip_byte_int - minval 79 | obfuscated_byte = (ip_byte_int + ip_constant) % (maxval-minval) 80 | obfuscated_byte = obfuscated_byte + minval 81 | obfuscated_str = format(obfuscated_byte,'x') 82 | if len(obfuscated_str) == 1: 83 | obfuscated_str = "0" + obfuscated_str 84 | obfuscated_str = unhexlify(obfuscated_str.rstrip(b"\n")) 85 | return obfuscated_str 86 | 87 | def scrub_dns_name(data, ip_ihl, ip_hdr_off, entropy): 88 | # UDP 89 | dns_hdr_off = ip_ihl + ip_hdr_off + 8 # 8 is UDP header size 90 | str_len_offset = 0 91 | name_offset = 0 92 | while True: 93 | try: 94 | str_len_off = ord(data[dns_hdr_off + 12 + name_offset]) # 12 is the offset inside the DNS packet 95 | except IndexError: 96 | print >> sys.stderr, "OOps, it seems this UDP packet is not properly formed DNS, break while True" 97 | break 98 | if str_len_off == 0: 99 | break 100 | idx = 0 101 | while idx < str_len_off: 102 | try: 103 | rtr = data[dns_hdr_off + 12 + name_offset + idx + 1] 104 | except IndexError: 105 | print >> sys.stderr, "OOps, it seems this UDP packet is not properly formed DNS, break while idx" 106 | break 107 | rtr = scrub_byte(rtr, ord('a'), ord('z') + 1, entropy[name_offset % len(entropy)]) 108 | rtr = scrub_byte(rtr, ord('A'), ord('Z') + 1, entropy[name_offset % len(entropy)]) 109 | rtr = scrub_byte(rtr, ord('0'), ord('9') + 1, entropy[name_offset % len(entropy)]) 110 | data[dns_hdr_off + 12 + name_offset + idx + 1] = rtr 111 | idx = idx + 1 112 | name_offset = name_offset + str_len_off + 1 113 | 114 | 115 | def do_scrub(l2, ip_hdr_off): 116 | entropy = [11,2,9,7,5,10,17,19,1,3,15] 117 | 118 | data = list(l2) 119 | if ip_hdr_off == 18: 120 | #Ethernet with vlan 121 | data[12] = '\x08' 122 | data[13] = '\x00' 123 | del data[14:18] 124 | ip_hdr_off = 14 125 | 126 | if ip_hdr_off not in (14, 16): 127 | raise Exception("ip_hdr_off=%i Not ethernet, not sure how to scrub MACS" % ip_hdr_off) 128 | for i in xrange(ip_hdr_off-2): 129 | data[i] = '\x00' 130 | 131 | ipver = ord(data[ip_hdr_off]) 132 | if ipver & 0xF0 == 0x40: 133 | # IPV4 134 | # Scrubbing IPs 135 | ip_ihl = (ipver & 0x0F)*4 136 | for i in xrange(ip_hdr_off+12, ip_hdr_off+12+4+4, 1): 137 | data[i] = scrub_byte(data[i], 0, 256, entropy[i % len(entropy)]) 138 | if ord(data[ip_hdr_off+9]) == 0x11: 139 | # UDP 140 | scrub_dns_name(data, ip_ihl, ip_hdr_off, entropy) 141 | elif ipver & 0xF0 == 0x60: 142 | # IPV6 143 | for i in xrange(ip_hdr_off+8, ip_hdr_off+8+16+16, 1): 144 | data[i] = scrub_byte(data[i], 0, 256, entropy[i % len(entropy)]) 145 | if ord(data[ip_hdr_off+6]) == 0x11: 146 | # UDP 147 | scrub_dns_name(data, 40, ip_hdr_off, entropy) 148 | return ''.join(data) 149 | -------------------------------------------------------------------------------- /parsedns: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import socket 4 | import struct 5 | import sys 6 | 7 | import bpftools.utils 8 | 9 | 10 | def usage(): 11 | print """ 12 | parsedns.py [ hex string... ] 13 | 14 | Read the hex string as a network packet and parse it as it were a DNS 15 | request. Decode it and pretty print it. 16 | """.lstrip() 17 | sys.exit(2) 18 | 19 | 20 | def unpack_domain(off, l5, rr=False): 21 | off1 = None 22 | 23 | while True: 24 | c, = struct.unpack_from('!B', l5, off) 25 | if c == 0x00: 26 | print '.' 27 | off += 1 28 | break 29 | elif (c & 0xC0): 30 | c, = struct.unpack_from('!H', l5, off) 31 | ptr = c ^ 0xc000 32 | off += 2 33 | print 'ptr->#%i' % (ptr,), 34 | if off1 == None: 35 | off1 = off 36 | off = ptr 37 | else: 38 | print '%r' % (l5[off+1:off+1+c],), 39 | off += c + 1 40 | 41 | if off1: 42 | off = off1 43 | 44 | qtype, qclass = struct.unpack_from('!HH', l5, off) 45 | off += 4 46 | print ' type=0x%04x class=0x%04x' % (qtype, qclass) 47 | if not rr: 48 | return off 49 | ttl, rlength = struct.unpack_from('!IH', l5, off) 50 | off += 6 51 | xxx = l5[off:off+rlength] 52 | print ' ttl=%i rrlen=%i: %s' % (ttl, rlength, xxx.encode('hex')) 53 | if qtype == 0x0029: 54 | print " "*23, "bufsize=%i" % (qclass) 55 | if ttl & (1<<15): 56 | print " "*23, "dnssec_ok_flag" 57 | edns_ver = (ttl >> 16) & 0xFF 58 | if edns_ver: 59 | print " "*23, "ends_version=%i (INVALID!)" % edns_ver 60 | if ttl & (0xFF007FFF): 61 | print " "*23, "extra edns flags %08x" % ttl 62 | 63 | while xxx: 64 | code, optlen = struct.unpack_from('!HH', xxx) 65 | xxy, xxx = xxx[4:4+optlen], xxx[4+optlen:] 66 | if code == 0x50fa: 67 | family, mask, scope = struct.unpack_from('!HBB', xxy) 68 | if family == 1: 69 | ip = socket.inet_ntoa(xxy[4:]) 70 | else: 71 | ip = xxy[4:].encode('hex') 72 | print " "*23, "family=%i mask=%i scope=%i %s" % (family, mask, scope, ip) 73 | elif code == 3 and len(xxy) == 0: 74 | print " "*23, "nsid request" 75 | else: 76 | print " "*23, "code=%04x data=%s" % (code, xxy.encode('hex')) 77 | 78 | off += rlength 79 | return off 80 | 81 | 82 | def parsedns(raw): 83 | 84 | l2len = bpftools.utils.find_ip_offset(raw) 85 | assert l2len is not None 86 | l2, l3 = raw[:l2len], raw[l2len:] 87 | 88 | print '[.] l2: %s' % l2.encode('hex') 89 | 90 | v_len, = struct.unpack_from('!B', l3) 91 | if v_len & 0xf0 == 0x40: 92 | l3len = (v_len & 0x0F) * 4 93 | l3, l4 = l3[:l3len], l3[l3len:] 94 | print '[.] l3: %s' % l3.encode('hex') 95 | 96 | v_len, dscp_ecn, total_length, ip_id, fragment, ttl, protocol, _checksum, sip, dip = struct.unpack_from('!BBHHHBBHII', l3) 97 | ip_extra = l3[20:] 98 | 99 | print ' ver: 0x%02x' % ((v_len & 0xf0) >> 4,) 100 | print ' hdl: 0x%02x' % ((v_len & 0x0f),) 101 | print ' ip_id: 0x%04x' % (ip_id,) 102 | print ' fragment: 0x%04x' % (fragment,) 103 | print ' ttl: 0x%02x (%d)' % (ttl, ttl) 104 | print ' protocol: 0x%02x' % (protocol,) 105 | print ' source: 0x%s %s' % (l3[12:16].encode('hex'), socket.inet_ntoa(l3[12:16])) 106 | print ' destination: 0x%s %s' % (l3[16:20].encode('hex'), socket.inet_ntoa(l3[16:20])) 107 | if ip_extra: 108 | print ' ip_extra: %s' % (ip_extra.encode('hex'),) 109 | elif v_len & 0xf0 == 0x60: 110 | l3len = 40 111 | l3, l4 = l3[:l3len], l3[l3len:] 112 | print '[.] l3: %s' % l3.encode('hex') 113 | 114 | v_ttl, _class, _flow, payload_length, next_header, ttl = struct.unpack_from('!BBHHBB', l3) 115 | print ' ver: 0x%02x' % ((v_len & 0xf0) >> 4,) 116 | print ' payload_length: 0x%04x' % (payload_length,) 117 | print ' next_header: 0x%02x' % (next_header,) 118 | print ' ttl: 0x%02x' % (ttl,) 119 | print ' source: %s' % (l3[8:24].encode('hex'),) 120 | print ' destination: %s' % (l3[24:40].encode('hex'),) 121 | else: 122 | assert False 123 | 124 | l4, l5 = l4[:8], l4[8:] 125 | 126 | print '[.] l4: %s' % l4.encode('hex') 127 | 128 | spt, dpt, l4len, _checksum = struct.unpack_from('!HHHH', l4) 129 | 130 | print ' source port: 0x%04x (%s)' % (spt, spt) 131 | print ' destination port: %s' % (dpt,) 132 | print ' length: %s' % (l4len,) 133 | 134 | print '[.] l5: %s' % l5.encode('hex') 135 | 136 | dns_id, flags, qdcnt, anscnt, authcnt, extracnt = struct.unpack_from('!HHHHHH', l5) 137 | 138 | print ' id: 0x%04x' % (dns_id,) 139 | print ' flags: 0x%04x' % (flags,), 140 | f = [] 141 | f.append('response' if flags & (1<<15) else 'query') 142 | if flags & (1<<10): 143 | f.append('authoritative') 144 | if flags & (1<<9): 145 | f.append('truncated') 146 | if flags & (1<<8): 147 | f.append('recursion_desired') 148 | if flags & (1<<7): 149 | f.append('recursion_available') 150 | if flags & (1<<6): 151 | f.append('z') 152 | if flags & (1<<5): 153 | f.append('authenticated_data') 154 | if flags & (1<<4): 155 | f.append('checking_disabled') 156 | f.append( 'op=%x' % ((flags>>11) & 0xF)) 157 | f.append( 'rcode=%x' % ((flags) & 0xF)) 158 | print ' '.join(f) 159 | print ' questions: %s' % (qdcnt,) 160 | print ' answers: %s' % (anscnt,) 161 | print ' auth: %s' % (authcnt,) 162 | print ' extra: %s' % (extracnt,) 163 | 164 | off = 12 165 | for i in xrange(qdcnt): 166 | print '#%3i q[%i]:' % (off-len(l5), i,), 167 | off = unpack_domain(off, l5) 168 | 169 | for cnt, n in (anscnt, 'answer'), (authcnt, 'auth'), (extracnt, 'extra'): 170 | for i in xrange(cnt): 171 | print '%14s[%i]:' % (n, i, ), 172 | off = unpack_domain(off, l5, rr=True) 173 | 174 | if len(l5) > off: 175 | print ' trailing: %s' % (l5[off:].encode('hex'),) 176 | 177 | 178 | if __name__ == "__main__": 179 | if not sys.argv[1:]: 180 | usage() 181 | 182 | for hexstr in sys.argv[1:]: 183 | parsedns(hexstr.decode('hex')) 184 | print 185 | -------------------------------------------------------------------------------- /linux_tools/linux/if_packet.h: -------------------------------------------------------------------------------- 1 | #ifndef __LINUX_IF_PACKET_H 2 | #define __LINUX_IF_PACKET_H 3 | 4 | #include 5 | 6 | #ifndef __aligned_u64 7 | # define __aligned_u64 __u64 __attribute__((aligned(8))) 8 | #endif 9 | 10 | struct sockaddr_pkt { 11 | unsigned short spkt_family; 12 | unsigned char spkt_device[14]; 13 | __be16 spkt_protocol; 14 | }; 15 | 16 | struct sockaddr_ll { 17 | unsigned short sll_family; 18 | __be16 sll_protocol; 19 | int sll_ifindex; 20 | unsigned short sll_hatype; 21 | unsigned char sll_pkttype; 22 | unsigned char sll_halen; 23 | unsigned char sll_addr[8]; 24 | }; 25 | 26 | /* Packet types */ 27 | 28 | #define PACKET_HOST 0 /* To us */ 29 | #define PACKET_BROADCAST 1 /* To all */ 30 | #define PACKET_MULTICAST 2 /* To group */ 31 | #define PACKET_OTHERHOST 3 /* To someone else */ 32 | #define PACKET_OUTGOING 4 /* Outgoing of any type */ 33 | /* These ones are invisible by user level */ 34 | #define PACKET_LOOPBACK 5 /* MC/BRD frame looped back */ 35 | #define PACKET_FASTROUTE 6 /* Fastrouted frame */ 36 | 37 | /* Packet socket options */ 38 | 39 | #define PACKET_ADD_MEMBERSHIP 1 40 | #define PACKET_DROP_MEMBERSHIP 2 41 | #define PACKET_RECV_OUTPUT 3 42 | /* Value 4 is still used by obsolete turbo-packet. */ 43 | #define PACKET_RX_RING 5 44 | #define PACKET_STATISTICS 6 45 | #define PACKET_COPY_THRESH 7 46 | #define PACKET_AUXDATA 8 47 | #define PACKET_ORIGDEV 9 48 | #define PACKET_VERSION 10 49 | #define PACKET_HDRLEN 11 50 | #define PACKET_RESERVE 12 51 | #define PACKET_TX_RING 13 52 | #define PACKET_LOSS 14 53 | #define PACKET_VNET_HDR 15 54 | #define PACKET_TX_TIMESTAMP 16 55 | #define PACKET_TIMESTAMP 17 56 | #define PACKET_FANOUT 18 57 | #define PACKET_TX_HAS_OFF 19 58 | 59 | #define PACKET_FANOUT_HASH 0 60 | #define PACKET_FANOUT_LB 1 61 | #define PACKET_FANOUT_CPU 2 62 | #define PACKET_FANOUT_ROLLOVER 3 63 | #define PACKET_FANOUT_FLAG_ROLLOVER 0x1000 64 | #define PACKET_FANOUT_FLAG_DEFRAG 0x8000 65 | 66 | struct tpacket_stats { 67 | unsigned int tp_packets; 68 | unsigned int tp_drops; 69 | }; 70 | 71 | struct tpacket_stats_v3 { 72 | unsigned int tp_packets; 73 | unsigned int tp_drops; 74 | unsigned int tp_freeze_q_cnt; 75 | }; 76 | 77 | union tpacket_stats_u { 78 | struct tpacket_stats stats1; 79 | struct tpacket_stats_v3 stats3; 80 | }; 81 | 82 | struct tpacket_auxdata { 83 | __u32 tp_status; 84 | __u32 tp_len; 85 | __u32 tp_snaplen; 86 | __u16 tp_mac; 87 | __u16 tp_net; 88 | __u16 tp_vlan_tci; 89 | __u16 tp_padding; 90 | }; 91 | 92 | /* Rx ring - header status */ 93 | #define TP_STATUS_KERNEL 0 94 | #define TP_STATUS_USER (1 << 0) 95 | #define TP_STATUS_COPY (1 << 1) 96 | #define TP_STATUS_LOSING (1 << 2) 97 | #define TP_STATUS_CSUMNOTREADY (1 << 3) 98 | #define TP_STATUS_VLAN_VALID (1 << 4) /* auxdata has valid tp_vlan_tci */ 99 | #define TP_STATUS_BLK_TMO (1 << 5) 100 | 101 | /* Tx ring - header status */ 102 | #define TP_STATUS_AVAILABLE 0 103 | #define TP_STATUS_SEND_REQUEST (1 << 0) 104 | #define TP_STATUS_SENDING (1 << 1) 105 | #define TP_STATUS_WRONG_FORMAT (1 << 2) 106 | 107 | /* Rx and Tx ring - header status */ 108 | #define TP_STATUS_TS_SOFTWARE (1 << 29) 109 | #define TP_STATUS_TS_SYS_HARDWARE (1 << 30) 110 | #define TP_STATUS_TS_RAW_HARDWARE (1 << 31) 111 | 112 | /* Rx ring - feature request bits */ 113 | #define TP_FT_REQ_FILL_RXHASH 0x1 114 | 115 | struct tpacket_hdr { 116 | unsigned long tp_status; 117 | unsigned int tp_len; 118 | unsigned int tp_snaplen; 119 | unsigned short tp_mac; 120 | unsigned short tp_net; 121 | unsigned int tp_sec; 122 | unsigned int tp_usec; 123 | }; 124 | 125 | #define TPACKET_ALIGNMENT 16 126 | #define TPACKET_ALIGN(x) (((x)+TPACKET_ALIGNMENT-1)&~(TPACKET_ALIGNMENT-1)) 127 | #define TPACKET_HDRLEN (TPACKET_ALIGN(sizeof(struct tpacket_hdr)) + sizeof(struct sockaddr_ll)) 128 | 129 | struct tpacket2_hdr { 130 | __u32 tp_status; 131 | __u32 tp_len; 132 | __u32 tp_snaplen; 133 | __u16 tp_mac; 134 | __u16 tp_net; 135 | __u32 tp_sec; 136 | __u32 tp_nsec; 137 | __u16 tp_vlan_tci; 138 | __u16 tp_padding; 139 | }; 140 | 141 | struct tpacket_hdr_variant1 { 142 | __u32 tp_rxhash; 143 | __u32 tp_vlan_tci; 144 | }; 145 | 146 | struct tpacket3_hdr { 147 | __u32 tp_next_offset; 148 | __u32 tp_sec; 149 | __u32 tp_nsec; 150 | __u32 tp_snaplen; 151 | __u32 tp_len; 152 | __u32 tp_status; 153 | __u16 tp_mac; 154 | __u16 tp_net; 155 | /* pkt_hdr variants */ 156 | union { 157 | struct tpacket_hdr_variant1 hv1; 158 | }; 159 | }; 160 | 161 | struct tpacket_bd_ts { 162 | unsigned int ts_sec; 163 | union { 164 | unsigned int ts_usec; 165 | unsigned int ts_nsec; 166 | }; 167 | }; 168 | 169 | struct tpacket_hdr_v1 { 170 | __u32 block_status; 171 | __u32 num_pkts; 172 | __u32 offset_to_first_pkt; 173 | 174 | /* Number of valid bytes (including padding) 175 | * blk_len <= tp_block_size 176 | */ 177 | __u32 blk_len; 178 | 179 | /* 180 | * Quite a few uses of sequence number: 181 | * 1. Make sure cache flush etc worked. 182 | * Well, one can argue - why not use the increasing ts below? 183 | * But look at 2. below first. 184 | * 2. When you pass around blocks to other user space decoders, 185 | * you can see which blk[s] is[are] outstanding etc. 186 | * 3. Validate kernel code. 187 | */ 188 | __aligned_u64 seq_num; 189 | 190 | /* 191 | * ts_last_pkt: 192 | * 193 | * Case 1. Block has 'N'(N >=1) packets and TMO'd(timed out) 194 | * ts_last_pkt == 'time-stamp of last packet' and NOT the 195 | * time when the timer fired and the block was closed. 196 | * By providing the ts of the last packet we can absolutely 197 | * guarantee that time-stamp wise, the first packet in the 198 | * next block will never precede the last packet of the 199 | * previous block. 200 | * Case 2. Block has zero packets and TMO'd 201 | * ts_last_pkt = time when the timer fired and the block 202 | * was closed. 203 | * Case 3. Block has 'N' packets and NO TMO. 204 | * ts_last_pkt = time-stamp of the last pkt in the block. 205 | * 206 | * ts_first_pkt: 207 | * Is always the time-stamp when the block was opened. 208 | * Case a) ZERO packets 209 | * No packets to deal with but atleast you know the 210 | * time-interval of this block. 211 | * Case b) Non-zero packets 212 | * Use the ts of the first packet in the block. 213 | * 214 | */ 215 | struct tpacket_bd_ts ts_first_pkt, ts_last_pkt; 216 | }; 217 | 218 | union tpacket_bd_header_u { 219 | struct tpacket_hdr_v1 bh1; 220 | }; 221 | 222 | struct tpacket_block_desc { 223 | __u32 version; 224 | __u32 offset_to_priv; 225 | union tpacket_bd_header_u hdr; 226 | }; 227 | 228 | #define TPACKET2_HDRLEN (TPACKET_ALIGN(sizeof(struct tpacket2_hdr)) + sizeof(struct sockaddr_ll)) 229 | #define TPACKET3_HDRLEN (TPACKET_ALIGN(sizeof(struct tpacket3_hdr)) + sizeof(struct sockaddr_ll)) 230 | 231 | enum tpacket_versions { 232 | TPACKET_V1, 233 | TPACKET_V2, 234 | TPACKET_V3 235 | }; 236 | 237 | /* 238 | Frame structure: 239 | 240 | - Start. Frame must be aligned to TPACKET_ALIGNMENT=16 241 | - struct tpacket_hdr 242 | - pad to TPACKET_ALIGNMENT=16 243 | - struct sockaddr_ll 244 | - Gap, chosen so that packet data (Start+tp_net) aligns to TPACKET_ALIGNMENT=16 245 | - Start+tp_mac: [ Optional MAC header ] 246 | - Start+tp_net: Packet data, aligned to TPACKET_ALIGNMENT=16. 247 | - Pad to align to TPACKET_ALIGNMENT=16 248 | */ 249 | 250 | struct tpacket_req { 251 | unsigned int tp_block_size; /* Minimal size of contiguous block */ 252 | unsigned int tp_block_nr; /* Number of blocks */ 253 | unsigned int tp_frame_size; /* Size of frame */ 254 | unsigned int tp_frame_nr; /* Total number of frames */ 255 | }; 256 | 257 | struct tpacket_req3 { 258 | unsigned int tp_block_size; /* Minimal size of contiguous block */ 259 | unsigned int tp_block_nr; /* Number of blocks */ 260 | unsigned int tp_frame_size; /* Size of frame */ 261 | unsigned int tp_frame_nr; /* Total number of frames */ 262 | unsigned int tp_retire_blk_tov; /* timeout in msecs */ 263 | unsigned int tp_sizeof_priv; /* offset to private data area */ 264 | unsigned int tp_feature_req_word; 265 | }; 266 | 267 | union tpacket_req_u { 268 | struct tpacket_req req; 269 | struct tpacket_req3 req3; 270 | }; 271 | 272 | struct packet_mreq { 273 | int mr_ifindex; 274 | unsigned short mr_type; 275 | unsigned short mr_alen; 276 | unsigned char mr_address[8]; 277 | }; 278 | 279 | #define PACKET_MR_MULTICAST 0 280 | #define PACKET_MR_PROMISC 1 281 | #define PACKET_MR_ALLMULTI 2 282 | #define PACKET_MR_UNICAST 3 283 | 284 | #endif 285 | -------------------------------------------------------------------------------- /bpftools/gen_dns.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import itertools 3 | import re 4 | import string 5 | import struct 6 | import sys 7 | import urllib 8 | 9 | 10 | # Accepts list of tuples [(mergeable, value)] and merges fields where 11 | # mergeable is True. 12 | def merge(iterable, merge=lambda a,b:a+b): 13 | for k, g in itertools.groupby(iterable, key=lambda a:a[0]): 14 | if k is True: 15 | yield reduce(merge, (i[1] for i in g)) 16 | else: 17 | for i in g: 18 | yield i[1] 19 | 20 | 21 | ACCEPTABLE_CHARS = set(string.printable) - set(string.whitespace) - set(string.punctuation) 22 | 23 | def gen(params, l3_off=0, ipversion=4, negate=False): 24 | parser = argparse.ArgumentParser( 25 | formatter_class=argparse.RawDescriptionHelpFormatter, 26 | prog="%s dns --" % (sys.argv[0]), 27 | description=r''' 28 | 29 | This tool creates a raw Berkeley Packet Filter (BPF) rule that will 30 | match packets which are DNS queries against listed domains. For 31 | example: 32 | 33 | %(prog)s example.com 34 | 35 | will print a BPF rule matching all packets that look like a DNS packet 36 | first query being equal to "example.com". Another example: 37 | 38 | %(prog)s *.www.fint.me 39 | 40 | will match packets that have a any prefix (subdomain) and exactly 41 | "www.fint.me" as suffix. It will match: 42 | 43 | blah.www.fint.me 44 | anyanyany.www.fint.me 45 | 46 | but it will not match: 47 | 48 | www.fint.me 49 | blah.blah.www.fint.me 50 | 51 | Also, star has a special meaning only if it's a sole part of 52 | subdomain: "*xxx.example.com" is treated as a literal star, so is 53 | "xxx*.example.com". On the other hand "xxx.*.example.com" will have a 54 | wildcard meaning. 55 | 56 | Question mark '?' matches exactly one character. For example this rule: 57 | 58 | %(prog)s fin?.me 59 | 60 | will match: 61 | 62 | fint.me, finT.me, finX.me, finZ,me 63 | 64 | but will not match: 65 | 66 | finXX.me, fiXX.me, www.finX.me, fin.me 67 | 68 | You can create a single rule matching than one domain: 69 | 70 | %(prog)s example.com *.www.fint.me 71 | 72 | The "--ignorecase" option will produce BPF bytecode that matches 73 | domains in case insensitive way. Beware, the generated bytecode will be 74 | significantly longer. 75 | 76 | Leading and trailing dots are ignored, this commands are equivalent: 77 | 78 | %(prog)s example.com fint.me 79 | %(prog)s .example.com fint.me. 80 | 81 | A special consideration is given if the suffix is '**' (star 82 | star). This is interpreted as "any suffix", for example this: 83 | 84 | %(prog)s example.** 85 | 86 | Will match: 87 | 88 | example.com example.de example.co.uk example.anything.whatsoever 89 | 90 | But not: 91 | 92 | www.example.com eexample.com 93 | 94 | Wildcard matches can specify ranges, for example 95 | 96 | %(prog)s *{4-255}.example.com 97 | 98 | will match any subdomains of example.com of 4 and more 99 | characters. Only a syntax with explicit minimum and maximum is 100 | supported. 101 | 102 | ''') 103 | 104 | parser.add_argument('-i', '--ignorecase', action='store_true', 105 | help='match domains in case-insensitive way') 106 | parser.add_argument('domains', nargs='+', 107 | help='DNS domain patterns to match on') 108 | 109 | args = parser.parse_args(params) 110 | 111 | list_of_rules = [] 112 | 113 | for domain in args.domains: 114 | # remove trailing and leading whitespace 115 | domain = domain.strip().lstrip(".") 116 | 117 | if domain.endswith('**'): 118 | free_suffix = True 119 | domain = domain[:-2] 120 | else: 121 | free_suffix = False 122 | 123 | if free_suffix and domain.endswith("."): 124 | exact_free_suffix = True 125 | else: 126 | exact_free_suffix = False 127 | 128 | # Ensure the trailing dot 129 | domain = domain.rstrip(".") 130 | if not free_suffix: 131 | domain += '.' 132 | 133 | parts = domain.split(".") 134 | rule = [] 135 | for i, part in enumerate(parts): 136 | matchstar = re.match('^[*]({(?P\d+)-(?P\d+)})?$', part) 137 | part = urllib.unquote(part) 138 | 139 | is_last = len(parts) - 1 == i 140 | 141 | # is_char is used to determine whether a particular byte is 142 | # a normal char or not. For the domain part length byte we 143 | # set it to False, or to None to signify that the length 144 | # should be masked and ignored. 145 | if is_last and free_suffix and not exact_free_suffix: 146 | len_is_char = None 147 | else: 148 | len_is_char = False 149 | 150 | if matchstar: 151 | rule.append( (False, matchstar.groupdict()) ) 152 | else: 153 | rule.append( (True, [(len_is_char, chr(len(part)))] \ 154 | + [(True, c) for c in part]) ) 155 | 156 | list_of_rules.append( list(merge(rule)) ) 157 | 158 | def match_exact(rule, label, last=False): 159 | mask = [] 160 | for is_char, b in rule: 161 | if is_char and b == '?': 162 | mask.append( '\xff' ) 163 | elif is_char and args.ignorecase: 164 | mask.append( '\x20' ) 165 | elif is_char is None and last: 166 | # ignore the length of last part if free_suffix 167 | mask.append( '\xff' ) 168 | else: 169 | # else, literal matching 170 | mask.append( '\x00' ) 171 | mask = ''.join(mask) 172 | s = ''.join(map(lambda (is_char, b): b, rule)) 173 | print " ; Match: %s %r mask=%s" % (s.encode('hex'), s, mask.encode('hex')) 174 | off = 0 175 | while s: 176 | if len(s) >= 4: 177 | m, s = s[:4], s[4:] 178 | mm, mask = mask[:4], mask[4:] 179 | m, = struct.unpack('!I', m) 180 | mm, = struct.unpack('!I', mm) 181 | print " ld [x + %i]" % off 182 | if mm: 183 | print " or #0x%08x" % mm 184 | m |= mm 185 | print " jneq #0x%08x, %s" % (m, label,) 186 | off += 4 187 | elif len(s) >= 2: 188 | m, s = s[:2], s[2:] 189 | mm, mask = mask[:2], mask[2:] 190 | m, = struct.unpack('!H', m) 191 | mm, = struct.unpack('!H', mm) 192 | print " ldh [x + %i]" % off 193 | if mm: 194 | print " or #0x%04x" % mm 195 | m |= mm 196 | print " jneq #0x%04x, %s" % (m, label,) 197 | off += 2 198 | else: 199 | m, s = s[:1], s[1:] 200 | m, = struct.unpack('!B', m) 201 | mm, mask = mask[:1], mask[1:] 202 | mm, = struct.unpack('!B', mm) 203 | print " ldb [x + %i]" % off 204 | if mm: 205 | print " or #0x%02x" % mm 206 | m |= mm 207 | print " jneq #0x%02x, %s" % (m, label,) 208 | off += 1 209 | if not last: 210 | print " txa" 211 | print " add #%i" % (off,) 212 | print " tax" 213 | 214 | def match_star(mdict, label): 215 | mi, ma = mdict['min'], mdict['max'] 216 | if not(mi and ma): 217 | print " ; Match: *" 218 | else: 219 | mi, ma = int(mi), int(ma) 220 | print " ; Match: *{%s-%s}" % (mi, ma) 221 | print " ldb [x + 0]" 222 | if mi or ma: 223 | if mi == ma and mi > 0 and ma < 255: 224 | print " jneq #%s, %s" % (mi, label,) 225 | else: 226 | if mi > 0: 227 | print " jlt #%s, %s" % (mi, label,) 228 | if ma < 255: 229 | print " jgt #%s, %s" % (ma, label,) 230 | print " add x" 231 | print " add #1" 232 | print " tax" 233 | 234 | if ipversion == 4: 235 | print " ldx 4*([%i]&0xf)" % (l3_off,) 236 | print " ; l3_off(%i) + 8 of udp + 12 of dns" % (l3_off,) 237 | print " ld #%i" % (l3_off + 8 + 12) # 8B of udp + 12B of dns header 238 | print " add x" 239 | elif ipversion == 6: 240 | # assuming first "next header" is UDP 241 | print " ld #%i" % (l3_off + 40 + 8 + 12) # 40B of ipv6 + 8B of udp + 12B of dns header 242 | 243 | print " tax" 244 | print " ; a = x = M[0] = offset of first dns query byte" 245 | print " %sst M[0]" % ('' if len(list_of_rules) > 1 else '; ',) 246 | print 247 | 248 | for i, rules in enumerate(list_of_rules): 249 | print "lb_%i:" % (i,) 250 | print " %sldx M[0]" % ('' if i != 0 else '; ') 251 | for j, rule in enumerate(rules): 252 | last = (j == len(rules)-1) 253 | label = 'lb_%i' % (i+1,) 254 | if not isinstance(rule, dict): 255 | match_exact(rule, label, last) 256 | else: 257 | match_star(rule, label) 258 | print " ret #%i" % (65535 if not negate else 0) 259 | print 260 | 261 | print "lb_%i:" % (i+1,) 262 | print " ret #%i" % (0 if not negate else 65535) 263 | 264 | name_parts = [] 265 | for domain in args.domains: 266 | if domain[0] == '-': 267 | continue 268 | 269 | domain = domain.strip(".").strip() 270 | parts = [] 271 | for part in domain.split("."): 272 | if part == '*': 273 | parts.append( 'any' ) 274 | else: 275 | parts.append( ''.join(c if c in ACCEPTABLE_CHARS else 'x' 276 | for c in part) ) 277 | name_parts.append( '_'.join(parts) ) 278 | return '_'.join(name_parts) 279 | -------------------------------------------------------------------------------- /bpftools/p0f.py: -------------------------------------------------------------------------------- 1 | import re 2 | import math 3 | 4 | ip = { 5 | 'hl': '(ip[0] & 0xf)', 6 | 'ecn': '(ip[1] & 0x2)', 7 | 'tl': 'ip[2:2]', 8 | 'ipid': 'ip[4:2]', 9 | 'df': '(ip[6] & 0x40)', 10 | 'zero': '(ip[6] & 0x80)', 11 | 'ttl': 'ip[8]', 12 | } 13 | 14 | ip6 = { 15 | 'pl': '(ip6[4:2])', 16 | 'ttl': '(ip6[7])', 17 | 'flow': '(ip6[2:2])', 18 | } 19 | 20 | tcp = { 21 | 'win_size': 'tcp[14:2]', 22 | 'data_off': '(tcp[12] >> 4)', 23 | 'seq': 'tcp[4:4]', 24 | 'ack_flag': '(tcp[tcpflags] & tcp-ack)', 25 | 'ack': 'tcp[8:4]', 26 | 'urg_flag': '(tcp[tcpflags] & tcp-urg)', 27 | 'urg': 'tcp[18:2]', 28 | 'push_flag': '(tcp[tcpflags] & tcp-push)', 29 | } 30 | 31 | tcp6 = { 32 | 'win_size': 'ip6[(40 + 14):2]', 33 | 'data_off': '(ip6[40 + 12] >> 4)', 34 | 'seq': 'ip6[(40 + 4):4]', 35 | 'ack_flag': '(ip6[40 + tcpflags] & tcp-ack)', 36 | 'ack': 'ip6[(40 + 8):4]', 37 | 'urg_flag': '(ip6[40 + tcpflags] & tcp-urg)', 38 | 'urg': 'ip6[(40 + 18):2]', 39 | 'push_flag': '(ip6[40 + tcpflags] & tcp-push)', 40 | } 41 | 42 | class P0fBPF: 43 | def __init__(self, p0f_str): 44 | self.p0f_str = p0f_str 45 | 46 | self.parse_sig() 47 | self.build_abstract_desc() 48 | self.build_bpf_filter() 49 | self.build_doc_bpf_filter() 50 | 51 | def parse_ittl(self): 52 | if self.ittl.endswith("-"): 53 | self.ttl_rand = True 54 | else: 55 | self.ttl_rand = False 56 | 57 | def parse_win_size(self): 58 | m = re.match("mss\*(?P(\d+))", self.win_size) 59 | if m: 60 | self.win_size_type = 'mss_mult' 61 | self.win_size = m.groupdict()['mss'] 62 | else: 63 | m = re.match("mtu\*(?P(\d+))", self.win_size) 64 | if m: 65 | self.win_size_type = 'mtu_mult' 66 | self.win_size = m.groupdict()['mtu'] 67 | else: 68 | m = re.match("%(?P(\d+))", self.win_size) 69 | if m: 70 | self.win_size_type = 'const_mult' 71 | self.win_size = m.groupdict()['const'] 72 | else: 73 | self.win_size_type = 'const' 74 | 75 | def parse_sig(self): 76 | self.ver, self.ittl, self.olen, self.mss, self.win, self.olayout, self.quirks, self.pclass = self.p0f_str.split(':') 77 | 78 | if self.ver != '4' and self.ver != '6': 79 | raise ValueError("IP version must be either 4 or 6") 80 | 81 | if not re.match("^(\d+)-?$", self.ittl): 82 | raise ValueError("Invalid ttl field") 83 | self.parse_ittl() 84 | 85 | if self.olen != '*' and not self.olen.isdigit(): 86 | raise ValueError("Invalid option len field") 87 | 88 | if self.mss != '*' and not self.mss.isdigit(): 89 | raise ValueError("Invalid mss field") 90 | 91 | self.win_size, self.win_scale = self.win.split(',') 92 | if self.win_size != "*" and not re.match("^((mss\*)|(mtu\*)|(%))?(\d+)$", self.win_size): 93 | raise ValueError("Invalid win size field") 94 | self.parse_win_size() 95 | 96 | if self.win_scale != '*' and not self.win_scale.isdigit(): 97 | raise ValueError("Invalid win size field") 98 | 99 | self.olayout = self.olayout.split(',') 100 | if self.olayout == ['']: self.olayout = [] 101 | opts = set(self.olayout) - set(['eol', 'nop', 'mss', 'ws', 'sok', 'ts']) 102 | if len(opts) == 1 and not re.match("^eol\+(\d+)$", opts.pop()): 103 | raise ValueError("Invalid option in olayout") 104 | elif len(opts) >= 1: 105 | raise ValueError("Invalid options in olayout") 106 | 107 | self.quirks = self.quirks.split(',') 108 | quirks = set(self.quirks) - set(['', 'df', 'id+', 'id-', 'ecn', '0+', 'flow', 'seq-', 'ack-', 'ack+', 'uptr+', 'urgf+', 'pushf+', 'ts1-', 'ts2+', 'opt+', 'exws', 'linux', 'bad']) 109 | if len(quirks) > 0: 110 | raise ValueError("Invalid quirks") 111 | 112 | if not re.match("0|\+|\*", self.pclass): 113 | raise ValueError("Invalid pclass") 114 | 115 | def ip_field(self, field): 116 | return ip[field] if self.ver == '4' else ip6[field] 117 | 118 | def tcp_field(self, field): 119 | return tcp[field] if self.ver == '4' else tcp6[field] 120 | 121 | def get_tcp_opt_offset(self, opts, opt, off, len): 122 | for o in opts: 123 | if o[0] == opt: 124 | if self.ver == '4': 125 | return ("tcp[%d:%d]" % (o[2] + off, len)) 126 | else: 127 | return ("ip6[(40 + %d):%d]" % (o[2] + off, len)) 128 | raise ValueError("Requested TCP option is not in olayout") 129 | 130 | def build_tcp_opt_table(self): 131 | self.tcp_opt_offsets = [] 132 | self.eol_start = 0 133 | self.eol_pad = 0 134 | self.tcp_opt_len = 0 135 | cur_tcp_opt_off = 20 136 | 137 | for opt in self.olayout: 138 | if opt == 'eol': code, inc = 0, 1 139 | elif opt == 'nop': code, inc = 1, 1 140 | elif opt == 'mss': code, inc = 2, 4 141 | elif opt == 'ws': code, inc = 3, 3 142 | elif opt == 'sok': code, inc = 4, 2 143 | elif opt == 'ts': code, inc = 8, 10 144 | else: 145 | m = re.match("eol\+(?P(\d+))", opt) 146 | if m: 147 | self.eol_pad = int(m.groupdict()['eol_pad']) 148 | opt = 'eol' 149 | code = 0 150 | inc = 1 151 | else: 152 | raise ValueError("Invalid TCP option in olayout field") 153 | 154 | self.tcp_opt_offsets.append([opt, code, cur_tcp_opt_off]) 155 | cur_tcp_opt_off += inc 156 | self.eol_start = cur_tcp_opt_off 157 | 158 | self.tcp_opt_len = cur_tcp_opt_off + self.eol_pad - 20 159 | 160 | def build_ipver(self): 161 | ver = 'ip' if self.ver == '4' else 'ip6' 162 | self.steps.append([ver, 'ip version']) 163 | 164 | def build_ttl(self): 165 | if self.ttl_rand == False: 166 | ttl = self.ip_field('ttl') 167 | ittl = int(self.ittl) 168 | 169 | self.steps.append([ttl, '<=', ittl, 'ttl <= %d' % ittl]) 170 | if ittl > 32: 171 | self.steps.append([ttl, '>', ittl - 35, 'ttl > %d' % (ittl - 35)]) 172 | 173 | def build_olen(self): 174 | if self.ver == '4' and self.olen != '*': 175 | hl = self.ip_field('hl') 176 | self.steps.append([hl, '==', 5 + int(self.olen), 'IP options len == %s' % self.olen]) 177 | 178 | def build_mss(self): 179 | if self.mss != '*' and self.mss != '0': 180 | mss_off = self.get_tcp_opt_offset(self.tcp_opt_offsets, 'mss', 2, 2) 181 | self.steps.append([mss_off, '==', self.mss, 'mss == %s' % self.mss]) 182 | 183 | def build_win_size(self): 184 | if self.win_size != '*': 185 | win_size = self.tcp_field('win_size') 186 | 187 | if self.win_size_type == 'mss_mult': 188 | mss_off = self.get_tcp_opt_offset(self.tcp_opt_offsets, 'mss', 2, 2) 189 | mss_mult = [mss_off, '*', self.win_size] 190 | self.steps.append([win_size, '==', mss_mult, 'win size == mss * %s' % self.win_size]) 191 | elif self.win_size_type == 'mtu_mult': 192 | mtu_mult = ['1500', '*', self.win_size] # assume mtu=1500 193 | self.steps.append([win_size, '==', mtu_mult, 'win size == mtu']) 194 | elif self.win_size_type == 'const_mult': 195 | const_mul = [win_size, '%', self.win_size] 196 | self.steps.append([const_mul, '==', 0, 'win size == x * %s' % self.win_size]) 197 | else: 198 | self.steps.append([win_size, '==', self.win_size, 'win size == %s' % self.win_size]) 199 | 200 | def build_win_scale(self): 201 | if self.win_scale != '*' and self.win_scale != '0': 202 | ws_off = self.get_tcp_opt_offset(self.tcp_opt_offsets, 'ws', 2, 1) 203 | self.steps.append([ws_off, '==', self.win_scale, 'win scale == %s' % self.win_scale]) 204 | 205 | def build_eol_pad(self): 206 | pad_pos = self.eol_start 207 | pad_left = self.eol_pad 208 | 209 | while pad_left > 0: 210 | if (self.eol_pad >= 4): 211 | cur_chunk_len = 4 212 | elif (self.eol_pad >= 2): 213 | cur_chunk_len = 2 214 | else: 215 | cur_chunk_len = 1 216 | 217 | op = '!=' if 'opt+' in self.quirks else '==' 218 | 219 | if self.ver == '4': 220 | self.steps.append(["tcp[%s:%s]" % (pad_pos, cur_chunk_len), op, 0, 'eol pad %s 0' % op]) 221 | else: 222 | self.steps.append(["ip6[(40 + %s):%s]" % (pad_pos, cur_chunk_len), op, 0, 'eol pad %s 0' % op]) 223 | 224 | pad_pos += cur_chunk_len 225 | pad_left -= cur_chunk_len 226 | 227 | def build_tcp_olayout(self): 228 | if not 'bad' in self.quirks: 229 | data_off = self.tcp_field('data_off') 230 | data_off_val = 5 + int(math.ceil(self.tcp_opt_len / 4.0)) 231 | self.steps.append([data_off, '==', data_off_val, 'TCP data offset']) 232 | 233 | for o in self.tcp_opt_offsets: 234 | if self.ver == '4': 235 | o_off = "tcp[%d]" % o[2] 236 | else: 237 | o_off = "ip6[40 + %d]" % (o[2]) 238 | self.steps.append([o_off, '==', o[1], "olayout " + o[0]]) 239 | 240 | self.build_eol_pad() 241 | 242 | def build_df(self): 243 | if self.ver == '4': 244 | df = self.ip_field('df') 245 | id = self.ip_field('ipid') 246 | 247 | if 'df' in self.quirks: 248 | self.steps.append([df, '!=', 0, 'df set']) 249 | elif 'id+' in self.quirks: 250 | self.steps.append([df, '!=', 0, 'id+ (df set)']) 251 | self.steps.append([id, '!=', 0, 'id+ (id set) ']) 252 | elif 'id-' in self.quirks: 253 | self.steps.append([df, '==', 0, 'id- (df not set)']) 254 | self.steps.append([id, '==', 0, 'id- (id not set)']) 255 | 256 | def build_ecn(self): 257 | if self.ver == '4' and 'ecn' in self.quirks: 258 | ecn = self.ip_field('ecn') 259 | self.steps.append([ecn, '!=', 0, 'ecn']) 260 | 261 | def build_zero(self): 262 | if self.ver == '4': 263 | zero = self.ip_field('zero') 264 | if '0+' in self.quirks: 265 | self.steps.append([zero, '!=', 0, 'mbz non zero']) 266 | else: 267 | self.steps.append([zero, '==', 0, 'mbz zero']) 268 | 269 | def build_flow(self): 270 | if self.ver == '6' and 'flow' in self.quirks: 271 | flow = self.ip_field('flow') 272 | self.steps.append([flow, '!=', 0, 'flow']) 273 | 274 | def build_seq(self): 275 | if 'seq-' in self.quirks: 276 | seq = self.tcp_field('seq') 277 | self.steps.append([seq, '==', 0, 'seq- (seq num not set)']) 278 | 279 | def build_ack(self): 280 | ack_flag = self.tcp_field('ack_flag') 281 | ack = self.tcp_field('ack') 282 | if 'ack+' in self.quirks: 283 | self.steps.append([ack_flag, '==', 0, 'ack+ (ack flag not set)']) 284 | self.steps.append([ack, '!=', 0, 'ack+ (ack num set)']) 285 | elif 'ack-' in self.quirks: 286 | self.steps.append([ack_flag, '!=', 0, 'ack- (ack flag set)']) 287 | self.steps.append([ack, '==', 0, 'ack- (ack num not set)']) 288 | 289 | def build_urg(self): 290 | urg_flag = self.tcp_field('urg_flag') 291 | urg = self.tcp_field('urg') 292 | 293 | if 'uptr+' in self.quirks: 294 | self.steps.append([urg_flag, '==', 0, 'uptr+ (urg flag not set)']) 295 | self.steps.append([urg, '!=', 0, 'uptr+ (urg ptr set)']) 296 | elif 'urgf+' in self.quirks: 297 | self.steps.append([urg_flag, '!=', 0, 'urgf+ (urg flag set)']) 298 | 299 | def build_push(self): 300 | if 'pushf+' in self.quirks: 301 | push_flag = self.tcp_field('push_flag') 302 | self.steps.append([push_flag, '!=', 0, 'pushf+ (psh flag set)']) 303 | 304 | def build_ts(self): 305 | if 'ts1-' in self.quirks: 306 | ts_off = self.get_tcp_opt_offset(self.tcp_opt_offsets, 'ts', 2, 4) 307 | self.steps.append([ts_off, '==', 0, 'ts1- (ts1 not set)']) 308 | 309 | if 'ts2+' in self.quirks: 310 | ts_off = self.get_tcp_opt_offset(self.tcp_opt_offsets, 'ts', 6, 4) 311 | self.steps.append([ts_off, '!=', 0, 'ts2+ (ts2 set)']) 312 | 313 | def build_exws(self): 314 | if 'exws' in self.quirks: 315 | ws_off = self.get_tcp_opt_offset(self.tcp_opt_offsets, 'ws', 2, 1) 316 | self.steps.append([ws_off, '>', 14, 'exws']) 317 | 318 | def build_linux(self): 319 | if 'linux' in self.quirks: 320 | if self.ver == '4': 321 | seq = self.tcp_field('seq') 322 | ts = self.get_tcp_opt_offset(self.tcp_opt_offsets, 'ts', 2, 4) 323 | id = self.ip_field('ipid') 324 | 325 | seq_xor_ts = [seq, '^', ts] 326 | seq_xor_ts = [seq_xor_ts, '&', '0xffff'] 327 | 328 | self.steps.append([id, '==', seq_xor_ts, 'linux']) 329 | 330 | def build_quirks(self): 331 | self.build_df() 332 | self.build_ecn() 333 | self.build_zero() 334 | self.build_flow() 335 | self.build_seq() 336 | self.build_ack() 337 | self.build_urg() 338 | self.build_push() 339 | self.build_ts() 340 | self.build_exws() 341 | self.build_linux() 342 | 343 | def build_pclass(self): 344 | if self.pclass != '*': 345 | if self.ver == '4': 346 | tl = self.ip_field('tl') 347 | hl = self.ip_field('hl') 348 | data_off = self.tcp_field('data_off') 349 | payload_len = "(%s - (%s * 4) - (%s * 4))" % (tl, hl, data_off) 350 | else: 351 | pl = self.ip_field('pl') 352 | data_off = self.tcp_field('data_off') 353 | payload_len = "(%s - (%s * 4))" % (pl, data_off) 354 | op = '==' if self.pclass == '0' else '!=' 355 | self.steps.append([payload_len, op, 0, 'payload len %s 0' % op]) 356 | 357 | def build_abstract_desc(self): 358 | self.steps = [] 359 | 360 | self.build_tcp_opt_table() 361 | self.build_ipver() 362 | self.build_ttl() 363 | self.build_olen() 364 | self.build_mss() 365 | self.build_win_size() 366 | self.build_win_scale() 367 | self.build_tcp_olayout() 368 | self.build_quirks() 369 | self.build_pclass() 370 | 371 | def expand_step(self, s, doc=False): 372 | if not isinstance(s, list): 373 | return s 374 | elif len(s) == 2: 375 | if doc: 376 | return "%s: %s" % (s[0], s[1]) 377 | else: 378 | return "%s" % s[0] 379 | else: 380 | lhs = self.expand_step(s[0]) 381 | rhs = self.expand_step(s[2]) 382 | if doc: 383 | return "(%s %s %s): %s" % (lhs, s[1], rhs, s[3]) 384 | else: 385 | return "(%s %s %s)" % (lhs, s[1], rhs) 386 | 387 | def build_bpf_filter(self): 388 | self.bpf_str = " and ".join(self.expand_step(s) for s in self.steps) 389 | 390 | def build_doc_bpf_filter(self): 391 | self.doc_bpf_str = "\n".join(self.expand_step(s, True) for s in self.steps) 392 | -------------------------------------------------------------------------------- /linux_tools/COPYING: -------------------------------------------------------------------------------- 1 | 2 | NOTE! This copyright does *not* cover user programs that use kernel 3 | services by normal system calls - this is merely considered normal use 4 | of the kernel, and does *not* fall under the heading of "derived work". 5 | Also note that the GPL below is copyrighted by the Free Software 6 | Foundation, but the instance of code that it refers to (the Linux 7 | kernel) is copyrighted by me and others who actually wrote it. 8 | 9 | Also note that the only valid version of the GPL as far as the kernel 10 | is concerned is _this_ particular version of the license (ie v2, not 11 | v2.2 or v3.x or whatever), unless explicitly otherwise stated. 12 | 13 | Linus Torvalds 14 | 15 | ---------------------------------------- 16 | 17 | GNU GENERAL PUBLIC LICENSE 18 | Version 2, June 1991 19 | 20 | Copyright (C) 1989, 1991 Free Software Foundation, Inc. 21 | 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 22 | Everyone is permitted to copy and distribute verbatim copies 23 | of this license document, but changing it is not allowed. 24 | 25 | Preamble 26 | 27 | The licenses for most software are designed to take away your 28 | freedom to share and change it. By contrast, the GNU General Public 29 | License is intended to guarantee your freedom to share and change free 30 | software--to make sure the software is free for all its users. This 31 | General Public License applies to most of the Free Software 32 | Foundation's software and to any other program whose authors commit to 33 | using it. (Some other Free Software Foundation software is covered by 34 | the GNU Library General Public License instead.) You can apply it to 35 | your programs, too. 36 | 37 | When we speak of free software, we are referring to freedom, not 38 | price. Our General Public Licenses are designed to make sure that you 39 | have the freedom to distribute copies of free software (and charge for 40 | this service if you wish), that you receive source code or can get it 41 | if you want it, that you can change the software or use pieces of it 42 | in new free programs; and that you know you can do these things. 43 | 44 | To protect your rights, we need to make restrictions that forbid 45 | anyone to deny you these rights or to ask you to surrender the rights. 46 | These restrictions translate to certain responsibilities for you if you 47 | distribute copies of the software, or if you modify it. 48 | 49 | For example, if you distribute copies of such a program, whether 50 | gratis or for a fee, you must give the recipients all the rights that 51 | you have. You must make sure that they, too, receive or can get the 52 | source code. And you must show them these terms so they know their 53 | rights. 54 | 55 | We protect your rights with two steps: (1) copyright the software, and 56 | (2) offer you this license which gives you legal permission to copy, 57 | distribute and/or modify the software. 58 | 59 | Also, for each author's protection and ours, we want to make certain 60 | that everyone understands that there is no warranty for this free 61 | software. If the software is modified by someone else and passed on, we 62 | want its recipients to know that what they have is not the original, so 63 | that any problems introduced by others will not reflect on the original 64 | authors' reputations. 65 | 66 | Finally, any free program is threatened constantly by software 67 | patents. We wish to avoid the danger that redistributors of a free 68 | program will individually obtain patent licenses, in effect making the 69 | program proprietary. To prevent this, we have made it clear that any 70 | patent must be licensed for everyone's free use or not licensed at all. 71 | 72 | The precise terms and conditions for copying, distribution and 73 | modification follow. 74 | 75 | GNU GENERAL PUBLIC LICENSE 76 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 77 | 78 | 0. This License applies to any program or other work which contains 79 | a notice placed by the copyright holder saying it may be distributed 80 | under the terms of this General Public License. The "Program", below, 81 | refers to any such program or work, and a "work based on the Program" 82 | means either the Program or any derivative work under copyright law: 83 | that is to say, a work containing the Program or a portion of it, 84 | either verbatim or with modifications and/or translated into another 85 | language. (Hereinafter, translation is included without limitation in 86 | the term "modification".) Each licensee is addressed as "you". 87 | 88 | Activities other than copying, distribution and modification are not 89 | covered by this License; they are outside its scope. The act of 90 | running the Program is not restricted, and the output from the Program 91 | is covered only if its contents constitute a work based on the 92 | Program (independent of having been made by running the Program). 93 | Whether that is true depends on what the Program does. 94 | 95 | 1. You may copy and distribute verbatim copies of the Program's 96 | source code as you receive it, in any medium, provided that you 97 | conspicuously and appropriately publish on each copy an appropriate 98 | copyright notice and disclaimer of warranty; keep intact all the 99 | notices that refer to this License and to the absence of any warranty; 100 | and give any other recipients of the Program a copy of this License 101 | along with the Program. 102 | 103 | You may charge a fee for the physical act of transferring a copy, and 104 | you may at your option offer warranty protection in exchange for a fee. 105 | 106 | 2. You may modify your copy or copies of the Program or any portion 107 | of it, thus forming a work based on the Program, and copy and 108 | distribute such modifications or work under the terms of Section 1 109 | above, provided that you also meet all of these conditions: 110 | 111 | a) You must cause the modified files to carry prominent notices 112 | stating that you changed the files and the date of any change. 113 | 114 | b) You must cause any work that you distribute or publish, that in 115 | whole or in part contains or is derived from the Program or any 116 | part thereof, to be licensed as a whole at no charge to all third 117 | parties under the terms of this License. 118 | 119 | c) If the modified program normally reads commands interactively 120 | when run, you must cause it, when started running for such 121 | interactive use in the most ordinary way, to print or display an 122 | announcement including an appropriate copyright notice and a 123 | notice that there is no warranty (or else, saying that you provide 124 | a warranty) and that users may redistribute the program under 125 | these conditions, and telling the user how to view a copy of this 126 | License. (Exception: if the Program itself is interactive but 127 | does not normally print such an announcement, your work based on 128 | the Program is not required to print an announcement.) 129 | 130 | These requirements apply to the modified work as a whole. If 131 | identifiable sections of that work are not derived from the Program, 132 | and can be reasonably considered independent and separate works in 133 | themselves, then this License, and its terms, do not apply to those 134 | sections when you distribute them as separate works. But when you 135 | distribute the same sections as part of a whole which is a work based 136 | on the Program, the distribution of the whole must be on the terms of 137 | this License, whose permissions for other licensees extend to the 138 | entire whole, and thus to each and every part regardless of who wrote it. 139 | 140 | Thus, it is not the intent of this section to claim rights or contest 141 | your rights to work written entirely by you; rather, the intent is to 142 | exercise the right to control the distribution of derivative or 143 | collective works based on the Program. 144 | 145 | In addition, mere aggregation of another work not based on the Program 146 | with the Program (or with a work based on the Program) on a volume of 147 | a storage or distribution medium does not bring the other work under 148 | the scope of this License. 149 | 150 | 3. You may copy and distribute the Program (or a work based on it, 151 | under Section 2) in object code or executable form under the terms of 152 | Sections 1 and 2 above provided that you also do one of the following: 153 | 154 | a) Accompany it with the complete corresponding machine-readable 155 | source code, which must be distributed under the terms of Sections 156 | 1 and 2 above on a medium customarily used for software interchange; or, 157 | 158 | b) Accompany it with a written offer, valid for at least three 159 | years, to give any third party, for a charge no more than your 160 | cost of physically performing source distribution, a complete 161 | machine-readable copy of the corresponding source code, to be 162 | distributed under the terms of Sections 1 and 2 above on a medium 163 | customarily used for software interchange; or, 164 | 165 | c) Accompany it with the information you received as to the offer 166 | to distribute corresponding source code. (This alternative is 167 | allowed only for noncommercial distribution and only if you 168 | received the program in object code or executable form with such 169 | an offer, in accord with Subsection b above.) 170 | 171 | The source code for a work means the preferred form of the work for 172 | making modifications to it. For an executable work, complete source 173 | code means all the source code for all modules it contains, plus any 174 | associated interface definition files, plus the scripts used to 175 | control compilation and installation of the executable. However, as a 176 | special exception, the source code distributed need not include 177 | anything that is normally distributed (in either source or binary 178 | form) with the major components (compiler, kernel, and so on) of the 179 | operating system on which the executable runs, unless that component 180 | itself accompanies the executable. 181 | 182 | If distribution of executable or object code is made by offering 183 | access to copy from a designated place, then offering equivalent 184 | access to copy the source code from the same place counts as 185 | distribution of the source code, even though third parties are not 186 | compelled to copy the source along with the object code. 187 | 188 | 4. You may not copy, modify, sublicense, or distribute the Program 189 | except as expressly provided under this License. Any attempt 190 | otherwise to copy, modify, sublicense or distribute the Program is 191 | void, and will automatically terminate your rights under this License. 192 | However, parties who have received copies, or rights, from you under 193 | this License will not have their licenses terminated so long as such 194 | parties remain in full compliance. 195 | 196 | 5. You are not required to accept this License, since you have not 197 | signed it. However, nothing else grants you permission to modify or 198 | distribute the Program or its derivative works. These actions are 199 | prohibited by law if you do not accept this License. Therefore, by 200 | modifying or distributing the Program (or any work based on the 201 | Program), you indicate your acceptance of this License to do so, and 202 | all its terms and conditions for copying, distributing or modifying 203 | the Program or works based on it. 204 | 205 | 6. Each time you redistribute the Program (or any work based on the 206 | Program), the recipient automatically receives a license from the 207 | original licensor to copy, distribute or modify the Program subject to 208 | these terms and conditions. You may not impose any further 209 | restrictions on the recipients' exercise of the rights granted herein. 210 | You are not responsible for enforcing compliance by third parties to 211 | this License. 212 | 213 | 7. If, as a consequence of a court judgment or allegation of patent 214 | infringement or for any other reason (not limited to patent issues), 215 | conditions are imposed on you (whether by court order, agreement or 216 | otherwise) that contradict the conditions of this License, they do not 217 | excuse you from the conditions of this License. If you cannot 218 | distribute so as to satisfy simultaneously your obligations under this 219 | License and any other pertinent obligations, then as a consequence you 220 | may not distribute the Program at all. For example, if a patent 221 | license would not permit royalty-free redistribution of the Program by 222 | all those who receive copies directly or indirectly through you, then 223 | the only way you could satisfy both it and this License would be to 224 | refrain entirely from distribution of the Program. 225 | 226 | If any portion of this section is held invalid or unenforceable under 227 | any particular circumstance, the balance of the section is intended to 228 | apply and the section as a whole is intended to apply in other 229 | circumstances. 230 | 231 | It is not the purpose of this section to induce you to infringe any 232 | patents or other property right claims or to contest validity of any 233 | such claims; this section has the sole purpose of protecting the 234 | integrity of the free software distribution system, which is 235 | implemented by public license practices. Many people have made 236 | generous contributions to the wide range of software distributed 237 | through that system in reliance on consistent application of that 238 | system; it is up to the author/donor to decide if he or she is willing 239 | to distribute software through any other system and a licensee cannot 240 | impose that choice. 241 | 242 | This section is intended to make thoroughly clear what is believed to 243 | be a consequence of the rest of this License. 244 | 245 | 8. If the distribution and/or use of the Program is restricted in 246 | certain countries either by patents or by copyrighted interfaces, the 247 | original copyright holder who places the Program under this License 248 | may add an explicit geographical distribution limitation excluding 249 | those countries, so that distribution is permitted only in or among 250 | countries not thus excluded. In such case, this License incorporates 251 | the limitation as if written in the body of this License. 252 | 253 | 9. The Free Software Foundation may publish revised and/or new versions 254 | of the General Public License from time to time. Such new versions will 255 | be similar in spirit to the present version, but may differ in detail to 256 | address new problems or concerns. 257 | 258 | Each version is given a distinguishing version number. If the Program 259 | specifies a version number of this License which applies to it and "any 260 | later version", you have the option of following the terms and conditions 261 | either of that version or of any later version published by the Free 262 | Software Foundation. If the Program does not specify a version number of 263 | this License, you may choose any version ever published by the Free Software 264 | Foundation. 265 | 266 | 10. If you wish to incorporate parts of the Program into other free 267 | programs whose distribution conditions are different, write to the author 268 | to ask for permission. For software which is copyrighted by the Free 269 | Software Foundation, write to the Free Software Foundation; we sometimes 270 | make exceptions for this. Our decision will be guided by the two goals 271 | of preserving the free status of all derivatives of our free software and 272 | of promoting the sharing and reuse of software generally. 273 | 274 | NO WARRANTY 275 | 276 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 277 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 278 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 279 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 280 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 281 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 282 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 283 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 284 | REPAIR OR CORRECTION. 285 | 286 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 287 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 288 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 289 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 290 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 291 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 292 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 293 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 294 | POSSIBILITY OF SUCH DAMAGES. 295 | 296 | END OF TERMS AND CONDITIONS 297 | 298 | How to Apply These Terms to Your New Programs 299 | 300 | If you develop a new program, and you want it to be of the greatest 301 | possible use to the public, the best way to achieve this is to make it 302 | free software which everyone can redistribute and change under these terms. 303 | 304 | To do so, attach the following notices to the program. It is safest 305 | to attach them to the start of each source file to most effectively 306 | convey the exclusion of warranty; and each file should have at least 307 | the "copyright" line and a pointer to where the full notice is found. 308 | 309 | 310 | Copyright (C) 311 | 312 | This program is free software; you can redistribute it and/or modify 313 | it under the terms of the GNU General Public License as published by 314 | the Free Software Foundation; either version 2 of the License, or 315 | (at your option) any later version. 316 | 317 | This program is distributed in the hope that it will be useful, 318 | but WITHOUT ANY WARRANTY; without even the implied warranty of 319 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 320 | GNU General Public License for more details. 321 | 322 | You should have received a copy of the GNU General Public License 323 | along with this program; if not, write to the Free Software 324 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 325 | 326 | 327 | Also add information on how to contact you by electronic and paper mail. 328 | 329 | If the program is interactive, make it output a short notice like this 330 | when it starts in an interactive mode: 331 | 332 | Gnomovision version 69, Copyright (C) year name of author 333 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 334 | This is free software, and you are welcome to redistribute it 335 | under certain conditions; type `show c' for details. 336 | 337 | The hypothetical commands `show w' and `show c' should show the appropriate 338 | parts of the General Public License. Of course, the commands you use may 339 | be called something other than `show w' and `show c'; they could even be 340 | mouse-clicks or menu items--whatever suits your program. 341 | 342 | You should also get your employer (if you work as a programmer) or your 343 | school, if any, to sign a "copyright disclaimer" for the program, if 344 | necessary. Here is a sample; alter the names: 345 | 346 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 347 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 348 | 349 | , 1 April 1989 350 | Ty Coon, President of Vice 351 | 352 | This General Public License does not permit incorporating your program into 353 | proprietary programs. If your program is a subroutine library, you may 354 | consider it more useful to permit linking proprietary applications with the 355 | library. If this is what you want to do, use the GNU Library General 356 | Public License instead of this License. 357 | -------------------------------------------------------------------------------- /linux_tools/bpf_exp.y: -------------------------------------------------------------------------------- 1 | /* 2 | * BPF asm code parser 3 | * 4 | * This program is free software; you can distribute it and/or modify 5 | * it under the terms of the GNU General Public License as published 6 | * by the Free Software Foundation; either version 2 of the License, 7 | * or (at your option) any later version. 8 | * 9 | * Syntax kept close to: 10 | * 11 | * Steven McCanne and Van Jacobson. 1993. The BSD packet filter: a new 12 | * architecture for user-level packet capture. In Proceedings of the 13 | * USENIX Winter 1993 Conference Proceedings on USENIX Winter 1993 14 | * Conference Proceedings (USENIX'93). USENIX Association, Berkeley, 15 | * CA, USA, 2-2. 16 | * 17 | * Copyright 2013 Daniel Borkmann 18 | * Licensed under the GNU General Public License, version 2.0 (GPLv2) 19 | */ 20 | 21 | %{ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "linux/filter.h" 32 | 33 | #include "bpf_exp.yacc.h" 34 | 35 | enum jmp_type { JTL, JFL, JKL }; 36 | 37 | extern FILE *yyin; 38 | extern int yylex(void); 39 | extern void yyerror(const char *str); 40 | 41 | extern void bpf_asm_compile(FILE *fp, bool cstyle); 42 | static void bpf_set_curr_instr(uint16_t op, uint8_t jt, uint8_t jf, uint32_t k); 43 | static void bpf_set_curr_label(char *label); 44 | static void bpf_set_jmp_label(char *label, enum jmp_type type); 45 | 46 | %} 47 | 48 | %union { 49 | char *label; 50 | uint32_t number; 51 | } 52 | 53 | %token OP_LDB OP_LDH OP_LD OP_LDX OP_ST OP_STX OP_JMP OP_JEQ OP_JGT OP_JGE 54 | %token OP_JSET OP_ADD OP_SUB OP_MUL OP_DIV OP_AND OP_OR OP_XOR OP_LSH OP_RSH 55 | %token OP_RET OP_TAX OP_TXA OP_LDXB OP_MOD OP_NEG OP_JNEQ OP_JLT OP_JLE OP_LDI 56 | %token OP_LDXI 57 | 58 | %token K_PKT_LEN K_PROTO K_TYPE K_NLATTR K_NLATTR_NEST K_MARK K_QUEUE K_HATYPE 59 | %token K_RXHASH K_CPU K_IFIDX K_VLANT K_VLANP K_POFF 60 | 61 | %token ':' ',' '[' ']' '(' ')' 'x' 'a' '+' 'M' '*' '&' '#' '%' 62 | 63 | %token number label 64 | 65 | %type