Commit 2e60314c authored by David S. Miller's avatar David S. Miller

Merge branch 'process-connector-bug-fixes-and-enhancements'

Anjali Kulkarni says:

====================
Process connector bug fixes & enhancements

Oracle DB is trying to solve a performance overhead problem it has been
facing for the past 10 years and using this patch series, we can fix this
issue.

Oracle DB runs on a large scale with 100000s of short lived processes,
starting up and exiting quickly. A process monitoring DB daemon which
tracks and cleans up after processes that have died without a proper exit
needs notifications only when a process died with a non-zero exit code
(which should be rare).

Due to the pmon architecture, which is distributed, each process is
independent and has minimal interaction with pmon. Hence fd based
solutions to track a process's spawning and exit cannot be used. Pmon
needs to detect the abnormal death of a process so it can cleanup after.
Currently it resorts to checking /proc every few seconds. Other methods
we tried like using system call to reduce the above overhead were not
accepted upstream.

With this change, we add event based filtering to proc connector module
so that DB can only listen to the events it is interested in. A new
event type PROC_EVENT_NONZERO_EXIT is added, which is only sent by kernel
to a listening application when any process exiting has a non-zero exit
status.

This change will give Oracle DB substantial performance savings - it takes
50ms to scan about 8K PIDs in /proc, about 500ms for 100K PIDs. DB does
this check every 3 secs, so over an hour we save 10secs for 100K PIDs.

With this, a client can register to listen for only exit or fork or a mix or
all of the events. This greatly enhances performance - currently, we
need to listen to all events, and there are 9 different types of events.
For eg. handling 3 types of events - 8K-forks + 8K-exits + 8K-execs takes
200ms, whereas handling 2 types - 8K-forks + 8K-exits takes about 150ms,
and handling just one type - 8K exits takes about 70ms.

Measuring the time using pidfds for monitoring 8K process exits took 4
times longer - 200ms, as compared to 70ms using only exit notifications
of proc connector. Hence, we cannot use pidfd for our use case.

This kind of a new event could also be useful to other applications like
Google's lmkd daemon, which needs a killed process's exit notification.

This patch series is organized as follows -

Patch 1 : Needed for patch 3 to work.
Patch 2 : Needed for patch 3 to work.
Patch 3 : Fixes some bugs in proc connector, details in the patch.
Patch 4 : Adds event based filtering for performance enhancements.
Patch 5 : Allow non-root users access to proc connector events.
Patch 6 : Selftest code for proc connector.

v9->v10 changes:
- Rebased to net-next, re-compiled and re-tested.

v8->v9 changes:
- Added sha1 ("title") of reversed patch as suggested by Eric Dumazet.

v7->v8 changes:
- Fixed an issue pointed by Liam Howlett in v7.

v6->v7 changes:
- Incorporated Liam Howlett's comments on v6
- Incorporated Kalesh Anakkur Purayil's comments

v5->v6 changes:
- Incorporated Liam Howlett's comments
- Removed FILTER define from proc_filter.c and added a "-f" run-time
  option to run new filter code.
- Made proc_filter.c a selftest in tools/testing/selftests/connector

v4->v5 changes:
- Change the cover letter
- Fix a small issue in proc_filter.c

v3->v4 changes:
- Fix comments by Jakub Kicinski to incorporate root access changes
  within bind call of connector

v2->v3 changes:
- Fix comments by Jakub Kicinski to separate netlink (patch 2) (after
  layering) from connector fixes (patch 3).
- Minor fixes suggested by Jakub.
- Add new multicast group level permissions check at netlink layer.
  Split this into netlink & connector layers (patches 6 & 7)

v1->v2 changes:
- Fix comments by Jakub Kicinski to keep layering within netlink and
  update kdocs.
- Move non-root users access patch last in series so remaining patches
  can go in first.

v->v1 changes:
- Changed commit log in patch 4 as suggested by Christian Brauner
- Changed patch 4 to make more fine grained access to non-root users
- Fixed warning in cn_proc.c,
Reported-by: default avatarkernel test robot <lkp@intel.com>
- Fixed some existing warnings in cn_proc.c
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 6bfef2ec 73a29531
...@@ -48,8 +48,47 @@ static DEFINE_PER_CPU(struct local_event, local_event) = { ...@@ -48,8 +48,47 @@ static DEFINE_PER_CPU(struct local_event, local_event) = {
.lock = INIT_LOCAL_LOCK(lock), .lock = INIT_LOCAL_LOCK(lock),
}; };
static int cn_filter(struct sock *dsk, struct sk_buff *skb, void *data)
{
__u32 what, exit_code, *ptr;
enum proc_cn_mcast_op mc_op;
uintptr_t val;
if (!dsk || !data)
return 0;
ptr = (__u32 *)data;
what = *ptr++;
exit_code = *ptr;
val = ((struct proc_input *)(dsk->sk_user_data))->event_type;
mc_op = ((struct proc_input *)(dsk->sk_user_data))->mcast_op;
if (mc_op == PROC_CN_MCAST_IGNORE)
return 1;
if ((__u32)val == PROC_EVENT_ALL)
return 0;
/*
* Drop packet if we have to report only non-zero exit status
* (PROC_EVENT_NONZERO_EXIT) and exit status is 0
*/
if (((__u32)val & PROC_EVENT_NONZERO_EXIT) &&
(what == PROC_EVENT_EXIT)) {
if (exit_code)
return 0;
}
if ((__u32)val & what)
return 0;
return 1;
}
static inline void send_msg(struct cn_msg *msg) static inline void send_msg(struct cn_msg *msg)
{ {
__u32 filter_data[2];
local_lock(&local_event.lock); local_lock(&local_event.lock);
msg->seq = __this_cpu_inc_return(local_event.count) - 1; msg->seq = __this_cpu_inc_return(local_event.count) - 1;
...@@ -61,7 +100,16 @@ static inline void send_msg(struct cn_msg *msg) ...@@ -61,7 +100,16 @@ static inline void send_msg(struct cn_msg *msg)
* *
* If cn_netlink_send() fails, the data is not sent. * If cn_netlink_send() fails, the data is not sent.
*/ */
cn_netlink_send(msg, 0, CN_IDX_PROC, GFP_NOWAIT); filter_data[0] = ((struct proc_event *)msg->data)->what;
if (filter_data[0] == PROC_EVENT_EXIT) {
filter_data[1] =
((struct proc_event *)msg->data)->event_data.exit.exit_code;
} else {
filter_data[1] = 0;
}
cn_netlink_send_mult(msg, msg->len, 0, CN_IDX_PROC, GFP_NOWAIT,
cn_filter, (void *)filter_data);
local_unlock(&local_event.lock); local_unlock(&local_event.lock);
} }
...@@ -341,16 +389,17 @@ static void cn_proc_ack(int err, int rcvd_seq, int rcvd_ack) ...@@ -341,16 +389,17 @@ static void cn_proc_ack(int err, int rcvd_seq, int rcvd_ack)
/** /**
* cn_proc_mcast_ctl * cn_proc_mcast_ctl
* @data: message sent from userspace via the connector * @msg: message sent from userspace via the connector
* @nsp: NETLINK_CB of the client's socket buffer
*/ */
static void cn_proc_mcast_ctl(struct cn_msg *msg, static void cn_proc_mcast_ctl(struct cn_msg *msg,
struct netlink_skb_parms *nsp) struct netlink_skb_parms *nsp)
{ {
enum proc_cn_mcast_op *mc_op = NULL; enum proc_cn_mcast_op mc_op = 0, prev_mc_op = 0;
int err = 0; struct proc_input *pinput = NULL;
enum proc_cn_event ev_type = 0;
if (msg->len != sizeof(*mc_op)) int err = 0, initial = 0;
return; struct sock *sk = NULL;
/* /*
* Events are reported with respect to the initial pid * Events are reported with respect to the initial pid
...@@ -361,19 +410,51 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg, ...@@ -361,19 +410,51 @@ static void cn_proc_mcast_ctl(struct cn_msg *msg,
!task_is_in_init_pid_ns(current)) !task_is_in_init_pid_ns(current))
return; return;
/* Can only change if privileged. */ if (msg->len == sizeof(*pinput)) {
if (!__netlink_ns_capable(nsp, &init_user_ns, CAP_NET_ADMIN)) { pinput = (struct proc_input *)msg->data;
err = EPERM; mc_op = pinput->mcast_op;
ev_type = pinput->event_type;
} else if (msg->len == sizeof(mc_op)) {
mc_op = *((enum proc_cn_mcast_op *)msg->data);
ev_type = PROC_EVENT_ALL;
} else {
return;
}
ev_type = valid_event((enum proc_cn_event)ev_type);
if (ev_type == PROC_EVENT_NONE)
ev_type = PROC_EVENT_ALL;
if (nsp->sk) {
sk = nsp->sk;
if (sk->sk_user_data == NULL) {
sk->sk_user_data = kzalloc(sizeof(struct proc_input),
GFP_KERNEL);
if (sk->sk_user_data == NULL) {
err = ENOMEM;
goto out; goto out;
} }
initial = 1;
} else {
prev_mc_op =
((struct proc_input *)(sk->sk_user_data))->mcast_op;
}
((struct proc_input *)(sk->sk_user_data))->event_type =
ev_type;
((struct proc_input *)(sk->sk_user_data))->mcast_op = mc_op;
}
mc_op = (enum proc_cn_mcast_op *)msg->data; switch (mc_op) {
switch (*mc_op) {
case PROC_CN_MCAST_LISTEN: case PROC_CN_MCAST_LISTEN:
if (initial || (prev_mc_op != PROC_CN_MCAST_LISTEN))
atomic_inc(&proc_event_num_listeners); atomic_inc(&proc_event_num_listeners);
break; break;
case PROC_CN_MCAST_IGNORE: case PROC_CN_MCAST_IGNORE:
if (!initial && (prev_mc_op != PROC_CN_MCAST_IGNORE))
atomic_dec(&proc_event_num_listeners); atomic_dec(&proc_event_num_listeners);
((struct proc_input *)(sk->sk_user_data))->event_type =
PROC_EVENT_NONE;
break; break;
default: default:
err = EINVAL; err = EINVAL;
......
...@@ -59,7 +59,9 @@ static int cn_already_initialized; ...@@ -59,7 +59,9 @@ static int cn_already_initialized;
* both, or if both are zero then the group is looked up and sent there. * both, or if both are zero then the group is looked up and sent there.
*/ */
int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group, int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group,
gfp_t gfp_mask) gfp_t gfp_mask,
int (*filter)(struct sock *dsk, struct sk_buff *skb, void *data),
void *filter_data)
{ {
struct cn_callback_entry *__cbq; struct cn_callback_entry *__cbq;
unsigned int size; unsigned int size;
...@@ -110,8 +112,9 @@ int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group, ...@@ -110,8 +112,9 @@ int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group,
NETLINK_CB(skb).dst_group = group; NETLINK_CB(skb).dst_group = group;
if (group) if (group)
return netlink_broadcast(dev->nls, skb, portid, group, return netlink_broadcast_filtered(dev->nls, skb, portid, group,
gfp_mask); gfp_mask, filter,
(void *)filter_data);
return netlink_unicast(dev->nls, skb, portid, return netlink_unicast(dev->nls, skb, portid,
!gfpflags_allow_blocking(gfp_mask)); !gfpflags_allow_blocking(gfp_mask));
} }
...@@ -121,7 +124,8 @@ EXPORT_SYMBOL_GPL(cn_netlink_send_mult); ...@@ -121,7 +124,8 @@ EXPORT_SYMBOL_GPL(cn_netlink_send_mult);
int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group,
gfp_t gfp_mask) gfp_t gfp_mask)
{ {
return cn_netlink_send_mult(msg, msg->len, portid, __group, gfp_mask); return cn_netlink_send_mult(msg, msg->len, portid, __group, gfp_mask,
NULL, NULL);
} }
EXPORT_SYMBOL_GPL(cn_netlink_send); EXPORT_SYMBOL_GPL(cn_netlink_send);
...@@ -162,6 +166,31 @@ static int cn_call_callback(struct sk_buff *skb) ...@@ -162,6 +166,31 @@ static int cn_call_callback(struct sk_buff *skb)
return err; return err;
} }
/*
* Allow non-root access for NETLINK_CONNECTOR family having CN_IDX_PROC
* multicast group.
*/
static int cn_bind(struct net *net, int group)
{
unsigned long groups = (unsigned long) group;
if (ns_capable(net->user_ns, CAP_NET_ADMIN))
return 0;
if (test_bit(CN_IDX_PROC - 1, &groups))
return 0;
return -EPERM;
}
static void cn_release(struct sock *sk, unsigned long *groups)
{
if (groups && test_bit(CN_IDX_PROC - 1, groups)) {
kfree(sk->sk_user_data);
sk->sk_user_data = NULL;
}
}
/* /*
* Main netlink receiving function. * Main netlink receiving function.
* *
...@@ -249,6 +278,9 @@ static int cn_init(void) ...@@ -249,6 +278,9 @@ static int cn_init(void)
struct netlink_kernel_cfg cfg = { struct netlink_kernel_cfg cfg = {
.groups = CN_NETLINK_USERS + 0xf, .groups = CN_NETLINK_USERS + 0xf,
.input = cn_rx_skb, .input = cn_rx_skb,
.flags = NL_CFG_F_NONROOT_RECV,
.bind = cn_bind,
.release = cn_release,
}; };
dev->nls = netlink_kernel_create(&init_net, NETLINK_CONNECTOR, &cfg); dev->nls = netlink_kernel_create(&init_net, NETLINK_CONNECTOR, &cfg);
......
...@@ -65,7 +65,8 @@ static void w1_unref_block(struct w1_cb_block *block) ...@@ -65,7 +65,8 @@ static void w1_unref_block(struct w1_cb_block *block)
u16 len = w1_reply_len(block); u16 len = w1_reply_len(block);
if (len) { if (len) {
cn_netlink_send_mult(block->first_cn, len, cn_netlink_send_mult(block->first_cn, len,
block->portid, 0, GFP_KERNEL); block->portid, 0,
GFP_KERNEL, NULL, NULL);
} }
kfree(block); kfree(block);
} }
...@@ -83,7 +84,8 @@ static void w1_reply_make_space(struct w1_cb_block *block, u16 space) ...@@ -83,7 +84,8 @@ static void w1_reply_make_space(struct w1_cb_block *block, u16 space)
{ {
u16 len = w1_reply_len(block); u16 len = w1_reply_len(block);
if (len + space >= block->maxlen) { if (len + space >= block->maxlen) {
cn_netlink_send_mult(block->first_cn, len, block->portid, 0, GFP_KERNEL); cn_netlink_send_mult(block->first_cn, len, block->portid,
0, GFP_KERNEL, NULL, NULL);
block->first_cn->len = 0; block->first_cn->len = 0;
block->cn = NULL; block->cn = NULL;
block->msg = NULL; block->msg = NULL;
......
...@@ -90,13 +90,19 @@ void cn_del_callback(const struct cb_id *id); ...@@ -90,13 +90,19 @@ void cn_del_callback(const struct cb_id *id);
* If @group is not zero, then message will be delivered * If @group is not zero, then message will be delivered
* to the specified group. * to the specified group.
* @gfp_mask: GFP mask. * @gfp_mask: GFP mask.
* @filter: Filter function to be used at netlink layer.
* @filter_data:Filter data to be supplied to the filter function
* *
* It can be safely called from softirq context, but may silently * It can be safely called from softirq context, but may silently
* fail under strong memory pressure. * fail under strong memory pressure.
* *
* If there are no listeners for given group %-ESRCH can be returned. * If there are no listeners for given group %-ESRCH can be returned.
*/ */
int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 group, gfp_t gfp_mask); int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid,
u32 group, gfp_t gfp_mask,
int (*filter)(struct sock *dsk, struct sk_buff *skb,
void *data),
void *filter_data);
/** /**
* cn_netlink_send - Sends message to the specified groups. * cn_netlink_send - Sends message to the specified groups.
......
...@@ -50,6 +50,7 @@ struct netlink_kernel_cfg { ...@@ -50,6 +50,7 @@ struct netlink_kernel_cfg {
struct mutex *cb_mutex; struct mutex *cb_mutex;
int (*bind)(struct net *net, int group); int (*bind)(struct net *net, int group);
void (*unbind)(struct net *net, int group); void (*unbind)(struct net *net, int group);
void (*release) (struct sock *sk, unsigned long *groups);
}; };
struct sock *__netlink_kernel_create(struct net *net, int unit, struct sock *__netlink_kernel_create(struct net *net, int unit,
...@@ -227,6 +228,11 @@ bool netlink_strict_get_check(struct sk_buff *skb); ...@@ -227,6 +228,11 @@ bool netlink_strict_get_check(struct sk_buff *skb);
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock); int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,
__u32 group, gfp_t allocation); __u32 group, gfp_t allocation);
int netlink_broadcast_filtered(struct sock *ssk, struct sk_buff *skb,
__u32 portid, __u32 group, gfp_t allocation,
int (*filter)(struct sock *dsk,
struct sk_buff *skb, void *data),
void *filter_data);
int netlink_set_err(struct sock *ssk, __u32 portid, __u32 group, int code); int netlink_set_err(struct sock *ssk, __u32 portid, __u32 group, int code);
int netlink_register_notifier(struct notifier_block *nb); int netlink_register_notifier(struct notifier_block *nb);
int netlink_unregister_notifier(struct notifier_block *nb); int netlink_unregister_notifier(struct notifier_block *nb);
......
...@@ -30,21 +30,16 @@ enum proc_cn_mcast_op { ...@@ -30,21 +30,16 @@ enum proc_cn_mcast_op {
PROC_CN_MCAST_IGNORE = 2 PROC_CN_MCAST_IGNORE = 2
}; };
#define PROC_EVENT_ALL (PROC_EVENT_FORK | PROC_EVENT_EXEC | PROC_EVENT_UID | \
PROC_EVENT_GID | PROC_EVENT_SID | PROC_EVENT_PTRACE | \
PROC_EVENT_COMM | PROC_EVENT_NONZERO_EXIT | \
PROC_EVENT_COREDUMP | PROC_EVENT_EXIT)
/* /*
* From the user's point of view, the process * If you add an entry in proc_cn_event, make sure you add it in
* ID is the thread group ID and thread ID is the internal * PROC_EVENT_ALL above as well.
* kernel "pid". So, fields are assigned as follow:
*
* In user space - In kernel space
*
* parent process ID = parent->tgid
* parent thread ID = parent->pid
* child process ID = child->tgid
* child thread ID = child->pid
*/ */
enum proc_cn_event {
struct proc_event {
enum what {
/* Use successive bits so the enums can be used to record /* Use successive bits so the enums can be used to record
* sets of events as well * sets of events as well
*/ */
...@@ -58,10 +53,41 @@ struct proc_event { ...@@ -58,10 +53,41 @@ struct proc_event {
PROC_EVENT_COMM = 0x00000200, PROC_EVENT_COMM = 0x00000200,
/* "next" should be 0x00000400 */ /* "next" should be 0x00000400 */
/* "last" is the last process event: exit, /* "last" is the last process event: exit,
* while "next to last" is coredumping event */ * while "next to last" is coredumping event
* before that is report only if process dies
* with non-zero exit status
*/
PROC_EVENT_NONZERO_EXIT = 0x20000000,
PROC_EVENT_COREDUMP = 0x40000000, PROC_EVENT_COREDUMP = 0x40000000,
PROC_EVENT_EXIT = 0x80000000 PROC_EVENT_EXIT = 0x80000000
} what; };
struct proc_input {
enum proc_cn_mcast_op mcast_op;
enum proc_cn_event event_type;
};
static inline enum proc_cn_event valid_event(enum proc_cn_event ev_type)
{
ev_type &= PROC_EVENT_ALL;
return ev_type;
}
/*
* From the user's point of view, the process
* ID is the thread group ID and thread ID is the internal
* kernel "pid". So, fields are assigned as follow:
*
* In user space - In kernel space
*
* parent process ID = parent->tgid
* parent thread ID = parent->pid
* child process ID = child->tgid
* child thread ID = child->pid
*/
struct proc_event {
enum proc_cn_event what;
__u32 cpu; __u32 cpu;
__u64 __attribute__((aligned(8))) timestamp_ns; __u64 __attribute__((aligned(8))) timestamp_ns;
/* Number of nano seconds since system boot */ /* Number of nano seconds since system boot */
......
...@@ -677,6 +677,7 @@ static int netlink_create(struct net *net, struct socket *sock, int protocol, ...@@ -677,6 +677,7 @@ static int netlink_create(struct net *net, struct socket *sock, int protocol,
struct netlink_sock *nlk; struct netlink_sock *nlk;
int (*bind)(struct net *net, int group); int (*bind)(struct net *net, int group);
void (*unbind)(struct net *net, int group); void (*unbind)(struct net *net, int group);
void (*release)(struct sock *sock, unsigned long *groups);
int err = 0; int err = 0;
sock->state = SS_UNCONNECTED; sock->state = SS_UNCONNECTED;
...@@ -704,6 +705,7 @@ static int netlink_create(struct net *net, struct socket *sock, int protocol, ...@@ -704,6 +705,7 @@ static int netlink_create(struct net *net, struct socket *sock, int protocol,
cb_mutex = nl_table[protocol].cb_mutex; cb_mutex = nl_table[protocol].cb_mutex;
bind = nl_table[protocol].bind; bind = nl_table[protocol].bind;
unbind = nl_table[protocol].unbind; unbind = nl_table[protocol].unbind;
release = nl_table[protocol].release;
netlink_unlock_table(); netlink_unlock_table();
if (err < 0) if (err < 0)
...@@ -719,6 +721,7 @@ static int netlink_create(struct net *net, struct socket *sock, int protocol, ...@@ -719,6 +721,7 @@ static int netlink_create(struct net *net, struct socket *sock, int protocol,
nlk->module = module; nlk->module = module;
nlk->netlink_bind = bind; nlk->netlink_bind = bind;
nlk->netlink_unbind = unbind; nlk->netlink_unbind = unbind;
nlk->netlink_release = release;
out: out:
return err; return err;
...@@ -763,6 +766,8 @@ static int netlink_release(struct socket *sock) ...@@ -763,6 +766,8 @@ static int netlink_release(struct socket *sock)
* OK. Socket is unlinked, any packets that arrive now * OK. Socket is unlinked, any packets that arrive now
* will be purged. * will be purged.
*/ */
if (nlk->netlink_release)
nlk->netlink_release(sk, nlk->groups);
/* must not acquire netlink_table_lock in any way again before unbind /* must not acquire netlink_table_lock in any way again before unbind
* and notifying genetlink is done as otherwise it might deadlock * and notifying genetlink is done as otherwise it might deadlock
...@@ -1432,6 +1437,8 @@ struct netlink_broadcast_data { ...@@ -1432,6 +1437,8 @@ struct netlink_broadcast_data {
int delivered; int delivered;
gfp_t allocation; gfp_t allocation;
struct sk_buff *skb, *skb2; struct sk_buff *skb, *skb2;
int (*tx_filter)(struct sock *dsk, struct sk_buff *skb, void *data);
void *tx_data;
}; };
static void do_one_broadcast(struct sock *sk, static void do_one_broadcast(struct sock *sk,
...@@ -1485,6 +1492,13 @@ static void do_one_broadcast(struct sock *sk, ...@@ -1485,6 +1492,13 @@ static void do_one_broadcast(struct sock *sk,
p->delivery_failure = 1; p->delivery_failure = 1;
goto out; goto out;
} }
if (p->tx_filter && p->tx_filter(sk, p->skb2, p->tx_data)) {
kfree_skb(p->skb2);
p->skb2 = NULL;
goto out;
}
if (sk_filter(sk, p->skb2)) { if (sk_filter(sk, p->skb2)) {
kfree_skb(p->skb2); kfree_skb(p->skb2);
p->skb2 = NULL; p->skb2 = NULL;
...@@ -1507,8 +1521,12 @@ static void do_one_broadcast(struct sock *sk, ...@@ -1507,8 +1521,12 @@ static void do_one_broadcast(struct sock *sk,
sock_put(sk); sock_put(sk);
} }
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid, int netlink_broadcast_filtered(struct sock *ssk, struct sk_buff *skb,
u32 group, gfp_t allocation) u32 portid,
u32 group, gfp_t allocation,
int (*filter)(struct sock *dsk,
struct sk_buff *skb, void *data),
void *filter_data)
{ {
struct net *net = sock_net(ssk); struct net *net = sock_net(ssk);
struct netlink_broadcast_data info; struct netlink_broadcast_data info;
...@@ -1527,6 +1545,8 @@ int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid, ...@@ -1527,6 +1545,8 @@ int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid,
info.allocation = allocation; info.allocation = allocation;
info.skb = skb; info.skb = skb;
info.skb2 = NULL; info.skb2 = NULL;
info.tx_filter = filter;
info.tx_data = filter_data;
/* While we sleep in clone, do not allow to change socket list */ /* While we sleep in clone, do not allow to change socket list */
...@@ -1552,6 +1572,14 @@ int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid, ...@@ -1552,6 +1572,14 @@ int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid,
} }
return -ESRCH; return -ESRCH;
} }
EXPORT_SYMBOL(netlink_broadcast_filtered);
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 portid,
u32 group, gfp_t allocation)
{
return netlink_broadcast_filtered(ssk, skb, portid, group, allocation,
NULL, NULL);
}
EXPORT_SYMBOL(netlink_broadcast); EXPORT_SYMBOL(netlink_broadcast);
struct netlink_set_err_data { struct netlink_set_err_data {
...@@ -2066,6 +2094,7 @@ __netlink_kernel_create(struct net *net, int unit, struct module *module, ...@@ -2066,6 +2094,7 @@ __netlink_kernel_create(struct net *net, int unit, struct module *module,
if (cfg) { if (cfg) {
nl_table[unit].bind = cfg->bind; nl_table[unit].bind = cfg->bind;
nl_table[unit].unbind = cfg->unbind; nl_table[unit].unbind = cfg->unbind;
nl_table[unit].release = cfg->release;
nl_table[unit].flags = cfg->flags; nl_table[unit].flags = cfg->flags;
} }
nl_table[unit].registered = 1; nl_table[unit].registered = 1;
......
...@@ -42,6 +42,8 @@ struct netlink_sock { ...@@ -42,6 +42,8 @@ struct netlink_sock {
void (*netlink_rcv)(struct sk_buff *skb); void (*netlink_rcv)(struct sk_buff *skb);
int (*netlink_bind)(struct net *net, int group); int (*netlink_bind)(struct net *net, int group);
void (*netlink_unbind)(struct net *net, int group); void (*netlink_unbind)(struct net *net, int group);
void (*netlink_release)(struct sock *sk,
unsigned long *groups);
struct module *module; struct module *module;
struct rhash_head node; struct rhash_head node;
...@@ -64,6 +66,8 @@ struct netlink_table { ...@@ -64,6 +66,8 @@ struct netlink_table {
struct module *module; struct module *module;
int (*bind)(struct net *net, int group); int (*bind)(struct net *net, int group);
void (*unbind)(struct net *net, int group); void (*unbind)(struct net *net, int group);
void (*release)(struct sock *sk,
unsigned long *groups);
int registered; int registered;
}; };
......
...@@ -8,6 +8,7 @@ TARGETS += cachestat ...@@ -8,6 +8,7 @@ TARGETS += cachestat
TARGETS += capabilities TARGETS += capabilities
TARGETS += cgroup TARGETS += cgroup
TARGETS += clone3 TARGETS += clone3
TARGETS += connector
TARGETS += core TARGETS += core
TARGETS += cpufreq TARGETS += cpufreq
TARGETS += cpu-hotplug TARGETS += cpu-hotplug
......
# SPDX-License-Identifier: GPL-2.0
CFLAGS += -Wall
TEST_GEN_PROGS = proc_filter
include ../lib.mk
// SPDX-License-Identifier: GPL-2.0-only
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/connector.h>
#include <linux/cn_proc.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>
#include <signal.h>
#include <string.h>
#include "../kselftest.h"
#define NL_MESSAGE_SIZE (sizeof(struct nlmsghdr) + sizeof(struct cn_msg) + \
sizeof(struct proc_input))
#define NL_MESSAGE_SIZE_NF (sizeof(struct nlmsghdr) + sizeof(struct cn_msg) + \
sizeof(int))
#define MAX_EVENTS 1
volatile static int interrupted;
static int nl_sock, ret_errno, tcount;
static struct epoll_event evn;
static int filter;
#ifdef ENABLE_PRINTS
#define Printf printf
#else
#define Printf ksft_print_msg
#endif
int send_message(void *pinp)
{
char buff[NL_MESSAGE_SIZE];
struct nlmsghdr *hdr;
struct cn_msg *msg;
hdr = (struct nlmsghdr *)buff;
if (filter)
hdr->nlmsg_len = NL_MESSAGE_SIZE;
else
hdr->nlmsg_len = NL_MESSAGE_SIZE_NF;
hdr->nlmsg_type = NLMSG_DONE;
hdr->nlmsg_flags = 0;
hdr->nlmsg_seq = 0;
hdr->nlmsg_pid = getpid();
msg = (struct cn_msg *)NLMSG_DATA(hdr);
msg->id.idx = CN_IDX_PROC;
msg->id.val = CN_VAL_PROC;
msg->seq = 0;
msg->ack = 0;
msg->flags = 0;
if (filter) {
msg->len = sizeof(struct proc_input);
((struct proc_input *)msg->data)->mcast_op =
((struct proc_input *)pinp)->mcast_op;
((struct proc_input *)msg->data)->event_type =
((struct proc_input *)pinp)->event_type;
} else {
msg->len = sizeof(int);
*(int *)msg->data = *(enum proc_cn_mcast_op *)pinp;
}
if (send(nl_sock, hdr, hdr->nlmsg_len, 0) == -1) {
ret_errno = errno;
perror("send failed");
return -3;
}
return 0;
}
int register_proc_netlink(int *efd, void *input)
{
struct sockaddr_nl sa_nl;
int err = 0, epoll_fd;
nl_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
if (nl_sock == -1) {
ret_errno = errno;
perror("socket failed");
return -1;
}
bzero(&sa_nl, sizeof(sa_nl));
sa_nl.nl_family = AF_NETLINK;
sa_nl.nl_groups = CN_IDX_PROC;
sa_nl.nl_pid = getpid();
if (bind(nl_sock, (struct sockaddr *)&sa_nl, sizeof(sa_nl)) == -1) {
ret_errno = errno;
perror("bind failed");
return -2;
}
epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (epoll_fd < 0) {
ret_errno = errno;
perror("epoll_create1 failed");
return -2;
}
err = send_message(input);
if (err < 0)
return err;
evn.events = EPOLLIN;
evn.data.fd = nl_sock;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, nl_sock, &evn) < 0) {
ret_errno = errno;
perror("epoll_ctl failed");
return -3;
}
*efd = epoll_fd;
return 0;
}
static void sigint(int sig)
{
interrupted = 1;
}
int handle_packet(char *buff, int fd, struct proc_event *event)
{
struct nlmsghdr *hdr;
hdr = (struct nlmsghdr *)buff;
if (hdr->nlmsg_type == NLMSG_ERROR) {
perror("NLMSG_ERROR error\n");
return -3;
} else if (hdr->nlmsg_type == NLMSG_DONE) {
event = (struct proc_event *)
((struct cn_msg *)NLMSG_DATA(hdr))->data;
tcount++;
switch (event->what) {
case PROC_EVENT_EXIT:
Printf("Exit process %d (tgid %d) with code %d, signal %d\n",
event->event_data.exit.process_pid,
event->event_data.exit.process_tgid,
event->event_data.exit.exit_code,
event->event_data.exit.exit_signal);
break;
case PROC_EVENT_FORK:
Printf("Fork process %d (tgid %d), parent %d (tgid %d)\n",
event->event_data.fork.child_pid,
event->event_data.fork.child_tgid,
event->event_data.fork.parent_pid,
event->event_data.fork.parent_tgid);
break;
case PROC_EVENT_EXEC:
Printf("Exec process %d (tgid %d)\n",
event->event_data.exec.process_pid,
event->event_data.exec.process_tgid);
break;
case PROC_EVENT_UID:
Printf("UID process %d (tgid %d) uid %d euid %d\n",
event->event_data.id.process_pid,
event->event_data.id.process_tgid,
event->event_data.id.r.ruid,
event->event_data.id.e.euid);
break;
case PROC_EVENT_GID:
Printf("GID process %d (tgid %d) gid %d egid %d\n",
event->event_data.id.process_pid,
event->event_data.id.process_tgid,
event->event_data.id.r.rgid,
event->event_data.id.e.egid);
break;
case PROC_EVENT_SID:
Printf("SID process %d (tgid %d)\n",
event->event_data.sid.process_pid,
event->event_data.sid.process_tgid);
break;
case PROC_EVENT_PTRACE:
Printf("Ptrace process %d (tgid %d), Tracer %d (tgid %d)\n",
event->event_data.ptrace.process_pid,
event->event_data.ptrace.process_tgid,
event->event_data.ptrace.tracer_pid,
event->event_data.ptrace.tracer_tgid);
break;
case PROC_EVENT_COMM:
Printf("Comm process %d (tgid %d) comm %s\n",
event->event_data.comm.process_pid,
event->event_data.comm.process_tgid,
event->event_data.comm.comm);
break;
case PROC_EVENT_COREDUMP:
Printf("Coredump process %d (tgid %d) parent %d, (tgid %d)\n",
event->event_data.coredump.process_pid,
event->event_data.coredump.process_tgid,
event->event_data.coredump.parent_pid,
event->event_data.coredump.parent_tgid);
break;
default:
break;
}
}
return 0;
}
int handle_events(int epoll_fd, struct proc_event *pev)
{
char buff[CONNECTOR_MAX_MSG_SIZE];
struct epoll_event ev[MAX_EVENTS];
int i, event_count = 0, err = 0;
event_count = epoll_wait(epoll_fd, ev, MAX_EVENTS, -1);
if (event_count < 0) {
ret_errno = errno;
if (ret_errno != EINTR)
perror("epoll_wait failed");
return -3;
}
for (i = 0; i < event_count; i++) {
if (!(ev[i].events & EPOLLIN))
continue;
if (recv(ev[i].data.fd, buff, sizeof(buff), 0) == -1) {
ret_errno = errno;
perror("recv failed");
return -3;
}
err = handle_packet(buff, ev[i].data.fd, pev);
if (err < 0)
return err;
}
return 0;
}
int main(int argc, char *argv[])
{
int epoll_fd, err;
struct proc_event proc_ev;
struct proc_input input;
signal(SIGINT, sigint);
if (argc > 2) {
printf("Expected 0(assume no-filter) or 1 argument(-f)\n");
exit(1);
}
if (argc == 2) {
if (strcmp(argv[1], "-f") == 0) {
filter = 1;
} else {
printf("Valid option : -f (for filter feature)\n");
exit(1);
}
}
if (filter) {
input.event_type = PROC_EVENT_NONZERO_EXIT;
input.mcast_op = PROC_CN_MCAST_LISTEN;
err = register_proc_netlink(&epoll_fd, (void*)&input);
} else {
enum proc_cn_mcast_op op = PROC_CN_MCAST_LISTEN;
err = register_proc_netlink(&epoll_fd, (void*)&op);
}
if (err < 0) {
if (err == -2)
close(nl_sock);
if (err == -3) {
close(nl_sock);
close(epoll_fd);
}
exit(1);
}
while (!interrupted) {
err = handle_events(epoll_fd, &proc_ev);
if (err < 0) {
if (ret_errno == EINTR)
continue;
if (err == -2)
close(nl_sock);
if (err == -3) {
close(nl_sock);
close(epoll_fd);
}
exit(1);
}
}
if (filter) {
input.mcast_op = PROC_CN_MCAST_IGNORE;
send_message((void*)&input);
} else {
enum proc_cn_mcast_op op = PROC_CN_MCAST_IGNORE;
send_message((void*)&op);
}
close(epoll_fd);
close(nl_sock);
printf("Done total count: %d\n", tcount);
exit(0);
}
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