From daae040f9cb2e9dbcbfd03e31d4e899fce2049c0 Mon Sep 17 00:00:00 2001
From: Eugene Bujak <hmage@hmage.net>
Date: Fri, 1 Feb 2019 19:25:04 +0300
Subject: [PATCH] Check if IP:port combinations are possible before returning
 OK on /install/configure

---
 app.go     | 16 +++++++++-------
 control.go | 17 ++++++++++++++---
 helpers.go | 14 ++++++++++++--
 3 files changed, 35 insertions(+), 12 deletions(-)

diff --git a/app.go b/app.go
index 6fa8ef34..71f3ee82 100644
--- a/app.go
+++ b/app.go
@@ -116,14 +116,16 @@ func run(args options) {
 		log.Fatal(err)
 	}
 
-	err = startDNSServer()
-	if err != nil {
-		log.Fatal(err)
-	}
+	if !config.firstRun {
+		err = startDNSServer()
+		if err != nil {
+			log.Fatal(err)
+		}
 
-	err = startDHCPServer()
-	if err != nil {
-		log.Fatal(err)
+		err = startDHCPServer()
+		if err != nil {
+			log.Fatal(err)
+		}
 	}
 
 	// Update filters we've just loaded right away, don't wait for periodic update timer
diff --git a/control.go b/control.go
index 38464a90..1f64ae6c 100644
--- a/control.go
+++ b/control.go
@@ -723,7 +723,7 @@ func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
 	// fill out the fields
 
 	// find out if port 80 is available -- if not, fall back to 3000
-	if checkPortAvailable(80) {
+	if checkPortAvailable("", 80) {
 		data.Web.Port = 80
 	} else {
 		data.Web.Port = 3000
@@ -731,7 +731,7 @@ func handleInstallGetAddresses(w http.ResponseWriter, r *http.Request) {
 
 	// find out if port 53 is available -- if not, show a big warning
 	data.DNS.Port = 53
-	if !checkPortAvailable(53) {
+	if !checkPacketPortAvailable("", 53) {
 		data.DNS.Warning = "Port 53 is not available for binding -- this will make DNS clients unable to contact AdGuard Home."
 	}
 
@@ -764,7 +764,18 @@ func handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
 	}
 
 	spew.Dump(newSettings)
-	// TODO: validate that hosts and ports are bindable
+	// validate that hosts and ports are bindable
+
+	if !checkPortAvailable(newSettings.Web.IP, newSettings.Web.Port) {
+		httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s:%d", newSettings.Web.IP, newSettings.Web.Port)
+		return
+	}
+
+	if !checkPacketPortAvailable(newSettings.DNS.IP, newSettings.DNS.Port) {
+		httpError(w, http.StatusBadRequest, "Impossible to listen on IP:port %s:%d", newSettings.DNS.IP, newSettings.DNS.Port)
+		return
+	}
+
 	config.firstRun = false
 	config.BindHost = newSettings.Web.IP
 	config.BindPort = newSettings.Web.Port
diff --git a/helpers.go b/helpers.go
index f83b9821..9dbd59b7 100644
--- a/helpers.go
+++ b/helpers.go
@@ -12,6 +12,7 @@ import (
 	"path"
 	"path/filepath"
 	"runtime"
+	"strconv"
 	"strings"
 
 	"github.com/hmage/golibs/log"
@@ -259,8 +260,17 @@ func findIPv4IfaceAddr(ifaces []netInterface) string {
 }
 
 // checkPortAvailable is not a cheap test to see if the port is bindable, because it's actually doing the bind momentarily
-func checkPortAvailable(port int) bool {
-	ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
+func checkPortAvailable(host string, port int) bool {
+	ln, err := net.Listen("tcp", net.JoinHostPort(host, strconv.Itoa(port)))
+	if err != nil {
+		return false
+	}
+	ln.Close()
+	return true
+}
+
+func checkPacketPortAvailable(host string, port int) bool {
+	ln, err := net.ListenPacket("udp", net.JoinHostPort(host, strconv.Itoa(port)))
 	if err != nil {
 		return false
 	}