static int sdp_extract_host(unsigned unused_ cmd, struct liner *liner, void *info_) { struct sdp_proto_info *info = info_; #define IN_IP "IN IP" #define IN_IP_LEN strlen(IN_IP) if (liner_tok_length(liner) < IN_IP_LEN) return -1; if (strncasecmp(liner->start, IN_IP, IN_IP_LEN)) return -1; char const *start = liner->start + IN_IP_LEN; int version = start[0] - '0'; if (version != 4 && version != 6) { SLOG(LOG_DEBUG, "Bogus IP version (%d)", version); return -1; } struct liner space_liner; liner_init(&space_liner, &delim_spaces, (char const *)start, liner_tok_length(liner) - IN_IP_LEN); liner_next(&space_liner); // skipping the IP version number #undef IN_IP #undef IN_IP_LEN if (0 != ip_addr_ctor_from_str(&info->host, space_liner.start, liner_tok_length(&space_liner), version)) return -1; info->set_values |= SDP_HOST_SET; SLOG(LOG_DEBUG, "host found (%s)", ip_addr_2_str(&info->host)); return 0; }
static void parse_observed_event(struct mgcp_proto_info *info, struct liner *liner) { size_t len = strlen(info->dialed); struct liner tokenizer; for ( liner_init(&tokenizer, ¶m_delims, liner->start, liner_tok_length(liner)); ! liner_eof(&tokenizer); liner_next(&tokenizer) ) { unsigned events = parse_events(&tokenizer); if (events) { info->observed |= events; continue; } if (liner_tok_length(&tokenizer) < 3) continue; if (tokenizer.start[0] != 'D' && tokenizer.start[0] != 'd') continue; if (tokenizer.start[1] != '/') continue; // append dialed 'digit' into dialed info for (unsigned d = 2; d < liner_tok_length(&tokenizer); d++) { if (len >= sizeof(info->dialed)-1) break; char const c = tokenizer.start[d]; if (c != 'T' && c != 't') info->dialed[len++] = c; } } assert(len < sizeof(info->dialed)); info->dialed[len] = '\0'; }
unsigned parse_events(struct liner *liner) { unsigned flags = 0; for (unsigned e = 0; e < NB_ELEMS(events); e++) { if ( (liner_tok_length(liner) >= 4 && 0 == strncasecmp(liner->start, events[e].code, 4)) || (events[e].from_line && (liner_tok_length(liner) >= 2 && 0 == strncasecmp(liner->start, events[e].code+2, 2))) ) { flags |= events[e].flag; } } return flags; }
static int sip_extract_via(unsigned unused_ field, struct liner *liner, void *info_) { struct sip_proto_info *info = info_; // We are interrested only in the first Via stanza if (info->set_values & SIP_VIA_SET) return 0; // We are parsing something like : SIP/2.0/UDP 123.456.789.123:12345;foo=bar etc struct liner spacer; liner_init(&spacer, &delim_blanks, liner->start, liner_tok_length(liner)); // Extract IP protocol # define SIP_VER "SIP/2.0/" if (liner_tok_length(&spacer) < strlen(SIP_VER) + 3) { SLOG(LOG_DEBUG, "Via token too short (%.*s)", (int)liner_tok_length(&spacer), spacer.start); return 0; } char const *proto_str = spacer.start + strlen(SIP_VER); if (0 == strncasecmp(proto_str, "UDP", 3)) { info->via.protocol = IPPROTO_UDP; } else if (0 == strncasecmp(proto_str, "TCP", 3)) { info->via.protocol = IPPROTO_TCP; } else { SLOG(LOG_DEBUG, "Via protocol unknown (%.*s)", 3, proto_str); return 0; } // Extract IP liner_next(&spacer); struct liner semicoloner; // first get IP:port or IP liner_init(&semicoloner, &delim_semicolons, spacer.start, liner_tok_length(&spacer)); struct liner coloner; // then only IP and then port liner_init(&coloner, &delim_colons, semicoloner.start, liner_tok_length(&semicoloner)); if (0 != ip_addr_ctor_from_str(&info->via.addr, coloner.start, liner_tok_length(&coloner), 4)) { // FIXME: ip_addr_ctor_from_str should detect IP version SLOG(LOG_DEBUG, "Cannot extract IP addr from Via string (%.*s)", (int)liner_tok_length(&coloner), coloner.start); return 0; } // Extract Port liner_next(&coloner); if (liner_eof(&coloner)) { // no port specified SLOG(LOG_DEBUG, "No port specified in Via string, assuming "STRIZE(SIP_PORT)); info->via.port = SIP_PORT; } else { // port is present char const *end; info->via.port = liner_strtoull(&coloner, &end, 10); if (end == coloner.start) { SLOG(LOG_DEBUG, "Cannot extract IP port from Via string (%.*s)", (int)liner_tok_length(&coloner), coloner.start); return 0; } } info->set_values |= SIP_VIA_SET; return 0; }
int sdper_parse(struct sdper const *sdper, size_t *head_sz, uint8_t const *packet, size_t packet_len, void *user_data) { // REVIEW: eols and spcs should be provided by liner. struct liner_delimiter eols[] = { { "\r\n", 2 }, { "\n", 1 } }, cols[] = { { "= ", 2}, { "=", 1 } }; struct liner_delimiter_set const lines = { NB_ELEMS(eols), eols, false }, eq = { NB_ELEMS(cols), cols, true }; struct liner liner, tokenizer; liner_init(&liner, &lines, (char const *)packet, packet_len); // Parse header fields while (true) { // Next line if (liner_eof(&liner)) break; // Otherwise tokenize the header line liner_init(&tokenizer, &eq, liner.start, liner_tok_length(&liner)); for (unsigned f = 0; f < sdper->nb_fields; f++) { struct sdper_field const *field = sdper->fields + f; size_t len = liner_tok_length(&tokenizer); if (len != field->length) continue; if (0 != strncasecmp(field->name, tokenizer.start, len)) continue; SLOG(LOG_DEBUG, "Found field %s", field->name); liner_next(&tokenizer); int ret = field->cb(f, &tokenizer, user_data); if (ret) return ret; break; } liner_next(&liner); } if (head_sz) *head_sz = liner_parsed(&liner); return 0; }
static void parse_signal_request(struct mgcp_proto_info *info, struct liner *liner) { struct liner tokenizer; for ( liner_init(&tokenizer, ¶m_delims, liner->start, liner_tok_length(liner)); ! liner_eof(&tokenizer); liner_next(&tokenizer) ) { info->signaled |= parse_events(&tokenizer); } }
static int sdp_extract_port(unsigned unused_ cmd, struct liner *liner, void *info_) { struct sdp_proto_info *info = info_; // In case several medias are advertised, we are interrested only in the first one. // FIXME: parse all m= stenzas with their respective attributes (a=). if (info->set_values & SDP_PORT_SET) return 0; // skip the media format ("audio", ...) struct liner space_liner; liner_init(&space_liner, &delim_spaces, (char const *)liner->start, liner_tok_length(liner)); liner_next(&space_liner); char const *end; info->port = liner_strtoull(&space_liner, &end, 10); if (!info->port) // unable to extract an integer value return -1; info->set_values |= SDP_PORT_SET; SLOG(LOG_DEBUG, "port found (%"PRIu16")", info->port); return 0; }
enum proto_parse_status httper_parse(struct httper const *httper, size_t *head_sz, uint8_t const *packet, size_t packet_len, void *user_data) { struct liner liner, tokenizer; bool found = false; for (unsigned c = 0; c < httper->nb_commands; c++) { struct httper_command const *const cmd = httper->commands + c; // Start by looking for the command before tokenizing (tokenizing takes too much time on random traffic) if (0 != strncmp(cmd->name, (char const *)packet, MIN(packet_len, cmd->len))) continue; if (packet_len < cmd->len) return PROTO_TOO_SHORT; liner_init(&liner, &delim_lines, (char const *)packet, packet_len); liner_init(&tokenizer, &delim_blanks, liner.start, liner_tok_length(&liner)); if (liner_tok_length(&tokenizer) != cmd->len) { no_command: SLOG(LOG_DEBUG, "Cannot find command"); return PROTO_PARSE_ERR; } SLOG(LOG_DEBUG, "Found command %s", cmd->name); liner_next(&tokenizer); int ret = cmd->cb(c, &tokenizer, user_data); if (ret) return PROTO_PARSE_ERR; found = true; break; } if (! found) goto no_command; // Parse header fields unsigned nb_hdr_lines = 0; int field_idx = -1; char const *field_end = NULL; while (true) { // Next line bool const has_newline = liner.delim_size > 0; liner_next(&liner); if (liner_eof(&liner)) { // As an accommodation to old HTTP implementations, we allow a single line command // FIXME: check line termination with "*/x.y" ? if (nb_hdr_lines == 0 && has_newline) break; return PROTO_TOO_SHORT; } // If empty line we reached the end of the headers if (liner_tok_length(&liner) == 0) break; // Check if we reached the end of a multiline field. // FIXME: Is isspace appropriate here? if (! isspace(liner.start[0])) { if (field_idx >= 0) { liner_grow(&tokenizer, field_end); // Absorb all remaining of line onto this token liner_expand(&tokenizer); int ret = httper->fields[field_idx].cb(field_idx, &tokenizer, user_data); if (ret) return PROTO_PARSE_ERR; } // Tokenize the header line liner_init(&tokenizer, &delim_colons, liner.start, liner_tok_length(&liner)); field_idx = -1; for (unsigned f = 0; f < httper->nb_fields; f++) { struct httper_field const *field = httper->fields + f; if (liner_tok_length(&tokenizer) < field->len) continue; if (0 != strncasecmp(field->name, tokenizer.start, liner_tok_length(&tokenizer))) continue; SLOG(LOG_DEBUG, "Found field %s", field->name); liner_next(&tokenizer); field_idx = f; break; } } field_end = liner.start + liner.tok_size; // save end of line position in field_end nb_hdr_lines ++; } if (field_idx >= 0) { liner_grow(&tokenizer, field_end); // Absorb all remaining of line onto this token liner_expand(&tokenizer); int ret = httper->fields[field_idx].cb(field_idx, &tokenizer, user_data); if (ret) return PROTO_PARSE_ERR; } if (head_sz) *head_sz = liner_parsed(&liner); return PROTO_OK; }
// FIXME: give wire_len to liner ?? static enum proto_parse_status mgcp_parse(struct parser *parser, struct proto_info *parent, unsigned way, uint8_t const *packet, size_t cap_len, size_t unused_ wire_len, struct timeval const *now, size_t tot_cap_len, uint8_t const *tot_packet) { struct mgcp_parser *mgcp_parser = DOWNCAST(parser, parser, mgcp_parser); size_t payload = 0; // Parse one message (in case of piggybacking, will call ourself recursively so that subscribers are called once for each msg) struct mgcp_proto_info info; struct liner liner; liner_init(&liner, &delim_lines, (char const *)packet, cap_len); // Parse command, which is either verb + seq + endpoint + version or code + seq + blabla struct liner tokenizer; liner_init(&tokenizer, &delim_blanks, liner.start, liner_tok_length(&liner)); if (liner_tok_length(&tokenizer) == 3 && isdigit(tokenizer.start[0])) { // Response info.response = true; info.u.resp.code = liner_strtoull(&tokenizer, NULL, 10); liner_next(&tokenizer); if (liner_eof(&tokenizer)) { SLOG(LOG_DEBUG, "Cannot parse MGCP response: missing TXID"); return PROTO_PARSE_ERR; } info.u.resp.txid = liner_strtoull(&tokenizer, NULL, 10); } else { // Request info.response = false; info.u.query.command = mgcp_code_2_command(tokenizer.start, liner_tok_length(&tokenizer)); if ((int)info.u.query.command == -1) return PROTO_PARSE_ERR; liner_next(&tokenizer); if (liner_eof(&tokenizer)) { SLOG(LOG_DEBUG, "Cannot parse MGCP query: missing TXID"); return PROTO_PARSE_ERR; } info.u.resp.txid = liner_strtoull(&tokenizer, NULL, 10); liner_next(&tokenizer); if (liner_eof(&tokenizer)) { SLOG(LOG_DEBUG, "Cannot parse MGCP query: missing endpoint"); return PROTO_PARSE_ERR; } copy_token(info.u.query.endpoint, sizeof(info.u.query.endpoint), &tokenizer); } liner_next(&liner); // Parse parameters up to end of msg or single dot struct parser *child = NULL; info.dialed[0] = '\0'; info.cnx_id[0] = '\0'; info.call_id[0] = '\0'; info.observed = info.signaled = 0; while (! liner_eof(&liner)) { liner_init(&tokenizer, &delim_blanks, liner.start, liner_tok_length(&liner)); liner_next(&liner); if (liner_tok_length(&tokenizer) == 0) { // we met an empty line, assume following msg is SDP if (! mgcp_parser->sdp_parser) { mgcp_parser->sdp_parser = proto_sdp->ops->parser_new(proto_sdp); } child = mgcp_parser->sdp_parser; break; } else if (liner_tok_length(&tokenizer) == 1 && tokenizer.start[0] == '.') { break; } else if (liner_tok_length(&tokenizer) == 2 && tokenizer.start[1] == ':') { char p = tokenizer.start[0]; liner_next(&tokenizer); if (liner_eof(&tokenizer)) { SLOG(LOG_DEBUG, "Cannot parse MGCP parameter '%c'", p); return PROTO_PARSE_ERR; } liner_expand(&tokenizer); SLOG(LOG_DEBUG, "parameter '%c'", p); switch (p) { case 'O': // ObservedEvents: we are looking for dialed numbers or other interresting events parse_observed_event(&info, &tokenizer); break; case 'S': parse_signal_request(&info, &tokenizer); break; case 'I': parse_connection_id(&info, &tokenizer); break; case 'C': parse_call_id(&info, &tokenizer); break; } } } // End of message const size_t tot_len = liner.start - (char const *)packet; mgcp_proto_info_ctor(&info, parser, parent, tot_len - payload, payload); if (child) { // TODO: We suppose a call is unique in the socket pair, ie. that this parser will handle only one call, so we can keep our SDP with us. // SO, create a mgcp_parser with an embedded sdp parser, created as soon as mgcp_parser is constructed. size_t const rem_len = liner_rem_length(&liner); int err = proto_parse(child, &info.info, way, (uint8_t *)liner.start, rem_len, rem_len, now, tot_cap_len, tot_packet); if (err) proto_parse(NULL, &info.info, way, NULL, 0, 0, now, tot_cap_len, tot_packet); return PROTO_OK; } (void)proto_parse(NULL, &info.info, way, NULL, 0, 0, now, tot_cap_len, tot_packet); // In case of piggybacking, we may have further messages down there if (! liner_eof(&liner)) { size_t const rem_len = liner_rem_length(&liner); return mgcp_parse(parser, parent, way, (uint8_t *)liner.start, rem_len, rem_len, now, tot_cap_len, tot_packet); } return PROTO_OK; }