1 /*
2  * Copyright 2016, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 // Simple program to try running an APF program against a packet.
18 
19 #include <errno.h>
20 #include <getopt.h>
21 #include <inttypes.h>
22 #include <libgen.h>
23 #include <limits.h>
24 #include <pcap.h>
25 #include <stdint.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 
30 #include "disassembler.h"
31 #include "apf_interpreter.h"
32 #include "v7/apf_interpreter.h"
33 #include "v7/test_buf_allocator.h"
34 
35 #define __unused __attribute__((unused))
36 
37 // The following list must be in sync with
38 // https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/NetworkStack/src/android/net/apf/ApfFilter.java;l=125
39 static const char* counter_name [] = {
40     "RESERVED_OOB",
41     "TOTAL_PACKETS",
42     "PASSED_ARP",
43     "PASSED_DHCP",
44     "PASSED_IPV4",
45     "PASSED_IPV6_NON_ICMP",
46     "PASSED_IPV4_UNICAST",
47     "PASSED_IPV6_ICMP",
48     "PASSED_IPV6_UNICAST_NON_ICMP",
49     "PASSED_ARP_NON_IPV4",
50     "PASSED_ARP_UNKNOWN",
51     "PASSED_ARP_UNICAST_REPLY",
52     "PASSED_NON_IP_UNICAST",
53     "PASSED_MDNS",
54     "DROPPED_ETH_BROADCAST",
55     "DROPPED_RA",
56     "DROPPED_GARP_REPLY",
57     "DROPPED_ARP_OTHER_HOST",
58     "DROPPED_IPV4_L2_BROADCAST",
59     "DROPPED_IPV4_BROADCAST_ADDR",
60     "DROPPED_IPV4_BROADCAST_NET",
61     "DROPPED_IPV4_MULTICAST",
62     "DROPPED_IPV6_ROUTER_SOLICITATION",
63     "DROPPED_IPV6_MULTICAST_NA",
64     "DROPPED_IPV6_MULTICAST",
65     "DROPPED_IPV6_MULTICAST_PING",
66     "DROPPED_IPV6_NON_ICMP_MULTICAST",
67     "DROPPED_802_3_FRAME",
68     "DROPPED_ETHERTYPE_BLACKLISTED",
69     "DROPPED_ARP_REPLY_SPA_NO_HOST",
70     "DROPPED_IPV4_KEEPALIVE_ACK",
71     "DROPPED_IPV6_KEEPALIVE_ACK",
72     "DROPPED_IPV4_NATT_KEEPALIVE",
73     "DROPPED_MDNS"
74 };
75 
76 enum {
77     OPT_PROGRAM,
78     OPT_PACKET,
79     OPT_PCAP,
80     OPT_DATA,
81     OPT_AGE,
82     OPT_TRACE,
83     OPT_V6,
84 };
85 
86 const struct option long_options[] = {{"program", 1, NULL, OPT_PROGRAM},
87                                       {"packet", 1, NULL, OPT_PACKET},
88                                       {"pcap", 1, NULL, OPT_PCAP},
89                                       {"data", 1, NULL, OPT_DATA},
90                                       {"age", 1, NULL, OPT_AGE},
91                                       {"trace", 0, NULL, OPT_TRACE},
92                                       {"v6", 0, NULL, OPT_V6},
93                                       {"help", 0, NULL, 'h'},
94                                       {"cnt", 0, NULL, 'c'},
95                                       {NULL, 0, NULL, 0}};
96 
97 const int COUNTER_SIZE = 4;
98 
99 // Parses hex in "input". Allocates and fills "*output" with parsed bytes.
100 // Returns length in bytes of "*output".
parse_hex(const char * input,uint8_t ** output)101 size_t parse_hex(const char* input, uint8_t** output) {
102     int length = strlen(input);
103     if (length & 1) {
104         fprintf(stderr, "Argument not even number of characters: %s\n", input);
105         exit(1);
106     }
107     length >>= 1;
108     *output = malloc(length);
109     if (*output == NULL) {
110         fprintf(stderr, "Out of memory, tried to allocate %d\n", length);
111         exit(1);
112     }
113     for (int i = 0; i < length; i++) {
114         char byte[3] = { input[i*2], input[i*2+1], 0 };
115         char* end_ptr;
116         (*output)[i] = strtol(byte, &end_ptr, 16);
117         if (end_ptr != byte + 2) {
118             fprintf(stderr, "Failed to parse hex %s\n", byte);
119             exit(1);
120         }
121     }
122     return length;
123 }
124 
print_hex(const uint8_t * input,int len)125 void print_hex(const uint8_t* input, int len) {
126     for (int i = 0; i < len; ++i) {
127         printf("%02x", input[i]);
128     }
129 }
130 
get_counter_value(const uint8_t * data,int data_len,int neg_offset)131 uint32_t get_counter_value(const uint8_t* data, int data_len, int neg_offset) {
132     if (neg_offset > -COUNTER_SIZE || neg_offset + data_len < 0) {
133         return 0;
134     }
135     uint32_t value = 0;
136     for (int i = 0; i < 4; ++i) {
137         value = value << 8 | data[data_len + neg_offset];
138         neg_offset++;
139     }
140     return value;
141 }
142 
print_counter(const uint8_t * data,int data_len)143 void print_counter(const uint8_t* data, int data_len) {
144     int counter_len = sizeof(counter_name) / sizeof(counter_name[0]);
145     for (int i = 0; i < counter_len; ++i) {
146         uint32_t value = get_counter_value(data, data_len, -COUNTER_SIZE * i);
147         if (value != 0) {
148             printf("%s : %d \n", counter_name[i], value);
149         }
150     }
151 }
152 
153 int tracing_enabled = 0;
154 
maybe_print_tracing_header()155 void maybe_print_tracing_header() {
156     if (!tracing_enabled) return;
157 
158     printf("      R0       R1       PC  Instruction\n");
159     printf("-------------------------------------------------\n");
160 
161 }
162 
print_all_transmitted_packets()163 void print_all_transmitted_packets() {
164     printf("transmitted packet: \n");
165     packet_buffer* current = head;
166     while (current) {
167         printf("\t");
168         print_hex(current->data, (int) current->len);
169         printf("\n");
170         current = current->next;
171     }
172 }
173 
174 // Process packet through APF filter
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)175 void packet_handler(int use_apf_v6_interpreter, uint8_t* program,
176                     uint32_t program_len, uint32_t ram_len, const char* pkt, uint32_t filter_age) {
177     uint8_t* packet;
178     uint32_t packet_len = parse_hex(pkt, &packet);
179 
180     maybe_print_tracing_header();
181 
182     int ret;
183     if (use_apf_v6_interpreter) {
184         ret = apf_run(NULL, (uint32_t*)program, program_len, ram_len, packet, packet_len,
185                             filter_age);
186     } else {
187         ret = accept_packet(program, program_len, ram_len, packet, packet_len,
188                         filter_age);
189     }
190     printf("Packet %sed\n", ret ? "pass" : "dropp");
191 
192     free(packet);
193 }
194 
195 
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)196 void apf_trace_hook(uint32_t pc, const uint32_t* regs, const uint8_t* program, uint32_t program_len,
197                     const uint8_t* packet __unused, uint32_t packet_len __unused,
198                     const uint32_t* memory __unused, uint32_t memory_len __unused) {
199     if (!tracing_enabled) return;
200 
201     printf("%8" PRIx32 " %8" PRIx32 " ", regs[0], regs[1]);
202     printf("%s\n", apf_disassemble(program, program_len, &pc));
203 }
204 
205 // Process pcap file through APF filter and generate output files
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)206 void file_handler(int use_apf_v6_interpreter, uint8_t* program,
207                   uint32_t program_len, uint32_t ram_len, const char* filename,
208                   uint32_t filter_age) {
209     char errbuf[PCAP_ERRBUF_SIZE];
210     pcap_t *pcap;
211     struct pcap_pkthdr apf_header;
212     const uint8_t* apf_packet;
213     pcap_dumper_t *passed_dumper, *dropped_dumper;
214     const char passed_file[] = "passed.pcap";
215     const char dropped_file[] = "dropped.pcap";
216     int pass = 0;
217     int drop = 0;
218 
219     pcap = pcap_open_offline(filename, errbuf);
220     if (pcap == NULL) {
221         printf("Open pcap file failed.\n");
222         exit(1);
223     }
224 
225     passed_dumper = pcap_dump_open(pcap, passed_file);
226     dropped_dumper = pcap_dump_open(pcap, dropped_file);
227 
228     if (!passed_dumper || !dropped_dumper) {
229         printf("pcap_dump_open(): open output file failed.\n");
230         pcap_close(pcap);
231         exit(1);
232     }
233 
234     while ((apf_packet = pcap_next(pcap, &apf_header)) != NULL) {
235         maybe_print_tracing_header();
236 
237         int result;
238         if (use_apf_v6_interpreter) {
239             result = apf_run(NULL, (uint32_t*)program, program_len, ram_len, apf_packet,
240                              apf_header.len, filter_age);
241         } else {
242             result = accept_packet(program, program_len, ram_len, apf_packet,
243                                    apf_header.len, filter_age);
244         }
245 
246         if (!result){
247             drop++;
248             pcap_dump((u_char*)dropped_dumper, &apf_header, apf_packet);
249         } else {
250             pass++;
251             pcap_dump((u_char*)passed_dumper, &apf_header, apf_packet);
252         }
253     }
254 
255     printf("%d packets dropped\n", drop);
256     printf("%d packets passed\n", pass);
257     pcap_dump_close(passed_dumper);
258     pcap_dump_close(dropped_dumper);
259     pcap_close(pcap);
260 }
261 
print_usage(char * cmd)262 void print_usage(char* cmd) {
263     fprintf(stderr,
264             "Usage: %s --program <program> --pcap <file>|--packet <packet> "
265             "[--data <content>] [--age <number>] [--trace]\n"
266             "  --program    APF program, in hex.\n"
267             "  --pcap       Pcap file to run through program.\n"
268             "  --packet     Packet to run through program.\n"
269             "  --data       Data memory contents, in hex.\n"
270             "  --age        Age of program in seconds (default: 0).\n"
271             "  --trace      Enable APF interpreter debug tracing\n"
272             "  --v6         Use APF v6\n"
273             "  -c, --cnt    Print the APF counters\n"
274             "  -h, --help   Show this message.\n",
275             basename(cmd));
276 }
277 
main(int argc,char * argv[])278 int main(int argc, char* argv[]) {
279     uint8_t* program = NULL;
280     uint32_t program_len;
281     const char* filename = NULL;
282     char* packet = NULL;
283     uint8_t* data = NULL;
284     uint32_t data_len = 0;
285     uint32_t filter_age = 0;
286     int print_counter_enabled = 0;
287     int use_apf_v6_interpreter = 0;
288 
289     int opt;
290     char *endptr;
291 
292     while ((opt = getopt_long_only(argc, argv, "ch", long_options, NULL)) != -1) {
293         switch (opt) {
294             case OPT_PROGRAM:
295                 program_len = parse_hex(optarg, &program);
296                 break;
297             case OPT_PACKET:
298                 if (!program) {
299                     printf("<packet> requires <program> first\n\'%s -h or --help\' "
300                            "for more information\n", basename(argv[0]));
301                     exit(1);
302                 }
303                 if (filename) {
304                     printf("Cannot use <file> with <packet> \n\'%s -h or --help\' "
305                            "for more information\n", basename(argv[0]));
306 
307                     exit(1);
308                 }
309                 packet = optarg;
310                 break;
311             case OPT_PCAP:
312                 if (!program) {
313                     printf("<file> requires <program> first\n\'%s -h or --help\' "
314                            "for more information\n", basename(argv[0]));
315 
316                     exit(1);
317                 }
318                 if (packet) {
319                     printf("Cannot use <packet> with <file>\n\'%s -h or --help\' "
320                            "for more information\n", basename(argv[0]));
321 
322                     exit(1);
323                 }
324                 filename = optarg;
325                 break;
326             case OPT_DATA:
327                 data_len = parse_hex(optarg, &data);
328                 break;
329             case OPT_AGE:
330                 errno = 0;
331                 filter_age = strtoul(optarg, &endptr, 10);
332                 if ((errno == ERANGE && filter_age == UINT32_MAX) ||
333                     (errno != 0 && filter_age == 0)) {
334                     perror("Error on age option: strtoul");
335                     exit(1);
336                 }
337                 if (endptr == optarg) {
338                     printf("No digit found in age.\n");
339                     exit(1);
340                 }
341                 break;
342             case OPT_TRACE:
343                 tracing_enabled = 1;
344                 break;
345             case OPT_V6:
346                 use_apf_v6_interpreter = 1;
347                 break;
348             case 'h':
349                 print_usage(argv[0]);
350                 exit(0);
351                 break;
352             case 'c':
353                 print_counter_enabled = 1;
354                 break;
355             default:
356                 print_usage(argv[0]);
357                 exit(1);
358                 break;
359         }
360     }
361 
362     if (!program) {
363         printf("Must have APF program in option.\n");
364         exit(1);
365     }
366 
367     if (!filename && !packet) {
368         printf("Missing file or packet after program.\n");
369         exit(1);
370     }
371 
372     // Combine the program and data into the unified APF buffer.
373     uint32_t ram_len = program_len + data_len;
374     if (use_apf_v6_interpreter) {
375        ram_len += 3;
376        ram_len &= ~3;
377        if (data_len < 20) ram_len += 20;
378     }
379 
380     if (data) {
381         program = realloc(program, ram_len);
382         memcpy(program + ram_len - data_len, data, data_len);
383         free(data);
384     }
385 
386     if (filename)
387         file_handler(use_apf_v6_interpreter, program, program_len, ram_len,
388                      filename, filter_age);
389     else
390         packet_handler(use_apf_v6_interpreter, program, program_len, ram_len,
391                        packet, filter_age);
392 
393     if (data_len) {
394         printf("Data: ");
395         print_hex(program + ram_len - data_len, data_len);
396         printf("\n");
397         if (print_counter_enabled) {
398           printf("APF packet counters: \n");
399           print_counter(program + ram_len - data_len, data_len);
400         }
401     }
402 
403     if (use_apf_v6_interpreter && head != NULL) {
404         print_all_transmitted_packets();
405     }
406 
407     free(program);
408     return 0;
409 }
410