Replacing PulseAudio with PipeWire

PipeWire is a multimedia server, best known for it’s video support in Wayland. It also provides an audio server which can replace PulseAudio. The appeal, for me at least, to switch over from PulseAudio to PipeWire stems from PipeWire’s better support of bluetooth audio, and especially support for modern A2DP codecs such as AptX, AptX HD and LDAC.

Starting with PipeWire 0.3.20 introduced native mSBC support. This profile support mSBC codec versus CSVD supported by the older HSP/HFP profiles. The difference is significant, as the CSVD only supported narrow band speech (NBS, 8kHz) compared with mSBC support for wide band speech (WBS, 16kHz). That is the difference between 90’s era call quality sound and modern call quality sound.

Installing PipeWire 0.3.23

As of writing this post, Debian Unstable only has PipeWire 0.3.19. We are going to install PipeWire from the experimental repo so we get the PipeWire 0.3.23 with the support for mSBC.

We start by enabling the experimental repo

$ sudo apt-add-repository "deb http://deb.debian.org/debian experimental main
$ sudo apt update

Install PipeWire from experimental:

$ sudo apt install -t experimental pipewire-audio-client-libraries libspa-0.2-bluetooth

(pipewire-audio-client-libraries will pull pipewire itself as a dependency)

Substituting PipeWire for PulseAudio

These instructions are based on the ones from Debian Wiki, Arch Wiki and Gentoo Wiki. Create the file

$ sudo touch /etc/pipewire/media-session.d/with-pulseaudio

It will instruct PipeWire to handle Bluetooth audio devices.

Copy the pipewire-pulse systemd service:

$ sudo cp /usr/share/doc/pipewire/examples/systemd/user/pipewire-pulse.{service,socket} /etc/systemd/user

Disable PulseAudio services and enable the PipeWire ones

$ systemctl --user disable pulseaudio.socket pulseaudio.service
$ systemctl --user stop pulseaudio.socket pulseaudio.service
$ systemctl --user enable pipewire pipewire-pulse
$ systemctl --user start pipewire pipewire-pulse

If everything worked well pactl info should report Server Name: PulseAudio (on PipeWire 0.3.23):

$ pactl info | grep "Server Name"
Server Name: PulseAudio (on PipeWire 0.3.23)

If not, you might need to restart (PulseAudio tends to be rather persistent). In case PulseAudio still doesn’t play nicely, you should mask it:

$ systemctl --user mask pulseaudio.service pulseaudio.socket
$ systemctl --user stop pulseaudio.service pulseaudio.socket

Removing PulseAudio completely is not a good move at this point in time. Some packages depend on it, although they could work with PipeWire just as well. For example, when I remvoed PipeWire libcanberra-pulse got removed as well which caused system notification sounds to break. Alternatively you could try to replace the PulseAudio package with a dummy using equivs but that seems like more effort than keeping the package.

Enabling mSBC and SBC XQ

Edit /etc/pipewire/media-session.d/bluez-monitor.conf and uncomment the following lines:

bluez5.msbc-support   = true
bluez5.sbc-xq-support = true

This will enable both mSBC and SBC XQ.

You can test that you’re headset is connected via mSBC using pw-cli info:

$ guyru@gdebian3:~$ pw-cli info all | grep bluez
info: unsupported type PipeWire:Interface:Profiler
info: unsupported type PipeWire:Interface:Metadata
info: unsupported type PipeWire:Interface:Metadata
*		device.api = "bluez5"
*		device.name = "bluez_card.94_DB_56_AC_36_52"
*		api.bluez5.path = "/org/bluez/hci0/dev_94_DB_56_AC_36_52"
*		api.bluez5.address = "94:DB:56:AC:36:52"
*		api.bluez5.device = ""
*		api.bluez5.class = "0x240404"
*		api.bluez5.transport = ""
*		api.bluez5.profile = "headset-head-unit"
*		api.bluez5.codec = "mSBC"
*		api.bluez5.address = "94:DB:56:AC:36:52"
*		node.name = "bluez_input.94_DB_56_AC_36_52.headset-head-unit"
*		factory.name = "api.bluez5.sco.source"
*		device.api = "bluez5"
*		api.bluez5.transport = ""
*		api.bluez5.profile = "headset-head-unit"
*		api.bluez5.codec = "mSBC"
*		api.bluez5.address = "94:DB:56:AC:36:52"
*		node.name = "bluez_output.94_DB_56_AC_36_52.headset-head-unit"
*		factory.name = "api.bluez5.sco.sink"
*		device.api = "bluez5"

In case mSBC is not supported you’ll see api.bluez5.codec = "CSVD" (and you’lll probably hear the difference).

Errors

Problem: Connecting to bluetooth headset fails, and the following error appears in journalctl:

bluetoothd[41893]: src/service.c:btd_service_connect() a2dp-sink profile connect failed for 94:DB:56:AC:36:52: Protocol not available

Solution: You’re missing the libspa-0.2-bluetooth package. Install it and restart PipeWire:

$ sudo apt install -t experimental libspa-0.2-bluetooth
$ systemctl --user restart pipewire pipewire-pulse

Problem: ALSA programs fail with the following error:

ALSA lib pcm_dmix.c:1075:(snd_pcm_dmix_open) unable to open slave
aplay: main:830: audio open error: Device or resource busy

Solution: You need to enable the ALSA backend for PipeWire:

$ sudo touch /etc/pipewire/media-session.d/with-alsa
$ systemctl --user restart pipewire pipewire-pulse

Rename Debian packages according to version

This is a small bash utility function to allow renaming deb archives according to their version.

rename-deb () 
{ 
    base="${1%.deb}";
    version="$(dpkg-deb -f $1 Version)" || return 1;
    new="$base-$version.deb";
    mv -i "$1" "$new";
    echo "$1 -> $new"
}

You can either run it one time in your shell, or define it in your ~/.bash_aliases.

Example:

$ rename-deb zoom_amd64.deb
zoom_amd64.deb -> zoom_amd64-5.5.7011.0206.deb

Checking Thunderbolt security on Linux

Thunderbolt connections may provide DMA access to the host and pose a security risk. There are two mechanisms to mitigate against Thunderbolt DMA attacks in Linux, and you would probably want to verify at least one of them is active.

IOMMU DMA Protection

This uses IOMMU to explicitly allow what memory Thunderbolt devices can access via DMA. This is the prefered way to protect against Thunderbolt-based DMA attacks. It is available on recent hardware (~2018 and forward) and requires Kernel >= 5.0. You can verify IOMMU DMA Protection is enabled using:

cat /sys/bus/thunderbolt/devices/domain0/iommu_dma_protection
1

Value of 1 means it is enabled. This setting is controlled through UEFI. At least in Lenovo systems it is named cat "Kernel DMA Protection" (like the Microsoft name for this feature). You can find it under the Security tab.

Thunderbolt protection

This is the old style of protection, that preceded IOMMU protection. If configured properly, it allows the user to explicitly authorize Thunderbolt devices before granting them DMA access.

$ cat /sys/bus/thunderbolt/devices/domain0/security 
none

none means no protection at all (that will also be the case if you have IOMMU DMA protection enabled). user requires the user to authorize a Thunderbolt device each time it’s connected. secure is like user but devices authorized in the past do not require re-authorization. dponly only allow DisplayPort pass-through.

Short cryptsetup/LUKS tutorial

This short tutorial will guide you in encrypting a drive with cryptsetup and LUKS scheme.

Before starting, if the device had previous data on it, it’s best to delete any filesystem signatures that may be on it. Assuming that the drive we operate is /dev/sda you can use the following command to remove the signatures:

$ sudo wipefs --all /dev/sda --no-act

Remove the --no-act flag to actually modify the disk.

The next step is to actually format the drive using LUKS. This is done using the cryptsetup utility.

$ sudo cryptsetup luksFormat --type=luks2 /dev/sda

WARNING!
========
This will overwrite data on /dev/sda irrevocably.

Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/sda: 
Verify passphrase: 

The command will prompt you to enter a passphrase for the encryption and should take a few seconds to complete.

The next step is to add an appropriate entry to crypttab which will simplify starting the dm-crypt mapping later. Add the following line to /etc/crypttab:

archive_crypt UUID=114d42e5-6aeb-4af0-8758-b4cc79dd1ba0 none luks,discard,noauto

where the UUID is obtained through lsblk /dev/sda -o UUID or a similar command. The archive_crypt is the name for the mapped device. It will appear as /dev/mapper/archive_crypt when the device is mapped. The none parameter specifies that no keyfile is used and the system should prompt for an encryption passphrase instead. The noauto, means not to attempt to load the device automatically upon boot. discard should be used if the underlying device is an SSD.

You can test everything works so far by opening and loading the LUKS device:

$ sudo cryptdisks_start archive_crypt

While the device is now encrypted, there is a possible leakage of metadata such as used blocks as an attacker can discern used vs unused blocks by examining the physical drive. This and other side-channel leaks can be mitigated by simply wiping the contents of the encrypted device.

$ openssl rand -hex 32 | openssl enc -chacha20 -in /dev/zero -pass stdin -nosalt | sudo dd if=/dev/stdin of=/dev/mapper/sda_crypt bs=4096 status=progress

We could also have used /dev/urandom but the above technique is much faster.

Now we can create the actual filesystem.

$ sudo mkfs.btrfs --label archive /dev/mapper/archive_crypt

At this point we’re actually pretty much done. You can add and entry to /etc/fstab to easily mount the filesystem and you’re done.

/dev/mapper/archive_crypt /home/guyru/archive btrfs noauto,user 0 0

Gave up waiting for suspend/resume device

My boot process was pretty slow in a new setup I had. It would stop for about 30 seconds and then give the following error:

Gave up waiting for suspend/resume device

Turns out I had a resumable device listed in /etc/initramfs-tools/conf.d/resume even though my swap is both encrypted with random keys and too small. Editing that file and setting RESUME=none and running sudo update-initramfs -u fixed the issue.

Fixing Zoom’s Screen Sharing on Debian Unstable

Zoom has a native Linux client which supports screen sharing in Wayland, at least on some platforms. Today when I tried to start a Share Screen I encountered the following error:

Error when trying to start Share Screen

Can not start share, we only support Wayland on GNOME with Ubuntu 17 and above, Fedora 25 and above, Debian 9 and above, CentOS 8 and above, OpenSUSE Leap 15 and above, Oracle Linux 8 and above, Arch Linux, AnterGos, Manjaro. If your OS is not on the list, please use x11 instead.

The feature works for me when I’m using Debian Stable (Buster), and also worked for the short while I’ve used Debian Testing (Bullseye). So, I guessed that the feature is broken due to wrong OS version detection. The fix is easy: Remove /etc/os-release (which is by default a symlink to /usr/lib/os-release) and append to the original contents the following lines:

VERSION_ID="99"
VERSION="99 (sid)"
VERSION_CODENAME="sid"

So the entire file should look like:

PRETTY_NAME="Debian GNU/Linux bullseye/sid"
NAME="Debian GNU/Linux"
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
VERSION_ID="99"
VERSION="99 (sid)"
VERSION_CODENAME="sid"

Why it works?

When I first encountered the error, I guessed Zoom doesn’t actually attempt the Share Screen, but relies on a pre-configured list of supported distros and (minimal) versions. It worked for me with Debian Stable (10) and Testing (11), but what version number is Unstable? Debian Unstable doesn’t have a version number associated with it, so it must be the problem.

A quick strace revealed how Zoom retrieves the current distro name and version:

$ strace -f -tt zoom
...
107498 11:20:58.943764 openat(AT_FDCWD, "/etc/os-release", O_RDONLY|O_CLOEXEC
107513 11:20:58.943771 futex(0x565257f88760, FUTEX_WAIT_BITSET_PRIVATE|FUTEX_CLOCK_REALTIME, 0, {tv_sec=1592727659, tv_nsec=303765000}, FUTEX_BITSET_MATCH_ANY
107498 11:20:58.943779 <… openat resumed>) = 17
107498 11:20:58.943787 fcntl(17, F_SETFD, FD_CLOEXEC) = 0
107498 11:20:58.943802 fstat(17, {st_mode=S_IFREG|0644, st_size=200, …}) = 0
107498 11:20:58.943817 read(17, "PRETTY_NAME=\"Debian GNU/Linux bu"…, 200) = 200
...

As you can see Zoom reads (and probably later parses) the entire /etc/os-release file. This file contains identification data for the current running distro including name and version. Because Debian Sid doesn’t have the version variables set, Zoom erroneously misinterpret it as an old version instead of the newest. Thus, it refuses to enable the Share Screen feature. Adding the relevant version variables solves this issue.

See also: man os-release (5)

Prevent applications from increasing output volume in PulseAudio

By default, PulseAudio allows an application to change the max volume output to be louder than the one set by the user. I find it annoying that some apps tend to set volume to 100% which ends up increasing the system volume to unreasonable levels. You can prevent it by setting flat-volumes to no in ~/.config/pulse/daemon.conf.

$ echo "flat-volumes = no" >> ~/.config/pulse/daemon.conf 
$ pulseaudio --kill
$ pulseaudio --start

With flat-volumes = no PulseAudio uses relative volumes. Each applications sets its own volume relative to the system output.

References:

`xdg-open` fails when using Firefox under Wayland

Recently I noticed xdg-open started failing opening links in Firefox. Giving me the following error:

Firefox is already running, but is not responding. To open a new window, you must first close the existing Firefox process, or restart your system.

Firefox is already running, but is not responding. To open a new window, you must first close the existing Firefox process, or restart your system.

It happened while I had Firefox running and responding to everything else. I’m running the latest stable Firefox (74 as I’m writing this) on Wayland. Wayland brings a lot of good things, but also a lot of interoperability problems, so I suspected it had something to do with it. Thanks to Martin Stransky I found out that the solution is to set the MOZ_DBUS_REMOTE environment variable prior to launching Firefox. If you are using a desktop file to launch Firefox, you can set the variable in the Exec line like this:

[Desktop Entry]
Type=Application
Name=Firefox
Exec=env MOZ_DBUS_REMOTE=1 MOZ_ENABLE_WAYLAND=1 /home/guyru/.local/firefox/firefox %u
X-MultipleArgs=false
Icon=firefox-esr
Categories=Network;WebBrowser;
Terminal=false
MimeType=text/html;text/xml;application/xhtml+xml;application/xml;application/vnd.mozilla.xul+xml;application/rss+xml;application/rdf+xml;image/gif;image/jpeg;image/png;x-scheme-handler/http;x-scheme-handler/https;

You will need to restart Firefox before the fix will take affect.

TP-Link Archer T4Uv2 support for Debian Buster

This post outlines how I backported rtl8812au-dkms from Ubuntu Focal for Debian Buster and added support for the TP-Link Archer T4U v2 card to it. If you are only interested in the resulting .deb file skip to the end.

The TP-Link Archer T4U v2 is an AC1300 WiFi USB adapter. TP-Link provides drivers but they are built only for old kernel versions (<=3.19) and do not supoort DKMS, which makes upgrading a hassle.

Ubuntu provides the rtl8812au-dkms package which support the chipset in the Archer T4Uv2, but it doesn’t recognize the TP-Link product. So I set out to backport it to Debian Buster and make it support the Archer T4Uv2.

We start by fetching the rtl8812au source package from Ubuntu.

$ dget --allow-unauthenticated http://archive.ubuntu.com/ubuntu/pool/universe/r/rtl8812au/rtl8812au_4.3.8.12175.20140902+dfsg-0ubuntu12.dsc
$ cd rtl8812au-4.3.8.12175.20140902+dfsg/
$ sed -i s/dh-modaliases// debian/control
$ sed -i s/,modaliases// debian/rules
$ mk-build-deps ./debian/control --install --root-cmd sudo --remove

The sed lines remove reference to the dh-modaliases build dependency which Debian doesn’t have. I’m not really sure why they needed it for this package, but removing it didn’t hurt.

Next we add a new patch using quilt to support the Archer T4Uv2. We extract the 2357:010d USB vid:pid pair of the adapter using lsusb.

$ quilt push -a
$ quilt new add_archer_t4uv2.patch
$ quilt add os_dep/linux/usb_intf.c
$ vim os_dep/linux/usb_intf.c

The change we’ll be making to os_dep/linux/usb_intf.c is outlined by the following patch:

--- rtl8812au-4.3.8.12175.20140902+dfsg.orig/os_dep/linux/usb_intf.c
+++ rtl8812au-4.3.8.12175.20140902+dfsg/os_dep/linux/usb_intf.c
@@ -303,6 +303,7 @@ static struct usb_device_id rtw_usb_id_t
 	{USB_DEVICE(0x20f4, 0x805b),.driver_info = RTL8812}, /* TRENDnet - */
 	{USB_DEVICE(0x2357, 0x0101),.driver_info = RTL8812}, /* TP-Link - Archer T4U */
 	{USB_DEVICE(0x2357, 0x0103),.driver_info = RTL8812}, /* TP-Link - Archer T4UH */
+	{USB_DEVICE(0x2357, 0x010d),.driver_info = RTL8812}, /* TP-Link - Archer T4Uv2 */
 	{USB_DEVICE(0x0411, 0x025d),.driver_info = RTL8812}, /* Buffalo - WI-U3-866D */
 #endif

Finish up adding the patch:

$ quilt header --dep3 -e
$ quilt refresh
$ quilt pop -a

And build the package:

$ DEBEMAIL="Guy Rutenberg <guyrutenberg@gmail.com>" debchange --bpo
$ debuild -us -uc

Now we can install the newly created deb package:

$ cd ../
$ sudo apt install ./rtl8812au-dkms_4.3.8.12175.20140902+dfsg-0ubuntu12~bpo10+1_all.deb

If you came here only for the actual binary package you can find it in my deb repository: https://guyrutenberg.com/debian/buster/

Install JetBrains Mono in Debian/Ubuntu

JetBrains Mono is a new monospace typeface designed to be comfortable to read. It has clear distinction between the different letters and relatively high x-height.

The installation instruction were tested on Debian, but should work on every Linux.

Download and unzip the font:

$ wget https://download.jetbrains.com/fonts/JetBrainsMono-1.0.0.zip
$ unzip JetBrainsMono-1.0.0.zip

Install the font to either the user’s font directory

$ mv JetBrainsMono-*.ttf ~/.local/share/fonts/

or the system-wide one:

$ sudo mv JetBrainsMono-*.ttf /usr/share/fonts/

To use the font in gVim set guifont accordingly in ~/.vimrc:

:set guifont=JetBrains\ Mono\ 13