Suspend on lid closed + battery

If you use FreeBSD 11 on the good ol’ ThinkPad X201, you’ve probably noticed now that suspend and resume work perfectly flawlessly with the latest FreeBSD release.

You probably wish to take advantage of this and suspend your laptop automatically when the lid is closed. Nothing could be easier:

sysctl hw.acpi.lid_switch_state=S3

Save this option /etc/sysctl.conf for it to be permanent.
Close the lid, the laptop goes to sleep, you’re done!
It’s that simple.

But, with this method it will go to sleep each time the lid is closed, completely ignoring whether the laptop is on battery or not. And if you’re like me you probably don’t want this. Instead you want it to suspend itself when the lid is closed and the laptop is on battery. Easy enough! We just have to invoke the mighty power of devd along with a very little shell script.

First we need to tell devd how to react when the lid is closed. Put this in /etc/devd/lid.conf, then restart (service devd restart):

# Notify lid close/open events.
notify 10 {
  match  "system"    "ACPI";
  match  "subsystem" "Lid";
  action "/etc/acpi/lid.sh $notify";
};

Now we will make a script that checks the lid and AC line states and chooses to suspend the laptop when both the lid is closed and AC line is disconnected. This goes in /etc/acpi/lid.sh:

#!/bin/sh

lid="$1" # 0x00 is closed, 0x01 is open (see devd.conf)
acline=$(sysctl -n hw.acpi.acline) # 0 is battery, 1 is online (man acpi)

if [ \( "$lid" = "0x00" \) -a \( "$acline" -eq 0 \) ]
then
  logger "Lid closed on battery. Going to sleep."
  acpiconf -s3
fi

Try it out! Disconnect the AC line, close the lid, it should go to sleep within seconds. However if it doesn’t work you might want to modify the script above a little to check whether lid events are received correctly or not.

Now you may use a modified version of this script to lock xscreensaver when the lid is closed and before the actual suspend. Well OK, I’ve done that for you:

#!/bin/sh
# XScreensaver should be called BEFORE going to sleep to avoid the desktop to be
# shown for a few seconds when the system resumes from sleep.
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin

lid="$1" # 0x00 is closed, 0x01 is open (see devd.conf)
acline=$(sysctl -n hw.acpi.acline) # 0 is battery, 1 is online (man acpi)

lock_display() (
  socket="$1"
  display=$(echo "$socket" | tr "X" ":")

  # Temporary pid file for the watching command
  tpid=$(mktemp)

  # Wait until the display is actually locked.
  (timeout 2s xscreensaver-command -display "$display" -watch & echo $! > $tpid) | (
    # Issue the lock command only when we know that
    # the watching pipe is ready.
    xscreensaver-command -display "$display" -lock

    while read line
    do
      line=$(echo $line | cut -d' ' -f 1)

      if [ "$line" = LOCK ]
      then
        # We have to kill the watching command manually before breaking.
        kill -TERM $(cat $tpid)
        break
      fi
    done
  )

  rm $tpid
)

# The X server may not be running
if [ ! -d /tmp/.X11-unix ]
then
  exit 0
fi

# Lock each available display
for socket in $(ls /tmp/.X11-unix)
do
  # Lock the display
  logger "Locking xscreensaver on $socket"
  lock_display $socket &
done

# Wait until every displays are locked
wait

# Now we can suspend if needed
if [ \( "$lid" = "0x00" \) -a \( "$acline" -eq 0 \) ]
then
  logger "Lid closed on battery. Going to sleep."
  acpiconf -s3
fi

Disk failure happens

So my not-so-trusty-anymore 5 years old HDD crashed unexpectedly this morning. That happened on my working laptop. But thank god I do backups! I still probably lost some documents but it could have been much worse.

Coincidentally I was just saying to myself that it would be a good idea to reinstall it just to force me to clean this old setup a little bit. Well that providential failure forced my hand. Thanks for that!

This also prompted me to start building a NAS, an idea that I have in my mind since quite a few months now. So you will probably read more of that in a few weeks.

Using NAT64 locally

NAT64 is a translation mechanism that allows IPv6 addresses to be transparently mapped to IPv4 addresses. The principle if very simple. In a NAT64 address, the IPv4 address is embedded into the 32 least significant bits of the IPv6 address. When you send an IPv6 packet to a NAT64 router, it will extract the IPv4 address embedded into the NAT64 IPv6 destination address and send the packet using this IPv4 instead. The source IPv4 address used is chosen within a provided pool of IPv4 addresses (which can be SNATed, in which case you can use a private network reserverd for the NAT64 pool). The NAT64 router will also do the reverse mapping and translate the reply from the IPv4 remote address to your original IPv6 address.

This is very convenient for IPv6 only hosts (such as IoT smart objects) that need to contact a very sparse IPv6 Internet. Even if a remote host is only accessible via IPv4, it can still be accessed from IPv6 using its NAT64 address. Also since the majority of addresses are resolved using DNS, there are some servers (unbound is one of them) that provide a DNS64 module which will automatically construct a NAT64 AAAA record when no other IPv6 records are found for a specific domain. This way your IPv6 only hosts using DNS believe that all domains have an AAAA record, and that all the Internet is IPv6 enabled.

In my case I use Tayga NAT64 router along with Unbound DNS64 module. There are several posts out there that explain how to configure these two and this is not my intent here. Instead I want to focus on a problem that I had using Tayga’s NAT64 locally. That is trying to ping a non-IPv6 domain from the host that is running Tayga:

$ ping6 ipv4.google.com

This command worked fine from outside the host running Tayga (for packets routed through the NAT64 router), but didn’t work at all when issued on the host running Tayga.

Using tcpdump, I found that when issued locally, Tayga used its own IPv4 address (its ipv4-addr option) as source address for the translated IPv6 packets. Since this address was not mapped in Tayga IPv4 source address pool, it did not translate the reply back to the original IPv6 address and so the ping6 command never got any reply.

What I also found was that the source address used by the ping6 command was also Tayga’s own IPv6 address (its ipv6-address option). Since the NAT64 address that ping6 tries to use is routed through the nat64 interface, it defaulted to the nat64 interface IPv6 address (that is Tayga’s IPv6 address) for its source address.

This is what triggered Tayga to use its own IPv4 address which is outside its pool resulting in the reply not being translated back to its own IPv6 address. So we need a way to avoid choosing Tayga’s own IPv6 address when trying to reach a NAT64 address locally. We know that Tayga configures its own IPv4 and IPv6 addresses on its nat64 interface. All we have to do is to add another IPv6 address to the interface so that it will be used instead as the source address for local packets destinated to the nat64 router interface.

Suppose we have:

  • Internet routed prefix: 2001:db8:1::/64
  • NAT64 IPv6 prefix: 2001:db8:64::/96
  • NAT64 IPv6 address: 2001:db8:1::64
  • NAT64 IPv4 address: 192.168.1.64

Then the nat64 interface will have the 2001:db8:1::64 IPv6 address. Just add another IPv6 address to this interface with:

ifconfig nat64 inet6 add 2001:db8:1::46 netmask 128

Tadaaa! Now when you try to reach a NAT64 address locally, the address you just added to nat64 will be used instead as the source address. Since this is not Tayga’s own IPv6 address, it will dutifully map the NAT64 using its IPv4 address pool and translate the reply back to the new IPv6 address. In other words NAT64 addresses now work on the host running the NAT64 router.

Linuxulator ist kaputt

Since a few weeks I’m running RELEASE on a custom kernel to use a patch that I made for a missing feature in the IPv6 stack (namely icmp_may_rst).

But a few minutes ago I had the surprise to find that the Linuxulator was no longer working. Trying to run a Linux binary failed with the following error:

ELF binary type "0" not known.
exec: test: Exec format error

Actually looking at kldstat, the Linux kernel module wasn’t even loaded. Trying to load it manually gave me the following error:

link_elf_obj: symbol kern_sched_setscheduler undefined
linker_load_file: Unsupported file type

OK so what is this sched_setscheduler you are talking about? Well there you go!
Now the Linuxulator depends on that syscall, but for some reason the necessary option disappeared from my custom kernel configuration. All I had to do was to add it again and recompile:

options _KPOSIX_PRIORITY_SCHEDULING # POSIX P1003_1B real-time extensions

And now I can run Linux binaries again!

Display WiFi RSSI on FreeBSD

On FreeBSD we also use the ifconfig command to configure wireless interfaces. The command ifconfig wlan0 list scan lists available AP along with their SNR. However the ifconfig command does not display the RSSI/SNR on the current link. To do so we need another command, wlanstats. It is not included in base system but you can find it in the source tree under /usr/src/tools/tools/net80211/wlanstats. This command also shows you the signal level (in dBm) and current transmit rate among other things.

I wonder why the RSSI is not visible with the ifconfig command. I suppose they wanted to regroup all those stats with another command. But again I wonder why this command is not part of the base system.

Dedibox serial shell access

If you cannot access the shell on a FreeBSD dedibox from the online.net console, here is a quick tip. Create an alternate root account with csh or tcsh and access ttyu1 on this account instead. That’s almost precisely what the toor account is made for, except that we generally leave the default shell on root.

No idea why sh, bash and zsh are not working through the serial connection, more doesn’t work either, but vi does. Probably a termcap thingy ? If anyone has a clue…