POS_VIA,
        POS_CONTACT,
        POS_CONTENT,
-       POS_MEDIA,
-       POS_OWNER_IP4,
-       POS_CONNECTION_IP4,
-       POS_OWNER_IP6,
-       POS_CONNECTION_IP6,
-       POS_SDP_HEADER,
+};
+
+struct sip_header {
+       const char      *name;
+       const char      *cname;
+       const char      *search;
+       unsigned int    len;
+       unsigned int    clen;
+       unsigned int    slen;
+       int             (*match_len)(const struct nf_conn *ct,
+                                    const char *dptr, const char *limit,
+                                    int *shift);
+};
+
+#define __SIP_HDR(__name, __cname, __search, __match)                  \
+{                                                                      \
+       .name           = (__name),                                     \
+       .len            = sizeof(__name) - 1,                           \
+       .cname          = (__cname),                                    \
+       .clen           = (__cname) ? sizeof(__cname) - 1 : 0,          \
+       .search         = (__search),                                   \
+       .slen           = (__search) ? sizeof(__search) - 1 : 0,        \
+       .match_len      = (__match),                                    \
+}
+
+#define SDP_HDR(__name, __search, __match) \
+       __SIP_HDR(__name, NULL, __search, __match)
+
+enum sdp_header_types {
+       SDP_HDR_UNSPEC,
+       SDP_HDR_VERSION,
+       SDP_HDR_OWNER_IP4,
+       SDP_HDR_CONNECTION_IP4,
+       SDP_HDR_OWNER_IP6,
+       SDP_HDR_CONNECTION_IP6,
+       SDP_HDR_MEDIA,
 };
 
 extern unsigned int (*nf_nat_sip_hook)(struct sk_buff *skb,
 extern const char *ct_sip_search(const char *needle, const char *haystack,
                                 size_t needle_len, size_t haystack_len,
                                 int case_sensitive);
+
+extern int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr,
+                                unsigned int dataoff, unsigned int datalen,
+                                enum sdp_header_types type,
+                                enum sdp_header_types term,
+                                unsigned int *matchoff, unsigned int *matchlen);
+
 #endif /* __KERNEL__ */
 #endif /* __NF_CONNTRACK_SIP_H__ */
 
        return NF_ACCEPT;
 }
 
-static unsigned int mangle_sip_packet(struct sk_buff *skb,
-                                     const char **dptr, unsigned int *datalen,
-                                     char *buffer, int bufflen,
-                                     enum sip_header_pos pos)
+static int mangle_content_len(struct sk_buff *skb,
+                             const char **dptr, unsigned int *datalen)
 {
        enum ip_conntrack_info ctinfo;
        struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
-       unsigned int matchlen, matchoff;
+       unsigned int matchoff, matchlen;
+       char buffer[sizeof("65536")];
+       int buflen, c_len;
 
+       /* Get actual SDP length */
+       if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen,
+                                 SDP_HDR_VERSION, SDP_HDR_UNSPEC,
+                                 &matchoff, &matchlen) <= 0)
+               return 0;
+       c_len = *datalen - matchoff + strlen("v=");
+
+       /* Now, update SDP length */
        if (ct_sip_get_info(ct, *dptr, *datalen, &matchoff, &matchlen,
-                           pos) <= 0)
+                           POS_CONTENT) <= 0)
                return 0;
 
+       buflen = sprintf(buffer, "%u", c_len);
        return mangle_packet(skb, dptr, datalen, matchoff, matchlen,
-                            buffer, bufflen);
+                            buffer, buflen);
 }
 
-static int mangle_content_len(struct sk_buff *skb,
-                             const char **dptr, unsigned int *datalen)
+static unsigned mangle_sdp_packet(struct sk_buff *skb,
+                                 const char **dptr, unsigned int *datalen,
+                                 enum sdp_header_types type,
+                                 char *buffer, int buflen)
 {
        enum ip_conntrack_info ctinfo;
        struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
-       unsigned int matchoff, matchlen;
-       char buffer[sizeof("65536")];
-       int bufflen;
+       unsigned int matchlen, matchoff;
 
-       /* Get actual SDP length */
-       if (ct_sip_get_info(ct, *dptr, *datalen, &matchoff,
-                           &matchlen, POS_SDP_HEADER) > 0) {
-
-               /* since ct_sip_get_info() give us a pointer passing 'v='
-                  we need to add 2 bytes in this count. */
-               int c_len = *datalen - matchoff + 2;
-
-               /* Now, update SDP length */
-               if (ct_sip_get_info(ct, *dptr, *datalen, &matchoff,
-                                   &matchlen, POS_CONTENT) > 0) {
-
-                       bufflen = sprintf(buffer, "%u", c_len);
-                       return mangle_packet(skb, dptr, datalen,
-                                            matchoff, matchlen,
-                                            buffer, bufflen);
-               }
-       }
-       return 0;
+       if (ct_sip_get_sdp_header(ct, *dptr, 0, *datalen, type, SDP_HDR_UNSPEC,
+                                 &matchoff, &matchlen) <= 0)
+               return 0;
+       return mangle_packet(skb, dptr, datalen, matchoff, matchlen,
+                            buffer, buflen);
 }
 
 static unsigned int mangle_sdp(struct sk_buff *skb,
 
        /* Mangle owner and contact info. */
        bufflen = sprintf(buffer, "%u.%u.%u.%u", NIPQUAD(newip));
-       if (!mangle_sip_packet(skb, dptr, datalen, buffer, bufflen,
-                              POS_OWNER_IP4))
+       if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_OWNER_IP4,
+                              buffer, bufflen))
                return 0;
 
-       if (!mangle_sip_packet(skb, dptr, datalen, buffer, bufflen,
-                              POS_CONNECTION_IP4))
+       if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_CONNECTION_IP4,
+                              buffer, bufflen))
                return 0;
 
        /* Mangle media port. */
        bufflen = sprintf(buffer, "%u", port);
-       if (!mangle_sip_packet(skb, dptr, datalen, buffer, bufflen,
-                              POS_MEDIA))
+       if (!mangle_sdp_packet(skb, dptr, datalen, SDP_HDR_MEDIA,
+                              buffer, bufflen))
                return 0;
 
        return mangle_content_len(skb, dptr, datalen);
 
                .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_IP4] = {     /* 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_IP4] = {/* 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_OWNER_IP6] = {     /* SDP owner address*/
-               .case_sensitive = 1,
-               .lname          = "\no=",
-               .lnlen          = sizeof("\no=") - 1,
-               .sname          = "\ro=",
-               .snlen          = sizeof("\ro=") - 1,
-               .ln_str         = "IN IP6 ",
-               .ln_strlen      = sizeof("IN IP6 ") - 1,
-               .match_len      = epaddr_len
-       },
-       [POS_CONNECTION_IP6] = {/* SDP connection info */
-               .case_sensitive = 1,
-               .lname          = "\nc=",
-               .lnlen          = sizeof("\nc=") - 1,
-               .sname          = "\rc=",
-               .snlen          = sizeof("\rc=") - 1,
-               .ln_str         = "IN IP6 ",
-               .ln_strlen      = sizeof("IN IP6 ") - 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 length until first CR or LF seen. */
 }
 EXPORT_SYMBOL_GPL(ct_sip_get_info);
 
+/* SDP header parsing: a SDP session description contains an ordered set of
+ * headers, starting with a section containing general session parameters,
+ * optionally followed by multiple media descriptions.
+ *
+ * SDP headers always start at the beginning of a line. According to RFC 2327:
+ * "The sequence CRLF (0x0d0a) is used to end a record, although parsers should
+ * be tolerant and also accept records terminated with a single newline
+ * character". We handle both cases.
+ */
+static const struct sip_header ct_sdp_hdrs[] = {
+       [SDP_HDR_VERSION]               = SDP_HDR("v=", NULL, digits_len),
+       [SDP_HDR_OWNER_IP4]             = SDP_HDR("o=", "IN IP4 ", epaddr_len),
+       [SDP_HDR_CONNECTION_IP4]        = SDP_HDR("c=", "IN IP4 ", epaddr_len),
+       [SDP_HDR_OWNER_IP6]             = SDP_HDR("o=", "IN IP6 ", epaddr_len),
+       [SDP_HDR_CONNECTION_IP6]        = SDP_HDR("c=", "IN IP6 ", epaddr_len),
+       [SDP_HDR_MEDIA]                 = SDP_HDR("m=", "audio ", digits_len),
+};
+
+/* Linear string search within SDP header values */
+static const char *ct_sdp_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')
+                       break;
+               if (strncmp(dptr, needle, len) == 0)
+                       return dptr;
+       }
+       return NULL;
+}
+
+/* Locate a SDP header (optionally a substring within the header value),
+ * optionally stopping at the first occurence of the term header, parse
+ * it and return the offset and length of the data we're interested in.
+ */
+int ct_sip_get_sdp_header(const struct nf_conn *ct, const char *dptr,
+                         unsigned int dataoff, unsigned int datalen,
+                         enum sdp_header_types type,
+                         enum sdp_header_types term,
+                         unsigned int *matchoff, unsigned int *matchlen)
+{
+       const struct sip_header *hdr = &ct_sdp_hdrs[type];
+       const struct sip_header *thdr = &ct_sdp_hdrs[term];
+       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;
+               }
+
+               if (term != SDP_HDR_UNSPEC &&
+                   limit - dptr >= thdr->len &&
+                   strnicmp(dptr, thdr->name, thdr->len) == 0)
+                       break;
+               else if (limit - dptr >= hdr->len &&
+                        strnicmp(dptr, hdr->name, hdr->len) == 0)
+                       dptr += hdr->len;
+               else
+                       continue;
+
+               *matchoff = dptr - start;
+               if (hdr->search) {
+                       dptr = ct_sdp_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;
+}
+EXPORT_SYMBOL_GPL(ct_sip_get_sdp_header);
+
 static int set_expected_rtp(struct sk_buff *skb,
                            const char **dptr, unsigned int *datalen,
                            union nf_inet_addr *addr, __be16 port)
        int ret = NF_ACCEPT;
        unsigned int matchoff, matchlen;
        u_int16_t port;
-       enum sip_header_pos pos;
+       enum sdp_header_types type;
        typeof(nf_nat_sip_hook) nf_nat_sip;
 
        /* No Data ? */
                goto out;
        }
        /* Get address and port from SDP packet. */
-       pos = family == AF_INET ? POS_CONNECTION_IP4 : POS_CONNECTION_IP6;
-       if (ct_sip_get_info(ct, dptr, datalen, &matchoff, &matchlen, pos) > 0) {
+       type = family == AF_INET ? SDP_HDR_CONNECTION_IP4 :
+                                  SDP_HDR_CONNECTION_IP6;
+       if (ct_sip_get_sdp_header(ct, dptr, 0, datalen, type, SDP_HDR_UNSPEC,
+                                 &matchoff, &matchlen) > 0) {
 
                /* We'll drop only if there are parse problems. */
                if (!parse_addr(ct, dptr + matchoff, NULL, &addr,
                        ret = NF_DROP;
                        goto out;
                }
-               if (ct_sip_get_info(ct, dptr, datalen, &matchoff, &matchlen,
-                                   POS_MEDIA) > 0) {
+               if (ct_sip_get_sdp_header(ct, dptr, 0, datalen,
+                                         SDP_HDR_MEDIA, SDP_HDR_UNSPEC,
+                                         &matchoff, &matchlen) > 0) {
 
                        port = simple_strtoul(dptr + matchoff, NULL, 10);
                        if (port < 1024) {