static enum proto_parse_status icmpv6_parse(struct parser *parser, struct proto_info *parent, unsigned way, uint8_t const *packet, size_t cap_len, size_t wire_len, struct timeval const *now, size_t tot_cap_len, uint8_t const *tot_packet) { struct icmp_hdr *icmphdr = (struct icmp_hdr *)packet; // Sanity checks if (wire_len < sizeof(*icmphdr)) return PROTO_PARSE_ERR; if (cap_len < sizeof(*icmphdr)) return PROTO_TOO_SHORT; struct icmp_proto_info info; uint8_t const type = READ_U8(&icmphdr->type); icmpv6_proto_info_ctor(&info, parser, parent, wire_len, type, READ_U8(&icmphdr->code)); if (icmpv6_has_id(type)) { info.set_values |= ICMP_ID_SET; info.id = READ_U16N(&icmphdr->id); } // Extract error values if (icmpv6_is_err(type)) { if (0 == icmpv6_extract_err_infos(&info, packet + sizeof(*icmphdr), cap_len - sizeof(*icmphdr))) { info.set_values |= ICMP_ERR_SET; } } return proto_parse(NULL, &info.info, way, NULL, 0, 0, now, tot_cap_len, tot_packet); }
static void rtp_proto_info_ctor(struct rtp_proto_info *info, struct parser *parser, struct proto_info *parent, struct rtp_hdr const *rtph, size_t head_len, size_t payload) { proto_info_ctor(&info->info, parser, parent, head_len, payload); info->payload_type = READ_U8(&rtph->flags1) & F1_PLD_TYPE_MASK; info->sync_src = READ_U32N(&rtph->ssrc); info->seq_num = READ_U16N(&rtph->seq_num); info->timestamp = READ_U32N(&rtph->timestamp); }
static void tcp_proto_info_ctor(struct tcp_proto_info *info, struct parser *parser, struct proto_info *parent, size_t head_len, size_t payload, uint16_t sport, uint16_t dport, struct tcp_hdr const *tcphdr) { proto_info_ctor(&info->info, parser, parent, head_len, payload); info->key.port[0] = sport; info->key.port[1] = dport; uint8_t const flags = READ_U8(&tcphdr->flags); info->syn = !!(flags & TCP_SYN_MASK); info->ack = !!(flags & TCP_ACK_MASK); info->rst = !!(flags & TCP_RST_MASK); info->fin = !!(flags & TCP_FIN_MASK); info->urg = !!(flags & TCP_URG_MASK); info->psh = !!(flags & TCP_PSH_MASK); // to_srv set later from tcp_subparser info->window = READ_U16N(&tcphdr->window); info->urg_ptr = READ_U16N(&tcphdr->urg_ptr); info->ack_num = READ_U32N(&tcphdr->ack_seq); info->seq_num = READ_U32N(&tcphdr->seq_num); info->rel_seq_num = 0U; info->set_values = 0; // options will be set later info->nb_options = 0; }
static enum proto_parse_status dns_tcp_parse(struct parser unused_ *parser, struct proto_info *parent, unsigned way, uint8_t const *packet, size_t cap_len, size_t wire_len, struct timeval const *now, size_t tot_cap_len, uint8_t const *tot_packet) { size_t const hlen = 2; size_t offset = 0; while (offset + hlen < cap_len) { size_t len = READ_U16N(packet); offset += hlen; // Sanity check if (offset + len > wire_len) return PROTO_PARSE_ERR; struct parser *dns = proto_dns->ops->parser_new(proto_dns); if (! dns) break; enum proto_parse_status status = proto_parse(dns, parent, way, packet+offset, cap_len-offset, wire_len-offset, now, tot_cap_len, tot_packet); parser_unref(&dns); if (status != PROTO_OK) break; offset += len; } return PROTO_OK; }
static enum proto_parse_status udp_parse(struct parser *parser, struct proto_info *parent, unsigned way, uint8_t const *packet, size_t cap_len, size_t wire_len, struct timeval const *now, size_t tot_cap_len, uint8_t const *tot_packet) { struct mux_parser *mux_parser = DOWNCAST(parser, parser, mux_parser); struct udp_hdr const *udphdr = (struct udp_hdr *)packet; // Sanity checks if (wire_len < sizeof(*udphdr)) { SLOG(LOG_DEBUG, "Bogus UDP packet: too short (%zu < %zu)", wire_len, sizeof(*udphdr)); return PROTO_PARSE_ERR; } if (cap_len < sizeof(*udphdr)) return PROTO_TOO_SHORT; size_t tot_len = READ_U16N(&udphdr->len); if (tot_len < sizeof(*udphdr)) { SLOG(LOG_DEBUG, "Bogus UDP packet: UDP tot len shorter than UDP header (%zu < %zu)", tot_len, sizeof(*udphdr)); return PROTO_PARSE_ERR; } size_t payload = tot_len - sizeof(*udphdr); if (payload > wire_len) { SLOG(LOG_DEBUG, "Bogus UDP packet: wrong length %zu > %zu", payload, wire_len); return PROTO_PARSE_ERR; } uint16_t const sport = READ_U16N(&udphdr->src); uint16_t const dport = READ_U16N(&udphdr->dst); SLOG(LOG_DEBUG, "New UDP packet of %zu bytes (%zu captured), ports %"PRIu16" -> %"PRIu16, wire_len, cap_len, sport, dport); // Parse struct udp_proto_info info; udp_proto_info_ctor(&info, parser, parent, sizeof(*udphdr), payload, sport, dport); // Search an already spawned subparser struct port_key key; port_key_init(&key, sport, dport, way); struct mux_subparser *subparser = mux_subparser_lookup(mux_parser, NULL, NULL, &key, now); if (subparser) SLOG(LOG_DEBUG, "Found subparser for this cnx, for proto %s", subparser->parser->proto->name); if (! subparser) { struct proto *requestor = NULL; struct proto *sub_proto = NULL; // Use connection tracking first ASSIGN_INFO_OPT2(ip, ip6, parent); if (! ip) ip = ip6; if (ip) sub_proto = cnxtrack_ip_lookup(IPPROTO_UDP, ip->key.addr+0, sport, ip->key.addr+1, dport, now, &requestor); if (! sub_proto) { // Then try predefined ports first sub_proto = port_muxer_find(&udp_port_muxers, info.key.port[0], info.key.port[1]); } if (sub_proto) subparser = mux_subparser_and_parser_new(mux_parser, sub_proto, requestor, &key, now); } if (! subparser) goto fallback; enum proto_parse_status status = proto_parse(subparser->parser, &info.info, way, packet + sizeof(*udphdr), cap_len - sizeof(*udphdr), wire_len - sizeof(*udphdr), now, tot_cap_len, tot_packet); if (status == PROTO_PARSE_ERR) { SLOG(LOG_DEBUG, "No suitable subparser for this payload"); mux_subparser_deindex(subparser); } mux_subparser_unref(&subparser); if (status == PROTO_OK) return PROTO_OK; fallback: (void)proto_parse(NULL, &info.info, way, packet + sizeof(*udphdr), cap_len - sizeof(*udphdr), wire_len - sizeof(*udphdr), now, tot_cap_len, tot_packet); return PROTO_OK; }
static enum proto_parse_status tcp_parse(struct parser *parser, struct proto_info *parent, unsigned way, uint8_t const *packet, size_t cap_len, size_t wire_len, struct timeval const *now, size_t tot_cap_len, uint8_t const *tot_packet) { struct mux_parser *mux_parser = DOWNCAST(parser, parser, mux_parser); struct tcp_hdr const *tcphdr = (struct tcp_hdr *)packet; // Sanity checks if (wire_len < sizeof(*tcphdr)) { SLOG(LOG_DEBUG, "Bogus TCP packet: too short (%zu < %zu)", wire_len, sizeof(*tcphdr)); return PROTO_PARSE_ERR; } if (cap_len < sizeof(*tcphdr)) return PROTO_TOO_SHORT; size_t tcphdr_len = TCP_HDR_LENGTH(tcphdr); if (tcphdr_len < sizeof(*tcphdr)) { SLOG(LOG_DEBUG, "Bogus TCP packet: header size too smal (%zu < %zu)", tcphdr_len, sizeof(*tcphdr)); return PROTO_PARSE_ERR; } if (tcphdr_len > wire_len) { SLOG(LOG_DEBUG, "Bogus TCP packet: wrong length %zu > %zu", tcphdr_len, wire_len); return PROTO_PARSE_ERR; } if (tcphdr_len > cap_len) return PROTO_TOO_SHORT; // TODO: move this below call to tcp_proto_info_ctor() and use info instead of reading tcphdr directly uint16_t const sport = READ_U16N(&tcphdr->src); uint16_t const dport = READ_U16N(&tcphdr->dst); bool const syn = !!(READ_U8(&tcphdr->flags) & TCP_SYN_MASK); bool const fin = !!(READ_U8(&tcphdr->flags) & TCP_FIN_MASK); bool const ack = !!(READ_U8(&tcphdr->flags) & TCP_ACK_MASK); bool const rst = !!(READ_U8(&tcphdr->flags) & TCP_RST_MASK); bool const urg = !!(READ_U8(&tcphdr->flags) & TCP_URG_MASK); bool const psh = !!(READ_U8(&tcphdr->flags) & TCP_PSH_MASK); SLOG(LOG_DEBUG, "New TCP packet of %zu bytes (%zu captured), %zu payload, ports %"PRIu16" -> %"PRIu16" Flags: %s%s%s%s%s%s, Seq:%"PRIu32", Ack:%"PRIu32, wire_len, cap_len, wire_len - tcphdr_len, sport, dport, syn ? "Syn":"", fin ? "Fin":"", ack ? "Ack":"", rst ? "Rst":"", urg ? "Urg":"", psh ? "Psh":"", READ_U32N(&tcphdr->seq_num), READ_U32N(&tcphdr->ack_seq)); // Parse struct tcp_proto_info info; tcp_proto_info_ctor(&info, parser, parent, tcphdr_len, wire_len - tcphdr_len, sport, dport, tcphdr); // Parse TCP options uint8_t const *options = (uint8_t *)(tcphdr+1); assert(tcphdr_len >= sizeof(*tcphdr)); for (size_t rem_len = tcphdr_len - sizeof(*tcphdr); rem_len > 0; ) { ssize_t const len = parse_next_option(&info, options, rem_len); if (len < 0) return PROTO_PARSE_ERR; rem_len -= len; options += len; } // Search an already spawned subparser struct port_key key; port_key_init(&key, sport, dport, way); struct mux_subparser *subparser = mux_subparser_lookup(mux_parser, NULL, NULL, &key, now); if (subparser) SLOG(LOG_DEBUG, "Found subparser@%p for this cnx, for proto %s", subparser->parser, subparser->parser->proto->name); if (! subparser) { struct proto *requestor = NULL; struct proto *sub_proto = NULL; // Use connection tracking first ASSIGN_INFO_OPT2(ip, ip6, parent); if (! ip) ip = ip6; if (ip) sub_proto = cnxtrack_ip_lookup(IPPROTO_TCP, ip->key.addr+0, sport, ip->key.addr+1, dport, now, &requestor); if (! sub_proto) { // Then try predefined ports sub_proto = port_muxer_find(&tcp_port_muxers, info.key.port[0], info.key.port[1]); } if (sub_proto) { subparser = mux_subparser_and_parser_new(mux_parser, sub_proto, requestor, &key, now); } else { // Even if we have no child parser to send payload to, we want to submit payload in stream order to our plugins subparser = tcp_subparser_new(mux_parser, NULL, NULL, &key, now); } } if (! subparser) goto fallback; // Keep track of TCP flags & ISN struct tcp_subparser *tcp_sub = DOWNCAST(subparser, mux_subparser, tcp_subparser); mutex_lock(tcp_sub->mutex); if ( info.ack && (!IS_SET_FOR_WAY(way, tcp_sub->ack) || seqnum_gt(info.ack_num, tcp_sub->max_acknum[way])) ) { SET_FOR_WAY(way, tcp_sub->ack); tcp_sub->max_acknum[way] = info.ack_num; } if (info.fin) { SET_FOR_WAY(way, tcp_sub->fin); tcp_sub->fin_seqnum[way] = info.seq_num + info.info.payload; // The FIN is acked after the payload } if (info.syn && !IS_SET_FOR_WAY(way, tcp_sub->syn)) { SET_FOR_WAY(way, tcp_sub->syn); tcp_sub->isn[way] = info.seq_num; } if (!IS_SET_FOR_WAY(way, tcp_sub->origin)) { SET_FOR_WAY(way, tcp_sub->origin); tcp_sub->wl_origin[way] = info.seq_num; if (! IS_SET_FOR_WAY(way, tcp_sub->syn)) SLOG(LOG_DEBUG, "Starting a WL while SYN is yet to be received!"); } // Set relative sequence number if we know it if (IS_SET_FOR_WAY(way, tcp_sub->syn)) info.rel_seq_num = info.seq_num - tcp_sub->isn[way]; // Set srv_way assert(tcp_sub->srv_set < 3); if (tcp_sub->srv_set == 0 || (tcp_sub->srv_set == 1 && info.syn)) { if (comes_from_client(info.key.port, info.syn, info.ack)) { // this packet comes from the client tcp_sub->srv_way = !way; } else { tcp_sub->srv_way = way; } tcp_sub->srv_set = info.syn ? 2:1; } // Now patch it into tcp info info.to_srv = tcp_sub->srv_way != way; SLOG(LOG_DEBUG, "Subparser@%p state: >ISN:%"PRIu32"%s Fin:%"PRIu32" Ack:%"PRIu32" <ISN:%"PRIu32"%s Fin:%"PRIu32" Ack:%"PRIu32", SrvWay=%u%s", subparser->parser, IS_SET_FOR_WAY(0, tcp_sub->syn) ? tcp_sub->isn[0] : IS_SET_FOR_WAY(0, tcp_sub->origin) ? tcp_sub->wl_origin[0] : 0, IS_SET_FOR_WAY(0, tcp_sub->syn) ? "" : " (approx)", IS_SET_FOR_WAY(0, tcp_sub->fin) ? tcp_sub->fin_seqnum[0] : 0, IS_SET_FOR_WAY(0, tcp_sub->ack) ? tcp_sub->max_acknum[0] : 0, IS_SET_FOR_WAY(1, tcp_sub->syn) ? tcp_sub->isn[1] : IS_SET_FOR_WAY(1, tcp_sub->origin) ? tcp_sub->wl_origin[1] : 0, IS_SET_FOR_WAY(1, tcp_sub->syn) ? "" : " (approx)", IS_SET_FOR_WAY(1, tcp_sub->fin) ? tcp_sub->fin_seqnum[1] : 0, IS_SET_FOR_WAY(1, tcp_sub->ack) ? tcp_sub->max_acknum[1] : 0, tcp_sub->srv_way, tcp_sub->srv_set == 0 ? " (unset)": tcp_sub->srv_set == 1 ? " (unsure)":"(certain)"); enum proto_parse_status err; /* Use the wait_list to parse this packet. Notice that we do queue empty packets because subparser (or subscriber) want to receive all packets in order, including empty ones. */ size_t const packet_len = wire_len - tcphdr_len; assert(IS_SET_FOR_WAY(way, tcp_sub->origin)); unsigned const offset = info.seq_num - tcp_sub->wl_origin[way]; unsigned const next_offset = offset + packet_len + info.syn + info.fin; unsigned const sync_offset = info.ack_num - tcp_sub->wl_origin[!way]; // we must not parse this one before we parsed (or timeouted) this one from wl[!way] // FIXME: Here the parser is chosen before we actually parse anything. If later the parser fails we cannot try another one. // Choice of parser should be delayed until we start actual parse. bool const do_sync = info.ack && IS_SET_FOR_WAY(!way, tcp_sub->origin); err = pkt_wait_list_add(tcp_sub->wl+way, offset, next_offset, do_sync, sync_offset, true, &info.info, way, packet + tcphdr_len, cap_len - tcphdr_len, packet_len, now, tot_cap_len, tot_packet); SLOG(LOG_DEBUG, "Waiting list returned %s", proto_parse_status_2_str(err)); if (err == PROTO_OK) { // Try advancing each WL until we are stuck or met an error pkt_wait_list_try_both(tcp_sub->wl+!way, &err, now, false); } bool const term = tcp_subparser_term(tcp_sub); mutex_unlock(tcp_sub->mutex); if (term || err == PROTO_PARSE_ERR) { if (term) { SLOG(LOG_DEBUG, "TCP cnx terminated (was %s)", parser_name(subparser->parser)); } else { SLOG(LOG_DEBUG, "No suitable subparser for this payload"); } mux_subparser_deindex(subparser); } mux_subparser_unref(&subparser); if (err == PROTO_OK) return PROTO_OK; fallback: (void)proto_parse(NULL, &info.info, way, packet + tcphdr_len, cap_len - tcphdr_len, wire_len - tcphdr_len, now, tot_cap_len, tot_packet); return PROTO_OK; }
static ssize_t parse_next_option(struct tcp_proto_info *info, uint8_t const *options, size_t rem_len) { assert(rem_len > 0); if (rem_len < 1) return -1; uint8_t const kind = options[0]; // We only decode MSS and WSF but record all options if (info->nb_options < NB_ELEMS(info->options)) { info->options[info->nb_options++] = kind; } if (kind == 0) { // end of option list if (rem_len > 4) { SLOG(LOG_DEBUG, "Option list terminated while %zu bytes left", rem_len-1); return rem_len; // keep parsing payload } if ((intptr_t)(options+rem_len) & 0x3) { SLOG(LOG_DEBUG, "Option list ends in a non word boundary"); return -1; } // TODO: check that padding is composed of zeros return rem_len; } else if (kind == 1) { return 1; } if (rem_len < 2) { SLOG(LOG_DEBUG, "Invalid TCP options: can't read length"); return -1; } size_t const len = options[1]; // len includes what's read before if (len < 2) { SLOG(LOG_DEBUG, "Invalid TCP options: len field (%zu) < 2", len); return -1; } if (rem_len < len) { SLOG(LOG_DEBUG, "Invalid TCP options: length (%zu) > rem options bytes (%zu)", len, rem_len); return -1; } switch (kind) { case 2: // MSS if (len != 4) { SLOG(LOG_DEBUG, "MSS with length %zu", len); return -1; } info->set_values |= TCP_MSS_SET; info->mss = READ_U16N(options+2); break; case 3: // Window Scale Factor if (len != 3) { SLOG(LOG_DEBUG, "WSF with length %zu", len); return -1; } info->set_values |= TCP_WSF_SET; info->wsf = options[2]; break; } return len; }
static enum proto_parse_status eth_parse(struct parser *parser, struct proto_info *parent, unsigned way, uint8_t const *packet, size_t cap_len, size_t wire_len, struct timeval const *now, size_t tot_cap_len, uint8_t const *tot_packet) { struct mux_parser *mux_parser = DOWNCAST(parser, parser, mux_parser); struct eth_hdr const *ethhdr = (struct eth_hdr *)packet; uint16_t h_proto = READ_U16N(ðhdr->proto); int vlan_id = VLAN_UNSET; size_t ethhdr_len = sizeof(*ethhdr); // Sanity checks if (wire_len < ethhdr_len) { SLOG(LOG_DEBUG, "Bogus Eth packet: too short (%zu < %zu)", wire_len, ethhdr_len); return PROTO_PARSE_ERR; } if (cap_len < ethhdr_len) return PROTO_TOO_SHORT; if (h_proto == 0) { // Take into account Linux Cooked Capture if (cap_len < ethhdr_len + 2) return PROTO_TOO_SHORT; struct eth_lcc { uint16_t h_proto; } packed_ *eth_lcc = (struct eth_lcc *)((char *)ethhdr + ethhdr_len); h_proto = READ_U16N(ð_lcc->h_proto); ethhdr_len += 2; // We dont care about the source MAC being funny } while (h_proto == ETH_PROTO_8021Q || h_proto == ETH_PROTO_8021QinQ || h_proto == ETH_PROTO_8021QinQ_alt) { // Take into account 802.1q vlan tag (with possible QinQ) if (cap_len < ethhdr_len + 4) return PROTO_TOO_SHORT; struct eth_vlan { uint16_t vlan_id, h_proto; } packed_ *eth_vlan = (struct eth_vlan *)((char *)ethhdr + ethhdr_len); h_proto = READ_U16N(ð_vlan->h_proto); vlan_id = READ_U16N(ð_vlan->vlan_id) & 0xfff; ethhdr_len += 4; } size_t frame_wire_len = wire_len - ethhdr_len; if (h_proto <= 1500) { // h_proto is then the length of payload /* According to IEEE Std. 802.3: * "This two-octet field takes one of two meanings, depending on its numeric value. For numerical evaluation, * the first octet is the most significant octet of this field. * a) If the value of this field is less than or equal to 1500 decimal (05DC hexadecimal), then the Length/ * Type field indicates the number of MAC client data octets contained in the subsequent MAC Client * Data field of the basic frame (Length interpretation). * b) If the value of this field is greater than or equal to 1536 decimal (0600 hexadecimal), then the * Length/Type field indicates the nature of the MAC client protocol (Type interpretation). * The Length and Type interpretations of this field are mutually exclusive." */ if (h_proto > frame_wire_len) { SLOG(LOG_DEBUG, "Bogus Eth packet: specified length too bug (%"PRIu16" > %zu)", h_proto, frame_wire_len); return PROTO_PARSE_ERR; } frame_wire_len = h_proto; h_proto = 0; // no indication of a protocol, then } size_t const frame_cap_len = MIN(cap_len - ethhdr_len, frame_wire_len); // Parse struct eth_proto_info info; eth_proto_info_ctor(&info, parser, parent, ethhdr_len, frame_wire_len, h_proto, vlan_id, ethhdr); if (! h_proto) goto fallback; // no indication of what's the payload struct proto *sub_proto = eth_subproto_lookup(h_proto); struct mux_subparser *subparser = mux_subparser_lookup(mux_parser, sub_proto, NULL, collapse_vlans ? &vlan_unset : &vlan_id, now); if (! subparser) goto fallback; assert(ethhdr_len <= cap_len); enum proto_parse_status status = proto_parse(subparser->parser, &info.info, way, packet + ethhdr_len, frame_cap_len, frame_wire_len, now, tot_cap_len, tot_packet); mux_subparser_unref(&subparser); if (status == PROTO_OK) return PROTO_OK; fallback: (void)proto_parse(NULL, &info.info, way, packet + ethhdr_len, frame_cap_len, frame_wire_len, now, tot_cap_len, tot_packet); return PROTO_OK; }