]>
Commit | Line | Data |
---|---|---|
342c141e | 1 | /* |
755a8d69 | 2 | * Copyright (c) 2013-2016 Apple Inc. All rights reserved. |
342c141e A |
3 | * |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. The rights granted to you under the License | |
10 | * may not be used to create, or enable the creation or redistribution of, | |
11 | * unlawful or unlicensed copies of an Apple operating system, or to | |
12 | * circumvent, violate, or enable the circumvention or violation of, any | |
13 | * terms of an Apple operating system software license agreement. | |
14 | * | |
15 | * Please obtain a copy of the License at | |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. | |
17 | * | |
18 | * The Original Code and all software distributed under the License are | |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
23 | * Please see the License for the specific language governing rights and | |
24 | * limitations under the License. | |
25 | * | |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ | |
27 | */ | |
28 | ||
29 | #include <sys/socket.h> | |
30 | #include <sys/errno.h> | |
31 | #include <sys/event.h> | |
32 | #include <sys/time.h> | |
33 | #include <sys/sys_domain.h> | |
34 | #include <sys/ioctl.h> | |
35 | #include <sys/kern_control.h> | |
36 | #include <sys/queue.h> | |
37 | #include <net/content_filter.h> | |
38 | #include <netinet/in.h> | |
39 | #include <stdio.h> | |
40 | #include <err.h> | |
41 | #include <string.h> | |
42 | #include <stdlib.h> | |
43 | #include <fcntl.h> | |
44 | #include <unistd.h> | |
45 | #include <ctype.h> | |
46 | #include <sysexits.h> | |
47 | ||
48 | extern void print_filter_list(void); | |
49 | extern void print_socket_list(void); | |
50 | extern void print_cfil_stats(void); | |
51 | ||
52 | #define MAX_BUFFER (65536 + 1024) | |
53 | ||
54 | #define MAXHEXDUMPCOL 16 | |
55 | ||
56 | ||
57 | enum { | |
58 | MODE_NONE = 0, | |
59 | MODE_INTERACTIVE = 0x01, | |
60 | MODE_PEEK = 0x02, | |
61 | MODE_PASS = 0x04, | |
62 | MODE_DELAY = 0x08 | |
63 | }; | |
64 | int mode = MODE_NONE; | |
65 | ||
66 | unsigned long delay_ms = 0; | |
67 | struct timeval delay_tv = { 0, 0 }; | |
68 | long verbosity = 0; | |
69 | uint32_t necp_control_unit = 0; | |
70 | unsigned long auto_start = 0; | |
71 | uint64_t peek_inc = 0; | |
72 | uint64_t pass_offset = 0; | |
73 | struct timeval now, deadline; | |
74 | int sf = -1; | |
75 | int pass_loopback = 0; | |
76 | uint32_t random_drop = 0; | |
77 | uint32_t event_total = 0; | |
78 | uint32_t event_dropped = 0; | |
79 | ||
80 | uint64_t default_in_pass = 0; | |
81 | uint64_t default_in_peek = 0; | |
82 | uint64_t default_out_pass = 0; | |
83 | uint64_t default_out_peek = 0; | |
84 | ||
85 | unsigned long max_dump_len = 32; | |
86 | ||
87 | TAILQ_HEAD(sock_info_head, sock_info) sock_info_head = TAILQ_HEAD_INITIALIZER(sock_info_head); | |
88 | ||
89 | ||
90 | struct sock_info { | |
91 | TAILQ_ENTRY(sock_info) si_link; | |
92 | cfil_sock_id_t si_sock_id; | |
93 | struct timeval si_deadline; | |
94 | uint64_t si_in_pass; | |
95 | uint64_t si_in_peek; | |
96 | uint64_t si_out_pass; | |
97 | uint64_t si_out_peek; | |
98 | }; | |
99 | ||
100 | static void | |
101 | HexDump(void *data, size_t len) | |
102 | { | |
103 | size_t i, j, k; | |
104 | unsigned char *ptr = (unsigned char *)data; | |
105 | unsigned char buf[32 + 3 * MAXHEXDUMPCOL + 2 + MAXHEXDUMPCOL + 1]; | |
106 | ||
107 | for (i = 0; i < len; i += MAXHEXDUMPCOL) { | |
108 | k = snprintf((char *)buf, sizeof(buf), "\t0x%04lx: ", i); | |
109 | for (j = i; j < i + MAXHEXDUMPCOL; j++) { | |
110 | if (j < len) { | |
111 | unsigned char msnbl = ptr[j] >> 4; | |
112 | unsigned char lsnbl = ptr[j] & 0x0f; | |
113 | ||
114 | buf[k++] = msnbl < 10 ? msnbl + '0' : msnbl + 'a' - 10; | |
115 | buf[k++] = lsnbl < 10 ? lsnbl + '0' : lsnbl + 'a' - 10; | |
116 | } else { | |
117 | buf[k++] = ' '; | |
118 | buf[k++] = ' '; | |
119 | } | |
120 | if ((j % 2) == 1) | |
121 | buf[k++] = ' '; | |
122 | if ((j % MAXHEXDUMPCOL) == MAXHEXDUMPCOL - 1) | |
123 | buf[k++] = ' '; | |
124 | } | |
125 | ||
126 | buf[k++] = ' '; | |
127 | buf[k++] = ' '; | |
128 | ||
129 | for (j = i; j < i + MAXHEXDUMPCOL && j < len; j++) { | |
130 | if (isprint(ptr[j])) | |
131 | buf[k++] = ptr[j]; | |
132 | else | |
133 | buf[k++] = '.'; | |
134 | } | |
135 | buf[k] = 0; | |
136 | printf("%s\n", buf); | |
137 | } | |
138 | } | |
139 | ||
140 | void | |
141 | print_hdr(struct cfil_msg_hdr *hdr) | |
142 | { | |
143 | const char *typestr = "unknown"; | |
144 | const char *opstr = "unknown"; | |
145 | ||
146 | if (hdr->cfm_type == CFM_TYPE_EVENT) { | |
147 | typestr = "event"; | |
148 | switch (hdr->cfm_op) { | |
149 | case CFM_OP_SOCKET_ATTACHED: | |
150 | opstr = "attached"; | |
151 | break; | |
152 | case CFM_OP_SOCKET_CLOSED: | |
153 | opstr = "closed"; | |
154 | break; | |
155 | case CFM_OP_DATA_OUT: | |
156 | opstr = "dataout"; | |
157 | break; | |
158 | case CFM_OP_DATA_IN: | |
159 | opstr = "datain"; | |
160 | break; | |
161 | case CFM_OP_DISCONNECT_OUT: | |
162 | opstr = "disconnectout"; | |
163 | break; | |
164 | case CFM_OP_DISCONNECT_IN: | |
165 | opstr = "disconnectin"; | |
166 | break; | |
167 | ||
168 | default: | |
169 | break; | |
170 | } | |
171 | } else if (hdr->cfm_type == CFM_TYPE_ACTION) { | |
172 | typestr = "action"; | |
173 | switch (hdr->cfm_op) { | |
174 | case CFM_OP_DATA_UPDATE: | |
175 | opstr = "update"; | |
176 | break; | |
177 | case CFM_OP_DROP: | |
178 | opstr = "drop"; | |
179 | break; | |
180 | ||
181 | default: | |
182 | break; | |
183 | } | |
184 | ||
185 | } | |
186 | printf("%s %s len %u version %u type %u op %u sock_id 0x%llx\n", | |
187 | typestr, opstr, | |
188 | hdr->cfm_len, hdr->cfm_version, hdr->cfm_type, | |
189 | hdr->cfm_op, hdr->cfm_sock_id); | |
190 | } | |
191 | ||
192 | void | |
193 | print_data_req(struct cfil_msg_data_event *data_req) | |
194 | { | |
195 | size_t datalen; | |
196 | void *databuf; | |
197 | ||
198 | if (verbosity <= 0) | |
199 | return; | |
200 | ||
201 | print_hdr(&data_req->cfd_msghdr); | |
202 | ||
203 | printf(" start %llu end %llu\n", | |
204 | data_req->cfd_start_offset, data_req->cfd_end_offset); | |
205 | ||
206 | datalen = (size_t)(data_req->cfd_end_offset - data_req->cfd_start_offset); | |
207 | ||
208 | databuf = (void *)(data_req + 1); | |
209 | ||
210 | if (verbosity > 1) | |
211 | HexDump(databuf, MIN(datalen, max_dump_len)); | |
212 | } | |
213 | ||
214 | void | |
215 | print_action_msg(struct cfil_msg_action *action) | |
216 | { | |
217 | if (verbosity <= 0) | |
218 | return; | |
219 | ||
220 | print_hdr(&action->cfa_msghdr); | |
221 | ||
222 | if (action->cfa_msghdr.cfm_op == CFM_OP_DATA_UPDATE) | |
223 | printf(" out pass %llu peek %llu in pass %llu peek %llu\n", | |
224 | action->cfa_out_pass_offset, action->cfa_out_peek_offset, | |
225 | action->cfa_in_pass_offset, action->cfa_in_peek_offset); | |
226 | } | |
227 | ||
228 | struct sock_info * | |
229 | find_sock_info(cfil_sock_id_t sockid) | |
230 | { | |
231 | struct sock_info *sock_info; | |
232 | ||
233 | TAILQ_FOREACH(sock_info, &sock_info_head, si_link) { | |
234 | if (sock_info->si_sock_id == sockid) | |
235 | return (sock_info); | |
236 | } | |
237 | return (NULL); | |
238 | } | |
239 | ||
240 | struct sock_info * | |
241 | add_sock_info(cfil_sock_id_t sockid) | |
242 | { | |
243 | struct sock_info *sock_info; | |
244 | ||
245 | if (find_sock_info(sockid) != NULL) | |
246 | return (NULL); | |
247 | ||
248 | sock_info = calloc(1, sizeof(struct sock_info)); | |
249 | if (sock_info == NULL) | |
250 | err(EX_OSERR, "calloc()"); | |
251 | sock_info->si_sock_id = sockid; | |
252 | TAILQ_INSERT_TAIL(&sock_info_head, sock_info, si_link); | |
253 | ||
254 | return (sock_info); | |
255 | } | |
256 | ||
257 | void | |
258 | remove_sock_info(cfil_sock_id_t sockid) | |
259 | { | |
260 | struct sock_info *sock_info = find_sock_info(sockid); | |
261 | ||
262 | if (sock_info != NULL) { | |
263 | TAILQ_REMOVE(&sock_info_head, sock_info, si_link); | |
264 | free(sock_info); | |
265 | } | |
266 | } | |
267 | ||
268 | /* return 0 if timer is already set */ | |
269 | int | |
270 | set_sock_info_deadline(struct sock_info *sock_info) | |
271 | { | |
272 | if (timerisset(&sock_info->si_deadline)) | |
273 | return (0); | |
274 | ||
275 | timeradd(&now, &sock_info->si_deadline, &sock_info->si_deadline); | |
276 | ||
277 | if (!timerisset(&deadline)) { | |
278 | timeradd(&now, &delay_tv, &deadline); | |
279 | } | |
280 | ||
281 | return (1); | |
282 | } | |
283 | ||
284 | void | |
285 | send_action_message(uint32_t op, struct sock_info *sock_info, int nodelay) | |
286 | { | |
287 | struct cfil_msg_action action; | |
288 | ||
289 | if (!nodelay && delay_ms) { | |
290 | set_sock_info_deadline(sock_info); | |
291 | return; | |
292 | } | |
293 | bzero(&action, sizeof(struct cfil_msg_action)); | |
294 | action.cfa_msghdr.cfm_len = sizeof(struct cfil_msg_action); | |
295 | action.cfa_msghdr.cfm_version = CFM_VERSION_CURRENT; | |
296 | action.cfa_msghdr.cfm_type = CFM_TYPE_ACTION; | |
297 | action.cfa_msghdr.cfm_op = op; | |
298 | action.cfa_msghdr.cfm_sock_id = sock_info->si_sock_id; | |
299 | switch (op) { | |
300 | case CFM_OP_DATA_UPDATE: | |
301 | action.cfa_out_pass_offset = sock_info->si_out_pass; | |
302 | action.cfa_out_peek_offset = sock_info->si_out_peek; | |
303 | action.cfa_in_pass_offset = sock_info->si_in_pass; | |
304 | action.cfa_in_peek_offset = sock_info->si_in_peek; | |
305 | break; | |
306 | ||
307 | default: | |
308 | break; | |
309 | } | |
310 | ||
311 | if (verbosity > -1) | |
312 | print_action_msg(&action); | |
313 | ||
314 | if (send(sf, &action, sizeof(struct cfil_msg_action), 0) == -1) | |
315 | warn("send()"); | |
316 | ||
317 | timerclear(&sock_info->si_deadline); | |
318 | } | |
319 | ||
320 | void | |
321 | process_delayed_actions() | |
322 | { | |
323 | struct sock_info *sock_info; | |
324 | ||
325 | TAILQ_FOREACH(sock_info, &sock_info_head, si_link) { | |
326 | if (timerisset(&sock_info->si_deadline) && | |
327 | timercmp(&sock_info->si_deadline, &now, >=)) | |
328 | send_action_message(CFM_OP_DATA_UPDATE, sock_info, 1); | |
329 | } | |
330 | } | |
331 | ||
332 | int | |
333 | set_non_blocking(int fd) | |
334 | { | |
335 | int flags; | |
336 | ||
337 | flags = fcntl(fd, F_GETFL); | |
338 | if (flags == -1) { | |
339 | warn("fcntl(F_GETFL)"); | |
340 | return (-1); | |
341 | } | |
342 | flags |= O_NONBLOCK; | |
343 | if (fcntl(fd, F_SETFL, flags) == -1) { | |
344 | warn("fcntl(F_SETFL)"); | |
345 | return (-1); | |
346 | } | |
347 | return (0); | |
348 | } | |
349 | ||
350 | int | |
351 | offset_from_str(const char *str, uint64_t *ret_val) | |
352 | { | |
353 | char *endptr; | |
354 | uint64_t offset; | |
355 | int success = 1; | |
356 | ||
357 | if (strcasecmp(str, "max") == 0 || strcasecmp(str, "all") == 0) | |
358 | offset = CFM_MAX_OFFSET; | |
359 | else { | |
360 | offset = strtoull(str, &endptr, 0); | |
361 | if (*str == '\0' || *endptr != '\0') | |
362 | success = 0; | |
363 | } | |
364 | if (success) | |
365 | *ret_val = offset; | |
366 | return (success); | |
367 | } | |
368 | ||
369 | #define IN6_IS_ADDR_V4MAPPED_LOOPBACK(a) \ | |
370 | ((*(const __uint32_t *)(const void *)(&(a)->s6_addr[0]) == 0) && \ | |
371 | (*(const __uint32_t *)(const void *)(&(a)->s6_addr[4]) == 0) && \ | |
372 | (*(const __uint32_t *)(const void *)(&(a)->s6_addr[8]) == ntohl(0x0000ffff)) && \ | |
373 | (*(const __uint32_t *)(const void *)(&(a)->s6_addr[12]) == ntohl(INADDR_LOOPBACK))) | |
374 | ||
375 | ||
376 | int | |
377 | is_loopback(struct cfil_msg_data_event *data_req) | |
378 | { | |
379 | if (data_req->cfc_dst.sa.sa_family == AF_INET && | |
380 | ntohl(data_req->cfc_dst.sin.sin_addr.s_addr) == INADDR_LOOPBACK) | |
381 | return (1); | |
382 | if (data_req->cfc_dst.sa.sa_family == AF_INET6 && | |
383 | IN6_IS_ADDR_LOOPBACK(&data_req->cfc_dst.sin6.sin6_addr)) | |
384 | return (1); | |
385 | if (data_req->cfc_dst.sa.sa_family == AF_INET6 && | |
386 | IN6_IS_ADDR_V4MAPPED_LOOPBACK(&data_req->cfc_dst.sin6.sin6_addr)) | |
387 | return (1); | |
388 | ||
389 | if (data_req->cfc_src.sa.sa_family == AF_INET && | |
390 | ntohl(data_req->cfc_src.sin.sin_addr.s_addr) == INADDR_LOOPBACK) | |
391 | return (1); | |
392 | if (data_req->cfc_src.sa.sa_family == AF_INET6 && | |
393 | IN6_IS_ADDR_LOOPBACK(&data_req->cfc_src.sin6.sin6_addr)) | |
394 | return (1); | |
395 | if (data_req->cfc_src.sa.sa_family == AF_INET6 && | |
396 | IN6_IS_ADDR_V4MAPPED_LOOPBACK(&data_req->cfc_src.sin6.sin6_addr)) | |
397 | return (1); | |
398 | ||
399 | return (0); | |
400 | } | |
401 | ||
402 | int | |
403 | drop(struct sock_info *sock_info) | |
404 | { | |
405 | event_total++; | |
406 | if (random_drop > 0) { | |
407 | uint32_t r = arc4random(); | |
408 | if (r <= random_drop) { | |
409 | event_dropped++; | |
410 | printf("dropping 0x%llx dropped %u total %u rate %f\n", | |
411 | sock_info->si_sock_id, | |
412 | event_dropped, event_total, | |
413 | (double)event_dropped/(double)event_total * 100); | |
414 | send_action_message(CFM_OP_DROP, sock_info, 0); | |
415 | return (1); | |
416 | } | |
417 | } | |
418 | return (0); | |
419 | } | |
420 | ||
421 | int | |
422 | doit() | |
423 | { | |
424 | struct sockaddr_ctl sac; | |
425 | struct ctl_info ctl_info; | |
426 | void *buffer = NULL; | |
427 | struct cfil_msg_hdr *hdr; | |
428 | int kq = -1; | |
429 | struct kevent kv; | |
430 | int fdin = fileno(stdin); | |
431 | char *linep = NULL; | |
432 | size_t linecap = 0; | |
433 | char *cmdptr = NULL; | |
434 | char *argptr = NULL; | |
435 | size_t cmdlen = 0; | |
436 | struct cfil_msg_action action; | |
437 | cfil_sock_id_t last_sock_id = 0; | |
438 | struct sock_info *sock_info = NULL; | |
439 | struct timeval last_time, elapsed, delta; | |
440 | struct timespec interval, *timeout = NULL; | |
441 | ||
442 | kq = kqueue(); | |
443 | if (kq == -1) | |
444 | err(1, "kqueue()"); | |
445 | ||
446 | sf = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL); | |
447 | if (sf == -1) | |
448 | err(1, "socket()"); | |
449 | ||
450 | bzero(&ctl_info, sizeof(struct ctl_info)); | |
451 | strlcpy(ctl_info.ctl_name, CONTENT_FILTER_CONTROL_NAME, sizeof(ctl_info.ctl_name)); | |
452 | if (ioctl(sf, CTLIOCGINFO, &ctl_info) == -1) | |
453 | err(1, "ioctl(CTLIOCGINFO)"); | |
454 | ||
455 | if (fcntl(sf, F_SETNOSIGPIPE, 1) == -1) | |
456 | err(1, "fcntl(F_SETNOSIGPIPE)"); | |
457 | ||
458 | bzero(&sac, sizeof(struct sockaddr_ctl)); | |
459 | sac.sc_len = sizeof(struct sockaddr_ctl); | |
460 | sac.sc_family = AF_SYSTEM; | |
461 | sac.ss_sysaddr = AF_SYS_CONTROL; | |
462 | sac.sc_id = ctl_info.ctl_id; | |
463 | ||
464 | if (connect(sf, (struct sockaddr *)&sac, sizeof(struct sockaddr_ctl)) == -1) | |
465 | err(1, "connect()"); | |
466 | ||
467 | if (set_non_blocking(sf) == -1) | |
468 | err(1, "set_non_blocking(sf)"); | |
469 | ||
470 | if (setsockopt(sf, SYSPROTO_CONTROL, CFIL_OPT_NECP_CONTROL_UNIT, | |
471 | &necp_control_unit, sizeof(uint32_t)) == -1) | |
472 | err(1, "setsockopt(CFIL_OPT_NECP_CONTROL_UNIT, %u)", necp_control_unit); | |
473 | ||
474 | bzero(&kv, sizeof(struct kevent)); | |
475 | kv.ident = sf; | |
476 | kv.filter = EVFILT_READ; | |
477 | kv.flags = EV_ADD; | |
478 | if (kevent(kq, &kv, 1, NULL, 0, NULL) == -1) | |
755a8d69 A |
479 | err(1, "kevent(sf %d)", sf); |
480 | ||
481 | /* | |
482 | * We can only read from an interactive terminal | |
483 | */ | |
484 | if (isatty(fdin)) { | |
485 | bzero(&kv, sizeof(struct kevent)); | |
486 | kv.ident = fdin; | |
487 | kv.filter = EVFILT_READ; | |
488 | kv.flags = EV_ADD; | |
489 | if (kevent(kq, &kv, 1, NULL, 0, NULL) == -1) | |
490 | err(1, "kevent(fdin %d)", fdin); | |
491 | } | |
342c141e A |
492 | |
493 | buffer = malloc(MAX_BUFFER); | |
494 | if (buffer == NULL) | |
495 | err(1, "malloc()"); | |
496 | ||
497 | gettimeofday(&now, NULL); | |
498 | ||
499 | while (1) { | |
500 | last_time = now; | |
501 | if (delay_ms && timerisset(&deadline)) { | |
502 | timersub(&deadline, &now, &delta); | |
503 | TIMEVAL_TO_TIMESPEC(&delta, &interval); | |
504 | timeout = &interval; | |
505 | } else { | |
506 | timeout = NULL; | |
507 | } | |
508 | ||
509 | if (kevent(kq, NULL, 0, &kv, 1, timeout) == -1) { | |
510 | if (errno == EINTR) | |
511 | continue; | |
512 | err(1, "kevent()"); | |
513 | } | |
514 | gettimeofday(&now, NULL); | |
515 | timersub(&now, &last_time, &elapsed); | |
516 | if (delay_ms && timerisset(&deadline)) { | |
517 | if (timercmp(&now, &deadline, >=)) { | |
518 | process_delayed_actions(); | |
519 | interval.tv_sec = 0; | |
520 | interval.tv_nsec = 0; | |
521 | } | |
522 | } | |
523 | ||
524 | if (kv.ident == sf && kv.filter == EVFILT_READ) { | |
525 | while (1) { | |
526 | ssize_t nread; | |
527 | ||
528 | nread = recv(sf, buffer, MAX_BUFFER, 0); | |
529 | if (nread == 0) { | |
530 | warnx("recv(sf) returned 0, connection closed"); | |
531 | break; | |
532 | } | |
533 | if (nread == -1) { | |
534 | if (errno == EINTR) | |
535 | continue; | |
536 | if (errno == EWOULDBLOCK) | |
537 | break; | |
538 | err(1, "recv()"); | |
539 | ||
540 | } | |
541 | if (nread < sizeof(struct cfil_msg_hdr)) | |
542 | errx(1, "too small"); | |
543 | hdr = (struct cfil_msg_hdr *)buffer; | |
544 | ||
545 | ||
546 | if (hdr->cfm_type != CFM_TYPE_EVENT) { | |
547 | warnx("not a content filter event type %u", hdr->cfm_type); | |
548 | continue; | |
549 | } | |
550 | switch (hdr->cfm_op) { | |
551 | case CFM_OP_SOCKET_ATTACHED: { | |
552 | struct cfil_msg_sock_attached *msg_attached = (struct cfil_msg_sock_attached *)hdr; | |
553 | ||
554 | if (verbosity > -2) | |
555 | print_hdr(hdr); | |
556 | if (verbosity > -1) | |
557 | printf(" fam %d type %d proto %d pid %u epid %u\n", | |
558 | msg_attached->cfs_sock_family, | |
559 | msg_attached->cfs_sock_type, | |
560 | msg_attached->cfs_sock_protocol, | |
561 | msg_attached->cfs_pid, | |
562 | msg_attached->cfs_e_pid); | |
563 | break; | |
564 | } | |
565 | case CFM_OP_SOCKET_CLOSED: | |
566 | case CFM_OP_DISCONNECT_IN: | |
567 | case CFM_OP_DISCONNECT_OUT: | |
568 | if (verbosity > -2) | |
569 | print_hdr(hdr); | |
570 | break; | |
571 | case CFM_OP_DATA_OUT: | |
572 | case CFM_OP_DATA_IN: | |
573 | if (verbosity > -3) | |
574 | print_data_req((struct cfil_msg_data_event *)hdr); | |
575 | break; | |
576 | default: | |
577 | warnx("unknown content filter event op %u", hdr->cfm_op); | |
578 | continue; | |
579 | } | |
580 | switch (hdr->cfm_op) { | |
581 | case CFM_OP_SOCKET_ATTACHED: | |
582 | sock_info = add_sock_info(hdr->cfm_sock_id); | |
583 | if (sock_info == NULL) { | |
584 | warnx("sock_id %llx already exists", hdr->cfm_sock_id); | |
585 | continue; | |
586 | } | |
587 | break; | |
588 | case CFM_OP_DATA_OUT: | |
589 | case CFM_OP_DATA_IN: | |
590 | case CFM_OP_DISCONNECT_IN: | |
591 | case CFM_OP_DISCONNECT_OUT: | |
592 | case CFM_OP_SOCKET_CLOSED: | |
593 | sock_info = find_sock_info(hdr->cfm_sock_id); | |
594 | ||
595 | if (sock_info == NULL) { | |
596 | warnx("unexpected data message, sock_info is NULL"); | |
597 | continue; | |
598 | } | |
599 | break; | |
600 | default: | |
601 | warnx("unknown content filter event op %u", hdr->cfm_op); | |
602 | continue; | |
603 | } | |
604 | ||
605 | ||
606 | switch (hdr->cfm_op) { | |
607 | case CFM_OP_SOCKET_ATTACHED: { | |
608 | if ((mode & MODE_PASS) || (mode & MODE_PEEK) || auto_start) { | |
609 | sock_info->si_out_pass = default_out_pass; | |
610 | sock_info->si_out_peek = (mode & MODE_PEEK) ? peek_inc : (mode & MODE_PASS) ? CFM_MAX_OFFSET : default_out_peek; | |
611 | sock_info->si_in_pass = default_in_pass; | |
612 | sock_info->si_in_peek = (mode & MODE_PEEK) ? peek_inc : (mode & MODE_PASS) ? CFM_MAX_OFFSET : default_in_peek; | |
613 | ||
614 | send_action_message(CFM_OP_DATA_UPDATE, sock_info, 0); | |
615 | } | |
616 | break; | |
617 | } | |
618 | case CFM_OP_SOCKET_CLOSED: { | |
619 | remove_sock_info(hdr->cfm_sock_id); | |
620 | sock_info = NULL; | |
621 | break; | |
622 | } | |
623 | case CFM_OP_DATA_OUT: | |
624 | case CFM_OP_DATA_IN: { | |
625 | struct cfil_msg_data_event *data_req = (struct cfil_msg_data_event *)hdr; | |
626 | ||
627 | if (pass_loopback && is_loopback(data_req)) { | |
628 | sock_info->si_out_pass = CFM_MAX_OFFSET; | |
629 | sock_info->si_in_pass = CFM_MAX_OFFSET; | |
630 | } else { | |
631 | if (drop(sock_info)) | |
632 | continue; | |
633 | ||
634 | if ((mode & MODE_PASS)) { | |
635 | if (data_req->cfd_msghdr.cfm_op == CFM_OP_DATA_OUT) { | |
636 | if (pass_offset == 0 || pass_offset == CFM_MAX_OFFSET) | |
637 | sock_info->si_out_pass = data_req->cfd_end_offset; | |
638 | else if (data_req->cfd_end_offset > pass_offset) { | |
639 | sock_info->si_out_pass = CFM_MAX_OFFSET; | |
640 | sock_info->si_in_pass = CFM_MAX_OFFSET; | |
641 | } | |
642 | sock_info->si_out_peek = (mode & MODE_PEEK) ? | |
643 | data_req->cfd_end_offset + peek_inc : 0; | |
644 | } else { | |
645 | if (pass_offset == 0 || pass_offset == CFM_MAX_OFFSET) | |
646 | sock_info->si_in_pass = data_req->cfd_end_offset; | |
647 | else if (data_req->cfd_end_offset > pass_offset) { | |
648 | sock_info->si_out_pass = CFM_MAX_OFFSET; | |
649 | sock_info->si_in_pass = CFM_MAX_OFFSET; | |
650 | } | |
651 | sock_info->si_in_peek = (mode & MODE_PEEK) ? | |
652 | data_req->cfd_end_offset + peek_inc : 0; | |
653 | } | |
654 | } else { | |
655 | break; | |
656 | } | |
657 | } | |
658 | send_action_message(CFM_OP_DATA_UPDATE, sock_info, 0); | |
659 | ||
660 | break; | |
661 | } | |
662 | case CFM_OP_DISCONNECT_IN: | |
663 | case CFM_OP_DISCONNECT_OUT: { | |
664 | if (drop(sock_info)) | |
665 | continue; | |
666 | ||
667 | if ((mode & MODE_PASS)) { | |
668 | sock_info->si_out_pass = CFM_MAX_OFFSET; | |
669 | sock_info->si_in_pass = CFM_MAX_OFFSET; | |
670 | ||
671 | send_action_message(CFM_OP_DATA_UPDATE, sock_info, 0); | |
672 | } | |
673 | break; | |
674 | } | |
675 | default: | |
676 | warnx("unkown message op %u", hdr->cfm_op); | |
677 | break; | |
678 | } | |
679 | if (sock_info) | |
680 | last_sock_id = sock_info->si_sock_id; | |
681 | } | |
682 | } | |
683 | if (kv.ident == fdin && kv.filter == EVFILT_READ) { | |
684 | ssize_t nread; | |
685 | uint64_t offset = 0; | |
686 | int nitems; | |
687 | int op = 0; | |
688 | ||
689 | nread = getline(&linep, &linecap, stdin); | |
690 | if (nread == -1) | |
691 | errx(1, "getline()"); | |
692 | ||
693 | if (verbosity > 2) | |
694 | printf("linecap %lu nread %lu\n", linecap, nread); | |
695 | if (nread > 0) | |
696 | linep[nread - 1] = '\0'; | |
697 | ||
698 | if (verbosity > 2) | |
699 | HexDump(linep, linecap); | |
700 | ||
701 | if (*linep == 0) | |
702 | continue; | |
703 | ||
704 | if (cmdptr == NULL || argptr == NULL || linecap > cmdlen) { | |
705 | cmdlen = linecap; | |
706 | cmdptr = realloc(cmdptr, cmdlen); | |
707 | argptr = realloc(argptr, cmdlen); | |
708 | } | |
709 | ||
710 | /* | |
711 | * Trick to support unisgned and hexadecimal arguments | |
712 | * as I can't figure out sscanf() conversions | |
713 | */ | |
714 | nitems = sscanf(linep, "%s %s", cmdptr, argptr); | |
715 | if (nitems == 0) { | |
716 | warnx("I didn't get that..."); | |
717 | continue; | |
718 | } else if (nitems > 1) { | |
719 | if (offset_from_str(argptr, &offset) == 0) { | |
720 | warnx("I didn't get that either..."); | |
721 | continue; | |
722 | } | |
723 | } | |
724 | if (verbosity > 2) | |
725 | printf("nitems %d %s %s\n", nitems, cmdptr, argptr); | |
726 | ||
727 | bzero(&action, sizeof(struct cfil_msg_action)); | |
728 | action.cfa_msghdr.cfm_len = sizeof(struct cfil_msg_action); | |
729 | action.cfa_msghdr.cfm_version = CFM_VERSION_CURRENT; | |
730 | action.cfa_msghdr.cfm_type = CFM_TYPE_ACTION; | |
731 | ||
732 | if (strcasecmp(cmdptr, "passout") == 0 && nitems > 1) { | |
733 | op = CFM_OP_DATA_UPDATE; | |
734 | action.cfa_out_pass_offset = offset; | |
735 | } else if (strcasecmp(cmdptr, "passin") == 0 && nitems > 1) { | |
736 | op = CFM_OP_DATA_UPDATE; | |
737 | action.cfa_in_pass_offset = offset; | |
738 | } else if (strcasecmp(cmdptr, "pass") == 0 && nitems > 1) { | |
739 | op = CFM_OP_DATA_UPDATE; | |
740 | action.cfa_out_pass_offset = offset; | |
741 | action.cfa_in_pass_offset = offset; | |
742 | } else if (strcasecmp(cmdptr, "peekout") == 0 && nitems > 1) { | |
743 | op = CFM_OP_DATA_UPDATE; | |
744 | action.cfa_out_peek_offset = offset; | |
745 | } else if (strcasecmp(cmdptr, "peekin") == 0 && nitems > 1) { | |
746 | op = CFM_OP_DATA_UPDATE; | |
747 | action.cfa_in_peek_offset = offset; | |
748 | } else if (strcasecmp(cmdptr, "peek") == 0 && nitems > 1) { | |
749 | op = CFM_OP_DATA_UPDATE; | |
750 | action.cfa_out_peek_offset = offset; | |
751 | action.cfa_in_peek_offset = offset; | |
752 | } else if (strcasecmp(cmdptr, "start") == 0) { | |
753 | op = CFM_OP_DATA_UPDATE; | |
754 | action.cfa_out_pass_offset = 0; | |
755 | action.cfa_out_peek_offset = CFM_MAX_OFFSET; | |
756 | action.cfa_in_pass_offset = 0; | |
757 | action.cfa_in_peek_offset = CFM_MAX_OFFSET; | |
758 | } else if (strcasecmp(cmdptr, "peekall") == 0) { | |
759 | op = CFM_OP_DATA_UPDATE; | |
760 | action.cfa_out_peek_offset = CFM_MAX_OFFSET; | |
761 | action.cfa_in_peek_offset = CFM_MAX_OFFSET; | |
762 | } else if (strcasecmp(cmdptr, "passall") == 0) { | |
763 | op = CFM_OP_DATA_UPDATE; | |
764 | action.cfa_out_pass_offset = CFM_MAX_OFFSET; | |
765 | action.cfa_out_peek_offset = CFM_MAX_OFFSET; | |
766 | action.cfa_in_pass_offset = CFM_MAX_OFFSET; | |
767 | action.cfa_in_peek_offset = CFM_MAX_OFFSET; | |
768 | } else if (strcasecmp(cmdptr, "drop") == 0) | |
769 | op = CFM_OP_DROP; | |
770 | else if (strcasecmp(cmdptr, "sock") == 0) { | |
771 | last_sock_id = offset; | |
772 | printf("last_sock_id 0x%llx\n", last_sock_id); | |
773 | } else | |
774 | warnx("syntax error"); | |
775 | ||
776 | if (op == CFM_OP_DATA_UPDATE || op == CFM_OP_DROP) { | |
777 | action.cfa_msghdr.cfm_op = op; | |
778 | action.cfa_msghdr.cfm_sock_id = last_sock_id; | |
779 | print_action_msg(&action); | |
780 | ||
781 | if (send(sf, &action, sizeof(struct cfil_msg_action), 0) == -1) | |
782 | warn("send()"); | |
783 | } | |
784 | } | |
785 | } | |
786 | ||
787 | return 0; | |
788 | } | |
789 | ||
790 | static const char * | |
791 | basename(const char * str) | |
792 | { | |
793 | const char *last_slash = strrchr(str, '/'); | |
794 | ||
795 | if (last_slash == NULL) | |
796 | return (str); | |
797 | else | |
798 | return (last_slash + 1); | |
799 | } | |
800 | ||
801 | struct option_desc { | |
802 | const char *option; | |
803 | const char *description; | |
804 | int required; | |
805 | }; | |
806 | ||
807 | struct option_desc option_desc_list[] = { | |
808 | { "-a offset", "auto start with offset", 0 }, | |
809 | { "-d offset value", "default offset value for passin, peekin, passout, peekout, pass, peek", 0 }, | |
810 | { "-h", "dsiplay this help", 0 }, | |
811 | { "-i", "interactive mode", 0 }, | |
812 | { "-k increment", "peek mode with increment", 0 }, | |
813 | {"-l", "pass loopback", 0 }, | |
814 | { "-m length", "max dump length", 0 }, | |
815 | { "-p offset", "pass mode (all or after given offset if > 0)", 0 }, | |
816 | { "-q", "decrease verbose level", 0 }, | |
817 | { "-r random", "random drop rate", 0 }, | |
818 | { "-s ", "display content filter statistics (all, sock, filt, cfil)", 0 }, | |
819 | { "-t delay", "pass delay in microseconds", 0 }, | |
820 | { "-u unit", "NECP filter control unit", 1 }, | |
821 | { "-v", "increase verbose level", 0 }, | |
822 | { NULL, NULL, 0 } /* Mark end of list */ | |
823 | }; | |
824 | ||
825 | static void | |
826 | usage(const char *cmd) | |
827 | { | |
828 | struct option_desc *option_desc; | |
829 | char *usage_str = malloc(LINE_MAX); | |
830 | size_t usage_len; | |
831 | ||
832 | if (usage_str == NULL) | |
833 | err(1, "%s: malloc(%d)", __func__, LINE_MAX); | |
834 | ||
835 | usage_len = snprintf(usage_str, LINE_MAX, "# usage: %s ", basename(cmd)); | |
836 | ||
837 | for (option_desc = option_desc_list; option_desc->option != NULL; option_desc++) { | |
838 | int len; | |
839 | ||
840 | if (option_desc->required) | |
841 | len = snprintf(usage_str + usage_len, LINE_MAX - usage_len, "%s ", option_desc->option); | |
842 | else | |
843 | len = snprintf(usage_str + usage_len, LINE_MAX - usage_len, "[%s] ", option_desc->option); | |
844 | if (len < 0) | |
845 | err(1, "%s: snprintf(", __func__); | |
846 | ||
847 | usage_len += len; | |
848 | if (usage_len > LINE_MAX) | |
849 | break; | |
850 | } | |
851 | printf("%s\n", usage_str); | |
852 | printf("options:\n"); | |
853 | ||
854 | for (option_desc = option_desc_list; option_desc->option != NULL; option_desc++) { | |
855 | printf(" %-20s # %s\n", option_desc->option, option_desc->description); | |
856 | } | |
857 | ||
858 | } | |
859 | ||
860 | int | |
861 | main(int argc, char * const argv[]) | |
862 | { | |
863 | int ch; | |
864 | double d; | |
865 | int stats_sock_list = 0; | |
866 | int stats_filt_list = 0; | |
867 | int stats_cfil_stats = 0; | |
868 | ||
869 | while ((ch = getopt(argc, argv, "a:d:hik:lm:p:qr:s:t:u:v")) != -1) { | |
870 | switch (ch) { | |
871 | case 'a': | |
872 | auto_start = strtoul(optarg, NULL, 0); | |
873 | break; | |
874 | case 'd': { | |
875 | if (optind >= argc) | |
876 | errx(1, "'-d' needs 2 parameters"); | |
877 | if (strcasecmp(optarg, "passout") == 0) { | |
878 | if (offset_from_str(argv[optind], &default_out_pass) == 0) | |
879 | errx(1, "bad %s offset: %s", optarg, argv[optind + 1]); | |
880 | } else if (strcasecmp(optarg, "passin") == 0) { | |
881 | if (offset_from_str(argv[optind], &default_in_pass) == 0) | |
882 | errx(1, "bad %s offset: %s", optarg, argv[optind + 1]); | |
883 | } else if (strcasecmp(optarg, "pass") == 0) { | |
884 | if (offset_from_str(argv[optind], &default_out_pass) == 0) | |
885 | errx(1, "bad %s offset: %s", optarg, argv[optind + 1]); | |
886 | default_in_pass = default_out_pass; | |
887 | } else if (strcasecmp(optarg, "peekout") == 0) { | |
888 | if (offset_from_str(argv[optind], &default_out_peek) == 0) | |
889 | errx(1, "bad %s offset: %s", optarg, argv[optind + 1]); | |
890 | } else if (strcasecmp(optarg, "peekin") == 0) { | |
891 | if (offset_from_str(argv[optind], &default_in_peek) == 0) | |
892 | errx(1, "bad %s offset: %s", optarg, argv[optind + 1]); | |
893 | } else if (strcasecmp(optarg, "peek") == 0) { | |
894 | if (offset_from_str(argv[optind], &default_out_peek) == 0) | |
895 | errx(1, "bad %s offset: %s", optarg, argv[optind + 1]); | |
896 | default_in_peek = default_out_peek; | |
897 | } else | |
898 | errx(1, "syntax error"); | |
899 | break; | |
900 | } | |
901 | case 'h': | |
902 | usage(argv[0]); | |
903 | exit(0); | |
904 | case 'i': | |
905 | mode |= MODE_INTERACTIVE; | |
906 | break; | |
907 | case 'k': | |
908 | mode |= MODE_PEEK; | |
909 | if (offset_from_str(optarg, &peek_inc) == 0) | |
910 | errx(1, "bad peek offset: %s", optarg); | |
911 | break; | |
912 | case 'l': | |
913 | pass_loopback = 1; | |
914 | break; | |
915 | case 'm': | |
916 | max_dump_len = strtoul(optarg, NULL, 0); | |
917 | break; | |
918 | case 'p': | |
919 | mode |= MODE_PASS; | |
920 | if (offset_from_str(optarg, &pass_offset) == 0) | |
921 | errx(1, "bad pass offset: %s", optarg); | |
922 | break; | |
923 | case 'q': | |
924 | verbosity--; | |
925 | break; | |
926 | case 'r': | |
927 | d = strtod(optarg, NULL); | |
928 | if (d < 0 || d > 1) | |
929 | errx(1, "bad drop rate: %s -- it must be between 0 and 1", optarg); | |
930 | random_drop = (uint32_t)(d * UINT32_MAX); | |
931 | break; | |
932 | case 's': | |
933 | if (strcasecmp(optarg, "all") == 0) { | |
934 | stats_sock_list = 1; | |
935 | stats_filt_list = 1; | |
936 | stats_cfil_stats = 1; | |
937 | } else if (strcasecmp(optarg, "sock") == 0) { | |
938 | stats_sock_list = 1; | |
939 | } else if (strcasecmp(optarg, "filt") == 0) { | |
940 | stats_filt_list = 1; | |
941 | } else if (strcasecmp(optarg, "cfil") == 0) { | |
942 | stats_cfil_stats = 1; | |
943 | } else { | |
944 | warnx("# Error: unknown type of statistic: %s", optarg); | |
945 | usage(argv[0]); | |
946 | exit(0); | |
947 | } | |
948 | break; | |
949 | case 't': | |
950 | mode |= MODE_DELAY; | |
951 | delay_ms = strtoul(optarg, NULL, 0); | |
952 | delay_tv.tv_sec = delay_ms / 1000; | |
953 | delay_tv.tv_usec = (delay_ms % 1000) * 1000; | |
954 | break; | |
955 | case 'u': | |
956 | necp_control_unit = (uint32_t)strtoul(optarg, NULL, 0); | |
957 | break; | |
958 | case 'v': | |
959 | verbosity++; | |
960 | break; | |
961 | default: | |
962 | errx(1, "# syntax error, unknow option '%d'", ch); | |
963 | usage(argv[0]); | |
964 | exit(0); | |
965 | } | |
966 | } | |
967 | ||
968 | if (stats_filt_list) | |
969 | print_filter_list(); | |
970 | if (stats_sock_list) | |
971 | print_socket_list(); | |
972 | if (stats_cfil_stats) | |
973 | print_cfil_stats(); | |
974 | if (necp_control_unit == 0 && (stats_filt_list || stats_sock_list || stats_cfil_stats)) | |
975 | return (0); | |
976 | ||
977 | if (necp_control_unit == 0) { | |
978 | warnx("necp filter control unit is 0"); | |
979 | usage(argv[0]); | |
980 | exit(EX_USAGE); | |
981 | } | |
982 | doit(); | |
983 | ||
984 | ||
985 | return (0); | |
986 | } | |
987 |