From d1ea70d6884696ca4ff23e791ece3a05ede996a3 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Mon, 20 Apr 2026 14:28:27 +0700 Subject: [PATCH] fix: prevent panic on network change during SetSelfIP SetSelfIP unconditionally accessed t.dhcp, but t.dhcp is only initialized when DHCP discovery is enabled. A network change event can fire SetSelfIP regardless of the discovery configuration, causing a nil pointer dereference. Guard the t.dhcp access with a nil check so the self IP is still updated on the Table even when DHCP discovery is disabled. --- internal/clientinfo/client_info.go | 6 ++- internal/clientinfo/client_info_test.go | 55 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/internal/clientinfo/client_info.go b/internal/clientinfo/client_info.go index f69b670..acae754 100644 --- a/internal/clientinfo/client_info.go +++ b/internal/clientinfo/client_info.go @@ -173,8 +173,10 @@ func (t *Table) SetSelfIP(ip string) { t.selfIPLock.Lock() defer t.selfIPLock.Unlock() t.selfIP = ip - t.dhcp.selfIP = t.selfIP - t.dhcp.addSelf() + if t.dhcp != nil { + t.dhcp.selfIP = t.selfIP + t.dhcp.addSelf() + } } // initSelfDiscover initializes necessary client metadata for self query. diff --git a/internal/clientinfo/client_info_test.go b/internal/clientinfo/client_info_test.go index b5bdfa5..a1e22a6 100644 --- a/internal/clientinfo/client_info_test.go +++ b/internal/clientinfo/client_info_test.go @@ -1,9 +1,64 @@ package clientinfo import ( + "sync" "testing" ) +// TestTable_SetSelfIP_NilDHCP ensures SetSelfIP does not panic when t.dhcp is +// nil, which happens when DHCP discovery is disabled and the network-change +// callback fires before or without initialisation. +func TestTable_SetSelfIP_NilDHCP(t *testing.T) { + table := &Table{} // dhcp is nil + // Must not panic. + table.SetSelfIP("192.168.1.1") + if got := table.SelfIP(); got != "192.168.1.1" { + t.Fatalf("SelfIP() = %q, want %q", got, "192.168.1.1") + } +} + +// TestTable_SetSelfIP_UpdatesDHCP ensures SetSelfIP propagates the new IP to +// the dhcp discover and calls addSelf when dhcp is initialised. +func TestTable_SetSelfIP_UpdatesDHCP(t *testing.T) { + table := &Table{ + dhcp: &dhcp{selfIP: "10.0.0.1"}, + } + table.SetSelfIP("10.0.0.2") + if got := table.SelfIP(); got != "10.0.0.2" { + t.Fatalf("SelfIP() = %q, want %q", got, "10.0.0.2") + } + if table.dhcp.selfIP != "10.0.0.2" { + t.Fatalf("dhcp.selfIP = %q, want %q", table.dhcp.selfIP, "10.0.0.2") + } +} + +// TestTable_SetSelfIP_Concurrent ensures concurrent calls to SetSelfIP do not +// race, regardless of whether dhcp is nil or not. +func TestTable_SetSelfIP_Concurrent(t *testing.T) { + for _, tc := range []struct { + name string + table *Table + }{ + {"nil dhcp", &Table{}}, + {"with dhcp", &Table{dhcp: &dhcp{}}}, + } { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + var wg sync.WaitGroup + for range 10 { + wg.Add(1) + go func() { + defer wg.Done() + tc.table.SetSelfIP("192.168.1.1") + _ = tc.table.SelfIP() + }() + } + wg.Wait() + }) + } +} + func Test_normalizeIP(t *testing.T) { tests := []struct { name string