|
/* |
|
* Public domain |
|
*/ |
|
|
|
#include <linux/eventfd.h> |
|
#include <linux/module.h> |
|
#include <net/tcp.h> |
|
#include <net/inet_common.h> |
|
|
|
#include "tcp_monitor.h" |
|
|
|
struct tcp_monitor_ctx { |
|
struct eventfd_ctx *ev_ctx; |
|
}; |
|
|
|
static int |
|
tcp_monitor_set_eventfd(struct tcp_monitor_ctx *ctx, char __user *optval, |
|
unsigned int optlen) |
|
{ |
|
int eventfd; |
|
struct eventfd_ctx *ev_ctx; |
|
|
|
if (copy_from_user(&eventfd, optval, sizeof(int)) != 0) { |
|
return -EFAULT; |
|
} |
|
|
|
if (eventfd == -1) { |
|
eventfd_ctx_put(ctx->ev_ctx); |
|
ctx->ev_ctx = NULL; |
|
return 0; |
|
} |
|
|
|
ev_ctx = eventfd_ctx_fdget(eventfd); |
|
if (IS_ERR(ev_ctx)) { |
|
return PTR_ERR(ev_ctx); |
|
} |
|
|
|
if (ctx->ev_ctx != NULL) { |
|
eventfd_ctx_put(ctx->ev_ctx); |
|
} |
|
|
|
ctx->ev_ctx = ev_ctx; |
|
|
|
return 0; |
|
} |
|
|
|
static int |
|
tcp_monitor_setsockopt(struct sock *sk, int level, int optname, char __user *optval, |
|
unsigned int optlen) |
|
{ |
|
struct inet_connection_sock *icsk = inet_csk(sk); |
|
struct tcp_monitor_ctx *ctx = icsk->icsk_ulp_data; |
|
|
|
if (level == SOL_TCP && optname == TCP_MONITOR_SET_EVENTFD) { |
|
return tcp_monitor_set_eventfd(ctx, optval, optlen); |
|
} |
|
|
|
return tcp_setsockopt(sk, level, optname, optval, optlen); |
|
} |
|
|
|
static int |
|
tcp_monitor_init(struct sock *sk) |
|
{ |
|
struct inet_connection_sock *icsk = inet_csk(sk); |
|
struct tcp_monitor_ctx *ctx; |
|
|
|
if (sk->sk_state != TCP_ESTABLISHED) { |
|
return -ENOTSUPP; |
|
} |
|
|
|
ctx = kzalloc(sizeof(*ctx), GFP_ATOMIC); |
|
if (!ctx) { |
|
return -ENOMEM; |
|
} |
|
|
|
icsk->icsk_ulp_data = ctx; |
|
ctx->ev_ctx = NULL; |
|
|
|
sk->sk_prot->setsockopt = tcp_monitor_setsockopt; |
|
|
|
return 0; |
|
} |
|
|
|
static void |
|
tcp_monitor_release(struct sock *sk) |
|
{ |
|
uint64_t ev_ctr; |
|
struct inet_connection_sock *isck = inet_csk(sk); |
|
struct tcp_monitor_ctx *ctx = isck->icsk_ulp_data; |
|
|
|
if (ctx->ev_ctx != NULL) { |
|
ev_ctr = eventfd_signal(ctx->ev_ctx, 1); |
|
eventfd_ctx_put(ctx->ev_ctx); |
|
} |
|
|
|
kfree(ctx); |
|
} |
|
|
|
static struct tcp_ulp_ops tcp_monitor_ulp_ops __read_mostly = { |
|
.name = "tcp_monitor", |
|
.owner = THIS_MODULE, |
|
.init = tcp_monitor_init, |
|
.release = tcp_monitor_release, |
|
.user_visible = true |
|
}; |
|
|
|
static int __init |
|
tcp_monitor_register(void) |
|
{ |
|
tcp_register_ulp(&tcp_monitor_ulp_ops); |
|
return 0; |
|
} |
|
|
|
static void __exit |
|
tcp_monitor_unregister(void) |
|
{ |
|
tcp_unregister_ulp(&tcp_monitor_ulp_ops); |
|
} |
|
|
|
module_init(tcp_monitor_register); |
|
module_exit(tcp_monitor_unregister); |
|
|
|
MODULE_AUTHOR("Yutaro Hayakawa"); |
|
MODULE_DESCRIPTION("TCP Lifetime Monitor"); |
|
MODULE_LICENSE("GPL"); |