├── .gitignore ├── Makefile ├── README.md └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.ko 3 | *.ko.unsigned 4 | *.cmd 5 | *.mod.c 6 | *.order 7 | *.symvers 8 | .tmp_versions 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MODNAME ?= drop-tcp-sock 2 | 3 | obj-m = $(MODNAME).o 4 | $(MODNAME)-y = main.o 5 | 6 | all: 7 | $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$$PWD 8 | clean: 9 | $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$$PWD clean 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 0 2 | 3 | This module allows one to drop TCP connections and can be usefull for killing `TIME-WAIT` sockets. 4 | 5 | # usage 6 | 7 | First compile and load the module: 8 | ~~~ 9 | $ make 10 | $ sudo insmod drop-tcp-sock.ko 11 | ~~~ 12 | 13 | Single socket killing: 14 | ~~~ 15 | # netstat -n -t | grep WAIT 16 | tcp 0 0 127.0.0.1:50866 127.0.0.1:22 TIME_WAIT 17 | 18 | # echo "127.0.0.1:50866 127.0.0.1:22" >/proc/net/tcpdropsock 19 | ~~~ 20 | 21 | Multiple sockets killing: 22 | ~~~ 23 | # netstat -n -t | grep WAIT | awk '{print $4"\t"$5}' 24 | 127.0.0.1:41278 127.0.0.1:22 25 | 127.0.0.1:41276 127.0.0.1:22 26 | 127.0.0.1:41274 127.0.0.1:22 27 | 28 | # netstat -n -t | grep WAIT | awk '{print $4"\t"$5}' >/proc/net/tcpdropsock 29 | ~~~ 30 | 31 | # features 32 | 33 | - 2.6.32+ kernels 34 | - network namespaces support 35 | - batch socket killing (multiple at once) 36 | 37 | # credits 38 | 39 | Original idea: [Roman Arutyunyan](https://github.com/arut) 40 | 41 | This module implementation: [Ilya V. Matveychikov](https://github.com/milabs) 42 | 43 | 2018, 2019, 2020 44 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | // 2 | // This module allows one to drop TCP connections. It can be 3 | // usefull for killing TIME-WAIT sockets. 4 | // 5 | // Original idea was taken from linux-tcp-drop project 6 | // written by Roman Arutyunyan (https://github.com/arut). 7 | // 8 | // Ilya V. Matveychikov, 2018 9 | // 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #ifndef CONFIG_IPV6 26 | # define in6_pton(args...) 0 27 | # define inet6_lookup(args...) NULL 28 | #endif 29 | 30 | #define DTS_PDE_NAME "tcpdropsock" 31 | 32 | struct dts_data { 33 | uint32_t len; 34 | uint32_t available; 35 | char data[0]; 36 | }; 37 | 38 | struct dts_pernet { 39 | struct net *net; 40 | struct proc_dir_entry *pde; 41 | }; 42 | 43 | struct dts_inet { 44 | int ipv6; 45 | const char *p; 46 | uint16_t port; 47 | uint32_t addr[ 4 ]; 48 | }; 49 | 50 | static void dts_kill(struct net *net, const struct dts_inet *src, const struct dts_inet *dst) 51 | { 52 | struct sock *sk = NULL; 53 | 54 | if (!src->ipv6) { 55 | sk = inet_lookup(net, &tcp_hashinfo, 56 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0) 57 | NULL, 0, 58 | #endif 59 | (__be32)dst->addr[0], htons(dst->port), 60 | (__be32)src->addr[0], htons(src->port), 0); 61 | if (!sk) return; 62 | } else { 63 | sk = inet6_lookup(net, &tcp_hashinfo, 64 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0) 65 | NULL, 0, 66 | #endif 67 | (const struct in6_addr *)dst->addr, htons(dst->port), 68 | (const struct in6_addr *)src->addr, htons(src->port), 0); 69 | if (!sk) return; 70 | } 71 | 72 | printk("DTS: killing sk:%p (%s -> %s) state %d\n", sk, src->p, dst->p, sk->sk_state); 73 | 74 | if (sk->sk_state == TCP_TIME_WAIT) { 75 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 3, 0) 76 | inet_twsk_deschedule_put(inet_twsk(sk)); 77 | #else 78 | inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row); 79 | inet_twsk_put(inet_twsk(sk)); 80 | #endif 81 | } else { 82 | tcp_done(sk); 83 | sock_put(sk); 84 | } 85 | } 86 | 87 | static int dts_pton(struct dts_inet *in) 88 | { 89 | char *p, *end; 90 | if (in4_pton(in->p, -1, (void *)in->addr, -1, (const char **)&end)) { 91 | in->ipv6 = 0; 92 | } else if (in6_pton(in->p, -1, (void *)in->addr, -1, (const char **)&end)) { 93 | in->ipv6 = 1; 94 | } else return -EINVAL; 95 | 96 | p = (end += 1); 97 | while (*p && isdigit(*p)) p++; 98 | *p = 0; // kstrtoXX requires 0 at the end 99 | 100 | return kstrtou16(end, 10, &in->port); 101 | } 102 | 103 | static void dts_process(struct dts_pernet *dts, struct dts_data *d) 104 | { 105 | char *p = d->data; 106 | struct dts_inet src, dst; 107 | 108 | #pragma GCC diagnostic push 109 | #pragma GCC diagnostic ignored "-Wmisleading-indentation" 110 | while (*p && p < d->data + d->len) { 111 | while (*p && isspace(*p)) p++; if (!*p) return; // skip spaces 112 | src.p = p; 113 | while (*p && !isspace(*p)) p++; if (!*p) return; // skip non-spaces 114 | 115 | while (*p && isspace(*p)) p++; if (!*p) return; // skip spaces 116 | dst.p = p; 117 | while (*p && !isspace(*p)) p++; if (!*p) return; // skip non-spaces 118 | 119 | if ((dts_pton(&src) || dts_pton(&dst)) || (src.ipv6 != dst.ipv6)) 120 | break; 121 | 122 | dts_kill(dts->net, &src, &dst), p++; 123 | } 124 | #pragma GCC diagnostic pop 125 | } 126 | 127 | static int dts_proc_open(struct inode *inode, struct file *file) 128 | { 129 | struct dts_data *d = kzalloc(PAGE_SIZE, GFP_KERNEL); 130 | if (!d) return -ENOMEM; 131 | d->available = PAGE_SIZE - (sizeof(*d)+1); 132 | file->private_data = d; 133 | return 0; 134 | } 135 | 136 | static ssize_t dts_proc_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) 137 | { 138 | struct dts_data *d = file->private_data; 139 | 140 | if (d->len + size > d->available) { 141 | size_t new_available = d->available + roundup(size, PAGE_SIZE); 142 | struct dts_data *dnew = krealloc(d, new_available + (sizeof(*d)+1), GFP_KERNEL); 143 | if (!dnew) { 144 | kfree(d), file->private_data = NULL; 145 | return -ENOMEM; 146 | } 147 | (d = dnew)->available = new_available; 148 | file->private_data = d; // update 149 | } 150 | 151 | if (copy_from_user(d->data + d->len, buf, size)) 152 | return -EFAULT; 153 | d->data[(d->len += size)] = 0; 154 | 155 | return size; 156 | } 157 | 158 | static int dts_proc_release(struct inode *inode, struct file *file) 159 | { 160 | struct dts_data *d = file->private_data; 161 | if (d) { 162 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) 163 | dts_process(PDE_DATA(file_inode(file)), d); 164 | #else 165 | dts_process(PDE(file->f_path.dentry->d_inode)->data, d); 166 | #endif 167 | kfree(d), file->private_data = NULL; 168 | } return 0; 169 | } 170 | 171 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) 172 | static const struct proc_ops dts_proc_fops = { 173 | .proc_open = dts_proc_open, 174 | .proc_write = dts_proc_write, 175 | .proc_release = dts_proc_release, 176 | }; 177 | #else 178 | static const struct file_operations dts_proc_fops = { 179 | .owner = THIS_MODULE, 180 | .open = dts_proc_open, 181 | .write = dts_proc_write, 182 | .release = dts_proc_release, 183 | }; 184 | #endif 185 | 186 | static int dts_pernet_id = 0; 187 | 188 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) 189 | static int dts_pernet_init(struct net *net) 190 | { 191 | struct dts_pernet *dts = net_generic(net, dts_pernet_id); 192 | dts->net = net; 193 | dts->pde = proc_create_data(DTS_PDE_NAME, 0600, net->proc_net, &dts_proc_fops, dts); 194 | return !dts->pde; 195 | } 196 | static void dts_pernet_exit(struct net* net) 197 | { 198 | struct dts_pernet *dts = net_generic(net, dts_pernet_id); 199 | BUG_ON(!dts->pde); 200 | remove_proc_entry(DTS_PDE_NAME, net->proc_net); 201 | } 202 | #else 203 | static int dts_pernet_init(struct net *net) 204 | { 205 | struct dts_pernet *dts = NULL; 206 | 207 | dts = kzalloc(sizeof(*dts), GFP_KERNEL); 208 | if (!dts) return -ENOMEM; 209 | 210 | dts->net = net; 211 | dts->pde = proc_create_data(DTS_PDE_NAME, 0600, net->proc_net, &dts_proc_fops, dts); 212 | if (!dts->pde) { 213 | kfree(dts); 214 | return -ENOMEM; 215 | } 216 | 217 | if (net_assign_generic(net, dts_pernet_id, dts)) { 218 | kfree(dts); 219 | return -ENOMEM; 220 | } 221 | 222 | return 0; 223 | } 224 | static void dts_pernet_exit(struct net* net) 225 | { 226 | struct dts_pernet *dts = net_generic(net, dts_pernet_id); 227 | net_assign_generic(net, dts_pernet_id, NULL); 228 | BUG_ON(!dts->pde); 229 | remove_proc_entry(DTS_PDE_NAME, net->proc_net); 230 | kfree(dts); 231 | } 232 | #endif 233 | 234 | static struct pernet_operations dts_pernet_ops = { 235 | .init = dts_pernet_init, 236 | .exit = dts_pernet_exit, 237 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) 238 | .id = &dts_pernet_id, 239 | .size = sizeof(struct dts_pernet), 240 | #endif 241 | }; 242 | 243 | static inline int dts_register_pernet(void) 244 | { 245 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) 246 | return register_pernet_subsys(&dts_pernet_ops); 247 | #else 248 | return register_pernet_gen_subsys(&dts_pernet_id, &dts_pernet_ops); 249 | #endif 250 | } 251 | 252 | static inline void dts_unregister_pernet(void) 253 | { 254 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 33) 255 | unregister_pernet_subsys(&dts_pernet_ops); 256 | #else 257 | unregister_pernet_gen_subsys(dts_pernet_id, &dts_pernet_ops); 258 | #endif 259 | } 260 | 261 | //////////////////////////////////////////////////////////////////////////////// 262 | 263 | int init_module(void) 264 | { 265 | int res = 0; 266 | 267 | res = dts_register_pernet(); 268 | if (res) return res; 269 | 270 | return 0; 271 | } 272 | 273 | void cleanup_module(void) 274 | { 275 | dts_unregister_pernet(); 276 | } 277 | 278 | MODULE_AUTHOR("Ilya V. Matveychikov "); 279 | MODULE_LICENSE("GPL"); 280 | --------------------------------------------------------------------------------