/* * Copyright (c) 2014 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Bill Yuan * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ip_fw3_nat.h" static struct lock nat_lock; extern struct ipfw_nat_context *ipfw_nat_ctx; extern struct ipfw_context *ipfw_ctx[MAXCPU]; extern ipfw_nat_cfg_t *ipfw_nat_cfg_ptr; extern ipfw_nat_cfg_t *ipfw_nat_del_ptr; extern ipfw_nat_cfg_t *ipfw_nat_flush_ptr; extern ipfw_nat_cfg_t *ipfw_nat_get_cfg_ptr; extern ipfw_nat_cfg_t *ipfw_nat_get_log_ptr; typedef int ipfw_nat_t(struct ip_fw_args *, struct cfg_nat *, struct mbuf *); int ipfw_nat(struct ip_fw_args *args, struct cfg_nat *t, struct mbuf *m); int ipfw_nat_cfg(struct sockopt *sopt); int ipfw_nat_del(struct sockopt *sopt); int ipfw_nat_flush(struct sockopt *sopt); void check_nat(int *cmd_ctl, int *cmd_val, struct ip_fw_args **args, struct ip_fw **f, ipfw_insn *cmd, uint16_t ip_len); void check_nat(int *cmd_ctl, int *cmd_val, struct ip_fw_args **args, struct ip_fw **f, ipfw_insn *cmd, uint16_t ip_len) { if ((*args)->eh != NULL) { *cmd_ctl = IP_FW_CTL_NO; *cmd_val = IP_FW_NOT_MATCH; return; } struct cfg_nat *t; int nat_id; (*args)->rule = *f; lockmgr(&nat_lock, LK_SHARED); t = ((ipfw_insn_nat *)cmd)->nat; if (t == NULL) { nat_id = cmd->arg1; LOOKUP_NAT((*ipfw_nat_ctx), nat_id, t); if (t == NULL) { lockmgr(&nat_lock, LK_RELEASE); *cmd_val = IP_FW_DENY; *cmd_ctl = IP_FW_CTL_DONE; return; } ((ipfw_insn_nat *)cmd)->nat = t; } *cmd_val = ipfw_nat(*args, t, (*args)->m); lockmgr(&nat_lock, LK_RELEASE); *cmd_ctl = IP_FW_CTL_NAT; } static void del_redir_spool_cfg(struct cfg_nat *n, struct redir_chain *head) { struct cfg_redir *r, *tmp_r; struct cfg_spool *s, *tmp_s; int i, num; LIST_FOREACH_MUTABLE(r, head, _next, tmp_r) { num = 1; /* Number of alias_link to delete. */ switch (r->mode) { case REDIR_PORT: num = r->pport_cnt; /* FALLTHROUGH */ case REDIR_ADDR: case REDIR_PROTO: /* Delete all libalias redirect entry. */ for (i = 0; i < num; i++) LibAliasRedirectDelete(n->lib, r->alink[i]); /* Del spool cfg if any. */ LIST_FOREACH_MUTABLE(s, &r->spool_chain, _next, tmp_s) { LIST_REMOVE(s, _next); kfree(s, M_IPFW_NAT); } kfree(r->alink, M_IPFW_NAT); LIST_REMOVE(r, _next); kfree(r, M_IPFW_NAT); break; default: kprintf("unknown redirect mode: %u\n", r->mode); /* XXX - panic?!?!? */ break; } } } static int add_redir_spool_cfg(char *buf, struct cfg_nat *ptr) { struct cfg_redir *r, *ser_r; struct cfg_spool *s, *ser_s; int cnt, off, i; char *panic_err; for (cnt = 0, off = 0; cnt < ptr->redir_cnt; cnt++) { ser_r = (struct cfg_redir *)&buf[off]; r = kmalloc(SOF_REDIR, M_IPFW_NAT, M_WAITOK | M_ZERO); memcpy(r, ser_r, SOF_REDIR); LIST_INIT(&r->spool_chain); off += SOF_REDIR; r->alink = kmalloc(sizeof(struct alias_link *) * r->pport_cnt, M_IPFW_NAT, M_WAITOK | M_ZERO); switch (r->mode) { case REDIR_ADDR: r->alink[0] = LibAliasRedirectAddr(ptr->lib, r->laddr, r->paddr); break; case REDIR_PORT: for (i = 0 ; i < r->pport_cnt; i++) { /* * If remotePort is all ports * set it to 0. */ u_short remotePortCopy = r->rport + i; if (r->rport_cnt == 1 && r->rport == 0) remotePortCopy = 0; r->alink[i] = LibAliasRedirectPort(ptr->lib, r->laddr,htons(r->lport + i), r->raddr,htons(remotePortCopy), r->paddr,htons(r->pport + i), r->proto); if (r->alink[i] == NULL) { r->alink[0] = NULL; break; } } break; case REDIR_PROTO: r->alink[0] = LibAliasRedirectProto(ptr->lib, r->laddr, r->raddr, r->paddr, r->proto); break; default: kprintf("unknown redirect mode: %u\n", r->mode); break; } if (r->alink[0] == NULL) { panic_err = "LibAliasRedirect* returned NULL"; goto bad; } else /* LSNAT handling. */ for (i = 0; i < r->spool_cnt; i++) { ser_s = (struct cfg_spool *)&buf[off]; s = kmalloc(SOF_REDIR, M_IPFW_NAT, M_WAITOK | M_ZERO); memcpy(s, ser_s, SOF_SPOOL); LibAliasAddServer(ptr->lib, r->alink[0], s->addr, htons(s->port)); off += SOF_SPOOL; /* Hook spool entry. */ HOOK_SPOOL(&r->spool_chain, s); } /* And finally hook this redir entry. */ HOOK_REDIR(&ptr->redir_chain, r); } return 1; bad: /* something really bad happened: panic! */ panic("%s\n", panic_err); } static int ipfw_nat_get_cfg(struct sockopt *sopt) { uint8_t *data; struct cfg_nat *n; struct cfg_redir *r; struct cfg_spool *s; int nat_cnt, off, nat_cfg_size; size_t size; nat_cnt = 0; nat_cfg_size = 0; off = sizeof(nat_cnt); size = sopt->sopt_valsize; data = sopt->sopt_val; lockmgr(&nat_lock, LK_SHARED); /* count the size of nat cfg */ LIST_FOREACH(n, &((*ipfw_nat_ctx).nat), _next) { nat_cfg_size += SOF_NAT; } LIST_FOREACH(n, &((*ipfw_nat_ctx).nat), _next) { nat_cnt++; if (off + SOF_NAT < size) { bcopy(n, &data[off], SOF_NAT); off += SOF_NAT; LIST_FOREACH(r, &n->redir_chain, _next) { if (off + SOF_REDIR < size) { bcopy(r, &data[off], SOF_REDIR); off += SOF_REDIR; LIST_FOREACH(s, &r->spool_chain, _next) { if (off + SOF_SPOOL < size) { bcopy(s, &data[off], SOF_SPOOL); off += SOF_SPOOL; } else goto nospace; } } else goto nospace; } } else goto nospace; } bcopy(&nat_cnt, data, sizeof(nat_cnt)); sopt->sopt_valsize = nat_cfg_size; lockmgr(&nat_lock, LK_RELEASE); return 0; nospace: lockmgr(&nat_lock, LK_RELEASE); bzero(sopt->sopt_val, sopt->sopt_valsize); sopt->sopt_valsize = nat_cfg_size; return 0; } static int ipfw_nat_get_log(struct sockopt *sopt) { struct cfg_nat *t; struct alias_link *lnk; struct libalias *la; size_t sopt_size, all_lnk_size = 0; int i, *nat_id; struct ipfw_ioc_nat_state *nat_state; nat_id = (int *)(sopt->sopt_val); sopt_size = sopt->sopt_valsize; lockmgr(&nat_lock, LK_SHARED); LOOKUP_NAT((*ipfw_nat_ctx), *nat_id, t); if (t != NULL) { nat_state = (struct ipfw_ioc_nat_state *)sopt->sopt_val; la = t->lib; LIBALIAS_LOCK_ASSERT(la); for (i = 0; i < LINK_TABLE_OUT_SIZE; i ++) { LIST_FOREACH(lnk, &la->linkTableOut[i], list_out) { all_lnk_size += sizeof(*nat_state); if (all_lnk_size > sopt_size) goto nospace; nat_state->src_addr = lnk->src_addr; nat_state->dst_addr = lnk->dst_addr; nat_state->alias_addr = lnk->alias_addr; nat_state->src_port = lnk->src_port; nat_state->dst_port = lnk->dst_port; nat_state->alias_port = lnk->alias_port; nat_state->link_type = lnk->link_type; nat_state->timestamp = lnk->timestamp; nat_state++; } } } sopt->sopt_valsize = all_lnk_size; lockmgr(&nat_lock, LK_RELEASE); return 0; nospace: lockmgr(&nat_lock, LK_RELEASE); return 0; } int ipfw_nat(struct ip_fw_args *args, struct cfg_nat *t, struct mbuf *m) { struct mbuf *mcl; struct ip *ip; int ldt, retval; char *c; ldt = 0; retval = 0; if ((mcl = m_megapullup(m, m->m_pkthdr.len)) ==NULL) goto badnat; ip = mtod(mcl, struct ip *); if (args->eh == NULL) { ip->ip_len = htons(ip->ip_len); ip->ip_off = htons(ip->ip_off); } if (mcl->m_pkthdr.rcvif == NULL && mcl->m_pkthdr.csum_flags & CSUM_DELAY_DATA) ldt = 1; c = mtod(mcl, char *); if (args->oif == NULL) retval = LibAliasIn(t->lib, c, mcl->m_len + M_TRAILINGSPACE(mcl)); else retval = LibAliasOut(t->lib, c, mcl->m_len + M_TRAILINGSPACE(mcl)); if (retval != PKT_ALIAS_OK && retval != PKT_ALIAS_FOUND_HEADER_FRAGMENT) { /* XXX - should i add some logging? */ m_free(mcl); badnat: args->m = NULL; return IP_FW_DENY; } mcl->m_pkthdr.len = mcl->m_len = ntohs(ip->ip_len); if ((ip->ip_off & htons(IP_OFFMASK)) == 0 && ip->ip_p == IPPROTO_TCP) { struct tcphdr *th; th = (struct tcphdr *)(ip + 1); if (th->th_x2){ ldt = 1; } } if (ldt) { struct tcphdr *th; struct udphdr *uh; u_short cksum; ip->ip_len = ntohs(ip->ip_len); cksum = in_pseudo( ip->ip_src.s_addr, ip->ip_dst.s_addr, htons(ip->ip_p + ip->ip_len - (ip->ip_hl << 2)) ); switch (ip->ip_p) { case IPPROTO_TCP: th = (struct tcphdr *)(ip + 1); th->th_x2 = 0; th->th_sum = cksum; mcl->m_pkthdr.csum_data = offsetof(struct tcphdr, th_sum); break; case IPPROTO_UDP: uh = (struct udphdr *)(ip + 1); uh->uh_sum = cksum; mcl->m_pkthdr.csum_data = offsetof(struct udphdr, uh_sum); break; } /* * No hw checksum offloading: do it * by ourself. */ if ((mcl->m_pkthdr.csum_flags & CSUM_DELAY_DATA) == 0) { in_delayed_cksum(mcl); mcl->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA; } ip->ip_len = htons(ip->ip_len); } if (args->eh == NULL) { ip->ip_len = ntohs(ip->ip_len); ip->ip_off = ntohs(ip->ip_off); } args->m = mcl; return IP_FW_NAT; } int ipfw_nat_cfg(struct sockopt *sopt) { struct cfg_nat *ptr, *ser_n; char *buf; buf = kmalloc(sopt->sopt_valsize, M_IPFW_NAT, M_WAITOK | M_ZERO); sooptcopyin(sopt, buf, sopt->sopt_valsize, sizeof(struct cfg_nat)); ser_n = (struct cfg_nat *)(sopt->sopt_val); /* * Find/create nat rule. */ lockmgr(&nat_lock, LK_EXCLUSIVE); LOOKUP_NAT((*ipfw_nat_ctx), ser_n->id, ptr); if (ptr == NULL) { /* New rule: allocate and init new instance. */ ptr = kmalloc(sizeof(struct cfg_nat), M_IPFW_NAT, M_WAITOK | M_ZERO); ptr->lib = LibAliasInit(NULL); if (ptr->lib == NULL) { kfree(ptr, M_IPFW_NAT); kfree(buf, M_IPFW_NAT); lockmgr(&nat_lock, LK_RELEASE); return EINVAL; } LIST_INIT(&ptr->redir_chain); } else { /* XXX TODO Entry already exists */ goto done; } /* * Basic nat configuration. */ ptr->id = ser_n->id; /* * XXX - what if this rule doesn't nat any ip and just * redirect? * do we set aliasaddress to 0.0.0.0? */ ptr->ip = ser_n->ip; ptr->redir_cnt = ser_n->redir_cnt; ptr->mode = ser_n->mode; LibAliasSetMode(ptr->lib, ser_n->mode, ser_n->mode); LibAliasSetAddress(ptr->lib, ptr->ip); memcpy(ptr->if_name, ser_n->if_name, IF_NAMESIZE); /* Add new entries. */ add_redir_spool_cfg(&buf[(sizeof(struct cfg_nat))], ptr); HOOK_NAT(&(ipfw_nat_ctx->nat), ptr); done: lockmgr(&nat_lock, LK_RELEASE); kfree(buf, M_IPFW_NAT); return 0; } int ipfw_nat_del(struct sockopt *sopt) { struct ipfw_context *ctx; struct cfg_nat *n, *tmp; struct ip_fw *f; ipfw_insn *cmd; int *i, cpu; i = sopt->sopt_val; lockmgr(&nat_lock, LK_EXCLUSIVE); LOOKUP_NAT((*ipfw_nat_ctx), *i, n); if (n == NULL) { lockmgr(&nat_lock, LK_RELEASE); return EINVAL; } /* * stop deleting when this cfg_nat was in use in ipfw_ctx */ for (cpu = 0; cpu < ncpus; cpu++) { ctx = ipfw_ctx[cpu]; for (f = ctx->ipfw_rule_chain; f; f = f->next) { cmd = ACTION_PTR(f); if ((int)cmd->module == MODULE_NAT_ID && (int)cmd->opcode == O_NAT_NAT) { tmp = ((ipfw_insn_nat *)cmd)->nat; if (tmp != NULL && tmp->id == n->id) { lockmgr(&nat_lock, LK_RELEASE); return EINVAL; } } } } UNHOOK_NAT(n); del_redir_spool_cfg(n, &n->redir_chain); LibAliasUninit(n->lib); kfree(n, M_IPFW_NAT); lockmgr(&nat_lock, LK_RELEASE); return 0; } int ipfw_nat_flush(struct sockopt *sopt) { struct ipfw_context *ctx; struct cfg_nat *ptr, *tmp; struct ip_fw *f; ipfw_insn *cmd; int cpu; /* * stop flushing when any cfg_nat was in use in ipfw_ctx */ for (cpu = 0; cpu < ncpus; cpu++) { ctx = ipfw_ctx[cpu]; for (f = ctx->ipfw_rule_chain; f; f = f->next) { cmd = ACTION_PTR(f); if ((int)cmd->module == MODULE_NAT_ID && (int)cmd->opcode == O_NAT_NAT) { return EINVAL; } } } lockmgr(&nat_lock, LK_EXCLUSIVE); LIST_FOREACH_MUTABLE(ptr, &(ipfw_nat_ctx->nat), _next, tmp) { LIST_REMOVE(ptr, _next); del_redir_spool_cfg(ptr, &ptr->redir_chain); LibAliasUninit(ptr->lib); kfree(ptr, M_IPFW_NAT); } lockmgr(&nat_lock, LK_RELEASE); return 0; } static int ipfw_nat_init(void) { lockinit(&nat_lock, "ipfw3 nat lock", 0, 0); register_ipfw_module(MODULE_NAT_ID, MODULE_NAT_NAME); register_ipfw_filter_funcs(MODULE_NAT_ID, O_NAT_NAT, (filter_func)check_nat); ipfw_nat_cfg_ptr = ipfw_nat_cfg; ipfw_nat_del_ptr = ipfw_nat_del; ipfw_nat_flush_ptr = ipfw_nat_flush; ipfw_nat_get_cfg_ptr = ipfw_nat_get_cfg; ipfw_nat_get_log_ptr = ipfw_nat_get_log; return 0; } static int ipfw_nat_stop(void) { struct cfg_nat *ptr, *ptr_temp; lockuninit(&nat_lock); LIST_FOREACH_MUTABLE(ptr, &(ipfw_nat_ctx->nat), _next, ptr_temp) { LIST_REMOVE(ptr, _next); del_redir_spool_cfg(ptr, &ptr->redir_chain); LibAliasUninit(ptr->lib); kfree(ptr, M_IPFW_NAT); } ipfw_nat_cfg_ptr = NULL; ipfw_nat_del_ptr = NULL; ipfw_nat_flush_ptr = NULL; ipfw_nat_get_cfg_ptr = NULL; ipfw_nat_get_log_ptr = NULL; return unregister_ipfw_module(MODULE_NAT_ID); } static int ipfw_nat_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: return ipfw_nat_init(); case MOD_UNLOAD: return ipfw_nat_stop(); default: break; } return 0; } static moduledata_t ipfw_nat_mod = { "ipfw3_nat", ipfw_nat_modevent, NULL }; DECLARE_MODULE(ipfw3_nat, ipfw_nat_mod, SI_SUB_PROTO_IFATTACHDOMAIN, SI_ORDER_ANY); MODULE_DEPEND(ipfw3_nat, libalias, 1, 1, 1); MODULE_DEPEND(ipfw3_nat, ipfw3_basic, 1, 1, 1); MODULE_VERSION(ipfw3_nat, 1);