#!/bin/bash # # Add `options single-request` to the system resolver configuration. # # glibc's getaddrinfo() sends the A and AAAA queries for a name in # parallel on one socket. Some routers answer the A query and drop the # AAAA one, so the resolver waits out its full timeout -- about five # seconds -- before returning an address that was already available. # Disabling IPv6 in the kernel does not help: the resolver still asks. # # `single-request` makes it send the two queries one after the other, # which those routers answer correctly. Anything on the matrix that # calls an external API pays that five seconds per lookup otherwise, and # a Starlark app with a render timeout will simply fail instead. # # Idempotent, and safe to run on a machine that does not need it. Run by # ledmatrix-dns-fix.service on every boot, because whatever manages # resolv.conf regenerates it and drops the option again. # # Usage: sudo ./scripts/utils/apply_dns_single_request.sh set -eu OPTION="options single-request" RESOLVCONF_TAIL="/etc/resolvconf/resolv.conf.d/tail" RESOLV_CONF="/etc/resolv.conf" log() { echo "[dns-single-request] $*"; } already_applied() { grep -qs "^${OPTION}\$" "$1" } # resolvconf regenerates /etc/resolv.conf from these fragments, so the # tail file is the only place an addition survives. Prefer it when the # directory exists, whether or not resolvconf has run yet. if [ -d "$(dirname "$RESOLVCONF_TAIL")" ]; then if already_applied "$RESOLVCONF_TAIL"; then log "already present in $RESOLVCONF_TAIL" else echo "$OPTION" >> "$RESOLVCONF_TAIL" log "added to $RESOLVCONF_TAIL" fi # Only a missing resolvconf is ignorable. If it is present and the # regeneration fails, /etc/resolv.conf still lacks the option, and # reporting success would be a lie. if command -v resolvconf >/dev/null 2>&1; then if ! resolvconf -u; then log "resolvconf -u failed; $RESOLV_CONF was not regenerated" exit 1 fi fi fi # systemd-resolved owns its stub file and rewrites anything appended to it, # and `single-request` is a glibc resolv.conf option with no resolved.conf # equivalent -- so there is nothing this script can do here. Exit non-zero: # the unit would otherwise record success while the workaround is inactive, # which is the failure mode this whole script exists to avoid. if [ -L "$RESOLV_CONF" ] && readlink -f "$RESOLV_CONF" | grep -q "systemd"; then log "$RESOLV_CONF is managed by systemd-resolved." log "'options single-request' is a glibc resolv.conf option and has no" log "resolved.conf equivalent, so it cannot be applied on this host." log "If external API calls are slow, the workaround is to stop using the" log "systemd-resolved stub (see 'man systemd-resolved', NSS/resolv.conf modes)." exit 1 fi if already_applied "$RESOLV_CONF"; then log "already present in $RESOLV_CONF" exit 0 fi if [ ! -w "$RESOLV_CONF" ] && [ -e "$RESOLV_CONF" ]; then log "cannot write $RESOLV_CONF (run with sudo?)" exit 1 fi # A NetworkManager-generated resolv.conf is regenerated on every connection # change, not only at boot -- and this unit is oneshot with RemainAfterExit, # so it will not re-run within the same boot to put the option back. Say so # rather than implying the fix is permanent. Nothing is silently swallowed: # the append below still happens and still works until the next renewal. if grep -qs "Generated by NetworkManager" "$RESOLV_CONF" \ && [ ! -d "$(dirname "$RESOLVCONF_TAIL")" ]; then log "NOTE: $RESOLV_CONF is generated by NetworkManager and has no" log "resolvconf tail directory to write to. The option is being added, but" log "NetworkManager will drop it on the next connection renewal, and this" log "unit does not run again until the next boot. If lookups go slow again" log "before a reboot, re-run this script." fi echo "$OPTION" >> "$RESOLV_CONF" log "added to $RESOLV_CONF"