From b0ca7a09f2df89c4903db9554c591b1e7fccc48a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Benko?= Date: Wed, 17 Dec 2025 19:37:24 +0100 Subject: [PATCH] interface-ip: overwrite resolv.conf file instead of renaming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, the change tracking of resolv.conf works as follows: 1. Create a temporary resolv.conf with current entries. 2. Calculate CRC32 of both the temporary and the existing (old) resolv.conf file. 3. If the CRC checksum differs, the temporary file is renamed to replace the old resolv.conf. However, this causes issues with ujail environments where the jail has bind-mounted the resolv.conf from the host. In this case, the new file would not be visible inside the jail until the jail was restarted. This is particularly troublesome for jailed ntpd deamon running on a device without a local DNS server, relying on a DNS info supplied from DHCP (e.g. dumb AP). Very often, the DHCP lease comes later than the ntpd is started, causing time synchronization to fail until the process is manually restarted. The proposed solution modifies the step 3 in the resolv.conf change tracking above: 3. If the CRC checksum differs, the existing resolv.conf is overwritten in place with the new (current) entries (ones from the temporary file). This approach, however, has a drawback - rename is atomic while overwrite is not. To mitigate this issue, a file locking mechanism is added (`flock`). Fixes: - openwrt/openwrt#10389 - openwrt/openwrt#10843 Signed-off-by: Dávid Benko --- interface-ip.c | 63 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 11 deletions(-) diff --git a/interface-ip.c b/interface-ip.c index 4bc6d87..3e7aaf2 100644 --- a/interface-ip.c +++ b/interface-ip.c @@ -12,10 +12,12 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ +#include #include #include #include #include +#include #include #include @@ -1578,6 +1580,22 @@ __interface_write_dns_entries(FILE *f, const char *jail) void interface_write_resolv_conf(const char *jail) { + /* + * This implementation writes to a temporary file, calculates its CRC + * and compares it to the existing resolv.conf file CRC. If they differ, + * the existing resolv.conf is overwritten. This avoids unnecessary writes + * to resolv.conf which may trigger unnecessary reloads in dependent + * services. The existing resolv.conf is exclusively locked (flock) + * to prevent the possibility of concurrent writes (although this should + * not happen). + * + * The reason for not renaming the temporary file over the existing + * resolv.conf is that this causes issues with ujail environments where + * the jail has bind-mounted resolv.conf from the host. In this case, + * the new file would not be visible inside the jail until the jail was + * restarted. + */ + size_t plen = (jail ? strlen(jail) + 1 : 0 ) + (strlen(resolv_conf) >= strlen(DEFAULT_RESOLV_CONF) ? strlen(resolv_conf) : strlen(DEFAULT_RESOLV_CONF) ) + 1; @@ -1596,34 +1614,57 @@ interface_write_resolv_conf(const char *jail) strcpy(path, resolv_conf); } + /* Write DNS entries to temporary resolv.conf file */ sprintf(tmppath, "%s.tmp", path); unlink(tmppath); f = fopen(tmppath, "w+"); if (!f) { - D(INTERFACE, "Failed to open %s for writing", path); + netifd_log_message(L_WARNING, "Failed to open temporary resolv.conf " + "(%s) for writing: %s", tmppath, strerror(errno)); return; } __interface_write_dns_entries(f, jail); + /* Calculate CRC of temporary file */ fflush(f); rewind(f); crcnew = crc32_file(f); fclose(f); - crcold = crcnew + 1; - f = fopen(path, "r"); - if (f) { - crcold = crc32_file(f); - fclose(f); + /* Open existing resolv.conf */ + f = fopen(path, "w+"); + if (!f) { + netifd_log_message(L_WARNING, "Failed to open resolv.conf (%s) " + "for writing: %s", path, strerror(errno)); + return; } - if (crcold == crcnew) { - unlink(tmppath); - } else if (rename(tmppath, path) < 0) { - D(INTERFACE, "Failed to replace %s", path); - unlink(tmppath); + /* Calculate CRC of existing resolv.conf */ + crcold = crc32_file(f); + + /* Overwrite resolv.conf if changed */ + if (crcold != crcnew) { + /* Lock the file */ + if (flock(fileno(f), LOCK_EX | LOCK_NB) < 0) { + netifd_log_message(L_WARNING, "Failed to exclusively lock " + "resolv.conf (%s): %s", path, strerror(errno)); + fclose(f); + return; + } + + __interface_write_dns_entries(f, jail); + + /* Unlock the file */ + flock(fileno(f), LOCK_UN); + + D(INTERFACE, "Updated resolv.conf (%s) content", path); + } else { + D(INTERFACE, "No change in resolv.conf (%s), no update needed", path); } + + fclose(f); + unlink(tmppath); } static void