diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 48a90a01a6c8..95c47d74d869 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -3,3 +3,4 @@ install(PROGRAMS ${EXAMPLE_PROGRAMS} DESTINATION share/bcc/examples) add_subdirectory(networking) add_subdirectory(tracing) +add_subdirectory(lua) \ No newline at end of file diff --git a/examples/lua/CMakeLists.txt b/examples/lua/CMakeLists.txt new file mode 100644 index 000000000000..b3220789150c --- /dev/null +++ b/examples/lua/CMakeLists.txt @@ -0,0 +1,4 @@ +file(GLOB C_FILES *.c) +file(GLOB LUA_FILES *.lua) +install(FILES ${C_FILES} DESTINATION share/bcc/examples/lua) +install(PROGRAMS ${LUA_FILES} DESTINATION share/bcc/examples/lua) \ No newline at end of file diff --git a/examples/networking/CMakeLists.txt b/examples/networking/CMakeLists.txt index e018980662a4..ddf326f26f74 100644 --- a/examples/networking/CMakeLists.txt +++ b/examples/networking/CMakeLists.txt @@ -6,3 +6,5 @@ install(PROGRAMS ${EXAMPLE_PROGRAMS} DESTINATION share/bcc/examples/networking) add_subdirectory(distributed_bridge) add_subdirectory(neighbor_sharing) add_subdirectory(vlan_learning) +add_subdirectory(tunnel_monitor) +add_subdirectory(http_filter) \ No newline at end of file diff --git a/examples/networking/dns_matching/dns_matching.c b/examples/networking/dns_matching/dns_matching.c new file mode 100644 index 000000000000..d7289886e8d0 --- /dev/null +++ b/examples/networking/dns_matching/dns_matching.c @@ -0,0 +1,105 @@ +/* + * dns_matching.c Drop DNS packets requesting DNS name contained in hash map + * For Linux, uses BCC, eBPF. See .py file. + * + * Copyright (c) 2016 Rudi Floren. + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 11-May-2016 Rudi Floren Created this. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define ETH_LEN 14 + +struct dns_hdr_t +{ + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +} BPF_PACKET_HEADER; + + +struct dns_query_flags_t +{ + uint16_t qtype; + uint16_t qclass; +} BPF_PACKET_HEADER; + +struct dns_char_t +{ + char c; +} BPF_PACKET_HEADER; + +struct Key { + unsigned char p[32]; +}; + +struct Leaf { + // Not really needed in this example + unsigned char p[4]; +}; + +BPF_TABLE("hash", struct Key, struct Leaf, cache, 128); + +int dns_matching(struct __sk_buff *skb) +{ + u8 *cursor = 0; + struct Key key = {}; + // Check of ethernet/IP frame. + struct ethernet_t *ethernet = cursor_advance(cursor, sizeof(*ethernet)); + if(ethernet->type == ETH_P_IP) { + + // Check for UDP. + struct ip_t *ip = cursor_advance(cursor, sizeof(*ip)); + u16 hlen_bytes = ip->hlen << 2; + if(ip->nextp == IPPROTO_UDP) { + + // Check for Port 53, DNS packet. + struct udp_t *udp = cursor_advance(cursor, sizeof(*udp)); + if(udp->dport == 53){ + + // Our Cursor + the length of our udp packet - size of the udp header + // - the two 16bit values for QTYPE and QCLASS. + u8 *sentinel = cursor + udp->length - sizeof(*udp) - 4; + + struct dns_hdr_t *dns_hdr = cursor_advance(cursor, sizeof(*dns_hdr)); + + // Do nothing if packet is not a request. + if((dns_hdr->flags >>15) != 0) { + // Exit if this packet is not a request. + return -1; + } + + u16 i = 0; + struct dns_char_t *c; + // This unroll worked not in latest BCC version. + for(u8 j = 0; i<255;i++){ + if (cursor == sentinel) goto end; c = cursor_advance(cursor, 1); key.p[i++] = c->c; + } + end: + {} + + struct Leaf * lookup_leaf = cache.lookup(&key); + + // If DNS name is contained in our map, drop packet. + if(lookup_leaf) { + return 0; + } + } + } + } + + return -1; +} diff --git a/examples/networking/dns_matching/dns_matching.py b/examples/networking/dns_matching/dns_matching.py new file mode 100644 index 000000000000..335db2c2c964 --- /dev/null +++ b/examples/networking/dns_matching/dns_matching.py @@ -0,0 +1,57 @@ +#!/usr/bin/python + +from __future__ import print_function +from bcc import BPF +from ctypes import * + +import sys +import socket +import os +import struct + + +def encode_dns(name): + size = 32 + if len(name) > 253: + raise Exception("DNS Name too long.") + b = bytearray(size) + i = 0; + elements = name.split(".") + for element in elements: + b[i] = struct.pack("!B", len(element)) + i += 1 + for j in range(0, len(element)): + b[i] = element[j] + i += 1 + + + return (c_ubyte * size).from_buffer(b) + + + +# initialize BPF - load source code from http-parse-simple.c +bpf = BPF(src_file = "dns_matching.c", debug=0) +# print(bpf.dump_func("dns_test")) + +#load eBPF program http_filter of type SOCKET_FILTER into the kernel eBPF vm +#more info about eBPF program types +#http://man7.org/linux/man-pages/man2/bpf.2.html +function_dns_matching = bpf.load_func("dns_matching", BPF.SOCKET_FILTER) + + +#create raw socket, bind it to eth0 +#attach bpf program to socket created +BPF.attach_raw_socket(function_dns_matching, "eth1") + +# Get the table. +cache = bpf.get_table("cache") + +# Create first entry for foo.bar +key = cache.Key() +key.p = encode_dns("foo.bar") + +leaf = cache.Leaf() +leaf.p = (c_ubyte * 4).from_buffer(bytearray(4)) +cache[key] = leaf + +bpf.trace_print() diff --git a/examples/networking/http_filter/CMakeLists.txt b/examples/networking/http_filter/CMakeLists.txt new file mode 100644 index 000000000000..6f854e78dc10 --- /dev/null +++ b/examples/networking/http_filter/CMakeLists.txt @@ -0,0 +1,4 @@ +set(FILES http-parse-complete.c http-parse-simple.c README.md) +set(PROGRAMS http-parse-complete.py http-parse-simple.py) +install(FILES ${FILES} DESTINATION share/bcc/examples/networking/http_filter) +install(PROGRAMS ${PROGRAMS} DESTINATION share/bcc/examples/networking/http_filter) diff --git a/examples/networking/tunnel_monitor/CMakeLists.txt b/examples/networking/tunnel_monitor/CMakeLists.txt new file mode 100644 index 000000000000..edc7c08e519f --- /dev/null +++ b/examples/networking/tunnel_monitor/CMakeLists.txt @@ -0,0 +1,4 @@ +set(FILES README.md chord.png monitor.c simulation.py vxlan.jpg) +set(PROGRAMS main.py monitor.py setup.sh traffic.sh) +install(FILES ${FILES} DESTINATION share/bcc/examples/networking/tunnel_monitor) +install(PROGRAMS ${PROGRAMS} DESTINATION share/bcc/examples/networking/tunnel_monitor) \ No newline at end of file diff --git a/src/lua/src/main.c b/src/lua/src/main.c index d5b41d6a2056..25dab9ad43ea 100644 --- a/src/lua/src/main.c +++ b/src/lua/src/main.c @@ -29,9 +29,9 @@ #include #include -#include -#include -#include +#include "lauxlib.h" +#include "lua.h" +#include "lualib.h" static lua_State *globalL = NULL; static const char *progname = NULL; diff --git a/tools/biosnoop.lua b/tools/biosnoop.lua new file mode 100644 index 000000000000..ac08897bbeff --- /dev/null +++ b/tools/biosnoop.lua @@ -0,0 +1,183 @@ +#!/usr/bin/env bcc-lua +--[[ +Copyright 2016 GitHub, Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--]] + +local program = [[ +#include +#include + +struct val_t { + u32 pid; + char name[TASK_COMM_LEN]; +}; + +struct data_t { + u32 pid; + u64 rwflag; + u64 delta; + u64 sector; + u64 len; + u64 ts; + char disk_name[DISK_NAME_LEN]; + char name[TASK_COMM_LEN]; +}; + +BPF_HASH(start, struct request *); +BPF_HASH(infobyreq, struct request *, struct val_t); +BPF_PERF_OUTPUT(events); + +// cache PID and comm by-req +int trace_pid_start(struct pt_regs *ctx, struct request *req) +{ + struct val_t val = {}; + + if (bpf_get_current_comm(&val.name, sizeof(val.name)) == 0) { + val.pid = bpf_get_current_pid_tgid(); + infobyreq.update(&req, &val); + } + return 0; +} + +// time block I/O +int trace_req_start(struct pt_regs *ctx, struct request *req) +{ + u64 ts; + + ts = bpf_ktime_get_ns(); + start.update(&req, &ts); + + return 0; +} + +// output +int trace_req_completion(struct pt_regs *ctx, struct request *req) +{ + u64 *tsp, delta; + u32 *pidp = 0; + struct val_t *valp; + struct data_t data ={}; + u64 ts; + + // fetch timestamp and calculate delta + tsp = start.lookup(&req); + if (tsp == 0) { + // missed tracing issue + return 0; + } + ts = bpf_ktime_get_ns(); + data.delta = ts - *tsp; + data.ts = ts / 1000; + + valp = infobyreq.lookup(&req); + if (valp == 0) { + data.len = req->__data_len; + strcpy(data.name,"?"); + } else { + data.pid = valp->pid; + data.len = req->__data_len; + data.sector = req->__sector; + bpf_probe_read(&data.name, sizeof(data.name), valp->name); + bpf_probe_read(&data.disk_name, sizeof(data.disk_name), + req->rq_disk->disk_name); + } + + if (req->cmd_flags & REQ_WRITE) { + data.rwflag=1; + } else { + data.rwflag=0; + } + events.perf_submit(ctx,&data,sizeof(data)); + start.delete(&req); + infobyreq.delete(&req); + + return 0; +} +]] + +local ffi = require("ffi") + +return function(BPF, utils) + local bpf = BPF:new{text=program} + + bpf:attach_kprobe{event="blk_account_io_start", fn_name="trace_pid_start"} + bpf:attach_kprobe{event="blk_start_request", fn_name="trace_req_start"} + bpf:attach_kprobe{event="blk_mq_start_request", fn_name="trace_req_start"} + bpf:attach_kprobe{event="blk_account_io_completion", + fn_name="trace_req_completion"} + + print("%-14s %-14s %-6s %-7s %-2s %-9s %-7s %7s" % {"TIME(s)", "COMM", "PID", + "DISK", "T", "SECTOR", "BYTES", "LAT(ms)"}) + + local rwflg = "" + local start_ts = 0 + local prev_ts = 0 + local delta = 0 + + local function print_event(cpu, event) + local val = -1 + local event_pid = event.pid + local event_delta = tonumber(event.delta) + local event_sector = tonumber(event.sector) + local event_len = tonumber(event.len) + local event_ts = tonumber(event.ts) + local event_disk_name = ffi.string(event.disk_name) + local event_name = ffi.string(event.name) + + if event.rwflag == 1 then + rwflg = "W" + end + + if event.rwflag == 0 then + rwflg = "R" + end + + if not event_name:match("%?") then + val = event_sector + end + + if start_ts == 0 then + prev_ts = start_ts + end + + if start_ts == 1 then + delta = delta + (event_ts - prev_ts) + end + + print("%-14.9f %-14.14s %-6s %-7s %-2s %-9s %-7s %7.2f" % { + delta / 1000000, event_name, event_pid, event_disk_name, rwflg, val, + event_len, event_delta / 1000000}) + + prev_ts = event_ts + start_ts = 1 + end + + local TASK_COMM_LEN = 16 -- linux/sched.h + local DISK_NAME_LEN = 32 -- linux/genhd.h + + bpf:get_table("events"):open_perf_buffer(print_event, [[ + struct { + uint32_t pid; + uint64_t rwflag; + uint64_t delta; + uint64_t sector; + uint64_t len; + uint64_t ts; + char disk_name[%d]; + char name[%d]; + } + ]] % {DISK_NAME_LEN, TASK_COMM_LEN}) + bpf:kprobe_poll_loop() +end diff --git a/tools/stacksnoop.lua b/tools/stacksnoop.lua new file mode 100644 index 000000000000..588b814803b0 --- /dev/null +++ b/tools/stacksnoop.lua @@ -0,0 +1,92 @@ +#!/usr/bin/env bcc-lua +--[[ +Copyright 2016 GitHub, Inc + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +--]] + +local program = [[ +#include + +BPF_STACK_TRACE(stack_traces, 128) + +void trace_stack(struct pt_regs *ctx) { + FILTER + int stack_id = stack_traces.get_stackid(ctx, BPF_F_REUSE_STACKID); + if (stack_id >= 0) + bpf_trace_printk("stack_id=%d\n", stack_id); +} +]] + +return function(BPF, utils) + local parser = utils.argparse("stacksnoop", + "Trace and print kernel stack traces for a kernel function") + parser:flag("-s --offset") + parser:flag("-v --verbose") + parser:option("-p --pid"):convert(tonumber) + parser:argument("function", "kernel function name"):target("fn") + + local args = parser:parse() + local ksym = BPF.SymbolCache() + local filter = "" + + if args.pid then + filter = [[ + u32 pid; + pid = bpf_get_current_pid_tgid(); + if (pid != %d) { return; } + ]] % args.pid + end + + local text = program:gsub("FILTER", filter) + local bpf = BPF:new{text=text} + bpf:attach_kprobe{event=args.fn, fn_name="trace_stack"} + + if BPF.num_open_kprobes() == 0 then + print("Function \"%s\" not found. Exiting." % args.fn) + return + end + + if args.verbose then + print("%-18s %-12s %-6s %-3s %s" % + {"TIME(s)", "COMM", "PID", "CPU", "SYSCALL"}) + else + print("%-18s %s" % {"TIME(s)", "SYSCALL"}) + end + + local stack_traces = bpf:get_table("stack_traces") + local pipe = bpf:pipe() + + while true do + local task, pid, cpu, flags, ts, msg = pipe:trace_fields() + local stack_id = string.match(msg, "stack_id=(%d+)") + + if stack_id then + if args.verbose then + print("%-18.9f %-12.12s %-6d %-3d %s" % {ts, task, pid, cpu, args.fn}) + else + print("%-18.9f %s" % {ts, args.fn}) + end + + for addr in stack_traces:walk(tonumber(stack_id)) do + local sym, offset = ksym:resolve(addr) + if args.offset then + print("\t%-16p %s+0x%x" % {addr, sym, tonumber(offset)}) + else + print("\t%-16p %s" % {addr, sym}) + end + end + end + print() + end +end