Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 1 | #define KMSG_COMPONENT "IPVS" |
| 2 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt |
| 3 | |
| 4 | #include <linux/module.h> |
| 5 | #include <linux/kernel.h> |
| 6 | |
| 7 | #include <net/ip_vs.h> |
| 8 | #include <net/netfilter/nf_conntrack.h> |
| 9 | #include <linux/netfilter/nf_conntrack_sip.h> |
| 10 | |
Simon Horman | a91fd26 | 2010-10-13 21:22:35 +0200 | [diff] [blame] | 11 | #ifdef CONFIG_IP_VS_DEBUG |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 12 | static const char *ip_vs_dbg_callid(char *buf, size_t buf_len, |
| 13 | const char *callid, size_t callid_len, |
| 14 | int *idx) |
| 15 | { |
Simon Horman | 9c37510 | 2013-04-19 10:33:59 +0900 | [diff] [blame] | 16 | size_t max_len = 64; |
| 17 | size_t len = min3(max_len, callid_len, buf_len - *idx - 1); |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 18 | memcpy(buf + *idx, callid, len); |
| 19 | buf[*idx+len] = '\0'; |
| 20 | *idx += len + 1; |
| 21 | return buf + *idx - len; |
| 22 | } |
| 23 | |
| 24 | #define IP_VS_DEBUG_CALLID(callid, len) \ |
| 25 | ip_vs_dbg_callid(ip_vs_dbg_buf, sizeof(ip_vs_dbg_buf), \ |
| 26 | callid, len, &ip_vs_dbg_idx) |
Simon Horman | a91fd26 | 2010-10-13 21:22:35 +0200 | [diff] [blame] | 27 | #endif |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 28 | |
| 29 | static int get_callid(const char *dptr, unsigned int dataoff, |
| 30 | unsigned int datalen, |
| 31 | unsigned int *matchoff, unsigned int *matchlen) |
| 32 | { |
| 33 | /* Find callid */ |
| 34 | while (1) { |
| 35 | int ret = ct_sip_get_header(NULL, dptr, dataoff, datalen, |
| 36 | SIP_HDR_CALL_ID, matchoff, |
| 37 | matchlen); |
| 38 | if (ret > 0) |
| 39 | break; |
| 40 | if (!ret) |
Hans Schillstrom | f7a1dd6 | 2013-04-27 20:06:14 +0200 | [diff] [blame] | 41 | return -EINVAL; |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 42 | dataoff += *matchoff; |
| 43 | } |
| 44 | |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 45 | /* Too large is useless */ |
| 46 | if (*matchlen > IP_VS_PEDATA_MAXLEN) |
| 47 | return -EINVAL; |
| 48 | |
| 49 | /* SIP headers are always followed by a line terminator */ |
| 50 | if (*matchoff + *matchlen == datalen) |
| 51 | return -EINVAL; |
| 52 | |
| 53 | /* RFC 2543 allows lines to be terminated with CR, LF or CRLF, |
| 54 | * RFC 3261 allows only CRLF, we support both. */ |
| 55 | if (*(dptr + *matchoff + *matchlen) != '\r' && |
| 56 | *(dptr + *matchoff + *matchlen) != '\n') |
| 57 | return -EINVAL; |
| 58 | |
| 59 | IP_VS_DBG_BUF(9, "SIP callid %s (%d bytes)\n", |
| 60 | IP_VS_DEBUG_CALLID(dptr + *matchoff, *matchlen), |
| 61 | *matchlen); |
| 62 | return 0; |
| 63 | } |
| 64 | |
| 65 | static int |
| 66 | ip_vs_sip_fill_param(struct ip_vs_conn_param *p, struct sk_buff *skb) |
| 67 | { |
| 68 | struct ip_vs_iphdr iph; |
| 69 | unsigned int dataoff, datalen, matchoff, matchlen; |
| 70 | const char *dptr; |
Hans Schillstrom | 3716522 | 2010-11-19 14:25:09 +0100 | [diff] [blame] | 71 | int retc; |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 72 | |
Arnd Bergmann | 3f20efb | 2016-01-27 14:52:02 +0100 | [diff] [blame] | 73 | retc = ip_vs_fill_iph_skb(p->af, skb, false, &iph); |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 74 | |
| 75 | /* Only useful with UDP */ |
Arnd Bergmann | 3f20efb | 2016-01-27 14:52:02 +0100 | [diff] [blame] | 76 | if (!retc || iph.protocol != IPPROTO_UDP) |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 77 | return -EINVAL; |
Jesper Dangaard Brouer | 92eec78 | 2012-09-26 14:07:33 +0200 | [diff] [blame] | 78 | /* todo: IPv6 fragments: |
| 79 | * I think this only should be done for the first fragment. /HS |
| 80 | */ |
Jiri Pirko | 6aafeef | 2013-11-06 17:52:20 +0100 | [diff] [blame] | 81 | dataoff = iph.len + sizeof(struct udphdr); |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 82 | |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 83 | if (dataoff >= skb->len) |
| 84 | return -EINVAL; |
Jesper Dangaard Brouer | 92eec78 | 2012-09-26 14:07:33 +0200 | [diff] [blame] | 85 | retc = skb_linearize(skb); |
| 86 | if (retc < 0) |
Hans Schillstrom | 3716522 | 2010-11-19 14:25:09 +0100 | [diff] [blame] | 87 | return retc; |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 88 | dptr = skb->data + dataoff; |
| 89 | datalen = skb->len - dataoff; |
| 90 | |
Marco Angaroni | 7617a24 | 2016-03-05 12:10:02 +0100 | [diff] [blame] | 91 | if (get_callid(dptr, 0, datalen, &matchoff, &matchlen)) |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 92 | return -EINVAL; |
| 93 | |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 94 | /* N.B: pe_data is only set on success, |
| 95 | * this allows fallback to the default persistence logic on failure |
| 96 | */ |
Shan Wei | 6060c74 | 2011-03-07 10:11:34 +0800 | [diff] [blame] | 97 | p->pe_data = kmemdup(dptr + matchoff, matchlen, GFP_ATOMIC); |
| 98 | if (!p->pe_data) |
| 99 | return -ENOMEM; |
| 100 | |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 101 | p->pe_data_len = matchlen; |
| 102 | |
| 103 | return 0; |
| 104 | } |
| 105 | |
| 106 | static bool ip_vs_sip_ct_match(const struct ip_vs_conn_param *p, |
| 107 | struct ip_vs_conn *ct) |
| 108 | |
| 109 | { |
Rusty Russell | 3db1cd5 | 2011-12-19 13:56:45 +0000 | [diff] [blame] | 110 | bool ret = false; |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 111 | |
| 112 | if (ct->af == p->af && |
| 113 | ip_vs_addr_equal(p->af, p->caddr, &ct->caddr) && |
| 114 | /* protocol should only be IPPROTO_IP if |
| 115 | * d_addr is a fwmark */ |
| 116 | ip_vs_addr_equal(p->protocol == IPPROTO_IP ? AF_UNSPEC : p->af, |
| 117 | p->vaddr, &ct->vaddr) && |
| 118 | ct->vport == p->vport && |
| 119 | ct->flags & IP_VS_CONN_F_TEMPLATE && |
| 120 | ct->protocol == p->protocol && |
| 121 | ct->pe_data && ct->pe_data_len == p->pe_data_len && |
| 122 | !memcmp(ct->pe_data, p->pe_data, p->pe_data_len)) |
Rusty Russell | 3db1cd5 | 2011-12-19 13:56:45 +0000 | [diff] [blame] | 123 | ret = true; |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 124 | |
| 125 | IP_VS_DBG_BUF(9, "SIP template match %s %s->%s:%d %s\n", |
| 126 | ip_vs_proto_name(p->protocol), |
| 127 | IP_VS_DEBUG_CALLID(p->pe_data, p->pe_data_len), |
| 128 | IP_VS_DBG_ADDR(p->af, p->vaddr), ntohs(p->vport), |
| 129 | ret ? "hit" : "not hit"); |
| 130 | |
| 131 | return ret; |
| 132 | } |
| 133 | |
| 134 | static u32 ip_vs_sip_hashkey_raw(const struct ip_vs_conn_param *p, |
| 135 | u32 initval, bool inverse) |
| 136 | { |
| 137 | return jhash(p->pe_data, p->pe_data_len, initval); |
| 138 | } |
| 139 | |
| 140 | static int ip_vs_sip_show_pe_data(const struct ip_vs_conn *cp, char *buf) |
| 141 | { |
| 142 | memcpy(buf, cp->pe_data, cp->pe_data_len); |
| 143 | return cp->pe_data_len; |
| 144 | } |
| 145 | |
Marco Angaroni | 39b9722 | 2016-04-05 18:26:29 +0200 | [diff] [blame] | 146 | static struct ip_vs_conn * |
| 147 | ip_vs_sip_conn_out(struct ip_vs_service *svc, |
| 148 | struct ip_vs_dest *dest, |
| 149 | struct sk_buff *skb, |
| 150 | const struct ip_vs_iphdr *iph, |
| 151 | __be16 dport, |
| 152 | __be16 cport) |
| 153 | { |
| 154 | if (likely(iph->protocol == IPPROTO_UDP)) |
| 155 | return ip_vs_new_conn_out(svc, dest, skb, iph, dport, cport); |
| 156 | /* currently no need to handle other than UDP */ |
| 157 | return NULL; |
| 158 | } |
| 159 | |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 160 | static struct ip_vs_pe ip_vs_sip_pe = |
| 161 | { |
| 162 | .name = "sip", |
| 163 | .refcnt = ATOMIC_INIT(0), |
| 164 | .module = THIS_MODULE, |
| 165 | .n_list = LIST_HEAD_INIT(ip_vs_sip_pe.n_list), |
| 166 | .fill_param = ip_vs_sip_fill_param, |
| 167 | .ct_match = ip_vs_sip_ct_match, |
| 168 | .hashkey_raw = ip_vs_sip_hashkey_raw, |
| 169 | .show_pe_data = ip_vs_sip_show_pe_data, |
Marco Angaroni | 39b9722 | 2016-04-05 18:26:29 +0200 | [diff] [blame] | 170 | .conn_out = ip_vs_sip_conn_out, |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 171 | }; |
| 172 | |
| 173 | static int __init ip_vs_sip_init(void) |
| 174 | { |
| 175 | return register_ip_vs_pe(&ip_vs_sip_pe); |
| 176 | } |
| 177 | |
| 178 | static void __exit ip_vs_sip_cleanup(void) |
| 179 | { |
| 180 | unregister_ip_vs_pe(&ip_vs_sip_pe); |
Julian Anastasov | 60b6aa3 | 2013-03-21 11:58:09 +0200 | [diff] [blame] | 181 | synchronize_rcu(); |
Simon Horman | 758ff03 | 2010-08-22 21:37:55 +0900 | [diff] [blame] | 182 | } |
| 183 | |
| 184 | module_init(ip_vs_sip_init); |
| 185 | module_exit(ip_vs_sip_cleanup); |
| 186 | MODULE_LICENSE("GPL"); |