Compare commits

...

5 Commits

4 changed files with 114 additions and 69 deletions

View File

@ -17,6 +17,7 @@ clean:
install: iblock
install -o root -g wheel iblock ${PREFIX}/sbin/
install -o root -g wheel iblock.rc /etc/rc.d/iblock
install -o root -g wheel iblock.8 ${PREFIX}/man/man8/
test: clean iblock
@printf "hello\n" | nc -4 localhost 666

View File

@ -40,10 +40,10 @@ pass in quick on egress inet6 proto tcp to port $blocking_tcp rdr-to ::1 port 25
Don't forget to reload the rules with `pfctl -f /etc/pf.conf`.
Use another table name by passing it as argument to iblock :
Use another table or port name by passing appropriate flags to iblock:
```
rcctl set iblock flags another_table_name
rcctl set iblock flags -t another_table_name -p 5373
```
# Get some statistics
@ -51,8 +51,3 @@ rcctl set iblock flags another_table_name
Done! You can see IP banned using `pfctl -t iblocked -T show` and iblock will send blocked addresses to syslog.
In the example I added a label to the block rule, you can use `pfctl -s labels` to view statistics from this rule, [see documentation for column meaning](https://man.openbsd.org/pfctl#s~8).
# TODO
- A proper man page

30
iblock.8 Normal file
View File

@ -0,0 +1,30 @@
.Dd $Mdocdate: September 03 2023 $
.Dt iblock 8
.Os
.Sh NAME
.Nm iblock
.Nd add unwanted IP to pf table
.Sh SYNOPSIS
.Nm iblock
.Op Fl t Ar table
.Op Fl p Ar port
.Sh DESCRIPTION
.Nm
is a program adding the client IP to a Packet Filter table.
.Pp
It is meant to be used to block scanner connecting on unused ports.
Upon connection, the IP is added to a PF table and all established connections with this IP are killed. You need to use a PF bloking rule using the table.
.Sh OPTIONS
.Bl -tag -width Ds
.It Op Fl t Ar table
Set the pf
.Ar table
to add the detected IP.
.It Op Fl p Ar port
Set the listening
.Ar port .
.El
.Sh DEPLOYMENT
TODO

143
main.c
View File

@ -22,12 +22,32 @@
#define MAXSOCK 2 /* ipv4 + ipv6 */
#define BACKLOG 10
static void ban(const char *, const char *);
static void *get_in_addr(struct sockaddr *);
static void runcmd(const char*, const char**);
static void runcmd(const char**);
static int setup_server(const char*, int *);
static void usage(void);
static void watch_event(const int, const int *, const char *);
static void
ban(const char *ip, const char *table)
{
const char *bancmd[] = { "/usr/bin/doas", "-n",
"/sbin/pfctl", "-t", table,
"-T", "add", ip,
NULL };
const char *killstatecmd[] = { "/usr/bin/doas", "-n",
"/sbin/pfctl",
"-k", ip,
NULL };
syslog(LOG_DAEMON, "block and kill states for %s", ip);
runcmd(bancmd);
runcmd(killstatecmd);
}
/* return printable ip from sockaddr */
static void
*get_in_addr(struct sockaddr *sa)
@ -40,14 +60,14 @@ static void
/* run cmd in execv() after fork() */
static void
runcmd(const char* cmd, const char** arg_list)
runcmd(const char **cmd_arg_list)
{
pid_t pid = fork();
if (pid == -1) {
syslog(LOG_DAEMON, "fork error");
err(1,"fork");
} else if (pid == 0) { /* child */
execv(cmd, (char **)arg_list);
execv(cmd_arg_list[0], (char **)cmd_arg_list);
/* if this is reached, then exec failed */
syslog(LOG_DAEMON, "execv error");
err(1,"execv");
@ -132,78 +152,37 @@ usage(void)
exit(1);
}
int
main(int argc, char *argv[])
static void
watch_event(const int nsock, const int s[], const char *table)
{
char table[PF_TABLE_NAME_SIZE] = DEFAULT_TABLE;
char port[6] = DEFAULT_PORT;
char ip[INET6_ADDRSTRLEN] = {'\0'};
int kq = 0;
int new_fd = 0;
int nsock = 0;
int kq = 0;
int option = 0;
int s[MAXSOCK] = {0};
socklen_t sin_size = 0;
char ip[INET6_ADDRSTRLEN] = {'\0'};
struct kevent ev[MAXSOCK] = {0};
socklen_t sin_size = 0;
struct sockaddr_storage client_addr;
/* initialize structures */
memset(&client_addr, 0, sizeof(client_addr));
while ((option = getopt(argc, argv, "t:p:")) != -1) {
switch (option) {
case 'p':
if (strlcpy(port, optarg, sizeof(port)) >=
sizeof(port))
err(1, "invalid port");
break;
case 't':
if (strlcpy(table, optarg, sizeof(table)) >=
sizeof(table))
err(1, "table name too long");
break;
default:
usage();
break;
}
}
const char *bancmd[] = { "/usr/bin/doas", "-n",
"/sbin/pfctl", "-t", table,
"-T", "add", ip,
NULL };
const char *killstatecmd[] = { "/usr/bin/doas", "-n",
"/sbin/pfctl",
"-k", ip,
NULL };
/* safety first */
if (unveil("/usr/bin/doas", "rx") != 0)
err(1, "unveil");
/* necessary to resolve localhost with getaddrinfo() */
if (unveil("/etc/hosts", "r") != 0)
err(1, "unveil");
if (pledge("stdio inet exec proc rpath", NULL) != 0)
err(1, "pledge");
nsock = setup_server(port, s);
/* configure events */
kq = kqueue();
/* add event for each IP */
for (int i = 0; i <= nsock; i++)
EV_SET(&(ev[i]), s[i], EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
for (int i = 0; i < nsock; i++)
EV_SET(&(ev[i]), s[i],
EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, 0);
/* register event */
if (kevent(kq, ev, MAXSOCK, NULL, 0, NULL) == -1)
err(1, "kevent");
err(1, "kevent register");
/* infinite loop to wait for connections */
for (;;) {
int nevents = kevent(kq, NULL, 0, ev, MAXSOCK, NULL);
if (nevents == -1)
err(1, "kevent");
err(1, "kevent get event");
/* loop for events */
for (int i = 0; i < nevents; i++) {
@ -223,11 +202,7 @@ main(int argc, char *argv[])
close(new_fd); /* no longer required */
/* ban this ip */
syslog(LOG_DAEMON, "blocking %s", ip);
runcmd(bancmd[0], bancmd);
syslog(LOG_DAEMON, "kill states for %s", ip);
runcmd(killstatecmd[0], killstatecmd);
ban(ip, table); /* ban this ip */
}
if (ev[i].filter & EVFILT_SIGNAL) {
break;
@ -235,9 +210,53 @@ main(int argc, char *argv[])
} /* events loop */
} /* infinite loop */
/* probably never reached */
/* probably never reached, but close properly */
close(kq);
for (int i = 0; i <= nsock; i++)
}
int
main(int argc, char *argv[])
{
char table[PF_TABLE_NAME_SIZE] = DEFAULT_TABLE;
char port[6] = DEFAULT_PORT;
int nsock = 0;
int option = 0;
int s[MAXSOCK] = {0};
while ((option = getopt(argc, argv, "t:p:")) != -1) {
switch (option) {
case 'p':
if (strlcpy(port, optarg, sizeof(port)) >=
sizeof(port))
err(1, "invalid port");
break;
case 't':
if (strlcpy(table, optarg, sizeof(table)) >=
sizeof(table))
err(1, "table name too long");
break;
default:
usage();
break;
}
}
/* safety first */
if (unveil("/usr/bin/doas", "rx") != 0)
err(1, "unveil");
/* necessary to resolve localhost with getaddrinfo() */
if (unveil("/etc/hosts", "r") != 0)
err(1, "unveil");
if (pledge("stdio inet exec proc rpath", NULL) != 0)
err(1, "pledge");
nsock = setup_server(port, s);
watch_event(nsock, s, table);
/* probably never reached, but close properly */
for (int i = 0; i < nsock; i++)
close(s[i]);
return 0;
}