static enum proto_parse_status rpc_parse(struct parser *parser, struct proto_info *parent, unsigned unused_ way, uint8_t const *packet, size_t cap_len, size_t wire_len, struct timeval const unused_ *now, size_t unused_ tot_cap_len, uint8_t const unused_ *tot_packet) { struct cursor cursor; cursor_ctor(&cursor, packet, cap_len); if (wire_len < 12) return PROTO_PARSE_ERR; if (cap_len < 12) return PROTO_TOO_SHORT; ASSIGN_INFO_OPT(tcp, parent); struct rpc_proto_info info; proto_info_ctor(&info.info, parser, parent, wire_len, 0); if (tcp) cursor_drop(&cursor, 4); // Drop fragment header cursor_drop(&cursor, 4); info.msg_type = cursor_read_u32n(&cursor); enum proto_parse_status status = PROTO_OK; switch (info.msg_type) { case RPC_CALL: if (wire_len < 40) return PROTO_PARSE_ERR; status = parse_rpc_call(&cursor, &info); break; case RPC_REPLY: status = parse_rpc_reply(&cursor, &info); break; default: return PROTO_PARSE_ERR; } SLOG(LOG_DEBUG, "Parsed rpc status %s, %s", proto_parse_status_2_str(status), rpc_info_2_str(&info.info)); if (status == PROTO_OK) { // TODO We can have a nfs payload (void)proto_parse(NULL, &info.info, way, NULL, 0, 0, now, tot_cap_len, tot_packet); } return status; }
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 enum proto_parse_status tds_sbuf_parse(struct parser *parser, struct proto_info *parent, unsigned way, uint8_t const *payload, size_t cap_len, size_t wire_len, struct timeval const *now, size_t tot_cap_len, uint8_t const *tot_packet) { if (cap_len == 0 && wire_len > 0) return PROTO_TOO_SHORT; // We do not know how to handle pure gaps struct tds_parser *tds_parser = DOWNCAST(parser, parser, tds_parser); SLOG(LOG_DEBUG, "Got tds packet, data_left %zu, way %d", tds_parser->data_left, way); bool has_gap = wire_len > cap_len; if (tds_parser->had_gap && tds_parser->data_left > 0) { tds_parser->data_left = wire_len > tds_parser->data_left ? 0 : tds_parser->data_left - wire_len; SLOG(LOG_DEBUG, "Discard tds with gap, data_left %zu...", tds_parser->data_left); timeval_reset(&tds_parser->first_ts); return PROTO_OK; } tds_parser->had_gap = has_gap; struct tds_header tds_header; bool unknown_token = false; struct cursor cursor; enum proto_parse_status status; cursor_ctor(&cursor, payload, cap_len); struct smp_header smp_header = {.length = 0}; if (PROTO_OK != (status = tds_parse_smp_header(&cursor, &smp_header))) return status; if (smp_header.length) { SLOG(LOG_DEBUG, "Smp header: %s", smp_header_2_str(&smp_header)); wire_len -= SMP_PKT_HDR_LEN; cap_len -= SMP_PKT_HDR_LEN; if (smp_header.flags & SMP_ACK) return PROTO_OK; } status = tds_parse_header(&cursor, &tds_header, &unknown_token); if (status != PROTO_OK) { // We have an unknown token if the payload is encrypted after a ssl handshake // It is valid but we don't know how to parse it yet // TODO It would be better if we knew the values of the encryption options exchanged in prelogin messages timeval_reset(&tds_parser->first_ts); if (unknown_token) return PROTO_OK; return status; } // Sanity check on pkt number if (tds_header.pkt_number > 1 && ((tds_parser->pkt_number + 1) != tds_header.pkt_number)) { SLOG(LOG_DEBUG, "Expected pkt number %"PRIu8", got %"PRIu8"", tds_parser->pkt_number + 1, tds_header.pkt_number); tds_parser->pkt_number = 1; timeval_reset(&tds_parser->first_ts); return PROTO_PARSE_ERR; } else if (tds_header.pkt_number <= 1) { SLOG(LOG_DEBUG, "Reset pkt number from %"PRIu8"", tds_parser->pkt_number); tds_parser->pkt_number = 1; } // Sanity check on channels if (tds_parser->channels[way] && (tds_parser->channels[way] != tds_header.channel)) { SLOG(LOG_DEBUG, "Expected channel %"PRIu16", got channel %"PRIu16"", tds_parser->channels[way], tds_header.channel); timeval_reset(&tds_parser->first_ts); return PROTO_PARSE_ERR; } if (wire_len > tds_header.len) { SLOG(LOG_DEBUG, "Wire len %zu unexpected (> %zu), considering a gap", wire_len, tds_header.len); has_gap = true; } tds_parser->data_left = wire_len >= tds_header.len ? 0 : tds_header.len - wire_len; SLOG(LOG_DEBUG, "Data left after wire %zu", tds_parser->data_left); if (tds_parser->data_left > 0 && !has_gap) { SLOG(LOG_DEBUG, "Incomplete tds packet, buffering it"); if (!timeval_is_set(&tds_parser->first_ts)) { SLOG(LOG_DEBUG, "Setting first ts to %s for way %d", timeval_2_str(now), way); tds_parser->first_ts = *now; } proto_parse(NULL, parent, way, NULL, 0, 0, now, tot_cap_len, tot_packet); streambuf_set_restart(&tds_parser->sbuf, way, payload, tds_header.len); return PROTO_OK; } // We have a buffered tds packet at this point if (!timeval_is_set(&tds_parser->first_ts)) { SLOG(LOG_DEBUG, "Setting first ts to %s for way %d since it is not setted", timeval_2_str(now), way); tds_parser->first_ts = *now; } struct tds_proto_info info; proto_info_ctor(&info.info, parser, parent, TDS_PKT_HDR_LEN, tds_header.len - TDS_PKT_HDR_LEN); info.type = tds_header.type; info.status = tds_header.status; info.length = tds_header.len; info.first_ts = tds_parser->first_ts; info.has_gap = has_gap; if (tds_header.channel > 0) { SLOG(LOG_DEBUG, "Saving channel %"PRIu16"", tds_header.channel); tds_parser->channels[way] = tds_header.channel; } SLOG(LOG_DEBUG, "Saving pkt number %"PRIu8"", tds_header.pkt_number); tds_parser->pkt_number = tds_header.pkt_number; SLOG(LOG_DEBUG, "Parsing %s", tds_header_2_str(&tds_header)); if (! tds_parser->msg_parser) { SLOG(LOG_DEBUG, "Building new tds_msg_parser"); tds_parser->msg_parser = proto_tds_msg->ops->parser_new(proto_tds_msg); if (!tds_parser->msg_parser) { SLOG(LOG_INFO, "Could not build tds msg parser"); return PROTO_PARSE_ERR; } } if (tds_header.status & TDS_EOM) { SLOG(LOG_DEBUG, "Reset pkt number from %"PRIu8" since we parsed an EOM", tds_parser->pkt_number); tds_parser->pkt_number = 1; } status = proto_parse(tds_parser->msg_parser, &info.info, way, cursor.head, cursor.cap_len, wire_len - TDS_PKT_HDR_LEN, now, tot_cap_len, tot_packet); if (status != PROTO_OK) { SLOG(LOG_INFO, "Tds msg parse failed, returning %s", proto_parse_status_2_str(status)); return status; } timeval_reset(&tds_parser->first_ts); // Advertise this packet if it was not done already return proto_parse(NULL, &info.info, way, payload, cap_len, wire_len, now, tot_cap_len, tot_packet); } static enum proto_parse_status tds_parse(struct parser *parser, struct proto_info *parent, unsigned way, uint8_t const *payload, size_t cap_len, size_t wire_len, struct timeval const *now, size_t tot_cap_len, uint8_t const *tot_packet) { struct tds_parser *tds_parser = DOWNCAST(parser, parser, tds_parser); enum proto_parse_status const status = streambuf_add(&tds_parser->sbuf, parser, parent, way, payload, cap_len, wire_len, now, tot_cap_len, tot_packet); return status; } /* * Construction/Destruction */ static struct proto proto_tds_; struct proto *proto_tds = &proto_tds_; static struct port_muxer tds_tcp_muxer; void tds_init(void) { log_category_proto_tds_init(); static struct proto_ops const ops = { .parse = tds_parse, .parser_new = tds_parser_new, .parser_del = tds_parser_del, .info_2_str = tds_info_2_str, .info_addr = tds_info_addr }; proto_ctor(&proto_tds_, &ops, "TDS", PROTO_CODE_TDS); port_muxer_ctor(&tds_tcp_muxer, &tcp_port_muxers, 1433, 1433, proto_tds); }