/* SIP extension for IP connection tracking. * * (C) 2005 by Christian Hentschel * based on RR's ip_conntrack_ftp.c and other modules. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #if 0 #define DEBUGP printk #else #define DEBUGP(format, args...) #endif MODULE_LICENSE("GPL"); MODULE_AUTHOR("Christian Hentschel "); MODULE_DESCRIPTION("SIP connection tracking helper"); #define MAX_PORTS 8 static unsigned short ports[MAX_PORTS]; static int ports_c; module_param_array(ports, ushort, &ports_c, 0400); MODULE_PARM_DESC(ports, "port numbers of sip servers"); static unsigned int sip_timeout = SIP_TIMEOUT; module_param(sip_timeout, uint, 0600); MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session"); unsigned int (*ip_nat_sip_hook)(struct sk_buff **pskb, enum ip_conntrack_info ctinfo, struct ip_conntrack *ct, const char **dptr); EXPORT_SYMBOL_GPL(ip_nat_sip_hook); unsigned int (*ip_nat_sdp_hook)(struct sk_buff **pskb, enum ip_conntrack_info ctinfo, struct ip_conntrack_expect *exp, const char *dptr); EXPORT_SYMBOL_GPL(ip_nat_sdp_hook); static int digits_len(const char *dptr, const char *limit, int *shift); static int epaddr_len(const char *dptr, const char *limit, int *shift); static int skp_digits_len(const char *dptr, const char *limit, int *shift); static int skp_epaddr_len(const char *dptr, const char *limit, int *shift); struct sip_header_nfo { const char *lname; const char *sname; const char *ln_str; size_t lnlen; size_t snlen; size_t ln_strlen; int case_sensitive; int (*match_len)(const char *, const char *, int *); }; static struct sip_header_nfo ct_sip_hdrs[] = { [POS_REG_REQ_URI] = { /* SIP REGISTER request URI */ .lname = "sip:", .lnlen = sizeof("sip:") - 1, .ln_str = ":", .ln_strlen = sizeof(":") - 1, .match_len = epaddr_len }, [POS_REQ_URI] = { /* SIP request URI */ .lname = "sip:", .lnlen = sizeof("sip:") - 1, .ln_str = "@", .ln_strlen = sizeof("@") - 1, .match_len = epaddr_len }, [POS_FROM] = { /* SIP From header */ .lname = "From:", .lnlen = sizeof("From:") - 1, .sname = "\r\nf:", .snlen = sizeof("\r\nf:") - 1, .ln_str = "sip:", .ln_strlen = sizeof("sip:") - 1, .match_len = skp_epaddr_len, }, [POS_TO] = { /* SIP To header */ .lname = "To:", .lnlen = sizeof("To:") - 1, .sname = "\r\nt:", .snlen = sizeof("\r\nt:") - 1, .ln_str = "sip:", .ln_strlen = sizeof("sip:") - 1, .match_len = skp_epaddr_len, }, [POS_VIA] = { /* SIP Via header */ .lname = "Via:", .lnlen = sizeof("Via:") - 1, .sname = "\r\nv:", .snlen = sizeof("\r\nv:") - 1, /* rfc3261 "\r\n" */ .ln_str = "UDP ", .ln_strlen = sizeof("UDP ") - 1, .match_len = epaddr_len, }, [POS_CONTACT] = { /* SIP Contact header */ .lname = "Contact:", .lnlen = sizeof("Contact:") - 1, .sname = "\r\nm:", .snlen = sizeof("\r\nm:") - 1, .ln_str = "sip:", .ln_strlen = sizeof("sip:") - 1, .match_len = skp_epaddr_len }, [POS_CONTENT] = { /* SIP Content length header */ .lname = "Content-Length:", .lnlen = sizeof("Content-Length:") - 1, .sname = "\r\nl:", .snlen = sizeof("\r\nl:") - 1, .ln_str = ":", .ln_strlen = sizeof(":") - 1, .match_len = skp_digits_len }, [POS_MEDIA] = { /* SDP media info */ .case_sensitive = 1, .lname = "\nm=", .lnlen = sizeof("\nm=") - 1, .sname = "\rm=", .snlen = sizeof("\rm=") - 1, .ln_str = "audio ", .ln_strlen = sizeof("audio ") - 1, .match_len = digits_len }, [POS_OWNER] = { /* SDP owner address*/ .case_sensitive = 1, .lname = "\no=", .lnlen = sizeof("\no=") - 1, .sname = "\ro=", .snlen = sizeof("\ro=") - 1, .ln_str = "IN IP4 ", .ln_strlen = sizeof("IN IP4 ") - 1, .match_len = epaddr_len }, [POS_CONNECTION] = { /* SDP connection info */ .case_sensitive = 1, .lname = "\nc=", .lnlen = sizeof("\nc=") - 1, .sname = "\rc=", .snlen = sizeof("\rc=") - 1, .ln_str = "IN IP4 ", .ln_strlen = sizeof("IN IP4 ") - 1, .match_len = epaddr_len }, [POS_SDP_HEADER] = { /* SDP version header */ .case_sensitive = 1, .lname = "\nv=", .lnlen = sizeof("\nv=") - 1, .sname = "\rv=", .snlen = sizeof("\rv=") - 1, .ln_str = "=", .ln_strlen = sizeof("=") - 1, .match_len = digits_len } }; /* get line lenght until first CR or LF seen. */ int ct_sip_lnlen(const char *line, const char *limit) { const char *k = line; while ((line <= limit) && (*line == '\r' || *line == '\n')) line++; while (line <= limit) { if (*line == '\r' || *line == '\n') break; line++; } return line - k; } EXPORT_SYMBOL_GPL(ct_sip_lnlen); /* Linear string search, case sensitive. */ const char *ct_sip_search(const char *needle, const char *haystack, size_t needle_len, size_t haystack_len, int case_sensitive) { const char *limit = haystack + (haystack_len - needle_len); while (haystack <= limit) { if (case_sensitive) { if (strncmp(haystack, needle, needle_len) == 0) return haystack; } else { if (strnicmp(haystack, needle, needle_len) == 0) return haystack; } haystack++; } return NULL; } EXPORT_SYMBOL_GPL(ct_sip_search); static int digits_len(const char *dptr, const char *limit, int *shift) { int len = 0; while (dptr <= limit && isdigit(*dptr)) { dptr++; len++; } return len; } /* get digits lenght, skiping blank spaces. */ static int skp_digits_len(const char *dptr, const char *limit, int *shift) { for (; dptr <= limit && *dptr == ' '; dptr++) (*shift)++; return digits_len(dptr, limit, shift); } /* Simple ipaddr parser.. */ static int parse_ipaddr(const char *cp, const char **endp, __be32 *ipaddr, const char *limit) { unsigned long int val; int i, digit = 0; for (i = 0, *ipaddr = 0; cp <= limit && i < 4; i++) { digit = 0; if (!isdigit(*cp)) break; val = simple_strtoul(cp, (char **)&cp, 10); if (val > 0xFF) return -1; ((u_int8_t *)ipaddr)[i] = val; digit = 1; if (*cp != '.') break; cp++; } if (!digit) return -1; if (endp) *endp = cp; return 0; } /* skip ip address. returns it lenght. */ static int epaddr_len(const char *dptr, const char *limit, int *shift) { const char *aux = dptr; __be32 ip; if (parse_ipaddr(dptr, &dptr, &ip, limit) < 0) { DEBUGP("ip: %s parse failed.!\n", dptr); return 0; } /* Port number */ if (*dptr == ':') { dptr++; dptr += digits_len(dptr, limit, shift); } return dptr - aux; } /* get address length, skiping user info. */ static int skp_epaddr_len(const char *dptr, const char *limit, int *shift) { int s = *shift; /* Search for @, but stop at the end of the line. * We are inside a sip: URI, so we don't need to worry about * continuation lines. */ while (dptr <= limit && *dptr != '@' && *dptr != '\r' && *dptr != '\n') { (*shift)++; dptr++; } if (dptr <= limit && *dptr == '@') { dptr++; (*shift)++; } else *shift = s; return epaddr_len(dptr, limit, shift); } /* Returns 0 if not found, -1 error parsing. */ int ct_sip_get_info(const char *dptr, size_t dlen, unsigned int *matchoff, unsigned int *matchlen, enum sip_header_pos pos) { struct sip_header_nfo *hnfo = &ct_sip_hdrs[pos]; const char *limit, *aux, *k = dptr; int shift = 0; limit = dptr + (dlen - hnfo->lnlen); while (dptr <= limit) { if ((strncmp(dptr, hnfo->lname, hnfo->lnlen) != 0) && (hnfo->sname == NULL || strncmp(dptr, hnfo->sname, hnfo->snlen) != 0)) { dptr++; continue; } aux = ct_sip_search(hnfo->ln_str, dptr, hnfo->ln_strlen, ct_sip_lnlen(dptr, limit), hnfo->case_sensitive); if (!aux) { DEBUGP("'%s' not found in '%s'.\n", hnfo->ln_str, hnfo->lname); return -1; } aux += hnfo->ln_strlen; *matchlen = hnfo->match_len(aux, limit, &shift); if (!*matchlen) return -1; *matchoff = (aux - k) + shift; DEBUGP("%s match succeeded! - len: %u\n", hnfo->lname, *matchlen); return 1; } DEBUGP("%s header not found.\n", hnfo->lname); return 0; } EXPORT_SYMBOL_GPL(ct_sip_get_info); static int set_expected_rtp(struct sk_buff **pskb, struct ip_conntrack *ct, enum ip_conntrack_info ctinfo, __be32 ipaddr, u_int16_t port, const char *dptr) { struct ip_conntrack_expect *exp; enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); int ret; typeof(ip_nat_sdp_hook) ip_nat_sdp; exp = ip_conntrack_expect_alloc(ct); if (exp == NULL) return NF_DROP; exp->tuple.src.ip = ct->tuplehash[!dir].tuple.src.ip; exp->tuple.src.u.udp.port = 0; exp->tuple.dst.ip = ipaddr; exp->tuple.dst.u.udp.port = htons(port); exp->tuple.dst.protonum = IPPROTO_UDP; exp->mask.src.ip = htonl(0xFFFFFFFF); exp->mask.src.u.udp.port = 0; exp->mask.dst.ip = htonl(0xFFFFFFFF); exp->mask.dst.u.udp.port = htons(0xFFFF); exp->mask.dst.protonum = 0xFF; exp->expectfn = NULL; exp->flags = 0; ip_nat_sdp = rcu_dereference(ip_nat_sdp_hook); if (ip_nat_sdp) ret = ip_nat_sdp(pskb, ctinfo, exp, dptr); else { if (ip_conntrack_expect_related(exp) != 0) ret = NF_DROP; else ret = NF_ACCEPT; } ip_conntrack_expect_put(exp); return ret; } static int sip_help(struct sk_buff **pskb, struct ip_conntrack *ct, enum ip_conntrack_info ctinfo) { unsigned int dataoff, datalen; const char *dptr; int ret = NF_ACCEPT; int matchoff, matchlen; __be32 ipaddr; u_int16_t port; typeof(ip_nat_sip_hook) ip_nat_sip; /* No Data ? */ dataoff = (*pskb)->nh.iph->ihl*4 + sizeof(struct udphdr); if (dataoff >= (*pskb)->len) { DEBUGP("skb->len = %u\n", (*pskb)->len); return NF_ACCEPT; } ip_ct_refresh(ct, *pskb, sip_timeout * HZ); if (!skb_is_nonlinear(*pskb)) dptr = (*pskb)->data + dataoff; else { DEBUGP("Copy of skbuff not supported yet.\n"); goto out; } ip_nat_sip = rcu_dereference(ip_nat_sip_hook); if (ip_nat_sip) { if (!ip_nat_sip(pskb, ctinfo, ct, &dptr)) { ret = NF_DROP; goto out; } } /* After this point NAT, could have mangled skb, so we need to recalculate payload lenght. */ datalen = (*pskb)->len - dataoff; if (datalen < (sizeof("SIP/2.0 200") - 1)) goto out; /* RTP info only in some SDP pkts */ if (memcmp(dptr, "INVITE", sizeof("INVITE") - 1) != 0 && memcmp(dptr, "SIP/2.0 200", sizeof("SIP/2.0 200") - 1) != 0) { goto out; } /* Get ip and port address from SDP packet. */ if (ct_sip_get_info(dptr, datalen, &matchoff, &matchlen, POS_CONNECTION) > 0) { /* We'll drop only if there are parse problems. */ if (parse_ipaddr(dptr + matchoff, NULL, &ipaddr, dptr + datalen) < 0) { ret = NF_DROP; goto out; } if (ct_sip_get_info(dptr, datalen, &matchoff, &matchlen, POS_MEDIA) > 0) { port = simple_strtoul(dptr + matchoff, NULL, 10); if (port < 1024) { ret = NF_DROP; goto out; } ret = set_expected_rtp(pskb, ct, ctinfo, ipaddr, port, dptr); } } out: return ret; } static struct ip_conntrack_helper sip[MAX_PORTS]; static char sip_names[MAX_PORTS][10]; static void fini(void) { int i; for (i = 0; i < ports_c; i++) { DEBUGP("unregistering helper for port %d\n", ports[i]); ip_conntrack_helper_unregister(&sip[i]); } } static int __init init(void) { int i, ret; char *tmpname; if (ports_c == 0) ports[ports_c++] = SIP_PORT; for (i = 0; i < ports_c; i++) { /* Create helper structure */ memset(&sip[i], 0, sizeof(struct ip_conntrack_helper)); sip[i].tuple.dst.protonum = IPPROTO_UDP; sip[i].tuple.src.u.udp.port = htons(ports[i]); sip[i].mask.src.u.udp.port = htons(0xFFFF); sip[i].mask.dst.protonum = 0xFF; sip[i].max_expected = 2; sip[i].timeout = 3 * 60; /* 3 minutes */ sip[i].me = THIS_MODULE; sip[i].help = sip_help; tmpname = &sip_names[i][0]; if (ports[i] == SIP_PORT) sprintf(tmpname, "sip"); else sprintf(tmpname, "sip-%d", i); sip[i].name = tmpname; DEBUGP("port #%d: %d\n", i, ports[i]); ret = ip_conntrack_helper_register(&sip[i]); if (ret) { printk("ERROR registering helper for port %d\n", ports[i]); fini(); return ret; } } return 0; } module_init(init); module_exit(fini);