1. Overview
The iptables command offers several non-terminating targets that can inspect or modify network packets before they’re either dropped or accepted. One of these non-terminating targets is the LOG target. With the LOG target, any network packets that match the filter will be logged by the kernel logging service, syslog.
In this tutorial, we’ll look at a step-by-step example of building a chain on the filter table that logs a matching network packet before dropping it.
2. Combining the LOG With ACCEPT or DROP Target
The iptables command-line tool is a firewall tool in Linux. It works by hooking into the netfilter framework’s hooks of the Linux kernel to filter packets and apply actions on packets that match. Through iptables, we can define rules that apply to network packets traversing the system.
2.1. Quick Primer to iptables
The iptables command organizes the rules sequentially into a chain. The chain, in turn, belongs to one of the three tables we can administer through iptables, namely the filter, nat, and mangle tables. For firewall rules that do not need to modify the packet, we’ll typically create rules on the filter table. In fact, without specifying the -t option, the iptables command assumes that we’re working on the filter table.
One usual rule we can install using the iptables command is to accept or drop packets that match a certain filter. To do that, we’ll need to append the rule to one of the built-in chains of the filter table:
$ sudo iptables -A INPUT -p tcp --dport 28 -j DROP
The command above uses the -A option to append a rule to the INPUT chain. Specifically, the rule we’re appending to the INPUT table drops all the TCP packets targeting port 28.
2.2. Combining With Non-Terminating Target
This one-liner works great when we just want to apply basic rules to the chains. However, this approach won’t work if we want to apply some non-terminating targets, such as the LOG target, before we drop the packets.
To achieve that, we’ll have to create a custom chain that consists of two rules, LOG and DROP. Then, we append the custom chain to the main filter tables, applying the appropriate filter.
In the following sections, we’ll demonstrate this idea by configuring an iptables rule that drops all the incoming TCP packets targeting port 28.
2.3. Prerequisites
Before we begin, there are two prerequisites we’ll need to check. First, we’ll have to have sudo privileges for configuring the iptables rules.
Besides that, we’ll need to have the iptables command-line tool. To obtain the iptables program, we can install the iptables package using the package manager of our system:
$ sudo apt-get install -y iptables
We can verify the presence of the tool by printing its version using the -v option:
$ iptables -V
iptables v1.8.4 (legacy)
2.4. Creating a New Custom Chain
Before we create a new chain, it’s always a good idea to check the current rules configured on the chains of the filter table. To do that, we can use the -L option:
$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
By default, the filter table consists of three built-in chains for incoming, outgoing, and forwarding packets. For a fresh Linux system, these tables will be empty as there are no rules on the chains. However, if the system has programs that configure the filter tables, such as Docker, the chain might contain some existing entries.
To create a new chain, we can use the -N option on iptables, followed by the chain name. Let’s name our custom chain LOG_AND_DROP:
$ sudo iptables -N LOG_AND_DROP
Let’s print the state of our iptables again:
$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain LOG_AND_DROP (0 references)
target prot opt source destination
From the output, we can see our new LOG_AND_DROP chain. In its current state, the chain is not doing anything as it’s not being referenced by another chain and has no rules.
2.5. Adding Rules to the Custom Chain
In our case, we want to log the packet and then drop it. Therefore, we’ll add two rules to the LOG_AND_DROP chain. Specifically, we’ll add a LOG target to the chain first, then the DROP target. Since the chain is made specifically for logging and dropping all the packets that come into it, we don’t have to apply any filtering matches.
To append the LOG target to our LOG_AND_DROP chain, we use the -A option on the iptables command:
$ sudo iptables -A LOG_AND_DROP -j LOG --log-level 4 --log-prefix "[INPUT][TARGET-PORT-28][DROP]: "
The LOG target takes several optional logging parameters. In our example, we use the –log-level and –log-prefix options to configure the logging rule.
The –log-level option specifies the level of logs we want to apply. Since the actual logging is done by the kernel logging facility, syslog, the available levels we can apply follow syslog‘s levels. Then, we add a prefix to our log using the –log-prefix option. This prefix will make it easier for us to filter the logs from other kernel logs in the same file.
Next, we add the DROP rule using the same -A option on iptables:
$ sudo iptables -A LOG_AND_DROP -j DROP
After we add the two new rules to our chain, we can double-confirm the configuration of our chain by listing the chain using the -L option:
$ sudo iptables -L LOG_AND_DROP
Chain LOG_AND_DROP (0 references)
target prot opt source destination
LOG all -- anywhere anywhere LOG level debug prefix "[INPUT][TARGET-PORT-28][DROP]"
DROP all -- anywhere anywhere
2.6. Directing Packets to the Custom Chain
The final piece of the puzzle is to direct all the incoming network packets targeting port 28 to our LOG_AND_DROP chain. To do that, we’ll append a rule to the INPUT chain of the filter table to direct the matching packet to our chain:
$ sudo iptables -A INPUT -p tcp --dport 28 -j LOG_AND_DROP
The command above appends a new rule to the INPUT chain. Specifically, the rule matches the TCP packets with a destination port 28. Then, for all the packets that match the condition, we direct them to our LOG_AND_DROP chain.
Let’s verify our configuration by listing the chains:
$ iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
LOG_AND_DROP tcp -- anywhere anywhere tcp dpt:28
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain LOG_AND_DROP (1 references)
target prot opt source destination
LOG all -- anywhere anywhere LOG level debug prefix "[INPUT][TARGET-PORT-28][DROP]"
As we can see from the output, the LOG_AND_DROP chain now has one reference. We can track this reference back to the INPUT chain, which now targets the LOG_AND_DROP chain with the filter condition tcp dpt:28.
2.7. Verifying the Rule
To verify the rule, we first start a process that listens for TCP packets on port 28 using the nc command-line tool:
$ sudo nc -l 28
Then, on another machine, we can use the nc command-line tool to establish a connection to port 28 on the first machine:
$ sudo nc node1.local 28
We can test to see that after we send a message using the established channel, the message doesn’t show up on the server node. This is because our rules have already dropped the packet silently as it matches the filter.
Additionally, we can check for the log at /var/log/kern.log:
$ cat /var/log/kern.log | grep -F '[INPUT][TARGET-PORT-28][DROP]'
Sep 30 12:16:18 bob-ubuntu kernel: [15311.423316] [INPUT][TARGET-PORT-28][DROP]IN=enp0s3 OUT= MAC=08:00:27:a9:1b:40:00:e0:32:81:26:46:08:00 SRC=192.168.100.8 DST=192.168.100.64 LEN=52 TOS=0x00 PREC=0x00 TTL=128 ID=40606 DF PROTO=TCP SPT=64293 DPT=28 WINDOW=64240 RES=0x00 SYN URGP=0
The command above uses the grep command-line tool to filter for the prefix we specify on the rules. This is because the kern.log file contains all other kernel logs. The presence of the logs with our custom prefix confirms that our rule is working as expected.
3. Conclusion
In this tutorial, we’ve learned that the netfilter framework on the Linux kernel offers hooks that the user-space program can hook into to manipulate network packets. The iptables command is one such program that hooks into the netfilter framework’s hooks to implement firewall functionality.
Next, we demonstrated a step-by-step process for logging before dropping TCP packets that target port 28. First, we created a custom chain. Then, we added the LOG and DROP targets to the chain in a specific order. To complete the loop, we routed all the incoming TCP packets targeting port 28 to the chain. Finally, we showed how to test the iptables rules and verify that the logging worked as expected.