mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-07 19:59:14 -04:00
fix(network): exclude virtual ether links and prune stale ones from networkd (#2505)
The networkd backend treated any link reporting Type=ether as a wired uplink. Podman bridges and veth pairs report Type=ether, so they were classified as ethernet: isWired() short-circuited on Type and never consulted looksVirtual(), which also lacked a podman prefix. The link map was also never pruned. Links discovered at enumeration or via signals were kept forever, so torn-down container interfaces lingered as routable and could win the wired-uplink slot over the real NIC -- leaving the indicator showing WiFi while a wired connection was active and default-routed. - isWired()/isWireless() exclude virtual interfaces before consulting Type, and looksVirtual() now recognises podman. - enumerateLinks() reconciles the cached map against ListLinks via syncLinks(), pruning links that no longer appear so dead interfaces don't accumulate.
This commit is contained in:
@@ -27,16 +27,19 @@ type linkInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkInfo) isWired() bool {
|
func (l *linkInfo) isWired() bool {
|
||||||
|
if looksVirtual(l.name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if l.linkType != "" {
|
if l.linkType != "" {
|
||||||
return l.linkType == "ether"
|
return l.linkType == "ether"
|
||||||
}
|
}
|
||||||
if looksVirtual(l.name) || strings.HasPrefix(l.name, "wlan") || strings.HasPrefix(l.name, "wlp") {
|
return !strings.HasPrefix(l.name, "wlan") && !strings.HasPrefix(l.name, "wlp")
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *linkInfo) isWireless() bool {
|
func (l *linkInfo) isWireless() bool {
|
||||||
|
if looksVirtual(l.name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if l.linkType != "" {
|
if l.linkType != "" {
|
||||||
return l.linkType == "wlan"
|
return l.linkType == "wlan"
|
||||||
}
|
}
|
||||||
@@ -45,7 +48,7 @@ func (l *linkInfo) isWireless() bool {
|
|||||||
|
|
||||||
func looksVirtual(name string) bool {
|
func looksVirtual(name string) bool {
|
||||||
virtualPrefixes := []string{
|
virtualPrefixes := []string{
|
||||||
"lo", "docker", "veth", "virbr", "br-", "vnet", "tun", "tap",
|
"lo", "docker", "podman", "veth", "virbr", "br-", "vnet", "tun", "tap",
|
||||||
"vboxnet", "vmnet", "kube", "cni", "flannel", "cali",
|
"vboxnet", "vmnet", "kube", "cni", "flannel", "cali",
|
||||||
}
|
}
|
||||||
for _, prefix := range virtualPrefixes {
|
for _, prefix := range virtualPrefixes {
|
||||||
@@ -110,6 +113,12 @@ func (b *SystemdNetworkdBackend) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type enumeratedLink struct {
|
||||||
|
ifindex int32
|
||||||
|
name string
|
||||||
|
path dbus.ObjectPath
|
||||||
|
}
|
||||||
|
|
||||||
func (b *SystemdNetworkdBackend) enumerateLinks() error {
|
func (b *SystemdNetworkdBackend) enumerateLinks() error {
|
||||||
obj := b.conn.Object(networkdBusName, b.managerPath)
|
obj := b.conn.Object(networkdBusName, b.managerPath)
|
||||||
|
|
||||||
@@ -123,25 +132,48 @@ func (b *SystemdNetworkdBackend) enumerateLinks() error {
|
|||||||
return fmt.Errorf("ListLinks: %w", err)
|
return fmt.Errorf("ListLinks: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fresh := make([]enumeratedLink, len(links))
|
||||||
|
for i, l := range links {
|
||||||
|
fresh[i] = enumeratedLink{ifindex: l.Ifindex, name: l.Name, path: l.Path}
|
||||||
|
}
|
||||||
|
|
||||||
b.linksMutex.Lock()
|
b.linksMutex.Lock()
|
||||||
defer b.linksMutex.Unlock()
|
defer b.linksMutex.Unlock()
|
||||||
|
b.syncLinks(fresh)
|
||||||
|
|
||||||
for _, l := range links {
|
return nil
|
||||||
if existing, ok := b.links[l.Name]; ok && existing.path == l.Path {
|
}
|
||||||
existing.ifindex = l.Ifindex
|
|
||||||
|
// syncLinks reconciles the cached link map against the freshly enumerated set:
|
||||||
|
// it adds links not seen before (querying their Type once), refreshes the
|
||||||
|
// ifindex of survivors, and prunes links that no longer appear. Pruning is what
|
||||||
|
// keeps torn-down container interfaces (podman bridges, veth pairs) from
|
||||||
|
// lingering as routable and being mistaken for the wired uplink.
|
||||||
|
// Callers must hold linksMutex.
|
||||||
|
func (b *SystemdNetworkdBackend) syncLinks(fresh []enumeratedLink) {
|
||||||
|
present := make(map[string]bool, len(fresh))
|
||||||
|
for _, l := range fresh {
|
||||||
|
present[l.name] = true
|
||||||
|
if existing, ok := b.links[l.name]; ok && existing.path == l.path {
|
||||||
|
existing.ifindex = l.ifindex
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
info := &linkInfo{
|
info := &linkInfo{
|
||||||
ifindex: l.Ifindex,
|
ifindex: l.ifindex,
|
||||||
name: l.Name,
|
name: l.name,
|
||||||
path: l.Path,
|
path: l.path,
|
||||||
linkType: b.fetchLinkType(l.Path),
|
linkType: b.fetchLinkType(l.path),
|
||||||
}
|
}
|
||||||
b.links[l.Name] = info
|
b.links[l.name] = info
|
||||||
log.Debugf("networkd: enumerated link %s (ifindex=%d, path=%s, type=%q)", l.Name, l.Ifindex, l.Path, info.linkType)
|
log.Debugf("networkd: enumerated link %s (ifindex=%d, path=%s, type=%q)", l.name, l.ifindex, l.path, info.linkType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
for name := range b.links {
|
||||||
|
if !present[name] {
|
||||||
|
log.Debugf("networkd: pruned stale link %s", name)
|
||||||
|
delete(b.links, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchLinkType queries networkd's Describe method and extracts the link Type
|
// fetchLinkType queries networkd's Describe method and extracts the link Type
|
||||||
|
|||||||
@@ -160,6 +160,12 @@ func TestLinkInfo_Classify(t *testing.T) {
|
|||||||
{"loopback type", "lo", "loopback", false, false},
|
{"loopback type", "lo", "loopback", false, false},
|
||||||
{"none type (tun overlay)", "nebula.homelab", "none", false, false},
|
{"none type (tun overlay)", "nebula.homelab", "none", false, false},
|
||||||
{"none type (wireguard)", "wg0", "none", false, false},
|
{"none type (wireguard)", "wg0", "none", false, false},
|
||||||
|
// Virtual interfaces report Type=ether but must never be mistaken for
|
||||||
|
// the wired uplink — stale podman/veth links would otherwise poison
|
||||||
|
// ethernet detection.
|
||||||
|
{"veth ether excluded", "veth1234", "ether", false, false},
|
||||||
|
{"podman bridge ether excluded", "podman3", "ether", false, false},
|
||||||
|
{"docker bridge ether excluded", "docker0", "ether", false, false},
|
||||||
// Fallback path: linkType unavailable, name-prefix heuristic applies.
|
// Fallback path: linkType unavailable, name-prefix heuristic applies.
|
||||||
{"fallback enp wired", "enp141s0", "", true, false},
|
{"fallback enp wired", "enp141s0", "", true, false},
|
||||||
{"fallback wlan wireless", "wlan0", "", false, true},
|
{"fallback wlan wireless", "wlan0", "", false, true},
|
||||||
@@ -205,8 +211,46 @@ func TestParseDescribeType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSyncLinks_PrunesRemovedLinks(t *testing.T) {
|
||||||
|
// Stale container interfaces (torn-down podman bridges, veth pairs) must
|
||||||
|
// not linger in the link map after they disappear from ListLinks — kept as
|
||||||
|
// routable, they stole the wired-uplink slot from the real ethernet NIC.
|
||||||
|
backend, _ := NewSystemdNetworkdBackend()
|
||||||
|
backend.links = map[string]*linkInfo{
|
||||||
|
"eno1": {ifindex: 2, name: "eno1", path: "/org/freedesktop/network1/link/_32", linkType: "ether", opState: "routable"},
|
||||||
|
"podman3": {ifindex: 9, name: "podman3", path: "/org/freedesktop/network1/link/_39", linkType: "ether", opState: "routable"},
|
||||||
|
"veth0": {ifindex: 10, name: "veth0", path: "/org/freedesktop/network1/link/_310", linkType: "ether", opState: "routable"},
|
||||||
|
}
|
||||||
|
|
||||||
|
backend.syncLinks([]enumeratedLink{
|
||||||
|
{ifindex: 2, name: "eno1", path: "/org/freedesktop/network1/link/_32"},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Len(t, backend.links, 1)
|
||||||
|
assert.Contains(t, backend.links, "eno1")
|
||||||
|
assert.NotContains(t, backend.links, "podman3")
|
||||||
|
assert.NotContains(t, backend.links, "veth0")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncLinks_RefreshesSurvivingLink(t *testing.T) {
|
||||||
|
// A link that survives keeps its cached Type — Describe is only queried for
|
||||||
|
// newly seen links — while picking up a refreshed ifindex.
|
||||||
|
backend, _ := NewSystemdNetworkdBackend()
|
||||||
|
backend.links = map[string]*linkInfo{
|
||||||
|
"eno1": {ifindex: 2, name: "eno1", path: "/org/freedesktop/network1/link/_32", linkType: "ether"},
|
||||||
|
}
|
||||||
|
|
||||||
|
backend.syncLinks([]enumeratedLink{
|
||||||
|
{ifindex: 7, name: "eno1", path: "/org/freedesktop/network1/link/_32"},
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Len(t, backend.links, 1)
|
||||||
|
assert.Equal(t, int32(7), backend.links["eno1"].ifindex)
|
||||||
|
assert.Equal(t, "ether", backend.links["eno1"].linkType)
|
||||||
|
}
|
||||||
|
|
||||||
func TestLooksVirtual(t *testing.T) {
|
func TestLooksVirtual(t *testing.T) {
|
||||||
virtual := []string{"lo", "docker0", "veth123", "virbr0", "br-abc", "vnet0", "tun0", "tap0", "vboxnet0", "vmnet1", "kube-ipvs0", "cni0", "flannel.1", "cali-abc"}
|
virtual := []string{"lo", "docker0", "veth123", "virbr0", "br-abc", "vnet0", "tun0", "tap0", "vboxnet0", "vmnet1", "kube-ipvs0", "cni0", "flannel.1", "cali-abc", "podman0", "podman3"}
|
||||||
for _, n := range virtual {
|
for _, n := range virtual {
|
||||||
assert.True(t, looksVirtual(n), "%s should look virtual", n)
|
assert.True(t, looksVirtual(n), "%s should look virtual", n)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user