Compiling and installing mainline kernel alongside vendor/stock kernel on Odroid C2

Introduction

NOTE: This guide is a copy of something I kept locally for a long time and may be slightly outdated, as we’ve now reached linux kernel 5.x serie. Still, it should be usable regardless, although some steps could differ slightly.

This guide will help you into compiling and installing latest mainline kernel on your Odroid C2, keeping the vendor kernel in case of emergency. Note that this installation is what is called a vanilla installation, and does not base itself on the modified kernel for ArchLinux, nor the one for ArchLinux-ARM.
WARNING ! WARNING ! \
If you plan on following the steps to get your RTC working with this
kernel, it is strongly advised to avoid booting the 3.14 kernel ever
again. The fact the RTC driver is not compiled as built-in in 3.14
kernel nor mainline by default makes it so that if you are working on
things that depend on them, (i.e: mysql databases), they will be
confused at the sudden change of time when booting up the older kernel
which has RTC as module. Hence, it is strongly advised to remove the
3.14 kernel alltogether.

What is in this guide ?

  1. Getting the kernel tarball
  2. Decompressing the tarball
  3. Make defconfig, what’s that all about ?
  4. Make menuconfig
    1. Enabling snd_usb_audio as a module
    2. [Optional] Enabling speakup and speakup_soft
    3. Enabling the RTC driver as built-in
    4. [Recommended] Compress the modules when installing
    5. [Recommended] Trim unused exported kernel symbols
    6. Enabling CONFIG_EXT4_FS_SECURITY and extended attributes for tmpfs
  5. Make, oof, you can take a break, it’s your odroid’s turn to have fun
  6. Make modules_install dtbs_install
  7. Creating a preset file for mkinitcpio and making a new initramfs
  8. [Optional] Add the RTC shield to the device tree blob
  9. Wrapping the kernel, what in the world does that mean ?
  10. Creating a U-Boot configuration from scratch
    1. With hardkernel’s specific U-Boot
    2. With mainline U-Boot

Getting the kernel tarball

First things first, let’s go on the Linux kernel’s website, and grab the latest tarball for mainline, which is 4.12.0 at the time of this writing. No need to go in their git repository as there’s a direct link to the latest mainline kernel directly available from there.

Decompressing the tarball

Once you’ve got the kernel downloaded, next thing to do is to decompress it. The following command will do it:

tar -xvJf linux-4.12.tar.xz

Where 4.12 is the version number of the kernel, yours may differ. Once that is taken care of, go in the newly created directory:

cd linux-4.12/

It is also necessary to install a few packages in order to compile the kernel successfully:

  • xmlto
  • docbook-xsl
  • kmod
  • inetutils
  • bc

Make defconfig, what’s that all about ?

Make defconfig is actually something really easy. It is used to generate the default configuration for the kernel — it’s better like this or you’d be designing your own configuration by hand or via make menuconfig, from botum to top. So now, type the following and let it work a bit:

make defconfig

Make menuconfig

Now it’s fun time ! You have a basic but working configuration for your kernel. It is still missing three things, though:

  • RTC driver for pcf8563 built-in.
  • The snd_usb_audio module to get most usb sound cards working out of the box.
  • Optionally, speakup and speakup_soft modules to get console speech.
  • And anything else you’d like to enable.

Type the following, you will end up in a nice menu with all sorts of options:

make menuconfig

Enabling snd_usb_audio

By default, the kernel configuration does not include the snd_usb_audio module which allows you to plug most driverless sound card and get them working instantly. So, we’re going to enable it, because if you plan on getting console speech without it, it won’t work for sure. Uses the arrow keys to navigate in the menu:

  • Device Drivers
    • Sound card support
      • Advanced Linux Sound Architecture
        • USB sound devices

Once there, make sure USB Audio/MIDI driver is selected and press ‘m’ to modularize it into the kernel. Press ‘x’ as many times as necessary to go back in the Device Drivers submenu.

[Optional] Enabling speakup and speakup_soft modules

Navigate with the arrow keys up to the Staging Drivers submenu. You’ll notice the brackets in front of it are empty, so press ‘y’ on your keyboard to enable this into the kernel as built-in. Press enter on this menu item, and navigate to Speakup Console Speech. Press enter on this item, and now press ‘m’ on the first and only available item at this point, speakup. Other items are unlocked when this is done so, find speakup userspace soft driver and press ‘m’. Press ‘x’ twice to get back to Device Drivers.

Enabling the RTC

Now, go into Real Time Clock submenu, and arrow down until you find Philips PCF8563/Epson RTC8564, and simply press ‘y’. You’re done ! Press ‘x’ twice to exit both the real time clock submenu and Device Drivers.

[Recommended] Compress the modules when installing

Quoting the Linux Kernel Driver Database:

Compresses kernel modules when ‘make modules_install’ is run; gzip or xz depending on “Compression algorithm” below.

module-init-tools MAY support gzip, and kmod MAY support gzip and xz.

Out-of-tree kernel modules installed using Kbuild will also be compressed upon installation.

Note: for modules inside an initrd or initramfs, it’s more efficient to compress the whole initrd or initramfs instead.

Note: This is fully compatible with signed modules.

To save disk space, it is recommended to set a compression method for the modules. This way, when we will install them later on, they will automatically be compressed, thus using less disk space on your eMMC/micro SD card. So, let’s do just that:

  • Enable loadable module support
    • Press y on ‘compress modules on installation’
      • Compression algorythm

Press the spacebar on the ‘XZ’ method.

[Recommended] Enable trimming of unused exported kernel symbols

From LKDDB:

The kernel and some modules make many symbols available for other modules to use via EXPORT_SYMBOL() and variants. Depending on the set of modules being selected in your kernel configuration, many of those exported symbols might never be used.

This option allows for unused exported symbols to be dropped from the build. In turn, this provides the compiler more opportunities (especially when using LTO) for optimizing the code and reducing binary size. This might have some security advantages as well.

If unsure, or if you need to build out-of-tree modules, say N.

Still in the module support menu, arrow down till you find Trim unused exported kernel symbols and press ‘y’. Then, press ‘x’ twice to exit the menu, and finally press enter when you are prompted about saving your configuration.

Enabling CONFIG_EXT4_FS_SECURITY and extended attributes for tmpfs

According to the Linux Kernel Driver Database:

Security labels support alternative access control models implemented by security modules like SELinux. This option enables an extended attribute handler for file security labels in the ext4 filesystem.

If you are not using a security module that requires using extended attributes for file security labels, say N.

In our case, this option is required because some tools such as ping(8) and tshark(1) from wireshark requires special capabilities that can’t be set otherwise. Since I do not know where exactly those options are in make menuconfig, we will do it manually. Open your .config file into your text editor like this:

nano .config

And press ctrl+w to search for ‘CONFIG_EXT4_FS_SECURITY’. You should find yourself on a line saying that this option is not set. Remove the ‘#’ symbol in front of it, also the extra space, and go at the end of the line to erase ‘is not set’. Replace it with ‘=y’, so that the line now looks like this:

CONFIG_EXT4_FS_SECURITY=y

Search for this line and repeat the same thing: CONFIG_TMPFS_XATTR.

  • Notes:
    • Enabling extended attributes for most filesystem in general is a good thing, unless you know you will never need it for a particular filesystem.

Save and close your text editor.

Make, oof, you can take a break, it’s your Odroid’s turn to have fun

Now that your kernel configuration is done, all you have to do is:

make
  • Notes:
    • The make command actually support specifying the number of thread it should allocate to compile things. So if your processor has, say, 4 cores, you can pass the following argument: -j4.

So, you’d need to type:

make -j4

You can now leave it running, it will take a while — about 40 ~50 minutes with all cores running at their best.

Make modules_install dtbs_install

I hope you are refreshed and well, it’s now time to install the modules and the dtbs your odroid just ended compiling. For now, make sure the make command didn’t give you any error and type this:

sudo make modules_install dtbs_install

This will install the modules of the compiled kernel at the right place, usually into /usr/lib/modules/kernel_version. This will also install all the device tree blob files for all the supported SoCs in /boot/dtbs/kernel_version. Once this is completed, move on to next step.

Creating a preset file for mkinitcpio and generating a new initramfs

It’s now time to generate a new initramfs for your kernel that will be put alongside the one that already exists for the vendor kernel. We will copy the existing preset file and modify it by hand:

sudo cp /etc/mkinitcpio.d/linux.preset /etc/mkinitcpio.d/linux-mainline.preset

Now, open the newly created file into your text editor, I personally use nano:

sudo nano /etc/mkinitcpio.d/linux-mainline.preset

Modify the file so it looks like this:

# mkinitcpio preset file for 'linux-mainline'.

ALL_config="/etc/mkinitcpio.conf"
ALL_kver="4.12.0"
PRESETS=('default')
#default_config="/etc/mkinitcpio.conf"
default_image="/boot/initramfs-linux-mainline.img"
#default_options=""
  • Notes:
    • The actual file name of your preset doesn’t matter, as long as it is something you can remember.
    • Defaut_image can be anything fore the image name, as long as it is located in the /boot directory.
    • It is better if ALL_kver matches the kernel version you are installing.

Save and exit your text editor, and run the following to generate the initramfs corresponding to your new preset:

sudo mkinitcpio -p linux-mainline

This will create a file called ‘linux-mainline.img’ in /boot. Now that this has been taken care of, let the harder part begins.

[Optional] Add the RTC to the device tree

We will use a set of tools from the dtc package, mainly fdtget and fdtput to modify the blob binary of the device tree that was placed in /boot/dtbs/kernel_version to add a new subnode in the corresponding bus controler, and set the required properties for it to be detected, allowing you to use the RTC shield of your odroid. It is recommanded that you backup the original file in case of mistakes, so let’s do just that

sudo mkdir /boot/dtb-backup
sudo cp /boot/dtbs/4.12.0/amlogic/meson-gxbb-odroidc2.dtb /boot/dtb-backup

Now, use the fdtget tool to explore the content of the blob:

fdtget -l /boot/dtbs/4.12.0/amlogic/meson-gxbb-odroidc2.dtb /soc

Sample output:

[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]

What interest us here is the cbus subnode:

fdtget -l /boot/dtbs/4.12.0/amlogic/meson-gxbb-odroidc2.dtb /soc/cbus

Sample output:

[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]

Let’s check the status of [email protected]:

fdtget /boot/dtbs/4.12.0/amlogic/meson-gxbb-odroidc2.dtb /soc/cbus/[email protected] status

It should report ‘okay’. Now, let’s create a subnode under this controler. It’s usually called [email protected].

sudo fdtput -c /boot/dtbs/4.12.0/amlogic/meson-gxbb-odroidc2.dtb /soc/cbus/[email protected]/[email protected]

Now, let’s add the properties to that subnode:

sudo fdtput -t s /boot/dtbs/4.12.0/amlogic/meson-gxbb-odroidc2.dtb /soc/cbus/[email protected]/[email protected] compatible "nxp,pcf8563"
sudo fdtput -t x /boot/dtbs/4.12.0/amlogic/meson-gxbb-odroidc2.dtb /soc/cbus/[email protected]/[email protected] reg 0x51

Let’s list the properties and make sure they are right:

fdtget /boot/dtbs/4.12.0/amlogic/meson-gxbb-odroidc2.dtb /soc/cbus/[email protected]/pcf8563 compatible
fdtget /boot/dtbs/4.12.0/amlogic/meson-gxbb-odroidc2.dtb /soc/cbus/[email protected]/pcf8563 reg

Compatible should report exactly ‘nxp,pcf8563’ and reg should report 81 which is 0x51 in hexadecimal.

Wrapping the kernel, what in the world does that mean ?

For now, your kernel is a simple raw image that doesn’t even have headers. We need to wrap it up first, in order to boot it up successfully. What does wrapping actually means ? Well, it means that we’re going to use the Image.gz file that is in your kernel source tree and ask a U-boot utility called mkimage to create a bootable image from it. So if it isn’t already installed, install the uboot-tools package. Here are the details you need to know and use:

  • Architecture is arm64.
  • OS type is linux.
  • Image type is kernel.
  • Compression method is gzip.
  • The kernel load address is 0x1080000.
  • Entry point is also 0x1080000.
  • Name can be anything you want.

So, all you have to do at this point is:

sudo mkimage -A arm64 -O linux -T kernel -C gzip -a 0x1080000 -e 0x1080000 -n linux-mainline -d arch/arm64/boot/Image.gz /boot/uImage

Sample output:

Image Name:   linux-mainline
Created:      Sun Feb 26 19:06:29 2017
Image Type:   AArch64 Linux Kernel Image (gzip compressed)
Data Size:    6431862 Bytes = 6281.12 kB = 6.13 MB
Load Address: 01080000
Entry Point:  01080000

You’re done with this step, you now have a bootable kernel image.

Creating a U-boot configuration from scratch

With hardkernel’s specific U-Boot

The U-Boot configuration that comes with Archlinux-ARM by default is not really suitable for a kernel mainline just yet. It is better to rename /boot/boot.ini into something else to still keep it around and writing your own configuration by hand. There are some things to know, though:

  • The serial console name is no longer ttyS0. As the kernel you have compiled is a generic kernel, they had to differenciate on which platform you are running. New name for the serial console is ttyAML0.
  • Be careful when setting up bootargs. Don’t forget to set the root fs, in particular.
  • Be careful when setting up load_addr, dtb_addr and initrd_addr. DTB is small, kernel is a bit bigger and initramfs is the biggest. You can load them wherever you want, except not below 0x1000000, and not to high above as to avoid accidentally overwriting the RAM. Make sure they are all far enough from each other as to not overlap, or that will result in unexplainable crashes.
  • If you plan on using usb, for the timebeing you will have to add the command ‘usb pwren’ into your configuration, as long as it is set before the final bootm command to boot the system.

Create an empty file called boot.ini:

sudo nano /boot/boot.ini

And put something similar into it:

ODROIDC2-UBOOT-CONFIG
setenv condev "console=ttyAML0,115200n8 console=tty0"
usb pwren
setenv bootargs "root=LABEL=ArchLinuxC2 rootwait rw ${condev} no_console_suspend fsck.repair=yes net.ifnames=0"
setenv loadaddr "0x12000000"
setenv dtb_loadaddr "0x2000000"
setenv initrd_loadaddr "0x33000000"
load mmc 0:1 ${loadaddr} /boot/uImage
load mmc 0:1 ${dtb_loadaddr} /boot/dtbs/4.12.0/amlogic/meson-gxbb-odroidc2.dtb
fdt addr ${dtb_loadaddr}
load mmc 0:1 ${initrd_loadaddr} /boot/initramfs-linux-mainline.img
bootm ${loadaddr} ${initrd_loadaddr}:${filesize} ${dtb_loadaddr}

You’re finally done ! Now, all you have to do is reboot the board, and it should boot as expected, with your new kernel.

With mainline U-Boot

For mainline U-Boot, usb pwren is not necessary from linux 4.12 onward, due to a workaround for the usb being made to work despite mainline U-Boot not supporting any form of usb command. All in all the same rules apply like the above, but do keep in mind you now need to use one of ‘mmc 0’ if you are using a micro SD card or ‘mmc 1’ if you are using an eMMC card. Also please do keep in mind the bootm command will not work with U-Boot 2017.05-rc3 and below due to a value being not large enough to support the deflating of the initramfs.

sudo nano /boot/boot.txt

setenv condev "console=ttyAML0,115200n8 console=tty0"
setenv bootargs "root=LABEL=ArchLinuxC2 rootwait rw ${condev} no_console_suspend fsck.repair=yes net.ifnames=0"
setenv loadaddr "0x12000000"
setenv dtb_loadaddr "0x2000000"
setenv initrd_loadaddr "0x33000000"
load mmc 1 ${loadaddr} /boot/uImage
load mmc 1 ${dtb_loadaddr} /boot/dtbs/4.12.0/amlogic/meson-gxbb-odroidc2.dtb
fdt addr ${dtb_loadaddr}
load mmc 1 ${initrd_loadaddr} /boot/initramfs-linux-mainline.img
bootm ${loadaddr} ${initrd_loadaddr}:${filesize} ${dtb_loadaddr}

Then you have to go in /boot and use the mkscr to generate a wrapper around boot.txt, like so:

cd /boot && sudo ./mkscr

You’re finally done ! Now, all you have to do is reboot the board, and it should boot as expected, with your new kernel.

Final notes

  • If something bad happens, make sure to have a uart cable nearby.
  • If you get a random IP address changing at every boot, make a static IP configuration with whatever network manager you are using.
  • Alternatively, if you are using systemd-networkd (must
    be possible with other network managers), you can create
    a file containing this, as an example, assuming your interface
    name is eth0 — it should be if you added “net.ifnames=0” to
    the bootargs in boot.ini or boot.txt depending on which U-Boot you are using.

/etc/systemd/network/10-eth0.link

[Match]
OriginalName=eth0

[Link]
MACAddress=00:1e:06:33:5a:6d

Enjoy !

Credits

I would like to thank Michal Zegan Aka. Webcat on irc. Without his precious help, this guide wouldn’t exist, nor would I be running mainline kernel on my own Odroid C2. Technically the credits are all his, all I did was putting his instructions in a guide that is, I hope, easy to read.
I am not in any way responseable if something really bad happens. All I did was providing instructions that should work for you, unless there is definitely something wrong going on. As always, be careful, and remember you still have an emergency kernel.

How to: Connect to IOFlood’s VPN

How to: Connect to IOFlood’s VPN

IOFlood is a company providing nice dedicated servers at relatively low prices for their worth. I have gotten my own server from them, and the staff was very helpful in explaining how to connect to their VPN to access the provided ipmi interface from a Linux system, and I’ve decided to create a post which explains how to proceed.

Requirements

I will be explaining for ArchLinux in this short how to, but the steps should be pretty much similar on most Linux distributions. All we need to do is to install the pptpclient package, which will pull in the pppd dependency.

pacman -S pptpclient

Configuration

We will be creating a short shell script in /etc/ppp/ip-up.d:

/etc/ppp/ip-up.d/01-route.sh
#!/bin/sh
ip route add 10.167.0.0/16 dev $1

Make this script executable with chmod +x /etc/ppp/ip-up.d/01-route.sh. Then, as root, run this:

pptpsetup --create tunnel_name --server ip_addr --username your_ioflood_username --password your_ioflood_password --encrypt
  • ip_address is the IOFlood external ip which serves as an entering point into their network.
  • tunnel_name is a custom name you can give to the connection.

“`The rest of the needed information is also provided in your welcome email from IOFlood. Now that everything is set up correctly, all you need to do is to activate the connection.

Using the VPN

To activate the connection, run this:

pon tunnel_name

To terminate the connection, run this:

poff tunnel_name

Conclusion

Congratulation, you now have access to the ipmi interface of your IOFlood machine. In case you still cannot reach it, make sure you have the correct routes in your routing table. Use the ip route command to check this.

I hope this was a useful how to, and you can let me know if something is missing by posting a comment.
See you soon !

Using ipset and iptables to block full bogons

Using ipset and iptables to block full bogons

From Team Cymru:

A bogon prefix is a route that should never appear in the Internet routing table. A packet routed over the public Internet (not including over VPNs or other tunnels) should never have a source address in a bogon range. These are commonly found as the source addresses of DDoS attacks.

In this tutorial, we will be using the full bogons list of Team Cymru to block martians (private and reserved addresses defined by RFC 1918, RFC 5735 and RFC 6598) and netblocks that have been allocated to a regional internet registry (RIR) by IANA, but were not assigned to an actual ISP or other end user.

Why should I bother filtering it ?

As stated by Team Cymru, a bogon is something that is never meant to appear on internet. As such they are most often used in DDoS attacks, but can also appear for various reason (e.g: misconfigured network). Hence, it is not harmful and even recommended to filter these so that they never reach your own network and more importantly, never leave it to go on internet. So, lets get started.

Prerequisites

  • An ipset capable kernel.

Most Linux distributions nowadays includes support for ipset in their kernel, either entirely built-in or as a module. Regardless, here is how to enable it if you are building your own kernel or using a distribution such as gentoo.

[*] Networking support  --->
    Networking options  --->
    [*] Network packet filtering framework (Netfilter) --->
        <M>  IP set support --->

as well as
             Core Netfilter Configuration --->       
                <M>  set target and match support

Notice that the IP set support is listed with a —> after itself, meaning it is a submenu. This is where you will also need to go to enable the various set types. It is recommended to enable them all, as modules, so that you will not have to rebuild because you noticed too late you are missing an important one later on.

  • Raw table support in iptables and ip6tables (optional).

To be able to filter the bogons as early as possible, we will need to add the rules in the raw table of iptables, and optionally ip6tables if you chose to enable it as well. It is the first table in the entire netfilter framework, and hence ideal to filter packets before they ever get to traverse the nat or filter table. You can find support for raw table under networking support -> networking options -> Network packet filtering framework (Netfilter). In this menu, choose either ‘IP: netfilter configuration’ or ‘IPv6: netfilter configuration’ for ipv4 and ipv6 support, respectively, then enable the raw table as a module.

  • The ipset tools for userspace.

Most distributions offer the ipset tools as a package that you can simply install through your package manager. I will not explain how to build this from source, as I have never done it and wish to avoid causing my readers to make mistakes.

Using a set of scripts to maintain the IP sets

For this tutorial, I provide two scripts to create and maintain the sets in ipset:

  • The updater, which is used to periodically verify the last time when the lists were downloaded to disk, and download them again if required.
  • The installer which is used to do the actual work with ipset, creating the sets and swapping them.

It is important to realize that the bogons and full bogons lists provided by Team Cymru are far from being static. They can evolve several times a day, and it is important to periodically download a fresh copy or you could end up blocking legit traffic over time. It is also important to not overdo it so that everyone has a chance at pulling the new lists. Team Cymru recommends 4 hours for the full bogons lists so that is what we’ll be using.

Of course, keep in mind these scripts that I offer as example were designed with my own setup in mind so there will be a few things you will need to change, most notably the paths where the file(s) are stored, the sets’ names if you so wish, and the path to the bogons-install script that is referenced in bogons-update.

You can grab the bogons-update here, and the bogons-install here.

Once this is done, be sure you have appropriately placed them both where you want them, and make sure all the data you have modified is correct.

  • Notes:
    • If you didn’t modify the scripts, then they should both be placed in /usr/local/bin.
    • You will have to create the directory to store the bogons file, the scripts do not create it as needed.
      For our example, we will use /data/bogons.
mkdir -p /data/bogons

Then go ahead and execute the bogons-update script.

  • For IPv4, do the following:
/path/to/bogons-update
  • For IPv6, run this instead:
/path/to/bogons-update -6

You should see the output of the curl command in both case, and nothing more before you are returned to your terminal prompt. It is worth noting that if the script fails to install properly, it will abort. Type the following, and confirm that both sets have been created and the number of entries should match the number of lines in your bogon file(s), minus one which is the header Team Cymru adds with the last updated timestamp. Your terminal output should look like this:

[[email protected] ~]# ipset -t list
Name: bogons4
Type: hash:net
Revision: 6
Header: family inet hashsize 2048 maxelem 65536
Size in memory: 103064
References: 0
Number of entries: 3029

Name: bogons6
Type: hash:net
Revision: 6
Header: family inet6 hashsize 65536 maxelem 2000000
Size in memory: 348776
References: 0
Number of entries: 106649

Automating the process

Now that your ipset has been confirmed to work, we still need to automate the running of the bogons-update and bogons-install scripts.

  • The bogons-update should be ran by a cron job or systemd timer every 5 minutes or so.
  • The bogons-install should be ran only once at boot, before network comes up. It will be called automatically by bogons-update the rest of the time.

The bogons-update script only verifies the last modification time of the file(s) before acting on this value, either aborting because the file(s) are not at least 4 hours old, or downloading a fresh coppy if they are at least 4 hours old or more. But we still need to automate the execution of this script with some kind of timer for this to work as intended.

So since I am using systemd on my machine I will provide all the required files you will need in order to set this up properly, the only things you should need to modify being the paths to the updater and installer that systemd will use, in case you modified them, and the path to the ipset binary in bogons-install.service.

You can get the bogons-install service file here, the bogons-update service file here and the bogons-update timer here.

  • Notes:
    • It is advised that you add the RandomizedDelaySec= setting to your timer unit, especially if you intend to use the bogons listing on several hosts or routers. It will avoid your machine being overloaded with many timers running all at once, and will also avoid everyone having the timer to go off exactly every 4 hours after the file is considered stale hitting the server of Team Cymru all at the same time, all across the internet, thus reducing the constant load it is under. A randomness of 5 minutes for a timer while another one has a 10 minutes randomness might be a good starting point. Gives a chance for everyone to download the new listings. See your local manpage for systemd.timer for the explanation and usage of RandomizedDelaySec= setting. If you plan on using it on more than 5 machines, I recommend you make use of a cdn which will grab the listings once and then redistribute it to the machines it is used on, but this is beyond what this post will cover.

Once they are modified according to your needs, place them all under /etc/systemd/system/ and run:

sudo systemctl enable bogons-update.timer bogons-install.service
sudo systemctl start bogons-update.timer

From now on the timer should trigger every 5 minutes and only the bogons-update script will know what to do if the age of the file(s) is under or above 4 hours old. As for the bogons-install.service it is only meant to run once per boot, before the network is brought up, so that it may restore the set(s) from the previously downloaded listing, which prevent your machine from downloading the list(s) every time you reboot it, and only when necessary due to the timer.

Blocking with iptables / ip6tables

Now that everything is organized on the ipset side of things, we need to configure iptables and optionally ip6tables if you chose to go for both full bogons listings. Lets add these lines to your /etc/iptables/iptables.rules, or whereever your distribution stores them:

*raw
:PREROUTING ACCEPT
:OUTPUT ACCEPT
-A PREROUTING -i wan -m set --match-set bogons4 src -j DROP
-A OUTPUT -o wan -m set --match-set bogons4 dst -j DROP
COMMIT
*filter
-A FORWARD -i br0 -o wan -s 192.168.1.0/24 -m set --match-set bogons4 dst -j DROP
COMMIT

Let me explain these rules. First in the *raw table, we add a rule to block bogons coming from the outside world, so that they will never get through. Still in the *raw table, the second rule prevent bogons from getting out of the router itself. We commit this table and move onto the *filter table, which contains a rule to prevent bogons from leaving your LAN, so from machines behind the router itself. We also commit this table.

  • Notes:
    • Be careful that the –match-set option of the set module matches the name of the actual IP set you created earlier, or iptables will not run properly!
    • Make sure the interface names are the correct ones, or you could bring the entire network down.
    • Make sure you are using the correct LAN address range, this is provided as example only.
    • Be mindful of the fact the ip set needs to exist before you run iptables-restore at boot, or it will fail to run because of a missing ip set.

Ready? Good. Now try a iptables-restore < /etc/iptables/iptables.rules, only if you have physical access to the machine and can rollback independently of network connectivity. Otherwise I recommend iptables-failsafe or iptables-apply, which I briefly mentioned in my previous post, using iptables the right way. If you've really messed up, they will allow you to recover, in case you have no physical access to the machine you are working on.

As for IPv6, this is basically the same, with at least four obvious differences:

  • It is done through /etc/iptables/ip6tables.rules instead, or whereever your distribution stores the IPv6 firewall configuration.
  • The set name is not the same, in my case bogons6 instead of bogons4.
  • The interface names for IPv6 might be different from your IPv4 ones. Be sure to verify this before applying the rules.
  • There should be no need for a rule to accept a LAN in IPv6, since you most likely have a global prefix.

Once again, be careful with the parameters, and remember to use iptables-apply or iptables-failsafe if you are on a remote host.

Using iptables the right way

I often come across tutorials on the Internet which attempt to show people how to use iptables. The intention behind these tutorials may be good, the problem is that sadly most of them are written by people believing to know all there is to know on this particular subject, when in reality they give advices that ultimately misguide more people. In this short tutorial I am going to explain what is the right way to use iptables, in the hope that someday, someone will find this post and use it rather than end up misguided by another poorly thought tutorial.

1. Stop using iptables -L

Iptables -L is a liar. When you want to examine your rules to find what may be wrong, don’t use it.

See for yourself, iptables -L VS. iptables-save.

Notice how iptables -L report the rules for the multiport up to 26200 and the UDP port to be from and to anywhere ?

Iptables -L is a pretty output that is there since a very long time, but should never be used for these reasons:

  • It lies about incoming / outgoing interfaces.
  • It takes time to display on some machine because it display the pretty name for a port if it is known rather than the port number.
  • It also try to resolve all the FQDN associated with the IPs, if any.

Even the classic iptables -L -v option is confusing, compared to the simple and clear output of iptables-save.

2. Use iptables-save and iptables-restore

One of the worst things you can do is trying to get help for your nice firewall you’ve made in bash scripting, posting on whatever pastebin you prefer and asking people for help debugging why some things work and some don’t, or why some work half the time. The reasons why not to do this can vary, but here is a couple:

  • You ask for help debugging a shell script, not a firewall.
  • The iptables command is not atomic.

Let me expand on that last statement. If you have a script such as this one for example:

#!/bin/bash
/usr/bin/iptables -P INPUT DROP
/usr/bin/iptables -P FORWARD DROP
/usr/bin/iptables -A INPUT -i lo -j ACCEPT
...

That script is going to be executed, line by line, in a non-atomic way. There will be a short period where your firewall will be imcomplete, each time a rule is applied up until it is all done. During that time the wrong packet could get into your network, or the right one could get stuck and not go out. Editing rules with the iptables command is also resource-intensive. Every time it makes a change, it copies your current rules, makes a one-line modification to the whole, and reloads that whole. The more your firewall becomes complex, the more time it will take. You could get locked out of your own remote server for this exact reason.

This is why using iptables-save syntax is better for writing your rule set file, and why you should use iptables-restore to load/reload it. Iptables-restore is atomic, contrary to a shell script. It loads your entire rule set, and replace the current set in a single shot with it, thus not leaving the possibility of wrong packets coming in or good packets not leaving the system while it loads the rules.

  • Notes:
    • if you want, you can use either iptables-apply or iptables-failsafe, which can be found here to reload your rule set. They both have a timeout mechanism to avoid locking you out if you have made a mistake in your rules by rolling back to the previous working set if you did not answer to the prompt before the timeout.
    • it is worth noting that iptables-save, by default, output the current ruleset to stdout. Hence if you want to create the file from which iptables-restore will load your rules from, you should use it like this: iptables-save > iptables.rules.

3. DON’T use iptables -F

I often read this answer to a simple question such as “How do I delete all of my rules ?” “Use iptables -F, works for me.”

Plain wrong. Imagine you have a machine where the input and forward policy are both set to drop, and you realize you made a big mistake in your 10 last rules. So because you feel lazy about deleting them, you prefer starting over, and run iptables -F. You have just gotten yourself two big problems:

  • That machine is a remote one.
  • You flushed all the chains, but did not restore their policy to the default.

In clear, your machine does not accept any packets, nor does it forward any like it was previously doing because it was acting as the outside link for some containers you have running up on there. You deleted the rules, without changing the policy.

So no, don’t use iptables -F, ever.

I had this with a frontend on centos, firewalld. The machine was a server from Scaleway, running centos 6. At the time I didn’t know a thing about iptables, so I went with their frontend. Set the policy to deny for input and so on and… Got myself both locked out and without a working machine, because the disk was a network block device (nbd). Flushing was automatically done by firewalld when I reloaded it, which destroyed the active connection to keep the OS running.
That’s when I decided to learn iptables and never try to use any frontend again.

4. Don’t set OUTPUT policy to DROP unless you know what you are doing

The header pretty much says it all. Setting the OUTPUT policy to drop traffic is a bad idea, and you could end up in a world of trouble if you try it.

  • You would need to add all the IPs of the repositories mirrors of your distribution, relying on domain names in iptables is unsupported and a very bad practice.
  • What if these IPs change ?
  • How do you plan on getting DNS to resolve in the first place ?

Needless to say these are only a few of the problems you could encounter if you persist in your idea to block the OUTPUT chain. There are probably way more problems that I have not thought of myself, but these should be enough to make you get the idea that doing this unless you know what you’re doing is a very bad idea. And even if you know what you are doing, is my personal opinion. Just don’t connect the machine to the internet.

Conclusion

These are the major points I found after hanging around in the #Netfilter irc channel on freenode, and that I try to change. I got inspired by this gist which is in itself a ton of very good resources on various topics including netfilter, iptables, iproute. I recommend you take a look at the resources it offers and consider them carefully.

I hope this post was helpful to you in any case.

Till next time.