ip46tables

wrote `ip46tables` C minimal program to handle both ipv4 and ipv6 at the same time.
fix #22
This commit is contained in:
ppom 2023-10-05 12:00:00 +02:00
parent e56b851d15
commit 92e07f5fe6
8 changed files with 133 additions and 42 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
/reaction
/ip46tables
/reaction*.db
/reaction*.sock
/result

9
Makefile Normal file
View file

@ -0,0 +1,9 @@
all: reaction ip46tables
clean:
rm -f reaction ip46tables
ip46tables: ip46tables.d/ip46tables.c
gcc ip46tables.d/ip46tables.c -o ip46tables
reaction: app/* reaction.go go.mod go.sum
go build .

View file

@ -32,9 +32,6 @@ definitions:
patterns:
ip: '(([0-9]{1,3}\.){3}[0-9]{1,3})|([0-9a-fA-F:]{2,90})'
ignore:
- '127.0.0.1'
- '::1'
streams:
ssh:
@ -63,7 +60,6 @@ local iptablesunban = ['iptables', '-w', '-D', 'reaction', '1', '-s', '<ip>', '-
patterns: {
ip: {
regex: @'(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3})|(?:[0-9a-fA-F:]{2,90})',
ignore: ['127.0.0.1', '::1'],
},
},
streams: {
@ -91,7 +87,7 @@ local iptablesunban = ['iptables', '-w', '-D', 'reaction', '1', '-s', '<ip>', '-
}
```
note that both yaml and jsonnet are extensions of json, so it is also inherently supported.
note that both yaml and jsonnet are extensions of json, so json is also inherently supported.
`/etc/systemd/system/reaction.service`
```systemd
@ -124,11 +120,24 @@ if you don't know where to start it, `/var/lib/reaction` should be a sane choice
the socket allowing communication between the cli and server will be created at `/run/reaction/reaction.socket`.
### `ip46tables`
`ip46tables` is a minimal c program present in its own subdirectory with only standard posix dependencies.
it permits to configure `iptables` and `ip6tables` at the same time.
it will execute `iptables` when detecting ipv4, `ip6tables` when detecting ipv6 and both if no ip address is present on the command line.
### compilation
you'll need the go toolchain.
you'll need the go toolchain for reaction and a c compiler for ip46tables.
```shell
$ make
```
alternatively,
```shell
$ go build .
$ gcc ip46tables.d/ip46tables.c -o ip46tables
```
### nixos

View file

@ -3,8 +3,10 @@
# using YAML anchors `&name` and pointers `*name`
# definitions are not readed by reaction
definitions:
- &iptablesban [ "iptables" "-w" "-A" "reaction" "1" "-s" "<ip>" "-j" "DROP" ]
- &iptablesunban [ "iptables" "-w" "-D" "reaction" "1" "-s" "<ip>" "-j" "DROP" ]
- &iptablesban [ "ip46tables" "-w" "-A" "reaction" "1" "-s" "<ip>" "-j" "DROP" ]
- &iptablesunban [ "ip46tables" "-w" "-D" "reaction" "1" "-s" "<ip>" "-j" "DROP" ]
# ip46tables is a minimal C program (only POSIX dependencies) present as a subdirectory.
# it permits to handle both ipv4/iptables and ipv6/ip6tables commands
# patterns are substitued in regexes.
# when a filter performs an action, it replaces the found pattern

View file

@ -3,8 +3,10 @@
// JSONNET is a superset of JSON, so one can write plain JSON files if wanted.
// variables defined for later use.
local iptablesban = ['iptables', '-w', '-A', 'reaction', '1', '-s', '<ip>', '-j', 'DROP'];
local iptablesunban = ['iptables', '-w', '-D', 'reaction', '1', '-s', '<ip>', '-j', 'DROP'];
local iptablesban = ['ip46tables', '-w', '-A', 'reaction', '1', '-s', '<ip>', '-j', 'DROP'];
local iptablesunban = ['ip46tables', '-w', '-D', 'reaction', '1', '-s', '<ip>', '-j', 'DROP'];
// ip46tables is a minimal C program (only POSIX dependencies) present as a subdirectory.
// it permits to handle both ipv4/iptables and ipv6/ip6tables commands
{
// patterns are substitued in regexes.

View file

@ -7,20 +7,22 @@ WantedBy=multi-user.target
ExecStart=/path/to/reaction -c /etc/reaction.yml
# Create an iptables chain for reaction
ExecStartPre=/path/to/iptables -w -N reaction
ExecStartPre=/path/to/ip46tables -w -N reaction
# Set its default to ACCEPT
ExecStartPre=/path/to/iptables -w -A reaction -j ACCEPT
ExecStartPre=/path/to/ip46tables -w -A reaction -j ACCEPT
# Always accept 127.0.0.1
ExecStartPre=/path/to/iptables -w -I reaction 1 -s 127.0.0.1 -j ACCEPT
ExecStartPre=/path/to/ip46tables -w -I reaction 1 -s 127.0.0.1 -j ACCEPT
# Always accept ::1
ExecStartPre=/path/to/ip46tables -w -I reaction 1 -s ::1 -j ACCEPT
# Insert this chain as the first item of the INPUT chain (for incoming connections)
ExecStartPre=/path/to/iptables -w -I INPUT -p all -j reaction
ExecStartPre=/path/to/ip46tables -w -I INPUT -p all -j reaction
# Remove the chain from the INPUT chain
ExecStopPost=/path/to/iptables -w -D INPUT -p all -j reaction
ExecStopPost=/path/to/ip46tables -w -D INPUT -p all -j reaction
# Empty the chain
ExecStopPost=/path/to/iptables -w -F reaction
ExecStopPost=/path/to/ip46tables -w -F reaction
# Delete the chain
ExecStopPost=/path/to/iptables -w -X reaction
ExecStopPost=/path/to/ip46tables -w -X reaction
# Ask systemd to create /var/lib/reaction (/var/lib/ is implicit)
StateDirectory=reaction

View file

@ -1,25 +0,0 @@
---
patterns:
num:
regex: '[0-9]+'
ip:
regex: '(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3})|(?:[0-9a-fA-F:]{2,90})'
ignore:
- 1.0.0.1
streams:
tailDown1:
cmd: [ "sh", "-c", "echo 1 2 3 4 5 1 2 3 4 5 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 | tr ' ' '\n' | while read i; do sleep 2; echo found $(($i % 10)); done" ]
filters:
findIP:
regex:
- '^found <num>$'
retry: 3
retryperiod: 30s
actions:
damn:
cmd: [ "echo", "<num>" ]
undamn:
cmd: [ "echo", "undamn", "<num>" ]
after: 30s
onexit: true

91
ip46tables.d/ip46tables.c Normal file
View file

@ -0,0 +1,91 @@
#include<ctype.h>
#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
// If this programs
// - receives an ipv4 address in its arguments:
// → it will executes iptables with the same arguments in place.
//
// - receives an ipv6 address in its arguments:
// → it will executes ip6tables with the same arguments in place.
//
// - doesn't receive an ipv4 or ipv6 address in its arguments:
// → it will executes both, with the same arguments in place.
int isIPv4(char *tab) {
int i,len;
// IPv4 addresses are at least 7 chars long
len = strlen(tab);
if (len < 7 || !isdigit(tab[0]) || !isdigit(tab[len-1])) {
return 0;
}
// Each char must be a digit or a dot between 2 digits
for (i=1; i<len-1; i++) {
if (!isdigit(tab[i]) && !(tab[i] == '.' && isdigit(tab[i-1]) && isdigit(tab[i+1]))) {
return 0;
}
}
return 1;
}
int isIPv6(char *tab) {
int i,len, twodots = 0;
// IPv6 addresses are at least 3 chars long
len = strlen(tab);
if (len < 3) {
return 0;
}
// Each char must be a digit, :, a-f, or A-F
for (i=0; i<len; i++) {
if (!isdigit(tab[i]) && tab[i] != ':' && !(tab[i] >= 'a' && tab[i] <= 'f') && !(tab[i] >= 'A' && tab[i] <= 'F')) {
return 0;
}
}
return 1;
}
int guess_type(int len, char *tab[]) {
int i;
for (i=0; i<len; i++) {
if (isIPv4(tab[i])) {
return 4;
} else if (isIPv6(tab[i])) {
return 6;
}
}
return 0;
}
int exec(char *str, char **argv) {
argv[0] = str;
execvp(str, argv);
// returns only if fails
printf("ip46tables: exec failed %d\n", errno);
}
int main(int argc, char **argv) {
if (argc < 2) {
printf("ip46tables: At least one argument has to be given\n");
exit(1);
}
int type;
type = guess_type(argc, argv);
if (type == 4) {
exec("iptables", argv);
} else if (type == 6) {
exec("ip6tables", argv);
} else {
pid_t pid = fork();
if (pid == -1) {
printf("ip46tables: fork failed\n");
exit(1);
} else if (pid) {
exec("iptables", argv);
} else {
exec("ip6tables", argv);
}
}
}