647 lines
15 KiB
C
647 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/* Copyright 2020 NXP */
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/string.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/skbuff.h>
|
|
#include <linux/rtnetlink.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <net/act_api.h>
|
|
#include <net/netlink.h>
|
|
#include <net/pkt_cls.h>
|
|
#include <net/tc_act/tc_gate.h>
|
|
|
|
static unsigned int gate_net_id;
|
|
static struct tc_action_ops act_gate_ops;
|
|
|
|
static ktime_t gate_get_time(struct tcf_gate *gact)
|
|
{
|
|
ktime_t mono = ktime_get();
|
|
|
|
switch (gact->tk_offset) {
|
|
case TK_OFFS_MAX:
|
|
return mono;
|
|
default:
|
|
return ktime_mono_to_any(mono, gact->tk_offset);
|
|
}
|
|
|
|
return KTIME_MAX;
|
|
}
|
|
|
|
static void gate_get_start_time(struct tcf_gate *gact, ktime_t *start)
|
|
{
|
|
struct tcf_gate_params *param = &gact->param;
|
|
ktime_t now, base, cycle;
|
|
u64 n;
|
|
|
|
base = ns_to_ktime(param->tcfg_basetime);
|
|
now = gate_get_time(gact);
|
|
|
|
if (ktime_after(base, now)) {
|
|
*start = base;
|
|
return;
|
|
}
|
|
|
|
cycle = param->tcfg_cycletime;
|
|
|
|
n = div64_u64(ktime_sub_ns(now, base), cycle);
|
|
*start = ktime_add_ns(base, (n + 1) * cycle);
|
|
}
|
|
|
|
static void gate_start_timer(struct tcf_gate *gact, ktime_t start)
|
|
{
|
|
ktime_t expires;
|
|
|
|
expires = hrtimer_get_expires(&gact->hitimer);
|
|
if (expires == 0)
|
|
expires = KTIME_MAX;
|
|
|
|
start = min_t(ktime_t, start, expires);
|
|
|
|
hrtimer_start(&gact->hitimer, start, HRTIMER_MODE_ABS_SOFT);
|
|
}
|
|
|
|
static enum hrtimer_restart gate_timer_func(struct hrtimer *timer)
|
|
{
|
|
struct tcf_gate *gact = container_of(timer, struct tcf_gate,
|
|
hitimer);
|
|
struct tcf_gate_params *p = &gact->param;
|
|
struct tcfg_gate_entry *next;
|
|
ktime_t close_time, now;
|
|
|
|
spin_lock(&gact->tcf_lock);
|
|
|
|
next = gact->next_entry;
|
|
|
|
/* cycle start, clear pending bit, clear total octets */
|
|
gact->current_gate_status = next->gate_state ? GATE_ACT_GATE_OPEN : 0;
|
|
gact->current_entry_octets = 0;
|
|
gact->current_max_octets = next->maxoctets;
|
|
|
|
gact->current_close_time = ktime_add_ns(gact->current_close_time,
|
|
next->interval);
|
|
|
|
close_time = gact->current_close_time;
|
|
|
|
if (list_is_last(&next->list, &p->entries))
|
|
next = list_first_entry(&p->entries,
|
|
struct tcfg_gate_entry, list);
|
|
else
|
|
next = list_next_entry(next, list);
|
|
|
|
now = gate_get_time(gact);
|
|
|
|
if (ktime_after(now, close_time)) {
|
|
ktime_t cycle, base;
|
|
u64 n;
|
|
|
|
cycle = p->tcfg_cycletime;
|
|
base = ns_to_ktime(p->tcfg_basetime);
|
|
n = div64_u64(ktime_sub_ns(now, base), cycle);
|
|
close_time = ktime_add_ns(base, (n + 1) * cycle);
|
|
}
|
|
|
|
gact->next_entry = next;
|
|
|
|
hrtimer_set_expires(&gact->hitimer, close_time);
|
|
|
|
spin_unlock(&gact->tcf_lock);
|
|
|
|
return HRTIMER_RESTART;
|
|
}
|
|
|
|
static int tcf_gate_act(struct sk_buff *skb, const struct tc_action *a,
|
|
struct tcf_result *res)
|
|
{
|
|
struct tcf_gate *gact = to_gate(a);
|
|
|
|
spin_lock(&gact->tcf_lock);
|
|
|
|
tcf_lastuse_update(&gact->tcf_tm);
|
|
bstats_update(&gact->tcf_bstats, skb);
|
|
|
|
if (unlikely(gact->current_gate_status & GATE_ACT_PENDING)) {
|
|
spin_unlock(&gact->tcf_lock);
|
|
return gact->tcf_action;
|
|
}
|
|
|
|
if (!(gact->current_gate_status & GATE_ACT_GATE_OPEN))
|
|
goto drop;
|
|
|
|
if (gact->current_max_octets >= 0) {
|
|
gact->current_entry_octets += qdisc_pkt_len(skb);
|
|
if (gact->current_entry_octets > gact->current_max_octets) {
|
|
gact->tcf_qstats.overlimits++;
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
spin_unlock(&gact->tcf_lock);
|
|
|
|
return gact->tcf_action;
|
|
drop:
|
|
gact->tcf_qstats.drops++;
|
|
spin_unlock(&gact->tcf_lock);
|
|
|
|
return TC_ACT_SHOT;
|
|
}
|
|
|
|
static const struct nla_policy entry_policy[TCA_GATE_ENTRY_MAX + 1] = {
|
|
[TCA_GATE_ENTRY_INDEX] = { .type = NLA_U32 },
|
|
[TCA_GATE_ENTRY_GATE] = { .type = NLA_FLAG },
|
|
[TCA_GATE_ENTRY_INTERVAL] = { .type = NLA_U32 },
|
|
[TCA_GATE_ENTRY_IPV] = { .type = NLA_S32 },
|
|
[TCA_GATE_ENTRY_MAX_OCTETS] = { .type = NLA_S32 },
|
|
};
|
|
|
|
static const struct nla_policy gate_policy[TCA_GATE_MAX + 1] = {
|
|
[TCA_GATE_PARMS] =
|
|
NLA_POLICY_EXACT_LEN(sizeof(struct tc_gate)),
|
|
[TCA_GATE_PRIORITY] = { .type = NLA_S32 },
|
|
[TCA_GATE_ENTRY_LIST] = { .type = NLA_NESTED },
|
|
[TCA_GATE_BASE_TIME] = { .type = NLA_U64 },
|
|
[TCA_GATE_CYCLE_TIME] = { .type = NLA_U64 },
|
|
[TCA_GATE_CYCLE_TIME_EXT] = { .type = NLA_U64 },
|
|
[TCA_GATE_FLAGS] = { .type = NLA_U32 },
|
|
[TCA_GATE_CLOCKID] = { .type = NLA_S32 },
|
|
};
|
|
|
|
static int fill_gate_entry(struct nlattr **tb, struct tcfg_gate_entry *entry,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
u32 interval = 0;
|
|
|
|
entry->gate_state = nla_get_flag(tb[TCA_GATE_ENTRY_GATE]);
|
|
|
|
if (tb[TCA_GATE_ENTRY_INTERVAL])
|
|
interval = nla_get_u32(tb[TCA_GATE_ENTRY_INTERVAL]);
|
|
|
|
if (interval == 0) {
|
|
NL_SET_ERR_MSG(extack, "Invalid interval for schedule entry");
|
|
return -EINVAL;
|
|
}
|
|
|
|
entry->interval = interval;
|
|
|
|
if (tb[TCA_GATE_ENTRY_IPV])
|
|
entry->ipv = nla_get_s32(tb[TCA_GATE_ENTRY_IPV]);
|
|
else
|
|
entry->ipv = -1;
|
|
|
|
if (tb[TCA_GATE_ENTRY_MAX_OCTETS])
|
|
entry->maxoctets = nla_get_s32(tb[TCA_GATE_ENTRY_MAX_OCTETS]);
|
|
else
|
|
entry->maxoctets = -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_gate_entry(struct nlattr *n, struct tcfg_gate_entry *entry,
|
|
int index, struct netlink_ext_ack *extack)
|
|
{
|
|
struct nlattr *tb[TCA_GATE_ENTRY_MAX + 1] = { };
|
|
int err;
|
|
|
|
err = nla_parse_nested(tb, TCA_GATE_ENTRY_MAX, n, entry_policy, extack);
|
|
if (err < 0) {
|
|
NL_SET_ERR_MSG(extack, "Could not parse nested entry");
|
|
return -EINVAL;
|
|
}
|
|
|
|
entry->index = index;
|
|
|
|
return fill_gate_entry(tb, entry, extack);
|
|
}
|
|
|
|
static void release_entry_list(struct list_head *entries)
|
|
{
|
|
struct tcfg_gate_entry *entry, *e;
|
|
|
|
list_for_each_entry_safe(entry, e, entries, list) {
|
|
list_del(&entry->list);
|
|
kfree(entry);
|
|
}
|
|
}
|
|
|
|
static int parse_gate_list(struct nlattr *list_attr,
|
|
struct tcf_gate_params *sched,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct tcfg_gate_entry *entry;
|
|
struct nlattr *n;
|
|
int err, rem;
|
|
int i = 0;
|
|
|
|
if (!list_attr)
|
|
return -EINVAL;
|
|
|
|
nla_for_each_nested(n, list_attr, rem) {
|
|
if (nla_type(n) != TCA_GATE_ONE_ENTRY) {
|
|
NL_SET_ERR_MSG(extack, "Attribute isn't type 'entry'");
|
|
continue;
|
|
}
|
|
|
|
entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
|
|
if (!entry) {
|
|
NL_SET_ERR_MSG(extack, "Not enough memory for entry");
|
|
err = -ENOMEM;
|
|
goto release_list;
|
|
}
|
|
|
|
err = parse_gate_entry(n, entry, i, extack);
|
|
if (err < 0) {
|
|
kfree(entry);
|
|
goto release_list;
|
|
}
|
|
|
|
list_add_tail(&entry->list, &sched->entries);
|
|
i++;
|
|
}
|
|
|
|
sched->num_entries = i;
|
|
|
|
return i;
|
|
|
|
release_list:
|
|
release_entry_list(&sched->entries);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void gate_setup_timer(struct tcf_gate *gact, u64 basetime,
|
|
enum tk_offsets tko, s32 clockid,
|
|
bool do_init)
|
|
{
|
|
if (!do_init) {
|
|
if (basetime == gact->param.tcfg_basetime &&
|
|
tko == gact->tk_offset &&
|
|
clockid == gact->param.tcfg_clockid)
|
|
return;
|
|
|
|
spin_unlock_bh(&gact->tcf_lock);
|
|
hrtimer_cancel(&gact->hitimer);
|
|
spin_lock_bh(&gact->tcf_lock);
|
|
}
|
|
gact->param.tcfg_basetime = basetime;
|
|
gact->param.tcfg_clockid = clockid;
|
|
gact->tk_offset = tko;
|
|
hrtimer_init(&gact->hitimer, clockid, HRTIMER_MODE_ABS_SOFT);
|
|
gact->hitimer.function = gate_timer_func;
|
|
}
|
|
|
|
static int tcf_gate_init(struct net *net, struct nlattr *nla,
|
|
struct nlattr *est, struct tc_action **a,
|
|
int ovr, int bind, bool rtnl_held,
|
|
struct tcf_proto *tp, u32 flags,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct tc_action_net *tn = net_generic(net, gate_net_id);
|
|
enum tk_offsets tk_offset = TK_OFFS_TAI;
|
|
struct nlattr *tb[TCA_GATE_MAX + 1];
|
|
struct tcf_chain *goto_ch = NULL;
|
|
u64 cycletime = 0, basetime = 0;
|
|
struct tcf_gate_params *p;
|
|
s32 clockid = CLOCK_TAI;
|
|
struct tcf_gate *gact;
|
|
struct tc_gate *parm;
|
|
int ret = 0, err;
|
|
u32 gflags = 0;
|
|
s32 prio = -1;
|
|
ktime_t start;
|
|
u32 index;
|
|
|
|
if (!nla)
|
|
return -EINVAL;
|
|
|
|
err = nla_parse_nested(tb, TCA_GATE_MAX, nla, gate_policy, extack);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (!tb[TCA_GATE_PARMS])
|
|
return -EINVAL;
|
|
|
|
if (tb[TCA_GATE_CLOCKID]) {
|
|
clockid = nla_get_s32(tb[TCA_GATE_CLOCKID]);
|
|
switch (clockid) {
|
|
case CLOCK_REALTIME:
|
|
tk_offset = TK_OFFS_REAL;
|
|
break;
|
|
case CLOCK_MONOTONIC:
|
|
tk_offset = TK_OFFS_MAX;
|
|
break;
|
|
case CLOCK_BOOTTIME:
|
|
tk_offset = TK_OFFS_BOOT;
|
|
break;
|
|
case CLOCK_TAI:
|
|
tk_offset = TK_OFFS_TAI;
|
|
break;
|
|
default:
|
|
NL_SET_ERR_MSG(extack, "Invalid 'clockid'");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
parm = nla_data(tb[TCA_GATE_PARMS]);
|
|
index = parm->index;
|
|
|
|
err = tcf_idr_check_alloc(tn, &index, a, bind);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
if (err && bind)
|
|
return 0;
|
|
|
|
if (!err) {
|
|
ret = tcf_idr_create(tn, index, est, a,
|
|
&act_gate_ops, bind, false, 0);
|
|
if (ret) {
|
|
tcf_idr_cleanup(tn, index);
|
|
return ret;
|
|
}
|
|
|
|
ret = ACT_P_CREATED;
|
|
} else if (!ovr) {
|
|
tcf_idr_release(*a, bind);
|
|
return -EEXIST;
|
|
}
|
|
|
|
if (tb[TCA_GATE_PRIORITY])
|
|
prio = nla_get_s32(tb[TCA_GATE_PRIORITY]);
|
|
|
|
if (tb[TCA_GATE_BASE_TIME])
|
|
basetime = nla_get_u64(tb[TCA_GATE_BASE_TIME]);
|
|
|
|
if (tb[TCA_GATE_FLAGS])
|
|
gflags = nla_get_u32(tb[TCA_GATE_FLAGS]);
|
|
|
|
gact = to_gate(*a);
|
|
if (ret == ACT_P_CREATED)
|
|
INIT_LIST_HEAD(&gact->param.entries);
|
|
|
|
err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
|
|
if (err < 0)
|
|
goto release_idr;
|
|
|
|
spin_lock_bh(&gact->tcf_lock);
|
|
p = &gact->param;
|
|
|
|
if (tb[TCA_GATE_CYCLE_TIME])
|
|
cycletime = nla_get_u64(tb[TCA_GATE_CYCLE_TIME]);
|
|
|
|
if (tb[TCA_GATE_ENTRY_LIST]) {
|
|
err = parse_gate_list(tb[TCA_GATE_ENTRY_LIST], p, extack);
|
|
if (err < 0)
|
|
goto chain_put;
|
|
}
|
|
|
|
if (!cycletime) {
|
|
struct tcfg_gate_entry *entry;
|
|
ktime_t cycle = 0;
|
|
|
|
list_for_each_entry(entry, &p->entries, list)
|
|
cycle = ktime_add_ns(cycle, entry->interval);
|
|
cycletime = cycle;
|
|
if (!cycletime) {
|
|
err = -EINVAL;
|
|
goto chain_put;
|
|
}
|
|
}
|
|
p->tcfg_cycletime = cycletime;
|
|
|
|
if (tb[TCA_GATE_CYCLE_TIME_EXT])
|
|
p->tcfg_cycletime_ext =
|
|
nla_get_u64(tb[TCA_GATE_CYCLE_TIME_EXT]);
|
|
|
|
gate_setup_timer(gact, basetime, tk_offset, clockid,
|
|
ret == ACT_P_CREATED);
|
|
p->tcfg_priority = prio;
|
|
p->tcfg_flags = gflags;
|
|
gate_get_start_time(gact, &start);
|
|
|
|
gact->current_close_time = start;
|
|
gact->current_gate_status = GATE_ACT_GATE_OPEN | GATE_ACT_PENDING;
|
|
|
|
gact->next_entry = list_first_entry(&p->entries,
|
|
struct tcfg_gate_entry, list);
|
|
|
|
goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
|
|
|
|
gate_start_timer(gact, start);
|
|
|
|
spin_unlock_bh(&gact->tcf_lock);
|
|
|
|
if (goto_ch)
|
|
tcf_chain_put_by_act(goto_ch);
|
|
|
|
return ret;
|
|
|
|
chain_put:
|
|
spin_unlock_bh(&gact->tcf_lock);
|
|
|
|
if (goto_ch)
|
|
tcf_chain_put_by_act(goto_ch);
|
|
release_idr:
|
|
/* action is not inserted in any list: it's safe to init hitimer
|
|
* without taking tcf_lock.
|
|
*/
|
|
if (ret == ACT_P_CREATED)
|
|
gate_setup_timer(gact, gact->param.tcfg_basetime,
|
|
gact->tk_offset, gact->param.tcfg_clockid,
|
|
true);
|
|
tcf_idr_release(*a, bind);
|
|
return err;
|
|
}
|
|
|
|
static void tcf_gate_cleanup(struct tc_action *a)
|
|
{
|
|
struct tcf_gate *gact = to_gate(a);
|
|
struct tcf_gate_params *p;
|
|
|
|
p = &gact->param;
|
|
hrtimer_cancel(&gact->hitimer);
|
|
release_entry_list(&p->entries);
|
|
}
|
|
|
|
static int dumping_entry(struct sk_buff *skb,
|
|
struct tcfg_gate_entry *entry)
|
|
{
|
|
struct nlattr *item;
|
|
|
|
item = nla_nest_start_noflag(skb, TCA_GATE_ONE_ENTRY);
|
|
if (!item)
|
|
return -ENOSPC;
|
|
|
|
if (nla_put_u32(skb, TCA_GATE_ENTRY_INDEX, entry->index))
|
|
goto nla_put_failure;
|
|
|
|
if (entry->gate_state && nla_put_flag(skb, TCA_GATE_ENTRY_GATE))
|
|
goto nla_put_failure;
|
|
|
|
if (nla_put_u32(skb, TCA_GATE_ENTRY_INTERVAL, entry->interval))
|
|
goto nla_put_failure;
|
|
|
|
if (nla_put_s32(skb, TCA_GATE_ENTRY_MAX_OCTETS, entry->maxoctets))
|
|
goto nla_put_failure;
|
|
|
|
if (nla_put_s32(skb, TCA_GATE_ENTRY_IPV, entry->ipv))
|
|
goto nla_put_failure;
|
|
|
|
return nla_nest_end(skb, item);
|
|
|
|
nla_put_failure:
|
|
nla_nest_cancel(skb, item);
|
|
return -1;
|
|
}
|
|
|
|
static int tcf_gate_dump(struct sk_buff *skb, struct tc_action *a,
|
|
int bind, int ref)
|
|
{
|
|
unsigned char *b = skb_tail_pointer(skb);
|
|
struct tcf_gate *gact = to_gate(a);
|
|
struct tc_gate opt = {
|
|
.index = gact->tcf_index,
|
|
.refcnt = refcount_read(&gact->tcf_refcnt) - ref,
|
|
.bindcnt = atomic_read(&gact->tcf_bindcnt) - bind,
|
|
};
|
|
struct tcfg_gate_entry *entry;
|
|
struct tcf_gate_params *p;
|
|
struct nlattr *entry_list;
|
|
struct tcf_t t;
|
|
|
|
spin_lock_bh(&gact->tcf_lock);
|
|
opt.action = gact->tcf_action;
|
|
|
|
p = &gact->param;
|
|
|
|
if (nla_put(skb, TCA_GATE_PARMS, sizeof(opt), &opt))
|
|
goto nla_put_failure;
|
|
|
|
if (nla_put_u64_64bit(skb, TCA_GATE_BASE_TIME,
|
|
p->tcfg_basetime, TCA_GATE_PAD))
|
|
goto nla_put_failure;
|
|
|
|
if (nla_put_u64_64bit(skb, TCA_GATE_CYCLE_TIME,
|
|
p->tcfg_cycletime, TCA_GATE_PAD))
|
|
goto nla_put_failure;
|
|
|
|
if (nla_put_u64_64bit(skb, TCA_GATE_CYCLE_TIME_EXT,
|
|
p->tcfg_cycletime_ext, TCA_GATE_PAD))
|
|
goto nla_put_failure;
|
|
|
|
if (nla_put_s32(skb, TCA_GATE_CLOCKID, p->tcfg_clockid))
|
|
goto nla_put_failure;
|
|
|
|
if (nla_put_u32(skb, TCA_GATE_FLAGS, p->tcfg_flags))
|
|
goto nla_put_failure;
|
|
|
|
if (nla_put_s32(skb, TCA_GATE_PRIORITY, p->tcfg_priority))
|
|
goto nla_put_failure;
|
|
|
|
entry_list = nla_nest_start_noflag(skb, TCA_GATE_ENTRY_LIST);
|
|
if (!entry_list)
|
|
goto nla_put_failure;
|
|
|
|
list_for_each_entry(entry, &p->entries, list) {
|
|
if (dumping_entry(skb, entry) < 0)
|
|
goto nla_put_failure;
|
|
}
|
|
|
|
nla_nest_end(skb, entry_list);
|
|
|
|
tcf_tm_dump(&t, &gact->tcf_tm);
|
|
if (nla_put_64bit(skb, TCA_GATE_TM, sizeof(t), &t, TCA_GATE_PAD))
|
|
goto nla_put_failure;
|
|
spin_unlock_bh(&gact->tcf_lock);
|
|
|
|
return skb->len;
|
|
|
|
nla_put_failure:
|
|
spin_unlock_bh(&gact->tcf_lock);
|
|
nlmsg_trim(skb, b);
|
|
return -1;
|
|
}
|
|
|
|
static int tcf_gate_walker(struct net *net, struct sk_buff *skb,
|
|
struct netlink_callback *cb, int type,
|
|
const struct tc_action_ops *ops,
|
|
struct netlink_ext_ack *extack)
|
|
{
|
|
struct tc_action_net *tn = net_generic(net, gate_net_id);
|
|
|
|
return tcf_generic_walker(tn, skb, cb, type, ops, extack);
|
|
}
|
|
|
|
static void tcf_gate_stats_update(struct tc_action *a, u64 bytes, u64 packets,
|
|
u64 drops, u64 lastuse, bool hw)
|
|
{
|
|
struct tcf_gate *gact = to_gate(a);
|
|
struct tcf_t *tm = &gact->tcf_tm;
|
|
|
|
tcf_action_update_stats(a, bytes, packets, drops, hw);
|
|
tm->lastuse = max_t(u64, tm->lastuse, lastuse);
|
|
}
|
|
|
|
static int tcf_gate_search(struct net *net, struct tc_action **a, u32 index)
|
|
{
|
|
struct tc_action_net *tn = net_generic(net, gate_net_id);
|
|
|
|
return tcf_idr_search(tn, a, index);
|
|
}
|
|
|
|
static size_t tcf_gate_get_fill_size(const struct tc_action *act)
|
|
{
|
|
return nla_total_size(sizeof(struct tc_gate));
|
|
}
|
|
|
|
static struct tc_action_ops act_gate_ops = {
|
|
.kind = "gate",
|
|
.id = TCA_ID_GATE,
|
|
.owner = THIS_MODULE,
|
|
.act = tcf_gate_act,
|
|
.dump = tcf_gate_dump,
|
|
.init = tcf_gate_init,
|
|
.cleanup = tcf_gate_cleanup,
|
|
.walk = tcf_gate_walker,
|
|
.stats_update = tcf_gate_stats_update,
|
|
.get_fill_size = tcf_gate_get_fill_size,
|
|
.lookup = tcf_gate_search,
|
|
.size = sizeof(struct tcf_gate),
|
|
};
|
|
|
|
static __net_init int gate_init_net(struct net *net)
|
|
{
|
|
struct tc_action_net *tn = net_generic(net, gate_net_id);
|
|
|
|
return tc_action_net_init(net, tn, &act_gate_ops);
|
|
}
|
|
|
|
static void __net_exit gate_exit_net(struct list_head *net_list)
|
|
{
|
|
tc_action_net_exit(net_list, gate_net_id);
|
|
}
|
|
|
|
static struct pernet_operations gate_net_ops = {
|
|
.init = gate_init_net,
|
|
.exit_batch = gate_exit_net,
|
|
.id = &gate_net_id,
|
|
.size = sizeof(struct tc_action_net),
|
|
};
|
|
|
|
static int __init gate_init_module(void)
|
|
{
|
|
return tcf_register_action(&act_gate_ops, &gate_net_ops);
|
|
}
|
|
|
|
static void __exit gate_cleanup_module(void)
|
|
{
|
|
tcf_unregister_action(&act_gate_ops, &gate_net_ops);
|
|
}
|
|
|
|
module_init(gate_init_module);
|
|
module_exit(gate_cleanup_module);
|
|
MODULE_LICENSE("GPL v2");
|