static enum proto_parse_status read_channel(struct skinny_parser *parser, unsigned from, struct skinny_proto_info *info, struct cursor *curs, struct timeval const *now) { assert(from == FROM_MGR || from == FROM_STATION); if (curs->cap_len < 4+16+4) return PROTO_TOO_SHORT; uint32_t ip_version = cursor_read_u32le(curs); if (ip_version == 0) { // v4 uint32_t ip = cursor_read_u32(curs); ip_addr_ctor_from_ip4(&parser->peer[from], ip); cursor_drop(curs, 12); // this field is 16 bytes in length } else if (ip_version == 1) { // v16 ip_addr_ctor_from_ip6(&parser->peer[from], (struct in6_addr const *)curs->head); cursor_drop(curs, 16); } else { SLOG(LOG_DEBUG, "Invalid IP version (%d)", ip_version); return PROTO_PARSE_ERR; } parser->port[from] = cursor_read_u32le(curs); parser->media_set[from] = true; try_cnxtrack(parser, now); // Copy these into the info block SLOG(LOG_DEBUG, "Got media info"); info->set_values |= SKINNY_MEDIA_CNX; info->media_ip = parser->peer[from]; info->media_port = parser->port[from]; return PROTO_OK; }
// | 1 byte | 1 byte | 2 bytes | 4 bytes | 4 bytes | 4 bytes | // | SMID (0x53) | Flag | SID | Length | Seq num | Window | static enum proto_parse_status tds_parse_smp_header(struct cursor *cursor, struct smp_header *out_header) { # define SMP_PKT_HDR_LEN 0x10 # define SMP_SMID 0x53 if (cursor_peek_u8(cursor, 0) == SMP_SMID) { CHECK_LEN(cursor, SMP_PKT_HDR_LEN, 0); cursor_drop(cursor, 1); out_header->flags = cursor_read_u8(cursor); out_header->sid = cursor_read_u16le(cursor); out_header->length = cursor_read_u32le(cursor); out_header->seq_num = cursor_read_u32le(cursor); out_header->window = cursor_read_u32le(cursor); } return PROTO_OK; }
enum proto_parse_status cursor_read_fixed_int_le(struct cursor *cursor, uint_least64_t *out_res, unsigned len) { uint_least64_t res; if (cursor->cap_len < len) return PROTO_TOO_SHORT; switch (len) { case 0: res = 0; break; case 1: res = cursor_read_u8(cursor); break; case 2: res = cursor_read_u16le(cursor); break; case 3: res = cursor_read_u24le(cursor); break; case 4: res = cursor_read_u32le(cursor); break; case 8: res = cursor_read_u64le(cursor); break; default: SLOG(LOG_DEBUG, "Can't read a %d bytes long number", len); return PROTO_PARSE_ERR; } if (out_res) *out_res = res; return PROTO_OK; }
static enum proto_parse_status read_channel(struct skinny_parser *parser, unsigned from, struct skinny_proto_info *info, struct cursor *curs, struct timeval const *now) { assert(from == FROM_MGR || from == FROM_STATION); if (curs->cap_len < 4+16+4) return PROTO_TOO_SHORT; uint32_t ip_version = 0; // The ip field has a 16 byte lenght on CM7 headers. We // need to drop some bytes before parsing remote port short offset_ip_port = 0; switch (info->header_ver) { case SKINNY_BASIC: break; case SKINNY_CM7_TYPE_A: case SKINNY_CM7_TYPE_B: case SKINNY_CM7_TYPE_C: ip_version = cursor_read_u32le(curs); // We drop (16 - 4) for ipv4 and (16 - 8) for ipv6 offset_ip_port = ip_version ? 8 : 12; break; } if (ip_version == 0) { // v4 uint32_t ip = cursor_read_u32(curs); ip_addr_ctor_from_ip4(&parser->peer[from], ip); } else if (ip_version == 1) { // v6 ip_addr_ctor_from_ip6(&parser->peer[from], (struct in6_addr const *)curs->head); } else { SLOG(LOG_DEBUG, "Invalid IP version (%d)", ip_version); return PROTO_PARSE_ERR; } cursor_drop(curs, offset_ip_port); parser->port[from] = cursor_read_u32le(curs); parser->media_set[from] = true; try_cnxtrack(parser, now); // Copy these into the info block SLOG(LOG_DEBUG, "Got media info"); info->set_values |= SKINNY_MEDIA_CNX; info->media_ip = parser->peer[from]; info->media_port = parser->port[from]; return PROTO_OK; }
uint_least64_t cursor_read_u64le(struct cursor *cursor) { uint_least64_t a = cursor_read_u32le(cursor); uint_least64_t b = cursor_read_u32le(cursor); return a | (b << 32); }
uint_least64_t cursor_read_u64n(struct cursor *cursor) { uint_least64_t a = cursor_read_u32le(cursor); uint_least64_t b = cursor_read_u32le(cursor); return (a << 32) | b; }
static enum proto_parse_status skinny_sbuf_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 skinny_parser *skinny_parser = DOWNCAST(parser, parser, skinny_parser); # define SKINNY_HDR_SIZE 8 # define SKINNY_MIN_MSG_SIZE 12 if (wire_len < SKINNY_MIN_MSG_SIZE) { streambuf_set_restart(&skinny_parser->sbuf, way, packet, true); // wait for more return PROTO_OK; } if (cap_len < SKINNY_MIN_MSG_SIZE) return PROTO_TOO_SHORT; struct cursor curs; cursor_ctor(&curs, packet, cap_len); uint32_t msg_len = cursor_read_u32le(&curs); enum skinny_header_version header_ver = cursor_read_u32le(&curs); enum skinny_msgid msg_id = cursor_read_u32le(&curs); SLOG(LOG_DEBUG, "New SKINNY msg of size %"PRIu32", msgid=0x%"PRIx32, msg_len, msg_id); if (header_ver != SKINNY_BASIC && header_ver != SKINNY_CM7_TYPE_A && header_ver != SKINNY_CM7_TYPE_B && header_ver != SKINNY_CM7_TYPE_C) return PROTO_PARSE_ERR; if (msg_len < 4 || msg_len > SKINNY_MAX_HDR_SIZE /* guestimated */) return PROTO_PARSE_ERR; if (wire_len < msg_len + SKINNY_HDR_SIZE) return PROTO_TOO_SHORT; // wait for the message to be complete // Ok we have what looks like a skinny message in there struct skinny_proto_info info; skinny_proto_info_ctor(&info, parser, parent, SKINNY_HDR_SIZE, msg_len, msg_id, header_ver); switch (msg_id) { case SKINNY_STATION_KEY_PAD_BUTTON: if (curs.cap_len < 12) return PROTO_TOO_SHORT; info.set_values |= SKINNY_NEW_KEY_PAD | SKINNY_LINE_INSTANCE | SKINNY_CALL_ID; info.new_key_pad = cursor_read_u32le(&curs); info.line_instance = cursor_read_u32le(&curs); info.call_id = cursor_read_u32le(&curs); break; case SKINNY_MGR_CALL_STATE: if (curs.cap_len < 12) return PROTO_TOO_SHORT; info.set_values |= SKINNY_CALL_STATE | SKINNY_LINE_INSTANCE | SKINNY_CALL_ID; info.call_state = cursor_read_u32le(&curs); info.line_instance = cursor_read_u32le(&curs); info.call_id = cursor_read_u32le(&curs); SLOG(LOG_DEBUG, "New call state: %s", skinny_call_state_2_str(info.call_state)); break; case SKINNY_MGR_CLOSE_RECV_CHANNEL: case SKINNY_MGR_STOP_MEDIA_TRANSMIT: if (curs.cap_len < 8) return PROTO_TOO_SHORT; info.set_values |= SKINNY_CONFERENCE_ID | SKINNY_PASS_THRU_ID; info.conf_id = cursor_read_u32le(&curs); info.pass_thru_id = cursor_read_u32le(&curs); break; case SKINNY_MGR_START_MEDIA_TRANSMIT: if (curs.cap_len < 8) return PROTO_TOO_SHORT; info.set_values |= SKINNY_CONFERENCE_ID | SKINNY_PASS_THRU_ID; info.conf_id = cursor_read_u32le(&curs); info.pass_thru_id = cursor_read_u32le(&curs); enum proto_parse_status status = read_channel(skinny_parser, FROM_MGR, &info, &curs, now); if (PROTO_OK != status) return status; break; case SKINNY_STATION_OPEN_RECV_CHANNEL_ACK: if (curs.cap_len < 4) return PROTO_TOO_SHORT; uint32_t open_status = cursor_read_u32le(&curs); if (open_status == 0 /* Ok */) { enum proto_parse_status status = read_channel(skinny_parser, FROM_STATION, &info, &curs, now); if (PROTO_OK != status) return status; info.set_values |= SKINNY_PASS_THRU_ID; if (curs.cap_len < 4) return PROTO_TOO_SHORT; info.pass_thru_id = cursor_read_u32le(&curs); } break; case SKINNY_MGR_OPEN_RECV_CHANNEL: if (curs.cap_len < 8) return PROTO_TOO_SHORT; info.set_values |= SKINNY_CONFERENCE_ID | SKINNY_PASS_THRU_ID; info.conf_id = cursor_read_u32le(&curs); info.pass_thru_id = cursor_read_u32le(&curs); break; case SKINNY_MGR_DIALED_NUMBER: # define DIALED_NUMBER_SIZE 24 if (curs.cap_len < DIALED_NUMBER_SIZE+8) return PROTO_TOO_SHORT; info.set_values |= SKINNY_CALLED_PARTY | SKINNY_LINE_INSTANCE | SKINNY_CALL_ID; // 24 chars, terminated with 0 (if fits) snprintf(info.called_party, sizeof(info.called_party), "%.*s", (int)DIALED_NUMBER_SIZE, curs.head); cursor_drop(&curs, DIALED_NUMBER_SIZE); info.line_instance = cursor_read_u32le(&curs); info.call_id = cursor_read_u32le(&curs); break; case SKINNY_MGR_CALL_INFO: if (curs.cap_len < 8 + 4 + 5*4) return PROTO_TOO_SHORT; info.set_values |= SKINNY_CALLING_PARTY | SKINNY_CALLED_PARTY | SKINNY_LINE_INSTANCE | SKINNY_CALL_ID; info.line_instance = cursor_read_u32le(&curs); info.call_id = cursor_read_u32le(&curs); cursor_drop(&curs, 4 + 5*4); // drop Call Type and 5 unknown fields // From now on, informations are nul terminated strings if (PROTO_OK != (status = read_string(info.calling_party, sizeof(info.calling_party), &curs))) return status; // Calling party if (header_ver == SKINNY_CM7_TYPE_A || header_ver == SKINNY_CM7_TYPE_B || header_ver == SKINNY_CM7_TYPE_C) { cursor_read_string(&curs, NULL, 24); // Drop calling party voice mailbox } if (PROTO_OK != (status = read_string(info.called_party, sizeof(info.called_party), &curs))) return status; // Called party // discard the rest of informations break; default: break; } (void)proto_parse(NULL, &info.info, way, NULL, 0, 0, now, tot_cap_len, tot_packet); streambuf_set_restart(&skinny_parser->sbuf, way, packet + SKINNY_HDR_SIZE + msg_len, false); // go to next msg return PROTO_OK; }