/* * Copyright (c) 2006 Sebastien Raveau * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * This file contains code for tracking TCP and VoIP (SIP & H.323) sessions. * * IMPORTANT: None of these features are available if libnids >= 1.21 wasn't * found by ./configure; SIP session tracking is available only if Libosip was * found by ./configure and H.323 session tracking is available only if * Libooh323c was found by ./configure. These libraries can be downloaded from: * - http://libnids.sourceforge.net/ * - https://www.gnu.org/software/osip/ * - https://sourceforge.net/projects/ooh323c/ * * There are several entry points (from tcpslice.c) to this file: * - sessions_init() has to be called once before any tracking can be done * - sessions_nids_init() has to be called each time we change PCAP file * - sessions_exit() is used to clean up and report after we're done * - ip_callback() is called for defragmented IPv4 packets, including UDP & TCP * - udp_callback() is called upon reception of correct UDP data * - tcp_callback() is called upon reception of correct TCP data */ #include #include #include #include #include "varattrs.h" #include "sessions.h" #include "diag-control.h" /* * The global variables below have the following purposes: * * `verbose' counts how many times -v was specified on the * command line. It is currently used by sessions_add() and * sessions_del() to show when sessions are opened and closed. * Specify -v once and it will show opening and closing of * primary sessions, but specify it at least twice to see * the opening and closing of subsessions, i.e. sessions that * are created because of some primary sessions (for example * a SIP session will open a RTP session for the call data). * * `bonus_time' equals 0 when we are between the start-time and * end-time specified by the user on the command line, and 1 when * we are past the end-time, in which case we continue to track * the existing sessions but ignore new sessions. * * `track_sessions' is a flag set by sessions_init() and * sessions_exit() but it is mostly used in tcpslice.c to know * whether or not to pass each PCAP frame to libnids in order to * track sessions (it saves further processing if the user did * not want to track sessions, which should be the case most of * the time). * * `sessions_count` is the number of active sessions at a time; * it is especially used by sessions_exit() to print a report about * unclosed sessions in case there are any left when we are done. * * `sessions_file_format' is either NULL if the user does not want * each primary session and all of its subsessions to be extracted * and saved to separate PCAP files, or a string of the form * "/path/filename-%s-%d.pcap", where %s will be replaced by the * primary session type string in lowercase ("tcp", "sip" or "h323") * and %d by the primary session ID. Note that you can specify %.6d * instead of %d so that files can correctly be sorted by session ID * (which will be zero-padded on the left up to six digits); this * prevents having x-sip-10.pcap before x-sip-2.pcap for example. * * `sessions_expiration_delay' can be set by the user to a number * of seconds after which an idle tracked session will be considered * to be closed. This is useful to deal with faulty implementations * of some protocols or packet loss, which otherwise would keep * resources allocated until the call to sessions_exit(). */ int verbose = 0; int bonus_time = 0; int track_sessions = 0; uint32_t sessions_count = 0; char *sessions_file_format = NULL; time_t sessions_expiration_delay = 0; #ifndef HAVE_LIBNIDS void sessions_init(const char *types _U_) { error("libnids required for session tracking support, sorry."); } void sessions_exit(void) { } void sessions_nids_init(pcap_t *p _U_) { } #else /* HAVE_LIBNIDS */ # include # include # ifdef HAVE_LIBOSIPPARSER2 # include # include # endif /* HAVE_LIBOSIPPARSER2 */ # ifdef HAVE_LIBOOH323C # include # include # include # include # endif /* HAVE_LIBOOH323C */ # include "tcpslice.h" # include # define IPHDRLEN (sizeof(struct ip)) # include # define UDPHDRLEN (sizeof(struct udphdr)) # include # define TCPHDRLEN (sizeof(struct tcphdr)) # include # include /* * Session type identifiers, used as bitmasks for * convenience in searches among tracked sessions. */ enum type { TYPE_NONE = 0x00, TYPE_UDP = 0x01, TYPE_TCP = 0x02, TYPE_SIP = 0x04, TYPE_RTP = 0x08, TYPE_RTCP = 0x10, TYPE_H225_RAS = 0x20, TYPE_H225_CS = 0x40, CLASS_SIP = TYPE_SIP | TYPE_RTP, CLASS_H323 = TYPE_H225_RAS | TYPE_H225_CS | TYPE_RTP | TYPE_RTCP }; /* * Structure used by sessions and subsessions to safely share * the same file descriptor, when they have to be saved in the * same PCAP file and can be closed in a different order than * they were opened. */ struct shared_dumper { char *filename; pcap_dumper_t *filedesc; uint32_t references; }; /* * (Almost) generic session description object containing * the following properties: * - addr: IPv4 address & port for source and destination * - type: protocol bitmask (e.g. TYPE_UDP | TYPE_SIP) * - id: unique number assigned to each session * - parent_id: instead of a pointer (which is dangerous because parents * are sometimes destroyed before their children) an ID * that can be used to search for children of a specific session * - parent_id: 0 for primary sessions, parent's ID for subsessions * - timeout: when the session has to be forcefully closed * - callback: method to call in order to process session data * - dumper: file to which this session's packets will be extracted * - lastseen: timestamp of the last packet in this session * - bytes: total amount of data captured for this session * - next: pointer to the next session in the list of all sessions * - prev: pointer to the previous session in the list of all sessions * - u: union containing extra properties that are needed for some session types */ struct session { struct tuple4 addr; enum type type; uint32_t id; uint32_t parent_id; time_t timeout; struct session *(*callback)(struct session *elt, u_char *data, uint32_t len); struct shared_dumper *dumper; struct timeval lastseen; uint64_t bytes; struct session *next; struct session *prev; # if defined(HAVE_LIBOSIPPARSER2) || defined(HAVE_LIBOOH323C) union { # ifdef HAVE_LIBOSIPPARSER2 struct { struct tuple4 rtp_addr; osip_call_id_t *call_id; int picked_up; } sip_params; # endif /* HAVE_LIBOSIPPARSER2 */ # ifdef HAVE_LIBOOH323C struct { struct tuple4 cs_addr; H225RequestSeqNum seqnum; char call_id[16]; } ras_params; # endif /* HAVE_LIBOOH323C */ } u; # endif /* defined(HAVE_LIBOSIPPARSER2) || defined(HAVE_LIBOOH323C) */ }; /* * Pointer to the head of the list containing all tracked sessions */ static struct session *first_session = NULL; /* * Bitmask of the session types the user asked to track */ static enum type sessions_track_types = TYPE_NONE; /* * Count how many PCAP savefiles were opened, to cope with the * "Too many open files" errors and work around them by closing * the oldest 10% of the file descriptors. */ static unsigned int dumper_fd_count = 0; /* * The static functions declared below have the following purposes: * * `sessions_add' checks if a session must be tracked or not depending on * what the user wants (i.e. if `t' matches `sessions_track_types'); if so * it creates a new session object, fills in the properties (based on `addr' * and `parent' if there is one) and inserts in the list of tracked sessions. * * `sessions_del' properly removes a session object from the list. * * `sessions_find' is a small search engine acting on several criteria; it * can search starting from the beginning of the list or from the element * pointed by the `start' parameter, it can search for sessions with specific * types described by the `t' parameter, or by parent ID, or most of the time * by source and destination IP address & port. * * `dumper_open' and `dumper_close' manage multiple references to the same * PCAP file used for saving packets when the user asked for extraction of * sessions into separate files. `dumper_too_many_open_files' tries to cope * with the "Too many open files" error that can happen when dealing with * large PCAP files containing many simultaneous sessions. * * `dump_frame' actually saves the current packet to a PCAP file. * * `parse_type' simply converts a type from string to numerical form. * * `type2string' simply converts a type from numerical form to string. * * `ip_callback', `tcp_callback' and `udp_callback' are called from inside * libnids upon reception of respectively IPv4, TCP and UDP packets. * * `sip_callback', `h225_ras_callback' and `h225_cs_callback' are called * from `tcp_callback' and `udp_callback' via the session::callback field, * in order to process respectively IETF's Session Initialization Protocol, * ITU's H.225 Registration Admission Status and H.225 Call Signaling (both * part of H.323) data. */ static struct session *sessions_add(const uint8_t t, const struct tuple4 *addr, const struct session *parent); static void sessions_del(struct session *elt); static struct session *sessions_find(struct session *start, const uint8_t t, const uint32_t parent_id, const struct tuple4 *addr); static struct shared_dumper *dumper_open(const enum type t, const uint32_t id); static void dumper_too_many_open_files(struct shared_dumper **d); static void dumper_close(struct shared_dumper *d); static void dump_frame(const u_char *data, const int len, struct shared_dumper *output); static enum type parse_type(const char *str); static const char *type2string(const enum type t, const int upper); static void ip_callback(struct ip *ip, int len); static void tcp_callback(struct tcp_stream *tcp, void **user); static void udp_callback(struct tuple4 *addr, u_char *data, int len, struct ip *ip); static struct session *sip_callback(struct session *elt, u_char *data, uint32_t len); static struct session *h225_ras_callback(struct session *elt, u_char *data, uint32_t len); static struct session *h225_cs_callback(struct session *elt, u_char *data, uint32_t len); static enum type parse_type(const char *str) { if (!strcmp("tcp", str)) return TYPE_TCP; # ifdef HAVE_LIBOSIPPARSER2 if (!strcmp("sip", str)) return CLASS_SIP; # endif /* HAVE_LIBOSIPPARSER2 */ # ifdef HAVE_LIBOOH323C if (!strcmp("h323", str)) return CLASS_H323; # endif /* HAVE_LIBOOH323C */ error("unsupported session type `%s'", str); /* NOTREACHED */ return TYPE_NONE; } void sessions_init(const char *types) { char *comma; bonus_time = 0; sessions_track_types = TYPE_NONE; while (NULL != (comma = strchr(types, ','))) { *comma = '\0'; sessions_track_types |= parse_type(types); types = comma + 1; } sessions_track_types |= parse_type(types); while (sessions_count) { struct session *elt = first_session; first_session = first_session->next; --sessions_count; dumper_close(elt->dumper); free(elt); } # ifdef HAVE_LIBOOH323C ooH323EpInitialize(OO_CALLMODE_AUDIOCALL, "/dev/null"); ooH323EpDisableAutoAnswer(); # endif /* HAVE_LIBOOH323C */ track_sessions = 1; } void sessions_exit(void) { struct session *elt; struct session *elt_next; time_t one_minute_later = 0; /* * Last pass to close timeout'd session... It is needed * because the last packet of a session marked for * deletion can be followed only by non-IP packets, so * it won't be deleted by ip_callback and would otherwise * appear as unclosed in the report generated below. * We jump forward one minute in order to timeout TCP * sessions that were opened during the last minute * of capture, which were given 60 seconds to complete * handshake but failed to do so. * * Also close SIP sessions that did not result in a call: * it happens often and the resulting spam in the report * generated below can be really annoying. */ if (NULL != nids_last_pcap_header) one_minute_later = nids_last_pcap_header->ts.tv_sec + 60; for (elt = first_session; NULL != elt; elt = elt_next) { elt_next = elt->next; if (elt->timeout && (one_minute_later >= elt->timeout)) { sessions_del(elt); continue; } # ifdef HAVE_LIBOSIPPARSER2 if ((elt->type & TYPE_SIP) && !elt->u.sip_params.picked_up) sessions_del(elt); # endif /* HAVE_LIBOSIPPARSER2 */ } /* * Print a report about unclosed sessions. */ if (sessions_count) { fprintf(stderr, "%u unclosed %s (id, type, last, source, destination, bytes):\n", sessions_count, sessions_count > 1 ? "sessions" : "session"); while (NULL != first_session) { fprintf(stderr, "#%u\t", first_session->id); fprintf(stderr, "%s\t", type2string(first_session->type, 1)); fprintf(stderr, "%s\t", timestamp_to_string(&first_session->lastseen)); fprintf(stderr, "%15s:%-5d\t", inet_ntoa(*((struct in_addr *)&first_session->addr.saddr)), first_session->addr.source); fprintf(stderr, "%15s:%-5d\t", inet_ntoa(*((struct in_addr *)&first_session->addr.daddr)), first_session->addr.dest); fprintf(stderr, "%12" PRIu64 "\n", first_session->bytes); dumper_close(first_session->dumper); if (NULL != first_session->next) { first_session = first_session->next; free(first_session->prev); first_session->prev = NULL; } else { free(first_session); first_session = NULL; } --sessions_count; } } track_sessions = 0; nids_exit(); } void sessions_nids_init(pcap_t *p) { nids_params.pcap_desc = p; nids_params.tcp_workarounds = 1; if (!nids_init()) { error("%s(): %s", __func__, nids_errbuf); } /* * These conversions between function pointer and void pointer upset GCC, * Clang and XL C. * See also: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83584 */ DIAG_OFF_PEDANTIC nids_register_ip(ip_callback); nids_register_udp(udp_callback); nids_register_tcp(tcp_callback); DIAG_ON_PEDANTIC } static struct session * sessions_add(const uint8_t t, const struct tuple4 *addr, const struct session *parent) { struct session *elt; static uint32_t counter = 0; if (!(t & sessions_track_types)) return NULL; elt = (struct session *) calloc(1, sizeof(struct session)); elt->addr = *addr; elt->type = t; elt->id = ++counter; if (sessions_expiration_delay) elt->timeout = nids_last_pcap_header->ts.tv_sec + sessions_expiration_delay; if (t & TYPE_SIP) elt->callback = sip_callback; else if (t & TYPE_H225_RAS) elt->callback = h225_ras_callback; else if (t & TYPE_H225_CS) elt->callback = h225_cs_callback; else elt->callback = NULL; if (NULL != parent) { elt->parent_id = parent->id; elt->dumper = parent->dumper; elt->dumper->references++; } else elt->dumper = sessions_file_format ? dumper_open(t, elt->id) : NULL; elt->next = first_session; if (NULL != elt->next) elt->next->prev = elt; elt->prev = NULL; first_session = elt; ++sessions_count; if (verbose && (!elt->parent_id || verbose > 1)) printf("Session #%u (%s) opened at %s (active sessions total: %u)\n", elt->parent_id ? elt->parent_id : elt->id, type2string(t, 1), timestamp_to_string(&nids_last_pcap_header->ts), sessions_count); return elt; } static void sessions_del(struct session *elt) { struct tcp_stream *tcp; if (NULL == elt) return; --sessions_count; if ((bonus_time || verbose) && (!elt->parent_id || verbose > 1)) printf("Session #%u (%s) closed at %s (active sessions total: %u)\n", elt->parent_id ? elt->parent_id : elt->id, type2string(elt->type, 1), timestamp_to_string(&nids_last_pcap_header->ts), sessions_count); if (NULL != elt->next) elt->next->prev = elt->prev; if (NULL != elt->prev) elt->prev->next = elt->next; else first_session = elt->next; /* * If this is a TCP connection, tell libnids we do not * want to be notified of new data in this connection. * * We must not do it when the stream is already in a * closing state (NIDS_CLOSE, NIDS_TIMED_OUT, NIDS_RESET * or NIDS_EXITING) because nids_free_tcp_stream() would * then be called twice, resulting in a crash. */ if ((elt->type & TYPE_TCP) && (NULL != (tcp = nids_find_tcp_stream(&elt->addr))) && (NIDS_DATA == tcp->nids_state)) nids_free_tcp_stream(tcp); # ifdef HAVE_LIBOSIPPARSER2 /* * If this is a SIP session, finally free the memory * allocated for the call ID (couldn't be done before) */ if (elt->type & TYPE_SIP) if (NULL != elt->u.sip_params.call_id) osip_call_id_free(elt->u.sip_params.call_id); # endif /* HAVE_LIBOSIPPARSER2 */ dumper_close(elt->dumper); free(elt); } static struct session * sessions_find(struct session *start, const uint8_t t, const uint32_t parent_id, const struct tuple4 *addr) { struct session *elt; for (elt = start; NULL != elt; elt = elt->next) { if (!(elt->type & t)) continue; if (parent_id && (elt->parent_id != parent_id)) continue; if (NULL != addr) { if ((!elt->addr.source || elt->addr.source == addr->source) && (!elt->addr.dest || elt->addr.dest == addr->dest) && (!elt->addr.saddr || elt->addr.saddr == addr->saddr) && (!elt->addr.daddr || elt->addr.daddr == addr->daddr)) return elt; if ((!elt->addr.source || elt->addr.source == addr->dest) && (!elt->addr.dest || elt->addr.dest == addr->source) && (!elt->addr.saddr || elt->addr.saddr == addr->daddr) && (!elt->addr.daddr || elt->addr.daddr == addr->saddr)) return elt; } if (parent_id) return elt; } return NULL; } static struct shared_dumper * dumper_open(const enum type t, const uint32_t id) { struct shared_dumper *d; d = malloc(sizeof (struct shared_dumper)); d->filename = malloc(strlen(sessions_file_format) + strlen(type2string(t, 0)) + 16); sprintf(d->filename, sessions_file_format, type2string(t, 0), id); d->filedesc = pcap_dump_open(nids_params.pcap_desc, d->filename); if (NULL == d->filedesc) dumper_too_many_open_files(&d); ++dumper_fd_count; d->references = 1; return d; } static void dumper_too_many_open_files(struct shared_dumper **d) { struct session *elt; unsigned int oldest_ten_percent; oldest_ten_percent = sessions_count / 10; if (EMFILE == errno && oldest_ten_percent) { for (elt = first_session; NULL != elt; elt = elt->next) { if (NULL != elt->dumper->filedesc) { pcap_dump_close(elt->dumper->filedesc); elt->dumper->filedesc = NULL; --dumper_fd_count; if (!--oldest_ten_percent) break; } } (*d)->filedesc = pcap_dump_open(nids_params.pcap_desc, (*d)->filename); } if (NULL == (*d)->filedesc) { error("%s(): %s: %s", __func__, (*d)->filename, pcap_geterr(nids_params.pcap_desc)); } } static void dumper_close(struct shared_dumper *d) { if (NULL == d) return; --d->references; if (!d->references) { free(d->filename); if (NULL != d->filedesc) { pcap_dump_close(d->filedesc); --dumper_fd_count; } } } static const char * type2string(const enum type t, const int upper) { if (t & TYPE_SIP) return upper ? "SIP" : "sip"; if ((t & TYPE_H225_RAS) || (t & TYPE_H225_CS)) return upper ? "H323" : "h323"; if (t & TYPE_RTP) return upper ? "RTP" : "rtp"; if (t & TYPE_TCP) return upper ? "TCP" : "tcp"; return "???"; } static void dump_frame(const u_char *data, const int len, struct shared_dumper *output) { u_char *frame; struct pcap_pkthdr ph; if (!bonus_time && NULL == output) return; frame = malloc(len + nids_linkoffset); memcpy(frame, nids_last_pcap_data, nids_linkoffset); memcpy(frame + nids_linkoffset, data, len); ph.ts = nids_last_pcap_header->ts; ph.caplen = ph.len = len + nids_linkoffset; if (NULL != output) { if (NULL == output->filedesc) { output->filedesc = pcap_dump_open(nids_params.pcap_desc, output->filename); if (NULL == output->filedesc) dumper_too_many_open_files(&output); ++dumper_fd_count; } pcap_dump((u_char *)output->filedesc, &ph, frame); } if (bonus_time) pcap_dump((u_char *)global_dumper, &ph, frame); free(frame); } /* * This function is called upon reception of all IPv4 packets, which * means some packets will be processed by both ip_callback and * {udp,tcp}_callback. It is necessary for TCP packets though because * tcp_callback is only called once a connection is established (i.e. * after the first SYN, SYN+ACK and ACK packets have passed) and only * when data is available (PSH packets). * Since we don't want failed TCP connections (portscans etc) to * mobilize resources, we give TCP sessions 60 seconds to complete * the TCP handshake or else they are considered to be closed. */ static void ip_callback(struct ip *ip, int len) { struct tuple4 addr; struct tcphdr *tcp; struct session *elt; struct session *elt_next; unsigned int ip_data_offset = IPHDRLEN; if (len < 0) error("%s(): len < 0", __func__); for (elt = first_session; NULL != elt; elt = elt_next) { elt_next = elt->next; if (elt->timeout && (nids_last_pcap_header->ts.tv_sec >= elt->timeout)) sessions_del(elt); } if ((ip->ip_hl > 5) && ((ip->ip_hl * 4) <= len)) ip_data_offset = ip->ip_hl * 4; if ((ip->ip_p != 6) || ((unsigned)len < (ip_data_offset + TCPHDRLEN))) return; /* not TCP or too short */ tcp = (struct tcphdr *)((char *)ip + ip_data_offset); addr.saddr = *((u_int *)&ip->ip_src); addr.daddr = *((u_int *)&ip->ip_dst); addr.source = ntohs(tcp->th_sport); addr.dest = ntohs(tcp->th_dport); if (NULL != (elt = sessions_find(first_session, TYPE_TCP, 0, &addr))) { dump_frame((u_char *)ip, len, elt->dumper); if (sessions_expiration_delay) elt->timeout = nids_last_pcap_header->ts.tv_sec + sessions_expiration_delay; elt->lastseen = nids_last_pcap_header->ts; return; } if (!(tcp->th_flags & TH_SYN) || bonus_time) return; if (addr.source == 5060 || addr.dest == 5060) elt = sessions_add(TYPE_TCP | TYPE_SIP, &addr, NULL); else elt = sessions_add(TYPE_TCP, &addr, NULL); if (NULL == elt) return; dump_frame((u_char *)ip, len, elt->dumper); elt->timeout = nids_last_pcap_header->ts.tv_sec + 60; /* 60 seconds to complete TCP handshake */ } static void udp_callback(struct tuple4 *addr, u_char *udp_data, int udp_data_len, struct ip *ip) { struct session *elt; unsigned int udp_data_offset = IPHDRLEN + UDPHDRLEN; if (NULL == (elt = sessions_find(first_session, TYPE_UDP, 0, addr))) { if (bonus_time) return; if (addr->source == 1719 || addr->dest == 1719) elt = sessions_add(TYPE_UDP | TYPE_H225_RAS, addr, NULL); else if (addr->source == 5060 || addr->dest == 5060) elt = sessions_add(TYPE_UDP | TYPE_SIP, addr, NULL); } if (NULL == elt) return; elt->bytes += udp_data_len; elt->lastseen = nids_last_pcap_header->ts; if (NULL != elt->callback) elt = elt->callback(elt, udp_data, udp_data_len); /* * We can dump the frame only after the data is processed because * a new session object (`elt') might be created by the callback, * with a pointer to a different PCAP file. */ if ((ip->ip_hl > 5) && ((ip->ip_hl * 4U) < (udp_data_offset + udp_data_len))) udp_data_offset = ip->ip_hl * 4 + UDPHDRLEN; dump_frame((u_char *)ip, udp_data_offset + udp_data_len, elt->dumper); } static void tcp_callback(struct tcp_stream *tcp, void **user) { struct session *elt; struct session *rtp; struct session *start; switch (tcp->nids_state) { case NIDS_JUST_EST: if (bonus_time) return; elt = sessions_find(first_session, TYPE_TCP, 0, &tcp->addr); if (NULL == elt) return; if (elt->type & TYPE_H225_CS) { elt->addr.saddr = tcp->addr.saddr; elt->addr.source = tcp->addr.source; } *user = elt; if (!sessions_expiration_delay) elt->timeout = 0; tcp->client.collect++; tcp->server.collect++; return; case NIDS_DATA: elt = (struct session *)*user; elt->bytes += tcp->client.count_new + tcp->server.count_new; if (NULL != elt->callback) { if (tcp->client.count_new) elt->callback(elt, (u_char *)tcp->client.data, tcp->client.count_new); if (tcp->server.count_new) elt->callback(elt, (u_char *)tcp->server.data, tcp->server.count_new); } return; case NIDS_CLOSE: case NIDS_RESET: case NIDS_TIMED_OUT: elt = (struct session *)*user; if (elt->type & TYPE_H225_CS) for (start = first_session; NULL != (rtp = sessions_find(start, TYPE_RTP | TYPE_RTCP, elt->id, NULL)); start = rtp->next) sessions_del(rtp); sessions_del((struct session *)*user); } } # ifndef HAVE_LIBOSIPPARSER2 static struct session *sip_callback(struct session *sip, u_char *data _U_, uint32_t len _U_) { return sip; } # else /* HAVE_LIBOSIPPARSER2 */ static int sip_get_address(const osip_message_t *msg, u_int *host, u_short *port) { osip_content_type_t *ctt; sdp_message_t *sdp; int i; int j; if (NULL == (ctt = osip_message_get_content_type(msg))) return 0; if ((NULL == ctt->type) || (NULL == ctt->subtype)) return 0; if (osip_strcasecmp(ctt->type, "application")) return 0; if (osip_strcasecmp(ctt->subtype, "sdp")) return 0; for (i = 0; !osip_list_eol(&msg->bodies, i); ++i) { sdp = NULL; sdp_message_init(&sdp); char *tmp = ((osip_body_t *)osip_list_get(&msg->bodies, i))->body; if (sdp_message_parse(sdp, tmp)) { sdp_message_free(sdp); continue; } for (j = 0; NULL != sdp_message_m_media_get(sdp, j); ++j) { if (NULL == (tmp = sdp_message_m_port_get(sdp, j))) continue; *port = atoi(tmp); if (NULL == (tmp = sdp_message_c_addr_get(sdp, -1, 0))) if (NULL == (tmp = sdp_message_c_addr_get(sdp, j, 0))) continue; *host = (u_int)inet_addr(tmp); sdp_message_free(sdp); return 1; } sdp_message_free(sdp); } return 0; } static struct session *sip_callback(struct session *sip, u_char *data, uint32_t len) { osip_message_t *msg; struct session *start; struct session *rtp; osip_call_id_t *call_id; osip_message_init(&msg); if (!osip_message_parse(msg, (char *)data, len)) { if (NULL == sip->u.sip_params.call_id) { /* * If the session object was created by udp_callback * we need to fill in the call_id field here because * udp_callback doesn't know anything about SIP */ if (NULL != (call_id = osip_message_get_call_id(msg))) osip_call_id_clone(call_id, &sip->u.sip_params.call_id); } else { /* * Otherwise check if the session object passed to this * function call was really the one corresponding to the * call ID in the SIP packet, in case several SIP calls * are passed upon the same transport layer protocol, * source and destination IPv4 address & port combination. * udp_callback has no way of knowing how to distinguish * SIP session objects based on call ID, so we have to do * it here. We just continue searching in the list of all * tracked sessions for similar SIP objects until we find * one that has the same call ID, or else we create a new * SIP session object that corresponds to the new call. */ start = sip; do { if (NULL == (call_id = osip_message_get_call_id(msg))) continue; if (!osip_call_id_match(sip->u.sip_params.call_id, call_id)) break; } while ((sip = sessions_find(sip->next, TYPE_SIP, 0, &sip->addr))); if (NULL == sip) { if (bonus_time) { osip_message_free(msg); return start; } sip = sessions_add(start->type, &start->addr, NULL); if (NULL != (call_id = osip_message_get_call_id(msg))) osip_call_id_clone(call_id, &sip->u.sip_params.call_id); } } /* * If the current SIP packet is an INVITE message, store the * advertised source port and IPv4 address. It is not very * important, since we can do only with the destination part * (useful in case the capture missed the INVITE packet), but * it helps discriminating from unrelated packets. * * Unfortunately, some SIP implementations such as the one in * Audiocodes Mediant 1000 SIP gateways actually use a source * port different from the one they advertised in the INVITE * message parameters - how outrageous! - so we have to make * our sessions search engine ignore the source port part by * zeroing it :-/ */ if (MSG_IS_INVITE(msg)) { if (!bonus_time) { sip_get_address(msg, &sip->u.sip_params.rtp_addr.saddr, &sip->u.sip_params.rtp_addr.source); #ifndef USING_NON_STUPID_SIP_IMPLEMENTATIONS sip->u.sip_params.rtp_addr.source = 0; #endif } } else if (MSG_TEST_CODE(msg, 200)) { if (MSG_IS_RESPONSE_FOR(msg, "INVITE")) { if (!bonus_time && sip_get_address(msg, &sip->u.sip_params.rtp_addr.daddr, &sip->u.sip_params.rtp_addr.dest)) { sessions_add(TYPE_UDP | TYPE_RTP, &sip->u.sip_params.rtp_addr, sip); sip->u.sip_params.picked_up = 1; } } else if (MSG_IS_RESPONSE_FOR(msg, "BYE") || MSG_IS_RESPONSE_FOR(msg, "CANCEL")) { start = first_session; while (NULL != (rtp = sessions_find(start, TYPE_RTP, sip->id, NULL))) { sessions_del(rtp); start = rtp->next; } /* * Mark for deletion in 2 seconds, in order to give some * time to the extra ACK packets that might be exchanged */ if (sip->type & TYPE_UDP) sip->timeout = nids_last_pcap_header->ts.tv_sec + 2; } } } osip_message_free(msg); return sip; } # endif /* HAVE_LIBOSIPPARSER2 */ # ifndef HAVE_LIBOOH323C static struct session *h225_ras_callback(struct session *ras, u_char *data _U_, uint32_t len _U_) { return ras; } static struct session *h225_cs_callback(struct session *cs, u_char *data _U_, uint32_t len _U_) { return cs; } # else /* HAVE_LIBOOH323C */ static struct session *h225_ras_callback(struct session *ras, u_char *data, uint32_t len) { OOCTXT ctxt; H225RasMessage *pRasMsg; struct session *cs; struct session *rasbkp; initContext(&ctxt); if (ASN_OK != setPERBuffer(&ctxt, data, len, TRUE)) return ras; rasbkp = ras; pRasMsg = (H225RasMessage *)memAlloc(&ctxt, sizeof (H225RasMessage)); if (ASN_OK == asn1PD_H225RasMessage(&ctxt, pRasMsg)) switch (pRasMsg->t) { case T_H225RasMessage_admissionRequest: if (bonus_time) break; if ('\0' != ras->u.ras_params.call_id[0]) ras = sessions_add(TYPE_UDP | TYPE_H225_RAS, &ras->addr, NULL); memcpy(ras->u.ras_params.call_id, pRasMsg->u.admissionRequest->conferenceID.data, 16); ras->u.ras_params.seqnum = pRasMsg->u.admissionRequest->requestSeqNum; ras->timeout = nids_last_pcap_header->ts.tv_sec + 60; /* 60 seconds for the gatekeeper to confirm admission */ break; case T_H225RasMessage_admissionConfirm: if (bonus_time) break; while ((NULL != ras) && (ras->u.ras_params.seqnum != pRasMsg->u.admissionConfirm->requestSeqNum)) ras = sessions_find(ras->next, TYPE_UDP | TYPE_H225_RAS, 0, &ras->addr); if (NULL == ras) { ras = rasbkp; break; } if (pRasMsg->u.admissionConfirm->destCallSignalAddress.t != T_H225TransportAddress_ipAddress) { ras->timeout = nids_last_pcap_header->ts.tv_sec; /* delete after dumping frame */ break; } ras->u.ras_params.cs_addr.dest = pRasMsg->u.admissionConfirm->destCallSignalAddress.u.ipAddress->port; ras->u.ras_params.cs_addr.daddr = *((u_int *)pRasMsg->u.admissionConfirm->destCallSignalAddress.u.ipAddress->ip.data); if (NULL != (cs = sessions_add(TYPE_TCP | TYPE_H225_CS, &ras->u.ras_params.cs_addr, ras))) cs->timeout = nids_last_pcap_header->ts.tv_sec + 60; /* 60 seconds to establish the Call Signaling stream */ break; case T_H225RasMessage_admissionReject: while ((NULL != ras) && (ras->u.ras_params.seqnum != pRasMsg->u.admissionReject->requestSeqNum)) ras = sessions_find(ras->next, TYPE_UDP | TYPE_H225_RAS, 0, &ras->addr); if (NULL == ras) { ras = rasbkp; break; } ras->timeout = nids_last_pcap_header->ts.tv_sec; /* delete after dumping frame */ break; case T_H225RasMessage_disengageRequest: while ((NULL != ras) && memcmp(ras->u.ras_params.call_id, pRasMsg->u.disengageRequest->conferenceID.data, 16)) ras = sessions_find(ras->next, TYPE_UDP | TYPE_H225_RAS, 0, &ras->addr); if (NULL == ras) { ras = rasbkp; break; } ras->u.ras_params.seqnum = pRasMsg->u.disengageRequest->requestSeqNum; break; case T_H225RasMessage_disengageConfirm: while ((NULL != ras) && (ras->u.ras_params.seqnum != pRasMsg->u.disengageConfirm->requestSeqNum)) ras = sessions_find(ras->next, TYPE_UDP | TYPE_H225_RAS, 0, &ras->addr); if (NULL == ras) { ras = rasbkp; break; } ras->timeout = nids_last_pcap_header->ts.tv_sec; /* delete after dumping frame */ } memFreePtr(&ctxt, pRasMsg); freeContext(&ctxt); return ras; } static struct session *h225_cs_callback(struct session *cs, u_char *data, uint32_t len _U_) { /* ooCreateCall() for some reason declares its read-only first argument as * "char *" instead of "const char *", wrap the string in an array in order * not to upset the compiler. */ char callType[] = "incoming"; char callToken[20]; OOH323CallData *call; Q931Message q931; DListNode *node; H245OpenLogicalChannel *olc; H245H2250LogicalChannelParameters *lcp; struct tuple4 addr; ooGenerateCallToken(callToken, 20); call = ooCreateCall(callType, callToken); call->pH225Channel = (OOH323Channel*) memAllocZ (call->pctxt, sizeof (OOH323Channel)); if (OO_OK == ooQ931Decode(call, &q931, ntohs(*((u_short *)(data + 2))) - 4, data + 4)) { if (OO_OK == ooHandleH2250Message(call, &q931)) { if (!bonus_time && (q931.messageType == Q931CallProceedingMsg)) { for (node = call->remoteFastStartOLCs.head; NULL != node; node = node->next) { olc = node->data; if (4 == olc->forwardLogicalChannelParameters.multiplexParameters.t) { lcp = olc->forwardLogicalChannelParameters.multiplexParameters.u.h2250LogicalChannelParameters; if (lcp->m.mediaChannelPresent && (1 == lcp->mediaChannel.t) && (1 == lcp->mediaChannel.u.unicastAddress->t)) { addr.source = 0; addr.saddr = 0; addr.dest = lcp->mediaChannel.u.unicastAddress->u.iPAddress->tsapIdentifier; addr.daddr = *((u_int *)lcp->mediaChannel.u.unicastAddress->u.iPAddress->network.data); sessions_add(TYPE_UDP | TYPE_RTP, &addr, cs); if (lcp->m.mediaControlChannelPresent && (2 == lcp->mediaControlChannel.t) && (1 == lcp->mediaControlChannel.u.multicastAddress->t)) { addr.source = 0; addr.saddr = 0; addr.dest = lcp->mediaControlChannel.u.unicastAddress->u.iPAddress->tsapIdentifier; addr.daddr = *((u_int *)lcp->mediaControlChannel.u.unicastAddress->u.iPAddress->network.data); sessions_add(TYPE_UDP | TYPE_RTCP, &addr, cs); } /* break; */ } } } } } } ooCleanCall(call); return cs; } # endif /* HAVE_LIBOOH323C */ #endif /* HAVE_LIBNIDS */