static int proto_ipv4_fragment_cleanup(struct conntrack_entry *ce, void *priv, ptime now) { struct proto_ipv4_fragment *f = priv; // Remove the frag from the conntrack if (f->prev) f->prev->next = f->next; else ce->priv = f->next; if (f->next) f->next->prev = f->prev; conntrack_unlock(ce); if (!(f->flags & PROTO_IPV4_FLAG_PROCESSED)) { registry_perf_inc(perf_frags_dropped, f->count); } if (f->multipart) packet_multipart_cleanup(f->multipart); if (f->t) conntrack_timer_cleanup(f->t); free(f); return POM_OK; }
static int proto_udp_process(void *proto_priv, struct packet *p, struct proto_process_stack *stack, unsigned int stack_index) { struct proto_process_stack *s = &stack[stack_index]; if (sizeof(struct udphdr) > s->plen) return PROTO_INVALID; struct udphdr *hdr = s->pload; uint16_t ulen = ntohs(hdr->uh_ulen); uint16_t sport = ntohs(hdr->uh_sport); uint16_t dport = ntohs(hdr->uh_dport); if (ulen > s->plen) return PROTO_INVALID; PTYPE_UINT16_SETVAL(s->pkt_info->fields_value[proto_udp_field_sport], sport); PTYPE_UINT16_SETVAL(s->pkt_info->fields_value[proto_udp_field_dport], dport); if (conntrack_get(stack, stack_index) != POM_OK) return POM_ERR; int res = POM_ERR; struct proto_process_stack *s_next = &stack[stack_index + 1]; if (s->ce->children) { res = conntrack_delayed_cleanup(s->ce, 0, p->ts); s_next->proto = s->ce->children->ce->proto; } else { uint32_t *conntrack_timeout = PTYPE_UINT32_GETVAL(param_conntrack_timeout); res = conntrack_delayed_cleanup(s->ce, *conntrack_timeout, p->ts); } conntrack_unlock(s->ce); s_next->pload = s->pload + sizeof(struct udphdr); s_next->plen = ulen - sizeof(struct udphdr); if (!s_next->proto) { if (dport == 53 || sport == 53) s_next->proto = proto_dns; if (dport == 69 || sport == 69) s_next->proto = proto_tftp; } return res; }
int analyzer_ppp_pap_event_process_begin(struct event *evt, void *obj, struct proto_process_stack *stack, unsigned int stack_index) { struct analyzer *analyzer = obj; struct analyzer_ppp_pap_priv *apriv = analyzer->priv; struct proto_process_stack *s = &stack[stack_index]; if (!s->ce) return PROTO_ERR; conntrack_lock(s->ce); struct ptype *src = NULL, *dst = NULL; struct analyzer_ppp_pap_ce_priv *cpriv = conntrack_get_priv(s->ce, analyzer); if (!cpriv) { cpriv = malloc(sizeof(struct analyzer_ppp_pap_ce_priv)); if (!cpriv) { pom_oom(sizeof(struct analyzer_ppp_pap_ce_priv)); goto err; } memset(cpriv, 0, sizeof(struct analyzer_ppp_pap_ce_priv)); if (conntrack_add_priv(s->ce, analyzer, cpriv, analyzer_ppp_pap_ce_priv_cleanup) != POM_OK) { free(cpriv); goto err; } // Try to find the source and destination unsigned int i = 0; for (i = 1; i <= 4; i++) { struct proto_process_stack *prev_stack = &stack[stack_index - i]; if (!prev_stack->proto) break; struct proto_reg_info *info = proto_get_info(prev_stack->proto); if (!strcmp(info->name, "vlan")) { cpriv->vlan = ptype_alloc_from(prev_stack->pkt_info->fields_value[proto_vlan_field_vid]); if (!cpriv->vlan) { conntrack_unlock(s->ce); return POM_ERR; } } unsigned int j; for (j = 0; !src || !dst; j++) { struct proto_reg_info *prev_info = proto_get_info(prev_stack->proto); if (!prev_info->pkt_fields) break; char *name = prev_info->pkt_fields[j].name; if (!name) break; if (!src && !strcmp(name, "src")) src = prev_stack->pkt_info->fields_value[j]; else if (!dst && !strcmp(name, "dst")) dst = prev_stack->pkt_info->fields_value[j]; } if (src || dst) break; } struct proto_process_stack *prev_stack = &stack[stack_index - 2]; if (prev_stack->proto) { struct proto_reg_info *info = proto_get_info(prev_stack->proto); cpriv->top_proto = info->name; } } struct event_reg *evt_reg = event_get_reg(evt); int dir = POM_DIR_UNK; if (evt_reg == apriv->evt_request) { if (!cpriv->evt_request) { event_refcount_inc(evt); cpriv->evt_request = evt; } dir = POM_DIR_FWD; } else { if (!cpriv->evt_ack_nack) { event_refcount_inc(evt); cpriv->evt_ack_nack = evt; } dir = POM_DIR_REV; } if (src && dst && dir != POM_DIR_UNK) { if (dir == POM_DIR_FWD) { cpriv->client = ptype_alloc_from(src); cpriv->server = ptype_alloc_from(dst); } else { cpriv->client = ptype_alloc_from(dst); cpriv->server = ptype_alloc_from(src); } } int res = POM_OK; if (cpriv->evt_request && cpriv->evt_ack_nack) res = analyzer_ppp_pap_finalize(apriv, cpriv); conntrack_unlock(s->ce); return res; err: conntrack_unlock(s->ce); return POM_ERR; }
int proto_process(struct packet *p, struct proto_process_stack *stack, unsigned int stack_index) { struct proto_process_stack *s = &stack[stack_index]; struct proto *proto = s->proto; if (!proto || !proto->info->process) return PROTO_ERR; int res = proto->info->process(proto->priv, p, stack, stack_index); registry_perf_inc(proto->perf_pkts, 1); registry_perf_inc(proto->perf_bytes, s->plen); if (res != PROTO_OK) return res; // Process the expectations ! pom_rwlock_rlock(&proto->expectation_lock); struct proto_expectation *e = proto->expectations; while (e) { int expt_dir = POM_DIR_UNK; struct proto_expectation_stack *es = e->tail; struct ptype *fwd_value = s->pkt_info->fields_value[s->proto->info->ct_info->fwd_pkt_field_id]; struct ptype *rev_value = s->pkt_info->fields_value[s->proto->info->ct_info->rev_pkt_field_id]; if ((!es->fields[POM_DIR_FWD] || ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_FWD], fwd_value)) && (!es->fields[POM_DIR_REV] || ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_REV], rev_value))) { // Expectation matched the forward direction expt_dir = POM_DIR_FWD; } else if ((!es->fields[POM_DIR_FWD] || ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_FWD], rev_value)) && (!es->fields[POM_DIR_REV] || ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_REV], fwd_value))) { // Expectation matched the reverse direction expt_dir = POM_DIR_REV; } if (expt_dir == POM_DIR_UNK) { // Expectation not matched e = e->next; continue; } es = es->prev; int stack_index_tmp = stack_index - 1; while (es) { struct proto_process_stack *s_tmp = &stack[stack_index_tmp]; if (s_tmp->proto != es->proto) { e = e->next; continue; } fwd_value = s_tmp->pkt_info->fields_value[s_tmp->proto->info->ct_info->fwd_pkt_field_id]; rev_value = s_tmp->pkt_info->fields_value[s_tmp->proto->info->ct_info->rev_pkt_field_id]; if (expt_dir == POM_DIR_FWD) { if ((es->fields[POM_DIR_FWD] && !ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_FWD], fwd_value)) || (es->fields[POM_DIR_REV] && !ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_REV], rev_value))) { e = e->next; continue; } } else { if ((es->fields[POM_DIR_FWD] && !ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_FWD], rev_value)) || (es->fields[POM_DIR_REV] && !ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_REV], fwd_value))) { e = e->next; continue; } } es = es->prev; stack_index_tmp--; } // Expectation matched ! // Relock with write access pom_rwlock_unlock(&proto->expectation_lock); pom_rwlock_wlock(&proto->expectation_lock); debug_expectation("Expectation %p matched !", e); // Remove it from the list if (e->next) e->next->prev = e->prev; if (e->prev) e->prev->next = e->next; else proto->expectations = e->next; struct proto_process_stack *s_next = &stack[stack_index + 1]; s_next->proto = e->proto; if (conntrack_get_unique_from_parent(stack, stack_index + 1) != POM_OK) { proto_expectation_cleanup(e); return PROTO_ERR; } s_next->ce->priv = e->priv; if (conntrack_session_bind(s_next->ce, e->session)) { proto_expectation_cleanup(e); return PROTO_ERR; } registry_perf_dec(e->proto->perf_expt_pending, 1); registry_perf_inc(e->proto->perf_expt_matched, 1); proto_expectation_cleanup(e); conntrack_unlock(s_next->ce); break; } pom_rwlock_unlock(&proto->expectation_lock); return res; }
static int proto_eap_process(void *proto_priv, struct packet *p, struct proto_process_stack *stack, unsigned int stack_index) { struct proto_process_stack *s = &stack[stack_index]; if (sizeof(struct eap_header) > s->plen) return PROTO_INVALID; struct proto_eap_priv *priv = proto_priv; struct eap_header *hdr = s->pload; PTYPE_UINT8_SETVAL(s->pkt_info->fields_value[proto_eap_field_code], hdr->code); PTYPE_UINT8_SETVAL(s->pkt_info->fields_value[proto_eap_field_identifier], hdr->identifier); if (hdr->code < 1 || hdr->code > 4) return PROTO_INVALID; uint16_t len = ntohs(hdr->length); if (len > s->plen) return PROTO_INVALID; // Keep only the payload lenght len -= sizeof(struct eap_header); if (conntrack_get(stack, stack_index) != POM_OK) return PROTO_ERR; if (conntrack_delayed_cleanup(s->ce, *PTYPE_UINT32_GETVAL(priv->p_timeout), p->ts) != POM_OK) { conntrack_unlock(s->ce); return PROTO_ERR; } conntrack_unlock(s->ce); if (hdr->code == 3 || hdr->code == 4) { // Content length is 0 for success and failure if (len != 4) return PROTO_INVALID; len = 0; if (!event_has_listener(priv->evt_success_failure)) return PROTO_OK; struct event *evt = event_alloc(priv->evt_success_failure); if (!evt) return PROTO_ERR; struct data *evt_data = event_get_data(evt); PTYPE_UINT8_SETVAL(evt_data[evt_eap_common_identifier].value, hdr->identifier); data_set(evt_data[evt_eap_common_identifier]); PTYPE_BOOL_SETVAL(evt_data[evt_eap_success_failure_success].value, (hdr->code == 3 ? 1 : 0)); data_set(evt_data[evt_eap_success_failure_success]); return event_process(evt, stack, stack_index, p->ts); } // At this point, code is either 1 or 2 (request/response) void *pload = s->pload + sizeof(struct eap_header); uint8_t type = 0; // There is at least 1 byte of data for request/response if (len < 1) return PROTO_INVALID; len--; type = *(uint8_t*)pload; pload++; struct event *evt = NULL; struct data *evt_data = NULL; switch (type) { case 1: // Identity if (!event_has_listener(priv->evt_identity)) break; evt = event_alloc(priv->evt_identity); if (!evt) return PROTO_ERR; if (len) { evt_data = event_get_data(evt); PTYPE_STRING_SETVAL_N(evt_data[evt_eap_identity_identity].value, pload, len); data_set(evt_data[evt_eap_identity_identity]); } break; case 4: // MD5-Challenge if (!event_has_listener(priv->evt_md5_challenge)) break; if (len < 17) return PROTO_INVALID; uint8_t value_size = *(uint8_t*)pload; if (value_size != 16) return PROTO_INVALID; pload++; len--; evt = event_alloc(priv->evt_md5_challenge); if (!evt) return PROTO_ERR; evt_data = event_get_data(evt); PTYPE_BYTES_SETLEN(evt_data[evt_eap_md5_challenge_value].value, 16); PTYPE_BYTES_SETVAL(evt_data[evt_eap_md5_challenge_value].value, pload); data_set(evt_data[evt_eap_md5_challenge_value]); if (len > 16) { PTYPE_STRING_SETVAL_N(evt_data[evt_eap_md5_challenge_name].value, pload + 16, len - 16); data_set(evt_data[evt_eap_md5_challenge_name]); } break; } if (evt) { if (!evt_data) evt_data = event_get_data(evt); PTYPE_UINT8_SETVAL(evt_data[evt_eap_common_identifier].value, hdr->identifier); data_set(evt_data[evt_eap_common_identifier]); PTYPE_UINT8_SETVAL(evt_data[evt_eap_common_code].value, hdr->code); data_set(evt_data[evt_eap_common_code]); if (event_process(evt, stack, stack_index, p->ts) != POM_OK) return PROTO_ERR; } return PROTO_OK; }
static int proto_smtp_process(void *proto_priv, struct packet *p, struct proto_process_stack *stack, unsigned int stack_index) { struct proto_process_stack *s = &stack[stack_index]; struct proto_process_stack *s_next = &stack[stack_index + 1]; if (conntrack_get_unique_from_parent(stack, stack_index) != POM_OK) { pomlog(POMLOG_ERR "Could not get conntrack entry"); return PROTO_ERR; } // There should no need to keep the lock here since we are in the packet_stream lock from proto_tcp conntrack_unlock(s->ce); struct proto_smtp_priv *ppriv = proto_priv; struct proto_smtp_conntrack_priv *priv = s->ce->priv; if (!priv) { priv = malloc(sizeof(struct proto_smtp_conntrack_priv)); if (!priv) { pom_oom(sizeof(struct proto_smtp_conntrack_priv)); return PROTO_ERR; } memset(priv, 0, sizeof(struct proto_smtp_conntrack_priv)); priv->parser[POM_DIR_FWD] = packet_stream_parser_alloc(SMTP_MAX_LINE, PACKET_STREAM_PARSER_FLAG_TRIM); if (!priv->parser[POM_DIR_FWD]) { free(priv); return PROTO_ERR; } priv->parser[POM_DIR_REV] = packet_stream_parser_alloc(SMTP_MAX_LINE, PACKET_STREAM_PARSER_FLAG_TRIM); if (!priv->parser[POM_DIR_REV]) { packet_stream_parser_cleanup(priv->parser[POM_DIR_FWD]); free(priv); return PROTO_ERR; } priv->server_direction = POM_DIR_UNK; s->ce->priv = priv; } if (priv->flags & PROTO_SMTP_FLAG_INVALID) return PROTO_OK; struct packet_stream_parser *parser = priv->parser[s->direction]; if (packet_stream_parser_add_payload(parser, s->pload, s->plen) != POM_OK) return PROTO_ERR; char *line = NULL; size_t len = 0; while (1) { // Some check to do prior to parse the payload if (s->direction == POM_DIR_REVERSE(priv->server_direction)) { if (priv->flags & PROTO_SMTP_FLAG_STARTTLS) { // Last command was a STARTTLS command, this is the TLS negociation // Since we can't parse this, mark it as invalid priv->flags |= PROTO_SMTP_FLAG_INVALID; return PROTO_OK; } else if (priv->flags & PROTO_SMTP_FLAG_CLIENT_DATA) { // We are receiving payload data, check where the end is void *pload; size_t plen; packet_stream_parser_get_remaining(parser, &pload, &plen); if (!plen) return PROTO_OK; // Look for the "<CR><LF>.<CR><LF>" sequence if (priv->data_end_pos > 0) { // The previous packet ended with something that might be the final sequence // Check if we have the rest int i, found = 1; for (i = 0; i < PROTO_SMTP_DATA_END_LEN - priv->data_end_pos && i <= plen; i++) { if (*(char*)(pload + i) != PROTO_SMTP_DATA_END[priv->data_end_pos + i]) { found = 0; break; } } if (found) { // If we have already processed the dot after <CR><LF> there is no way to remove it // Thus we mark this connection as invalid. Most MTA will send at worst the last // 3 bytes of the end sequence in a sequence packet if (i != plen || (priv->data_end_pos >= 2 && plen < 3)) { pomlog(POMLOG_DEBUG "The final line was not at the of a packet as expected !"); priv->flags |= PROTO_SMTP_FLAG_INVALID; event_process_end(priv->data_evt); priv->data_evt = NULL; return PROTO_OK; } s_next->pload = pload; s_next->plen = plen - PROTO_SMTP_DATA_END_LEN + 2; // The last line return is part of the payload priv->flags |= PROTO_SMTP_FLAG_CLIENT_DATA_END; priv->flags &= ~PROTO_SMTP_FLAG_CLIENT_DATA; priv->data_end_pos = 0; return PROTO_OK; } priv->data_end_pos = 0; } char *dotline = pom_strnstr(pload, PROTO_SMTP_DATA_END, plen); if (dotline) { if (pload + plen - PROTO_SMTP_DATA_END_LEN != dotline) { pomlog(POMLOG_DEBUG "The final line was not at the of a packet as expected !"); priv->flags |= PROTO_SMTP_FLAG_INVALID; event_process_end(priv->data_evt); priv->data_evt = NULL; return PROTO_OK; } s_next->pload = pload; s_next->plen = plen - PROTO_SMTP_DATA_END_LEN + 2; // The last line return is part of the payload priv->flags |= PROTO_SMTP_FLAG_CLIENT_DATA_END; priv->flags &= ~PROTO_SMTP_FLAG_CLIENT_DATA; } else { // Check if the end of the payload contains part of the "<CR><LF>.<CR><LF>" sequence int i, found = 0; for (i = 1 ; (i < PROTO_SMTP_DATA_END_LEN) && (i <= plen); i++) { if (!memcmp(pload + plen - i, PROTO_SMTP_DATA_END, i)) { found = 1; break; } } if (found) priv->data_end_pos = i; s_next->pload = pload; s_next->plen = plen; } return PROTO_OK; } } // Process commands if (packet_stream_parser_get_line(parser, &line, &len) != POM_OK) return PROTO_ERR; if (!line) return PROTO_OK; if (!len) // Probably a missed packet return PROTO_OK; // Try to find the server direction if (priv->server_direction == POM_DIR_UNK) { unsigned int code = atoi(line); if (code > 0) { priv->server_direction = s->direction; } else { priv->server_direction = POM_DIR_REVERSE(s->direction); } } if (s->direction == priv->server_direction) { // Parse the response code and generate the event if ((len < 5) || // Server response is 3 digit error code, a space or hyphen and then at least one letter of text (line[3] != ' ' && line[3] != '-')) { pomlog(POMLOG_DEBUG "Too short or invalid response from server"); priv->flags |= PROTO_SMTP_FLAG_INVALID; return POM_OK; } int code = atoi(line); if (code == 0) { pomlog(POMLOG_DEBUG "Invalid response from server"); priv->flags |= PROTO_SMTP_FLAG_INVALID; return POM_OK; } if (event_has_listener(ppriv->evt_reply)) { struct data *evt_data = NULL; if (priv->reply_evt) { evt_data = event_get_data(priv->reply_evt); uint16_t cur_code = *PTYPE_UINT16_GETVAL(evt_data[proto_smtp_reply_code].value); if (cur_code != code) { pomlog(POMLOG_WARN "Multiline code not the same as previous line : %hu -> %hu", cur_code, code); event_process_end(priv->reply_evt); priv->reply_evt = NULL; } } if (!priv->reply_evt) { priv->reply_evt = event_alloc(ppriv->evt_reply); if (!priv->reply_evt) return PROTO_ERR; evt_data = event_get_data(priv->reply_evt); PTYPE_UINT16_SETVAL(evt_data[proto_smtp_reply_code].value, code); data_set(evt_data[proto_smtp_reply_code]); } if (len > 4) { struct ptype *txt = ptype_alloc("string"); if (!txt) return PROTO_ERR; PTYPE_STRING_SETVAL_N(txt, line + 4, len - 4); if (data_item_add_ptype(evt_data, proto_smtp_reply_text, strdup("text"), txt) != POM_OK) return PROTO_ERR; } if (!event_is_started(priv->reply_evt)) event_process_begin(priv->reply_evt, stack, stack_index, p->ts); } if (line[3] != '-') { // Last line in the response if (priv->reply_evt) { event_process_end(priv->reply_evt); priv->reply_evt = NULL; } } if (priv->flags & PROTO_SMTP_FLAG_STARTTLS) { // The last command was STARTTLS priv->flags &= ~PROTO_SMTP_FLAG_STARTTLS; if (code == 220) { // TLS has the go, we can't parse from now so mark as invalid priv->flags |= PROTO_SMTP_FLAG_INVALID; return POM_OK; } } } else { // Client command if (len < 4) { // Client commands are at least 4 bytes long pomlog(POMLOG_DEBUG "Too short or invalid query from client"); priv->flags |= PROTO_SMTP_FLAG_INVALID; return POM_OK; } // Make sure it's a command by checking it's at least a four letter word int i; for (i = 0; i < 4; i++) { // In some case it can also be a base64 encoded word if (! ((line[i] >= 'A' && line[i] <= 'Z') || (line[i] >= 'a' && line[i] <= 'z') || (line[i] >= '0' && line [i] <= '9') || line[i] == '=')) break; } if ((i < 4)) { pomlog(POMLOG_DEBUG "Recieved invalid client command"); priv->flags |= PROTO_SMTP_FLAG_INVALID; return POM_OK; } if (!strncasecmp(line, "DATA", strlen("DATA")) && len == strlen("DATA")) { priv->flags |= PROTO_SMTP_FLAG_CLIENT_DATA; } else if (!strncasecmp(line, "STARTTLS", strlen("STARTTLS")) && len == strlen("STARTTLS")) { priv->flags |= PROTO_SMTP_FLAG_STARTTLS; } if (event_has_listener(ppriv->evt_cmd)) { struct event *evt = event_alloc(ppriv->evt_cmd); if (!evt) return PROTO_ERR; size_t cmdlen = len; char *space = memchr(line, ' ', len); if (space) cmdlen = space - line; struct data *evt_data = event_get_data(evt); PTYPE_STRING_SETVAL_N(evt_data[proto_smtp_cmd_name].value, line, cmdlen); data_set(evt_data[proto_smtp_cmd_name]); if (space) { PTYPE_STRING_SETVAL_N(evt_data[proto_smtp_cmd_arg].value, space + 1, len - 1 - cmdlen); data_set(evt_data[proto_smtp_cmd_arg]); } if (priv->flags & PROTO_SMTP_FLAG_CLIENT_DATA) { // The event ends at the end of the message priv->data_evt = evt; return event_process_begin(evt, stack, stack_index, p->ts); } else { return event_process(evt, stack, stack_index, p->ts); } } } } return PROTO_OK; }
static int proto_ipv4_process(void *proto_priv, struct packet *p, struct proto_process_stack *stack, unsigned int stack_index) { struct proto_process_stack *s = &stack[stack_index]; struct proto_process_stack *s_next = &stack[stack_index + 1]; struct ip* hdr = s->pload; unsigned int hdr_len = hdr->ip_hl * 4; if (s->plen < sizeof(struct ip) || // length smaller than header hdr->ip_hl < 5 || // ip header < 5 bytes ntohs(hdr->ip_len) < hdr_len || // datagram size < ip header length ntohs(hdr->ip_len) > s->plen) { // datagram size > given size return PROTO_INVALID; } PTYPE_IPV4_SETADDR(s->pkt_info->fields_value[proto_ipv4_field_src], hdr->ip_src); PTYPE_IPV4_SETADDR(s->pkt_info->fields_value[proto_ipv4_field_dst], hdr->ip_dst); PTYPE_UINT8_SETVAL(s->pkt_info->fields_value[proto_ipv4_field_tos], hdr->ip_tos); PTYPE_UINT8_SETVAL(s->pkt_info->fields_value[proto_ipv4_field_ttl], hdr->ip_ttl); // Handle conntrack stuff if (conntrack_get(stack, stack_index) != POM_OK) return PROTO_ERR; s_next->pload = s->pload + hdr_len; s_next->plen = ntohs(hdr->ip_len) - hdr_len; s_next->proto = proto_get_by_number(s->proto, hdr->ip_p); int res = POM_ERR; if (s->ce->children) { res = conntrack_delayed_cleanup(s->ce, 0, p->ts); } else { uint32_t *conntrack_timeout = PTYPE_UINT32_GETVAL(param_conntrack_timeout); res = conntrack_delayed_cleanup(s->ce, *conntrack_timeout, p->ts); } if (res == POM_ERR) { conntrack_unlock(s->ce); return PROTO_ERR; } uint16_t frag_off = ntohs(hdr->ip_off); // Check if packet is fragmented and need more handling if (frag_off & IP_DONT_FRAG) { conntrack_unlock(s->ce); return PROTO_OK; // Nothing to do } if (!(frag_off & IP_MORE_FRAG) && !(frag_off & IP_OFFSET_MASK)) { conntrack_unlock(s->ce); return PROTO_OK; // Nothing to do, full packet } uint16_t offset = (frag_off & IP_OFFSET_MASK) << 3; size_t frag_size = ntohs(hdr->ip_len) - (hdr->ip_hl * 4); // Ignore invalid fragments if (frag_size > 0xFFFF) { conntrack_unlock(s->ce); return PROTO_INVALID; } if (frag_size > s->plen + hdr_len) { conntrack_unlock(s->ce); return PROTO_INVALID; } // Account for one more fragment registry_perf_inc(perf_frags, 1); struct proto_ipv4_fragment *tmp = s->ce->priv; // Let's find the right buffer for (; tmp && tmp->id != hdr->ip_id; tmp = tmp->next); if (!tmp) { // Buffer not found, create it tmp = malloc(sizeof(struct proto_ipv4_fragment)); if (!tmp) { pom_oom(sizeof(struct proto_ipv4_fragment)); conntrack_unlock(s->ce); return PROTO_ERR; } memset(tmp, 0, sizeof(struct proto_ipv4_fragment)); tmp->t = conntrack_timer_alloc(s->ce, proto_ipv4_fragment_cleanup, tmp); if (!tmp->t) { conntrack_unlock(s->ce); free(tmp); return PROTO_ERR; } tmp->id = hdr->ip_id; if (!s_next->proto) { // Set processed flag so no attempt to process this will be done tmp->flags |= PROTO_IPV4_FLAG_PROCESSED; conntrack_unlock(s->ce); conntrack_timer_cleanup(tmp->t); free(tmp); return PROTO_STOP; } tmp->multipart = packet_multipart_alloc(s_next->proto, 0); if (!tmp->multipart) { conntrack_unlock(s->ce); conntrack_timer_cleanup(tmp->t); free(tmp); return PROTO_ERR; } tmp->next = s->ce->priv; if (tmp->next) tmp->next->prev = tmp; s->ce->priv = tmp; } // Fragment was already handled if (tmp->flags & PROTO_IPV4_FLAG_PROCESSED) { conntrack_unlock(s->ce); registry_perf_inc(perf_frags_dropped, 1); return PROTO_STOP; } // Add the fragment if (packet_multipart_add_packet(tmp->multipart, p, offset, frag_size, (s->pload - (void*)p->buff) + (hdr->ip_hl * 4)) != POM_OK) { conntrack_unlock(s->ce); packet_multipart_cleanup(tmp->multipart); conntrack_timer_cleanup(tmp->t); free(tmp); return PROTO_ERR; } tmp->count++; // Schedule the timeout for the fragment uint32_t *frag_timeout = PTYPE_UINT32_GETVAL(param_frag_timeout); conntrack_timer_queue(tmp->t, *frag_timeout, p->ts); if (!(frag_off & IP_MORE_FRAG)) tmp->flags |= PROTO_IPV4_FLAG_GOT_LAST; if ((tmp->flags & PROTO_IPV4_FLAG_GOT_LAST) && !tmp->multipart->gaps) tmp->flags |= PROTO_IPV4_FLAG_PROCESSED; conntrack_unlock(s->ce); if ((tmp->flags & PROTO_IPV4_FLAG_PROCESSED)) { int res = packet_multipart_process(tmp->multipart, stack, stack_index + 1); tmp->multipart = NULL; // Multipart will be cleared automatically if (res == PROTO_ERR) { return PROTO_ERR; } else if (res == PROTO_INVALID) { registry_perf_inc(perf_frags_dropped, tmp->count); } else { registry_perf_inc(perf_reassembled_pkts, 1); } } return PROTO_STOP; // Stop processing the packet }
static int proto_ppp_pap_process(void *proto_priv, struct packet *p, struct proto_process_stack *stack, unsigned int stack_index) { struct proto_process_stack *s = &stack[stack_index]; if (sizeof(struct ppp_pap_header) > s->plen) return PROTO_INVALID; struct ppp_pap_header *pchdr = s->pload; size_t len = ntohs(pchdr->length); if (len > s->plen) return PROTO_INVALID; // Keep only the payload len len -= sizeof(struct ppp_pap_header); PTYPE_UINT8_SETVAL(s->pkt_info->fields_value[proto_ppp_pap_field_code], pchdr->code); PTYPE_UINT8_SETVAL(s->pkt_info->fields_value[proto_ppp_pap_field_identifier], pchdr->identifier); struct proto_ppp_pap_priv *priv = proto_priv; if (conntrack_get(stack, stack_index) != POM_OK) return PROTO_ERR; if (conntrack_delayed_cleanup(s->ce, *PTYPE_UINT32_GETVAL(priv->p_auth_timeout), p->ts) != POM_OK) { conntrack_unlock(s->ce); return PROTO_ERR; } conntrack_unlock(s->ce); if (pchdr->code == 1 && event_has_listener(priv->evt_request)) { if (len < 4) return PROTO_INVALID; uint8_t *peer_id_len = s->pload + sizeof(struct ppp_pap_header); if (*peer_id_len > len - 2) return PROTO_INVALID; len -= (*peer_id_len + 1); uint8_t *pwd_len = peer_id_len + *peer_id_len + 1; if (*pwd_len > len - 1) return PROTO_INVALID; // Process the challenge/response event struct event *evt = event_alloc(priv->evt_request); if (!evt) return PROTO_ERR; struct data *evt_data = event_get_data(evt); PTYPE_UINT8_SETVAL(evt_data[evt_ppp_pap_request_code].value, pchdr->code); data_set(evt_data[evt_ppp_pap_request_code]); PTYPE_UINT8_SETVAL(evt_data[evt_ppp_pap_request_identifier].value, pchdr->identifier); data_set(evt_data[evt_ppp_pap_request_identifier]); PTYPE_STRING_SETVAL_N(evt_data[evt_ppp_pap_request_peer_id].value, (char *)peer_id_len + 1, *peer_id_len); data_set(evt_data[evt_ppp_pap_request_peer_id]); PTYPE_STRING_SETVAL_N(evt_data[evt_ppp_pap_request_password].value, (char *)pwd_len + 1, *pwd_len); data_set(evt_data[evt_ppp_pap_request_password]); if (event_process(evt, stack, stack_index, p->ts) != POM_OK) return PROTO_ERR; } if ((pchdr->code == 2 || pchdr->code == 3) && event_has_listener(priv->evt_ack_nack)) { struct event *evt = event_alloc(priv->evt_ack_nack); if (!evt) return PROTO_ERR; struct data *evt_data = event_get_data(evt); PTYPE_UINT8_SETVAL(evt_data[evt_ppp_pap_ack_nack_code].value, pchdr->code); data_set(evt_data[evt_ppp_pap_ack_nack_code]); PTYPE_UINT8_SETVAL(evt_data[evt_ppp_pap_ack_nack_identifier].value, pchdr->identifier); data_set(evt_data[evt_ppp_pap_ack_nack_identifier]); uint8_t *msg_len = s->pload + sizeof(struct ppp_pap_header); if (*msg_len > len - 1) return PROTO_INVALID; PTYPE_STRING_SETVAL_N(evt_data[evt_ppp_pap_ack_nack_message].value, (char *)msg_len + 1, *msg_len); data_set(evt_data[evt_ppp_pap_ack_nack_message]); if (event_process(evt, stack, stack_index, p->ts) != POM_OK) return PROTO_ERR; } return PROTO_OK; }
int conntrack_cleanup(struct conntrack_tables *ct, uint32_t hash, struct conntrack_entry *ce) { // Remove the conntrack from the conntrack table pom_mutex_lock(&ct->locks[hash]); // Try to find the conntrack in the list struct conntrack_list *lst = NULL; for (lst = ct->table[hash]; lst && lst->ce != ce; lst = lst->next); if (!lst) { pom_mutex_unlock(&ct->locks[hash]); pomlog(POMLOG_ERR "Trying to cleanup a non existing conntrack : %p", ce); return POM_OK; } conntrack_lock(ce); if (ce->refcount) { debug_conntrack(POMLOG_ERR "Conntrack %p is still being referenced : %u !", ce, ce->refcount); conntrack_delayed_cleanup(ce, 1, core_get_clock_last()); conntrack_unlock(ce); pom_mutex_unlock(&ct->locks[hash]); return POM_OK; } if (lst->prev) lst->prev->next = lst->next; else ct->table[hash] = lst->next; if (lst->next) lst->next->prev = lst->prev; free(lst); pom_mutex_unlock(&ct->locks[hash]); if (ce->cleanup_timer && ce->cleanup_timer != (void *) -1) { conntrack_timer_cleanup(ce->cleanup_timer); ce->cleanup_timer = (void *) -1; // Mark that the conntrack is being cleaned up } // Once the conntrack is removed from the hash table, it will not be referenced ever again conntrack_unlock(ce); if (ce->parent) { debug_conntrack("Cleaning up conntrack %p, with parent %p", ce, ce->parent->ce); } else { debug_conntrack("Cleaning up conntrack %p, with no parent", ce); } if (ce->parent) { // Remove the child from the parent // Make sure the parent still exists uint32_t hash = ce->parent->hash; pom_mutex_lock(&ce->parent->ct->locks[hash]); for (lst = ce->parent->ct->table[hash]; lst && lst->ce != ce->parent->ce; lst = lst->next); if (lst) { conntrack_lock(ce->parent->ce); struct conntrack_node_list *tmp = ce->parent->ce->children; for (; tmp && tmp->ce != ce; tmp = tmp->next); if (tmp) { if (tmp->prev) tmp->prev->next = tmp->next; else ce->parent->ce->children = tmp->next; if (tmp->next) tmp->next->prev = tmp->prev; free(tmp); } else { pomlog(POMLOG_WARN "Conntrack %s not found in parent's %s children list", ce, ce->parent->ce); } if (!ce->parent->ce->children) // Parent has no child anymore, clean it up after some time conntrack_delayed_cleanup(ce->parent->ce, CONNTRACK_CHILDLESS_TIMEOUT, core_get_clock_last()); conntrack_unlock(ce->parent->ce); } else { debug_conntrack("Parent conntrack %p not found while cleaning child %p !", ce->parent->ce, ce); } pom_mutex_unlock(&ce->parent->ct->locks[hash]); free(ce->parent); } if (ce->session) conntrack_session_refcount_dec(ce->session); // Cleanup private stuff from the conntrack if (ce->priv && ce->proto->info->ct_info->cleanup_handler) { if (ce->proto->info->ct_info->cleanup_handler(ce->priv) != POM_OK) pomlog(POMLOG_WARN "Unable to free the private memory of a conntrack"); } // Cleanup the priv_list struct conntrack_priv_list *priv_lst = ce->priv_list; while (priv_lst) { if (priv_lst->cleanup) { if (priv_lst->cleanup(priv_lst->obj, priv_lst->priv) != POM_OK) pomlog(POMLOG_WARN "Error while cleaning up private objects in conntrack_entry"); } ce->priv_list = priv_lst->next; free(priv_lst); priv_lst = ce->priv_list; } // Cleanup the children while (ce->children) { struct conntrack_node_list *child = ce->children; ce->children = child->next; if (conntrack_cleanup(child->ct, child->hash, child->ce) != POM_OK) return POM_ERR; free(child); } if (ce->fwd_value) ptype_cleanup(ce->fwd_value); if (ce->rev_value) ptype_cleanup(ce->rev_value); pthread_mutex_destroy(&ce->lock); registry_perf_dec(ce->proto->perf_conn_cur, 1); free(ce); return POM_OK; }
int conntrack_get_unique_from_parent(struct proto_process_stack *stack, unsigned int stack_index) { struct conntrack_node_list *child = NULL; struct conntrack_list *lst = NULL; struct proto_process_stack *s = &stack[stack_index]; struct proto_process_stack *s_prev = &stack[stack_index - 1]; struct conntrack_entry *parent = s_prev->ce; if (!s->proto) { pomlog(POMLOG_ERR "Cannot allocate conntrack for NULL proto"); return POM_ERR; } if (!parent) { pomlog(POMLOG_ERR "Cannot allocate unique conntrack without a parent"); return POM_ERR; } if (s->ce) { // This should only occur in the case that an expectation matched // Make sure the conntrack is locked int res = pthread_mutex_trylock(&s->ce->lock); if (res && res != EBUSY && res == EDEADLK) { pomlog(POMLOG_ERR "Error while locking the conntrack : %s", pom_strerror(res)); return POM_ERR; } return POM_OK; } conntrack_lock(parent); struct conntrack_tables *ct = s->proto->ct; struct conntrack_entry *res = NULL; // Look for the conntrack if (parent->children) { struct conntrack_node_list *child = parent->children; for (child = parent->children; child && child->ce->proto != s->proto; child = child->next); if (child) res = child->ce; } if (!res) { // Alloc the conntrack res = malloc(sizeof(struct conntrack_entry)); if (!res) { pom_oom(sizeof(struct conntrack_entry)); goto err; } memset(res, 0, sizeof(struct conntrack_entry)); res->proto = s->proto; if (pom_mutex_init_type(&res->lock, PTHREAD_MUTEX_ERRORCHECK) != POM_OK) goto err; // Alloc the child list child = malloc(sizeof(struct conntrack_node_list)); if (!child) goto err; memset(child, 0, sizeof(struct conntrack_node_list)); child->ce = res; child->ct = ct; // Alloc the parent node res->parent = malloc(sizeof(struct conntrack_node_list)); if (!res->parent) { free(child); goto err; } memset(res->parent, 0, sizeof(struct conntrack_node_list)); res->parent->ce = parent; res->parent->ct = parent->proto->ct; res->parent->hash = parent->hash; // Alloc the list node lst = malloc(sizeof(struct conntrack_list)); if (!lst) { pom_oom(sizeof(struct conntrack_list)); goto err; } memset(lst, 0, sizeof(struct conntrack_list)); lst->ce = res; // Add the child to the parent child->next = parent->children; if (child->next) child->next->prev = child; parent->children = child; // Add the conntrack to the table pom_mutex_lock(&ct->locks[0]); lst->next = ct->table[0]; if (lst->next) lst->next->prev = lst; ct->table[0] = lst; pom_mutex_unlock(&ct->locks[0]); debug_conntrack("Allocated conntrack %p with parent %p (uniq child)", res, parent); registry_perf_inc(s->proto->perf_conn_cur, 1); registry_perf_inc(s->proto->perf_conn_tot, 1); } conntrack_unlock(parent); conntrack_lock(res); res->refcount++; s->ce = res; s->direction = s_prev->direction; struct proto_process_stack *s_next = &stack[stack_index + 1]; s_next->direction = s->direction; return POM_OK; err: if (res) { pthread_mutex_destroy(&res->lock); free(res); } if (child) free(child); conntrack_unlock(parent); return POM_ERR; }
int proto_process(struct packet *p, struct proto_process_stack *stack, unsigned int stack_index) { struct proto_process_stack *s = &stack[stack_index]; struct proto *proto = s->proto; if (!proto || !proto->info->process) return PROTO_ERR; int res = proto->info->process(proto->priv, p, stack, stack_index); registry_perf_inc(proto->perf_pkts, 1); registry_perf_inc(proto->perf_bytes, s->plen); if (res != PROTO_OK) return res; int matched = 0; // Process the expectations ! pom_rwlock_rlock(&proto->expectation_lock); struct proto_expectation *e = NULL; for (e = proto->expectations; e; e = e->next) { if (e->flags & PROTO_EXPECTATION_FLAG_MATCHED) { // Another thread already matched the expectation, continue continue; } // Bit one means it matches the forward direction // Bit two means it matches the reverse direction int expt_dir = 3; struct proto_expectation_stack *es = e->tail; int stack_index_tmp = stack_index; while (es) { struct proto_process_stack *s_tmp = &stack[stack_index_tmp]; if (s_tmp->proto != es->proto) { expt_dir = 0; break; } struct ptype *fwd_value = s_tmp->pkt_info->fields_value[s_tmp->proto->info->ct_info->fwd_pkt_field_id]; struct ptype *rev_value = s_tmp->pkt_info->fields_value[s_tmp->proto->info->ct_info->rev_pkt_field_id]; if (expt_dir & 1) { if ((es->fields[POM_DIR_FWD] && !ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_FWD], fwd_value)) || (es->fields[POM_DIR_REV] && !ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_REV], rev_value))) { expt_dir &= ~1; // It doesn't match in the forward direction } } if (expt_dir & 2) { if ((es->fields[POM_DIR_FWD] && !ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_FWD], rev_value)) || (es->fields[POM_DIR_REV] && !ptype_compare_val(PTYPE_OP_EQ, es->fields[POM_DIR_REV], fwd_value))) { expt_dir &= ~2; } } if (!expt_dir) break; es = es->prev; stack_index_tmp--; } if (expt_dir) { // It matched if (!(__sync_fetch_and_or(&e->flags, PROTO_EXPECTATION_FLAG_MATCHED) & PROTO_EXPECTATION_FLAG_MATCHED)) { // Something matched matched++; } } } pom_rwlock_unlock(&proto->expectation_lock); if (!matched) return POM_OK; // At least one expectation matched ! debug_expectation("%u expectation matched !", matched); // Relock with write access pom_rwlock_wlock(&proto->expectation_lock); e = proto->expectations; while (e) { struct proto_expectation *cur = e; e = e->next; if (!(cur->flags & PROTO_EXPECTATION_FLAG_MATCHED)) continue; // Remove the expectation from the conntrack if (cur->next) cur->next->prev = cur->prev; if (cur->prev) cur->prev->next = cur->next; else proto->expectations = cur->next; // Remove matched and queued flags __sync_fetch_and_and(&cur->flags, ~(PROTO_EXPECTATION_FLAG_MATCHED | PROTO_EXPECTATION_FLAG_QUEUED)); struct proto_process_stack *s_next = &stack[stack_index + 1]; s_next->proto = cur->proto; if (conntrack_get_unique_from_parent(stack, stack_index + 1) != POM_OK) { proto_expectation_cleanup(cur); continue; } if (!s_next->ce->priv) { s_next->ce->priv = cur->priv; // Prevent cleanup of private data while cleaning the expectation cur->priv = NULL; } if (cur->session) { if (conntrack_session_bind(s_next->ce, cur->session)) { proto_expectation_cleanup(cur); continue; } } registry_perf_dec(cur->proto->perf_expt_pending, 1); registry_perf_inc(cur->proto->perf_expt_matched, 1); if (cur->match_callback) { // Call the callback with the conntrack locked cur->match_callback(cur, cur->callback_priv, s_next->ce); // Nullify callback_priv so it doesn't get cleaned up cur->callback_priv = NULL; } if (cur->expiry) { // The expectation was added using 'add_and_cleanup' function proto_expectation_cleanup(cur); } conntrack_unlock(s_next->ce); } pom_rwlock_unlock(&proto->expectation_lock); return res; }
static int proto_tftp_process(void *proto_priv, struct packet *p, struct proto_process_stack *stack, unsigned int stack_index) { struct proto_process_stack *s = &stack[stack_index]; struct proto_process_stack *s_prev = &stack[stack_index - 1]; struct proto_process_stack *s_next = &stack[stack_index + 1]; if (conntrack_get_unique_from_parent(stack, stack_index) != POM_OK) { pomlog(POMLOG_ERR "Could not get a conntrack entry"); return PROTO_ERR; } struct proto_tftp_conntrack_priv *priv = s->ce->priv; if (!priv) { priv = malloc(sizeof(struct proto_tftp_conntrack_priv)); if (!priv) { pom_oom(sizeof(struct proto_tftp_conntrack_priv)); conntrack_unlock(s->ce); return POM_ERR; } memset(priv, 0, sizeof(struct proto_tftp_conntrack_priv)); s->ce->priv = priv; } if (priv->flags & PROTO_TFTP_CONN_INVALID) { conntrack_unlock(s->ce); return PROTO_INVALID; } void *pload = s->pload; uint32_t plen = s->plen; // proto_tftp only process up to the opcode field // afterwards, it's up to the analyzer to parse the rest uint16_t opcode = ntohs(*((uint16_t*)pload)); PTYPE_UINT16_SETVAL(s->pkt_info->fields_value[proto_tftp_field_opcode], opcode); pload += sizeof(uint16_t); plen -= sizeof(uint16_t); s_next->pload = pload; s_next->plen = plen; switch (opcode) { case tftp_rrq: case tftp_wrq: { // Find the filename char *filename = pload; char *mode = memchr(filename, 0, plen - 1); if (!mode) { priv->flags |= PROTO_TFTP_CONN_INVALID; conntrack_unlock(s->ce); debug_tftp("End of filename not found in read/write request"); return PROTO_INVALID; } mode++; ssize_t filename_len = mode - filename; char *end = memchr(mode, 0, plen - filename_len); if (!end) { priv->flags |= PROTO_TFTP_CONN_INVALID; conntrack_unlock(s->ce); debug_tftp("End of mode not found in read/write request"); return PROTO_INVALID; } debug_tftp("Got read/write request for filename \"%s\" with mode \"%s\"", filename, mode); struct conntrack_session *session = conntrack_session_get(s->ce); if (!session) { conntrack_unlock(s->ce); return POM_ERR; } // We don't need to do anything with the session conntrack_session_unlock(session); struct proto_expectation *expt = proto_expectation_alloc_from_conntrack(s_prev->ce, proto_tftp, NULL); if (!expt) { conntrack_unlock(s->ce); return PROTO_ERR; } proto_expectation_set_field(expt, -1, NULL, POM_DIR_REV); if (proto_expectation_add(expt, session, PROTO_TFTP_EXPT_TIMER, p->ts) != POM_OK) { conntrack_unlock(s->ce); proto_expectation_cleanup(expt); return PROTO_ERR; } break; } case tftp_data: { if (plen < 2) { priv->flags |= PROTO_TFTP_CONN_INVALID; conntrack_unlock(s->ce); return PROTO_INVALID; } uint16_t block_id = ntohs(*((uint16_t*)(pload))); int set_start_seq = 0; if (!priv->stream) { priv->stream = stream_alloc(PROTO_TFTP_STREAM_BUFF, s->ce, 0, proto_tftp_process_payload); if (!priv->stream) { conntrack_unlock(s->ce); return PROTO_ERR; } stream_set_timeout(priv->stream, PROTO_TFTP_PKT_TIMER); set_start_seq = 1; } conntrack_unlock(s->ce); if (set_start_seq) stream_set_start_seq(priv->stream, s->direction, PROTO_TFTP_BLK_SIZE + 2); int res = stream_process_packet(priv->stream, p, stack, stack_index + 1, block_id * (PROTO_TFTP_BLK_SIZE + 2), 0); return (res == PROTO_OK ? PROTO_STOP : res); } case tftp_ack: // Nothing to do break; case tftp_error: // An error occured, cleanup this conntrack soon conntrack_delayed_cleanup(s->ce, 1, p->ts); break; default: priv->flags |= PROTO_TFTP_CONN_INVALID; conntrack_unlock(s->ce); return PROTO_INVALID; } conntrack_delayed_cleanup(s->ce, PROTO_TFTP_PKT_TIMER, p->ts); conntrack_unlock(s->ce); return PROTO_OK; }