├── LICENSE ├── Makefile ├── README.md ├── dns.c ├── dns.h ├── flow.c ├── log.c ├── log.h ├── setup.sql ├── task.c └── task.h /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2020 The University of Queensland 4 | 5 | Permission to use, copy, modify, and distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | PROG= flow 3 | SRCS= flow.c 4 | SRCS+= log.c task.c 5 | MAN= 6 | 7 | LDADD=-lpcap -lpthread -levent 8 | DPADD=${LIBPCAP} ${LIBPTHREAD} ${LIBEVENT} 9 | 10 | DEBUG=-g 11 | WARNINGS=yes 12 | 13 | .include 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flow-collector(8) 2 | 3 | flow-collector aggregates IP flow data for storage in a ClickHouse 4 | database. 5 | 6 | IP flows are generated from packets captured on one or more Ethernet 7 | interfaces, typically connected to SPAN ports on switches. This 8 | allows for the aggregation of flow information in a redundant 9 | switching environment. 10 | 11 | Flows are collected within timeslices, each of which is 2.5 seconds 12 | long by default. 13 | 14 | The collector also features parsing of DNS packets for building 15 | mappings of IPs to names. 16 | 17 | ## Todo 18 | 19 | - Improve the robustness of the POSTs into clickhouse 20 | - Improve handling of memory shortages 21 | - investigate merging timeslices under memory pressure 22 | -------------------------------------------------------------------------------- /dns.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 The University of Queensland 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include "dns.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #include 45 | #include 46 | 47 | struct question { 48 | TAILQ_ENTRY(question) q_entry; 49 | char q_nbuf[512]; 50 | struct dns_question q; 51 | }; 52 | 53 | struct record { 54 | TAILQ_ENTRY(record) r_entry; 55 | char r_nbuf[512]; 56 | u_char r_dbuf[512]; 57 | struct dns_record r; 58 | }; 59 | 60 | TAILQ_HEAD(question_list, question); 61 | TAILQ_HEAD(record_list, record); 62 | 63 | struct dns_buf { 64 | const u_char * db_buf; 65 | const u_char * db_ptr; 66 | size_t db_len; 67 | size_t db_rem; 68 | struct dns_header db_head; 69 | struct question_list db_qs; 70 | struct record_list db_rs; 71 | }; 72 | 73 | struct dns_buf * 74 | dns_buf_from(const u_char *buf, size_t len) 75 | { 76 | struct dns_buf *db; 77 | 78 | db = calloc(1, sizeof(*db)); 79 | if (db == NULL) 80 | return (NULL); 81 | db->db_buf = buf; 82 | db->db_ptr = buf; 83 | db->db_len = len; 84 | db->db_rem = len; 85 | TAILQ_INIT(&db->db_qs); 86 | TAILQ_INIT(&db->db_rs); 87 | return (db); 88 | } 89 | 90 | void 91 | dns_buf_free(struct dns_buf *db) 92 | { 93 | struct question *q, *nq; 94 | struct record *r, *nr; 95 | 96 | TAILQ_FOREACH_SAFE(q, &db->db_qs, q_entry, nq) { 97 | free(q); 98 | } 99 | TAILQ_FOREACH_SAFE(r, &db->db_rs, r_entry, nr) { 100 | free(r); 101 | } 102 | 103 | free(db); 104 | } 105 | 106 | static enum dns_parser_rc 107 | dns_read_uint8(struct dns_buf *db, uint8_t *retp) 108 | { 109 | if (db->db_rem < sizeof(*retp)) 110 | return (DNS_R_SHORT); 111 | db->db_rem -= sizeof(*retp); 112 | *retp = *db->db_ptr++; 113 | return (DNS_R_OK); 114 | } 115 | 116 | static enum dns_parser_rc 117 | dns_read_chars(struct dns_buf *db, char *buf, size_t len) 118 | { 119 | size_t i; 120 | 121 | if (db->db_rem < len) 122 | return (DNS_R_SHORT); 123 | db->db_rem -= len; 124 | for (i = 0; i < len; i++) { 125 | int ch = *db->db_ptr++; 126 | if (!isprint(ch)) 127 | return (DNS_R_ERROR); 128 | buf[i] = ch; 129 | } 130 | return (DNS_R_OK); 131 | } 132 | 133 | static enum dns_parser_rc 134 | dns_read_uint16(struct dns_buf *db, uint16_t *retp) 135 | { 136 | if (db->db_rem < sizeof(*retp)) 137 | return (DNS_R_SHORT); 138 | db->db_rem -= sizeof(*retp); 139 | *retp = *db->db_ptr++ << 8; 140 | *retp |= *db->db_ptr++; 141 | return (DNS_R_OK); 142 | } 143 | 144 | static enum dns_parser_rc 145 | dns_read_uint32(struct dns_buf *db, uint32_t *retp) 146 | { 147 | if (db->db_rem < sizeof(*retp)) 148 | return (DNS_R_SHORT); 149 | db->db_rem -= sizeof(*retp); 150 | *retp = *db->db_ptr++ << 24; 151 | *retp |= *db->db_ptr++ << 16; 152 | *retp |= *db->db_ptr++ << 8; 153 | *retp |= *db->db_ptr++; 154 | return (DNS_R_OK); 155 | } 156 | 157 | enum dns_parser_rc 158 | dns_read_header(struct dns_buf *db, const struct dns_header **retp) 159 | { 160 | struct dns_header *dh = &db->db_head; 161 | enum dns_parser_rc rc; 162 | uint16_t flags; 163 | 164 | if ((rc = dns_read_uint16(db, &dh->dh_id))) 165 | return (rc); 166 | if ((rc = dns_read_uint16(db, &flags))) 167 | return (rc); 168 | dh->dh_flags = flags & ~(DNS_OPCODE | DNS_RCODE); 169 | if (flags & DNS_QR) 170 | dh->dh_rcode = flags & DNS_RCODE; 171 | dh->dh_opcode = (flags & DNS_OPCODE) >> 11; 172 | if ((rc = dns_read_uint16(db, &dh->dh_questions))) 173 | return (rc); 174 | if ((rc = dns_read_uint16(db, &dh->dh_answers))) 175 | return (rc); 176 | if ((rc = dns_read_uint16(db, &dh->dh_authorities))) 177 | return (rc); 178 | if ((rc = dns_read_uint16(db, &dh->dh_additionals))) 179 | return (rc); 180 | 181 | *retp = dh; 182 | return (DNS_R_OK); 183 | } 184 | 185 | enum name_meta { 186 | DNS_NAME_MASK = 0xC0, 187 | DNS_NAME_STRING = 0x00, 188 | DNS_NAME_PTR = 0xC0 189 | }; 190 | 191 | static enum dns_parser_rc 192 | dns_read_name(struct dns_buf *db, char *buf, size_t buflen, u_int depth) 193 | { 194 | char *optr = buf; 195 | const u_char *pptr; 196 | size_t prem; 197 | size_t rem = buflen - 1; 198 | uint8_t rlen, ptrl; 199 | uint16_t ptr; 200 | enum dns_parser_rc rc; 201 | 202 | if ((rc = dns_read_uint8(db, &rlen))) 203 | return (rc); 204 | while (rlen != 0 && rem > 0) { 205 | switch (rlen & DNS_NAME_MASK) { 206 | 207 | case DNS_NAME_STRING: 208 | if (rem < rlen + 1) 209 | return (DNS_R_NOMEM); 210 | if (optr != buf) { 211 | *optr++ = '.'; 212 | --rem; 213 | } 214 | if ((rc = dns_read_chars(db, optr, rlen))) 215 | return (rc); 216 | optr += rlen; 217 | rem -= rlen; 218 | break; 219 | 220 | case DNS_NAME_PTR: 221 | if (depth == 0) 222 | return (DNS_R_PTRLIMIT); 223 | 224 | if ((rc = dns_read_uint8(db, &ptrl))) 225 | return (rc); 226 | ptr = ((rlen & ~DNS_NAME_MASK) << 8) | ptrl; 227 | 228 | if (ptr > (db->db_ptr - db->db_buf)) 229 | return (DNS_R_ERROR); 230 | 231 | if (optr != buf) { 232 | *optr++ = '.'; 233 | --rem; 234 | } 235 | if (rem < 2) 236 | return (DNS_R_NOMEM); 237 | 238 | pptr = db->db_ptr; 239 | prem = db->db_rem; 240 | 241 | db->db_ptr = db->db_buf + ptr; 242 | db->db_rem = db->db_len - ptr; 243 | rc = dns_read_name(db, optr, rem, depth - 1); 244 | db->db_ptr = pptr; 245 | db->db_rem = prem; 246 | return (rc); 247 | } 248 | if ((rc = dns_read_uint8(db, &rlen))) 249 | return (rc); 250 | } 251 | *optr++ = '\0'; 252 | 253 | return (DNS_R_OK); 254 | } 255 | 256 | enum dns_parser_rc 257 | dns_read_question(struct dns_buf *db, const struct dns_question **retp) 258 | { 259 | struct question *q; 260 | struct dns_question *dq; 261 | enum dns_parser_rc rc; 262 | uint16_t tmp16; 263 | 264 | q = calloc(1, sizeof(*q)); 265 | if (q == NULL) 266 | return (DNS_R_NOMEM); 267 | dq = &q->q; 268 | 269 | if ((rc = dns_read_name(db, q->q_nbuf, sizeof(q->q_nbuf), 3))) 270 | goto err; 271 | dq->dq_name = q->q_nbuf; 272 | if ((rc = dns_read_uint16(db, &tmp16))) 273 | goto err; 274 | dq->dq_type = tmp16; 275 | if ((rc = dns_read_uint16(db, &tmp16))) 276 | goto err; 277 | dq->dq_class = tmp16; 278 | 279 | TAILQ_INSERT_TAIL(&db->db_qs, q, q_entry); 280 | *retp = dq; 281 | return (DNS_R_OK); 282 | err: 283 | free(q); 284 | return (rc); 285 | } 286 | 287 | enum dns_parser_rc 288 | dns_read_record(struct dns_buf *db, const struct dns_record **retp) 289 | { 290 | struct record *r; 291 | struct dns_record *dr; 292 | enum dns_parser_rc rc; 293 | uint16_t tmp16; 294 | 295 | r = calloc(1, sizeof(*r)); 296 | if (r == NULL) 297 | return (DNS_R_NOMEM); 298 | dr = &r->r; 299 | 300 | if ((rc = dns_read_name(db, r->r_nbuf, sizeof(r->r_nbuf), 3))) 301 | goto err; 302 | dr->dr_name = r->r_nbuf; 303 | if ((rc = dns_read_uint16(db, &tmp16))) 304 | goto err; 305 | dr->dr_type = tmp16; 306 | if ((rc = dns_read_uint16(db, &tmp16))) 307 | goto err; 308 | dr->dr_class = tmp16; 309 | if ((rc = dns_read_uint32(db, &dr->dr_ttl))) 310 | goto err; 311 | 312 | if ((rc = dns_read_uint16(db, &tmp16))) 313 | goto err; 314 | if (db->db_rem < tmp16) { 315 | rc = DNS_R_SHORT; 316 | goto err; 317 | } 318 | db->db_rem -= tmp16; 319 | 320 | switch (dr->dr_type) { 321 | case DNS_T_A: 322 | bcopy(db->db_ptr, &dr->dr_data._dr_a_data, tmp16); 323 | break; 324 | case DNS_T_AAAA: 325 | bcopy(db->db_ptr, &dr->dr_data._dr_aaaa_data, tmp16); 326 | break; 327 | default: 328 | dr->dr_data._dr_raw_data._dr_buf = db->db_ptr; 329 | dr->dr_data._dr_raw_data._dr_len = tmp16; 330 | } 331 | 332 | TAILQ_INSERT_TAIL(&db->db_rs, r, r_entry); 333 | db->db_ptr += tmp16; 334 | *retp = dr; 335 | return (DNS_R_OK); 336 | err: 337 | free(r); 338 | return (rc); 339 | } 340 | -------------------------------------------------------------------------------- /dns.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 The University of Queensland 3 | * 4 | * Permission to use, copy, modify, and distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #if !defined(_DNS_H) 18 | #define _DNS_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | struct dns_buf; 28 | 29 | enum dns_flags { 30 | DNS_QR = 0x8000, 31 | DNS_OPCODE = 0x7800, 32 | DNS_AA = 0x0400, 33 | DNS_TC = 0x0200, 34 | DNS_RD = 0x0100, 35 | DNS_RA = 0x0080, 36 | DNS_Z = 0x0040, 37 | DNS_AD = 0x0020, 38 | DNS_CD = 0x0010, 39 | DNS_RCODE = 0x00F 40 | }; 41 | 42 | enum dns_parser_rc { 43 | DNS_R_OK = 0, 44 | DNS_R_NOMEM = 1, 45 | DNS_R_SHORT = 2, 46 | DNS_R_ERROR = 3, 47 | DNS_R_PTRLIMIT = 4 48 | }; 49 | 50 | enum dns_rcode { 51 | DNS_NOERROR = 0, 52 | DNS_FORMERR = 1, 53 | DNS_SERVFAIL = 2, 54 | DNS_NXDOMAIN = 3, 55 | DNS_NOTIMP = 4, 56 | DNS_REFUSED = 5, 57 | DNS_YXDOMAIN = 6, 58 | DNS_XRRSET = 7, 59 | DNS_NOTAUTH = 9, 60 | DNS_NOTZONE = 10 61 | }; 62 | 63 | enum dns_opcode { 64 | DNS_QUERY = 0, 65 | DNS_IQUERY = 1, 66 | DNS_STATUS = 2, 67 | DNS_NOTIFY = 4, 68 | DNS_UPDATE = 5 69 | }; 70 | 71 | enum dns_type { 72 | DNS_T_A = 0x01, 73 | DNS_T_NS = 0x02, 74 | DNS_T_CNAME = 0x05, 75 | DNS_T_SOA = 0x06, 76 | DNS_T_PTR = 0x0C, 77 | DNS_T_TXT = 0x10, 78 | DNS_T_AAAA = 0x1C 79 | }; 80 | 81 | enum dns_class { 82 | DNS_C_IN = 0x01, 83 | DNS_C_CS = 0x02, 84 | DNS_C_CH = 0x03, 85 | DNS_C_HS = 0x04, 86 | DNS_C_ANY = 0xFF 87 | }; 88 | 89 | struct dns_header { 90 | uint16_t dh_id; 91 | enum dns_flags dh_flags; 92 | enum dns_opcode dh_opcode; 93 | enum dns_rcode dh_rcode; 94 | uint16_t dh_questions; 95 | uint16_t dh_answers; 96 | uint16_t dh_authorities; 97 | uint16_t dh_additionals; 98 | }; 99 | 100 | struct dns_question { 101 | const char * dq_name; 102 | enum dns_type dq_type; 103 | enum dns_class dq_class; 104 | }; 105 | 106 | struct dns_record { 107 | const char * dr_name; 108 | enum dns_type dr_type; 109 | enum dns_class dr_class; 110 | uint32_t dr_ttl; 111 | union { 112 | struct { 113 | const u_char * _dr_buf; 114 | size_t _dr_len; 115 | } _dr_raw_data; 116 | struct in_addr _dr_a_data; 117 | struct in6_addr _dr_aaaa_data; 118 | } dr_data; 119 | }; 120 | 121 | struct dns_buf *dns_buf_from(const u_char *, size_t); 122 | void dns_buf_free(struct dns_buf *); 123 | 124 | enum dns_parser_rc dns_read_header(struct dns_buf *, 125 | const struct dns_header **); 126 | enum dns_parser_rc dns_read_question(struct dns_buf *, 127 | const struct dns_question **); 128 | enum dns_parser_rc dns_read_record(struct dns_buf *, 129 | const struct dns_record **); 130 | 131 | #endif 132 | -------------------------------------------------------------------------------- /flow.c: -------------------------------------------------------------------------------- 1 | /* */ 2 | 3 | /* 4 | * Copyright (c) 2020 The University of Queensland 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | #include 52 | #include 53 | 54 | #include 55 | #include 56 | 57 | #include "log.h" 58 | #include "task.h" 59 | 60 | #ifndef nitems 61 | #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) 62 | #endif 63 | 64 | #ifndef ISSET 65 | #define ISSET(_v, _m) ((_v) & (_m)) 66 | #endif 67 | 68 | static const unsigned int pkt_lens[] = { 69 | 64, 70 | 128, 71 | 256, 72 | 512, 73 | 1024, 74 | 1514, /* XXX 1500 would make more sense for IP */ 75 | 2048, 76 | 4096, 77 | 8192, 78 | 65535, /* > 8192 */ 79 | }; 80 | 81 | #define NUM_PKT_LENS nitems(pkt_lens) 82 | 83 | struct gre_header { 84 | uint16_t gre_flags; 85 | #define GRE_CP 0x8000 /* Checksum Present */ 86 | #define GRE_KP 0x2000 /* Key Present */ 87 | #define GRE_SP 0x1000 /* Sequence Present */ 88 | 89 | #define GRE_VERS_MASK 0x0007 90 | #define GRE_VERS_0 0x0000 91 | #define GRE_VERS_1 0x0001 92 | 93 | uint16_t gre_proto; 94 | } __packed __aligned(4); 95 | 96 | struct gre_h_cksum { 97 | uint16_t gre_cksum; 98 | uint16_t gre_reserved1; 99 | } __packed __aligned(4); 100 | 101 | struct gre_h_key { 102 | uint32_t gre_key; 103 | } __packed __aligned(4); 104 | 105 | struct esp_header { 106 | uint32_t esp_spi; 107 | uint32_t esp_seq; 108 | } __packed __aligned(4); 109 | 110 | struct ah_header { 111 | uint8_t ah_proto; 112 | uint8_t ah_len; 113 | uint16_t ah_reserved; 114 | uint32_t ah_spi; 115 | uint32_t ah_seq; 116 | } __packed __aligned(4); 117 | 118 | struct sctp_header { 119 | uint16_t sctp_sport; 120 | uint16_t sctp_dport; 121 | uint32_t sctp_vtag; 122 | uint32_t sctp_cksum; 123 | } __packed __aligned(4); 124 | 125 | int rdaemon(int); 126 | 127 | union flow_addr { 128 | struct in_addr addr4; 129 | struct in6_addr addr6; 130 | }; 131 | 132 | struct flow_key { 133 | uint16_t k_sport; 134 | uint16_t k_dport; 135 | 136 | int k_vlan; 137 | #define FLOW_VLAN_UNSET -1 138 | uint8_t k_ipv; 139 | uint8_t k_ipproto; 140 | 141 | union flow_addr k_saddr; 142 | #define k_saddr4 k_saddr.addr4 143 | #define k_saddr6 k_saddr.addr6 144 | union flow_addr k_daddr; 145 | #define k_daddr4 k_daddr.addr4 146 | #define k_daddr6 k_daddr.addr6 147 | 148 | #define k_icmp_type k_sport 149 | #define k_icmp_code k_dport 150 | 151 | #define k_gre_flags k_sport 152 | #define k_gre_proto k_dport 153 | 154 | uint32_t k_gre_key; 155 | #define k_ipsec_spi k_gre_key 156 | } __aligned(8); 157 | 158 | struct flow { 159 | struct flow_key f_key; 160 | 161 | uint64_t f_packets; 162 | uint64_t f_bytes; 163 | uint64_t f_frags; 164 | 165 | uint64_t f_syns; 166 | uint64_t f_fins; 167 | uint64_t f_rsts; 168 | uint64_t f_rstacks; 169 | 170 | uint64_t f_pkt_lens[NUM_PKT_LENS]; 171 | 172 | uint16_t f_min_tcpwin; 173 | uint16_t f_max_tcpwin; 174 | 175 | unsigned int f_min_pktlen; 176 | unsigned int f_max_pktlen; 177 | uint8_t f_min_ttl; 178 | uint8_t f_max_ttl; 179 | 180 | RBT_ENTRY(flow) f_entry_tree; 181 | TAILQ_ENTRY(flow) f_entry_list; 182 | }; 183 | 184 | RBT_HEAD(flow_tree, flow); 185 | TAILQ_HEAD(flow_list, flow); 186 | 187 | static inline int 188 | flow_cmp(const struct flow *a, const struct flow *b) 189 | { 190 | const struct flow_key *ka = &a->f_key; 191 | const struct flow_key *kb = &b->f_key; 192 | const unsigned long *la = (const unsigned long *)ka; 193 | const unsigned long *lb = (const unsigned long *)kb; 194 | size_t i; 195 | 196 | for (i = 0; i < sizeof(*ka) / sizeof(*la); i++) { 197 | if (la[i] > lb[i]) 198 | return (1); 199 | if (la[i] < lb[i]) 200 | return (-1); 201 | } 202 | 203 | return (0); 204 | } 205 | 206 | RBT_PROTOTYPE(flow_tree, flow, f_entry_tree, flow_cmp); 207 | 208 | struct timeslice { 209 | unsigned int ts_flow_count; 210 | struct flow_tree ts_flow_tree; 211 | struct flow_list ts_flow_list; 212 | 213 | struct timeval ts_begin; 214 | struct timeval ts_end; 215 | struct timeval ts_utime; 216 | struct timeval ts_stime; 217 | uint64_t ts_reads; 218 | uint64_t ts_packets; 219 | uint64_t ts_bytes; 220 | 221 | uint64_t ts_mdrop; 222 | 223 | uint64_t ts_short_ether; 224 | uint64_t ts_short_vlan; 225 | uint64_t ts_short_ip4; 226 | uint64_t ts_short_ip6; 227 | uint64_t ts_short_ipproto; 228 | uint64_t ts_nonip; 229 | 230 | unsigned int ts_pcap_recv; 231 | unsigned int ts_pcap_drop; 232 | unsigned int ts_pcap_ifdrop; 233 | 234 | struct task ts_task; 235 | }; 236 | 237 | struct timeslice *timeslice_alloc(const struct timeval *); 238 | static struct flow *flow_alloc(void); 239 | 240 | struct flow_daemon; 241 | 242 | struct pkt_source { 243 | const char *ps_name; 244 | struct flow_daemon *ps_d; 245 | pcap_t *ps_ph; 246 | struct pcap_stat ps_pstat; 247 | struct event ps_ev; 248 | 249 | TAILQ_ENTRY(pkt_source) ps_entry; 250 | }; 251 | 252 | TAILQ_HEAD(pkt_sources, pkt_source); 253 | 254 | struct flow_daemon { 255 | struct taskq *d_taskq; 256 | struct event d_tick; 257 | struct timeval d_tv; 258 | 259 | struct pkt_sources d_pkt_sources; 260 | struct flow *d_flow; 261 | 262 | struct timeslice *d_ts; 263 | 264 | struct rusage d_rusage[2]; 265 | unsigned int d_rusage_gen; 266 | }; 267 | 268 | static int bpf_maxbufsize(void); 269 | static void flow_tick(int, short, void *); 270 | void pkt_capture(int, short, void *); 271 | static struct addrinfo * 272 | clickhouse_resolve(void); 273 | 274 | static int flow_pcap_filter(pcap_t *); 275 | 276 | __dead static void 277 | usage(void) 278 | { 279 | extern char *__progname; 280 | 281 | fprintf(stderr, "usage: %s [-46d] [-u user] [-h clickhouse_host] " 282 | "[-p clickhouse_port] [-D clickhouse_db] [-U clickhouse_user] " 283 | "[-k clickhouse_key] if0 ...\n", __progname); 284 | 285 | exit(1); 286 | } 287 | 288 | static int clickhouse_af = PF_UNSPEC; 289 | static const char *clickhouse_host = "localhost"; 290 | static const char *clickhouse_port = "8123"; 291 | static const char *clickhouse_user = "default"; 292 | static const char *clickhouse_database = NULL; 293 | static const char *clickhouse_key = NULL; 294 | static struct addrinfo *clickhouse_res; 295 | 296 | static int debug = 0; 297 | static int pagesize; 298 | 299 | int 300 | main(int argc, char *argv[]) 301 | { 302 | const char *user = "_flow"; 303 | char errbuf[PCAP_ERRBUF_SIZE]; 304 | const char *errstr; 305 | struct flow_daemon _d = { 306 | .d_tv = { 2, 500000 }, 307 | .d_pkt_sources = TAILQ_HEAD_INITIALIZER(_d.d_pkt_sources), 308 | }; 309 | struct flow_daemon *d = &_d; 310 | struct pkt_source *ps; 311 | 312 | struct timeval now; 313 | struct passwd *pw; 314 | int ch; 315 | int devnull = -1; 316 | int maxbufsize; 317 | 318 | maxbufsize = bpf_maxbufsize(); 319 | if (maxbufsize == -1) 320 | err(1, "sysctl net.bpf.maxbufsize"); 321 | 322 | pagesize = sysconf(_SC_PAGESIZE); 323 | if (pagesize == -1) 324 | err(1, "page size"); 325 | if (pagesize < 1024) /* in case we're run on a crappy vax OS */ 326 | pagesize = 1024; 327 | 328 | while ((ch = getopt(argc, argv, "46dD:u:w:h:p:U:k:")) != -1) { 329 | switch (ch) { 330 | case '4': 331 | clickhouse_af = PF_INET; 332 | break; 333 | case '6': 334 | clickhouse_af = PF_INET6; 335 | break; 336 | case 'd': 337 | debug = 1; 338 | break; 339 | case 'D': 340 | clickhouse_database = optarg; 341 | break; 342 | case 'u': 343 | user = optarg; 344 | break; 345 | case 'w': 346 | d->d_tv.tv_sec = strtonum(optarg, 1, 900, &errstr); 347 | if (errstr != NULL) 348 | errx(1, "%s: %s", optarg, errstr); 349 | break; 350 | case 'h': 351 | clickhouse_host = optarg; 352 | break; 353 | case 'p': 354 | clickhouse_port = optarg; 355 | break; 356 | case 'U': 357 | clickhouse_user = optarg; 358 | break; 359 | case 'k': 360 | clickhouse_key = optarg; 361 | break; 362 | default: 363 | usage(); 364 | } 365 | } 366 | 367 | argc -= optind; 368 | argv += optind; 369 | 370 | if (argc == 0) 371 | usage(); 372 | 373 | clickhouse_res = clickhouse_resolve(); 374 | 375 | signal(SIGPIPE, SIG_IGN); 376 | 377 | if (geteuid()) 378 | lerrx(1, "need root privileges"); 379 | 380 | pw = getpwnam(user); 381 | if (pw == NULL) 382 | errx(1, "%s: unknown user", user); 383 | 384 | if (!debug) { 385 | extern char *__progname; 386 | 387 | devnull = open(_PATH_DEVNULL, O_RDWR, 0); 388 | if (devnull == -1) 389 | err(1, "open %s", _PATH_DEVNULL); 390 | 391 | logger_syslog(__progname); 392 | } 393 | 394 | for (ch = 0; ch < argc; ch++) { 395 | ps = malloc(sizeof(*ps)); 396 | if (ps == NULL) 397 | err(1, NULL); 398 | 399 | ps->ps_ph = pcap_create(argv[ch], errbuf); 400 | if (ps->ps_ph == NULL) 401 | errx(1, "%s", errbuf); 402 | 403 | /* XXX TOCTOU */ 404 | if (pcap_set_buffer_size(ps->ps_ph, maxbufsize) != 0) 405 | errx(1, "%s: %s", argv[ch], pcap_geterr(ps->ps_ph)); 406 | 407 | if (pcap_set_promisc(ps->ps_ph, 1) != 0) 408 | errx(1, "%s", errbuf); 409 | 410 | if (pcap_set_snaplen(ps->ps_ph, 256) != 0) 411 | errx(1, "%s", errbuf); 412 | 413 | if (pcap_set_timeout(ps->ps_ph, 10) != 0) 414 | errx(1, "%s", errbuf); 415 | 416 | if (pcap_activate(ps->ps_ph) != 0) 417 | errx(1, "%s", errbuf); 418 | 419 | if (pcap_setnonblock(ps->ps_ph, 1, errbuf) != 0) 420 | errx(1, "%s", errbuf); 421 | 422 | if (flow_pcap_filter(ps->ps_ph) != 0) 423 | errx(1, "%s: %s", argv[ch], pcap_geterr(ps->ps_ph)); 424 | 425 | ps->ps_d = d; 426 | ps->ps_name = argv[ch]; 427 | 428 | /* fetch a baseline */ 429 | memset(&ps->ps_pstat, 0, sizeof(ps->ps_pstat)); 430 | if (pcap_stats(ps->ps_ph, &ps->ps_pstat) != 0) 431 | errx(1, "%s %s", ps->ps_name, pcap_geterr(ps->ps_ph)); 432 | 433 | TAILQ_INSERT_TAIL(&d->d_pkt_sources, ps, ps_entry); 434 | } 435 | 436 | if (chroot(pw->pw_dir) == -1) 437 | err(1, "chroot %s", pw->pw_dir); 438 | if (chdir("/") == -1) 439 | err(1, "chdir %s", pw->pw_dir); 440 | 441 | if (setgroups(1, &pw->pw_gid) || 442 | setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 443 | setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 444 | errx(1, "unable to drop privileges"); 445 | 446 | endpwent(); 447 | 448 | d->d_taskq = taskq_create("store"); 449 | if (d->d_taskq == NULL) 450 | err(1, "taskq"); 451 | 452 | d->d_flow = flow_alloc(); 453 | if (d->d_flow == NULL) 454 | err(1, NULL); 455 | 456 | gettimeofday(&now, NULL); 457 | 458 | d->d_ts = timeslice_alloc(&now); 459 | if (d->d_ts == NULL) 460 | err(1, NULL); 461 | 462 | if (!debug && rdaemon(devnull) == -1) 463 | err(1, "unable to daemonize"); 464 | 465 | event_init(); 466 | 467 | evtimer_set(&d->d_tick, flow_tick, d); 468 | evtimer_add(&d->d_tick, &d->d_tv); 469 | 470 | TAILQ_FOREACH(ps, &d->d_pkt_sources, ps_entry) { 471 | event_set(&ps->ps_ev, pcap_get_selectable_fd(ps->ps_ph), 472 | EV_READ | EV_PERSIST, pkt_capture, ps); 473 | event_add(&ps->ps_ev, NULL); 474 | } 475 | 476 | event_dispatch(); 477 | 478 | return (0); 479 | } 480 | 481 | static int 482 | bpf_maxbufsize(void) 483 | { 484 | int mib[] = { CTL_NET, PF_BPF, NET_BPF_MAXBUFSIZE }; 485 | int maxbuf; 486 | size_t maxbufsize = sizeof(maxbuf); 487 | 488 | if (sysctl(mib, nitems(mib), &maxbuf, &maxbufsize, NULL, 0) == -1) 489 | return (-1); 490 | 491 | return (maxbuf); 492 | } 493 | 494 | static int 495 | flow_pcap_filter(pcap_t *p) 496 | { 497 | struct bpf_insn bpf_filter[] = { 498 | BPF_STMT(BPF_RET+BPF_K, pcap_snapshot(p)), 499 | }; 500 | struct bpf_program bp = { 501 | .bf_insns = bpf_filter, 502 | .bf_len = nitems(bpf_filter), 503 | }; 504 | 505 | return (pcap_setfilter(p, &bp)); 506 | } 507 | 508 | static inline int 509 | flow_gre_key_valid(const struct flow *f) 510 | { 511 | uint16_t v = f->f_key.k_gre_flags; 512 | /* ignore checksum and seq no */ 513 | v &= ~htons(GRE_CP|GRE_SP); 514 | return (v == htons(GRE_VERS_0|GRE_KP)); 515 | } 516 | 517 | static struct addrinfo * 518 | clickhouse_resolve(void) 519 | { 520 | struct addrinfo hints, *res0; 521 | int error; 522 | 523 | memset(&hints, 0, sizeof(hints)); 524 | hints.ai_family = clickhouse_af; 525 | hints.ai_socktype = SOCK_STREAM; 526 | error = getaddrinfo(clickhouse_host, clickhouse_port, &hints, &res0); 527 | if (error) { 528 | errx(1, "clickhouse host %s port %s resolve: %s", 529 | clickhouse_host, clickhouse_port, gai_strerror(error)); 530 | } 531 | 532 | return (res0); 533 | } 534 | 535 | static int 536 | clickhouse_connect(void) 537 | { 538 | struct addrinfo *res0 = clickhouse_res, *res; 539 | int serrno; 540 | int s; 541 | const char *cause = NULL; 542 | 543 | s = -1; 544 | for (res = res0; res; res = res->ai_next) { 545 | s = socket(res->ai_family, res->ai_socktype, res->ai_protocol); 546 | if (s == -1) { 547 | cause = "socket"; 548 | serrno = errno; 549 | continue; 550 | } 551 | 552 | if (connect(s, res->ai_addr, res->ai_addrlen) == -1) { 553 | cause = "connect"; 554 | serrno = errno; 555 | close(s); 556 | s = -1; 557 | continue; 558 | } 559 | 560 | break; /* okay we got one */ 561 | } 562 | 563 | if (s == -1) { 564 | errno = serrno; 565 | lwarnx("clickhouse host %s port %s %s", 566 | clickhouse_host, clickhouse_port, cause); 567 | return (-1); 568 | } 569 | 570 | return (s); 571 | } 572 | 573 | struct buf { 574 | char *mem; 575 | size_t len; 576 | size_t off; 577 | }; 578 | 579 | static inline void 580 | buf_init(struct buf *b) 581 | { 582 | b->off = 0; 583 | } 584 | 585 | static void 586 | buf_resize(struct buf *b) 587 | { 588 | b->len += pagesize; 589 | b->mem = realloc(b->mem, b->len); 590 | if (b->mem == NULL) 591 | lerr(1, "buffer resize"); 592 | } 593 | 594 | static void 595 | buf_reserve(struct buf *b) 596 | { 597 | if ((b->off + pagesize) > b->len) 598 | buf_resize(b); 599 | } 600 | 601 | static void 602 | buf_cat(struct buf *b, const char *str) 603 | { 604 | size_t off, rv; 605 | 606 | buf_reserve(b); 607 | 608 | for (;;) { 609 | rv = strlcpy(b->mem + b->off, str, b->len - b->off); 610 | off = b->off + rv; 611 | if (off < b->len) 612 | break; 613 | 614 | buf_resize(b); 615 | } 616 | 617 | b->off = off; 618 | } 619 | 620 | static void 621 | buf_printf(struct buf *b, const char *fmt, ...) 622 | { 623 | va_list ap; 624 | size_t off; 625 | int rv; 626 | 627 | buf_reserve(b); 628 | 629 | for (;;) { 630 | va_start(ap, fmt); 631 | rv = vsnprintf(b->mem + b->off, b->len - b->off, fmt, ap); 632 | va_end(ap); 633 | 634 | if (rv == -1) 635 | lerr(1, "%s", __func__); 636 | 637 | off = b->off + rv; 638 | if (off < b->len) 639 | break; 640 | 641 | buf_resize(b); 642 | } 643 | 644 | b->off = off; 645 | } 646 | 647 | static void 648 | do_clickhouse_sql(const struct buf *sqlbuf, size_t rows, const char *what) 649 | { 650 | static struct buf reqbuf; 651 | int sock; 652 | struct iovec iov[2]; 653 | FILE *ss; 654 | char head[256]; 655 | 656 | buf_init(&reqbuf); 657 | 658 | sock = clickhouse_connect(); 659 | if (sock == -1) { 660 | /* error was already logged */ 661 | return; 662 | } 663 | 664 | buf_printf(&reqbuf, "POST / HTTP/1.0\r\n"); 665 | buf_printf(&reqbuf, "Host: %s:%s\r\n", 666 | clickhouse_host, clickhouse_port); 667 | if (clickhouse_database != NULL) { 668 | buf_printf(&reqbuf, "X-ClickHouse-Database: %s\r\n", 669 | clickhouse_database); 670 | } 671 | buf_printf(&reqbuf, "X-ClickHouse-User: %s\r\n", clickhouse_user); 672 | if (clickhouse_key != NULL) 673 | buf_printf(&reqbuf, "X-ClickHouse-Key: %s\r\n", clickhouse_key); 674 | buf_printf(&reqbuf, "Content-Length: %zu\r\n", sqlbuf->off); 675 | buf_printf(&reqbuf, "Content-Type: text/sql\r\n"); 676 | buf_printf(&reqbuf, "\r\n"); 677 | 678 | iov[0].iov_base = reqbuf.mem; 679 | iov[0].iov_len = reqbuf.off; 680 | iov[1].iov_base = sqlbuf->mem; 681 | iov[1].iov_len = sqlbuf->off; 682 | 683 | writev(sock, iov, nitems(iov)); /* XXX */ 684 | 685 | ss = fdopen(sock, "r"); 686 | if (ss == NULL) 687 | lerr(1, "fdopen"); 688 | 689 | fgets(head, sizeof (head), ss); 690 | head[strlen(head) - 1] = '\0'; 691 | head[strlen(head) - 1] = '\0'; 692 | if (strcmp(head, "HTTP/1.0 200 OK") != 0) 693 | lwarnx("clickhouse: error: returned %s", head); 694 | 695 | if (debug) { 696 | linfo("clickhouse: POST of %zu %s rows (%zu bytes): %s", 697 | rows, what, sqlbuf->off, head); 698 | } 699 | 700 | fclose(ss); 701 | } 702 | 703 | static uint32_t 704 | tv_to_msec(const struct timeval *tv) 705 | { 706 | uint32_t msecs; 707 | 708 | msecs = tv->tv_sec * 1000; 709 | msecs += tv->tv_usec / 1000; 710 | 711 | return (msecs); 712 | } 713 | 714 | static void 715 | timeslice_post_flows(struct timeslice *ts, struct buf *sqlbuf, 716 | const char *st, const char *et) 717 | { 718 | char ipbuf[NI_MAXHOST]; 719 | struct flow *f, *nf; 720 | const struct flow_key *k; 721 | size_t rows = 0; 722 | const char *join = ""; 723 | 724 | if (TAILQ_EMPTY(&ts->ts_flow_list)) 725 | return; 726 | 727 | buf_init(sqlbuf); 728 | buf_cat(sqlbuf, "INSERT INTO flows (" 729 | "begin_at, end_at, " 730 | "vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key, " 731 | "packets, bytes, frags, " 732 | "syns, fins, rsts, rstacks, mintcpwin, maxtcpwin, " 733 | "minpktlen, maxpktlen, min_ttl, max_ttl, pkt_lens" 734 | ")\n" "FORMAT Values\n"); 735 | 736 | TAILQ_FOREACH_SAFE(f, &ts->ts_flow_list, f_entry_list, nf) { 737 | const char *mjoin = ""; 738 | unsigned int i; 739 | 740 | k = &f->f_key; 741 | buf_printf(sqlbuf, "%s(%s,%s,", join, st, et); 742 | buf_printf(sqlbuf, "%u,%u,%u,", k->k_vlan, k->k_ipv, 743 | k->k_ipproto); 744 | if (k->k_ipv == 4) { 745 | inet_ntop(PF_INET, &k->k_saddr4, ipbuf, sizeof(ipbuf)); 746 | buf_printf(sqlbuf, "IPv4ToIPv6(toIPv4('%s')),", ipbuf); 747 | inet_ntop(PF_INET, &k->k_daddr4, ipbuf, sizeof(ipbuf)); 748 | buf_printf(sqlbuf, "IPv4ToIPv6(toIPv4('%s')),", ipbuf); 749 | } else if (k->k_ipv == 6) { 750 | inet_ntop(PF_INET6, &k->k_saddr6, ipbuf, sizeof(ipbuf)); 751 | buf_printf(sqlbuf, "toIPv6('%s'),", ipbuf); 752 | inet_ntop(PF_INET6, &k->k_daddr6, ipbuf, sizeof(ipbuf)); 753 | buf_printf(sqlbuf, "toIPv6('%s'),", ipbuf); 754 | } else { 755 | buf_printf(sqlbuf, "toIPv6('::'),toIPv6('::'),"); 756 | } 757 | buf_printf(sqlbuf, 758 | "%u,%u,%u,%llu,%llu,%llu,%llu,%llu,%llu,%llu," 759 | "%u,%u,%u,%u,%u,%u,{", 760 | ntohs(k->k_sport), ntohs(k->k_dport), ntohl(k->k_gre_key), 761 | f->f_packets, f->f_bytes, f->f_frags, 762 | f->f_syns, f->f_fins, f->f_rsts, f->f_rstacks, 763 | f->f_min_tcpwin, f->f_max_tcpwin, 764 | f->f_min_pktlen, f->f_max_pktlen, 765 | f->f_min_ttl, f->f_max_ttl); 766 | for (i = 0; i < nitems(f->f_pkt_lens); i++) { 767 | uint64_t pkts = f->f_pkt_lens[i]; 768 | if (pkts == 0) 769 | continue; 770 | 771 | buf_printf(sqlbuf, "%s%u:%llu", mjoin, 772 | pkt_lens[i], pkts); 773 | 774 | mjoin = ","; 775 | } 776 | buf_printf(sqlbuf, "})"); 777 | 778 | free(f); 779 | join = ",\n"; 780 | 781 | ++rows; 782 | } 783 | buf_printf(sqlbuf, ";\n"); 784 | 785 | do_clickhouse_sql(sqlbuf, rows, "flow"); 786 | } 787 | 788 | static void 789 | timeslice_post_flowstats(struct timeslice *ts, struct buf *sqlbuf, 790 | const char *st, const char *et) 791 | { 792 | buf_init(sqlbuf); 793 | buf_cat(sqlbuf, "INSERT INTO flowstats (" 794 | "begin_at, end_at, user_ms, kern_ms, " 795 | "reads, packets, bytes, flows, " 796 | "pcap_recv, pcap_drop, pcap_ifdrop, mdrop" 797 | ")\n" "FORMAT Values\n"); 798 | buf_printf(sqlbuf, "(%s,%s,", st, et); 799 | buf_printf(sqlbuf, "%u,%u,", 800 | tv_to_msec(&ts->ts_utime), tv_to_msec(&ts->ts_stime)); 801 | buf_printf(sqlbuf, "%llu,%llu,%llu,%lu,", ts->ts_reads, 802 | ts->ts_packets, ts->ts_bytes, ts->ts_flow_count); 803 | buf_printf(sqlbuf, "%u,%u,%u,%llu", ts->ts_pcap_recv, ts->ts_pcap_drop, 804 | ts->ts_pcap_ifdrop, ts->ts_mdrop); 805 | buf_cat(sqlbuf, ");\n"); 806 | 807 | do_clickhouse_sql(sqlbuf, 1, "flowstats"); 808 | } 809 | 810 | static void 811 | timeslice_post(void *arg) 812 | { 813 | static struct buf sqlbuf; 814 | struct timeslice *ts = arg; 815 | 816 | char stbuf[128], etbuf[128]; 817 | snprintf(stbuf, sizeof(stbuf), "%lld.%06lld", 818 | (long long)ts->ts_begin.tv_sec, (long long)ts->ts_begin.tv_usec); 819 | snprintf(etbuf, sizeof(etbuf), "%lld.%06lld", 820 | (long long)ts->ts_end.tv_sec, (long long)ts->ts_end.tv_usec); 821 | 822 | timeslice_post_flows(ts, &sqlbuf, stbuf, etbuf); 823 | timeslice_post_flowstats(ts, &sqlbuf, stbuf, etbuf); 824 | 825 | free(ts); 826 | } 827 | 828 | struct timeslice * 829 | timeslice_alloc(const struct timeval *now) 830 | { 831 | struct timeslice *ts; 832 | 833 | ts = calloc(1, sizeof(*ts)); 834 | if (ts == NULL) 835 | return (NULL); 836 | 837 | ts->ts_begin = *now; 838 | ts->ts_flow_count = 0; 839 | RBT_INIT(flow_tree, &ts->ts_flow_tree); 840 | TAILQ_INIT(&ts->ts_flow_list); 841 | 842 | task_set(&ts->ts_task, timeslice_post, ts); 843 | 844 | return (ts); 845 | } 846 | 847 | static struct flow * 848 | flow_alloc(void) 849 | { 850 | struct flow *f; 851 | size_t i; 852 | 853 | f = malloc(sizeof(*f)); 854 | if (f == NULL) 855 | return (NULL); 856 | 857 | for (i = 0; i < nitems(f->f_pkt_lens); i++) 858 | f->f_pkt_lens[i] = 0; 859 | 860 | return (f); 861 | } 862 | 863 | static void 864 | flow_tick(int nope, short events, void *arg) 865 | { 866 | struct flow_daemon *d = arg; 867 | struct pkt_source *ps; 868 | struct timeslice *ts = d->d_ts; 869 | struct timeslice *nts; 870 | struct timeval now; 871 | unsigned int gen; 872 | struct rusage *oru, *nru; 873 | 874 | gettimeofday(&now, NULL); 875 | 876 | evtimer_add(&d->d_tick, &d->d_tv); 877 | 878 | nts = timeslice_alloc(&now); 879 | if (nts == NULL) { 880 | /* just make this ts wider if we can't get a new one */ 881 | return; 882 | } 883 | 884 | TAILQ_FOREACH(ps, &d->d_pkt_sources, ps_entry) { 885 | struct pcap_stat pstat; 886 | 887 | pkt_capture(pcap_get_selectable_fd(ps->ps_ph), 0, ps); 888 | 889 | memset(&pstat, 0, sizeof(pstat)); /* for ifdrop */ 890 | 891 | if (pcap_stats(ps->ps_ph, &pstat) != 0) 892 | lerrx(1, "%s %s", ps->ps_name, pcap_geterr(ps->ps_ph)); 893 | 894 | ts->ts_pcap_recv += pstat.ps_recv - ps->ps_pstat.ps_recv; 895 | ts->ts_pcap_drop += pstat.ps_drop - ps->ps_pstat.ps_drop; 896 | ts->ts_pcap_ifdrop += pstat.ps_ifdrop - ps->ps_pstat.ps_ifdrop; 897 | 898 | ps->ps_pstat = pstat; 899 | } 900 | 901 | gen = d->d_rusage_gen; 902 | oru = &d->d_rusage[gen % nitems(d->d_rusage)]; 903 | gen++; 904 | nru = &d->d_rusage[gen % nitems(d->d_rusage)]; 905 | d->d_rusage_gen = gen; 906 | 907 | if (getrusage(RUSAGE_THREAD, nru) == -1) 908 | lerr(1, "getrusage"); 909 | 910 | timersub(&nru->ru_utime, &oru->ru_utime, &ts->ts_utime); 911 | timersub(&nru->ru_stime, &oru->ru_stime, &ts->ts_stime); 912 | 913 | ts->ts_end = now; 914 | task_add(d->d_taskq, &ts->ts_task); 915 | 916 | d->d_ts = nts; 917 | } 918 | 919 | static int 920 | pkt_count_tcp(struct timeslice *ts, struct flow *f, 921 | const u_char *buf, u_int buflen) 922 | { 923 | const struct tcphdr *th; 924 | 925 | if (buflen < sizeof(*th)) { 926 | ts->ts_short_ipproto++; 927 | return (-1); 928 | } 929 | 930 | th = (const struct tcphdr *)buf; 931 | 932 | f->f_key.k_sport = th->th_sport; 933 | f->f_key.k_dport = th->th_dport; 934 | if ((th->th_flags & (TH_SYN | TH_ACK)) == TH_SYN) 935 | f->f_syns = 1; 936 | if (th->th_flags & TH_FIN) 937 | f->f_fins = 1; 938 | if (th->th_flags & TH_RST) { 939 | if (th->th_flags & TH_ACK) 940 | f->f_rstacks = 1; 941 | else 942 | f->f_rsts = 1; 943 | } 944 | f->f_min_tcpwin = f->f_max_tcpwin = ntohs(th->th_win); 945 | 946 | return (0); 947 | } 948 | 949 | static int 950 | pkt_count_udp(struct timeslice *ts, struct flow *f, 951 | const u_char *buf, u_int buflen) 952 | { 953 | const struct udphdr *uh; 954 | 955 | if (buflen < sizeof(*uh)) { 956 | ts->ts_short_ipproto++; 957 | return (-1); 958 | } 959 | 960 | uh = (const struct udphdr *)buf; 961 | 962 | f->f_key.k_sport = uh->uh_sport; 963 | f->f_key.k_dport = uh->uh_dport; 964 | 965 | return (0); 966 | } 967 | 968 | static int 969 | pkt_count_gre(struct timeslice *ts, struct flow *f, 970 | const u_char *buf, u_int buflen) 971 | { 972 | const struct gre_header *gh; 973 | const struct gre_h_key *gkh; 974 | u_int hlen; 975 | 976 | if (buflen < sizeof(*gh)) { 977 | ts->ts_short_ipproto++; 978 | return (-1); 979 | } 980 | 981 | gh = (const struct gre_header *)buf; 982 | 983 | f->f_key.k_gre_flags = gh->gre_flags; 984 | f->f_key.k_gre_proto = gh->gre_proto; 985 | 986 | if (!flow_gre_key_valid(f)) 987 | return (0); 988 | 989 | hlen = sizeof(*gh); 990 | if (ISSET(f->f_key.k_gre_flags, htons(GRE_CP))) 991 | hlen += sizeof(struct gre_h_cksum); 992 | gkh = (const struct gre_h_key *)buf; 993 | hlen += sizeof(*gkh); 994 | if (buflen < hlen) { 995 | return ts->ts_short_ipproto++; 996 | return (-1); 997 | } 998 | 999 | f->f_key.k_gre_key = gkh->gre_key; 1000 | 1001 | return (0); 1002 | } 1003 | 1004 | static int 1005 | pkt_count_esp(struct timeslice *ts, struct flow *f, 1006 | const u_char *buf, u_int buflen) 1007 | { 1008 | const struct esp_header *eh; 1009 | 1010 | if (buflen < sizeof(*eh)) { 1011 | ts->ts_short_ipproto++; 1012 | return (-1); 1013 | } 1014 | 1015 | eh = (const struct esp_header *)buf; 1016 | 1017 | f->f_key.k_ipsec_spi = eh->esp_spi; 1018 | 1019 | return (0); 1020 | } 1021 | 1022 | static int 1023 | pkt_count_ah(struct timeslice *ts, struct flow *f, 1024 | const u_char *buf, u_int buflen) 1025 | { 1026 | const struct ah_header *ah; 1027 | 1028 | if (buflen < sizeof(*ah)) { 1029 | ts->ts_short_ipproto++; 1030 | return (-1); 1031 | } 1032 | 1033 | ah = (const struct ah_header *)buf; 1034 | 1035 | f->f_key.k_ipsec_spi = ah->ah_spi; 1036 | 1037 | return (0); 1038 | } 1039 | 1040 | static int 1041 | pkt_count_sctp(struct timeslice *ts, struct flow *f, 1042 | const u_char *buf, u_int buflen) 1043 | { 1044 | const struct sctp_header *sh; 1045 | 1046 | if (buflen < sizeof(*sh)) { 1047 | ts->ts_short_ipproto++; 1048 | return (-1); 1049 | } 1050 | 1051 | sh = (const struct sctp_header *)buf; 1052 | 1053 | f->f_key.k_sport = sh->sctp_sport; 1054 | f->f_key.k_dport = sh->sctp_dport; 1055 | f->f_key.k_gre_key = sh->sctp_vtag; 1056 | 1057 | return (0); 1058 | } 1059 | 1060 | static int 1061 | pkt_count_ipproto(struct timeslice *ts, struct flow *f, 1062 | const u_char *buf, u_int buflen) 1063 | { 1064 | switch (f->f_key.k_ipproto) { 1065 | case IPPROTO_TCP: 1066 | return (pkt_count_tcp(ts, f, buf, buflen)); 1067 | case IPPROTO_UDP: 1068 | case IPPROTO_UDPLITE: 1069 | return (pkt_count_udp(ts, f, buf, buflen)); 1070 | case IPPROTO_GRE: 1071 | return (pkt_count_gre(ts, f, buf, buflen)); 1072 | case IPPROTO_ESP: 1073 | return (pkt_count_esp(ts, f, buf, buflen)); 1074 | case IPPROTO_AH: 1075 | return (pkt_count_ah(ts, f, buf, buflen)); 1076 | case IPPROTO_SCTP: 1077 | return (pkt_count_sctp(ts, f, buf, buflen)); 1078 | } 1079 | 1080 | return (0); 1081 | } 1082 | 1083 | static int 1084 | pkt_count_icmp4(struct timeslice *ts, struct flow *f, 1085 | const u_char *buf, u_int buflen) 1086 | { 1087 | const struct icmp *icmp4h; 1088 | 1089 | if (buflen < offsetof(struct icmp, icmp_cksum)) { 1090 | ts->ts_short_ipproto++; 1091 | return (-1); 1092 | } 1093 | 1094 | icmp4h = (const struct icmp *)buf; 1095 | 1096 | f->f_key.k_icmp_type = htons(icmp4h->icmp_type); 1097 | f->f_key.k_icmp_code = htons(icmp4h->icmp_code); 1098 | switch (icmp4h->icmp_type) { 1099 | case ICMP_ECHO: 1100 | case ICMP_ECHOREPLY: 1101 | if (buflen < offsetof(struct icmp, icmp_seq)) { 1102 | ts->ts_short_ipproto++; 1103 | return (-1); 1104 | } 1105 | f->f_key.k_gre_key = htonl(ntohs(icmp4h->icmp_id)); 1106 | break; 1107 | } 1108 | 1109 | return (0); 1110 | } 1111 | 1112 | static int 1113 | pkt_count_ip4(struct timeslice *ts, struct flow *f, 1114 | const u_char *buf, u_int buflen) 1115 | { 1116 | const struct ip *iph; 1117 | u_int hlen; 1118 | uint8_t proto; 1119 | 1120 | if (buflen < sizeof(*iph)) { 1121 | ts->ts_short_ip4++; 1122 | return (-1); 1123 | } 1124 | 1125 | iph = (const struct ip *)buf; 1126 | 1127 | /* XXX check ipv and all that poop? */ 1128 | 1129 | hlen = iph->ip_hl << 2; 1130 | if (buflen < hlen) { 1131 | ts->ts_short_ip4++; 1132 | return (-1); 1133 | } 1134 | 1135 | buf += hlen; 1136 | buflen -= hlen; 1137 | 1138 | proto = iph->ip_p; 1139 | 1140 | if (iph->ip_off & htons(~(IP_DF | IP_RF))) { 1141 | if ((iph->ip_off & htons(IP_OFFMASK)) != htons(0)) 1142 | proto = IPPROTO_FRAGMENT; 1143 | 1144 | f->f_frags = 1; 1145 | } 1146 | 1147 | f->f_key.k_ipv = 4; 1148 | f->f_key.k_ipproto = proto; 1149 | f->f_key.k_saddr4 = iph->ip_src; 1150 | f->f_key.k_daddr4 = iph->ip_dst; 1151 | f->f_min_ttl = f->f_max_ttl = iph->ip_ttl; 1152 | 1153 | if (f->f_key.k_ipproto == IPPROTO_ICMP) 1154 | return (pkt_count_icmp4(ts, f, buf, buflen)); 1155 | 1156 | return (pkt_count_ipproto(ts, f, buf, buflen)); 1157 | } 1158 | 1159 | static int 1160 | pkt_count_icmp6(struct timeslice *ts, struct flow *f, 1161 | const u_char *buf, u_int buflen) 1162 | { 1163 | const struct icmp6_hdr *icmp6h; 1164 | 1165 | if (buflen < offsetof(struct icmp6_hdr, icmp6_cksum)) { 1166 | ts->ts_short_ipproto++; 1167 | return (-1); 1168 | } 1169 | 1170 | icmp6h = (const struct icmp6_hdr *)buf; 1171 | 1172 | f->f_key.k_icmp_type = htons(icmp6h->icmp6_type); 1173 | f->f_key.k_icmp_code = htons(icmp6h->icmp6_code); 1174 | 1175 | switch (icmp6h->icmp6_type) { 1176 | case ICMP6_ECHO_REQUEST: 1177 | case ICMP6_ECHO_REPLY: 1178 | if (buflen < offsetof(struct icmp6_hdr, icmp6_seq)) { 1179 | ts->ts_short_ipproto++; 1180 | return (-1); 1181 | } 1182 | f->f_key.k_gre_key = htonl(ntohs(icmp6h->icmp6_id)); 1183 | break; 1184 | } 1185 | 1186 | return (0); 1187 | } 1188 | 1189 | static int 1190 | pkt_count_ip6(struct timeslice *ts, struct flow *f, 1191 | const u_char *buf, u_int buflen) 1192 | { 1193 | const struct ip6_hdr *ip6; 1194 | uint8_t nxt; 1195 | 1196 | if (buflen < sizeof(*ip6)) { 1197 | ts->ts_short_ip6++; 1198 | return (-1); 1199 | } 1200 | 1201 | ip6 = (const struct ip6_hdr *)buf; 1202 | 1203 | /* XXX check ipv and all that poop? */ 1204 | 1205 | buf += sizeof(*ip6); 1206 | buflen -= sizeof(*ip6); 1207 | 1208 | nxt = ip6->ip6_nxt; 1209 | if (nxt == IPPROTO_FRAGMENT) { 1210 | const struct ip6_frag *ip6f; 1211 | 1212 | if (buflen < sizeof(*ip6f)) { 1213 | ts->ts_short_ip6++; 1214 | return (-1); 1215 | } 1216 | 1217 | ip6f = (const struct ip6_frag *)buf; 1218 | if ((ip6f->ip6f_offlg & htons(IP6F_OFF_MASK)) == htons(0)) { 1219 | /* we can parse the first fragment */ 1220 | buf += sizeof(*ip6f); 1221 | buflen -= sizeof(*ip6f); 1222 | nxt = ip6f->ip6f_nxt; 1223 | } 1224 | 1225 | f->f_frags = 1; 1226 | } 1227 | 1228 | f->f_key.k_ipv = 6; 1229 | f->f_key.k_ipproto = nxt; 1230 | f->f_key.k_saddr6 = ip6->ip6_src; 1231 | f->f_key.k_daddr6 = ip6->ip6_dst; 1232 | f->f_min_ttl = f->f_max_ttl = ip6->ip6_hlim; 1233 | 1234 | if (nxt == IPPROTO_ICMPV6) 1235 | return (pkt_count_icmp6(ts, f, buf, buflen)); 1236 | 1237 | return (pkt_count_ipproto(ts, f, buf, buflen)); 1238 | } 1239 | 1240 | static inline unsigned int 1241 | pkt_len_bucket(unsigned int pktlen) 1242 | { 1243 | unsigned int i; 1244 | 1245 | /* the last bucket is special */ 1246 | for (i = 0; i < NUM_PKT_LENS - 1; i++) { 1247 | if (pktlen <= pkt_lens[i]) 1248 | break; 1249 | } 1250 | 1251 | return (i); 1252 | } 1253 | 1254 | static void 1255 | pkt_count(u_char *arg, const struct pcap_pkthdr *hdr, const u_char *buf) 1256 | { 1257 | struct flow_daemon *d = (struct flow_daemon *)arg; 1258 | struct timeslice *ts = d->d_ts; 1259 | struct flow *f = d->d_flow; 1260 | struct flow *of; 1261 | unsigned int len_bucket; 1262 | 1263 | struct ether_header *eh; 1264 | uint16_t type; 1265 | u_int hlen = sizeof(*eh); 1266 | 1267 | u_int buflen = hdr->caplen; 1268 | u_int pktlen = hdr->len; 1269 | 1270 | memset(&f->f_key, 0, sizeof(f->f_key)); 1271 | 1272 | if (buflen < hlen) { 1273 | ts->ts_short_ether++; 1274 | return; 1275 | } 1276 | 1277 | eh = (struct ether_header *)buf; 1278 | type = eh->ether_type; 1279 | 1280 | if (type == htons(ETHERTYPE_VLAN)) { 1281 | struct ether_vlan_header *evh; 1282 | hlen = sizeof(*evh); 1283 | 1284 | if (buflen < hlen) { 1285 | ts->ts_short_vlan++; 1286 | return; 1287 | } 1288 | 1289 | evh = (struct ether_vlan_header *)buf; 1290 | f->f_key.k_vlan = EVL_VLANOFTAG(htons(evh->evl_tag)); 1291 | type = evh->evl_proto; 1292 | } else 1293 | f->f_key.k_vlan = FLOW_VLAN_UNSET; 1294 | 1295 | buf += hlen; 1296 | buflen -= hlen; 1297 | pktlen -= hlen; 1298 | 1299 | ts->ts_packets++; 1300 | ts->ts_bytes += pktlen; 1301 | 1302 | f->f_packets = 1; 1303 | f->f_bytes = pktlen; 1304 | f->f_frags = 0; 1305 | f->f_syns = 0; 1306 | f->f_fins = 0; 1307 | f->f_rsts = 0; 1308 | f->f_rstacks = 0; 1309 | f->f_min_tcpwin = 0; 1310 | f->f_max_tcpwin = 0; 1311 | f->f_min_pktlen = pktlen; 1312 | f->f_max_pktlen = pktlen; 1313 | f->f_min_ttl = f->f_max_ttl = 0; 1314 | 1315 | switch (type) { 1316 | case htons(ETHERTYPE_IP): 1317 | if (pkt_count_ip4(ts, f, buf, buflen) == -1) 1318 | return; 1319 | break; 1320 | case htons(ETHERTYPE_IPV6): 1321 | if (pkt_count_ip6(ts, f, buf, buflen) == -1) 1322 | return; 1323 | break; 1324 | 1325 | default: 1326 | ts->ts_nonip++; 1327 | return; 1328 | } 1329 | 1330 | of = RBT_INSERT(flow_tree, &ts->ts_flow_tree, f); 1331 | if (of == NULL) { 1332 | struct flow *nf = flow_alloc(); 1333 | if (nf == NULL) { 1334 | /* drop this packet due to lack of memory */ 1335 | RBT_REMOVE(flow_tree, &ts->ts_flow_tree, f); 1336 | ts->ts_mdrop++; 1337 | return; 1338 | } 1339 | d->d_flow = nf; 1340 | 1341 | ts->ts_flow_count++; 1342 | TAILQ_INSERT_TAIL(&ts->ts_flow_list, f, f_entry_list); 1343 | 1344 | of = f; 1345 | } else { 1346 | of->f_packets++; 1347 | of->f_bytes += f->f_bytes; 1348 | of->f_frags += f->f_frags; 1349 | 1350 | if (f->f_key.k_ipproto == IPPROTO_TCP) { 1351 | of->f_syns += f->f_syns; 1352 | of->f_fins += f->f_fins; 1353 | of->f_rsts += f->f_rsts; 1354 | of->f_rstacks += f->f_rstacks; 1355 | 1356 | if (of->f_min_tcpwin > f->f_min_tcpwin) 1357 | of->f_min_tcpwin = f->f_min_tcpwin; 1358 | if (of->f_max_tcpwin < f->f_max_tcpwin) 1359 | of->f_max_tcpwin = f->f_max_tcpwin; 1360 | } 1361 | 1362 | if (of->f_min_pktlen > f->f_min_pktlen) 1363 | of->f_min_pktlen = f->f_min_pktlen; 1364 | if (of->f_max_pktlen < f->f_max_pktlen) 1365 | of->f_max_pktlen = f->f_max_pktlen; 1366 | 1367 | if (of->f_min_ttl > f->f_min_ttl) 1368 | of->f_min_ttl = f->f_min_ttl; 1369 | if (of->f_max_ttl < f->f_max_ttl) 1370 | of->f_max_ttl = f->f_max_ttl; 1371 | } 1372 | 1373 | len_bucket = pkt_len_bucket(pktlen); 1374 | of->f_pkt_lens[len_bucket]++; 1375 | } 1376 | 1377 | void 1378 | pkt_capture(int fd, short events, void *arg) 1379 | { 1380 | struct pkt_source *ps = arg; 1381 | struct flow_daemon *d = ps->ps_d; 1382 | struct timeslice *ts = d->d_ts; 1383 | 1384 | if (pcap_dispatch(ps->ps_ph, -1, pkt_count, (u_char *)d) < 0) 1385 | lerrx(1, "%s", pcap_geterr(ps->ps_ph)); 1386 | 1387 | ts->ts_reads++; 1388 | } 1389 | 1390 | RBT_GENERATE(flow_tree, flow, f_entry_tree, flow_cmp); 1391 | 1392 | /* daemon(3) clone, intended to be used in a "r"estricted environment */ 1393 | int 1394 | rdaemon(int devnull) 1395 | { 1396 | if (devnull == -1) { 1397 | errno = EBADF; 1398 | return (-1); 1399 | } 1400 | if (fcntl(devnull, F_GETFL) == -1) 1401 | return (-1); 1402 | 1403 | switch (fork()) { 1404 | case -1: 1405 | return (-1); 1406 | case 0: 1407 | break; 1408 | default: 1409 | _exit(0); 1410 | } 1411 | 1412 | if (setsid() == -1) 1413 | return (-1); 1414 | 1415 | (void)dup2(devnull, STDIN_FILENO); 1416 | (void)dup2(devnull, STDOUT_FILENO); 1417 | (void)dup2(devnull, STDERR_FILENO); 1418 | if (devnull > 2) 1419 | (void)close(devnull); 1420 | 1421 | return (0); 1422 | } 1423 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (c) 2008 David Gwynne 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "log.h" 28 | 29 | static const struct __logger conslogger = { 30 | err, 31 | errx, 32 | warn, 33 | warnx, 34 | warnx, /* info */ 35 | warnx /* debug */ 36 | }; 37 | 38 | __dead static void syslog_err(int, const char *, ...) 39 | __attribute__((__format__ (printf, 2, 3))); 40 | __dead static void syslog_errx(int, const char *, ...) 41 | __attribute__((__format__ (printf, 2, 3))); 42 | static void syslog_warn(const char *, ...) 43 | __attribute__((__format__ (printf, 1, 2))); 44 | static void syslog_warnx(const char *, ...) 45 | __attribute__((__format__ (printf, 1, 2))); 46 | static void syslog_info(const char *, ...) 47 | __attribute__((__format__ (printf, 1, 2))); 48 | static void syslog_debug(const char *, ...) 49 | __attribute__((__format__ (printf, 1, 2))); 50 | static void syslog_vstrerror(int, int, const char *, va_list) 51 | __attribute__((__format__ (printf, 3, 0))); 52 | 53 | static const struct __logger syslogger = { 54 | syslog_err, 55 | syslog_errx, 56 | syslog_warn, 57 | syslog_warnx, 58 | syslog_info, 59 | syslog_debug 60 | }; 61 | 62 | const struct __logger *__logger = &conslogger; 63 | 64 | void 65 | logger_syslog(const char *progname) 66 | { 67 | openlog(progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); 68 | tzset(); 69 | 70 | __logger = &syslogger; 71 | } 72 | 73 | static void 74 | syslog_vstrerror(int e, int priority, const char *fmt, va_list ap) 75 | { 76 | char *s; 77 | 78 | if (vasprintf(&s, fmt, ap) == -1) { 79 | syslog(LOG_EMERG, "unable to alloc in syslog_vstrerror"); 80 | exit(1); 81 | } 82 | 83 | syslog(priority, "%s: %s", s, strerror(e)); 84 | 85 | free(s); 86 | } 87 | 88 | static void 89 | syslog_err(int ecode, const char *fmt, ...) 90 | { 91 | va_list ap; 92 | 93 | va_start(ap, fmt); 94 | syslog_vstrerror(errno, LOG_CRIT, fmt, ap); 95 | va_end(ap); 96 | 97 | exit(ecode); 98 | } 99 | 100 | static void 101 | syslog_errx(int ecode, const char *fmt, ...) 102 | { 103 | va_list ap; 104 | 105 | va_start(ap, fmt); 106 | vsyslog(LOG_CRIT, fmt, ap); 107 | va_end(ap); 108 | 109 | exit(ecode); 110 | } 111 | 112 | static void 113 | syslog_warn(const char *fmt, ...) 114 | { 115 | va_list ap; 116 | 117 | va_start(ap, fmt); 118 | syslog_vstrerror(errno, LOG_ERR, fmt, ap); 119 | va_end(ap); 120 | } 121 | 122 | static void 123 | syslog_warnx(const char *fmt, ...) 124 | { 125 | va_list ap; 126 | 127 | va_start(ap, fmt); 128 | vsyslog(LOG_ERR, fmt, ap); 129 | va_end(ap); 130 | } 131 | 132 | static void 133 | syslog_info(const char *fmt, ...) 134 | { 135 | va_list ap; 136 | 137 | va_start(ap, fmt); 138 | vsyslog(LOG_INFO, fmt, ap); 139 | va_end(ap); 140 | } 141 | 142 | static void 143 | syslog_debug(const char *fmt, ...) 144 | { 145 | va_list ap; 146 | 147 | va_start(ap, fmt); 148 | vsyslog(LOG_DEBUG, fmt, ap); 149 | va_end(ap); 150 | } 151 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Copyright (c) 2008 David Gwynne 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the above 7 | * copyright notice and this permission notice appear in all copies. 8 | * 9 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | */ 17 | 18 | #ifndef _LOG_H_ 19 | #define _LOG_H_ 20 | 21 | struct __logger { 22 | __dead void (*err)(int, const char *, ...) 23 | __attribute__((__format__ (printf, 2, 3))); 24 | __dead void (*errx)(int, const char *, ...) 25 | __attribute__((__format__ (printf, 2, 3))); 26 | void (*warn)(const char *, ...) 27 | __attribute__((__format__ (printf, 1, 2))); 28 | void (*warnx)(const char *, ...) 29 | __attribute__((__format__ (printf, 1, 2))); 30 | void (*info)(const char *, ...) 31 | __attribute__((__format__ (printf, 1, 2))); 32 | void (*debug)(const char *, ...) 33 | __attribute__((__format__ (printf, 1, 2))); 34 | }; 35 | 36 | extern const struct __logger *__logger; 37 | 38 | #define lerr(_e, _f...) __logger->err((_e), _f) 39 | #define lerrx(_e, _f...) __logger->errx((_e), _f) 40 | #define lwarn(_f...) __logger->warn(_f) 41 | #define lwarnx(_f...) __logger->warnx(_f) 42 | #define linfo(_f...) __logger->info(_f) 43 | #define ldebug(_f...) __logger->debug(_f) 44 | 45 | void logger_syslog(const char *); 46 | 47 | #endif /* _LOG_H_ */ 48 | -------------------------------------------------------------------------------- /setup.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE flows 2 | ( 3 | `begin_at` DateTime64(3) CODEC(DoubleDelta), 4 | `end_at` DateTime64(3) CODEC(DoubleDelta), 5 | `vlan` UInt16 CODEC(Gorilla), 6 | `ipv` UInt8 CODEC(NONE), 7 | `ipproto` UInt8 CODEC(NONE), 8 | `saddr` IPv6 CODEC(ZSTD(3)), 9 | `daddr` IPv6 CODEC(ZSTD(3)), 10 | `sport` UInt16 CODEC(NONE), 11 | `dport` UInt16 CODEC(NONE), 12 | `gre_key` UInt32 CODEC(Gorilla), 13 | `packets` UInt64 CODEC(Gorilla), 14 | `bytes` UInt64 CODEC(Gorilla), 15 | `frags` UInt64 CODEC(Gorilla), 16 | `syns` UInt32 CODEC(Gorilla), 17 | `fins` UInt32 CODEC(Gorilla), 18 | `rsts` UInt32 CODEC(Gorilla), 19 | `mintcpwin` UInt16 CODEC(DoubleDelta), 20 | `maxtcpwin` UInt16 CODEC(DoubleDelta), 21 | `minpktlen` UInt32 CODEC(DoubleDelta), 22 | `maxpktlen` UInt32 CODEC(DoubleDelta), 23 | `min_ttl` UInt8 CODEC(Gorilla), 24 | `max_ttl` UInt8 CODEC(Gorilla), 25 | `pkt_lens` Map(UInt16, UInt64) CODEC(DoubleDelta), 26 | INDEX begin_at_idx begin_at TYPE minmax GRANULARITY 2048, 27 | INDEX end_at_idx end_at TYPE minmax GRANULARITY 2048 28 | ) 29 | ENGINE = SummingMergeTree() 30 | PARTITION BY toStartOfDay(begin_at) 31 | ORDER BY (vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key, begin_at, end_at) 32 | TTL toDateTime(end_at) + toIntervalHour(4); 33 | 34 | CREATE TABLE flowstats 35 | ( 36 | `begin_at` DateTime64(3) CODEC(DoubleDelta), 37 | `end_at` DateTime64(3) CODEC(DoubleDelta), 38 | `user_ms` UInt32 CODEC(Gorilla), 39 | `kern_ms` UInt32 CODEC(Gorilla), 40 | `reads` UInt64 CODEC(Gorilla), 41 | `packets` UInt64 CODEC(Gorilla), 42 | `bytes` UInt64 CODEC(Gorilla), 43 | `flows` UInt32 CODEC(Gorilla), 44 | `pcap_recv` UInt32 CODEC(Gorilla), 45 | `pcap_drop` UInt32 CODEC(Gorilla), 46 | `pcap_ifdrop` UInt32 CODEC(Gorilla), 47 | `mdrop` UInt64 CODEC(Gorilla), 48 | INDEX begin_at_idx begin_at TYPE minmax GRANULARITY 2048, 49 | INDEX end_at_idx end_at TYPE minmax GRANULARITY 2048 50 | ) 51 | ENGINE = SummingMergeTree() 52 | PARTITION BY toStartOfDay(begin_at) 53 | ORDER BY (begin_at, end_at) 54 | TTL toDateTime(end_at) + toIntervalHour(4); 55 | 56 | CREATE TABLE dns_lookups 57 | ( 58 | `begin_at` DateTime64(3) CODEC(DoubleDelta), 59 | `end_at` DateTime64(3) CODEC(DoubleDelta), 60 | `saddr` FixedString(16) CODEC(ZSTD(1)), 61 | `daddr` FixedString(16) CODEC(ZSTD(1)), 62 | `sport` UInt16 CODEC(Gorilla), 63 | `dport` UInt16 CODEC(Gorilla), 64 | `qid` UInt16 CODEC(Gorilla), 65 | `name` String CODEC(ZSTD(1)), 66 | INDEX begin_at_idx begin_at TYPE minmax GRANULARITY 1024, 67 | INDEX end_at_idx end_at TYPE minmax GRANULARITY 1024 68 | ) 69 | ENGINE = MergeTree() 70 | PARTITION BY toStartOfDay(begin_at) 71 | ORDER BY (saddr, daddr, sport, dport, qid, begin_at, end_at); 72 | 73 | CREATE TABLE rdns 74 | ( 75 | `begin_at` DateTime64(3) CODEC(DoubleDelta), 76 | `end_at` DateTime64(3) CODEC(DoubleDelta), 77 | `addr` FixedString(16) CODEC(ZSTD(1)), 78 | `name` String CODEC(ZSTD(1)), 79 | INDEX end_at_idx end_at TYPE minmax GRANULARITY 1024, 80 | INDEX begin_at_idx begin_at TYPE minmax GRANULARITY 1024 81 | ) 82 | ENGINE = MergeTree() 83 | PARTITION BY toStartOfDay(end_at) 84 | ORDER BY (addr, end_at, begin_at); 85 | 86 | CREATE MATERIALIZED VIEW flows_5sec 87 | ( 88 | `interval` DateTime CODEC(DoubleDelta), 89 | `vlan` UInt16 CODEC(Gorilla), 90 | `ipv` UInt8 CODEC(NONE), 91 | `ipproto` UInt8 CODEC(NONE), 92 | `saddr` FixedString(16) CODEC(ZSTD(3)), 93 | `daddr` FixedString(16) CODEC(ZSTD(3)), 94 | `sport` UInt16 CODEC(Gorilla), 95 | `dport` UInt16 CODEC(Gorilla), 96 | `gre_key` UInt32 CODEC(Gorilla), 97 | `packets` UInt64 CODEC(Gorilla), 98 | `bytes` UInt64 CODEC(Gorilla), 99 | `syns` UInt64 CODEC(Gorilla), 100 | `fins` UInt64 CODEC(Gorilla), 101 | `rsts` UInt64 CODEC(Gorilla), 102 | INDEX interval_idx interval TYPE minmax GRANULARITY 2048, 103 | INDEX daddr_idx daddr TYPE bloom_filter(0.025) GRANULARITY 8192, 104 | INDEX daddrport_idx (ipproto, daddr, dport) TYPE bloom_filter(0.025) GRANULARITY 8192, 105 | INDEX dport_idx (ipproto, dport) TYPE bloom_filter(0.025) GRANULARITY 8192 106 | ) 107 | ENGINE = SummingMergeTree 108 | PARTITION BY toStartOfDay(interval) 109 | ORDER BY (vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key, interval) 110 | TTL interval + toIntervalDay(14) 111 | AS SELECT 112 | toStartOfInterval(begin_at, toIntervalSecond(5)) AS interval, 113 | vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key, 114 | sum(packets) AS packets, 115 | sum(bytes) AS bytes, 116 | sum(syns) AS syns, 117 | sum(fins) AS fins, 118 | sum(rsts) AS rsts 119 | FROM flows 120 | GROUP BY 121 | interval, vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key; 122 | 123 | CREATE MATERIALIZED VIEW flows_1min 124 | ( 125 | `interval` DateTime CODEC(DoubleDelta), 126 | `vlan` UInt16 CODEC(Gorilla), 127 | `ipv` UInt8 CODEC(NONE), 128 | `ipproto` UInt8 CODEC(NONE), 129 | `saddr` FixedString(16) CODEC(ZSTD(3)), 130 | `daddr` FixedString(16) CODEC(ZSTD(3)), 131 | `sport` UInt16 CODEC(Gorilla), 132 | `dport` UInt16 CODEC(Gorilla), 133 | `gre_key` UInt32 CODEC(Gorilla), 134 | `packets` UInt64 CODEC(Gorilla), 135 | `bytes` UInt64 CODEC(Gorilla), 136 | `syns` UInt64 CODEC(Gorilla), 137 | `fins` UInt64 CODEC(Gorilla), 138 | `rsts` UInt64 CODEC(Gorilla), 139 | INDEX interval_idx interval TYPE minmax GRANULARITY 2048, 140 | INDEX daddr_idx daddr TYPE bloom_filter(0.025) GRANULARITY 8192, 141 | INDEX daddrport_idx (ipproto, daddr, dport) TYPE bloom_filter(0.025) GRANULARITY 8192, 142 | INDEX dport_idx (ipproto, dport) TYPE bloom_filter(0.025) GRANULARITY 8192 143 | ) 144 | ENGINE = SummingMergeTree 145 | PARTITION BY toStartOfDay(interval) 146 | ORDER BY (vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key, interval) 147 | TTL interval + toIntervalDay(180) 148 | AS SELECT 149 | toStartOfInterval(begin_at, toIntervalSecond(60)) AS interval, 150 | vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key, 151 | sum(packets) AS packets, 152 | sum(bytes) AS bytes, 153 | sum(syns) AS syns, 154 | sum(fins) AS fins, 155 | sum(rsts) AS rsts 156 | FROM flows 157 | GROUP BY 158 | interval, vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key; 159 | 160 | CREATE MATERIALIZED VIEW flows_5min 161 | ( 162 | `interval` DateTime CODEC(DoubleDelta), 163 | `vlan` UInt16 CODEC(Gorilla), 164 | `ipv` UInt8 CODEC(NONE), 165 | `ipproto` UInt8 CODEC(NONE), 166 | `saddr` FixedString(16) CODEC(ZSTD(3)), 167 | `daddr` FixedString(16) CODEC(ZSTD(3)), 168 | `sport` UInt16 CODEC(Gorilla), 169 | `dport` UInt16 CODEC(Gorilla), 170 | `gre_key` UInt32 CODEC(Gorilla), 171 | `packets` UInt64 CODEC(Gorilla), 172 | `bytes` UInt64 CODEC(Gorilla), 173 | `syns` UInt64 CODEC(Gorilla), 174 | `fins` UInt64 CODEC(Gorilla), 175 | `rsts` UInt64 CODEC(Gorilla), 176 | INDEX interval_idx interval TYPE minmax GRANULARITY 2048, 177 | INDEX daddr_idx daddr TYPE bloom_filter(0.025) GRANULARITY 8192, 178 | INDEX daddrport_idx (ipproto, daddr, dport) TYPE bloom_filter(0.025) GRANULARITY 8192, 179 | INDEX dport_idx (ipproto, dport) TYPE bloom_filter(0.025) GRANULARITY 8192 180 | ) 181 | ENGINE = SummingMergeTree 182 | PARTITION BY toStartOfDay(interval) 183 | ORDER BY (vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key, interval) 184 | TTL interval + toIntervalDay(540) 185 | AS SELECT 186 | toStartOfInterval(begin_at, toIntervalSecond(300)) AS interval, 187 | vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key, 188 | sum(packets) AS packets, 189 | sum(bytes) AS bytes, 190 | sum(syns) AS syns, 191 | sum(fins) AS fins, 192 | sum(rsts) AS rsts 193 | FROM flows 194 | GROUP BY 195 | interval, vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key; 196 | 197 | CREATE MATERIALIZED VIEW flows_1hr 198 | ( 199 | `interval` DateTime CODEC(DoubleDelta), 200 | `vlan` UInt16 CODEC(Gorilla), 201 | `ipv` UInt8 CODEC(NONE), 202 | `ipproto` UInt8 CODEC(NONE), 203 | `saddr` FixedString(16) CODEC(ZSTD(3)), 204 | `daddr` FixedString(16) CODEC(ZSTD(3)), 205 | `sport` UInt16 CODEC(Gorilla), 206 | `dport` UInt16 CODEC(Gorilla), 207 | `gre_key` UInt32 CODEC(Gorilla), 208 | `packets` UInt64 CODEC(Gorilla), 209 | `bytes` UInt64 CODEC(Gorilla), 210 | `syns` UInt64 CODEC(Gorilla), 211 | `fins` UInt64 CODEC(Gorilla), 212 | `rsts` UInt64 CODEC(Gorilla), 213 | INDEX interval_idx interval TYPE minmax GRANULARITY 2048, 214 | INDEX daddr_idx daddr TYPE bloom_filter(0.025) GRANULARITY 8192, 215 | INDEX daddrport_idx (ipproto, daddr, dport) TYPE bloom_filter(0.025) GRANULARITY 8192, 216 | INDEX dport_idx (ipproto, dport) TYPE bloom_filter(0.025) GRANULARITY 8192 217 | ) 218 | ENGINE = SummingMergeTree 219 | PARTITION BY toStartOfDay(interval) 220 | ORDER BY (vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key, interval) 221 | AS SELECT 222 | toStartOfInterval(begin_at, toIntervalSecond(3600)) AS interval, 223 | vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key, 224 | sum(packets) AS packets, 225 | sum(bytes) AS bytes, 226 | sum(syns) AS syns, 227 | sum(fins) AS fins, 228 | sum(rsts) AS rsts 229 | FROM flows 230 | GROUP BY 231 | interval, vlan, ipv, ipproto, saddr, daddr, sport, dport, gre_key; 232 | -------------------------------------------------------------------------------- /task.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: task.c,v 1.2 2018/06/19 17:12:34 reyk Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2017 David Gwynne 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "task.h" 26 | 27 | #define ISSET(_v, _m) ((_v) & (_m)) 28 | #define SET(_v, _m) ((_v) |= (_m)) 29 | #define CLR(_v, _m) ((_v) &= ~(_m)) 30 | 31 | struct taskq { 32 | pthread_t thread; 33 | struct task_list list; 34 | pthread_mutex_t mtx; 35 | pthread_cond_t cv; 36 | }; 37 | 38 | #define TASK_ONQUEUE (1 << 0) 39 | 40 | static void *taskq_run(void *); 41 | 42 | struct taskq * 43 | taskq_create(const char *name) 44 | { 45 | struct taskq *tq; 46 | int error; 47 | 48 | tq = malloc(sizeof(*tq)); 49 | if (tq == NULL) 50 | return (NULL); 51 | 52 | TAILQ_INIT(&tq->list); 53 | 54 | error = pthread_mutex_init(&tq->mtx, NULL); 55 | if (error != 0) 56 | goto free; 57 | 58 | error = pthread_cond_init(&tq->cv, NULL); 59 | if (error != 0) 60 | goto mtx; 61 | 62 | error = pthread_create(&tq->thread, NULL, taskq_run, tq); 63 | if (error != 0) 64 | goto cv; 65 | 66 | pthread_set_name_np(tq->thread, name); 67 | 68 | return (tq); 69 | 70 | cv: 71 | pthread_cond_destroy(&tq->cv); 72 | mtx: 73 | pthread_mutex_destroy(&tq->mtx); /* can this really fail? */ 74 | free: 75 | free(tq); 76 | 77 | errno = error; 78 | return (NULL); 79 | } 80 | 81 | static void * 82 | taskq_run(void *tqarg) 83 | { 84 | struct taskq *tq = tqarg; 85 | struct task *t; 86 | 87 | void (*t_func)(void *); 88 | void *t_arg; 89 | 90 | for (;;) { 91 | pthread_mutex_lock(&tq->mtx); 92 | while ((t = TAILQ_FIRST(&tq->list)) == NULL) 93 | pthread_cond_wait(&tq->cv, &tq->mtx); 94 | 95 | TAILQ_REMOVE(&tq->list, t, t_entry); 96 | CLR(t->t_flags, TASK_ONQUEUE); 97 | 98 | t_func = t->t_func; 99 | t_arg = t->t_arg; 100 | 101 | pthread_mutex_unlock(&tq->mtx); 102 | 103 | (*t_func)(t_arg); 104 | } 105 | 106 | return (NULL); 107 | } 108 | 109 | void 110 | task_set(struct task *t, void (*fn)(void *), void *arg) 111 | { 112 | t->t_func = fn; 113 | t->t_arg = arg; 114 | t->t_flags = 0; 115 | } 116 | 117 | int 118 | task_add(struct taskq *tq, struct task *t) 119 | { 120 | int rv = 1; 121 | 122 | if (ISSET(t->t_flags, TASK_ONQUEUE)) 123 | return (0); 124 | 125 | pthread_mutex_lock(&tq->mtx); 126 | if (ISSET(t->t_flags, TASK_ONQUEUE)) 127 | rv = 0; 128 | else { 129 | SET(t->t_flags, TASK_ONQUEUE); 130 | TAILQ_INSERT_TAIL(&tq->list, t, t_entry); 131 | pthread_cond_signal(&tq->cv); 132 | } 133 | pthread_mutex_unlock(&tq->mtx); 134 | 135 | return (rv); 136 | } 137 | 138 | int 139 | task_del(struct taskq *tq, struct task *t) 140 | { 141 | int rv = 1; 142 | 143 | if (!ISSET(t->t_flags, TASK_ONQUEUE)) 144 | return (0); 145 | 146 | pthread_mutex_lock(&tq->mtx); 147 | if (!ISSET(t->t_flags, TASK_ONQUEUE)) 148 | rv = 0; 149 | else { 150 | TAILQ_REMOVE(&tq->list, t, t_entry); 151 | CLR(t->t_flags, TASK_ONQUEUE); 152 | } 153 | pthread_mutex_unlock(&tq->mtx); 154 | 155 | return (rv); 156 | } 157 | -------------------------------------------------------------------------------- /task.h: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: task.h,v 1.1 2017/09/15 02:39:33 dlg Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2013 David Gwynne 5 | * 6 | * Permission to use, copy, modify, and distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #ifndef _TASK_H_ 20 | #define _TASK_H_ 21 | 22 | #include 23 | 24 | struct taskq; 25 | 26 | struct task { 27 | TAILQ_ENTRY(task) t_entry; 28 | void (*t_func)(void *); 29 | void *t_arg; 30 | unsigned int t_flags; 31 | }; 32 | 33 | TAILQ_HEAD(task_list, task); 34 | 35 | #define TASK_INITIALIZER(_f, _a) {{ NULL, NULL }, (_f), (_a), 0 } 36 | 37 | struct taskq *taskq_create(const char *); 38 | 39 | void task_set(struct task *, void (*)(void *), void *); 40 | int task_add(struct taskq *, struct task *); 41 | int task_del(struct taskq *, struct task *); 42 | 43 | #endif /* _TASK_H_ */ 44 | --------------------------------------------------------------------------------