blob: d48c26ef3ed324be8ca4b74e8a962b7ddd6421b8 [file] [log] [blame]
/*
* Copyright (c) 2012, 2015, The Linux Foundation. All rights reserved.
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifdef KVER32
#include <linux/kconfig.h>
#include <generated/autoconf.h>
#else
#include <linux/autoconf.h>
#endif
#include <linux/netfilter_ipv4/ip_tables.h>
#include <net/netfilter/nf_nat.h>
#include <linux/netfilter/nf_nat.h>
#include <linux/netfilter/xt_multiport.h>
#include <linux/netfilter/xt_tcpudp.h>
#include <linux/netfilter/x_tables.h>
#include "nat_helper.h"
#include "lib/nat_helper_hsl.h"
#include "lib/nat_helper_dt.h"
#define nf_nat_ipv4_multi_range_compat \
nf_nat_ipv4_multi_range_compat
#define nf_nat_range nf_nat_ipv4_range
#define ipt_entry_target xt_entry_target
#define ipt_entry_match xt_entry_match
#define IPT_MATCH_ITERATE(e, fun, args...) \
({ \
unsigned int i; \
int ret = 0; \
struct xt_entry_match *m; \
\
for (i = sizeof(struct ipt_entry); \
i < (e)->target_offset; \
i += m->u.match_size) { \
m = (void *)e + i; \
ret = fun(m , ##args); \
if (ret != 0) \
break; \
} \
ret; \
})
#define IPT_ENTRY_ITERATE(entries, size, fun, args...) \
({ \
unsigned int k, j; \
int ret = 0; \
struct ipt_entry *e; \
\
for (k = 0, j = 0; k < (size); \
k += (e)->next_offset, j++) { \
e = (void *)(entries) + k; \
if (j < 0) \
continue; \
\
ret = fun(e , ##args); \
if (ret != 0) \
break; \
} \
ret; \
})
#define IPT_BUFFER_INIT_LEN 1000
#define NF_NAT_INIT_ENTRIES_NUM 5
static int
nat_ipt_set_ctl(struct sock *sk, int cmd, void __user * user, unsigned int len);
static int
nat_ipt_get_ctl(struct sock *sk, int cmd, void __user * user, int *len);
int nat_sockopts_init = 0;
/*those initial value will be overwrited by orignal iptables sockopts*/
static struct nf_sockopt_ops orgi_ipt_sockopts;
static struct nf_sockopt_ops tmp_ipt_sockopts =
{
/*pls check linux/in.h*/
#define IPT_TEMP_BASE_CTL 60
#define IPT_TEMP_SET_MAX (IPT_TEMP_BASE_CTL+1)
#define IPT_TEMP_GET_MAX (IPT_TEMP_BASE_CTL+2)
.pf = PF_INET,
.set_optmin = IPT_TEMP_BASE_CTL,
.set_optmax = IPT_TEMP_SET_MAX,
.set = nat_ipt_set_ctl,
.get_optmin = IPT_TEMP_BASE_CTL,
.get_optmax = IPT_TEMP_GET_MAX,
.get = nat_ipt_get_ctl
};
static struct nf_sockopt_ops *ipt_sockopts = NULL;
static uint32_t snat_seq = 0;
static uint32_t hw_nat_ipt_seq[NAT_HW_NUM] = {0};
static uint32_t hw_nat_pip_idx[NAT_HW_NUM] = {0};
static uint8_t *gbuffer, *sbuffer;
static unsigned int glen, slen;
static struct ipt_replace old_replace;
static void
nat_ipt_del(struct ipt_replace ireplace)
{
int i, j;
struct ipt_entry *gentry = NULL;
struct ipt_entry *sentry = NULL;
struct xt_entry_target *gtarget = NULL;
struct xt_entry_target *starget = NULL;
struct nf_nat_ipv4_multi_range_compat *grange = NULL;
struct nf_nat_ipv4_multi_range_compat *srange = NULL;
uint8_t *gptr, *sptr;
unsigned int oldnum = ireplace.num_counters;
unsigned int seq = 1;
gptr = gbuffer;
sptr = sbuffer;
HNAT_PRINTK("into nat_ipt_del\n");
for (i = oldnum; i >= 0; i--)//NF_NAT_INIT_ENTRIES_NUM; i--)
{
gentry = (struct ipt_entry *)gptr;
sentry = (struct ipt_entry *)sptr;
gtarget = (struct xt_entry_target *)((uint8_t *) gentry + gentry->target_offset);
starget = (struct xt_entry_target *)((uint8_t *) sentry + sentry->target_offset);
grange = (struct nf_nat_ipv4_multi_range_compat *)((uint8_t *) gtarget + sizeof (*gtarget));
srange = (struct nf_nat_ipv4_multi_range_compat *)((uint8_t *) starget + sizeof (*starget));
HNAT_PRINTK("(%d)isis_nat_del name %s:%s#####(%x:%x %x)###\n",
i, gtarget->u.user.name, starget->u.user.name,
gentry->ip.src.s_addr, gentry->ip.dst.s_addr,
grange->range[0].min.all);
if (strcmp(gtarget->u.user.name, starget->u.user.name))
{
/*if (!strcmp(gtarget->u.user.name, "DNAT")) {
if (gentry->ip.src.s_addr || !gentry->ip.dst.s_addr
|| grange->range[0].min.all)
return;
goto delete;
} else */
if (!strcmp(gtarget->u.user.name, "SNAT"))
{
if (!gentry->ip.src.s_addr || gentry->ip.dst.s_addr
|| grange->range[0].min.all)
return;
goto delete;
}
return;
} /*else if (!strcmp(gtarget->u.user.name, "DNAT")) {
if (memcmp(gentry, sentry, gentry->next_offset)) {
if (gentry->ip.src.s_addr || !gentry->ip.dst.s_addr
|| grange->range[0].min.all)
return;
goto delete;
}
} */else if (!strcmp(gtarget->u.user.name, "SNAT"))
{
if (memcmp(gentry, sentry, gentry->next_offset))
{
if (!gentry->ip.src.s_addr || gentry->ip.dst.s_addr
|| grange->range[0].min.all)
return;
goto delete;
}
}
gptr += gentry->next_offset;
sptr += gentry->next_offset;
if(!strcmp(gtarget->u.user.name, "SNAT"))
{
seq++;
}
}
HNAT_PRINTK("NONE to delete\n");
return;
delete:
HNAT_PRINTK("READY to delete one\n");
for (j = 0; j < NAT_HW_NUM; j++)
{
HNAT_PRINTK("ready [%d] (hw)%x:(sw)%x######\n",
j, hw_nat_ipt_seq[j], seq);
if (hw_nat_ipt_seq[j] == seq)
{
if(nat_hw_del_by_index(j) != 0)
{
return;
}
//public_ip_del(hw_nat_pip_idx[j]);
}
}
for(i = 0; i < NAT_HW_NUM; i++)
{
if(hw_nat_ipt_seq[i] > seq)
{
hw_nat_ipt_seq[i]--;
}
else if(hw_nat_ipt_seq[i] == seq)
{
hw_nat_ipt_seq[i]=0;
}
}
return;
}
static void
nat_ipt_to_hw_entry(struct ipt_entry *e,
nat_entry_t *nat)
{
#define P_ANY 0
#define P_TCP 6
#define P_UDP 17
struct ipt_entry_target *t = (struct ipt_entry_target *)ipt_get_target(e);
const struct nf_nat_ipv4_multi_range_compat *mr =
(struct nf_nat_ipv4_multi_range_compat *)(t->data);
const struct nf_nat_range *range = &mr->range[0];
uint32_t sip = ntohl(e->ip.src.s_addr);
uint32_t pip = ntohl(range->min_ip);
uint16_t proto = e->ip.proto;
memset((void *) nat, 0, sizeof (nat_entry_t));
nat->src_addr = sip;
nat->trans_addr = pip;
if (proto == P_TCP)
{
nat->flags = FAL_NAT_ENTRY_PROTOCOL_TCP;
}
else if (proto == P_UDP)
{
nat->flags = FAL_NAT_ENTRY_PROTOCOL_UDP;
}
else if (proto == P_ANY)
{
nat->flags = FAL_NAT_ENTRY_PROTOCOL_ANY;
}
}
static int
nat_ipt_hw_add(nat_entry_t *nat)
{
uint32_t index = 0;
if(nat_hw_add(nat) != 0)
{
return -1;
}
hw_nat_ipt_seq[nat->entry_id] = snat_seq;
HNAT_PRINTK("###nat_ipt_hw_add hw_nat_ipt_seq[%d]:%d###\n",
nat->entry_id, snat_seq);
hw_nat_pip_idx[nat->entry_id] = nat->trans_addr;
if(nat_hw_prv_base_can_update())
{
nat_hw_prv_base_set(nat->src_addr);
nat_hw_prv_base_update_disable();
}
if(nat_hw_pub_ip_add(nat->trans_addr, &index)!= 0)
{
return -1;
}
return 0;
}
static int
nat_ipt_hw_port_range_add(nat_entry_t *nat,
uint16_t port_start, uint16_t port_end,
struct xt_multiport *xport)
{
unsigned int i;
nat->flags |= FAL_NAT_ENTRY_PORT_CHECK;
if(xport)
{
//some discontinuous ports
for (i = 0; i < xport->count; i++)
{
nat->port_num = xport->ports[i];
nat->port_range = 1;
if(nat_ipt_hw_add(nat))
{
HNAT_PRINTK("isis_nat_add(xport:%d) fail!\n", nat->port_num);
return -1;
}
HNAT_PRINTK("(1)isis_nat_add(xport:%d) success\n", nat->port_num);
}
}
else
{
//one port or port range
uint16_t port_min, port_max;
for (i = port_start; i <= port_end; i+= NAT_HW_PORT_RANGE_MAX)
{
port_min = i;
if((port_end-port_min)>(NAT_HW_PORT_RANGE_MAX-1))
{
port_max = port_min+(NAT_HW_PORT_RANGE_MAX-1);
}
else
{
port_max = port_end;
}
nat->port_num = port_min;
nat->port_range = (port_max - port_min + 1);
if(nat_ipt_hw_add(nat))
{
HNAT_PRINTK("isis_nat_add(range port:%d~%d) fail!\n",
port_min, port_max);
return -1;
}
HNAT_PRINTK("(2)isis_nat_add(range port:%d~%d) success\n", port_min, port_max);
}
}
return 0;
}
static int
nat_ipt_check_none_matches(struct ipt_entry *e)
{
nat_entry_t nat = {0};
nat_ipt_to_hw_entry(e, &nat);
if(nat_ipt_hw_add(&nat) != 0)
{
HNAT_PRINTK("(1)isis_nat_add(none port)fail!\n");
return -1;
}
HNAT_PRINTK("(1)isis_nat_add(none port) success\n");
return 0;
}
static int
nat_ipt_check_matches(struct ipt_entry_match *m,
struct ipt_entry *e,
unsigned int *j)
{
int ret = 0;
nat_entry_t nat = {0};
uint16_t port_start = 0, port_end = 0;
struct xt_multiport *xport = NULL;
if(strcmp(m->u.user.name, "udp") == 0)
{
struct xt_udp *udpinfo = (struct xt_udp *)m->data;
port_start = udpinfo->spts[0];
port_end = udpinfo->spts[1];
}
else if(strcmp(m->u.user.name, "tcp") == 0)
{
struct xt_tcp *tcpinfo = (struct xt_tcp *)m->data;
port_start = tcpinfo->spts[0];
port_end = tcpinfo->spts[1];
}
else if(strcmp(m->u.user.name, "multiport") == 0)
{
struct xt_multiport xport_data = {0};
struct ipt_entry_target *t = ipt_get_target(e);
xport = &xport_data;
if(t->u.user.revision == 0)
{
xport = (struct xt_multiport *)m->data;
}
else if(t->u.user.revision == 1)
{
const struct xt_multiport_v1 *xportv1 =
(struct xt_multiport_v1 *)m->data;
memcpy(xport->ports, xportv1->ports, sizeof(xportv1->ports));
xport->count = xportv1->count;
}
if(xport->flags != XT_MULTIPORT_SOURCE)
{
memset(xport->ports, 0, sizeof(xport->ports));
}
}
else
{
(*j)++ ;
HNAT_PRINTK("###no support matches m->u.user.name:%s\n",
m->u.user.name);
return -1;
}
nat_ipt_to_hw_entry(e, &nat);
ret = nat_ipt_hw_port_range_add(&nat, port_start, port_end, xport);
(*j)++ ;
return ret;
}
//check netmask !=32
#define NAT_IPT_RULE_IS_FOR_NAPT(e) (((e)->ip.smsk.s_addr) != 0xffffffff)
#define NAT_IPT_RULE_IS_NONE_MATCHES(e) (((e)->target_offset) == \
(sizeof(struct ipt_entry)))
static int
nat_ipt_find_check_entry(struct ipt_entry *e, unsigned int underflow,
unsigned int *i)
{
int ret = 0;
static uint16_t next_offset = 0;
struct ipt_entry_target *t = ipt_get_target(e);
if(*i == 0)
{
snat_seq = 0;
next_offset = e->next_offset;
}
else
{
next_offset += e->next_offset;
}
if (!strcmp(t->u.user.name, "SNAT"))
{
++snat_seq;
if(NAT_IPT_RULE_IS_FOR_NAPT(e))
{
HNAT_PRINTK("this ipt rule only for HW napt offload\n");
}
else
{
/*for basic nat offload*/
HNAT_PRINTK("[%d]next_offset:%d underflow:%d\n",
*i, next_offset, underflow);
if(next_offset == underflow) //new one
{
if(NAT_IPT_RULE_IS_NONE_MATCHES(e))
{
/*none matches*/
ret = nat_ipt_check_none_matches(e);
}
else
{
unsigned int j = 0;
/*iterate matches*/
ret = IPT_MATCH_ITERATE(e, nat_ipt_check_matches, e, &j);
}
}
}
}
(*i)++ ;
return ret;
}
static void
nat_ipt_data_cleanup(void)
{
if (gbuffer)
kfree(gbuffer);
gbuffer = NULL;
if (sbuffer)
kfree(sbuffer);
sbuffer = NULL;
}
static void
nat_ipt_data_init(void)
{
/*alloc initial set buffer*/
sbuffer = kmalloc(IPT_BUFFER_INIT_LEN, GFP_ATOMIC);
if(sbuffer)
{
memset(sbuffer, 0, IPT_BUFFER_INIT_LEN);
slen = IPT_BUFFER_INIT_LEN;
}
else
{
HNAT_PRINTK("%s sbuffer memory allocate fail\n", __func__);
}
/*alloc initial get buffer*/
gbuffer = kmalloc(IPT_BUFFER_INIT_LEN, GFP_ATOMIC);
if(gbuffer)
{
memset(gbuffer, 0, IPT_BUFFER_INIT_LEN);
glen = IPT_BUFFER_INIT_LEN;
}
else
{
HNAT_PRINTK("%s gbuffer memory allocate fail\n", __func__);
}
/*set initial underflow: nf_nat_rule.c*/
memset(&old_replace, 0, sizeof (old_replace));
/*record ipt rule(SNAT) sequence for hw nat*/
memset(hw_nat_ipt_seq, 0, NAT_HW_NUM);
/*record ipt rule(SNAT) pubip index for hw nat*/
memset(hw_nat_pip_idx, 0, NAT_HW_NUM);
}
static void
nat_ipt_flush(void)
{
napt_hw_flush();
nat_hw_flush();
nat_ipt_data_cleanup();
nat_ipt_data_init();
HNAT_PRINTK("------(nat flush done)------\n");
}
static void
nat_ipt_add(struct ipt_replace ireplace)
{
unsigned int i = 0;
IPT_ENTRY_ITERATE(sbuffer,
ireplace.size,
nat_ipt_find_check_entry,
ireplace.underflow[NF_INET_POST_ROUTING],
&i);
}
static int
nat_ipt_hook_type_check(struct ipt_replace ireplace)
{
int ret = -1;
HNAT_PRINTK("------we only support SNAT-----\n");
if((old_replace.underflow[NF_INET_POST_ROUTING]-
old_replace.hook_entry[NF_INET_POST_ROUTING]) !=
(ireplace.underflow[NF_INET_POST_ROUTING]-
ireplace.hook_entry[NF_INET_POST_ROUTING]))
{
HNAT_PRINTK("------this is POSTROUTING(SNAT):yes!------\n");
ret = 0;
}
else if ((old_replace.underflow[NF_INET_PRE_ROUTING]-
old_replace.hook_entry[NF_INET_PRE_ROUTING]) !=
(ireplace.underflow[NF_INET_PRE_ROUTING]-
ireplace.hook_entry[NF_INET_PRE_ROUTING]))
{
HNAT_PRINTK("------this is PREROUTING(DNAT):no!------\n");
}
else if((old_replace.underflow[NF_INET_LOCAL_OUT]-
old_replace.hook_entry[NF_INET_LOCAL_OUT]) !=
(ireplace.underflow[NF_INET_LOCAL_OUT]-
ireplace.hook_entry[NF_INET_LOCAL_OUT]))
{
HNAT_PRINTK("------this is OUTPUT:no!------\n");
}
else
{
HNAT_PRINTK("------this is UNKNOW:no!------\n");
}
return ret;
}
static void
nat_ipt_rules_cp_from_user(void **buf, unsigned int *buf_len,
void __user *user, unsigned int user_len)
{
if((*buf == 0) || (user == 0))
{
return;
}
if (*buf_len < user_len)
{
if(*buf)
{
kfree(*buf);
*buf = kmalloc(user_len, GFP_ATOMIC);
if(*buf == NULL)
{
HNAT_PRINTK("%s memory allocate fail\n", __func__);
return;
}
*buf_len = user_len;
}
}
HNAT_PRINTK("(2)nat_ipt_rules_cp_from_user *buf:%x user:%x user_len:%d\n",
(unsigned int)(uintptr_t)*buf, (unsigned int)(uintptr_t)user, user_len);
copy_from_user(*buf, user, user_len);
return;
}
static int
nat_ipt_set_ctl(struct sock *sk, int cmd, void __user * user, unsigned int len)
{
struct ipt_replace ireplace;
memset(&ireplace, 0, sizeof(ireplace));
HNAT_PRINTK("NAT set hook\n");
if (cmd != IPT_SO_SET_REPLACE)
goto normal;
copy_from_user(&ireplace, user, sizeof (ireplace));
if (strcmp(ireplace.name, "nat")
|| (ireplace.num_entries == ireplace.num_counters))
{
HNAT_PRINTK("none NAT or no new entry %d", ireplace.num_entries);
goto normal;
}
if (ireplace.num_entries == NF_NAT_INIT_ENTRIES_NUM)
{
nat_ipt_flush();
goto normal;
}
if (nat_ipt_hook_type_check(ireplace) != 0)
{
goto normal;
}
nat_ipt_rules_cp_from_user((void **)&sbuffer, &slen,
(user + sizeof (ireplace)),
ireplace.size);
if (ireplace.num_entries > ireplace.num_counters)
{
nat_ipt_add(ireplace);
}
else
{
nat_ipt_del(ireplace);
}
normal:
/*save old_replace for next hook type check*/
old_replace = ireplace;
return orgi_ipt_sockopts.set(sk, cmd, user, len);
}
static int
nat_ipt_get_ctl(struct sock *sk, int cmd, void __user * user, int *len)
{
int k = orgi_ipt_sockopts.get(sk, cmd, user, len);
if (cmd == IPT_SO_GET_ENTRIES)
{
struct ipt_get_entries entries;
copy_from_user(&entries, user, sizeof (entries));
nat_ipt_rules_cp_from_user((void **)&gbuffer, &glen,
(user + sizeof (struct ipt_get_entries)),
(*len - sizeof (entries)));
}
return k;
}
void
nat_ipt_sockopts_replace(void)
{
int ret = 0;
/*register an temp sockopts to find ipt_sockopts*/
if((ret = nf_register_sockopt(&tmp_ipt_sockopts)) < 0) {
return;
}
list_for_each_entry(ipt_sockopts, tmp_ipt_sockopts.list.next, list)
{
if (ipt_sockopts->set_optmin == IPT_BASE_CTL)
{
nat_sockopts_init = 1;
break;
}
}
nf_unregister_sockopt(&tmp_ipt_sockopts);
if(!nat_sockopts_init)
return;
/*save orginal ipt_sockopts*/
orgi_ipt_sockopts = *ipt_sockopts;
/*replace ipt_sockopts with our opts*/
ipt_sockopts->set = nat_ipt_set_ctl;
ipt_sockopts->get = nat_ipt_get_ctl;
}
static void
nat_ipt_sockopts_restore(void)
{
ipt_sockopts->set = orgi_ipt_sockopts.set;
ipt_sockopts->get = orgi_ipt_sockopts.get;
}
void
nat_ipt_helper_init(void)
{
nat_ipt_sockopts_replace();
nat_ipt_data_init();
}
void
nat_ipt_helper_exit(void)
{
nat_ipt_sockopts_restore();
nat_ipt_data_cleanup();
nat_hw_flush();
}