freebsd-dev/sys/netpfil/ipfw/ip_fw_iface.c
2014-10-04 11:40:35 +00:00

539 lines
12 KiB
C

/*-
* Copyright (c) 2014 Yandex LLC.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <sys/cdefs.h>
__FBSDID("$FreeBSD: projects/ipfw/sys/netpfil/ipfw/ip_fw_iface.c 267384 2014-06-12 09:59:11Z melifaro $");
/*
* Kernel interface tracking API.
*
*/
#include "opt_ipfw.h"
#include "opt_inet.h"
#ifndef INET
#error IPFIREWALL requires INET.
#endif /* INET */
#include "opt_inet6.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/rwlock.h>
#include <sys/rmlock.h>
#include <sys/socket.h>
#include <sys/queue.h>
#include <sys/eventhandler.h>
#include <net/if.h>
#include <net/if_var.h>
#include <net/vnet.h>
#include <netinet/in.h>
#include <netinet/ip_var.h> /* struct ipfw_rule_ref */
#include <netinet/ip_fw.h>
#include <netpfil/ipfw/ip_fw_private.h>
#define CHAIN_TO_II(ch) ((struct namedobj_instance *)ch->ifcfg)
#define DEFAULT_IFACES 128
static void handle_ifdetach(struct ip_fw_chain *ch, struct ipfw_iface *iif,
uint16_t ifindex);
static void handle_ifattach(struct ip_fw_chain *ch, struct ipfw_iface *iif,
uint16_t ifindex);
static int list_ifaces(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
struct sockopt_data *sd);
static struct ipfw_sopt_handler scodes[] = {
{ IP_FW_XIFLIST, 0, HDIR_GET, list_ifaces },
};
/*
* FreeBSD Kernel interface.
*/
static void ipfw_kifhandler(void *arg, struct ifnet *ifp);
static int ipfw_kiflookup(char *name);
static void iface_khandler_register(void);
static void iface_khandler_deregister(void);
static eventhandler_tag ipfw_ifdetach_event, ipfw_ifattach_event;
static int num_vnets = 0;
static struct mtx vnet_mtx;
/*
* Checks if kernel interface is contained in our tracked
* interface list and calls attach/detach handler.
*/
static void
ipfw_kifhandler(void *arg, struct ifnet *ifp)
{
struct ip_fw_chain *ch;
struct ipfw_iface *iif;
struct namedobj_instance *ii;
uintptr_t htype;
if (V_ipfw_vnet_ready == 0)
return;
ch = &V_layer3_chain;
htype = (uintptr_t)arg;
IPFW_UH_WLOCK(ch);
ii = CHAIN_TO_II(ch);
if (ii == NULL) {
IPFW_UH_WUNLOCK(ch);
return;
}
iif = (struct ipfw_iface*)ipfw_objhash_lookup_name(ii, 0,
if_name(ifp));
if (iif != NULL) {
if (htype == 1)
handle_ifattach(ch, iif, ifp->if_index);
else
handle_ifdetach(ch, iif, ifp->if_index);
}
IPFW_UH_WUNLOCK(ch);
}
/*
* Reference current VNET as iface tracking API user.
* Registers interface tracking handlers for first VNET.
*/
static void
iface_khandler_register()
{
int create;
create = 0;
mtx_lock(&vnet_mtx);
if (num_vnets == 0)
create = 1;
num_vnets++;
mtx_unlock(&vnet_mtx);
if (create == 0)
return;
printf("IPFW: starting up interface tracker\n");
ipfw_ifdetach_event = EVENTHANDLER_REGISTER(
ifnet_departure_event, ipfw_kifhandler, NULL,
EVENTHANDLER_PRI_ANY);
ipfw_ifattach_event = EVENTHANDLER_REGISTER(
ifnet_arrival_event, ipfw_kifhandler, (void*)((uintptr_t)1),
EVENTHANDLER_PRI_ANY);
}
/*
*
* Detach interface event handlers on last VNET instance
* detach.
*/
static void
iface_khandler_deregister()
{
int destroy;
destroy = 0;
mtx_lock(&vnet_mtx);
if (num_vnets == 1)
destroy = 1;
num_vnets--;
mtx_unlock(&vnet_mtx);
if (destroy == 0)
return;
EVENTHANDLER_DEREGISTER(ifnet_arrival_event,
ipfw_ifattach_event);
EVENTHANDLER_DEREGISTER(ifnet_departure_event,
ipfw_ifdetach_event);
}
/*
* Retrieves ifindex for given @name.
*
* Returns ifindex or 0.
*/
static int
ipfw_kiflookup(char *name)
{
struct ifnet *ifp;
int ifindex;
ifindex = 0;
if ((ifp = ifunit_ref(name)) != NULL) {
ifindex = ifp->if_index;
if_rele(ifp);
}
return (ifindex);
}
/*
* Global ipfw startup hook.
* Since we perform lazy initialization, do nothing except
* mutex init.
*/
int
ipfw_iface_init()
{
mtx_init(&vnet_mtx, "IPFW ifhandler mtx", NULL, MTX_DEF);
IPFW_ADD_SOPT_HANDLER(1, scodes);
return (0);
}
/*
* Global ipfw destroy hook.
* Unregister khandlers iff init has been done.
*/
void
ipfw_iface_destroy()
{
IPFW_DEL_SOPT_HANDLER(1, scodes);
mtx_destroy(&vnet_mtx);
}
/*
* Perform actual init on internal request.
* Inits both namehash and global khandler.
*/
static void
vnet_ipfw_iface_init(struct ip_fw_chain *ch)
{
struct namedobj_instance *ii;
ii = ipfw_objhash_create(DEFAULT_IFACES);
IPFW_UH_WLOCK(ch);
if (ch->ifcfg == NULL) {
ch->ifcfg = ii;
ii = NULL;
}
IPFW_UH_WUNLOCK(ch);
if (ii != NULL) {
/* Already initialized. Free namehash. */
ipfw_objhash_destroy(ii);
} else {
/* We're the first ones. Init kernel hooks. */
iface_khandler_register();
}
}
static void
destroy_iface(struct namedobj_instance *ii, struct named_object *no,
void *arg)
{
/* Assume all consumers have been already detached */
free(no, M_IPFW);
}
/*
* Per-VNET ipfw detach hook.
*
*/
void
vnet_ipfw_iface_destroy(struct ip_fw_chain *ch)
{
struct namedobj_instance *ii;
IPFW_UH_WLOCK(ch);
ii = CHAIN_TO_II(ch);
ch->ifcfg = NULL;
IPFW_UH_WUNLOCK(ch);
if (ii != NULL) {
ipfw_objhash_foreach(ii, destroy_iface, ch);
ipfw_objhash_destroy(ii);
iface_khandler_deregister();
}
}
/*
* Notify the subsystem that we are interested in tracking
* interface @name. This function has to be called without
* holding any locks to permit allocating the necessary states
* for proper interface tracking.
*
* Returns 0 on success.
*/
int
ipfw_iface_ref(struct ip_fw_chain *ch, char *name,
struct ipfw_ifc *ic)
{
struct namedobj_instance *ii;
struct ipfw_iface *iif, *tmp;
if (strlen(name) >= sizeof(iif->ifname))
return (EINVAL);
IPFW_UH_WLOCK(ch);
ii = CHAIN_TO_II(ch);
if (ii == NULL) {
/*
* First request to subsystem.
* Let's perform init.
*/
IPFW_UH_WUNLOCK(ch);
vnet_ipfw_iface_init(ch);
IPFW_UH_WLOCK(ch);
ii = CHAIN_TO_II(ch);
}
iif = (struct ipfw_iface *)ipfw_objhash_lookup_name(ii, 0, name);
if (iif != NULL) {
iif->no.refcnt++;
ic->iface = iif;
IPFW_UH_WUNLOCK(ch);
return (0);
}
IPFW_UH_WUNLOCK(ch);
/* Not found. Let's create one */
iif = malloc(sizeof(struct ipfw_iface), M_IPFW, M_WAITOK | M_ZERO);
TAILQ_INIT(&iif->consumers);
iif->no.name = iif->ifname;
strlcpy(iif->ifname, name, sizeof(iif->ifname));
/*
* Ref & link to the list.
*
* We assume ifnet_arrival_event / ifnet_departure_event
* are not holding any locks.
*/
iif->no.refcnt = 1;
IPFW_UH_WLOCK(ch);
tmp = (struct ipfw_iface *)ipfw_objhash_lookup_name(ii, 0, name);
if (tmp != NULL) {
/* Interface has been created since unlock. Ref and return */
tmp->no.refcnt++;
ic->iface = tmp;
IPFW_UH_WUNLOCK(ch);
free(iif, M_IPFW);
return (0);
}
iif->ifindex = ipfw_kiflookup(name);
if (iif->ifindex != 0)
iif->resolved = 1;
ipfw_objhash_add(ii, &iif->no);
ic->iface = iif;
IPFW_UH_WUNLOCK(ch);
return (0);
}
/*
* Adds @ic to the list of iif interface consumers.
* Must be called with holding both UH+WLOCK.
* Callback may be immediately called (if interface exists).
*/
void
ipfw_iface_add_notify(struct ip_fw_chain *ch, struct ipfw_ifc *ic)
{
struct ipfw_iface *iif;
IPFW_UH_WLOCK_ASSERT(ch);
IPFW_WLOCK_ASSERT(ch);
iif = ic->iface;
TAILQ_INSERT_TAIL(&iif->consumers, ic, next);
if (iif->resolved != 0)
ic->cb(ch, ic->cbdata, iif->ifindex);
}
/*
* Unlinks interface tracker object @ic from interface.
* Must be called while holding UH lock.
*/
void
ipfw_iface_del_notify(struct ip_fw_chain *ch, struct ipfw_ifc *ic)
{
struct ipfw_iface *iif;
IPFW_UH_WLOCK_ASSERT(ch);
iif = ic->iface;
if (ic->linked != 0)
TAILQ_REMOVE(&iif->consumers, ic, next);
}
/*
* Unreference interface specified by @ic.
* Must be called without holding any locks.
*/
void
ipfw_iface_unref(struct ip_fw_chain *ch, struct ipfw_ifc *ic)
{
struct ipfw_iface *iif;
iif = ic->iface;
ic->iface = NULL;
IPFW_UH_WLOCK(ch);
iif->no.refcnt--;
/* TODO: check for references & delete */
IPFW_UH_WUNLOCK(ch);
}
/*
* Interface arrival handler.
*/
static void
handle_ifattach(struct ip_fw_chain *ch, struct ipfw_iface *iif,
uint16_t ifindex)
{
struct ipfw_ifc *ic;
IPFW_UH_WLOCK_ASSERT(ch);
iif->gencnt++;
iif->resolved = 1;
iif->ifindex = ifindex;
IPFW_WLOCK(ch);
TAILQ_FOREACH(ic, &iif->consumers, next)
ic->cb(ch, ic->cbdata, iif->ifindex);
IPFW_WUNLOCK(ch);
}
/*
* Interface departure handler.
*/
static void
handle_ifdetach(struct ip_fw_chain *ch, struct ipfw_iface *iif,
uint16_t ifindex)
{
struct ipfw_ifc *ic;
IPFW_UH_WLOCK_ASSERT(ch);
IPFW_WLOCK(ch);
TAILQ_FOREACH(ic, &iif->consumers, next)
ic->cb(ch, ic->cbdata, 0);
IPFW_WUNLOCK(ch);
iif->gencnt++;
iif->resolved = 0;
iif->ifindex = 0;
}
struct dump_iface_args {
struct ip_fw_chain *ch;
struct sockopt_data *sd;
};
static void
export_iface_internal(struct namedobj_instance *ii, struct named_object *no,
void *arg)
{
ipfw_iface_info *i;
struct dump_iface_args *da;
struct ipfw_iface *iif;
da = (struct dump_iface_args *)arg;
i = (ipfw_iface_info *)ipfw_get_sopt_space(da->sd, sizeof(*i));
KASSERT(i != 0, ("previously checked buffer is not enough"));
iif = (struct ipfw_iface *)no;
strlcpy(i->ifname, iif->ifname, sizeof(i->ifname));
if (iif->resolved)
i->flags |= IPFW_IFFLAG_RESOLVED;
i->ifindex = iif->ifindex;
i->refcnt = iif->no.refcnt;
i->gencnt = iif->gencnt;
}
/*
* Lists all interface currently tracked by ipfw.
* Data layout (v0)(current):
* Request: [ ipfw_obj_lheader ], size = ipfw_obj_lheader.size
* Reply: [ ipfw_obj_lheader ipfw_iface_info x N ]
*
* Returns 0 on success
*/
static int
list_ifaces(struct ip_fw_chain *ch, ip_fw3_opheader *op3,
struct sockopt_data *sd)
{
struct namedobj_instance *ii;
struct _ipfw_obj_lheader *olh;
struct dump_iface_args da;
uint32_t count, size;
olh = (struct _ipfw_obj_lheader *)ipfw_get_sopt_header(sd,sizeof(*olh));
if (olh == NULL)
return (EINVAL);
if (sd->valsize < olh->size)
return (EINVAL);
IPFW_UH_RLOCK(ch);
ii = CHAIN_TO_II(ch);
if (ii != NULL)
count = ipfw_objhash_count(ii);
else
count = 0;
size = count * sizeof(ipfw_iface_info) + sizeof(ipfw_obj_lheader);
/* Fill in header regadless of buffer size */
olh->count = count;
olh->objsize = sizeof(ipfw_iface_info);
if (size > olh->size) {
olh->size = size;
IPFW_UH_RUNLOCK(ch);
return (ENOMEM);
}
olh->size = size;
da.ch = ch;
da.sd = sd;
if (ii != NULL)
ipfw_objhash_foreach(ii, export_iface_internal, &da);
IPFW_UH_RUNLOCK(ch);
return (0);
}