Commit 59a49e38 authored by Linus Torvalds's avatar Linus Torvalds
parents 52c1da39 f2d368fa
...@@ -198,18 +198,12 @@ err_exit: ...@@ -198,18 +198,12 @@ err_exit:
static void static void
sl_free_bufs(struct slip *sl) sl_free_bufs(struct slip *sl)
{ {
void * tmp;
/* Free all SLIP frame buffers. */ /* Free all SLIP frame buffers. */
tmp = xchg(&sl->rbuff, NULL); kfree(xchg(&sl->rbuff, NULL));
kfree(tmp); kfree(xchg(&sl->xbuff, NULL));
tmp = xchg(&sl->xbuff, NULL);
kfree(tmp);
#ifdef SL_INCLUDE_CSLIP #ifdef SL_INCLUDE_CSLIP
tmp = xchg(&sl->cbuff, NULL); kfree(xchg(&sl->cbuff, NULL));
kfree(tmp); slhc_free(xchg(&sl->slcomp, NULL));
if ((tmp = xchg(&sl->slcomp, NULL)) != NULL)
slhc_free(tmp);
#endif #endif
} }
......
...@@ -164,12 +164,6 @@ struct netif_rx_stats ...@@ -164,12 +164,6 @@ struct netif_rx_stats
unsigned total; unsigned total;
unsigned dropped; unsigned dropped;
unsigned time_squeeze; unsigned time_squeeze;
unsigned throttled;
unsigned fastroute_hit;
unsigned fastroute_success;
unsigned fastroute_defer;
unsigned fastroute_deferred_out;
unsigned fastroute_latency_reduction;
unsigned cpu_collision; unsigned cpu_collision;
}; };
...@@ -562,12 +556,9 @@ static inline int unregister_gifconf(unsigned int family) ...@@ -562,12 +556,9 @@ static inline int unregister_gifconf(unsigned int family)
struct softnet_data struct softnet_data
{ {
int throttle; struct net_device *output_queue;
int cng_level;
int avg_blog;
struct sk_buff_head input_pkt_queue; struct sk_buff_head input_pkt_queue;
struct list_head poll_list; struct list_head poll_list;
struct net_device *output_queue;
struct sk_buff *completion_queue; struct sk_buff *completion_queue;
struct net_device backlog_dev; /* Sorry. 8) */ struct net_device backlog_dev; /* Sorry. 8) */
......
...@@ -408,6 +408,7 @@ enum ...@@ -408,6 +408,7 @@ enum
TCF_EM_NBYTE, TCF_EM_NBYTE,
TCF_EM_U32, TCF_EM_U32,
TCF_EM_META, TCF_EM_META,
TCF_EM_TEXT,
__TCF_EM_MAX __TCF_EM_MAX
}; };
......
...@@ -892,10 +892,13 @@ extern void __rta_fill(struct sk_buff *skb, int attrtype, int attrlen, const voi ...@@ -892,10 +892,13 @@ extern void __rta_fill(struct sk_buff *skb, int attrtype, int attrlen, const voi
goto rtattr_failure; \ goto rtattr_failure; \
__rta_fill(skb, attrtype, attrlen, data); }) __rta_fill(skb, attrtype, attrlen, data); })
#define RTA_PUT_NOHDR(skb, attrlen, data) \ #define RTA_APPEND(skb, attrlen, data) \
({ if (unlikely(skb_tailroom(skb) < (int)(attrlen))) \ ({ if (unlikely(skb_tailroom(skb) < (int)(attrlen))) \
goto rtattr_failure; \ goto rtattr_failure; \
memcpy(skb_put(skb, RTA_ALIGN(attrlen)), data, attrlen); }) memcpy(skb_put(skb, attrlen), data, attrlen); })
#define RTA_PUT_NOHDR(skb, attrlen, data) \
RTA_APPEND(skb, RTA_ALIGN(attrlen), data)
#define RTA_PUT_U8(skb, attrtype, value) \ #define RTA_PUT_U8(skb, attrtype, value) \
({ u8 _tmp = (value); \ ({ u8 _tmp = (value); \
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include <linux/highmem.h> #include <linux/highmem.h>
#include <linux/poll.h> #include <linux/poll.h>
#include <linux/net.h> #include <linux/net.h>
#include <linux/textsearch.h>
#include <net/checksum.h> #include <net/checksum.h>
#define HAVE_ALLOC_SKB /* For the drivers to know */ #define HAVE_ALLOC_SKB /* For the drivers to know */
...@@ -321,6 +322,28 @@ extern void skb_over_panic(struct sk_buff *skb, int len, ...@@ -321,6 +322,28 @@ extern void skb_over_panic(struct sk_buff *skb, int len,
extern void skb_under_panic(struct sk_buff *skb, int len, extern void skb_under_panic(struct sk_buff *skb, int len,
void *here); void *here);
struct skb_seq_state
{
__u32 lower_offset;
__u32 upper_offset;
__u32 frag_idx;
__u32 stepped_offset;
struct sk_buff *root_skb;
struct sk_buff *cur_skb;
__u8 *frag_data;
};
extern void skb_prepare_seq_read(struct sk_buff *skb,
unsigned int from, unsigned int to,
struct skb_seq_state *st);
extern unsigned int skb_seq_read(unsigned int consumed, const u8 **data,
struct skb_seq_state *st);
extern void skb_abort_seq_read(struct skb_seq_state *st);
extern unsigned int skb_find_text(struct sk_buff *skb, unsigned int from,
unsigned int to, struct ts_config *config,
struct ts_state *state);
/* Internal */ /* Internal */
#define skb_shinfo(SKB) ((struct skb_shared_info *)((SKB)->end)) #define skb_shinfo(SKB) ((struct skb_shared_info *)((SKB)->end))
......
...@@ -243,6 +243,7 @@ enum ...@@ -243,6 +243,7 @@ enum
NET_CORE_MOD_CONG=16, NET_CORE_MOD_CONG=16,
NET_CORE_DEV_WEIGHT=17, NET_CORE_DEV_WEIGHT=17,
NET_CORE_SOMAXCONN=18, NET_CORE_SOMAXCONN=18,
NET_CORE_BUDGET=19,
}; };
/* /proc/sys/net/ethernet */ /* /proc/sys/net/ethernet */
......
#ifndef __LINUX_TC_EM_TEXT_H
#define __LINUX_TC_EM_TEXT_H
#include <linux/pkt_cls.h>
#define TC_EM_TEXT_ALGOSIZ 16
struct tcf_em_text
{
char algo[TC_EM_TEXT_ALGOSIZ];
__u16 from_offset;
__u16 to_offset;
__u16 pattern_len;
__u8 from_layer:4;
__u8 to_layer:4;
__u8 pad;
};
#endif
...@@ -127,6 +127,7 @@ enum { ...@@ -127,6 +127,7 @@ enum {
#define TCP_WINDOW_CLAMP 10 /* Bound advertised window */ #define TCP_WINDOW_CLAMP 10 /* Bound advertised window */
#define TCP_INFO 11 /* Information about this connection. */ #define TCP_INFO 11 /* Information about this connection. */
#define TCP_QUICKACK 12 /* Block/reenable quick acks */ #define TCP_QUICKACK 12 /* Block/reenable quick acks */
#define TCP_CONGESTION 13 /* Congestion control algorithm */
#define TCPI_OPT_TIMESTAMPS 1 #define TCPI_OPT_TIMESTAMPS 1
#define TCPI_OPT_SACK 2 #define TCPI_OPT_SACK 2
......
#ifndef __LINUX_TEXTSEARCH_H
#define __LINUX_TEXTSEARCH_H
#ifdef __KERNEL__
#include <linux/types.h>
#include <linux/list.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/err.h>
struct ts_config;
/**
* TS_AUTOLOAD - Automatically load textsearch modules when needed
*/
#define TS_AUTOLOAD 1
/**
* struct ts_state - search state
* @offset: offset for next match
* @cb: control buffer, for persistant variables of get_next_block()
*/
struct ts_state
{
unsigned int offset;
char cb[40];
};
/**
* struct ts_ops - search module operations
* @name: name of search algorithm
* @init: initialization function to prepare a search
* @find: find the next occurrence of the pattern
* @destroy: destroy algorithm specific parts of a search configuration
* @get_pattern: return head of pattern
* @get_pattern_len: return length of pattern
* @owner: module reference to algorithm
*/
struct ts_ops
{
const char *name;
struct ts_config * (*init)(const void *, unsigned int, int);
unsigned int (*find)(struct ts_config *,
struct ts_state *);
void (*destroy)(struct ts_config *);
void * (*get_pattern)(struct ts_config *);
unsigned int (*get_pattern_len)(struct ts_config *);
struct module *owner;
struct list_head list;
};
/**
* struct ts_config - search configuration
* @ops: operations of chosen algorithm
* @get_next_block: callback to fetch the next block to search in
* @finish: callback to finalize a search
*/
struct ts_config
{
struct ts_ops *ops;
/**
* get_next_block - fetch next block of data
* @consumed: number of bytes consumed by the caller
* @dst: destination buffer
* @conf: search configuration
* @state: search state
*
* Called repeatedly until 0 is returned. Must assign the
* head of the next block of data to &*dst and return the length
* of the block or 0 if at the end. consumed == 0 indicates
* a new search. May store/read persistant values in state->cb.
*/
unsigned int (*get_next_block)(unsigned int consumed,
const u8 **dst,
struct ts_config *conf,
struct ts_state *state);
/**
* finish - finalize/clean a series of get_next_block() calls
* @conf: search configuration
* @state: search state
*
* Called after the last use of get_next_block(), may be used
* to cleanup any leftovers.
*/
void (*finish)(struct ts_config *conf,
struct ts_state *state);
};
/**
* textsearch_next - continue searching for a pattern
* @conf: search configuration
* @state: search state
*
* Continues a search looking for more occurrences of the pattern.
* textsearch_find() must be called to find the first occurrence
* in order to reset the state.
*
* Returns the position of the next occurrence of the pattern or
* UINT_MAX if not match was found.
*/
static inline unsigned int textsearch_next(struct ts_config *conf,
struct ts_state *state)
{
unsigned int ret = conf->ops->find(conf, state);
if (conf->finish)
conf->finish(conf, state);
return ret;
}
/**
* textsearch_find - start searching for a pattern
* @conf: search configuration
* @state: search state
*
* Returns the position of first occurrence of the pattern or
* UINT_MAX if no match was found.
*/
static inline unsigned int textsearch_find(struct ts_config *conf,
struct ts_state *state)
{
state->offset = 0;
return textsearch_next(conf, state);
}
/**
* textsearch_get_pattern - return head of the pattern
* @conf: search configuration
*/
static inline void *textsearch_get_pattern(struct ts_config *conf)
{
return conf->ops->get_pattern(conf);
}
/**
* textsearch_get_pattern_len - return length of the pattern
* @conf: search configuration
*/
static inline unsigned int textsearch_get_pattern_len(struct ts_config *conf)
{
return conf->ops->get_pattern_len(conf);
}
extern int textsearch_register(struct ts_ops *);
extern int textsearch_unregister(struct ts_ops *);
extern struct ts_config *textsearch_prepare(const char *, const void *,
unsigned int, int, int);
extern void textsearch_destroy(struct ts_config *conf);
extern unsigned int textsearch_find_continuous(struct ts_config *,
struct ts_state *,
const void *, unsigned int);
#define TS_PRIV_ALIGNTO 8
#define TS_PRIV_ALIGN(len) (((len) + TS_PRIV_ALIGNTO-1) & ~(TS_PRIV_ALIGNTO-1))
static inline struct ts_config *alloc_ts_config(size_t payload, int gfp_mask)
{
struct ts_config *conf;
conf = kmalloc(TS_PRIV_ALIGN(sizeof(*conf)) + payload, gfp_mask);
if (conf == NULL)
return ERR_PTR(-ENOMEM);
memset(conf, 0, TS_PRIV_ALIGN(sizeof(*conf)) + payload);
return conf;
}
static inline void *ts_config_priv(struct ts_config *conf)
{
return ((u8 *) conf + TS_PRIV_ALIGN(sizeof(struct ts_config)));
}
#endif /* __KERNEL__ */
#endif
#ifndef __LINUX_TEXTSEARCH_FSM_H
#define __LINUX_TEXTSEARCH_FSM_H
#include <linux/types.h>
enum {
TS_FSM_SPECIFIC, /* specific character */
TS_FSM_WILDCARD, /* any character */
TS_FSM_DIGIT, /* isdigit() */
TS_FSM_XDIGIT, /* isxdigit() */
TS_FSM_PRINT, /* isprint() */
TS_FSM_ALPHA, /* isalpha() */
TS_FSM_ALNUM, /* isalnum() */
TS_FSM_ASCII, /* isascii() */
TS_FSM_CNTRL, /* iscntrl() */
TS_FSM_GRAPH, /* isgraph() */
TS_FSM_LOWER, /* islower() */
TS_FSM_UPPER, /* isupper() */
TS_FSM_PUNCT, /* ispunct() */
TS_FSM_SPACE, /* isspace() */
__TS_FSM_TYPE_MAX,
};
#define TS_FSM_TYPE_MAX (__TS_FSM_TYPE_MAX - 1)
enum {
TS_FSM_SINGLE, /* 1 occurrence */
TS_FSM_PERHAPS, /* 1 or 0 occurrence */
TS_FSM_ANY, /* 0..n occurrences */
TS_FSM_MULTI, /* 1..n occurrences */
TS_FSM_HEAD_IGNORE, /* 0..n ignored occurrences at head */
__TS_FSM_RECUR_MAX,
};
#define TS_FSM_RECUR_MAX (__TS_FSM_RECUR_MAX - 1)
/**
* struct ts_fsm_token - state machine token (state)
* @type: type of token
* @recur: number of recurrences
* @value: character value for TS_FSM_SPECIFIC
*/
struct ts_fsm_token
{
__u16 type;
__u8 recur;
__u8 value;
};
#endif
...@@ -1162,12 +1162,14 @@ extern void tcp_init_congestion_control(struct tcp_sock *tp); ...@@ -1162,12 +1162,14 @@ extern void tcp_init_congestion_control(struct tcp_sock *tp);
extern void tcp_cleanup_congestion_control(struct tcp_sock *tp); extern void tcp_cleanup_congestion_control(struct tcp_sock *tp);
extern int tcp_set_default_congestion_control(const char *name); extern int tcp_set_default_congestion_control(const char *name);
extern void tcp_get_default_congestion_control(char *name); extern void tcp_get_default_congestion_control(char *name);
extern int tcp_set_congestion_control(struct tcp_sock *tp, const char *name);
extern struct tcp_congestion_ops tcp_reno; extern struct tcp_congestion_ops tcp_init_congestion_ops;
extern u32 tcp_reno_ssthresh(struct tcp_sock *tp); extern u32 tcp_reno_ssthresh(struct tcp_sock *tp);
extern void tcp_reno_cong_avoid(struct tcp_sock *tp, u32 ack, extern void tcp_reno_cong_avoid(struct tcp_sock *tp, u32 ack,
u32 rtt, u32 in_flight, int flag); u32 rtt, u32 in_flight, int flag);
extern u32 tcp_reno_min_cwnd(struct tcp_sock *tp); extern u32 tcp_reno_min_cwnd(struct tcp_sock *tp);
extern struct tcp_congestion_ops tcp_reno;
static inline void tcp_set_ca_state(struct tcp_sock *tp, u8 ca_state) static inline void tcp_set_ca_state(struct tcp_sock *tp, u8 ca_state)
{ {
......
...@@ -63,5 +63,32 @@ config REED_SOLOMON_ENC16 ...@@ -63,5 +63,32 @@ config REED_SOLOMON_ENC16
config REED_SOLOMON_DEC16 config REED_SOLOMON_DEC16
boolean boolean
endmenu config TEXTSEARCH
boolean "Textsearch infrastructure"
default y
help
Say Y here if you want to provide a textsearch infrastructure
to other subsystems.
config TEXTSEARCH_KMP
depends on TEXTSEARCH
tristate "Knuth-Morris-Pratt"
help
Say Y here if you want to be able to search text using the
Knuth-Morris-Pratt textsearch algorithm.
To compile this code as a module, choose M here: the
module will be called ts_kmp.
config TEXTSEARCH_FSM
depends on TEXTSEARCH
tristate "Finite state machine"
help
Say Y here if you want to be able to search text using a
naive finite state machine approach implementing a subset
of regular expressions.
To compile this code as a module, choose M here: the
module will be called ts_fsm.
endmenu
...@@ -36,6 +36,10 @@ obj-$(CONFIG_ZLIB_INFLATE) += zlib_inflate/ ...@@ -36,6 +36,10 @@ obj-$(CONFIG_ZLIB_INFLATE) += zlib_inflate/
obj-$(CONFIG_ZLIB_DEFLATE) += zlib_deflate/ obj-$(CONFIG_ZLIB_DEFLATE) += zlib_deflate/
obj-$(CONFIG_REED_SOLOMON) += reed_solomon/ obj-$(CONFIG_REED_SOLOMON) += reed_solomon/
obj-$(CONFIG_TEXTSEARCH) += textsearch.o
obj-$(CONFIG_TEXTSEARCH_KMP) += ts_kmp.o
obj-$(CONFIG_TEXTSEARCH_FSM) += ts_fsm.o
hostprogs-y := gen_crc32table hostprogs-y := gen_crc32table
clean-files := crc32table.h clean-files := crc32table.h
......
/*
* lib/textsearch.c Generic text search interface
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Authors: Thomas Graf <tgraf@suug.ch>
* Pablo Neira Ayuso <pablo@eurodev.net>
*
* ==========================================================================
*
* INTRODUCTION
*
* The textsearch infrastructure provides text searching facitilies for
* both linear and non-linear data. Individual search algorithms are
* implemented in modules and chosen by the user.
*
* ARCHITECTURE
*
* User
* +----------------+
* | finish()|<--------------(6)-----------------+
* |get_next_block()|<--------------(5)---------------+ |
* | | Algorithm | |
* | | +------------------------------+
* | | | init() find() destroy() |
* | | +------------------------------+
* | | Core API ^ ^ ^
* | | +---------------+ (2) (4) (8)
* | (1)|----->| prepare() |---+ | |
* | (3)|----->| find()/next() |-----------+ |
* | (7)|----->| destroy() |----------------------+
* +----------------+ +---------------+
*
* (1) User configures a search by calling _prepare() specifying the
* search parameters such as the pattern and algorithm name.
* (2) Core requests the algorithm to allocate and initialize a search
* configuration according to the specified parameters.
* (3) User starts the search(es) by calling _find() or _next() to
* fetch subsequent occurrences. A state variable is provided
* to the algorihtm to store persistant variables.
* (4) Core eventually resets the search offset and forwards the find()
* request to the algorithm.
* (5) Algorithm calls get_next_block() provided by the user continously
* to fetch the data to be searched in block by block.
* (6) Algorithm invokes finish() after the last call to get_next_block
* to clean up any leftovers from get_next_block. (Optional)
* (7) User destroys the configuration by calling _destroy().
* (8) Core notifies the algorithm to destroy algorithm specific
* allocations. (Optional)
*
* USAGE
*
* Before a search can be performed, a configuration must be created
* by calling textsearch_prepare() specyfing the searching algorithm and
* the pattern to look for. The returned configuration may then be used
* for an arbitary amount of times and even in parallel as long as a
* separate struct ts_state variable is provided to every instance.
*
* The actual search is performed by either calling textsearch_find_-
* continuous() for linear data or by providing an own get_next_block()
* implementation and calling textsearch_find(). Both functions return
* the position of the first occurrence of the patern or UINT_MAX if
* no match was found. Subsequent occurences can be found by calling
* textsearch_next() regardless of the linearity of the data.
*
* Once you're done using a configuration it must be given back via
* textsearch_destroy.
*
* EXAMPLE
*
* int pos;
* struct ts_config *conf;
* struct ts_state state;
* const char *pattern = "chicken";
* const char *example = "We dance the funky chicken";
*
* conf = textsearch_prepare("kmp", pattern, strlen(pattern),
* GFP_KERNEL, TS_AUTOLOAD);
* if (IS_ERR(conf)) {
* err = PTR_ERR(conf);
* goto errout;
* }
*
* pos = textsearch_find_continuous(conf, &state, example, strlen(example));
* if (pos != UINT_MAX)
* panic("Oh my god, dancing chickens at %d\n", pos);
*
* textsearch_destroy(conf);
*
* ==========================================================================
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/rcupdate.h>
#include <linux/err.h>
#include <linux/textsearch.h>
static LIST_HEAD(ts_ops);
static DEFINE_SPINLOCK(ts_mod_lock);
static inline struct ts_ops *lookup_ts_algo(const char *name)
{
struct ts_ops *o;
rcu_read_lock();
list_for_each_entry_rcu(o, &ts_ops, list) {
if (!strcmp(name, o->name)) {
if (!try_module_get(o->owner))
o = NULL;
rcu_read_unlock();
return o;
}
}
rcu_read_unlock();
return NULL;
}
/**
* textsearch_register - register a textsearch module
* @ops: operations lookup table
*
* This function must be called by textsearch modules to announce
* their presence. The specified &@ops must have %name set to a
* unique identifier and the callbacks find(), init(), get_pattern(),
* and get_pattern_len() must be implemented.
*
* Returns 0 or -EEXISTS if another module has already registered
* with same name.
*/
int textsearch_register(struct ts_ops *ops)
{
int err = -EEXIST;
struct ts_ops *o;
if (ops->name == NULL || ops->find == NULL || ops->init == NULL ||
ops->get_pattern == NULL || ops->get_pattern_len == NULL)
return -EINVAL;
spin_lock(&ts_mod_lock);
list_for_each_entry(o, &ts_ops, list) {
if (!strcmp(ops->name, o->name))
goto errout;
}
list_add_tail_rcu(&ops->list, &ts_ops);
err = 0;
errout:
spin_unlock(&ts_mod_lock);
return err;
}
/**
* textsearch_unregister - unregister a textsearch module
* @ops: operations lookup table
*
* This function must be called by textsearch modules to announce
* their disappearance for examples when the module gets unloaded.
* The &ops parameter must be the same as the one during the
* registration.
*
* Returns 0 on success or -ENOENT if no matching textsearch
* registration was found.
*/
int textsearch_unregister(struct ts_ops *ops)
{
int err = 0;
struct ts_ops *o;
spin_lock(&ts_mod_lock);
list_for_each_entry(o, &ts_ops, list) {
if (o == ops) {
list_del_rcu(&o->list);
goto out;
}
}
err = -ENOENT;
out:
spin_unlock(&ts_mod_lock);
return err;
}
struct ts_linear_state
{
unsigned int len;
const void *data;
};
static unsigned int get_linear_data(unsigned int consumed, const u8 **dst,
struct ts_config *conf,
struct ts_state *state)
{
struct ts_linear_state *st = (struct ts_linear_state *) state->cb;
if (likely(consumed < st->len)) {
*dst = st->data + consumed;
return st->len - consumed;
}
return 0;
}
/**
* textsearch_find_continuous - search a pattern in continuous/linear data
* @conf: search configuration
* @state: search state
* @data: data to search in
* @len: length of data
*
* A simplified version of textsearch_find() for continuous/linear data.
* Call textsearch_next() to retrieve subsequent matches.
*
* Returns the position of first occurrence of the pattern or
* UINT_MAX if no occurrence was found.
*/
unsigned int textsearch_find_continuous(struct ts_config *conf,
struct ts_state *state,
const void *data, unsigned int len)
{
struct ts_linear_state *st = (struct ts_linear_state *) state->cb;
conf->get_next_block = get_linear_data;
st->data = data;
st->len = len;
return textsearch_find(conf, state);
}
/**
* textsearch_prepare - Prepare a search
* @algo: name of search algorithm
* @pattern: pattern data
* @len: length of pattern
* @gfp_mask: allocation mask
* @flags: search flags
*
* Looks up the search algorithm module and creates a new textsearch
* configuration for the specified pattern. Upon completion all
* necessary refcnts are held and the configuration must be put back
* using textsearch_put() after usage.
*
* Note: The format of the pattern may not be compatible between
* the various search algorithms.
*
* Returns a new textsearch configuration according to the specified
* parameters or a ERR_PTR().
*/
struct ts_config *textsearch_prepare(const char *algo, const void *pattern,
unsigned int len, int gfp_mask, int flags)
{
int err = -ENOENT;
struct ts_config *conf;
struct ts_ops *ops;
ops = lookup_ts_algo(algo);
#ifdef CONFIG_KMOD
/*
* Why not always autoload you may ask. Some users are
* in a situation where requesting a module may deadlock,
* especially when the module is located on a NFS mount.
*/
if (ops == NULL && flags & TS_AUTOLOAD) {
request_module("ts_%s", algo);
ops = lookup_ts_algo(algo);
}
#endif
if (ops == NULL)
goto errout;
conf = ops->init(pattern, len, gfp_mask);
if (IS_ERR(conf)) {
err = PTR_ERR(conf);
goto errout;
}
conf->ops = ops;
return conf;
errout:
if (ops)
module_put(ops->owner);
return ERR_PTR(err);
}
/**
* textsearch_destroy - destroy a search configuration
* @conf: search configuration
*
* Releases all references of the configuration and frees
* up the memory.
*/
void textsearch_destroy(struct ts_config *conf)
{
if (conf->ops) {
if (conf->ops->destroy)
conf->ops->destroy(conf);
module_put(conf->ops->owner);
}
kfree(conf);
}
EXPORT_SYMBOL(textsearch_register);
EXPORT_SYMBOL(textsearch_unregister);
EXPORT_SYMBOL(textsearch_prepare);
EXPORT_SYMBOL(textsearch_find_continuous);
EXPORT_SYMBOL(textsearch_destroy);
/*
* lib/ts_fsm.c A naive finite state machine text search approach
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Authors: Thomas Graf <tgraf@suug.ch>
*
* ==========================================================================
*
* A finite state machine consists of n states (struct ts_fsm_token)
* representing the pattern as a finite automation. The data is read
* sequentially on a octet basis. Every state token specifies the number
* of recurrences and the type of value accepted which can be either a
* specific character or ctype based set of characters. The available
* type of recurrences include 1, (0|1), [0 n], and [1 n].
*
* The algorithm differs between strict/non-strict mode specyfing
* whether the pattern has to start at the first octect. Strict mode
* is enabled by default and can be disabled by inserting
* TS_FSM_HEAD_IGNORE as the first token in the chain.
*
* The runtime performance of the algorithm should be around O(n),
* however while in strict mode the average runtime can be better.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/textsearch.h>
#include <linux/textsearch_fsm.h>
struct ts_fsm
{
unsigned int ntokens;
struct ts_fsm_token tokens[0];
};
/* other values derived from ctype.h */
#define _A 0x100 /* ascii */
#define _W 0x200 /* wildcard */
/* Map to _ctype flags and some magic numbers */
static u16 token_map[TS_FSM_TYPE_MAX+1] = {
[TS_FSM_SPECIFIC] = 0,
[TS_FSM_WILDCARD] = _W,
[TS_FSM_CNTRL] = _C,
[TS_FSM_LOWER] = _L,
[TS_FSM_UPPER] = _U,
[TS_FSM_PUNCT] = _P,
[TS_FSM_SPACE] = _S,
[TS_FSM_DIGIT] = _D,
[TS_FSM_XDIGIT] = _D | _X,
[TS_FSM_ALPHA] = _U | _L,
[TS_FSM_ALNUM] = _U | _L | _D,
[TS_FSM_PRINT] = _P | _U | _L | _D | _SP,
[TS_FSM_GRAPH] = _P | _U | _L | _D,
[TS_FSM_ASCII] = _A,
};
static u16 token_lookup_tbl[256] = {
_W|_A|_C, _W|_A|_C, _W|_A|_C, _W|_A|_C, /* 0- 3 */
_W|_A|_C, _W|_A|_C, _W|_A|_C, _W|_A|_C, /* 4- 7 */
_W|_A|_C, _W|_A|_C|_S, _W|_A|_C|_S, _W|_A|_C|_S, /* 8- 11 */
_W|_A|_C|_S, _W|_A|_C|_S, _W|_A|_C, _W|_A|_C, /* 12- 15 */
_W|_A|_C, _W|_A|_C, _W|_A|_C, _W|_A|_C, /* 16- 19 */
_W|_A|_C, _W|_A|_C, _W|_A|_C, _W|_A|_C, /* 20- 23 */
_W|_A|_C, _W|_A|_C, _W|_A|_C, _W|_A|_C, /* 24- 27 */
_W|_A|_C, _W|_A|_C, _W|_A|_C, _W|_A|_C, /* 28- 31 */
_W|_A|_S|_SP, _W|_A|_P, _W|_A|_P, _W|_A|_P, /* 32- 35 */
_W|_A|_P, _W|_A|_P, _W|_A|_P, _W|_A|_P, /* 36- 39 */
_W|_A|_P, _W|_A|_P, _W|_A|_P, _W|_A|_P, /* 40- 43 */
_W|_A|_P, _W|_A|_P, _W|_A|_P, _W|_A|_P, /* 44- 47 */
_W|_A|_D, _W|_A|_D, _W|_A|_D, _W|_A|_D, /* 48- 51 */
_W|_A|_D, _W|_A|_D, _W|_A|_D, _W|_A|_D, /* 52- 55 */
_W|_A|_D, _W|_A|_D, _W|_A|_P, _W|_A|_P, /* 56- 59 */
_W|_A|_P, _W|_A|_P, _W|_A|_P, _W|_A|_P, /* 60- 63 */
_W|_A|_P, _W|_A|_U|_X, _W|_A|_U|_X, _W|_A|_U|_X, /* 64- 67 */
_W|_A|_U|_X, _W|_A|_U|_X, _W|_A|_U|_X, _W|_A|_U, /* 68- 71 */
_W|_A|_U, _W|_A|_U, _W|_A|_U, _W|_A|_U, /* 72- 75 */
_W|_A|_U, _W|_A|_U, _W|_A|_U, _W|_A|_U, /* 76- 79 */
_W|_A|_U, _W|_A|_U, _W|_A|_U, _W|_A|_U, /* 80- 83 */
_W|_A|_U, _W|_A|_U, _W|_A|_U, _W|_A|_U, /* 84- 87 */
_W|_A|_U, _W|_A|_U, _W|_A|_U, _W|_A|_P, /* 88- 91 */
_W|_A|_P, _W|_A|_P, _W|_A|_P, _W|_A|_P, /* 92- 95 */
_W|_A|_P, _W|_A|_L|_X, _W|_A|_L|_X, _W|_A|_L|_X, /* 96- 99 */
_W|_A|_L|_X, _W|_A|_L|_X, _W|_A|_L|_X, _W|_A|_L, /* 100-103 */
_W|_A|_L, _W|_A|_L, _W|_A|_L, _W|_A|_L, /* 104-107 */
_W|_A|_L, _W|_A|_L, _W|_A|_L, _W|_A|_L, /* 108-111 */
_W|_A|_L, _W|_A|_L, _W|_A|_L, _W|_A|_L, /* 112-115 */
_W|_A|_L, _W|_A|_L, _W|_A|_L, _W|_A|_L, /* 116-119 */
_W|_A|_L, _W|_A|_L, _W|_A|_L, _W|_A|_P, /* 120-123 */
_W|_A|_P, _W|_A|_P, _W|_A|_P, _W|_A|_C, /* 124-127 */
_W, _W, _W, _W, /* 128-131 */
_W, _W, _W, _W, /* 132-135 */
_W, _W, _W, _W, /* 136-139 */
_W, _W, _W, _W, /* 140-143 */
_W, _W, _W, _W, /* 144-147 */
_W, _W, _W, _W, /* 148-151 */
_W, _W, _W, _W, /* 152-155 */
_W, _W, _W, _W, /* 156-159 */
_W|_S|_SP, _W|_P, _W|_P, _W|_P, /* 160-163 */
_W|_P, _W|_P, _W|_P, _W|_P, /* 164-167 */
_W|_P, _W|_P, _W|_P, _W|_P, /* 168-171 */
_W|_P, _W|_P, _W|_P, _W|_P, /* 172-175 */
_W|_P, _W|_P, _W|_P, _W|_P, /* 176-179 */
_W|_P, _W|_P, _W|_P, _W|_P, /* 180-183 */
_W|_P, _W|_P, _W|_P, _W|_P, /* 184-187 */
_W|_P, _W|_P, _W|_P, _W|_P, /* 188-191 */
_W|_U, _W|_U, _W|_U, _W|_U, /* 192-195 */
_W|_U, _W|_U, _W|_U, _W|_U, /* 196-199 */
_W|_U, _W|_U, _W|_U, _W|_U, /* 200-203 */
_W|_U, _W|_U, _W|_U, _W|_U, /* 204-207 */
_W|_U, _W|_U, _W|_U, _W|_U, /* 208-211 */
_W|_U, _W|_U, _W|_U, _W|_P, /* 212-215 */
_W|_U, _W|_U, _W|_U, _W|_U, /* 216-219 */
_W|_U, _W|_U, _W|_U, _W|_L, /* 220-223 */
_W|_L, _W|_L, _W|_L, _W|_L, /* 224-227 */
_W|_L, _W|_L, _W|_L, _W|_L, /* 228-231 */
_W|_L, _W|_L, _W|_L, _W|_L, /* 232-235 */
_W|_L, _W|_L, _W|_L, _W|_L, /* 236-239 */
_W|_L, _W|_L, _W|_L, _W|_L, /* 240-243 */
_W|_L, _W|_L, _W|_L, _W|_P, /* 244-247 */
_W|_L, _W|_L, _W|_L, _W|_L, /* 248-251 */
_W|_L, _W|_L, _W|_L, _W|_L}; /* 252-255 */
static inline int match_token(struct ts_fsm_token *t, u8 d)
{
if (t->type)
return (token_lookup_tbl[d] & t->type) != 0;
else
return t->value == d;
}
static unsigned int fsm_find(struct ts_config *conf, struct ts_state *state)
{
struct ts_fsm *fsm = ts_config_priv(conf);
struct ts_fsm_token *cur = NULL, *next;
unsigned int match_start, block_idx = 0, tok_idx;
unsigned block_len = 0, strict, consumed = state->offset;
const u8 *data;
#define GET_NEXT_BLOCK() \
({ consumed += block_idx; \
block_idx = 0; \
block_len = conf->get_next_block(consumed, &data, conf, state); })
#define TOKEN_MISMATCH() \
do { \
if (strict) \
goto no_match; \
block_idx++; \
goto startover; \
} while(0)
#define end_of_data() unlikely(block_idx >= block_len && !GET_NEXT_BLOCK())
if (end_of_data())
goto no_match;
strict = fsm->tokens[0].recur != TS_FSM_HEAD_IGNORE;
startover:
match_start = consumed + block_idx;
for (tok_idx = 0; tok_idx < fsm->ntokens; tok_idx++) {
cur = &fsm->tokens[tok_idx];
if (likely(tok_idx < (fsm->ntokens - 1)))
next = &fsm->tokens[tok_idx + 1];
else
next = NULL;
switch (cur->recur) {
case TS_FSM_SINGLE:
if (end_of_data())
goto no_match;
if (!match_token(cur, data[block_idx]))
TOKEN_MISMATCH();
break;
case TS_FSM_PERHAPS:
if (end_of_data() ||
!match_token(cur, data[block_idx]))
continue;
break;
case TS_FSM_MULTI:
if (end_of_data())
goto no_match;
if (!match_token(cur, data[block_idx]))
TOKEN_MISMATCH();
block_idx++;
/* fall through */
case TS_FSM_ANY:
if (next == NULL)
goto found_match;
if (end_of_data())
continue;
while (!match_token(next, data[block_idx])) {
if (!match_token(cur, data[block_idx]))
TOKEN_MISMATCH();
block_idx++;
if (end_of_data())
goto no_match;
}
continue;
/*
* Optimization: Prefer small local loop over jumping
* back and forth until garbage at head is munched.
*/
case TS_FSM_HEAD_IGNORE:
if (end_of_data())
continue;
while (!match_token(next, data[block_idx])) {
/*
* Special case, don't start over upon
* a mismatch, give the user the
* chance to specify the type of data
* allowed to be ignored.
*/
if (!match_token(cur, data[block_idx]))
goto no_match;
block_idx++;
if (end_of_data())
goto no_match;
}
match_start = consumed + block_idx;
continue;
}
block_idx++;
}
if (end_of_data())
goto found_match;
no_match:
return UINT_MAX;
found_match:
state->offset = consumed + block_idx;
return match_start;
}
static struct ts_config *fsm_init(const void *pattern, unsigned int len,
int gfp_mask)
{
int i, err = -EINVAL;
struct ts_config *conf;
struct ts_fsm *fsm;
struct ts_fsm_token *tokens = (struct ts_fsm_token *) pattern;
unsigned int ntokens = len / sizeof(*tokens);
size_t priv_size = sizeof(*fsm) + len;
if (len % sizeof(struct ts_fsm_token) || ntokens < 1)
goto errout;
for (i = 0; i < ntokens; i++) {
struct ts_fsm_token *t = &tokens[i];
if (t->type > TS_FSM_TYPE_MAX || t->recur > TS_FSM_RECUR_MAX)
goto errout;
if (t->recur == TS_FSM_HEAD_IGNORE &&
(i != 0 || i == (ntokens - 1)))
goto errout;
}
conf = alloc_ts_config(priv_size, gfp_mask);
if (IS_ERR(conf))
return conf;
fsm = ts_config_priv(conf);
fsm->ntokens = ntokens;
memcpy(fsm->tokens, pattern, len);
for (i = 0; i < fsm->ntokens; i++) {
struct ts_fsm_token *t = &fsm->tokens[i];
t->type = token_map[t->type];
}
return conf;
errout:
return ERR_PTR(err);
}
static void *fsm_get_pattern(struct ts_config *conf)
{
struct ts_fsm *fsm = ts_config_priv(conf);
return fsm->tokens;
}
static unsigned int fsm_get_pattern_len(struct ts_config *conf)
{
struct ts_fsm *fsm = ts_config_priv(conf);
return fsm->ntokens * sizeof(struct ts_fsm_token);
}
static struct ts_ops fsm_ops = {
.name = "fsm",
.find = fsm_find,
.init = fsm_init,
.get_pattern = fsm_get_pattern,
.get_pattern_len = fsm_get_pattern_len,
.owner = THIS_MODULE,
.list = LIST_HEAD_INIT(fsm_ops.list)
};
static int __init init_fsm(void)
{
return textsearch_register(&fsm_ops);
}
static void __exit exit_fsm(void)
{
textsearch_unregister(&fsm_ops);
}
MODULE_LICENSE("GPL");
module_init(init_fsm);
module_exit(exit_fsm);
/*
* lib/ts_kmp.c Knuth-Morris-Pratt text search implementation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Authors: Thomas Graf <tgraf@suug.ch>
*
* ==========================================================================
*
* Implements a linear-time string-matching algorithm due to Knuth,
* Morris, and Pratt [1]. Their algorithm avoids the explicit
* computation of the transition function DELTA altogether. Its
* matching time is O(n), for n being length(text), using just an
* auxiliary function PI[1..m], for m being length(pattern),
* precomputed from the pattern in time O(m). The array PI allows
* the transition function DELTA to be computed efficiently
* "on the fly" as needed. Roughly speaking, for any state
* "q" = 0,1,...,m and any character "a" in SIGMA, the value
* PI["q"] contains the information that is independent of "a" and
* is needed to compute DELTA("q", "a") [2]. Since the array PI
* has only m entries, whereas DELTA has O(m|SIGMA|) entries, we
* save a factor of |SIGMA| in the preprocessing time by computing
* PI rather than DELTA.
*
* [1] Cormen, Leiserson, Rivest, Stein
* Introdcution to Algorithms, 2nd Edition, MIT Press
* [2] See finite automation theory
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/textsearch.h>
struct ts_kmp
{
u8 * pattern;
unsigned int pattern_len;
unsigned int prefix_tbl[0];
};
static unsigned int kmp_find(struct ts_config *conf, struct ts_state *state)
{
struct ts_kmp *kmp = ts_config_priv(conf);
unsigned int i, q = 0, text_len, consumed = state->offset;
const u8 *text;
for (;;) {
text_len = conf->get_next_block(consumed, &text, conf, state);
if (unlikely(text_len == 0))
break;
for (i = 0; i < text_len; i++) {
while (q > 0 && kmp->pattern[q] != text[i])
q = kmp->prefix_tbl[q - 1];
if (kmp->pattern[q] == text[i])
q++;
if (unlikely(q == kmp->pattern_len)) {
state->offset = consumed + i + 1;
return state->offset - kmp->pattern_len;
}
}
consumed += text_len;
}
return UINT_MAX;
}
static inline void compute_prefix_tbl(const u8 *pattern, unsigned int len,
unsigned int *prefix_tbl)
{
unsigned int k, q;
for (k = 0, q = 1; q < len; q++) {
while (k > 0 && pattern[k] != pattern[q])
k = prefix_tbl[k-1];
if (pattern[k] == pattern[q])
k++;
prefix_tbl[q] = k;
}
}
static struct ts_config *kmp_init(const void *pattern, unsigned int len,
int gfp_mask)
{
struct ts_config *conf;
struct ts_kmp *kmp;
unsigned int prefix_tbl_len = len * sizeof(unsigned int);
size_t priv_size = sizeof(*kmp) + len + prefix_tbl_len;
conf = alloc_ts_config(priv_size, gfp_mask);
if (IS_ERR(conf))
return conf;
kmp = ts_config_priv(conf);
kmp->pattern_len = len;
compute_prefix_tbl(pattern, len, kmp->prefix_tbl);
kmp->pattern = (u8 *) kmp->prefix_tbl + prefix_tbl_len;
memcpy(kmp->pattern, pattern, len);
return conf;
}
static void *kmp_get_pattern(struct ts_config *conf)
{
struct ts_kmp *kmp = ts_config_priv(conf);
return kmp->pattern;
}
static unsigned int kmp_get_pattern_len(struct ts_config *conf)
{
struct ts_kmp *kmp = ts_config_priv(conf);
return kmp->pattern_len;
}
static struct ts_ops kmp_ops = {
.name = "kmp",
.find = kmp_find,
.init = kmp_init,
.get_pattern = kmp_get_pattern,
.get_pattern_len = kmp_get_pattern_len,
.owner = THIS_MODULE,
.list = LIST_HEAD_INIT(kmp_ops.list)
};
static int __init init_kmp(void)
{
return textsearch_register(&kmp_ops);
}
static void __exit exit_kmp(void)
{
textsearch_unregister(&kmp_ops);
}
MODULE_LICENSE("GPL");
module_init(init_kmp);
module_exit(exit_kmp);
...@@ -115,18 +115,6 @@ ...@@ -115,18 +115,6 @@
#endif /* CONFIG_NET_RADIO */ #endif /* CONFIG_NET_RADIO */
#include <asm/current.h> #include <asm/current.h>
/* This define, if set, will randomly drop a packet when congestion
* is more than moderate. It helps fairness in the multi-interface
* case when one of them is a hog, but it kills performance for the
* single interface case so it is off now by default.
*/
#undef RAND_LIE
/* Setting this will sample the queue lengths and thus congestion
* via a timer instead of as each packet is received.
*/
#undef OFFLINE_SAMPLE
/* /*
* The list of packet types we will receive (as opposed to discard) * The list of packet types we will receive (as opposed to discard)
* and the routines to invoke. * and the routines to invoke.
...@@ -159,11 +147,6 @@ static DEFINE_SPINLOCK(ptype_lock); ...@@ -159,11 +147,6 @@ static DEFINE_SPINLOCK(ptype_lock);
static struct list_head ptype_base[16]; /* 16 way hashed list */ static struct list_head ptype_base[16]; /* 16 way hashed list */
static struct list_head ptype_all; /* Taps */ static struct list_head ptype_all; /* Taps */
#ifdef OFFLINE_SAMPLE
static void sample_queue(unsigned long dummy);
static struct timer_list samp_timer = TIMER_INITIALIZER(sample_queue, 0, 0);
#endif
/* /*
* The @dev_base list is protected by @dev_base_lock and the rtln * The @dev_base list is protected by @dev_base_lock and the rtln
* semaphore. * semaphore.
...@@ -215,7 +198,7 @@ static struct notifier_block *netdev_chain; ...@@ -215,7 +198,7 @@ static struct notifier_block *netdev_chain;
* Device drivers call our routines to queue packets here. We empty the * Device drivers call our routines to queue packets here. We empty the
* queue in the local softnet handler. * queue in the local softnet handler.
*/ */
DEFINE_PER_CPU(struct softnet_data, softnet_data) = { 0, }; DEFINE_PER_CPU(struct softnet_data, softnet_data) = { NULL };
#ifdef CONFIG_SYSFS #ifdef CONFIG_SYSFS
extern int netdev_sysfs_init(void); extern int netdev_sysfs_init(void);
...@@ -1363,71 +1346,13 @@ out: ...@@ -1363,71 +1346,13 @@ out:
Receiver routines Receiver routines
=======================================================================*/ =======================================================================*/
int netdev_max_backlog = 300; int netdev_max_backlog = 1000;
int netdev_budget = 300;
int weight_p = 64; /* old backlog weight */ int weight_p = 64; /* old backlog weight */
/* These numbers are selected based on intuition and some
* experimentatiom, if you have more scientific way of doing this
* please go ahead and fix things.
*/
int no_cong_thresh = 10;
int no_cong = 20;
int lo_cong = 100;
int mod_cong = 290;
DEFINE_PER_CPU(struct netif_rx_stats, netdev_rx_stat) = { 0, }; DEFINE_PER_CPU(struct netif_rx_stats, netdev_rx_stat) = { 0, };
static void get_sample_stats(int cpu)
{
#ifdef RAND_LIE
unsigned long rd;
int rq;
#endif
struct softnet_data *sd = &per_cpu(softnet_data, cpu);
int blog = sd->input_pkt_queue.qlen;
int avg_blog = sd->avg_blog;
avg_blog = (avg_blog >> 1) + (blog >> 1);
if (avg_blog > mod_cong) {
/* Above moderate congestion levels. */
sd->cng_level = NET_RX_CN_HIGH;
#ifdef RAND_LIE
rd = net_random();
rq = rd % netdev_max_backlog;
if (rq < avg_blog) /* unlucky bastard */
sd->cng_level = NET_RX_DROP;
#endif
} else if (avg_blog > lo_cong) {
sd->cng_level = NET_RX_CN_MOD;
#ifdef RAND_LIE
rd = net_random();
rq = rd % netdev_max_backlog;
if (rq < avg_blog) /* unlucky bastard */
sd->cng_level = NET_RX_CN_HIGH;
#endif
} else if (avg_blog > no_cong)
sd->cng_level = NET_RX_CN_LOW;
else /* no congestion */
sd->cng_level = NET_RX_SUCCESS;
sd->avg_blog = avg_blog;
}
#ifdef OFFLINE_SAMPLE
static void sample_queue(unsigned long dummy)
{
/* 10 ms 0r 1ms -- i don't care -- JHS */
int next_tick = 1;
int cpu = smp_processor_id();
get_sample_stats(cpu);
next_tick += jiffies;
mod_timer(&samp_timer, next_tick);
}
#endif
/** /**
* netif_rx - post buffer to the network code * netif_rx - post buffer to the network code
* @skb: buffer to post * @skb: buffer to post
...@@ -1448,7 +1373,6 @@ static void sample_queue(unsigned long dummy) ...@@ -1448,7 +1373,6 @@ static void sample_queue(unsigned long dummy)
int netif_rx(struct sk_buff *skb) int netif_rx(struct sk_buff *skb)
{ {
int this_cpu;
struct softnet_data *queue; struct softnet_data *queue;
unsigned long flags; unsigned long flags;
...@@ -1464,38 +1388,22 @@ int netif_rx(struct sk_buff *skb) ...@@ -1464,38 +1388,22 @@ int netif_rx(struct sk_buff *skb)
* short when CPU is congested, but is still operating. * short when CPU is congested, but is still operating.
*/ */
local_irq_save(flags); local_irq_save(flags);
this_cpu = smp_processor_id();
queue = &__get_cpu_var(softnet_data); queue = &__get_cpu_var(softnet_data);
__get_cpu_var(netdev_rx_stat).total++; __get_cpu_var(netdev_rx_stat).total++;
if (queue->input_pkt_queue.qlen <= netdev_max_backlog) { if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
if (queue->input_pkt_queue.qlen) { if (queue->input_pkt_queue.qlen) {
if (queue->throttle)
goto drop;
enqueue: enqueue:
dev_hold(skb->dev); dev_hold(skb->dev);
__skb_queue_tail(&queue->input_pkt_queue, skb); __skb_queue_tail(&queue->input_pkt_queue, skb);
#ifndef OFFLINE_SAMPLE
get_sample_stats(this_cpu);
#endif
local_irq_restore(flags); local_irq_restore(flags);
return queue->cng_level; return NET_RX_SUCCESS;
} }
if (queue->throttle)
queue->throttle = 0;
netif_rx_schedule(&queue->backlog_dev); netif_rx_schedule(&queue->backlog_dev);
goto enqueue; goto enqueue;
} }
if (!queue->throttle) {
queue->throttle = 1;
__get_cpu_var(netdev_rx_stat).throttled++;
}
drop:
__get_cpu_var(netdev_rx_stat).dropped++; __get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags); local_irq_restore(flags);
...@@ -1780,8 +1688,6 @@ job_done: ...@@ -1780,8 +1688,6 @@ job_done:
smp_mb__before_clear_bit(); smp_mb__before_clear_bit();
netif_poll_enable(backlog_dev); netif_poll_enable(backlog_dev);
if (queue->throttle)
queue->throttle = 0;
local_irq_enable(); local_irq_enable();
return 0; return 0;
} }
...@@ -1790,8 +1696,7 @@ static void net_rx_action(struct softirq_action *h) ...@@ -1790,8 +1696,7 @@ static void net_rx_action(struct softirq_action *h)
{ {
struct softnet_data *queue = &__get_cpu_var(softnet_data); struct softnet_data *queue = &__get_cpu_var(softnet_data);
unsigned long start_time = jiffies; unsigned long start_time = jiffies;
int budget = netdev_max_backlog; int budget = netdev_budget;
local_irq_disable(); local_irq_disable();
...@@ -2055,15 +1960,9 @@ static int softnet_seq_show(struct seq_file *seq, void *v) ...@@ -2055,15 +1960,9 @@ static int softnet_seq_show(struct seq_file *seq, void *v)
struct netif_rx_stats *s = v; struct netif_rx_stats *s = v;
seq_printf(seq, "%08x %08x %08x %08x %08x %08x %08x %08x %08x\n", seq_printf(seq, "%08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
s->total, s->dropped, s->time_squeeze, s->throttled, s->total, s->dropped, s->time_squeeze, 0,
s->fastroute_hit, s->fastroute_success, s->fastroute_defer, 0, 0, 0, 0, /* was fastroute */
s->fastroute_deferred_out, s->cpu_collision );
#if 0
s->fastroute_latency_reduction
#else
s->cpu_collision
#endif
);
return 0; return 0;
} }
...@@ -3305,9 +3204,6 @@ static int __init net_dev_init(void) ...@@ -3305,9 +3204,6 @@ static int __init net_dev_init(void)
queue = &per_cpu(softnet_data, i); queue = &per_cpu(softnet_data, i);
skb_queue_head_init(&queue->input_pkt_queue); skb_queue_head_init(&queue->input_pkt_queue);
queue->throttle = 0;
queue->cng_level = 0;
queue->avg_blog = 10; /* arbitrary non-zero */
queue->completion_queue = NULL; queue->completion_queue = NULL;
INIT_LIST_HEAD(&queue->poll_list); INIT_LIST_HEAD(&queue->poll_list);
set_bit(__LINK_STATE_START, &queue->backlog_dev.state); set_bit(__LINK_STATE_START, &queue->backlog_dev.state);
...@@ -3316,11 +3212,6 @@ static int __init net_dev_init(void) ...@@ -3316,11 +3212,6 @@ static int __init net_dev_init(void)
atomic_set(&queue->backlog_dev.refcnt, 1); atomic_set(&queue->backlog_dev.refcnt, 1);
} }
#ifdef OFFLINE_SAMPLE
samp_timer.expires = jiffies + (10 * HZ);
add_timer(&samp_timer);
#endif
dev_boot_phase = 0; dev_boot_phase = 0;
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL); open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
......
...@@ -1500,6 +1500,159 @@ void skb_split(struct sk_buff *skb, struct sk_buff *skb1, const u32 len) ...@@ -1500,6 +1500,159 @@ void skb_split(struct sk_buff *skb, struct sk_buff *skb1, const u32 len)
skb_split_no_header(skb, skb1, len, pos); skb_split_no_header(skb, skb1, len, pos);
} }
/**
* skb_prepare_seq_read - Prepare a sequential read of skb data
* @skb: the buffer to read
* @from: lower offset of data to be read
* @to: upper offset of data to be read
* @st: state variable
*
* Initializes the specified state variable. Must be called before
* invoking skb_seq_read() for the first time.
*/
void skb_prepare_seq_read(struct sk_buff *skb, unsigned int from,
unsigned int to, struct skb_seq_state *st)
{
st->lower_offset = from;
st->upper_offset = to;
st->root_skb = st->cur_skb = skb;
st->frag_idx = st->stepped_offset = 0;
st->frag_data = NULL;
}
/**
* skb_seq_read - Sequentially read skb data
* @consumed: number of bytes consumed by the caller so far
* @data: destination pointer for data to be returned
* @st: state variable
*
* Reads a block of skb data at &consumed relative to the
* lower offset specified to skb_prepare_seq_read(). Assigns
* the head of the data block to &data and returns the length
* of the block or 0 if the end of the skb data or the upper
* offset has been reached.
*
* The caller is not required to consume all of the data
* returned, i.e. &consumed is typically set to the number
* of bytes already consumed and the next call to
* skb_seq_read() will return the remaining part of the block.
*
* Note: The size of each block of data returned can be arbitary,
* this limitation is the cost for zerocopy seqeuental
* reads of potentially non linear data.
*
* Note: Fragment lists within fragments are not implemented
* at the moment, state->root_skb could be replaced with
* a stack for this purpose.
*/
unsigned int skb_seq_read(unsigned int consumed, const u8 **data,
struct skb_seq_state *st)
{
unsigned int block_limit, abs_offset = consumed + st->lower_offset;
skb_frag_t *frag;
if (unlikely(abs_offset >= st->upper_offset))
return 0;
next_skb:
block_limit = skb_headlen(st->cur_skb);
if (abs_offset < block_limit) {
*data = st->cur_skb->data + abs_offset;
return block_limit - abs_offset;
}
if (st->frag_idx == 0 && !st->frag_data)
st->stepped_offset += skb_headlen(st->cur_skb);
while (st->frag_idx < skb_shinfo(st->cur_skb)->nr_frags) {
frag = &skb_shinfo(st->cur_skb)->frags[st->frag_idx];
block_limit = frag->size + st->stepped_offset;
if (abs_offset < block_limit) {
if (!st->frag_data)
st->frag_data = kmap_skb_frag(frag);
*data = (u8 *) st->frag_data + frag->page_offset +
(abs_offset - st->stepped_offset);
return block_limit - abs_offset;
}
if (st->frag_data) {
kunmap_skb_frag(st->frag_data);
st->frag_data = NULL;
}
st->frag_idx++;
st->stepped_offset += frag->size;
}
if (st->cur_skb->next) {
st->cur_skb = st->cur_skb->next;
st->frag_idx = 0;
goto next_skb;
} else if (st->root_skb == st->cur_skb &&
skb_shinfo(st->root_skb)->frag_list) {
st->cur_skb = skb_shinfo(st->root_skb)->frag_list;
goto next_skb;
}
return 0;
}
/**
* skb_abort_seq_read - Abort a sequential read of skb data
* @st: state variable
*
* Must be called if skb_seq_read() was not called until it
* returned 0.
*/
void skb_abort_seq_read(struct skb_seq_state *st)
{
if (st->frag_data)
kunmap_skb_frag(st->frag_data);
}
#define TS_SKB_CB(state) ((struct skb_seq_state *) &((state)->cb))
static unsigned int skb_ts_get_next_block(unsigned int offset, const u8 **text,
struct ts_config *conf,
struct ts_state *state)
{
return skb_seq_read(offset, text, TS_SKB_CB(state));
}
static void skb_ts_finish(struct ts_config *conf, struct ts_state *state)
{
skb_abort_seq_read(TS_SKB_CB(state));
}
/**
* skb_find_text - Find a text pattern in skb data
* @skb: the buffer to look in
* @from: search offset
* @to: search limit
* @config: textsearch configuration
* @state: uninitialized textsearch state variable
*
* Finds a pattern in the skb data according to the specified
* textsearch configuration. Use textsearch_next() to retrieve
* subsequent occurrences of the pattern. Returns the offset
* to the first occurrence or UINT_MAX if no match was found.
*/
unsigned int skb_find_text(struct sk_buff *skb, unsigned int from,
unsigned int to, struct ts_config *config,
struct ts_state *state)
{
config->get_next_block = skb_ts_get_next_block;
config->finish = skb_ts_finish;
skb_prepare_seq_read(skb, from, to, TS_SKB_CB(state));
return textsearch_find(config, state);
}
void __init skb_init(void) void __init skb_init(void)
{ {
skbuff_head_cache = kmem_cache_create("skbuff_head_cache", skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
...@@ -1538,3 +1691,7 @@ EXPORT_SYMBOL(skb_queue_tail); ...@@ -1538,3 +1691,7 @@ EXPORT_SYMBOL(skb_queue_tail);
EXPORT_SYMBOL(skb_unlink); EXPORT_SYMBOL(skb_unlink);
EXPORT_SYMBOL(skb_append); EXPORT_SYMBOL(skb_append);
EXPORT_SYMBOL(skb_split); EXPORT_SYMBOL(skb_split);
EXPORT_SYMBOL(skb_prepare_seq_read);
EXPORT_SYMBOL(skb_seq_read);
EXPORT_SYMBOL(skb_abort_seq_read);
EXPORT_SYMBOL(skb_find_text);
...@@ -13,12 +13,8 @@ ...@@ -13,12 +13,8 @@
#ifdef CONFIG_SYSCTL #ifdef CONFIG_SYSCTL
extern int netdev_max_backlog; extern int netdev_max_backlog;
extern int netdev_budget;
extern int weight_p; extern int weight_p;
extern int no_cong_thresh;
extern int no_cong;
extern int lo_cong;
extern int mod_cong;
extern int netdev_fastroute;
extern int net_msg_cost; extern int net_msg_cost;
extern int net_msg_burst; extern int net_msg_burst;
...@@ -85,38 +81,6 @@ ctl_table core_table[] = { ...@@ -85,38 +81,6 @@ ctl_table core_table[] = {
.mode = 0644, .mode = 0644,
.proc_handler = &proc_dointvec .proc_handler = &proc_dointvec
}, },
{
.ctl_name = NET_CORE_NO_CONG_THRESH,
.procname = "no_cong_thresh",
.data = &no_cong_thresh,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{
.ctl_name = NET_CORE_NO_CONG,
.procname = "no_cong",
.data = &no_cong,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{
.ctl_name = NET_CORE_LO_CONG,
.procname = "lo_cong",
.data = &lo_cong,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{
.ctl_name = NET_CORE_MOD_CONG,
.procname = "mod_cong",
.data = &mod_cong,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{ {
.ctl_name = NET_CORE_MSG_COST, .ctl_name = NET_CORE_MSG_COST,
.procname = "message_cost", .procname = "message_cost",
...@@ -161,6 +125,14 @@ ctl_table core_table[] = { ...@@ -161,6 +125,14 @@ ctl_table core_table[] = {
.mode = 0644, .mode = 0644,
.proc_handler = &proc_dointvec .proc_handler = &proc_dointvec
}, },
{
.ctl_name = NET_CORE_BUDGET,
.procname = "netdev_budget",
.data = &netdev_budget,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = &proc_dointvec
},
{ .ctl_name = 0 } { .ctl_name = 0 }
}; };
......
...@@ -1927,6 +1927,25 @@ int tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval, ...@@ -1927,6 +1927,25 @@ int tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
return tp->af_specific->setsockopt(sk, level, optname, return tp->af_specific->setsockopt(sk, level, optname,
optval, optlen); optval, optlen);
/* This is a string value all the others are int's */
if (optname == TCP_CONGESTION) {
char name[TCP_CA_NAME_MAX];
if (optlen < 1)
return -EINVAL;
val = strncpy_from_user(name, optval,
min(TCP_CA_NAME_MAX-1, optlen));
if (val < 0)
return -EFAULT;
name[val] = 0;
lock_sock(sk);
err = tcp_set_congestion_control(tp, name);
release_sock(sk);
return err;
}
if (optlen < sizeof(int)) if (optlen < sizeof(int))
return -EINVAL; return -EINVAL;
...@@ -2211,6 +2230,16 @@ int tcp_getsockopt(struct sock *sk, int level, int optname, char __user *optval, ...@@ -2211,6 +2230,16 @@ int tcp_getsockopt(struct sock *sk, int level, int optname, char __user *optval,
case TCP_QUICKACK: case TCP_QUICKACK:
val = !tp->ack.pingpong; val = !tp->ack.pingpong;
break; break;
case TCP_CONGESTION:
if (get_user(len, optlen))
return -EFAULT;
len = min_t(unsigned int, len, TCP_CA_NAME_MAX);
if (put_user(len, optlen))
return -EFAULT;
if (copy_to_user(optval, tp->ca_ops->name, len))
return -EFAULT;
return 0;
default: default:
return -ENOPROTOOPT; return -ENOPROTOOPT;
}; };
...@@ -2224,7 +2253,7 @@ int tcp_getsockopt(struct sock *sk, int level, int optname, char __user *optval, ...@@ -2224,7 +2253,7 @@ int tcp_getsockopt(struct sock *sk, int level, int optname, char __user *optval,
extern void __skb_cb_too_small_for_tcp(int, int); extern void __skb_cb_too_small_for_tcp(int, int);
extern void tcpdiag_init(void); extern struct tcp_congestion_ops tcp_reno;
static __initdata unsigned long thash_entries; static __initdata unsigned long thash_entries;
static int __init set_thash_entries(char *str) static int __init set_thash_entries(char *str)
......
...@@ -21,7 +21,7 @@ static struct tcp_congestion_ops *tcp_ca_find(const char *name) ...@@ -21,7 +21,7 @@ static struct tcp_congestion_ops *tcp_ca_find(const char *name)
{ {
struct tcp_congestion_ops *e; struct tcp_congestion_ops *e;
list_for_each_entry(e, &tcp_cong_list, list) { list_for_each_entry_rcu(e, &tcp_cong_list, list) {
if (strcmp(e->name, name) == 0) if (strcmp(e->name, name) == 0)
return e; return e;
} }
...@@ -77,6 +77,9 @@ void tcp_init_congestion_control(struct tcp_sock *tp) ...@@ -77,6 +77,9 @@ void tcp_init_congestion_control(struct tcp_sock *tp)
{ {
struct tcp_congestion_ops *ca; struct tcp_congestion_ops *ca;
if (tp->ca_ops != &tcp_init_congestion_ops)
return;
rcu_read_lock(); rcu_read_lock();
list_for_each_entry_rcu(ca, &tcp_cong_list, list) { list_for_each_entry_rcu(ca, &tcp_cong_list, list) {
if (try_module_get(ca->owner)) { if (try_module_get(ca->owner)) {
...@@ -139,6 +142,34 @@ void tcp_get_default_congestion_control(char *name) ...@@ -139,6 +142,34 @@ void tcp_get_default_congestion_control(char *name)
rcu_read_unlock(); rcu_read_unlock();
} }
/* Change congestion control for socket */
int tcp_set_congestion_control(struct tcp_sock *tp, const char *name)
{
struct tcp_congestion_ops *ca;
int err = 0;
rcu_read_lock();
ca = tcp_ca_find(name);
if (ca == tp->ca_ops)
goto out;
if (!ca)
err = -ENOENT;
else if (!try_module_get(ca->owner))
err = -EBUSY;
else {
tcp_cleanup_congestion_control(tp);
tp->ca_ops = ca;
if (tp->ca_ops->init)
tp->ca_ops->init(tp);
}
out:
rcu_read_unlock();
return err;
}
/* /*
* TCP Reno congestion control * TCP Reno congestion control
* This is special case used for fallback as well. * This is special case used for fallback as well.
...@@ -192,4 +223,15 @@ struct tcp_congestion_ops tcp_reno = { ...@@ -192,4 +223,15 @@ struct tcp_congestion_ops tcp_reno = {
.min_cwnd = tcp_reno_min_cwnd, .min_cwnd = tcp_reno_min_cwnd,
}; };
EXPORT_SYMBOL_GPL(tcp_reno); /* Initial congestion control used (until SYN)
* really reno under another name so we can tell difference
* during tcp_set_default_congestion_control
*/
struct tcp_congestion_ops tcp_init_congestion_ops = {
.name = "",
.owner = THIS_MODULE,
.ssthresh = tcp_reno_ssthresh,
.cong_avoid = tcp_reno_cong_avoid,
.min_cwnd = tcp_reno_min_cwnd,
};
EXPORT_SYMBOL_GPL(tcp_init_congestion_ops);
...@@ -2048,7 +2048,7 @@ static int tcp_v4_init_sock(struct sock *sk) ...@@ -2048,7 +2048,7 @@ static int tcp_v4_init_sock(struct sock *sk)
tp->mss_cache_std = tp->mss_cache = 536; tp->mss_cache_std = tp->mss_cache = 536;
tp->reordering = sysctl_tcp_reordering; tp->reordering = sysctl_tcp_reordering;
tp->ca_ops = &tcp_reno; tp->ca_ops = &tcp_init_congestion_ops;
sk->sk_state = TCP_CLOSE; sk->sk_state = TCP_CLOSE;
......
...@@ -2025,7 +2025,7 @@ static int tcp_v6_init_sock(struct sock *sk) ...@@ -2025,7 +2025,7 @@ static int tcp_v6_init_sock(struct sock *sk)
sk->sk_state = TCP_CLOSE; sk->sk_state = TCP_CLOSE;
tp->af_specific = &ipv6_specific; tp->af_specific = &ipv6_specific;
tp->ca_ops = &tcp_reno; tp->ca_ops = &tcp_init_congestion_ops;
sk->sk_write_space = sk_stream_write_space; sk->sk_write_space = sk_stream_write_space;
sock_set_flag(sk, SOCK_USE_WRITE_QUEUE); sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
......
...@@ -449,6 +449,18 @@ config NET_EMATCH_META ...@@ -449,6 +449,18 @@ config NET_EMATCH_META
To compile this code as a module, choose M here: the To compile this code as a module, choose M here: the
module will be called em_meta. module will be called em_meta.
config NET_EMATCH_TEXT
tristate "Textsearch"
depends on NET_EMATCH
select TEXTSEARCH
---help---
Say Y here if you want to be ablt to classify packets based on
textsearch comparisons. Please select the appropriate textsearch
algorithms in the Library section.
To compile this code as a module, choose M here: the
module will be called em_text.
config NET_CLS_ACT config NET_CLS_ACT
bool "Packet ACTION" bool "Packet ACTION"
depends on EXPERIMENTAL && NET_CLS && NET_QOS depends on EXPERIMENTAL && NET_CLS && NET_QOS
......
...@@ -40,3 +40,4 @@ obj-$(CONFIG_NET_EMATCH_CMP) += em_cmp.o ...@@ -40,3 +40,4 @@ obj-$(CONFIG_NET_EMATCH_CMP) += em_cmp.o
obj-$(CONFIG_NET_EMATCH_NBYTE) += em_nbyte.o obj-$(CONFIG_NET_EMATCH_NBYTE) += em_nbyte.o
obj-$(CONFIG_NET_EMATCH_U32) += em_u32.o obj-$(CONFIG_NET_EMATCH_U32) += em_u32.o
obj-$(CONFIG_NET_EMATCH_META) += em_meta.o obj-$(CONFIG_NET_EMATCH_META) += em_meta.o
obj-$(CONFIG_NET_EMATCH_TEXT) += em_text.o
/*
* net/sched/em_text.c Textsearch ematch
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
* Authors: Thomas Graf <tgraf@suug.ch>
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/skbuff.h>
#include <linux/textsearch.h>
#include <linux/tc_ematch/tc_em_text.h>
#include <net/pkt_cls.h>
struct text_match
{
u16 from_offset;
u16 to_offset;
u8 from_layer;
u8 to_layer;
struct ts_config *config;
};
#define EM_TEXT_PRIV(m) ((struct text_match *) (m)->data)
static int em_text_match(struct sk_buff *skb, struct tcf_ematch *m,
struct tcf_pkt_info *info)
{
struct text_match *tm = EM_TEXT_PRIV(m);
int from, to;
struct ts_state state;
from = tcf_get_base_ptr(skb, tm->from_layer) - skb->data;
from += tm->from_offset;
to = tcf_get_base_ptr(skb, tm->to_layer) - skb->data;
to += tm->to_offset;
return skb_find_text(skb, from, to, tm->config, &state) != UINT_MAX;
}
static int em_text_change(struct tcf_proto *tp, void *data, int len,
struct tcf_ematch *m)
{
struct text_match *tm;
struct tcf_em_text *conf = data;
struct ts_config *ts_conf;
int flags = 0;
printk("Configuring text: %s from %d:%d to %d:%d len %d\n", conf->algo, conf->from_offset,
conf->from_layer, conf->to_offset, conf->to_layer, conf->pattern_len);
if (len < sizeof(*conf) || len < (sizeof(*conf) + conf->pattern_len))
return -EINVAL;
if (conf->from_layer > conf->to_layer)
return -EINVAL;
if (conf->from_layer == conf->to_layer &&
conf->from_offset > conf->to_offset)
return -EINVAL;
retry:
ts_conf = textsearch_prepare(conf->algo, (u8 *) conf + sizeof(*conf),
conf->pattern_len, GFP_KERNEL, flags);
if (flags & TS_AUTOLOAD)
rtnl_lock();
if (IS_ERR(ts_conf)) {
if (PTR_ERR(ts_conf) == -ENOENT && !(flags & TS_AUTOLOAD)) {
rtnl_unlock();
flags |= TS_AUTOLOAD;
goto retry;
} else
return PTR_ERR(ts_conf);
} else if (flags & TS_AUTOLOAD) {
textsearch_destroy(ts_conf);
return -EAGAIN;
}
tm = kmalloc(sizeof(*tm), GFP_KERNEL);
if (tm == NULL) {
textsearch_destroy(ts_conf);
return -ENOBUFS;
}
tm->from_offset = conf->from_offset;
tm->to_offset = conf->to_offset;
tm->from_layer = conf->from_layer;
tm->to_layer = conf->to_layer;
tm->config = ts_conf;
m->datalen = sizeof(*tm);
m->data = (unsigned long) tm;
return 0;
}
static void em_text_destroy(struct tcf_proto *tp, struct tcf_ematch *m)
{
textsearch_destroy(EM_TEXT_PRIV(m)->config);
}
static int em_text_dump(struct sk_buff *skb, struct tcf_ematch *m)
{
struct text_match *tm = EM_TEXT_PRIV(m);
struct tcf_em_text conf;
strncpy(conf.algo, tm->config->ops->name, sizeof(conf.algo) - 1);
conf.from_offset = tm->from_offset;
conf.to_offset = tm->to_offset;
conf.from_layer = tm->from_layer;
conf.to_layer = tm->to_layer;
conf.pattern_len = textsearch_get_pattern_len(tm->config);
conf.pad = 0;
RTA_PUT_NOHDR(skb, sizeof(conf), &conf);
RTA_APPEND(skb, conf.pattern_len, textsearch_get_pattern(tm->config));
return 0;
rtattr_failure:
return -1;
}
static struct tcf_ematch_ops em_text_ops = {
.kind = TCF_EM_TEXT,
.change = em_text_change,
.match = em_text_match,
.destroy = em_text_destroy,
.dump = em_text_dump,
.owner = THIS_MODULE,
.link = LIST_HEAD_INIT(em_text_ops.link)
};
static int __init init_em_text(void)
{
return tcf_em_register(&em_text_ops);
}
static void __exit exit_em_text(void)
{
tcf_em_unregister(&em_text_ops);
}
MODULE_LICENSE("GPL");
module_init(init_em_text);
module_exit(exit_em_text);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment