christiansaga.de
Deactivate bluetooth on startup
For some reason my laptop thinks bluetooth is the technology to go and activates it on startup of Linux Mint/Ubuntu.
Searching the internet you will find a lot of answers for this problem, that quite brutally kill the bluetooth service or block devices. All not very elegant.
So what is the solution
Most laptops use tlp for power management. If you don’t you should check it out. With that it is quite easy. Uncomment and edit in /etc/tlp.conf:
DEVICES_TO_DISABLE_ON_STARTUP="bluetooth"
You can also edit tlp to remember the last state when powering down. But for me deactivating bluetooth was all enough.
Full article view...Open Source Wifi Speaker based on IKEA Symfonisk - Part II: Software / OS
After finishing the hardware in part I of this little tutorial, we need to set up the operating system to run our speakers. Of course it should be open source software.
There are multiple solutions out there to do multiroom streaming, however the slimserver or squeezserver eco system looks the easiest for me right now. It seems to support everything I am looking for:
- Central streaming server with lightweight clients for playback
- Multiroom and sync features
- Extensible with plugins
- Internet radio
- Streaming services, like Spotify or Tidal
- Open source
Most of the features are actually done in the server and are pretty easy to set up. I am running the server in a docker on my NAS, where it also has access to my local music library.
This part however is focusing on the player and have that as slim and robust as possible. I checked a setup on HifiberryOS, Rasbian, etc.. However was convinced to start with piCorePlayer as a clever solution based on tinycore Linux.
The advantage of piCorePlayer / tinycore Linux is, that it runs completely in RAM. Hence you can unplug the speaker without too much worries about the SD card in the raspi. Also the system is very small. I use now only a 1GB SD card, which is not even used 10% (Sadly you can not buy such small cards anymore ;-)). I could have used piCore (the barebone tinycore Linux for raspis), however then I would need to do a lot of work that has already been done by the piCorePlayer team.
So now we only need to extend the piCorePlayer with specialties of our setup:
- Richard Taylor’s LADSPA plugins for Active Loudspeakers to create the crossover and mono downmix of the speaker
- Upfront Wifi configuration to avoid using a network cable
Step 1: Prepare Richard Taylor’s LADSPA plugins
As tinycore is loading everything into RAM on boot, the system is build out of different extensions, which are pretty much zips with the files for that extension. All loaded extensions get merged into one filesystem in RAM on which the system is running. Most things are already prepackaged and can be downloaded as an extension, however within the repositories, I could not find the LADSPA plugins of Richard as a prepared extension. However creating such extension is not too complicated.
Download Plugins (Author published source code under GPL-v3.0)
If you want to compile the plugins yourself, it makes it easier to use a piCore setup on the Raspberry.
Step 2: Get piCorePlayer and install it to the SD card of your speaker
Get the latest piCorePlayer here and write it to your SD card.
Next you need to add your wifi information to have it log in automatically. For that, mount the just created SD card. It should have two partitions, a PCP_BOOT (probably mmcblk0p1) and PCP_ROOT (probably mmcblk0p2) partition. Create a wpa_supplicant.conf in the root of the PCP_BOOT partition and fill in the relevant fields (country, SSID, password). There is also a sample file which you can just copy, rename and edit. This is also explained here.
Next you need to resize the root partition to use the free space on the SD card. On my systems (Linux Mint), I can easily to this with gparted (right click on partition to resize). Otherwise you can do it in the piCorePlayer GUI after booting. However to do it right away is easier, as we want to copy Richard Taylors plugins as well. The plugins from Step 1, will now be added to the piCorePlayer by copying them to the PCP_ROOT storage
cp rt-plugins-0.0.6.tcz /mnt/mmcblk0p2/tce/optional/
cp rt-plugins-0.0.6.tcz.md5.txt /mnt/mmcblk0p2/tce/optional/
echo "rt-plugins-0.0.6.tcz" >> /mnt/mmcblk0p2/tce/onboot.lst
The last line adds the extension to the boot list, so they will be loaded and available in the file system after boot. Now the piCorePlayer is ready to boot and to run the speaker.
Step 3: Configure piCorePlayer
You need to do some basic configuration of piCorePlayer to run it with the speaker. For that use a browser to access the piCorePlayer web interface under the IP of our speaker.
First activate the advanced mode on the tab of the bottom of the Main Page
. During the configuration you will have a few reboots. I would do each one of them, to be thorough.
If you haven’t extended the filesystem yet, you need to do it now (Main Page
-> Resize FS
) and probably still need to copy the plugin
After that update the player under Main Page
-> Full Update
. Update the extension under Main Page
-> Update
. Check for possible hotfixes under Main Page
-> HotFix
.
Then head to the Squeezelite Settings
page and set Amp2 as audio output device. After saving and possible reboot, use the Card control
button and deactivate the raspis internal audio device down at the bottom. Only after that squeezelite will start.
Back on the Squeezelite Settings
page you have to enter the name of this player and the IP of the LMS server.
Finally on the Tweaks
page enable the ALSA 10 band Equalizer
. Probably another reboot is helpful and our basic configuration is complete.
The speaker now will be working, however it will sound weird. You still need to activate the crossover and probably use the equalizer.
Step 4: Activate crossover, mono downmix and equalizer settings
For the crossover you need to connect to the player console via SSH. After login you need to edit the /etc/asound.conf
file. Vi is installed in the system.
You will find a pre-generated configuration from piCorePlayer. Edit this to match the following:
# default - Generated by piCorePlayer
pcm.!default {
type plug
slave.pcm "plugequal"
}
ctl.!default {
type hw
card 0
}
pcm.pcpinput {
type plug
slave.pcm "hw:1,0"
}
#---ALSA EQ Below--------
ctl.equal {
type equal;
controls "/home/tc/.alsaequal.bin"
library "/usr/local/lib/ladspa/caps.so"
}
pcm.plugequal {
type equal;
slave.pcm "plug:crossover";
controls "/home/tc/.alsaequal.bin"
library "/usr/local/lib/ladspa/caps.so"
}
pcm.equal {
type plug;
slave.pcm plugequal;
}
#--- Speaker crossover below-----
pcm.crossover {
type ladspa
slave.pcm "amp2"
path "/usr/local/lib/ladspa"
channels 6
plugins
{
0 {
label RTlr4lowpass # lowpass left output to channel 2
policy none
input.bindings.0 "Input"
output.bindings.2 "Output"
input { controls [ 3000 ] }
}
1 {
label RTlr4lowpass # lowpass right output to channel 3
policy none
input.bindings.1 "Input"
output.bindings.3 "Output"
input { controls [ 3000 ] }
}
2 {
label RTlr4hipass # highpass left output to channel 4
policy none
input.bindings.0 "Input"
output.bindings.4 "Output"
input { controls [ 3000 ] }
}
3 {
label RTlr4hipass # highpass right output to channel 5
policy none
input.bindings.1 "Input"
output.bindings.5 "Output"
input { controls [ 3000 ] }
}
}
}
# --- Output device and mono downmix below -----
pcm.amp2 {
type plug
slave {
pcm "t-table"
channels 6
rate "unchanged"
}
}
pcm.t-table {
type route
slave {
pcm "hw:0,0"
channels 2
}
ttable {
2.0 0.5 # Mix both stereo low (main) to mono
3.0 0.5
4.1 0.5 # Mix both stereo high (tweeter) to mono
5.1 0.5
}
}
pcm.plughw.slave.rate = "unchanged";
So what is this doing? Basically it tells ALSA to use a chain of filters for the default device and then send it to the hardware:
- pcm.!default: redirect the default device to plugequal
- pcm.plugequal/pcm.equal: this is the ALSA equalizer and first step in our chain. We will configure it later. We can use this to influence the sound of our speaker
- pcm.crossover: here we use Richard Taylor’s plugins to split our stereo signal in 4 channels. The highpass ones above 3000Hz and the lowpass below that
- pcm.amp2/pcm.t-table: this is the hardware device for playback (hw:0,0), the table does the mono downmix for high- and lowpass channels
After this, we get 2 mono signals, one above 3000Hz, one below, each left and right of the stereo signal downmixed. These are then put to the tweeter and mid-range speaker. Having the equalizer in the chain, we additionally have the opportunity to tune the sound of our speaker. This will impact all sound being send to the speaker.
Save the file and get back to the command line. Next you need to set the equalizer
Step 5: Setting the equalizer
The ALSA equalizer is set via the command line.
export TERM=xterm
sudo alsamixer -D equal
The first line will prevent the error Error opening terminal: xterm-256color
.
You will get a nice looking console equalizer, in which you can tune the sound of the speaker (left/right, up/down arrow keys). Now this is complicated, as sound is something very personal. Hence my settings might not be the right ones for you. I advise to come back later when you play some music and tune this to your taste (I am happy to hear of any settings). Changes to the equalizer are audible right away.
Here are my settings (observe the numbers on the bottom)
As you can see, I did not change too much. As far as I know, within an equalizer the less is better.
IMPORTANT step: We now edited some files on the player, to make that survive the next reboot, we need to backup our config.
Go back to the piCorePlayer web interface to the Main Page
and click the Backup
button. Now the settings will survive possible reboots.
Step 5: Add controls to the front buttons
To make our buttons on the front work during playback, we need another extension.
Head to Main Page
-> Extensions
and click on the Available
tab.
Under the Available extensions
select pcp-sbpd.tcz
and click load. This installs all necessary plugins to monitor the raspi pins.
Next - via SSH - create the file /home/tc/sbpd-script.sh
with the following content (please adjust the -A
and -P
to your LMS server instance and port. You can try auto discovery by removing both flags as well):
#!/bin/sh
# start pigpiod daemon
sudo pigpiod -t 0
# give the daemon a moment to start up before issuing the sbpd command
sleep 1
# load uinput module, then set the permission to group writable, so you don't need to run sbpd with root
sudo modprobe uinput
sudo chmod g+w /dev/uinput
# issue the sbpd command
sbpd -s -d -A 192.168.2.9 -P 9000 b,11,PLAY,2,0 b,10,VOL-,2,0 b,9,VOL+,2,0
After saving the file, make it executable chmod +x /home/tc/sbpd-script.sh
This script basically starts the GPIO monitoring. The last line defines the GPIOs to watch. here it is 9, 10 and 11 (each right after the b,
).
If you remember the table out of the hardware wiring for the buttons this is SW_Mute, SW_Down, SW_Up. If you changed the pins used on the raspi, then you will need to adapt it here.
If you are interested in the whole syntax given there or to add a long press event, use sbpd --help
on the command line to get the details.
Now the script needs to be launched at startup. In the web interface go to Tweaks
and add /home/tc/sbpd-script.sh
as User command #1
and click Save.
Now finally go back to the Main Page
and hit Backup
one last time.
One last reboot and your speaker should be ready to rock…
Addendum: Changing crossover frequency and equalizer
The selected crossover frequency and equalizer settings are based on my personal taste. It might be that there are better options. Overall, this is not a high audiophile level of speaker, hence do not expect too much. If you want to fiddle with that, the following commands might help a bit.
The crossover frequency is set in /etc/asound.conf
. I guess you can spot the 3000. ;-). The frequencies for each filter should match, otherwise it sounds quite weird, but feel free to play around with it. Just beware, that forcing a speaker into frequencies it was not build for is stressful for it and might even break it. Hence not change your tweeter into a sub-woofer and vice versa.
Calling the equalizer:
export TERM=xterm
sudo alsamixer -D equal
Relaod ALSA after changes (reboot is always better):
alsactl kill rescan
Reduce the volume directly on the speaker to 70% (otherwise the next command is very loud):
amixer sset Digital 70%
Produce a pink noise speaker test sound (for frequency measurements):
speaker-test -c2 -tpink
Open Source Wifi Speaker based on IKEA Symfonisk - Part I: Hardware
For years I had Sonos speakers back at home to play my music and stream internet radio, spotify, etc. With the announcement of Sonos to not support older devices, it became clear, that again perfectly fine hardware will become useless, just because of software decisions. For long I was annoyed with samba v1 on the speakers, which couldn’t be updated due to limited space inside the flash memory. For adding Alexa and other new features apparently there was always space.
Hence I thought to build a speaker that is build with standard components (can be repaired if broken) and uses open source software. However if you look into the internet, there is tons of howtos on how to build your own speaker and speaker casing, even some awesome builds into old radios etc. I wanted to have the small devices with good sound like I had with my Sonos (not audiophile high def super sound). The importance for me was design, to have a device that fits nicely into the living room (high WAF).
So basically I need to rebuild a Sonos speaker. However these are quite expensive to just tinker with. But lately IKEA got the Symfonisk speakers for 99€. Hence I got one and rebuild it into a complete open source Wifi enabled speaker.
This is probably not the cheapest way of getting a wifi speaker, but the result was worth it for me. If you sum up the costs you are in the range of a small Sonos speaker, however with the huge advantage of being free from a single vendor and having a maintainable device.
What did I use (For some parts there are other options, e.g. the amp or power supply, but “This is my design”). The links below are just for informational purposes, feel free to use your own supplier.
- IKEA Symfonisk
- Mean Well HDR-60-15 power supply (DIN-rail) 15 V/DC 4 A 60 W
- Raspi Zero WH
- Hifiberry Amp2
- Micro SD card (8GB is more than enough)
- SD card slot extension
- Micro USB port
- FPC adapter with 10pins, 0,5mm
- Wires for power supply >1mm diameter
- Spacers for the raspberry board (M2, so they fit to the Hifiberry ones, I used 1 and 2 cm ones)
- Small cables
I measured the setup in terms of power consumption. It uses roughly 2,5W in standby. Compared with Sonos, that is quite good.
You personally need some really basic soldering expertise and good common sense (“Don’t eat from the yellow snow”, “Don’t touch live wires”). I glued most of the parts into the chassis, however the raspi can be unscrewed.
DISCLAIMER: I am not a technician or expert on this, and learned a lot by doing this project. If you rebuild this, you do it on your own risk.
If you found any errors, I am happy to hear of them. If you try this guide, start off by building it outside of the case to verify correct wiring and function of each part.
Step 1: Disassembly
First you need to completely disassemble the Symfonisk speaker. A good guide you will find here: Hacking the Sonos Ikea Symfonisk Into a High Quality Speaker Amp That guide is basically doing the reverse: Using the Sonos parts for a regular speaker. Also a good project and probably good to sell off the Sonos components. However goal of this tutorial is to get independent of a single supplier. After the disassembly you pretty much will have a lot of Sonos spare parts and en empty housing for the speaker. For the build we will only use/reuse the following, so make sure you do not damage them in disassembly:
- Housing/Chassis
- Front part with both drivers
- Wiring towards the speakers
- Power plug and cable on the back (not the wiring inside)
- Buttons on front with electronics and the flex cable towards mainboard
- Screws, etc.
Step 2: Power supply
I used a DIN-rail power supply, as they are very compact. The speakers inside the Symfonisk are a 4Ohm mid range and a 8Ohm Tweeter. Hifiberry recommends a 18V 60W ~3A power supply, if you connect the Amp2 to 4-8Ohm speakers.
Finding a 18V power supply in compact form was quite a hassle. The Mean Well HDR-60-15 actually is in the 15V category, but can be tuned to deliver up to 18V. Mean Well even has open housing power supplies, which are a few € cheaper, however they are not as compact as this one. The power supply has a little screw next to the outputs to increase voltage output. It is quite clearly marked. You can turn it fully for the 18V.
Alternatively you can use an external power supply (Like old notebook supply), however then you will need new plug and wiring at the backside.
Step 3: Add the Raspberry Pi and Hifiberry Amp2
The regular raspi zero W does not have the pin header soldered on, that is why I went for the zero WH. If you happen to have a W only, the only thing to do is to solder the header on and test it (German).
The Amp2 is just placed on top of the raspi:
On the picture you can see I used a regular plug to power the Amp2 with raspi, mainly for testing.
Step 4: Wiring the power supply
Make sure you use proper cables for the power supply, I used >1mm diameter full copper and used the original plug from the backside of the Symfonisk and soldered new cables on towards the power supply. +/- does not matter here, as it is AC.
The Amp2 powers the raspi, so you only need to connect the Amp2 to the power supply. Here it is important to correctly wire +/- with the Amp2 as the output is DC. The power supply has two outputs, it does not matter which to use.
Step 5: Extend Raspberry Pi ports
As the SD cards in raspis easily get damaged, I decided I want to extend the sd card slot and make it accessible from the outside when the housing is closed. Also I want to extend one USB port to the back of the housing to possibly add some hdd or to connect a usb network adapter (should I not use wifi anymore). You could build in the network adapter and glue it on the old port, however I felt this to be more flexible. This is pretty straight forward, as you just connect the USB port plug and the SD-card extender on the raspi.
Step 6: Connect the Sonos buttons
The Symfonisk has pretty little buttons and LEDs on the front and it would be nice to be able to still use them. The parts you pulled out in the disassembly (+ the cable):
The rubber parts are not needed for now, but we need to check the electronics. You have 4 LEDs and 3 buttons on it, all wired to a FPC 10 pin, 0,5mm connector. With the adapter linked above you can actually use the original cable and solder on some cables to connect it to the raspi.
On the adapter you can see the lines of the cable in 1-10. With a multimeter you can figure out what it connects to on the button electronics board (or use my table below). We also need to map them to raspi pins to use them later on. The Amp2 uses HW pins 3, 5, 7, 12, 35, 38 and 40. Also 27 and 28 are off limits for the eeprom.
Please be careful: HW pins are not the GPIO numbers. See chart below, HW pins in the middle. Nr. 1 ist the squared one on the raspi and Amp2 board.
To pull everything together, I did the following mapping for the buttons and LEDs for the adapter and raspi connection:
CABLE/ADAPTER | FUNCTION | HW-PIN | GPIO | EXPLANATION |
---|---|---|---|---|
1 | SW_Mute | 23 | 11 | play/pause button |
2 | SW_Mute | 23 | 11 | just use one |
3 | Green LED | 16 | 23 | |
4 | Red LED | 18 | 24 | |
5 | GND | 20 | GND | just use one |
6 | Amber LED | 22 | 25 | |
7 | GND | 20 | GND | just use one |
8 | White LED | 15 | 22 | |
9 | SW_UP | 21 | 9 | volume up button |
10 | SW_DW | 19 | 10 | volume down button |
When you solder everything together you get something like this.
You can now connect the Button panel with the original flexcable to this.
Step 7: Wire the speakers
Polarity is key in here, hence make sure you connect it correctly. The speakers and the Amp2 are marked with +/-.
I used the Sonos wiring, but you can of course create new ones. On the Sonos wiring, the big plugs are the + (this is also marked on the speakers) Yes, you see correctly, I am using one stereo channel for of the Amp2 for the mid range and one channel for the tweeter speaker. No hardware crossover, we will need to do that later on with ALSA.
Now everything is ready. If you want to do some tests you should be able to. However be careful with live wires!
Step 8: Fit everything into the chassis
Now everything needs to go into the chassis. I mainly glued everything into it, but made sure, I can unscrew the raspi for possible maintenance. To hold it in place I used spacers which are then glued to the chassis.
You can see the original speaker wiring in the foam, the red and white wires go to the power supply. In the back you see the red white again, which is the wiring from the power supply to the plug on the back of the chassis. The SD-card extension is leaving the raspi on the left, the USB port is the thick cable in the front.
The spacers are stepped, left 1cm, on the right 2cm. The chassis is not even in the back, hence you need this to fit. Only these parts are glued and the raspi is screwed on.
After adding the power supply you get something like this
The bottom of the picture is also the bottom of the speaker. Hence if you close the chassis, the big speaker will be on the right.
In the middle you can see the power plug and the extended port glued to the back. From the back you can then access one USB port and the SD-card of the raspi.
If you connect the buttons and soldered adapter from step 6 you get the following
Hardware should be done now and we close up everything. If you have, you can glue a bit of padding to the cables and things inside. It is a speaker, so everything will be vibrating.
Now you have a Wifi enabled speaker build with freely available parts, maintainable and good looking. Next step is to make it smart, which is basically choosing the right software. See next -> Open Source Wifi Speaker based on IKEA Symfonisk – Part II: Software / OS
Full article view...Docker and IPv6 with dynamic prefix
Getting IPv6 on your private connection should be quite easy by today. Getting services you might have hosted before via dynDNS towards IPv6 however is a bit more work. Mainly due to the fascinating concept of dynamic prefixes.
Most private dial up connections with dual-stack use dynamic prefixes. This gives you three options:
- Change provider or ask yours nicely for a fixed prefix (nice, but not possible for everyone)
- Use NAT and IPv6 ULAs with tools like these: https://github.com/robbertkl/docker-ipv6nat (working, but pretty much the old IPv4 world)
- What we want to do: or improvise, adapt, overcome.
The article is about how to work with a dynamic prefix and not use something like DHCPv6 and basically results in a few problems with each prefix change triggered via SLAAC.
- You need IPv6 dynDNS to update your AAAA records
- Docker will need updates for the network configurations
- Neighbor solicitation will need to be updated
- ip6tables becomes more complicated
So what is the solution
BEWARE: This is not about configuring your router. I assume that it is not blocking IPv6 traffic IN- or OUTBOUND.
First you need to get your docker IPv6 setup working with the current prefix unchanged. I used the official Docker documentation here https://docs.docker.com/v17.09/engine/userguide/networking/default_network/ipv6/#how-ipv6-works-on-docker
This is very straightforward. But some comments to this:
- Docker itself advises against using the default network and advises to setup and use your own network. This is what I did, however I would advise to also setup the default network properly, as containers started without any network will come up in that
- I got a /56 prefix from my provider, so best for me was a /80 subnet for docker. This would allow each docker container to code their MAC into the IPv6
- If you are not sure, how to cut your networks, use a IPv6 calculator from the internet, it helps to visualize. I also decided to number my networks manually to make them more readable for me (hence using the MAC only for random containers)
- While setting this up, make sure your ip6tables is in ACCEPT mode on your host machine, especially in the FORWARD chain, as this is the relevant chain for DOCKER. The additional docker chains you might know from IPv4 are not existent in IPv6
I will use the following format to reference the IPv6 (letters the prefix, numbers my subnet numbering): aaaa:bbbb:cccc:dddd:1::/80
Yes, my network is just 1
, so containers then get ::1::1
, ::1::2
etc. Keep it simple here.
Next, add the ndppd daemon to automate the neighbor solicitation on the host machine. It is available as a package in Debian. You will need this, as otherwise more scripting is needed. I used the following config (/etc/ndppd.conf):
# route-ttl (NEW)
# This tells 'ndppd' how often to reload the route file /proc/net/ipv6_route.
# Default value is '30000' (30 seconds).
route-ttl 30000
# proxy
# This sets up a listener, that will listen for any Neighbor Solicitation
# messages, and respond to them according to a set of rules (see below).
# is required. You may have several 'proxy' sections.
proxy eth0 {
# router <yes|no|true|false>
# This option turns on or off the router flag for Neighbor Advertisement
# messages. Default value is 'true'.
router no
# timeout
# Controls how long to wait for a Neighbor Advertisment message before
# invalidating the entry, in milliseconds. Default value is '500'.
timeout 500
# ttl
# Controls how long a valid or invalid entry remains in the cache, in
# milliseconds. Default value is '30000' (30 seconds).
ttl 30000
# rule [/]
# This is a rule that the target address is to match against. If no netmask
# is provided, /128 is assumed. You may have several rule sections, and the
# addresses may or may not overlap.
rule aaaa:bbbb:cccc:dddd:1::/80 {
# Only one of 'static', 'auto' and 'interface' may be specified. Please
# read 'ndppd.conf' manpage for details about the methods below.
# 'auto' should work in most cases.
# static (NEW)
# 'ndppd' will immediately answer any Neighbor Solicitation Messages
# (if they match the IP rule).
# iface
# 'ndppd' will forward the Neighbor Solicitation Message through the
# specified interface - and only respond if a matching Neighbor
# Advertisement Message is received.
# auto (NEW)
# Same as above, but instead of manually specifying the outgoing
# interface, 'ndppd' will check for a matching route in /proc/net/ipv6_route.
auto
# Note that before version 0.2.2 of 'ndppd', if you didn't choose a
# method, it defaulted to 'static'. For compatibility reasons we choose
# to keep this behavior - for now (it may be removed in a future version).
}
}
eth0 represents the host interface in which requests will show up. Hence this is the interface towards your router.
If your docker container has the global routeable IPv6 aaaa:bbbb:cccc:dddd:1::1
and a request from the outside is delivered to your router (identified by prefix), your router will ask every connected node who has this IPv6. However the container is not connected to the router but to the docker host. Hence the docker host needs to answer to the router and then forward it to the docker container. For this the NDP proxying is needed to listen on eth0 on the host machine.
rule is the docker network you want to activate in your docker host, so it can answer to the router. You can have multiple rule sections for multiple docker networks. With the ndppd deamon you can skip the manual adding as described in the documentation.
Info: If you use ip -6 neigh
to chech if it was registered, know that the daemon will only add it when traffic is routed. Hence trigger it with something like a ping.
At this point you should be able to have your docker containers on IPv6 and ping6 the internet.
Next, to prepare for prefix changes, we need a script that updates several things. Sadly I could not find a hook that would allow to trigger right when the prefix changes. the dhcpclient apparently has a hook, but as we are not using DHCP… Hence the script is triggered by cron every 5 minutes and checks for a prefix change. Below find an example, however you will need to adapt it to your needs, especially networks after the prefix, the docker compose and container information and dyndns setup.
update-prefix.sh
#!/bin/bash
# Update script to adapt docker networking to changed IPv6 prefix
export LC_ALL=C
DYN_USER="<your dyndns user>"
DYN_PASS="<your dyndns pass>"
# Grep current configured prefix from docker settings
PREFIX_OLD=$(grep -o -P '(?<=fixed-cidr-v6": ").*(?=:0::)' /etc/docker/daemon.json)
# Get latest prefix from ip, latest = highest ttl, hence on top
PREFIX_NEW=$(ip -6 addr show eth0 | grep inet6 | grep -v 'inet6 f[de]' | awk '{print $2}' | cut -f 1-4 -d : | head -n 1)
# If prefix changed, update docker settings and restart docker service
if [ $PREFIX_OLD != $PREFIX_NEW ]; then
echo "Prefix needs update"
echo " Stopping Docker container..."
/usr/local/bin/docker-compose -f docker-compose.yml down
/usr/bin/docker network rm docker-network
echo " Changing prefix from ${PREFIX_OLD} to ${PREFIX_NEW}..."
sed -i 's'"@$PREFIX_OLD"'@'"$PREFIX_NEW"'@g' /etc/docker/daemon.json
sed -i 's'"@$PREFIX_OLD"'@'"$PREFIX_NEW"'@g' /etc/ndppd.conf
echo " Restarting Docker..."
/etc/init.d/docker restart
/usr/bin/docker network create \
--driver=bridge \
--subnet=192.168.1.0/24 \
--gateway=192.168.1.1 \
--ipv6 \
--subnet="${PREFIX_NEW}:1::/80" \
docker-network
/usr/local/bin/docker-compose -f docker-compose.yml up -d
echo " Restarting ndppd..."
/etc/init.d/ndppd restart
echo " Updating DNS..."
IP_NEW=$(/usr/bin/docker inspect -f '{{range .NetworkSettings.Networks}}{{.GlobalIPv6Address}}{{end}}' nginx)
curl -4 -s -u $DYN_USER:$DYN_PASS "<dyndns update url>"
DYN_USER=""
DYN_PASS=""
fi
exit 0
The script basically does the following:
- extracts the prefix stored in the
daemon.conf
of docker for the default network and the newest prefix of eth0, here betweenfixed-cidr-v6":
and:0::
in the config file. I used0
for my default docker network (henceaaaa:bbbb:cccc:dddd:0::/80
) and1
for my custom network. - compares both and only acts if there is a change
- updates the new prefix in
/etc/docker/daemon.conf
and/etc/ndppd.conf
- restarts docker and ndppd for changes to take effect
- extracts new IPv6 from container named
nginx
and updates dyndns with this IPv6
Lastly adapt ip6tables to changing IPv6 prefix is not easy, as it seems, that the function is undocumented.
This is only needed, if you actually DROP all forwarding rules in your ip6tables. Hence would close off all docker containers from the internet, other than exceptions. I would recommend this, as in IPv4 Docker the standard configuration would only open exposed ports to the internet. With IPv6 you have to take care of the filtering yourself. And remember -with this config- all containers have internet routable IPv6s.
To expose a Docker container (e.g. here aaaa:bbbb:cccc:dddd:1::1
) with port 80 to the internet, you normally would add following exception to your host forwarding.
ip6tables -I FORWARD -p tcp -m tcp -d aaaa:bbbb:cccc:dddd:1::1 --dport 80 -j ACCEPT
However as the prefix aaaa:bbbb:cccc:dddd
could change you need something different:
ip6tables -I FORWARD -p tcp -m tcp -d ::1:0:0:1/::ffff:ffff:ffff:ffff --dport 80 -j ACCEPT
As you can see, the prefix is gone and you are able to target a specific container. Now you only need to allow inter-container traffic, this should be handled by the following.
ip6tables -I FORWARD -s ::1:0:0:1/::ffff:ffff:ffff:ffff -j ACCEPT
You could add these rules in the end of the script. I fixed the container IPv6 ends in the docker-compose, hence I can leave them static in the firewall.
Addendum
I would recommend to test each of this part by part, e.g. make sure, the dyndns works before putting the script together, etc. It makes debugging and changes way easier. Thanks to input of following sources:
- https://docs.docker.com/v17.09/engine/userguide/networking/default_network/ipv6/#how-ipv6-works-on-docker
- https://github.com/DanielAdolfsson/ndppd
- http://blog.dupondje.be/?p=17
- https://www.puzzle.ch/de/blog/articles/2017/06/13/docker-container-mit-ipv6-anbinden
Protect docker from internet while allowing LAN with iptables
So some of you might have noticed, that docker is setting up its own rules in iptables. However these rules reside mostly in the FORWARD chains and not in the INPUT. Most users, that setup their small server or laptop use INPUT rules to secure it. This is allright, as normally a server and a laptop are standalone and no routers. However with installing docker it becomes a router for the containers, hence docker uses the FORWARD chains. Knowing iptables means, that traffic gets sorted after PREROUTING into INPUT or FORWARD. Hence the INPUT rules are not affecting the FORWARD chain.
Docker manages the FORWARD chain (and even the nat table) itself to control access to the containers. Basically it setups NAT and the FORWARDING to the containers as soon as you use the --port
command or docker-compose port:
setting.
In this docker does not differ between traffic from the internet or a local LAN. Everything that arrives at the FORWARD chain is used.
ISSUE: In the scenario of of having a laptop or small home server, which is usually connected via local LAN to the internet, this could lead to opening services that should only run in LAN, to the internet. Only an additional firewall outside could prevent this.
So what is the solution
To get back to a state where we can control opening services to the internet, you need four iptables rules. We will use the DOCKER-USER chain, which should be empty and is made by docker for user written rules within the docker setup. Therefore they will not be touched by docker.
Before starting you should make a list of your local networks, including the docker network (even the default network).
First stop docker from communicating.
iptables -A DOCKER-USER -j DROP
This is adding a rule, that is just stopping any docker communication. After this all containers will be offline for the internet or locally.
Next allow local networks to communicate.
iptables -I DOCKER-USER -s <docker-network> -j RETURN
iptables -I DOCKER-USER -s <LAN> -j RETURN
The first rule allows the docker containers to communicate with each other, hence you need to add your docker network her or have multiple rules if you have multiple networks (e.g., 172.18.0.0/24). The second rule allows the LAN to communicate again, hence it should be something along 192.168.1.0/24. After this your docker services will be available from the local network, however not from the internet (IP spoofing would work, but you can only handle that with different interfaces). If you do not want to publish services to the internet, you can stop here.
Finally allow outside communication to reach containers
iptables -I DOCKER-USER -p tcp --dport <target-port> -j RETURN
Put here the target port of the service on the docker container to allow traffic. IMPORTANT: This happens after nat, so if you have docker do some portmapping, e.g., 8080 -> 80 you need to put the container port 80 here. With this you can have your docker services accessible on the LAN and decide which to publish to the internet.
Notes:
We use -I
to add these rules before the DROP rule that was added first.
RETURN
is used as the chain DOCKER-USER is in front of all other docker rules in the filter table. Hence we need to return to those.