freebsd-dev/sys/netpfil/ipfw/ip_fw_iface.c
Andrey V. Elsukov 88d950a650 Remove "IPFW static rules" rmlock.
Make PFIL's lock global and use it for this purpose.
This reduces the number of locks needed to acquire for each packet.

Obtained from:	Yandex LLC
MFC after:	2 weeks
Sponsored by:	Yandex LLC
No objection from: #network
Differential Revision:	https://reviews.freebsd.org/D10154
2017-04-03 13:35:04 +00:00

541 lines
11 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$");
/*
* 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/pfil.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 int
destroy_iface(struct namedobj_instance *ii, struct named_object *no,
void *arg)
{
/* Assume all consumers have been already detached */
free(no, M_IPFW);
return (0);
}
/*
* 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;
TAILQ_REMOVE(&iif->consumers, ic, next);
}
/*
* Unreference interface specified by @ic.
* Must be called while holding UH lock.
*/
void
ipfw_iface_unref(struct ip_fw_chain *ch, struct ipfw_ifc *ic)
{
struct ipfw_iface *iif;
IPFW_UH_WLOCK_ASSERT(ch);
iif = ic->iface;
ic->iface = NULL;
iif->no.refcnt--;
/* TODO: check for references & delete */
}
/*
* 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 int
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 != NULL, ("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;
return (0);
}
/*
* 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);
}