/* * Copyright 2016, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Simple program to try running an APF program against a packet. #include #include #include #include #include #include #include #include #include #include #include "disassembler.h" #include "apf_interpreter.h" #include "v7/apf_interpreter.h" #include "v7/test_buf_allocator.h" #define __unused __attribute__((unused)) // The following list must be in sync with // https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java;l=125 static const char* counter_name [] = { "RESERVED_OOB", "TOTAL_PACKETS", "PASSED_ARP", "PASSED_DHCP", "PASSED_IPV4", "PASSED_IPV6_NON_ICMP", "PASSED_IPV4_UNICAST", "PASSED_IPV6_ICMP", "PASSED_IPV6_UNICAST_NON_ICMP", "PASSED_ARP_NON_IPV4", "PASSED_ARP_UNKNOWN", "PASSED_ARP_UNICAST_REPLY", "PASSED_NON_IP_UNICAST", "PASSED_MDNS", "DROPPED_ETH_BROADCAST", "DROPPED_RA", "DROPPED_GARP_REPLY", "DROPPED_ARP_OTHER_HOST", "DROPPED_IPV4_L2_BROADCAST", "DROPPED_IPV4_BROADCAST_ADDR", "DROPPED_IPV4_BROADCAST_NET", "DROPPED_IPV4_MULTICAST", "DROPPED_IPV6_ROUTER_SOLICITATION", "DROPPED_IPV6_MULTICAST_NA", "DROPPED_IPV6_MULTICAST", "DROPPED_IPV6_MULTICAST_PING", "DROPPED_IPV6_NON_ICMP_MULTICAST", "DROPPED_802_3_FRAME", "DROPPED_ETHERTYPE_BLACKLISTED", "DROPPED_ARP_REPLY_SPA_NO_HOST", "DROPPED_IPV4_KEEPALIVE_ACK", "DROPPED_IPV6_KEEPALIVE_ACK", "DROPPED_IPV4_NATT_KEEPALIVE", "DROPPED_MDNS" }; enum { OPT_PROGRAM, OPT_PACKET, OPT_PCAP, OPT_DATA, OPT_AGE, OPT_TRACE, OPT_V6, }; const struct option long_options[] = {{"program", 1, NULL, OPT_PROGRAM}, {"packet", 1, NULL, OPT_PACKET}, {"pcap", 1, NULL, OPT_PCAP}, {"data", 1, NULL, OPT_DATA}, {"age", 1, NULL, OPT_AGE}, {"trace", 0, NULL, OPT_TRACE}, {"v6", 0, NULL, OPT_V6}, {"help", 0, NULL, 'h'}, {"cnt", 0, NULL, 'c'}, {NULL, 0, NULL, 0}}; const int COUNTER_SIZE = 4; // Parses hex in "input". Allocates and fills "*output" with parsed bytes. // Returns length in bytes of "*output". size_t parse_hex(const char* input, uint8_t** output) { int length = strlen(input); if (length & 1) { fprintf(stderr, "Argument not even number of characters: %s\n", input); exit(1); } length >>= 1; *output = malloc(length); if (*output == NULL) { fprintf(stderr, "Out of memory, tried to allocate %d\n", length); exit(1); } for (int i = 0; i < length; i++) { char byte[3] = { input[i*2], input[i*2+1], 0 }; char* end_ptr; (*output)[i] = strtol(byte, &end_ptr, 16); if (end_ptr != byte + 2) { fprintf(stderr, "Failed to parse hex %s\n", byte); exit(1); } } return length; } void print_hex(const uint8_t* input, int len) { for (int i = 0; i < len; ++i) { printf("%02x", input[i]); } } uint32_t get_counter_value(const uint8_t* data, int data_len, int neg_offset) { if (neg_offset > -COUNTER_SIZE || neg_offset + data_len < 0) { return 0; } uint32_t value = 0; for (int i = 0; i < 4; ++i) { value = value << 8 | data[data_len + neg_offset]; neg_offset++; } return value; } void print_counter(const uint8_t* data, int data_len) { int counter_len = sizeof(counter_name) / sizeof(counter_name[0]); for (int i = 0; i < counter_len; ++i) { uint32_t value = get_counter_value(data, data_len, -COUNTER_SIZE * i); if (value != 0) { printf("%s : %d \n", counter_name[i], value); } } } int tracing_enabled = 0; void maybe_print_tracing_header() { if (!tracing_enabled) return; printf(" R0 R1 PC Instruction\n"); printf("-------------------------------------------------\n"); } void print_transmitted_packet() { printf("transmitted packet: "); print_hex(apf_test_buffer, (int) apf_test_tx_packet_len); printf("\n"); } // Process packet through APF filter void packet_handler(int use_apf_v6_interpreter, uint8_t* program, uint32_t program_len, uint32_t ram_len, const char* pkt, uint32_t filter_age) { uint8_t* packet; uint32_t packet_len = parse_hex(pkt, &packet); maybe_print_tracing_header(); int ret; if (use_apf_v6_interpreter) { ret = apf_run(NULL, (uint32_t*)program, program_len, ram_len, packet, packet_len, filter_age); } else { ret = accept_packet(program, program_len, ram_len, packet, packet_len, filter_age); } printf("Packet %sed\n", ret ? "pass" : "dropp"); free(packet); } void apf_trace_hook(uint32_t pc, const uint32_t* regs, const uint8_t* program, uint32_t program_len, const uint8_t* packet __unused, uint32_t packet_len __unused, const uint32_t* memory __unused, uint32_t memory_len __unused) { if (!tracing_enabled) return; printf("%8" PRIx32 " %8" PRIx32 " ", regs[0], regs[1]); printf("%s\n", apf_disassemble(program, program_len, &pc)); } // Process pcap file through APF filter and generate output files void file_handler(int use_apf_v6_interpreter, uint8_t* program, uint32_t program_len, uint32_t ram_len, const char* filename, uint32_t filter_age) { char errbuf[PCAP_ERRBUF_SIZE]; pcap_t *pcap; struct pcap_pkthdr apf_header; const uint8_t* apf_packet; pcap_dumper_t *passed_dumper, *dropped_dumper; const char passed_file[] = "passed.pcap"; const char dropped_file[] = "dropped.pcap"; int pass = 0; int drop = 0; pcap = pcap_open_offline(filename, errbuf); if (pcap == NULL) { printf("Open pcap file failed.\n"); exit(1); } passed_dumper = pcap_dump_open(pcap, passed_file); dropped_dumper = pcap_dump_open(pcap, dropped_file); if (!passed_dumper || !dropped_dumper) { printf("pcap_dump_open(): open output file failed.\n"); pcap_close(pcap); exit(1); } while ((apf_packet = pcap_next(pcap, &apf_header)) != NULL) { maybe_print_tracing_header(); int result; if (use_apf_v6_interpreter) { result = apf_run(NULL, (uint32_t*)program, program_len, ram_len, apf_packet, apf_header.len, filter_age); } else { result = accept_packet(program, program_len, ram_len, apf_packet, apf_header.len, filter_age); } if (!result){ drop++; pcap_dump((u_char*)dropped_dumper, &apf_header, apf_packet); } else { pass++; pcap_dump((u_char*)passed_dumper, &apf_header, apf_packet); } } printf("%d packets dropped\n", drop); printf("%d packets passed\n", pass); pcap_dump_close(passed_dumper); pcap_dump_close(dropped_dumper); pcap_close(pcap); } void print_usage(char* cmd) { fprintf(stderr, "Usage: %s --program --pcap |--packet " "[--data ] [--age ] [--trace]\n" " --program APF program, in hex.\n" " --pcap Pcap file to run through program.\n" " --packet Packet to run through program.\n" " --data Data memory contents, in hex.\n" " --age Age of program in seconds (default: 0).\n" " --trace Enable APF interpreter debug tracing\n" " --v6 Use APF v6\n" " -c, --cnt Print the APF counters\n" " -h, --help Show this message.\n", basename(cmd)); } int main(int argc, char* argv[]) { uint8_t* program = NULL; uint32_t program_len; const char* filename = NULL; char* packet = NULL; uint8_t* data = NULL; uint32_t data_len = 0; uint32_t filter_age = 0; int print_counter_enabled = 0; int use_apf_v6_interpreter = 0; int opt; char *endptr; while ((opt = getopt_long_only(argc, argv, "ch", long_options, NULL)) != -1) { switch (opt) { case OPT_PROGRAM: program_len = parse_hex(optarg, &program); break; case OPT_PACKET: if (!program) { printf(" requires first\n\'%s -h or --help\' " "for more information\n", basename(argv[0])); exit(1); } if (filename) { printf("Cannot use with \n\'%s -h or --help\' " "for more information\n", basename(argv[0])); exit(1); } packet = optarg; break; case OPT_PCAP: if (!program) { printf(" requires first\n\'%s -h or --help\' " "for more information\n", basename(argv[0])); exit(1); } if (packet) { printf("Cannot use with \n\'%s -h or --help\' " "for more information\n", basename(argv[0])); exit(1); } filename = optarg; break; case OPT_DATA: data_len = parse_hex(optarg, &data); break; case OPT_AGE: errno = 0; filter_age = strtoul(optarg, &endptr, 10); if ((errno == ERANGE && filter_age == UINT32_MAX) || (errno != 0 && filter_age == 0)) { perror("Error on age option: strtoul"); exit(1); } if (endptr == optarg) { printf("No digit found in age.\n"); exit(1); } break; case OPT_TRACE: tracing_enabled = 1; break; case OPT_V6: use_apf_v6_interpreter = 1; break; case 'h': print_usage(argv[0]); exit(0); break; case 'c': print_counter_enabled = 1; break; default: print_usage(argv[0]); exit(1); break; } } if (!program) { printf("Must have APF program in option.\n"); exit(1); } if (!filename && !packet) { printf("Missing file or packet after program.\n"); exit(1); } // Combine the program and data into the unified APF buffer. if (data) { program = realloc(program, program_len + data_len); memcpy(program + program_len, data, data_len); free(data); } uint32_t ram_len = program_len + data_len; if (filename) file_handler(use_apf_v6_interpreter, program, program_len, ram_len, filename, filter_age); else packet_handler(use_apf_v6_interpreter, program, program_len, ram_len, packet, filter_age); if (data_len) { printf("Data: "); print_hex(program + program_len, data_len); printf("\n"); if (print_counter_enabled) { printf("APF packet counters: \n"); print_counter(program + program_len, data_len); } } if (use_apf_v6_interpreter && apf_test_tx_packet_len != 0) { print_transmitted_packet(); } free(program); return 0; }