Exemple #1
0
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;
}
Exemple #2
0
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;
}
Exemple #3
0
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;

}
Exemple #4
0
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;
}