From d9fb6a37885bfdf2163d66c72c989bd0c88d8210 Mon Sep 17 00:00:00 2001
From: cyBerta <cyberta@riseup.net>
Date: Mon, 24 Feb 2025 12:57:17 +0100
Subject: [PATCH] fix nilpointer dereference in server hopping mode, in case
 ports obvpn wants to listen to are already bound. Allow 10% failure rate for
 failing port bindings for now, clients should hop to next IP:port tuple if
 dialing fails.

---
 server/udpserver.go | 16 +++++++++++++++-
 1 file changed, 15 insertions(+), 1 deletion(-)

diff --git a/server/udpserver.go b/server/udpserver.go
index 70de25b..af7d368 100644
--- a/server/udpserver.go
+++ b/server/udpserver.go
@@ -55,14 +55,28 @@ func (s *UDPServer) Start() error {
 	if s.cfg.HoppingEnabled {
 		listeners = make([]net.Listener, s.cfg.PortCount)
 		portHopRange := int(s.cfg.MaxHopPort - s.cfg.MinHopPort)
+		listenerCount := 0
 		for i := 0; i < int(s.cfg.PortCount); i++ {
 			portOffset := r.Intn(portHopRange)
 			addr := net.JoinHostPort(s.cfg.Obfs4ListenIP, fmt.Sprint(portOffset+int(s.cfg.MinHopPort)))
-			listeners[i], err = listenConfig.Listen(s.ctx, addr)
+			listener, err := listenConfig.Listen(s.ctx, addr)
 
 			if err != nil {
 				s.logger.Printf("Error binding to %s: %v", addr, err)
+				continue
 			}
+			listeners[listenerCount] = listener
+			listenerCount++
+		}
+
+		if listenerCount < int(float64(s.cfg.PortCount)*0.9) {
+			// if too many of the required ports are already occupied, clients will suffer from
+			// bad throughput due to many dialing errors
+			return fmt.Errorf("too many port binding errors: %d/%d", len(listeners), int(s.cfg.PortCount))
+		} else if listenerCount < int(s.cfg.PortCount) {
+			// adjust the listener slice size so that we avoid nil entries
+			listeners = listeners[:listenerCount]
+			s.debug.Printf("WARNING: listening on less ports then clients expect: %d / %d", len(listeners), int(s.cfg.PortCount))
 		}
 	} else {
 		listenAddr := net.JoinHostPort(s.cfg.Obfs4ListenIP, strconv.Itoa(s.cfg.Obfs4ListenPort))
-- 
GitLab