/* $Id: mediaproxy.c,v 1.23.2.4 2005/07/20 05:45:10 danp Exp $ * * Copyright (C) 2004 Dan Pascu * * This file is part of ser, a free SIP server. * * ser is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version * * For a license to use the ser software under conditions * other than those described here, or to purchase support for this * software, please contact iptel.org by e-mail at the following addresses: * info@iptel.org * * ser is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ // TODO // // - make the asymmetric files use CFG_DIR // - find a way to install the config files with make install #include "../../sr_module.h" #include "../../dprint.h" #include "../../str.h" #include "../../error.h" #include "../../data_lump.h" #include "../../forward.h" #include "../../mem/mem.h" #include "../../resolve.h" #include "../../timer.h" #include "../../ut.h" #include "../../parser/parse_from.h" #include "../../parser/parse_to.h" #include "../../parser/parse_uri.h" #include "../../msg_translator.h" #include "../registrar/sip_msg.h" #include "../usrloc/usrloc.h" #include #include #include #include #include #include #include #include #include #include #include #include #include MODULE_VERSION // Although `AF_LOCAL' is mandated by POSIX.1g, `AF_UNIX' is portable to // more systems. `AF_UNIX' was the traditional name stemming from BSD, so // even most POSIX systems support it. It is also the name of choice in // the Unix98 specification. So if there's no AF_LOCAL fallback to AF_UNIX #ifndef AF_LOCAL # define AF_LOCAL AF_UNIX #endif #define min(x, y) (((x) < (y)) ? (x) : (y)) #define isAnyAddress(adr) ((adr).len==7 && memcmp("0.0.0.0", (adr).s, 7)==0) typedef int Bool; #define True 1 #define False 0 typedef int (*CheckLocalPartyProc)(struct sip_msg* msg, char* s1, char* s2); typedef Bool (*NatTestProc)(struct sip_msg* msg); typedef enum { NTNone=0, NTPrivateContact=1, NTSourceAddress=2, NTPrivateVia=4 } NatTestType; typedef enum { STUnknown=0, STAudio, STVideo, STAudioVideo } StreamType; typedef struct { const char *name; uint32_t address; uint32_t mask; } NetInfo; typedef struct { str ip; str port; str type; // stream type (`audio', `video', ...) int localIP; // true if the IP is locally defined inside this media stream } StreamInfo; typedef struct { NatTestType test; NatTestProc proc; } NatTest; typedef struct { char *file; // the file which lists the asymmetric clients long timestamp; // for checking if it was modified regex_t **clients; // the asymmetric clients regular expressions int size; // size of array above int count; // how many clients are in array above } AsymmetricClients; /* Function prototypes */ static int ClientNatTest(struct sip_msg *msg, char *str1, char *str2); static int FixContact(struct sip_msg *msg, char *str1, char *str2); static int UseMediaProxy(struct sip_msg *msg, char *str1, char *str2); static int EndMediaSession(struct sip_msg *msg, char *str1, char *str2); static int mod_init(void); static int fixstring2int(void **param, int param_count); static Bool testPrivateContact(struct sip_msg* msg); static Bool testSourceAddress(struct sip_msg* msg); static Bool testPrivateVia(struct sip_msg* msg); /* Local global variables */ static char *mediaproxySocket = "/var/run/proxydispatcher.sock"; static int natpingInterval = 60; // 60 seconds static usrloc_api_t userLocation; static AsymmetricClients sipAsymmetrics = { "/etc/ser/sip-asymmetric-clients", //CFG_DIR"/sip-asymmetric-clients", 0, NULL, 0, 0 }; static AsymmetricClients rtpAsymmetrics = { "/etc/ser/rtp-asymmetric-clients", 0, NULL, 0, 0 }; static CheckLocalPartyProc isFromLocal; //static CheckLocalPartyProc isToLocal; static CheckLocalPartyProc isDestinationLocal; NetInfo rfc1918nets[] = { {"10.0.0.0", 0x0a000000UL, 0xff000000UL}, {"172.16.0.0", 0xac100000UL, 0xfff00000UL}, {"192.168.0.0", 0xc0a80000UL, 0xffff0000UL}, {NULL, 0UL, 0UL} }; NatTest natTests[] = { {NTPrivateContact, testPrivateContact}, {NTSourceAddress, testSourceAddress}, {NTPrivateVia, testPrivateVia}, {NTNone, NULL} }; static cmd_export_t commands[] = { {"fix_contact", FixContact, 0, 0, REQUEST_ROUTE | ONREPLY_ROUTE }, {"use_media_proxy", UseMediaProxy, 0, 0, REQUEST_ROUTE | ONREPLY_ROUTE }, {"end_media_session", EndMediaSession, 0, 0, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE }, {"client_nat_test", ClientNatTest, 1, fixstring2int, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE }, {0, 0, 0, 0, 0} }; static param_export_t parameters[] = { {"mediaproxy_socket", STR_PARAM, &mediaproxySocket}, {"sip_asymmetrics", STR_PARAM, &(sipAsymmetrics.file)}, {"rtp_asymmetrics", STR_PARAM, &(rtpAsymmetrics.file)}, {"natping_interval", INT_PARAM, &natpingInterval}, {0, 0, 0} }; struct module_exports exports = { "mediaproxy", // module name commands, // module exported functions parameters, // module exported parameters mod_init, // module init (before any kid is created. kids will inherit) NULL, // reply processing NULL, // destroy function NULL, // on_break NULL // child_init }; /* Helper functions */ // Functions dealing with strings /* * strfind() finds the start of the first occurrence of the substring needle * of length nlen in the memory area haystack of length len. */ static void* strfind(const void *haystack, size_t len, const void *needle, size_t nlen) { char *sp; /* Sanity check */ if(!(haystack && needle && nlen && len>=nlen)) return NULL; for (sp = (char*)haystack; sp <= (char*)haystack + len - nlen; sp++) { if (*sp == *(char*)needle && memcmp(sp, needle, nlen)==0) { return sp; } } return NULL; } /* * strcasefind() finds the start of the first occurrence of the substring * needle of length nlen in the memory area haystack of length len by doing * a case insensitive search */ static void* strcasefind(const char *haystack, size_t len, const char *needle, size_t nlen) { char *sp; /* Sanity check */ if(!(haystack && needle && nlen && len>=nlen)) return NULL; for (sp = (char*)haystack; sp <= (char*)haystack + len - nlen; sp++) { if (tolower(*sp) == tolower(*(char*)needle) && strncasecmp(sp, needle, nlen)==0) { return sp; } } return NULL; } /* returns string with whitespace trimmed from left end */ static inline void ltrim(str *string) { while (string->len>0 && isspace((int)*(string->s))) { string->len--; string->s++; } } /* returns string with whitespace trimmed from right end */ static inline void rtrim(str *string) { char *ptr; ptr = string->s + string->len - 1; while (string->len>0 && (*ptr==0 || isspace((int)*ptr))) { string->len--; ptr--; } } /* returns string with whitespace trimmed from both ends */ static inline void trim(str *string) { ltrim(string); rtrim(string); } /* returns a pointer to first CR or LF char found or the end of string */ static char* findendline(char *string, int len) { char *ptr = string; while(ptr - string < len && *ptr != '\n' && *ptr != '\r') ptr++; return ptr; } static int strtoint(str *data) { long int result; char c; // hack to avoid copying the string c = data->s[data->len]; data->s[data->len] = 0; result = strtol(data->s, NULL, 10); data->s[data->len] = c; return (int)result; } /* Free the returned string with pkg_free() after you've done with it */ static char* encodeQuopri(str buf) { char *result; int i, j; char c; result = pkg_malloc(buf.len*3+1); if (!result) { LOG(L_ERR, "error: mediaproxy/encodeQuopri(): out of memory\n"); return NULL; } for (i=0, j=0; i0x20 && c<0x7f && c!='=') || c=='\n' || c=='\r') { result[j++] = c; } else { result[j++] = '='; sprintf(&result[j], "%02X", (c & 0xff)); j += 2; } } result[j] = 0; return result; } /* Find a line in str `block' that starts with `start'. */ static char* findLineStartingWith(str *block, char *start, int ignoreCase) { char *ptr, *bend; str zone; int tlen; bend = block->s + block->len; tlen = strlen(start); ptr = NULL; for (zone = *block; zone.len > 0; zone.len = bend - zone.s) { if (ignoreCase) ptr = strcasefind(zone.s, zone.len, start, tlen); else ptr = strfind(zone.s, zone.len, start, tlen); if (!ptr || ptr==zone.s || ptr[-1]=='\n' || ptr[-1]=='\r') break; zone.s = ptr + tlen; } return ptr; } /* get up to `limit' whitespace separated tokens from `char *string' */ static int getTokens(char *string, str *tokens, int limit) { int i, len, size; char *ptr; if (!string) { return 0; } len = strlen(string); for (ptr=string, i=0; i0; i++) { size = strspn(ptr, " \t\n\r"); ptr += size; len -= size; if (len <= 0) break; size = strcspn(ptr, " \t\n\r"); if (size==0) break; tokens[i].s = ptr; tokens[i].len = size; ptr += size; len -= size; } return i; } /* get up to `limit' whitespace separated tokens from `str *string' */ static int getStrTokens(str *string, str *tokens, int limit) { int count; char c; if (!string || !string->s) { return 0; } c = string->s[string->len]; string->s[string->len] = 0; count = getTokens(string->s, tokens, limit); string->s[string->len] = c; return count; } /* Functions to extract the info we need from the SIP/SDP message */ /* Extract Call-ID value. */ static Bool getCallId(struct sip_msg* msg, str *cid) { if (msg->callid == NULL) { if (parse_headers(msg, HDR_CALLID, 0) == -1) { return False; } if (msg->callid == NULL) { return False; } } *cid = msg->callid->body; trim(cid); return True; } /* Get caller domain */ static str getFromDomain(struct sip_msg* msg) { static char buf[16] = "unknown"; // buf is here for a reason. don't static str notfound = {buf, 7}; // use the constant string directly! static struct sip_uri puri; str uri; if (parse_from_header(msg) < 0) { LOG(L_ERR, "error: mediaproxy/getFromDomain(): error parsing `From' header\n"); return notfound; } uri = get_from(msg)->uri; if (parse_uri(uri.s, uri.len, &puri) < 0) { LOG(L_ERR, "error: mediaproxy/getFromDomain(): error parsing `From' URI\n"); return notfound; } else if (puri.host.len == 0) { return notfound; } return puri.host; } /* Get called domain */ static str getToDomain(struct sip_msg* msg) { static char buf[16] = "unknown"; // buf is here for a reason. don't static str notfound = {buf, 7}; // use the constant string directly! static struct sip_uri puri; str uri; uri = get_to(msg)->uri; if (parse_uri(uri.s, uri.len, &puri) < 0) { LOG(L_ERR, "error: mediaproxy/getToDomain(): error parsing `To' URI\n"); return notfound; } else if (puri.host.len == 0) { return notfound; } return puri.host; } /* Get destination domain */ // This function only works when called for a request although it's more // reliable than the getToDomain function. static str getDestinationDomain(struct sip_msg* msg) { static char buf[16] = "unknown"; // buf is here for a reason. don't static str notfound = {buf, 7}; // use the constant string directly! if (parse_sip_msg_uri(msg) < 0) { LOG(L_ERR, "error: mediaproxy/getDestinationDomain(): error parsing destination URI\n"); return notfound; } else if (msg->parsed_uri.host.len==0) { return notfound; } return msg->parsed_uri.host; } /* Get From tag */ static str getFromAddress(struct sip_msg *msg) { static char buf[16] = "unknown"; // buf is here for a reason. don't static str notfound = {buf, 7}; // use the constant string directly! str uri; char *ptr; if (parse_from_header(msg) == -1) { LOG(L_ERR, "error: mediaproxy/getFromAddress(): error parsing From: field\n"); return notfound; } uri = get_from(msg)->uri; if (uri.len == 0) return notfound; if (strncmp(uri.s, "sip:", 4)==0) { uri.s += 4; uri.len -= 4; } if ((ptr = strfind(uri.s, uri.len, ";", 1))!=NULL) { uri.len = ptr - uri.s; } return uri; } /* Get To tag */ static str getToAddress(struct sip_msg *msg) { static char buf[16] = "unknown"; // buf is here for a reason. don't static str notfound = {buf, 7}; // use the constant string directly! str uri; char *ptr; if (!msg->to) { LOG(L_ERR, "error: mediaproxy/getToAddress(): missing To: field\n"); return notfound; } uri = get_to(msg)->uri; if (uri.len == 0) return notfound; if (strncmp(uri.s, "sip:", 4)==0) { uri.s += 4; uri.len -= 4; } if ((ptr = strfind(uri.s, uri.len, ";", 1))!=NULL) { uri.len = ptr - uri.s; } return uri; } /* Get From tag */ static str getFromTag(struct sip_msg *msg) { static char buf[4] = ""; // buf is here for a reason. don't static str notfound = {buf, 0}; // use the constant string directly! str tag; if (parse_from_header(msg) == -1) { LOG(L_ERR, "error: mediaproxy/getFromTag(): error parsing From: field\n"); return notfound; } tag = get_from(msg)->tag_value; if (tag.len == 0) return notfound; return tag; } /* Get To tag */ static str getToTag(struct sip_msg *msg) { static char buf[4] = ""; // buf is here for a reason. don't static str notfound = {buf, 0}; // use the constant string directly! str tag; if (!msg->to) { LOG(L_ERR, "error: mediaproxy/getToTag(): missing To: field\n"); return notfound; } tag = get_to(msg)->tag_value; if (tag.len == 0) return notfound; return tag; } /* Extract User-Agent */ static str getUserAgent(struct sip_msg* msg) { static char buf[16] = "unknown-agent"; // buf is here for a reason. don't static str notfound = {buf, 13}; // use the constant string directly! str block, server; char *ptr; if ((parse_headers(msg, HDR_USERAGENT, 0)!=-1) && msg->user_agent && msg->user_agent->body.len>0) { return msg->user_agent->body; } // If we can't find user-agent, look after the Server: field // This is a temporary hack. Normally it should be extracted by ser // (either as the Server field, or if User-Agent is missing in place // of the User-Agent field) block.s = msg->buf; block.len = msg->len; ptr = findLineStartingWith(&block, "Server:", True); if (!ptr) return notfound; server.s = ptr + 7; server.len = findendline(server.s, block.s+block.len-server.s) - server.s; trim(&server); if (server.len == 0) return notfound; return server; } // Get URI from the Contact: field. static Bool getContactURI(struct sip_msg* msg, struct sip_uri *uri, contact_t** _c) { if ((parse_headers(msg, HDR_CONTACT, 0) == -1) || !msg->contact) return False; if (!msg->contact->parsed && parse_contact(msg->contact) < 0) { LOG(L_ERR, "error: mediaproxy/getContactURI(): cannot parse Contact header\n"); return False; } *_c = ((contact_body_t*)msg->contact->parsed)->contacts; if (*_c == NULL) { return False; } if (parse_uri((*_c)->uri.s, (*_c)->uri.len, uri) < 0 || uri->host.len <= 0) { LOG(L_ERR, "error: mediaproxy/getContactURI(): cannot parse Contact URI\n"); return False; } return True; } // Functions to manipulate the SDP message body static Bool checkContentType(struct sip_msg *msg) { str type; if (!msg->content_type) { LOG(L_WARN, "warning: mediaproxy/checkContentType(): Content-Type " "header missing! Let's assume the content is text/plain ;-)\n"); return True; } type = msg->content_type->body; trim(&type); if (strncasecmp(type.s, "application/sdp", 15) != 0) { LOG(L_ERR, "error: mediaproxy/checkContentType(): invalid Content-Type " "for SDP message\n"); return False; } if (!(isspace((int)type.s[15]) || type.s[15] == ';' || type.s[15] == 0)) { LOG(L_ERR,"error: mediaproxy/checkContentType(): invalid character " "after Content-Type!\n"); return False; } return True; } // Get the SDP message from SIP message and check it's Content-Type // return -1 on error, 0 if empty message, 1 if message present and not empty static int getSDPMessage(struct sip_msg *msg, str *sdp) { sdp->s = get_body(msg); if (sdp->s==NULL) { LOG(L_ERR, "error: mediaproxy/getSDPMessage(): cannot get the SDP body from SIP message\n"); return -1; } sdp->len = msg->buf + msg->len - sdp->s; if (sdp->len==0) { // 0 length body is ok for ACK messages if (!(msg->first_line.type == SIP_REQUEST && msg->first_line.u.request.method_value == METHOD_ACK)) { LOG(L_ERR, "error: mediaproxy/getSDPMessage(): SDP message has zero length\n"); } return 0; } if (!checkContentType(msg)) { LOG(L_ERR, "error: mediaproxy/getSDPMessage(): content type is not `application/sdp'\n"); return -1; } return 1; } // will return the ip address present in a `c=' line in the given block // returns: -1 on error, 0 if not found, 1 if found static int getMediaIPFromBlock(str *block, str *mediaip) { str tokens[3], zone; char *ptr; int count; ptr = findLineStartingWith(block, "c=", False); if (!ptr) { mediaip->s = NULL; mediaip->len = 0; return 0; } zone.s = ptr + 2; zone.len = findendline(zone.s, block->s + block->len - zone.s) - zone.s; count = getStrTokens(&zone, tokens, 3); if (count != 3) { LOG(L_ERR, "error: mediaproxy/getMediaIPFromBlock(): invalid `c=' " "line in SDP body\n"); return -1; } // can also check if tokens[1] == 'IP4' *mediaip = tokens[2]; return 1; } // Get the session-level media IP defined by the SDP message static Bool getSessionLevelMediaIP(str *sdp, str *mediaip) { str block; char *ptr; // session IP can be found from the beginning up to the first media block ptr = findLineStartingWith(sdp, "m=", False); if (ptr) { block.s = sdp->s; block.len = ptr - block.s; } else { block = *sdp; } if (getMediaIPFromBlock(&block, mediaip) == -1) { LOG(L_ERR, "error: mediaproxy/getSessionLevelMediaIP(): parse error " "while getting session-level media IP from SDP body\n"); return False; } // it's not an error to be missing. it can be locally defined // by each media stream. thus we return true even if not found return True; } // will get all media streams static int getMediaStreams(str *sdp, str *sessionIP, StreamInfo *streams, int limit) { str tokens[2], block, zone; char *ptr, *sdpEnd; int i, count, streamCount, result; sdpEnd = sdp->s + sdp->len; for (i=0, block=*sdp; ilen==oldElem->len && memcmp(newElem->s, oldElem->s, newElem->len)==0) { return True; } buf = pkg_malloc(newElem->len); if (!buf) { LOG(L_ERR, "error: mediaproxy/replaceElement(): out of memory\n"); return False; } anchor = del_lump(msg, oldElem->s - msg->buf, oldElem->len, 0); if (!anchor) { LOG(L_ERR, "error: mediaproxy/replaceElement(): failed to delete old element\n"); pkg_free(buf); return False; } memcpy(buf, newElem->s, newElem->len); if (insert_new_lump_after(anchor, buf, newElem->len, 0)==0) { LOG(L_ERR, "error: mediaproxy/replaceElement(): failed to insert new element\n"); pkg_free(buf); return False; } return True; } // Functions dealing with the external mediaproxy helper static inline size_t uwrite(int fd, const void *buf, size_t count) { int len; do len = write(fd, buf, count); while (len == -1 && errno == EINTR); return len; } static inline size_t uread(int fd, void *buf, size_t count) { int len; do len = read(fd, buf, count); while (len == -1 && errno == EINTR); return len; } static inline size_t readall(int fd, void *buf, size_t count) { int len, total; for (len=0, total=0; count-total>0; total+=len) { len = uread(fd, (char*)buf+total, count-total); if (len == -1) { return -1; } if (len == 0) { break; } } return total; } static char* sendMediaproxyCommand(char *command) { struct sockaddr_un addr; int smpSocket, len; static char buf[1024]; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; strncpy(addr.sun_path, mediaproxySocket, sizeof(addr.sun_path) - 1); #ifdef HAVE_SOCKADDR_SA_LEN addr.sun_len = strlen(addr.sun_path); #endif smpSocket = socket(AF_LOCAL, SOCK_STREAM, 0); if (smpSocket < 0) { LOG(L_ERR, "error: mediaproxy/sendMediaproxyCommand(): can't create socket\n"); return NULL; } if (connect(smpSocket, (struct sockaddr*)&addr, sizeof(addr)) < 0) { close(smpSocket); LOG(L_ERR, "error: mediaproxy/sendMediaproxyCommand(): can't connect to MediaProxy\n"); return NULL; } len = uwrite(smpSocket, command, strlen(command)); if (len <= 0) { close(smpSocket); LOG(L_ERR, "error: mediaproxy/sendMediaproxyCommand(): can't send command to MediaProxy\n"); return NULL; } len = readall(smpSocket, buf, sizeof(buf)-1); close(smpSocket); if (len < 0) { LOG(L_ERR, "error: mediaproxy/sendMediaproxyCommand(): can't read reply from MediaProxy\n"); return NULL; } buf[len] = 0; return buf; } // Miscellaneous helper functions /* Test if IP in `address' belongs to a RFC1918 network */ static inline int rfc1918address(str *address) { struct in_addr inaddr; uint32_t netaddr; int i, result; char c; c = address->s[address->len]; address->s[address->len] = 0; result = inet_aton(address->s, &inaddr); address->s[address->len] = c; if (result==0) return -1; /* invalid address to test */ netaddr = ntohl(inaddr.s_addr); for (i=0; rfc1918nets[i].name!=NULL; i++) { if ((netaddr & rfc1918nets[i].mask)==rfc1918nets[i].address) { return 1; } } return 0; } #define isPrivateAddress(x) (rfc1918address(x)==1 ? 1 : 0) // test for a public address is more complex (also need to test for // address not in 0.0.0.0/8, 127.0.0.0/8, 224.0.0.0/4). // #define isPublicAddress(x) (rfc1918address(x)==0 ? 1 : 0) // Check if the requested asymmetrics file has changed and reload it if needed static void checkAsymmetricFile(AsymmetricClients *aptr) { char buf[512], errbuf[256], *which; regex_t *re, **regs; int i, size, code; Bool firstTime = False; struct stat statbuf; FILE *file; str line; if (stat(aptr->file, &statbuf) < 0) return; // ignore missing file if (statbuf.st_mtime <= aptr->timestamp) return; // not changed // now we have work to do which = (aptr == &sipAsymmetrics ? "SIP" : "RTP"); if (!aptr->clients) { // if we are here the first time allocate memory to hold the regexps // initial size of array // (hopefully not reached so we won't need to realloc) size = 32; aptr->clients = (regex_t**)pkg_malloc(size*sizeof(regex_t**)); if (!aptr->clients) { LOG(L_WARN, "warning: mediaproxy/checkAsymmetricFile() cannot " "allocate memory for the %s asymmetric client list. " "%s asymmetric clients will not be handled properly.\n", which, which); return; // ignore as it is not fatal } aptr->size = size; aptr->count = 0; firstTime = True; } else { // else clean old stuff for (i=0; icount; i++) { regfree(aptr->clients[i]); pkg_free(aptr->clients[i]); aptr->clients[i] = NULL; } aptr->count = 0; } // read new file = fopen(aptr->file, "r"); i = 0; // this will count on which line are we in the file while (!feof(file)) { if (!fgets(buf, 512, file)) break; i++; line.s = buf; line.len = strlen(buf); trim(&line); if (line.len == 0 || line.s[0] == '#') continue; // comment or empty line. ignore line.s[line.len] = 0; re = (regex_t*)pkg_malloc(sizeof(regex_t)); if (!re) { LOG(L_WARN, "warning: mediaproxy/checkAsymmetricFile(): " "cannot allocate memory for all the %s asymmetric " "clients listed in file. Some of them will not be " "handled properly.\n", which); break; } code = regcomp(re, line.s, REG_EXTENDED|REG_ICASE|REG_NOSUB); if (code == 0) { if (aptr->count+1 > aptr->size) { size = aptr->size * 2; regs = aptr->clients; regs = (regex_t**)pkg_realloc(regs, size*sizeof(regex_t**)); if (!regs) { LOG(L_WARN, "warning: mediaproxy/checkAsymmetricFile(): " "cannot allocate memory for all the %s asymmetric " "clients listed in file. Some of them will not be " "handled properly.\n", which); break; } aptr->clients = regs; aptr->size = size; } aptr->clients[aptr->count] = re; aptr->count++; } else { regerror(code, re, errbuf, 256); LOG(L_WARN, "warning: mediaproxy/checkAsymmetricFile(): cannot " "compile line %d of the %s asymmetric clients file into a " "regular expression (will be ignored): %s", i, which, errbuf); pkg_free(re); } } aptr->timestamp = statbuf.st_mtime; LOG(L_INFO, "info: mediaproxy: %sloaded %s asymmetric clients file " "containing %d entr%s.\n", firstTime ? "" : "re", which, aptr->count, aptr->count==1 ? "y" : "ies"); } // // This is a hack. Until a I find a better way to allow all children to update // the asymmetric client list when the files change on disk, it stays as it is // A timer registered from mod_init() only runs in one of the ser processes // and the others will always see the file that was read at startup. // #include #define CHECK_INTERVAL 5 static void periodicAsymmetricsCheck(void) { static time_t last = 0; time_t now; // this is not guaranteed to run at every CHECK_INTERVAL. // it is only guaranteed that the files won't be checked more often. now = time(NULL); if (now > last + CHECK_INTERVAL) { checkAsymmetricFile(&sipAsymmetrics); checkAsymmetricFile(&rtpAsymmetrics); last = now; } } static Bool isSIPAsymmetric(str userAgent) { int i, code; char c; periodicAsymmetricsCheck(); if (!sipAsymmetrics.clients || sipAsymmetrics.count==0) return False; c = userAgent.s[userAgent.len]; userAgent.s[userAgent.len] = 0; for (i=0; ivia1->port ? msg->via1->port : SIP_PORT); diffPort = (msg->rcv.src_port != via1Port); } return (diffIP || diffPort); } /* tests if Contact field contains a private IP address as defined in RFC1918 */ static Bool testPrivateContact(struct sip_msg* msg) { struct sip_uri uri; contact_t* contact; if (!getContactURI(msg, &uri, &contact)) return False; return isPrivateAddress(&(uri.host)); } /* tests if top Via field contains a private IP address as defined in RFC1918 */ static Bool testPrivateVia(struct sip_msg* msg) { return isPrivateAddress(&(msg->via1->host)); } #include "functions.h" /* The public functions that are exported by this module */ static int ClientNatTest(struct sip_msg* msg, char* str1, char* str2) { int tests, i; tests = (int)(long)str1; for (i=0; natTests[i].test!=NTNone; i++) { if ((tests & natTests[i].test)!=0 && natTests[i].proc(msg)) { return 1; } } return -1; // all failed } static int EndMediaSession(struct sip_msg* msg, char* str1, char* str2) { char *command, *result; str callId; if (!getCallId(msg, &callId)) { LOG(L_ERR, "error: end_media_session(): can't get Call-Id\n"); return -1; } command = pkg_malloc(callId.len + 20); if (command == NULL) { LOG(L_ERR, "error: end_media_session(): out of memory\n"); return -1; } sprintf(command, "delete %.*s info=\n", callId.len, callId.s); result = sendMediaproxyCommand(command); pkg_free(command); return result==NULL ? -1 : 1; } #define MSG_UNKNOWN 0 #define MSG_INVITE 1 #define MSG_ACK 2 #define MSG_REPLY 3 static int UseMediaProxy(struct sip_msg* msg, char* str1, char* str2) { str sdp, sessionIP, callId, fromDomain, toDomain, userAgent, tokens[64]; str fromAddr, toAddr, fromTag, toTag; char *clientIP, *ptr, *command, *result, *agent, *fromType, *toType, *info; int streamCount, i, port, count, portCount, cmdlen, infolen, success, type; StreamInfo streams[64], stream; Bool request; if (msg->first_line.type == SIP_REQUEST) { if (msg->first_line.u.request.method_value == METHOD_INVITE) type = MSG_INVITE; else if (msg->first_line.u.request.method_value == METHOD_ACK) type = MSG_ACK; else type = MSG_UNKNOWN; } else if (msg->first_line.type == SIP_REPLY) { type = MSG_REPLY; } else { type = MSG_UNKNOWN; } if (type==MSG_INVITE || type==MSG_ACK) { request = True; } else if (type==MSG_REPLY) { request = False; } else { return -1; } if (!getCallId(msg, &callId)) { LOG(L_ERR, "error: use_media_proxy(): can't get Call-Id\n"); return -1; } success = getSDPMessage(msg, &sdp); if (success==0 && type==MSG_ACK) { return 1; // nothing to do. it's ok for ACK to not have a SDP body } else if (success <= 0) { LOG(L_ERR, "error: use_media_proxy(): failed to get the SDP message\n"); return -1; } if (!getSessionLevelMediaIP(&sdp, &sessionIP)) { LOG(L_ERR, "error: use_media_proxy(): error parsing the SDP message\n"); return -1; } streamCount = getMediaStreams(&sdp, &sessionIP, streams, 64); if (streamCount == -1) { LOG(L_ERR, "error: use_media_proxy(): can't extract media streams " "from the SDP message\n"); return -1; } if (streamCount == 0) { // there are no media streams. we have nothing to do. return 1; } fromDomain = getFromDomain(msg); fromType = (isFromLocal(msg, NULL, NULL)>0) ? "local" : "remote"; fromAddr = getFromAddress(msg); toAddr = getToAddress(msg); fromTag = getFromTag(msg); toTag = getToTag(msg); userAgent = getUserAgent(msg); if (request) { toDomain = getDestinationDomain(msg); // call only for requests toType = (isDestinationLocal(msg, NULL, NULL)>0) ? "local" : "remote"; } else { toDomain = getToDomain(msg); toType = "unknown"; //there's no function to check if To domain is local } clientIP = ip_addr2a(&msg->rcv.src_ip); infolen = fromAddr.len + toAddr.len + fromTag.len + toTag.len + 64; cmdlen = callId.len + strlen(clientIP) + fromDomain.len + toDomain.len + userAgent.len*3 + infolen + 128; for (i=0; i 65535) { LOG(L_ERR, "error: use_media_proxy(): invalid port returned " "by mediaproxy: %.*s\n", tokens[i+1].len, tokens[i+1].s); //return -1; continue; } if (streams[i].port.len!=1 || streams[i].port.s[0]!='0') { success = replaceElement(msg, &(streams[i].port), &tokens[i+1]); if (!success) { LOG(L_ERR, "error: use_media_proxy(): failed to replace " "port in media stream nr. %d\n", i+1); return -1; } } if (streams[i].localIP && !isAnyAddress(streams[i].ip)) { success = replaceElement(msg, &(streams[i].ip), &tokens[0]); if (!success) { LOG(L_ERR, "error: use_media_proxy(): failed to replace " "IP address in media stream nr. %d\n", i+1); return -1; } } } return 1; } /* Module management: initialization/destroy/function-parameter-fixing/... */ static int mod_init(void) { bind_usrloc_t ul_bind_usrloc; isFromLocal = (CheckLocalPartyProc)find_export("is_from_local", 0, 0); isDestinationLocal = (CheckLocalPartyProc)find_export("is_uri_host_local", 0, 0); if (!isFromLocal || !isDestinationLocal) { LOG(L_ERR, "error: mediaproxy/mod_init(): can't find is_from_local " "and/or is_uri_host_local functions. Check if domain.so is loaded\n"); return -1; } if (natpingInterval > 0) { ul_bind_usrloc = (bind_usrloc_t)find_export("ul_bind_usrloc", 1, 0); if (!ul_bind_usrloc) { LOG(L_ERR, "error: mediaproxy/mod_init(): can't find the usrloc " "module. Check if usrloc.so is loaded.\n"); return -1; } if (ul_bind_usrloc(&userLocation) < 0) { LOG(L_ERR, "error: mediaproxy/mod_init(): can't access the usrloc module.\n"); return -1; } register_timer(pingClients, NULL, natpingInterval); } checkAsymmetricFile(&sipAsymmetrics); checkAsymmetricFile(&rtpAsymmetrics); // children won't benefit from this. figure another way //register_timer(checkAsymmetricFiles, NULL, 5); return 0; } // convert string parameter to integer for functions that expect an integer static int fixstring2int(void **param, int param_count) { unsigned long number; int err; if (param_count == 1) { number = str2s(*param, strlen(*param), &err); if (err == 0) { pkg_free(*param); *param = (void*)number; return 0; } else { LOG(L_ERR, "error: mediaproxy/fixstring2int(): bad number `%s'\n", (char*)(*param)); return E_CFG; } } return 0; }