--- /dev/null
+#!/usr/bin/env tclsh
+# Generate network reconfiguration commands, based on dsc's net/
+# configuration and current interfaces state.
+# Copyright (C) 2025 Sergey Matveev <stargrave@stargrave.org>
+#
+# That script calls dsc command, so do not forget proper $DSC_STASH value.
+# It outputs ip-* commands to stdout, intending you to feed to the shell.
+
+proc list-addrs {iface} {
+ set addrs [list]
+ catch {exec ip addr list dev $iface} lines
+ foreach l [split $lines \n] {
+ if {[regexp {inet (\S+)} $l _ addr]} {
+ lappend addrs $addr
+ }
+ if {[regexp {inet6 (\S+)} $l _ addr] && [string range $addr 0 4] != "fe80:"} {
+ lappend addrs $addr
+ }
+ }
+ return $addrs
+}
+
+# Get list of non-loopback system interfaces
+set sysIfaces [list]
+foreach l [split [exec ip link] \n] {
+ if {[regexp {^\d+: (\w+): } $l _ iface] && $iface != "lo"} {
+ lappend sysIfaces $iface
+ }
+}
+
+set dscIfaces [split [exec dsc get net/*] \n]
+
+foreach iface $sysIfaces {
+ if {[lsearch -exact $dscIfaces $iface] == -1} {
+ # If interface is not mentioned in dsc, then disable it and remove
+ # all addresses, as they are still known to the system
+ puts "ip link set $iface down"
+ foreach addr [list-addrs $iface] {
+ puts "ip addr del $addr dev $iface"
+ }
+ } else {
+ set mtu [exec dsc get net/$iface/mtu]
+ puts "ip link set $iface mtu $mtu"
+ puts "ip link set $iface up"
+ }
+}
+
+foreach iface $dscIfaces {
+ if {[lsearch -exact $sysIfaces $iface] == -1} {
+ # If dsc's interface is not present in the system, skip it
+ continue
+ }
+ set dscAddrs [list]
+ foreach addr [split [exec dsc get net/$iface/addr/*] \n] {
+ set prefixlen [exec dsc get net/$iface/addr/$addr/prefixlen]
+ lappend dscAddrs $addr/$prefixlen
+ }
+ foreach addr [list-addrs $iface] {
+ set idx [lsearch -exact $dscAddrs $addr]
+ if {$idx == -1} {
+ puts "ip addr del $addr dev $iface"
+ } else {
+ # Address is already present
+ set $dscAddrs [lreplace $dscAddrs $idx $idx]
+ }
+ }
+ foreach addr $dscAddrs {
+ puts "ip addr add $addr dev $iface"
+ }
+}