# `pf` Rule Tables - Advanced Options



## l008com (Jan 22, 2019)

I have a PF table defined in my .conf file to which my server automatically adds bad traffic. There are a bunch of different systems that will block an host for various reasons, and for various amounts of time. However rather than relying on PF itself to 'expire' old rules, I manage that myself by adding rules to an SQL table with an expiration date. Once a day, I delete all expired rules from SQL, then dump all rules in my 'bad hosts' table, then re-add all of the rules from SQL.

The most common bans are for 2 days, but they go all the way up to 90 day bans. (depends on how much you pissed me off )
There are a lot of rules coming and going constantly. Which is why I like doing things this way, that way no stray rules can get left behind. I start with a clean slate every day. 

The problem is, there is a very short period of time where all bad hosts are unblocked. Because I first flush all rules out of the table, then I re-add all non-expired hosts back to the table. Very aggressive hosts will get a few packets through before they get blocked again. This isn't a huge problem, but I would like to seal the gap nonetheless. 

1) The easiest way for me to do this would be to build the new table first, under the name newbadhosts. Once built, I could change the name of badhosts to oldbadhosts, and then change the name of newbadhosts to badhosts. Then I can drop oldbadhosts entirely. 

This is my preferred way of doing this, but I have not found any way to change table names like this. 

2) The second idea I had was similar, but instead of changing table names, using two fixed tables badhosts and oldbadhosts. Then at reset time, move all rules from the badhosts to oldbadhosts. Then rebuild badhosts, then empty oldbadhosts.

But if you can't change the name of a table, I doubt there is an easy way to move all of the rules in one table, to another table. I suppose I could read out the list of hosts in the table in my script, then manually add them to the old table.

3) The third way I thought of is kind of similar to 2 but doesn't require reading out the rules. I set up two tables, badhosts_a and badhosts_b. I have my script keep track of which table was the last one I loaded up. Then at reset time, I fill the "other" table (which should be empty), and then flush the first table. It would alternate back and forth every day. I could also check for an empty table instead of keeping track of which I used last, but if theres ever a glitch in the system where both tables are full or empty, I'd rather steam roll over that. 

Number 3 is probably the way I'm going to end up going. Unless anyone knows of a better option. Better, excluding PF's on built in rule expirations. I really prefer managing that myself. There are many benefits to it, including my great web based firewall monitor I was able to easily whip up, since all the info I need is right in SQL already.


----------



## Kristof Provost (Jan 22, 2019)

Wouldn't it make more sense to teach your script to look at the SQL data and remove only the expired addresses and add the missing ones, rather than removing everything and re-adding everything?


----------



## l008com (Jan 22, 2019)

Kristof Provost said:


> Wouldn't it make more sense to teach your script to look at the SQL data and remove only the expired addresses and add the missing ones, rather than removing everything and re-adding everything?


As I said, there are a lot of rules being added and expiring every day. I like doing it this way because it makes sure no rules ever get stuck. For example, if a rule expires, I remove it from the pf table, but for whatever reason that command is not successful, that rule and that IP is now blocked forever. I really like the "clean slate" nature of the way I do it.


----------



## Kristof Provost (Jan 22, 2019)

If you teach your code to look at what addresses are already present you'll get the same effect, without trying to make pf do things it can't do.

Alternatively, you could do multi-stage approaches like having two tables, adding new addresses to one and only then deleting the old ones from the other. If both are block rules you'll get the desired effect of not having a small window where blocked addresses can connect.


----------



## Datapanic (Jan 22, 2019)

Why not just make a separate table for every ban duration you have and then use `pfctl` with the expire option to purge entries based on age?  You could also export the entries of these tables and import them to SQL.  That way, you do not have a point in time where your blocking tables are 'off'.


----------



## l008com (Jan 23, 2019)

Kristof Provost said:


> Alternatively, you could do multi-stage approaches like having two tables, adding new addresses to one and only then deleting the old ones from the other. If both are block rules you'll get the desired effect of not having a small window where blocked addresses can connect.



Well yeah, that was plan "plan 3" above. That's probably what I'll do.


----------



## l008com (Jan 23, 2019)

Datapanic said:


> Why not just make a separate table for every ban duration you have and then use `pfctl` with the expire option to purge entries based on age?  You could also export the entries of these tables and import them to SQL.  That way, you do not have a point in time where your blocking tables are 'off'.


What would be the point of putting different length rules into different tables? If I'm having pf expire them anyway?


----------



## l008com (Jan 30, 2019)

So I made two "blacklist" tables and implemented switching between them in my scripts, storing the 'currently used' table in my sql table. It's working great and took less code than I expected it to.


----------

