ck: net: kernel hookers service for toa module

fix #32251972

LVS fullnat will replace network traffic's source ip with its local ip,
and thus the backend servers cannot obtain the real client ip.

To solve this, LVS has introduced the tcp option address (TOA) to store
the essential ip address information in the last tcp ack packet of the
3-way handshake, and the backend servers need to retrieve it from the
packet header.

In this patch, we have introduced the sk_toa_data member in the sock
structure to hold the TOA information. There used to be an in-tree
module for TOA managing, whereas it has now been maintained as an
standalone module.

In this case, the toa module should register its hook function(s) using
the provided interfaces in the hookers module.

TOA in sock structure:

	__be32 sk_toa_data[16];

The hookers module only provides the sk_toa_data placeholder, and the
toa module can use this variable through the layout it needs.

Hook interfaces:

The hookers module replaces the kernel's syn_recv_sock and getname
handler with a stub that chains the toa module's hook function(s) to the
original handling function. The hookers module allows hook functions to
be installed and uninstalled in any order.

toa module:

The external toa module will be provided in separate RPM package.

Reviewed-by: Caspar Zhang <caspar@linux.alibaba.com>
Signed-off-by: George Zhang <georgezhang@linux.alibaba.com>
Signed-off-by: Xu Yu <xuyu@linux.alibaba.com>
Signed-off-by: Xin Hao <xhao@linux.alibaba.com>
Reviewed-by: Tony Lu <tonylu@linux.alibaba.com>
This commit is contained in:
George Zhang 2019-03-15 11:17:17 +08:00 committed by Qiao Ma
parent 4f01df9f66
commit cf0dd0a267
14 changed files with 440 additions and 2 deletions

View File

@ -1053,6 +1053,7 @@ CONFIG_NET_KEY_MIGRATE=y
# CONFIG_SMC is not set
CONFIG_XDP_SOCKETS=y
CONFIG_XDP_SOCKETS_DIAG=m
CONFIG_HOOKERS=m
CONFIG_INET=y
CONFIG_IP_MULTICAST=y
CONFIG_IP_ADVANCED_ROUTER=y

View File

@ -1034,6 +1034,7 @@ CONFIG_NET_KEY_MIGRATE=y
# CONFIG_SMC is not set
CONFIG_XDP_SOCKETS=y
CONFIG_XDP_SOCKETS_DIAG=m
CONFIG_HOOKERS=m
CONFIG_INET=y
CONFIG_IP_MULTICAST=y
CONFIG_IP_ADVANCED_ROUTER=y

View File

@ -1062,6 +1062,7 @@ CONFIG_NET_KEY_MIGRATE=y
# CONFIG_SMC is not set
CONFIG_XDP_SOCKETS=y
CONFIG_XDP_SOCKETS_DIAG=m
CONFIG_HOOKERS=m
CONFIG_INET=y
CONFIG_IP_MULTICAST=y
CONFIG_IP_ADVANCED_ROUTER=y

View File

@ -1063,6 +1063,7 @@ CONFIG_NET_KEY_MIGRATE=y
# CONFIG_SMC is not set
CONFIG_XDP_SOCKETS=y
CONFIG_XDP_SOCKETS_DIAG=m
CONFIG_HOOKERS=m
CONFIG_INET=y
CONFIG_IP_MULTICAST=y
CONFIG_IP_ADVANCED_ROUTER=y

49
include/linux/hookers.h Normal file
View File

@ -0,0 +1,49 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2019 Alibaba Group Holding Limited. All Rights Reserved.
*
* Changes: Li Yu
*/
#ifndef _LINUX_HOOKER_H_
#define _LINUX_HOOKER_H_
#include <linux/types.h>
struct hooked_place;
struct hooker {
struct hooked_place *hplace;
void *func; /* the installed hooker function pointer */
struct list_head chain;
};
/*
* Install the hooker function at specified address.
* This function may sleep.
*
* Parameters:
* place - the address that saves function pointer
* hooker - the hooker to install, the caller must fill
* its func member first
*
* Return:
* 0 - All OK, please note that hooker func may be called before
* this return
* < 0 - any error, e.g. out of memory, existing same installed hooker
*/
extern int hooker_install(const void *place, struct hooker *hooker);
/*
* Remove the installed hooker function that saved in hooker->func.
* This function may sleep.
*
* Parameters:
* place - the address that saves function pointer
* hooker - the installed hooker struct
*/
extern void hooker_uninstall(struct hooker *hooker);
#endif

View File

@ -343,6 +343,7 @@ struct bpf_local_storage;
* @sk_txtime_deadline_mode: set deadline mode for SO_TXTIME
* @sk_txtime_report_errors: set report errors mode for SO_TXTIME
* @sk_txtime_unused: unused txtime flags
* @sk_toa_data: tcp option address (toa) data
*/
struct sock {
/*
@ -518,6 +519,7 @@ struct sock {
#endif
void (*sk_destruct)(struct sock *sk);
struct sock_reuseport __rcu *sk_reuseport_cb;
__be32 sk_toa_data[16];
#ifdef CONFIG_BPF_SYSCALL
struct bpf_local_storage __rcu *sk_bpf_storage;
#endif

View File

@ -56,6 +56,9 @@ ip6_dgram_sock_seq_show(struct seq_file *seq, struct sock *sp, __u16 srcp,
#define LOOPBACK4_IPV6 cpu_to_be32(0x7f000006)
extern const struct inet_connection_sock_af_ops ipv6_specific;
extern const struct inet_connection_sock_af_ops ipv6_mapped;
void inet6_destroy_sock(struct sock *sk);
#define IPV6_SEQ_DGRAM_HEADER \

View File

@ -67,6 +67,7 @@ source "net/xfrm/Kconfig"
source "net/iucv/Kconfig"
source "net/smc/Kconfig"
source "net/xdp/Kconfig"
source "net/hookers/Kconfig"
config INET
bool "TCP/IP networking"

View File

@ -87,4 +87,5 @@ endif
obj-$(CONFIG_QRTR) += qrtr/
obj-$(CONFIG_NET_NCSI) += ncsi/
obj-$(CONFIG_XDP_SOCKETS) += xdp/
obj-$(CONFIG_HOOKERS) += hookers/
obj-$(CONFIG_MPTCP) += mptcp/

8
net/hookers/Kconfig Normal file
View File

@ -0,0 +1,8 @@
config HOOKERS
tristate "Hooker service"
default m
depends on X86
help
Allow replacing and restore the function pointer in any order.
See include/linux/hookers.h for details.

4
net/hookers/Makefile Normal file
View File

@ -0,0 +1,4 @@
#
# Makefile for hookers module.
#
obj-$(CONFIG_HOOKERS) += hookers.o

363
net/hookers/hookers.c Normal file
View File

@ -0,0 +1,363 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2019 Alibaba Group Holding Limited. All Rights Reserved. */
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/cpu.h>
#include <asm/cacheflush.h>
#include <linux/rculist.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <net/net_namespace.h>
#include <net/tcp.h>
#include <net/transp_v6.h>
#include <net/inet_common.h>
#include <net/ipv6.h>
#include <linux/inet.h>
#include <linux/hookers.h>
struct hooked_place {
const char *name; /* position information shown in procfs */
void *place; /* the kernel address to be hook */
void *orig; /* original content at hooked place */
void *stub; /* hooker function stub */
int nr_hookers; /* how many hookers are linked at below chain */
struct list_head chain; /* hookers chain */
};
static spinlock_t hookers_lock;
static struct sock *
ipv4_specific_syn_recv_sock_stub(struct sock *sk,
struct sk_buff *skb, struct request_sock *req,
struct dst_entry *dst,
struct request_sock *req_unhash,
bool *own_req);
static int
inet_stream_ops_getname_stub(struct socket *sock,
struct sockaddr *uaddr, int peer);
#if IS_ENABLED(CONFIG_IPV6)
static struct sock *
ipv6_specific_syn_recv_sock_stub(struct sock *sk,
struct sk_buff *skb, struct request_sock *req,
struct dst_entry *dst,
struct request_sock *req_unhash,
bool *own_req);
static struct sock *
ipv6_mapped_syn_recv_sock_stub(struct sock *sk,
struct sk_buff *skb, struct request_sock *req,
struct dst_entry *dst,
struct request_sock *req_unhash,
bool *own_req);
static int
inet6_stream_ops_getname_stub(struct socket *sock,
struct sockaddr *uaddr, int peer);
#endif
enum pt_types {
IPV4_SPECIFIC_SYN_RECV_SOCK = 0,
INET_STREAM_OPS_GETNAME,
#if IS_ENABLED(CONFIG_IPV6)
IPV6_SPECIFIC_SYN_RECV_SOCK,
IPV6_MAPPED_SYN_RECV_SOCK,
INET6_STREAM_OPS_GETNAME,
#endif
PLACE_TABLE_SZ
};
static struct hooked_place place_table[] = {
{
.name = "ipv4_specific.syn_recv_sock",
.place = (void *)&ipv4_specific.syn_recv_sock,
.stub = ipv4_specific_syn_recv_sock_stub,
},
{
.name = "inet_stream_ops.getname",
.place = (void *)&inet_stream_ops.getname,
.stub = inet_stream_ops_getname_stub,
},
#if IS_ENABLED(CONFIG_IPV6)
{
.name = "ipv6_specific.syn_recv_sock",
.place = (void *)&ipv6_specific.syn_recv_sock,
.stub = ipv6_specific_syn_recv_sock_stub,
},
{
.name = "ipv6_mapped.syn_recv_sock",
.place = (void *)&ipv6_mapped.syn_recv_sock,
.stub = ipv6_mapped_syn_recv_sock_stub,
},
{
.name = "inet6_stream_ops.getname",
.place = (void *)&inet6_stream_ops.getname,
.stub = inet6_stream_ops_getname_stub,
},
#endif
};
static struct sock *
__syn_recv_sock_hstub(struct hooked_place *place,
struct sock *sk, struct sk_buff *skb,
struct request_sock *req, struct dst_entry *dst,
struct request_sock *req_unhash, bool *own_req)
{
struct hooker *iter;
struct sock *(*hooker_func)(struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
struct dst_entry *dst,
struct request_sock *req_unhash,
bool *own_req,
struct sock **ret);
struct sock *(*orig_func)(struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
struct dst_entry *dst,
struct request_sock *req_unhash,
bool *own_req);
struct sock *ret;
orig_func = place->orig;
ret = orig_func(sk, skb, req, dst, req_unhash, own_req);
rcu_read_lock();
list_for_each_entry_rcu(iter, &place->chain, chain) {
hooker_func = iter->func;
hooker_func(sk, skb, req, dst, req_unhash, own_req, &ret);
}
rcu_read_unlock();
return ret;
}
static int __getname_hstub(struct hooked_place *place,
struct socket *sock, struct sockaddr *uaddr,
int peer)
{
struct hooker *iter;
int (*hooker_func)(struct socket *sock, struct sockaddr *uaddr,
int peer, int *ret);
int (*orig_func)(struct socket *sock, struct sockaddr *uaddr,
int peer);
int ret;
orig_func = place->orig;
ret = orig_func(sock, uaddr, peer);
rcu_read_lock();
list_for_each_entry_rcu(iter, &place->chain, chain) {
hooker_func = iter->func;
hooker_func(sock, uaddr, peer, &ret);
}
rcu_read_unlock();
return ret;
}
static struct sock *
ipv4_specific_syn_recv_sock_stub(struct sock *sk,
struct sk_buff *skb, struct request_sock *req,
struct dst_entry *dst,
struct request_sock *req_unhash,
bool *own_req)
{
return __syn_recv_sock_hstub(&place_table[IPV4_SPECIFIC_SYN_RECV_SOCK],
sk, skb, req, dst, req_unhash, own_req);
}
static int
inet_stream_ops_getname_stub(struct socket *sock,
struct sockaddr *uaddr, int peer)
{
return __getname_hstub(&place_table[INET_STREAM_OPS_GETNAME], sock,
uaddr, peer);
}
#if IS_ENABLED(CONFIG_IPV6)
static struct sock *
ipv6_specific_syn_recv_sock_stub(struct sock *sk,
struct sk_buff *skb, struct request_sock *req,
struct dst_entry *dst,
struct request_sock *req_unhash,
bool *own_req)
{
return __syn_recv_sock_hstub(&place_table[IPV6_SPECIFIC_SYN_RECV_SOCK],
sk, skb, req, dst, req_unhash, own_req);
}
static struct sock *
ipv6_mapped_syn_recv_sock_stub(struct sock *sk,
struct sk_buff *skb, struct request_sock *req,
struct dst_entry *dst,
struct request_sock *req_unhash,
bool *own_req)
{
return __syn_recv_sock_hstub(&place_table[IPV6_MAPPED_SYN_RECV_SOCK],
sk, skb, req, dst, req_unhash, own_req);
}
static int
inet6_stream_ops_getname_stub(struct socket *sock,
struct sockaddr *uaddr, int peer)
{
return __getname_hstub(&place_table[INET6_STREAM_OPS_GETNAME], sock,
uaddr, peer);
}
#endif
int hooker_install(const void *place, struct hooker *h)
{
enum pt_types i;
struct hooked_place *hplace;
/* synchronize_rcu() */
might_sleep();
if (!place || !h || !h->func)
return -EINVAL;
for (i = 0; i < PLACE_TABLE_SZ; i++) {
hplace = &place_table[i];
if (hplace->place == place) {
INIT_LIST_HEAD(&h->chain);
spin_lock(&hookers_lock);
hplace->nr_hookers++;
h->hplace = hplace;
list_add_tail_rcu(&h->chain, &place_table[i].chain);
spin_unlock(&hookers_lock);
synchronize_rcu();
break;
}
}
return (i >= PLACE_TABLE_SZ) ? -EINVAL : 0;
}
EXPORT_SYMBOL_GPL(hooker_install);
void hooker_uninstall(struct hooker *h)
{
/* synchronize_rcu(); */
might_sleep();
spin_lock(&hookers_lock);
list_del_rcu(&h->chain);
h->hplace->nr_hookers--;
h->hplace = NULL;
spin_unlock(&hookers_lock);
synchronize_rcu();
}
EXPORT_SYMBOL_GPL(hooker_uninstall);
static inline unsigned int hookers_clear_cr0(void)
{
unsigned int cr0 = read_cr0();
write_cr0(cr0 & 0xfffeffff);
return cr0;
}
static inline void hookers_restore_cr0(unsigned int val)
{
write_cr0(val);
}
static void *hookers_seq_start(struct seq_file *seq, loff_t *pos)
{
if (*pos < PLACE_TABLE_SZ)
return &place_table[*pos];
return NULL;
}
static void *hookers_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
if (++(*pos) >= PLACE_TABLE_SZ)
return NULL;
return (void *)&place_table[*pos];
}
static void hookers_seq_stop(struct seq_file *seq, void *v)
{
}
static int hookers_seq_show(struct seq_file *seq, void *v)
{
struct hooked_place *hplace = (struct hooked_place *)v;
seq_printf(seq, "name:%-24s addr:0x%p hookers:%-10d\n",
hplace->name, hplace->place, hplace->nr_hookers);
return 0;
}
static const struct seq_operations hookers_seq_ops = {
.start = hookers_seq_start,
.next = hookers_seq_next,
.stop = hookers_seq_stop,
.show = hookers_seq_show,
};
static int hookers_seq_open(struct inode *inode, struct file *file)
{
return seq_open(file, &hookers_seq_ops);
}
static const struct proc_ops hookers_seq_fops = {
.proc_open = hookers_seq_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = seq_release,
};
static int hookers_init(void)
{
enum pt_types i;
if (!proc_create("hookers", 0444, NULL, &hookers_seq_fops))
return -ENODEV;
spin_lock_init(&hookers_lock);
for (i = 0; i < PLACE_TABLE_SZ; i++) {
unsigned int cr0;
void **place = place_table[i].place;
place_table[i].orig = *place;
if (!place_table[i].stub)
break;
INIT_LIST_HEAD(&place_table[i].chain);
get_online_cpus();
cr0 = hookers_clear_cr0();
*place = place_table[i].stub;
hookers_restore_cr0(cr0);
put_online_cpus();
}
return 0;
}
static void hookers_exit(void)
{
enum pt_types i;
remove_proc_entry("hookers", NULL);
for (i = 0; i < PLACE_TABLE_SZ; i++) {
unsigned int cr0;
void **place = place_table[i].place;
get_online_cpus();
cr0 = hookers_clear_cr0();
*place = place_table[i].orig;
hookers_restore_cr0(cr0);
put_online_cpus();
}
synchronize_rcu();
}
module_init(hookers_init);
module_exit(hookers_exit);
MODULE_LICENSE("GPL");

View File

@ -692,6 +692,7 @@ const struct proto_ops inet6_stream_ops = {
#endif
.set_rcvlowat = tcp_set_rcvlowat,
};
EXPORT_SYMBOL(inet6_stream_ops);
const struct proto_ops inet6_dgram_ops = {
.family = PF_INET6,

View File

@ -74,7 +74,7 @@ static void tcp_v6_reqsk_send_ack(const struct sock *sk, struct sk_buff *skb,
static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb);
static const struct inet_connection_sock_af_ops ipv6_mapped;
const struct inet_connection_sock_af_ops ipv6_mapped;
const struct inet_connection_sock_af_ops ipv6_specific;
#ifdef CONFIG_TCP_MD5SIG
static const struct tcp_sock_af_ops tcp_sock_ipv6_specific;
@ -1881,6 +1881,7 @@ const struct inet_connection_sock_af_ops ipv6_specific = {
.sockaddr_len = sizeof(struct sockaddr_in6),
.mtu_reduced = tcp_v6_mtu_reduced,
};
EXPORT_SYMBOL(ipv6_specific);
#ifdef CONFIG_TCP_MD5SIG
static const struct tcp_sock_af_ops tcp_sock_ipv6_specific = {
@ -1893,7 +1894,7 @@ static const struct tcp_sock_af_ops tcp_sock_ipv6_specific = {
/*
* TCP over IPv4 via INET6 API
*/
static const struct inet_connection_sock_af_ops ipv6_mapped = {
const struct inet_connection_sock_af_ops ipv6_mapped = {
.queue_xmit = ip_queue_xmit,
.send_check = tcp_v4_send_check,
.rebuild_header = inet_sk_rebuild_header,
@ -1907,6 +1908,7 @@ static const struct inet_connection_sock_af_ops ipv6_mapped = {
.sockaddr_len = sizeof(struct sockaddr_in6),
.mtu_reduced = tcp_v4_mtu_reduced,
};
EXPORT_SYMBOL(ipv6_mapped);
#ifdef CONFIG_TCP_MD5SIG
static const struct tcp_sock_af_ops tcp_sock_ipv6_mapped_specific = {