/* * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2017 Nicira, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #undef NDEBUG #include "netflow.h" #include #include #include #include #include #include "command-line.h" #include "daemon.h" #include "openvswitch/dynamic-string.h" #include "openvswitch/ofpbuf.h" #include "ovstest.h" #include "packets.h" #include "openvswitch/poll-loop.h" #include "socket-util.h" #include "unixctl.h" #include "util.h" #include "openvswitch/vlog.h" OVS_NO_RETURN static void usage(void); static void parse_options(int argc, char *argv[]); static unixctl_cb_func test_netflow_exit; static void print_netflow(struct ofpbuf *buf) { const struct netflow_v5_header *hdr; int i; hdr = ofpbuf_try_pull(buf, sizeof *hdr); if (!hdr) { printf("truncated NetFlow packet header\n"); return; } printf("header: v%"PRIu16", " "uptime %"PRIu32", " "now %"PRIu32".%09"PRIu32", " "seq %"PRIu32", " "engine %"PRIu8",%"PRIu8, ntohs(hdr->version), ntohl(hdr->sysuptime), ntohl(hdr->unix_secs), ntohl(hdr->unix_nsecs), ntohl(hdr->flow_seq), hdr->engine_type, hdr->engine_id); if (hdr->sampling_interval != htons(0)) { printf(", interval %"PRIu16, ntohs(hdr->sampling_interval)); } putchar('\n'); for (i = 0; i < ntohs(hdr->count); i++) { struct netflow_v5_record *rec; rec = ofpbuf_try_pull(buf, sizeof *rec); if (!rec) { printf("truncated NetFlow records\n"); return; } printf("seq %"PRIu32": "IP_FMT" > "IP_FMT, ntohl(hdr->flow_seq), IP_ARGS(rec->src_addr), IP_ARGS(rec->dst_addr)); printf(", if %"PRIu16" > %"PRIu16, ntohs(rec->input), ntohs(rec->output)); printf(", %"PRIu32" pkts, %"PRIu32" bytes", ntohl(rec->packet_count), ntohl(rec->byte_count)); switch (rec->ip_proto) { case IPPROTO_TCP: printf(", TCP %"PRIu16" > %"PRIu16, ntohs(rec->src_port), ntohs(rec->dst_port)); if (rec->tcp_flags) { struct ds s = DS_EMPTY_INITIALIZER; packet_format_tcp_flags(&s, rec->tcp_flags); printf(" %s", ds_cstr(&s)); ds_destroy(&s); } break; case IPPROTO_UDP: printf(", UDP %"PRIu16" > %"PRIu16, ntohs(rec->src_port), ntohs(rec->dst_port)); break; case IPPROTO_SCTP: printf(", SCTP %"PRIu16" > %"PRIu16, ntohs(rec->src_port), ntohs(rec->dst_port)); break; case IPPROTO_ICMP: printf(", ICMP %u:%u", ntohs(rec->dst_port) >> 8, ntohs(rec->dst_port) & 0xff); if (rec->src_port != htons(0)) { printf(", src_port=%"PRIu16, ntohs(rec->src_port)); } break; default: printf(", proto %"PRIu8, rec->ip_proto); break; } if (rec->ip_proto != IPPROTO_TCP && rec->tcp_flags != 0) { printf(", flags %"PRIx8, rec->tcp_flags); } if (rec->ip_proto != IPPROTO_TCP && rec->ip_proto != IPPROTO_UDP && rec->ip_proto != IPPROTO_SCTP && rec->ip_proto != IPPROTO_ICMP) { if (rec->src_port != htons(0)) { printf(", src_port %"PRIu16, ntohs(rec->src_port)); } if (rec->dst_port != htons(0)) { printf(", dst_port %"PRIu16, ntohs(rec->dst_port)); } } if (rec->ip_tos) { printf(", TOS %"PRIx8, rec->ip_tos); } printf(", time %"PRIu32"...%"PRIu32, ntohl(rec->init_time), ntohl(rec->used_time)); if (rec->nexthop != htonl(0)) { printf(", nexthop "IP_FMT, IP_ARGS(rec->nexthop)); } if (rec->src_as != htons(0) || rec->dst_as != htons(0)) { printf(", AS %"PRIu16" > %"PRIu16, ntohs(rec->src_as), ntohs(rec->dst_as)); } if (rec->src_mask != 0 || rec->dst_mask != 0) { printf(", mask %"PRIu8" > %"PRIu8, rec->src_mask, rec->dst_mask); } if (rec->pad1) { printf(", pad1 %"PRIu8, rec->pad1); } if (rec->pad[0] || rec->pad[1]) { printf(", pad %"PRIu8", %"PRIu8, rec->pad[0], rec->pad[1]); } putchar('\n'); } if (buf->size) { printf("%"PRIu32" extra bytes after last record\n", buf->size); } } static void test_netflow_main(int argc, char *argv[]) { struct unixctl_server *server; enum { MAX_RECV = 1500 }; const char *target; struct ofpbuf buf; bool exiting = false; int error; int sock; int n; ovs_cmdl_proctitle_init(argc, argv); set_program_name(argv[0]); service_start(&argc, &argv); parse_options(argc, argv); if (argc - optind != 1) { ovs_fatal(0, "exactly one non-option argument required " "(use --help for help)"); } target = argv[optind]; sock = inet_open_passive(SOCK_DGRAM, target, 0, NULL, 0, true); if (sock < 0) { ovs_fatal(0, "%s: failed to open (%s)", argv[1], ovs_strerror(-sock)); } daemon_save_fd(STDOUT_FILENO); daemonize_start(false, false); error = unixctl_server_create(NULL, &server); if (error) { ovs_fatal(error, "failed to create unixctl server"); } unixctl_command_register("exit", "", 0, 0, test_netflow_exit, &exiting); daemonize_complete(); ofpbuf_init(&buf, MAX_RECV); n = 0; for (;;) { int retval; unixctl_server_run(server); ofpbuf_clear(&buf); do { retval = recv(sock, buf.data, buf.allocated, 0); } while (retval < 0 && errno == EINTR); if (retval > 0) { ofpbuf_put_uninit(&buf, retval); if (n++ > 0) { putchar('\n'); } print_netflow(&buf); fflush(stdout); } if (exiting) { break; } poll_fd_wait(sock, POLLIN); unixctl_server_wait(server); poll_block(); } ofpbuf_uninit(&buf); unixctl_server_destroy(server); } static void parse_options(int argc, char *argv[]) { enum { DAEMON_OPTION_ENUMS, VLOG_OPTION_ENUMS }; static const struct option long_options[] = { {"help", no_argument, NULL, 'h'}, DAEMON_LONG_OPTIONS, VLOG_LONG_OPTIONS, {NULL, 0, NULL, 0}, }; char *short_options = ovs_cmdl_long_options_to_short_options(long_options); for (;;) { int c = getopt_long(argc, argv, short_options, long_options, NULL); if (c == -1) { break; } switch (c) { case 'h': usage(); DAEMON_OPTION_HANDLERS VLOG_OPTION_HANDLERS case '?': exit(EXIT_FAILURE); default: abort(); } } free(short_options); } static void usage(void) { printf("%s: netflow collector test utility\n" "usage: %s [OPTIONS] PORT[:IP]\n" "where PORT is the UDP port to listen on and IP is optionally\n" "the IP address to listen on.\n", program_name, program_name); daemon_usage(); vlog_usage(); printf("\nOther options:\n" " -h, --help display this help message\n"); exit(EXIT_SUCCESS); } static void test_netflow_exit(struct unixctl_conn *conn, int argc OVS_UNUSED, const char *argv[] OVS_UNUSED, void *exiting_) { bool *exiting = exiting_; *exiting = true; unixctl_command_reply(conn, NULL); } OVSTEST_REGISTER("test-netflow", test_netflow_main);