cregit-Linux how code gets into the kernel

Release 4.11 net/netfilter/nf_conntrack_sip.c

Directory: net/netfilter
/* SIP extension for IP connection tracking.
 *
 * (C) 2005 by Christian Hentschel <chentschel@arnet.com.ar>
 * based on RR's ip_conntrack_ftp.c and other modules.
 * (C) 2007 United Security Providers
 * (C) 2007, 2008 Patrick McHardy <kaber@trash.net>
 *
 * 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.
 */


#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/module.h>
#include <linux/ctype.h>
#include <linux/skbuff.h>
#include <linux/inet.h>
#include <linux/in.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include <linux/netfilter.h>

#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_core.h>
#include <net/netfilter/nf_conntrack_expect.h>
#include <net/netfilter/nf_conntrack_helper.h>
#include <net/netfilter/nf_conntrack_zones.h>
#include <linux/netfilter/nf_conntrack_sip.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Christian Hentschel <chentschel@arnet.com.ar>");
MODULE_DESCRIPTION("SIP connection tracking helper");
MODULE_ALIAS("ip_conntrack_sip");
MODULE_ALIAS_NFCT_HELPER("sip");


#define MAX_PORTS	8

static unsigned short ports[MAX_PORTS];

static unsigned 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 __read_mostly = SIP_TIMEOUT;
module_param(sip_timeout, uint, 0600);
MODULE_PARM_DESC(sip_timeout, "timeout for the master SIP session");


static int sip_direct_signalling __read_mostly = 1;
module_param(sip_direct_signalling, int, 0600);
MODULE_PARM_DESC(sip_direct_signalling, "expect incoming calls from registrar "
					"only (default 1)");


static int sip_direct_media __read_mostly = 1;
module_param(sip_direct_media, int, 0600);
MODULE_PARM_DESC(sip_direct_media, "Expect Media streams between signalling "
				   "endpoints only (default 1)");


const struct nf_nat_sip_hooks *nf_nat_sip_hooks;

EXPORT_SYMBOL_GPL(nf_nat_sip_hooks);


static int string_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { int len = 0; while (dptr < limit && isalpha(*dptr)) { dptr++; len++; } return len; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy5398.15%266.67%
Jan Engelhardt11.85%133.33%
Total54100.00%3100.00%


static int digits_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { int len = 0; while (dptr < limit && isdigit(*dptr)) { dptr++; len++; } return len; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy5398.15%266.67%
Jan Engelhardt11.85%133.33%
Total54100.00%3100.00%


static int iswordc(const char c) { if (isalnum(c) || c == '!' || c == '"' || c == '%' || (c >= '(' && c <= '+') || c == ':' || c == '<' || c == '>' || c == '?' || (c >= '[' && c <= ']') || c == '_' || c == '`' || c == '{' || c == '}' || c == '~' || (c >= '-' && c <= '/') || c == '\'') return 1; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Simon Horman9085.71%150.00%
Marco Angaroni1514.29%150.00%
Total105100.00%2100.00%


static int word_len(const char *dptr, const char *limit) { int len = 0; while (dptr < limit && iswordc(*dptr)) { dptr++; len++; } return len; }

Contributors

PersonTokensPropCommitsCommitProp
Simon Horman44100.00%1100.00%
Total44100.00%1100.00%


static int callid_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { int len, domain_len; len = word_len(dptr, limit); dptr += len; if (!len || dptr == limit || *dptr != '@') return len; dptr++; len++; domain_len = word_len(dptr, limit); if (!domain_len) return 0; return len + domain_len; }

Contributors

PersonTokensPropCommitsCommitProp
Simon Horman89100.00%1100.00%
Total89100.00%1100.00%

/* get media type + port length */
static int media_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { int len = string_len(ct, dptr, limit, shift); dptr += len; if (dptr >= limit || *dptr != ' ') return 0; len++; dptr++; return len + digits_len(ct, dptr, limit, shift); }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy78100.00%1100.00%
Total78100.00%1100.00%


static int sip_parse_addr(const struct nf_conn *ct, const char *cp, const char **endp, union nf_inet_addr *addr, const char *limit, bool delim) { const char *end; int ret; if (!ct) return 0; memset(addr, 0, sizeof(*addr)); switch (nf_ct_l3num(ct)) { case AF_INET: ret = in4_pton(cp, limit - cp, (u8 *)&addr->ip, -1, &end); if (ret == 0) return 0; break; case AF_INET6: if (cp < limit && *cp == '[') cp++; else if (delim) return 0; ret = in6_pton(cp, limit - cp, (u8 *)&addr->ip6, -1, &end); if (ret == 0) return 0; if (end < limit && *end == ']') end++; else if (delim) return 0; break; default: BUG(); } if (endp) *endp = end; return 1; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy20395.75%571.43%
Simon Horman83.77%114.29%
Jan Engelhardt10.47%114.29%
Total212100.00%7100.00%

/* skip ip address. returns its length. */
static int epaddr_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { union nf_inet_addr addr; const char *aux = dptr; if (!sip_parse_addr(ct, dptr, &dptr, &addr, limit, true)) { pr_debug("ip: %s parse failed.!\n", dptr); return 0; } /* Port number */ if (*dptr == ':') { dptr++; dptr += digits_len(ct, dptr, limit, shift); } return dptr - aux; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy9999.00%480.00%
Jan Engelhardt11.00%120.00%
Total100100.00%5100.00%

/* get address length, skiping user info. */
static int skp_epaddr_len(const struct nf_conn *ct, const char *dptr, const char *limit, int *shift) { const char *start = dptr; 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 { dptr = start; *shift = s; } return epaddr_len(ct, dptr, limit, shift); }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy11799.15%375.00%
Jan Engelhardt10.85%125.00%
Total118100.00%4100.00%

/* Parse a SIP request line of the form: * * Request-Line = Method SP Request-URI SP SIP-Version CRLF * * and return the offset and length of the address contained in the Request-URI. */
int ct_sip_parse_request(const struct nf_conn *ct, const char *dptr, unsigned int datalen, unsigned int *matchoff, unsigned int *matchlen, union nf_inet_addr *addr, __be16 *port) { const char *start = dptr, *limit = dptr + datalen, *end; unsigned int mlen; unsigned int p; int shift = 0; /* Skip method and following whitespace */ mlen = string_len(ct, dptr, limit, NULL); if (!mlen) return 0; dptr += mlen; if (++dptr >= limit) return 0; /* Find SIP URI */ for (; dptr < limit - strlen("sip:"); dptr++) { if (*dptr == '\r' || *dptr == '\n') return -1; if (strncasecmp(dptr, "sip:", strlen("sip:")) == 0) { dptr += strlen("sip:"); break; } } if (!skp_epaddr_len(ct, dptr, limit, &shift)) return 0; dptr += shift; if (!sip_parse_addr(ct, dptr, &end, addr, limit, true)) return -1; if (end < limit && *end == ':') { end++; p = simple_strtoul(end, (char **)&end, 10); if (p < 1024 || p > 65535) return -1; *port = htons(p); } else *port = htons(SIP_PORT); if (end == dptr) return 0; *matchoff = dptr - start; *matchlen = end - dptr; return 1; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy29899.33%675.00%
Jan Engelhardt10.33%112.50%
Rasmus Villemoes10.33%112.50%
Total300100.00%8100.00%

EXPORT_SYMBOL_GPL(ct_sip_parse_request); /* SIP header parsing: SIP headers are located at the beginning of a line, but * may span several lines, in which case the continuation lines begin with a * whitespace character. RFC 2543 allows lines to be terminated with CR, LF or * CRLF, RFC 3261 allows only CRLF, we support both. * * Headers are followed by (optionally) whitespace, a colon, again (optionally) * whitespace and the values. Whitespace in this context means any amount of * tabs, spaces and continuation lines, which are treated as a single whitespace * character. * * Some headers may appear multiple times. A comma separated list of values is * equivalent to multiple headers. */ static const struct sip_header ct_sip_hdrs[] = { [SIP_HDR_CSEQ] = SIP_HDR("CSeq", NULL, NULL, digits_len), [SIP_HDR_FROM] = SIP_HDR("From", "f", "sip:", skp_epaddr_len), [SIP_HDR_TO] = SIP_HDR("To", "t", "sip:", skp_epaddr_len), [SIP_HDR_CONTACT] = SIP_HDR("Contact", "m", "sip:", skp_epaddr_len), [SIP_HDR_VIA_UDP] = SIP_HDR("Via", "v", "UDP ", epaddr_len), [SIP_HDR_VIA_TCP] = SIP_HDR("Via", "v", "TCP ", epaddr_len), [SIP_HDR_EXPIRES] = SIP_HDR("Expires", NULL, NULL, digits_len), [SIP_HDR_CONTENT_LENGTH] = SIP_HDR("Content-Length", "l", NULL, digits_len), [SIP_HDR_CALL_ID] = SIP_HDR("Call-Id", "i", NULL, callid_len), };
static const char *sip_follow_continuation(const char *dptr, const char *limit) { /* Walk past newline */ if (++dptr >= limit) return NULL; /* Skip '\n' in CR LF */ if (*(dptr - 1) == '\r' && *dptr == '\n') { if (++dptr >= limit) return NULL; } /* Continuation line? */ if (*dptr != ' ' && *dptr != '\t') return NULL; /* skip leading whitespace */ for (; dptr < limit; dptr++) { if (*dptr != ' ' && *dptr != '\t') break; } return dptr; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy103100.00%2100.00%
Total103100.00%2100.00%


static const char *sip_skip_whitespace(const char *dptr, const char *limit) { for (; dptr < limit; dptr++) { if (*dptr == ' ' || *dptr == '\t') continue; if (*dptr != '\r' && *dptr != '\n') break; dptr = sip_follow_continuation(dptr, limit); break; } return dptr; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy6391.30%360.00%
Marco Angaroni68.70%240.00%
Total69100.00%5100.00%

/* Search within a SIP header value, dealing with continuation lines */
static const char *ct_sip_header_search(const char *dptr, const char *limit, const char *needle, unsigned int len) { for (limit -= len; dptr < limit; dptr++) { if (*dptr == '\r' || *dptr == '\n') { dptr = sip_follow_continuation(dptr, limit); if (dptr == NULL) break; continue; } if (strncasecmp(dptr, needle, len) == 0) return dptr; } return NULL; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy9198.91%266.67%
Rasmus Villemoes11.09%133.33%
Total92100.00%3100.00%


int ct_sip_get_header(const struct nf_conn *ct, const char *dptr, unsigned int dataoff, unsigned int datalen, enum sip_header_types type, unsigned int *matchoff, unsigned int *matchlen) { const struct sip_header *hdr = &ct_sip_hdrs[type]; const char *start = dptr, *limit = dptr + datalen; int shift = 0; for (dptr += dataoff; dptr < limit; dptr++) { /* Find beginning of line */ if (*dptr != '\r' && *dptr != '\n') continue; if (++dptr >= limit) break; if (*(dptr - 1) == '\r' && *dptr == '\n') { if (++dptr >= limit) break; } /* Skip continuation lines */ if (*dptr == ' ' || *dptr == '\t') continue; /* Find header. Compact headers must be followed by a * non-alphabetic character to avoid mismatches. */ if (limit - dptr >= hdr->len && strncasecmp(dptr, hdr->name, hdr->len) == 0) dptr += hdr->len; else if (hdr->cname && limit - dptr >= hdr->clen + 1 && strncasecmp(dptr, hdr->cname, hdr->clen) == 0 && !isalpha(*(dptr + hdr->clen))) dptr += hdr->clen; else continue; /* Find and skip colon */ dptr = sip_skip_whitespace(dptr, limit); if (dptr == NULL) break; if (*dptr != ':' || ++dptr >= limit) break; /* Skip whitespace after colon */ dptr = sip_skip_whitespace(dptr, limit); if (dptr == NULL) break; *matchoff = dptr - start; if (hdr->search) { dptr = ct_sip_header_search(dptr, limit, hdr->search, hdr->slen); if (!dptr) return -1; dptr += hdr->slen; } *matchlen = hdr->match_len(ct, dptr, limit, &shift); if (!*matchlen) return -1; *matchoff = dptr - start + shift; return 1; } return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy36499.18%360.00%
Rasmus Villemoes20.54%120.00%
Jan Engelhardt10.27%120.00%
Total367100.00%5100.00%

EXPORT_SYMBOL_GPL(ct_sip_get_header); /* Get next header field in a list of comma separated values */
static int ct_sip_next_header(const struct nf_conn *ct, const char *dptr, unsigned int dataoff, unsigned int datalen, enum sip_header_types type, unsigned int *matchoff, unsigned int *matchlen) { const struct sip_header *hdr = &ct_sip_hdrs[type]; const char *start = dptr, *limit = dptr + datalen; int shift = 0; dptr += dataoff; dptr = ct_sip_header_search(dptr, limit, ",", strlen(",")); if (!dptr) return 0; dptr = ct_sip_header_search(dptr, limit, hdr->search, hdr->slen); if (!dptr) return 0; dptr += hdr->slen; *matchoff = dptr - start; *matchlen = hdr->match_len(ct, dptr, limit, &shift); if (!*matchlen) return -1; *matchoff += shift; return 1; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy171100.00%1100.00%
Total171100.00%1100.00%

/* Walk through headers until a parsable one is found or no header of the * given type is left. */
static int ct_sip_walk_headers(const struct nf_conn *ct, const char *dptr, unsigned int dataoff, unsigned int datalen, enum sip_header_types type, int *in_header, unsigned int *matchoff, unsigned int *matchlen) { int ret; if (in_header && *in_header) { while (1) { ret = ct_sip_next_header(ct, dptr, dataoff, datalen, type, matchoff, matchlen); if (ret > 0) return ret; if (ret == 0) break; dataoff += *matchoff; } *in_header = 0; } while (1) { ret = ct_sip_get_header(ct, dptr, dataoff, datalen, type, matchoff, matchlen); if (ret > 0) break; if (ret == 0) return ret; dataoff += *matchoff; } if (in_header) *in_header = 1; return 1; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy164100.00%1100.00%
Total164100.00%1100.00%

/* Locate a SIP header, parse the URI and return the offset and length of * the address as well as the address and port themselves. A stream of * headers can be parsed by handing in a non-NULL datalen and in_header * pointer. */
int ct_sip_parse_header_uri(const struct nf_conn *ct, const char *dptr, unsigned int *dataoff, unsigned int datalen, enum sip_header_types type, int *in_header, unsigned int *matchoff, unsigned int *matchlen, union nf_inet_addr *addr, __be16 *port) { const char *c, *limit = dptr + datalen; unsigned int p; int ret; ret = ct_sip_walk_headers(ct, dptr, dataoff ? *dataoff : 0, datalen, type, in_header, matchoff, matchlen); WARN_ON(ret < 0); if (ret == 0) return ret; if (!sip_parse_addr(ct, dptr + *matchoff, &c, addr, limit, true)) return -1; if (*c == ':') { c++; p = simple_strtoul(c, (char **)&c, 10); if (p < 1024 || p > 65535) return -1; *port = htons(p); } else *port = htons(SIP_PORT); if (dataoff) *dataoff = c - dptr; return 1; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy213100.00%2100.00%
Total213100.00%2100.00%

EXPORT_SYMBOL_GPL(ct_sip_parse_header_uri);
static int ct_sip_parse_param(const struct nf_conn *ct, const char *dptr, unsigned int dataoff, unsigned int datalen, const char *name, unsigned int *matchoff, unsigned int *matchlen) { const char *limit = dptr + datalen; const char *start; const char *end; limit = ct_sip_header_search(dptr + dataoff, limit, ",", strlen(",")); if (!limit) limit = dptr + datalen; start = ct_sip_header_search(dptr + dataoff, limit, name, strlen(name)); if (!start) return 0; start += strlen(name); end = ct_sip_header_search(start, limit, ";", strlen(";")); if (!end) end = limit; *matchoff = start - dptr; *matchlen = end - start; return 1; }

Contributors

PersonTokensPropCommitsCommitProp
Patrick McHardy163100.00%1100.00%
Total163100.00%1100.00%

/* Parse address from header parameter and return address, offset and length */
int ct_sip_parse_address_param(const struct nf_conn *ct, const char *dptr, unsigned int dataoff, unsigned int datalen, const char *name, unsigned int *matchoff, unsigned int *matchlen, union nf_inet_addr *addr, bool delim) { const char *limit =