Path: blob/main/sys/netpfil/ipfw/nat64/nat64clat_control.c
39536 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2019 Yandex LLC4* Copyright (c) 2019 Andrey V. Elsukov <[email protected]>5* Copyright (c) 2019 Boris N. Lytochkin <[email protected]>6*7* Redistribution and use in source and binary forms, with or without8* modification, are permitted provided that the following conditions9* are met:10*11* 1. Redistributions of source code must retain the above copyright12* notice, this list of conditions and the following disclaimer.13* 2. Redistributions in binary form must reproduce the above copyright14* notice, this list of conditions and the following disclaimer in the15* documentation and/or other materials provided with the distribution.16*17* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR18* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES19* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.20* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,21* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT22* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF26* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.27*/2829#include <sys/param.h>30#include <sys/systm.h>31#include <sys/counter.h>32#include <sys/errno.h>33#include <sys/kernel.h>34#include <sys/lock.h>35#include <sys/malloc.h>36#include <sys/mbuf.h>37#include <sys/module.h>38#include <sys/rmlock.h>39#include <sys/rwlock.h>40#include <sys/socket.h>41#include <sys/sockopt.h>42#include <sys/syslog.h>43#include <sys/sysctl.h>4445#include <net/if.h>46#include <net/if_var.h>47#include <net/route.h>48#include <net/vnet.h>4950#include <netinet/in.h>51#include <netinet/ip_var.h>52#include <netinet/ip_fw.h>53#include <netinet6/in6_var.h>54#include <netinet6/ip6_var.h>55#include <netinet6/ip_fw_nat64.h>5657#include <netpfil/ipfw/ip_fw_private.h>5859#include "nat64clat.h"6061VNET_DEFINE(uint32_t, nat64clat_eid) = 0;6263static struct nat64clat_cfg *nat64clat_alloc_config(const char *name,64uint8_t set);65static void nat64clat_free_config(struct nat64clat_cfg *cfg);66static struct nat64clat_cfg *nat64clat_find(struct namedobj_instance *ni,67const char *name, uint8_t set);6869static struct nat64clat_cfg *70nat64clat_alloc_config(const char *name, uint8_t set)71{72struct nat64clat_cfg *cfg;7374cfg = malloc(sizeof(struct nat64clat_cfg), M_IPFW, M_WAITOK | M_ZERO);75COUNTER_ARRAY_ALLOC(cfg->base.stats.cnt, NAT64STATS, M_WAITOK);76cfg->no.name = cfg->name;77cfg->no.etlv = IPFW_TLV_NAT64CLAT_NAME;78cfg->no.set = set;79strlcpy(cfg->name, name, sizeof(cfg->name));80return (cfg);81}8283static void84nat64clat_free_config(struct nat64clat_cfg *cfg)85{8687COUNTER_ARRAY_FREE(cfg->base.stats.cnt, NAT64STATS);88free(cfg, M_IPFW);89}9091static void92nat64clat_export_config(struct ip_fw_chain *ch, struct nat64clat_cfg *cfg,93ipfw_nat64clat_cfg *uc)94{95uc->plat_prefix = cfg->base.plat_prefix;96uc->plat_plen = cfg->base.plat_plen;97uc->clat_prefix = cfg->base.clat_prefix;98uc->clat_plen = cfg->base.clat_plen;99uc->flags = cfg->base.flags & NAT64CLAT_FLAGSMASK;100uc->set = cfg->no.set;101strlcpy(uc->name, cfg->no.name, sizeof(uc->name));102}103104struct nat64clat_dump_arg {105struct ip_fw_chain *ch;106struct sockopt_data *sd;107};108109static int110export_config_cb(struct namedobj_instance *ni, struct named_object *no,111void *arg)112{113struct nat64clat_dump_arg *da = (struct nat64clat_dump_arg *)arg;114ipfw_nat64clat_cfg *uc;115116uc = (ipfw_nat64clat_cfg *)ipfw_get_sopt_space(da->sd, sizeof(*uc));117nat64clat_export_config(da->ch, (struct nat64clat_cfg *)no, uc);118return (0);119}120121static struct nat64clat_cfg *122nat64clat_find(struct namedobj_instance *ni, const char *name, uint8_t set)123{124struct nat64clat_cfg *cfg;125126cfg = (struct nat64clat_cfg *)ipfw_objhash_lookup_name_type(ni, set,127IPFW_TLV_NAT64CLAT_NAME, name);128129return (cfg);130}131132/*133* Creates new consumer-side nat64 translator instance.134* Data layout (v0)(current):135* Request: [ ipfw_obj_lheader ipfw_nat64clat_cfg ]136*137* Returns 0 on success138*/139static int140nat64clat_create(struct ip_fw_chain *ch, ip_fw3_opheader *op3,141struct sockopt_data *sd)142{143ipfw_obj_lheader *olh;144ipfw_nat64clat_cfg *uc;145struct namedobj_instance *ni;146struct nat64clat_cfg *cfg;147148if (sd->valsize != sizeof(*olh) + sizeof(*uc))149return (EINVAL);150151olh = (ipfw_obj_lheader *)sd->kbuf;152uc = (ipfw_nat64clat_cfg *)(olh + 1);153154if (ipfw_check_object_name_generic(uc->name) != 0)155return (EINVAL);156157if (uc->set >= IPFW_MAX_SETS ||158nat64_check_prefix6(&uc->plat_prefix, uc->plat_plen) != 0 ||159nat64_check_prefix6(&uc->clat_prefix, uc->clat_plen) != 0)160return (EINVAL);161162ni = CHAIN_TO_SRV(ch);163164IPFW_UH_RLOCK(ch);165if (nat64clat_find(ni, uc->name, uc->set) != NULL) {166IPFW_UH_RUNLOCK(ch);167return (EEXIST);168}169IPFW_UH_RUNLOCK(ch);170171cfg = nat64clat_alloc_config(uc->name, uc->set);172cfg->base.plat_prefix = uc->plat_prefix;173cfg->base.plat_plen = uc->plat_plen;174cfg->base.clat_prefix = uc->clat_prefix;175cfg->base.clat_plen = uc->clat_plen;176cfg->base.flags = (uc->flags & NAT64CLAT_FLAGSMASK) |177NAT64_CLATPFX | NAT64_PLATPFX;178if (IN6_IS_ADDR_WKPFX(&cfg->base.plat_prefix))179cfg->base.flags |= NAT64_WKPFX;180181IPFW_UH_WLOCK(ch);182183if (nat64clat_find(ni, uc->name, uc->set) != NULL) {184IPFW_UH_WUNLOCK(ch);185nat64clat_free_config(cfg);186return (EEXIST);187}188189if (ipfw_objhash_alloc_idx(ni, &cfg->no.kidx) != 0) {190IPFW_UH_WUNLOCK(ch);191nat64clat_free_config(cfg);192return (ENOSPC);193}194ipfw_objhash_add(CHAIN_TO_SRV(ch), &cfg->no);195/* Okay, let's link data */196SRV_OBJECT(ch, cfg->no.kidx) = cfg;197IPFW_UH_WUNLOCK(ch);198199return (0);200}201202/*203* Change existing nat64clat instance configuration.204* Data layout (v0)(current):205* Request: [ ipfw_obj_header ipfw_nat64clat_cfg ]206* Reply: [ ipfw_obj_header ipfw_nat64clat_cfg ]207*208* Returns 0 on success209*/210static int211nat64clat_config(struct ip_fw_chain *ch, ip_fw3_opheader *op,212struct sockopt_data *sd)213{214ipfw_obj_header *oh;215ipfw_nat64clat_cfg *uc;216struct nat64clat_cfg *cfg;217struct namedobj_instance *ni;218uint32_t flags;219220if (sd->valsize != sizeof(*oh) + sizeof(*uc))221return (EINVAL);222223oh = (ipfw_obj_header *)ipfw_get_sopt_space(sd,224sizeof(*oh) + sizeof(*uc));225uc = (ipfw_nat64clat_cfg *)(oh + 1);226227if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 ||228oh->ntlv.set >= IPFW_MAX_SETS)229return (EINVAL);230231ni = CHAIN_TO_SRV(ch);232if (sd->sopt->sopt_dir == SOPT_GET) {233IPFW_UH_RLOCK(ch);234cfg = nat64clat_find(ni, oh->ntlv.name, oh->ntlv.set);235if (cfg == NULL) {236IPFW_UH_RUNLOCK(ch);237return (ENOENT);238}239nat64clat_export_config(ch, cfg, uc);240IPFW_UH_RUNLOCK(ch);241return (0);242}243244IPFW_UH_WLOCK(ch);245cfg = nat64clat_find(ni, oh->ntlv.name, oh->ntlv.set);246if (cfg == NULL) {247IPFW_UH_WUNLOCK(ch);248return (ENOENT);249}250251/*252* For now allow to change only following values:253* plat_prefix, plat_plen, clat_prefix, clat_plen, flags.254*/255flags = 0;256if (uc->plat_plen != cfg->base.plat_plen ||257!IN6_ARE_ADDR_EQUAL(&uc->plat_prefix, &cfg->base.plat_prefix)) {258if (nat64_check_prefix6(&uc->plat_prefix, uc->plat_plen) != 0) {259IPFW_UH_WUNLOCK(ch);260return (EINVAL);261}262flags |= NAT64_PLATPFX;263}264265if (uc->clat_plen != cfg->base.clat_plen ||266!IN6_ARE_ADDR_EQUAL(&uc->clat_prefix, &cfg->base.clat_prefix)) {267if (nat64_check_prefix6(&uc->clat_prefix, uc->clat_plen) != 0) {268IPFW_UH_WUNLOCK(ch);269return (EINVAL);270}271flags |= NAT64_CLATPFX;272}273274if (flags != 0) {275IPFW_WLOCK(ch);276if (flags & NAT64_PLATPFX) {277cfg->base.plat_prefix = uc->plat_prefix;278cfg->base.plat_plen = uc->plat_plen;279}280if (flags & NAT64_CLATPFX) {281cfg->base.clat_prefix = uc->clat_prefix;282cfg->base.clat_plen = uc->clat_plen;283}284IPFW_WUNLOCK(ch);285}286287cfg->base.flags &= ~NAT64CLAT_FLAGSMASK;288cfg->base.flags |= uc->flags & NAT64CLAT_FLAGSMASK;289290IPFW_UH_WUNLOCK(ch);291return (0);292}293294static void295nat64clat_detach_config(struct ip_fw_chain *ch, struct nat64clat_cfg *cfg)296{297298IPFW_UH_WLOCK_ASSERT(ch);299300ipfw_objhash_del(CHAIN_TO_SRV(ch), &cfg->no);301ipfw_objhash_free_idx(CHAIN_TO_SRV(ch), cfg->no.kidx);302}303304/*305* Destroys nat64 instance.306* Data layout (v0)(current):307* Request: [ ipfw_obj_header ]308*309* Returns 0 on success310*/311static int312nat64clat_destroy(struct ip_fw_chain *ch, ip_fw3_opheader *op3,313struct sockopt_data *sd)314{315ipfw_obj_header *oh;316struct nat64clat_cfg *cfg;317318if (sd->valsize != sizeof(*oh))319return (EINVAL);320321oh = (ipfw_obj_header *)sd->kbuf;322if (ipfw_check_object_name_generic(oh->ntlv.name) != 0)323return (EINVAL);324325IPFW_UH_WLOCK(ch);326cfg = nat64clat_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set);327if (cfg == NULL) {328IPFW_UH_WUNLOCK(ch);329return (ENOENT);330}331if (cfg->no.refcnt > 0) {332IPFW_UH_WUNLOCK(ch);333return (EBUSY);334}335336ipfw_reset_eaction_instance(ch, V_nat64clat_eid, cfg->no.kidx);337SRV_OBJECT(ch, cfg->no.kidx) = NULL;338nat64clat_detach_config(ch, cfg);339IPFW_UH_WUNLOCK(ch);340341nat64clat_free_config(cfg);342return (0);343}344345/*346* Lists all nat64clat instances currently available in kernel.347* Data layout (v0)(current):348* Request: [ ipfw_obj_lheader ]349* Reply: [ ipfw_obj_lheader ipfw_nat64clat_cfg x N ]350*351* Returns 0 on success352*/353static int354nat64clat_list(struct ip_fw_chain *ch, ip_fw3_opheader *op3,355struct sockopt_data *sd)356{357ipfw_obj_lheader *olh;358struct nat64clat_dump_arg da;359360/* Check minimum header size */361if (sd->valsize < sizeof(ipfw_obj_lheader))362return (EINVAL);363364olh = (ipfw_obj_lheader *)ipfw_get_sopt_header(sd, sizeof(*olh));365366IPFW_UH_RLOCK(ch);367olh->count = ipfw_objhash_count_type(CHAIN_TO_SRV(ch),368IPFW_TLV_NAT64CLAT_NAME);369olh->objsize = sizeof(ipfw_nat64clat_cfg);370olh->size = sizeof(*olh) + olh->count * olh->objsize;371372if (sd->valsize < olh->size) {373IPFW_UH_RUNLOCK(ch);374return (ENOMEM);375}376memset(&da, 0, sizeof(da));377da.ch = ch;378da.sd = sd;379ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), export_config_cb,380&da, IPFW_TLV_NAT64CLAT_NAME);381IPFW_UH_RUNLOCK(ch);382383return (0);384}385386#define __COPY_STAT_FIELD(_cfg, _stats, _field) \387(_stats)->_field = NAT64STAT_FETCH(&(_cfg)->base.stats, _field)388static void389export_stats(struct ip_fw_chain *ch, struct nat64clat_cfg *cfg,390struct ipfw_nat64clat_stats *stats)391{392393__COPY_STAT_FIELD(cfg, stats, opcnt64);394__COPY_STAT_FIELD(cfg, stats, opcnt46);395__COPY_STAT_FIELD(cfg, stats, ofrags);396__COPY_STAT_FIELD(cfg, stats, ifrags);397__COPY_STAT_FIELD(cfg, stats, oerrors);398__COPY_STAT_FIELD(cfg, stats, noroute4);399__COPY_STAT_FIELD(cfg, stats, noroute6);400__COPY_STAT_FIELD(cfg, stats, noproto);401__COPY_STAT_FIELD(cfg, stats, nomem);402__COPY_STAT_FIELD(cfg, stats, dropped);403}404405/*406* Get nat64clat statistics.407* Data layout (v0)(current):408* Request: [ ipfw_obj_header ]409* Reply: [ ipfw_obj_header ipfw_obj_ctlv [ uint64_t x N ]]410*411* Returns 0 on success412*/413static int414nat64clat_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op,415struct sockopt_data *sd)416{417struct ipfw_nat64clat_stats stats;418struct nat64clat_cfg *cfg;419ipfw_obj_header *oh;420ipfw_obj_ctlv *ctlv;421size_t sz;422423sz = sizeof(ipfw_obj_header) + sizeof(ipfw_obj_ctlv) + sizeof(stats);424if (sd->valsize % sizeof(uint64_t))425return (EINVAL);426if (sd->valsize < sz)427return (ENOMEM);428oh = (ipfw_obj_header *)ipfw_get_sopt_header(sd, sz);429if (oh == NULL)430return (EINVAL);431memset(&stats, 0, sizeof(stats));432433IPFW_UH_RLOCK(ch);434cfg = nat64clat_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set);435if (cfg == NULL) {436IPFW_UH_RUNLOCK(ch);437return (ENOENT);438}439export_stats(ch, cfg, &stats);440IPFW_UH_RUNLOCK(ch);441442ctlv = (ipfw_obj_ctlv *)(oh + 1);443memset(ctlv, 0, sizeof(*ctlv));444ctlv->head.type = IPFW_TLV_COUNTERS;445ctlv->head.length = sz - sizeof(ipfw_obj_header);446ctlv->count = sizeof(stats) / sizeof(uint64_t);447ctlv->objsize = sizeof(uint64_t);448ctlv->version = IPFW_NAT64_VERSION;449memcpy(ctlv + 1, &stats, sizeof(stats));450return (0);451}452453/*454* Reset nat64clat statistics.455* Data layout (v0)(current):456* Request: [ ipfw_obj_header ]457*458* Returns 0 on success459*/460static int461nat64clat_reset_stats(struct ip_fw_chain *ch, ip_fw3_opheader *op,462struct sockopt_data *sd)463{464struct nat64clat_cfg *cfg;465ipfw_obj_header *oh;466467if (sd->valsize != sizeof(*oh))468return (EINVAL);469oh = (ipfw_obj_header *)sd->kbuf;470if (ipfw_check_object_name_generic(oh->ntlv.name) != 0 ||471oh->ntlv.set >= IPFW_MAX_SETS)472return (EINVAL);473474IPFW_UH_WLOCK(ch);475cfg = nat64clat_find(CHAIN_TO_SRV(ch), oh->ntlv.name, oh->ntlv.set);476if (cfg == NULL) {477IPFW_UH_WUNLOCK(ch);478return (ENOENT);479}480COUNTER_ARRAY_ZERO(cfg->base.stats.cnt, NAT64STATS);481IPFW_UH_WUNLOCK(ch);482return (0);483}484485static struct ipfw_sopt_handler scodes[] = {486{ IP_FW_NAT64CLAT_CREATE, IP_FW3_OPVER, HDIR_SET, nat64clat_create },487{ IP_FW_NAT64CLAT_DESTROY, IP_FW3_OPVER, HDIR_SET, nat64clat_destroy },488{ IP_FW_NAT64CLAT_CONFIG, IP_FW3_OPVER, HDIR_BOTH, nat64clat_config },489{ IP_FW_NAT64CLAT_LIST, IP_FW3_OPVER, HDIR_GET, nat64clat_list },490{ IP_FW_NAT64CLAT_STATS, IP_FW3_OPVER, HDIR_GET, nat64clat_stats },491{ IP_FW_NAT64CLAT_RESET_STATS, IP_FW3_OPVER, HDIR_SET, nat64clat_reset_stats },492};493494static int495nat64clat_manage_sets(struct ip_fw_chain *ch, uint32_t set, uint8_t new_set,496enum ipfw_sets_cmd cmd)497{498499return (ipfw_obj_manage_sets(CHAIN_TO_SRV(ch),500IPFW_TLV_NAT64CLAT_NAME, set, new_set, cmd));501}502NAT64_DEFINE_OPCODE_REWRITER(nat64clat, NAT64CLAT, opcodes);503504static int505destroy_config_cb(struct namedobj_instance *ni, struct named_object *no,506void *arg)507{508struct nat64clat_cfg *cfg;509struct ip_fw_chain *ch;510511ch = (struct ip_fw_chain *)arg;512cfg = (struct nat64clat_cfg *)SRV_OBJECT(ch, no->kidx);513SRV_OBJECT(ch, no->kidx) = NULL;514nat64clat_detach_config(ch, cfg);515nat64clat_free_config(cfg);516return (0);517}518519int520nat64clat_init(struct ip_fw_chain *ch, int first)521{522523V_nat64clat_eid = ipfw_add_eaction(ch, ipfw_nat64clat, "nat64clat");524if (V_nat64clat_eid == 0)525return (ENXIO);526IPFW_ADD_SOPT_HANDLER(first, scodes);527IPFW_ADD_OBJ_REWRITER(first, opcodes);528return (0);529}530531void532nat64clat_uninit(struct ip_fw_chain *ch, int last)533{534535IPFW_DEL_OBJ_REWRITER(last, opcodes);536IPFW_DEL_SOPT_HANDLER(last, scodes);537ipfw_del_eaction(ch, V_nat64clat_eid);538/*539* Since we already have deregistered external action,540* our named objects become unaccessible via rules, because541* all rules were truncated by ipfw_del_eaction().542* So, we can unlink and destroy our named objects without holding543* IPFW_WLOCK().544*/545IPFW_UH_WLOCK(ch);546ipfw_objhash_foreach_type(CHAIN_TO_SRV(ch), destroy_config_cb, ch,547IPFW_TLV_NAT64CLAT_NAME);548V_nat64clat_eid = 0;549IPFW_UH_WUNLOCK(ch);550}551552553