Skip to content

Instantly share code, notes, and snippets.

@YutaroHayakawa
Last active January 17, 2019 12:57
Show Gist options
  • Save YutaroHayakawa/b515b3b1cb55a15223076ebaec5c7eea to your computer and use it in GitHub Desktop.
Save YutaroHayakawa/b515b3b1cb55a15223076ebaec5c7eea to your computer and use it in GitHub Desktop.
Detect deletion of kernel internal TCP socket structure on Linux using ULP and eventfd.

Detecting deletion of kernel internal TCP socket structure on Linux

This module adds new type of socket option called TCP_MONITOR_SET_EVENT_FD using Linux ULP (Upper Layer Protocol) feature. It allows users to monitor the lifetime of kernel internal TCP data structure via eventfd without any modification to kernel code.

Usage

Please see test.c

How it works?

The destructor function of ULP is called when the kernel deletes its internal data structure called struct tcp_sock which represents the single TCP connection.

This module simply makes notification to eventfd when the destructor function called.

obj-m:=tcp_monitor.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean
/*
* 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");
/*
* Public domain
*/
#pragma once
/*
* (Maximum number of TCP socket options) + 1
*
* Only works on Linux 4.18.0
*/
#define TCP_MONITOR_SET_EVENTFD 35
/*
* Public domain
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/eventfd.h>
#include <linux/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "tcp_monitor.h"
int
main(void)
{
int error, csock, evfd;
csock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (csock == -1) {
perror("socket");
return EXIT_FAILURE;
}
struct sockaddr_in caddr;
caddr.sin_family = AF_INET;
caddr.sin_addr.s_addr = inet_addr("127.0.0.1");
caddr.sin_port = htons(8000);
error = connect(csock, (struct sockaddr *)&caddr, sizeof(caddr));
if (error) {
perror("connect");
return EXIT_FAILURE;
}
const char *ulp = "tcp_monitor";
error = setsockopt(csock, IPPROTO_TCP, TCP_ULP, ulp, strlen(ulp));
if (error) {
perror("setsockopt TCP_ULP");
return EXIT_FAILURE;
}
evfd = eventfd(0, 0);
if (evfd == -1) {
perror("eventfd");
return EXIT_FAILURE;
}
error = setsockopt(csock, IPPROTO_TCP, TCP_MONITOR_SET_EVENTFD,
&evfd, sizeof(evfd));
if (error) {
perror("setsockopt TCP_MONITOR_SET_EVENTFD");
return EXIT_FAILURE;
}
printf("Set eventfd\n");
pid_t pid = fork();
if (pid == 0) {
sleep(5);
exit(EXIT_SUCCESS);
}
close(csock);
/*
* Block until kernel internal socket destroyed.
* You also can (e)poll(2) this eventfd.
*/
ssize_t rlen;
uint64_t ev;
rlen = read(evfd, &ev, sizeof(ev));
if (rlen == -1) {
perror("read");
return EXIT_FAILURE;
}
printf("ev: %lu socket destroied\n", ev);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment