ddwrt router refusing to forward multicast packets over an OpenVPN tap device joined to a bridge

Trying out the go-chromecast CLI, I was able to see mDNS requests coming to my home network over OpenVPN (a L2 tunnel using a TAP device bridged using br0 on both ends). I could also see responses being generated, but none of them were seen on the other end. The build of dd-wrt I use has no tcpdump, making it hard to observe both ends of the tunnel.

There's a lot of sources suggesting a bunch of actions:

However, that was all tapping in the dark.

But when someone mentioned ebtables, nat and PREROUTING, this led me to the right path: what if one of the chains in one of the tables was dropping outgoing packets?

# ebtables -t nat -L POSTROUTING
Bridge table: nat

Bridge chain: POSTROUTING, entries: 1, policy: ACCEPT
-o tap1 --pkttype-type multicast -j DROP

Voila. All multicast packets were being dropped on L2.

# ebtables -t nat -D POSTROUTING -o tap1 --pkttype-type multicast -j DROP

This is fine because I happen to control both ends of the tunnel. Because my systems use multicast only for mDNS, I don't expect traffic to require drops.

On the bridge device (br0 on both ends), I currently have multicast_router set to 2 on both ends; I have multicast_querier set to 1 on both ends; non-ddwrt system has multicast_igmp_version set to 2; and multicast_snooping is set to 1 on both ends. I don't claim correctness of any of these, nor do I claim them to be optimal. But, getting mDNS traffic through is exactly what I wanted, so I'm happy right now.

Exploring the sudoers file: "sudo: sorry, you are not allowed to preserve the environment"

On my NAS I've received sudo: sorry, you are not allowed to preserve the environment today when running sudo -E to transfer the environment from whatever the current shell allows sudo to see, into the new shell.

That's because I was copying something from a remote machine to my NAS, and I (temporarily) wanted to tell remote rsync to use sudo rsync on my NAS to be sure files will be replicated correctly. While this is an absolutely horrible idea to do permanently, a temporary workaround in a safe environment is that sudo rsync should not require a root password:

ivucica ALL=NOPASSWD:/usr/bin/rsync

However, compare this to some of the defaults in /etc/sudoers:

# User privilege specification
root    ALL=(ALL:ALL) ALL

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL

Without reading the sudoers(5) manpage, I decided to add ALL at the end:

ivucica ALL=NOPASSWD:/usr/bin/rsync ALL

This seemed to work, but despite a lack of a syntax error, it's wrong (see below): now no password is required for rsync nor for any other command.

# Wrong order:
 ALL=NOPASSWD: /usr/bin/rsync, PASSWD: ALL
# Correct order:
ivucica ALL=PASSWD: ALL, NOPASSWD: /usr/bin/rsync

The best option, however, is to remove the entry completely given that allowing rsync root without a password is dangerous; even if someone managed to hijack my own account's non-password NAS credentials, without the password itself they can't do much.

However, looking online for this error, I realized I've never looked into the syntax of the sudoers file. What's Defaults env_reset, for instance? Turns out sudoers(5) is an interesting read. If you're seriously going to play with editing /etc/sudoers beyond just adding a user (even just specifying the NOPASSWD tag), please read the manpage and carefully experiment. Cover all the edge cases you can think of. Have someone else review your changes.

Here's some interesting stuff:

  • #include (and presumably #includedir) directive to include a file can contain %h (a hostname) in file path
  • to match all users except root, instead of ALL we can specify ALL,!root
  • you can do things like disable setting utmp (set_utmp) or prohibit the capability to disable env_reset (setenv) — which is what I did
  • the first-time "lecture" status ('has this user seen the sudo lecture?') is in /var/lib/sudo/lectured and configurable using lecture_status_dir
  • the password prompt can be overridden with passprompt and can include %H FQDN, %h hostname, %p the user whose password is requested, %U the user whom we're switching to [caveat: PAM module's output must match Password: or username's Password:
  • there are ways to specify directives per running host (important for standardized configs deployed across an org), per requesting user, per run-as user, per command
  • there are tags to tweak execution of a command

While I haven't actually tried all of the following, here are some "notes-to-self" on what individual fields mean. Please prefer learning the details from the manpage.

What does ALL before = mean? Those are the hosts. For instance, we can let roger run anything on all the fileservers:

Host_Alias FILESERVERS = fs1, fs, fs3
Host_Alias FINANCE =,


(Just like Host_Alias, there's User_Alias, Runas_Alias and Cmnd_Alias.)

What's (ALL:ALL) after =? That's called a Runas_Spec and it's saying "You can pretend to be any user of any group." Let's only allow roger to run only /usr/bin/rsync and only to do it as mainweb:

User_Alias     FINANCE_DEPLOY      = james, mike
roger          FILESERVERS         = (mainweb) /usr/bin/rsync
richard        richard-workstation = (www-data) ALL
FINANCE_DEPLOY FINANCE             = (financeweb : financeservices) /usr/bin/rsync

And what's the ALL after (ALL:ALL)? As demonstrated, that's the command specification.

Alright, so what's NOPASSWD:? That's a Tag_Spec. Each command may be prefixed with zero or more tags associated with it, such as EXEC, NOEXEC, PASSWD, NOPASSWD, MAIL, NOMAIL. For instance:

# Allow ls and cat without password, but require a password for vi.
richard richard-workstation = (www-data) NOPASSWD: /usr/bin/ls, /usr/bin/cat, NOEXEC: PASSWD: /usr/bin/vi
richard richard-workstation = (www-data) NOPASSWD: /usr/bin/ls, /usr/bin/cat, PASSWD: /usr/bin/vi

Allowlisting only some commands results in the following when a non-allowed command is run:

Sorry, user richard is not allowed to execute '/bin/bash' as root on my.machine.hostname.

And attempting to run a command from within vi when NOEXEC is specified results in:

Cannot execute shell /bin/bash

There's even a way to set things like timeouts. But, again, read the manpage for details.

As usual, this is not advice; these are personal notes from trying to resolve an immediate issue. As this is a security-sensitive feature, for even one person that reads this and configures an important system incorrectly, please note: these notes are written as a hobby. Just like you would with PAM, NSS, LDAP, Kerberos and other things, please carefully read more authoritative documentation sources; I'm not responsible for breakage you may cause with my non-advice.

Getting all tabs from Chrome on Android

I damaged the screen on my Pixel 3a XL and it's now increasingly bleeding meaning hour after hour I see less of the image. See illustration I found online; I still haven't fully lost the screen, but it's getting there. Touch still mostly works (except for the top portion of the screen), but enough to unlock the phone. I'm in Ireland and preliminary price for official repair from Google is EUR167 with tax.

scrcpy, installable from Debian unstable, proved to be useful to connect to the phone. I've previously set up developer mode, and I saw enough of the screen to confirm adb connection; now I see and click on the screen on the Linux machine.

One of the things that's not backed up to the cloud and is terribly bad with syncing is the list of open tabs in Chrome (I'm only seeing about 15 tabs in "Tabs from other devices" on my desktop). There's no bookmarking of all tabs (crbug/488106 and crbug/1026222).

While Chrome's developer tools can be used to connect to the device and see the list of open tabs, using adb to forward localabstract:chrome_devtools_remote is better.

$ adb forward tcp:9222 localabstract:chrome_devtools_remote

This way, the relevant API can be accessed over HTTP on port 9222. (You can even visit the service's home page in your browser.)

$ curl http://localhost:9222/json/list > 2020-10-pixel3axl-chrome-tabs.json

You can confirm if the tabs have been fetched using less, or counting the entries in JSON with jq:

$ jq '. | length' 2020-10-pixel3axl-chrome-tabs.json 

You can also use jq to get just the URLs one at a time:

$ jq  -r '.[].url' 2020-10-pixel3axl-chrome-tabs.json 

Unfortunately, the tabs are not ordered correctly. Still, I'd rather have all tabs in wrong order than just a subset in the correct order.

Scanning on Linux from Canon TS5050 over the network

scangearmp2 screenshot

Turns out that ScanGear MP 3.70 works perfectly with my Canon TS5050. After unpacking the archive and dpkg -i scangearmp2_3.70-1_amd64.deb, I ran scangearmp2, got simple GTK-based GUI, the TS5050 was recognized, and that was it.

scangearmp2 screenshot

I have no idea if it integrates in desktop GUIs, since when I'm under Debian GNU/Linux these days, I am happily using i3 and terminals.

It would be really nice if Canon listed any software for Linux on their official support page for Canon TS5050, but they don't. There are no Linux "bundles" listed (whew!), but this also means there is no software offered at all. Once you choose Linux, you get random offers for Windows and Mac software — but that's just generic ads displayed under the 'no results' area.

Samba 4 + Windows 10 time synchronization issues

Where does ListeningThread -- Recvd 52 of 48/68 bytes come from?

If you follow the instructions for setting up Samba 4 AD DC for time synchronization, ntpd (coming out of Debian's ntp package at some version 4.2.8) should just work.1

I came to this discovery after giving up and discarding my /etc/ntp.conf. Suddenly, after restarting ntpd and running w32tm /resync, things just worked. It's not the software that's broken — it's me that was crazy.

The packet was now 110 bytes in Wireshark (68 of which was data). This was a stark improvement over seeing a 94 byte packet (52 of which was data). C:\temp\ntpDebug.log 2 no longer contained this:

ListeningThread -- Recvd 52 of 48/68 bytes

Hoozah! Now I wanted to figure out what was causing ntpd to send 52b packets, and not either 48b or 68b packets.

Turns out that my restrict statements had unexpected side effects. For instance, Samba wiki-recommended config tries to unrestrict localhost using restrict 3

But I wanted to do the same for IPv6 localhost, so I did restrict ::1. This seems to have greatly confused ntpd.

The way out?

restrict -4
restrict -6 ::1

Second mistake was restrict mask It didn't specify that mssntp should be enabled. For good measure I threw in -4:

restrict -4 mask mssntp

Given that Samba config doesn't recommend any special allowlisting for my internal IP range, I'll just remove this line completely; the default restriction from the wiki should cover everything clients need to do anyway:

# Access control
# Default restriction: Allow clients only to query the time
restrict default kod nomodify notrap nopeer mssntp

Moral of the story? ntpd seems to be awfully sensitive to restrict statements. If w32time service complains or breaks in some way, be sure to remove the statements bit by bit, or make sure IPv4 and IPv6 statements don't stomp over each other.

  1. Granted, I needed to modify the path to the socket to say /var/lib/samba/ntp_signd/ instead of /usr/local/samba/var/lib/ntp_signd/, but otherwise it just worked. 
  2. That file was created using w32tm /debug /enable /file:C:\temp\ntpDebug.log /size:102400 /entries:0-300 which I found somewhere online. 
  3. Apparently, passing no restrictions at all after the address simply means "unrestrict these peers". 

Fixing "Use of undefined constant jq_syntax_htmlentities"

I upgraded the PHP version backing my WordPress setup to PHP7.3. Of course, something had to go wrong:

Warning: Use of undefined constant jq_syntax_htmlentities - assumed 'jq_syntax_htmlentities' (this will throw an Error in a future version of PHP) in ...... wp-content/plugins/jquery-syntax/jquery-syntax.php on line 37

There's no updates for jquery-syntax plugin that I'm using. Let's assume that the author will fix this if they ever release an update.

For the time being, opening jquery-syntax.php manually and editing jq_syntax_quote to replace passing what looks like a function object (is it in PHP? I can't be bothered to check) with a string when invoking preg_replace_callback() does the trick:

function jq_syntax_quote($content) {
        $content = preg_replace_callback('/<(pre)(.*?)>(.*?)<\/pre>/imsu','jq_syntax_htmlentities', $content);
        $content = preg_replace_callback('/<(code)(.*?)>(.*?)<\/code>/imsu','jq_syntax_htmlentities', $content);

        return $content;

Note how jq_syntax_htmlentities received single-quotes around it.