From de837e4eec58763ad96eea2eb9b357336fc0fea3 Mon Sep 17 00:00:00 2001
From: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu, 29 Sep 2022 14:56:37 +0300
Subject: [PATCH] all: use netip for web

---
 internal/aghnet/net.go          | 14 +++++++-------
 internal/aghnet/net_test.go     |  2 +-
 internal/home/config.go         | 11 +++++------
 internal/home/control.go        |  2 +-
 internal/home/controlinstall.go |  6 +++---
 internal/home/dns.go            |  2 +-
 internal/home/home.go           |  2 +-
 internal/home/options.go        | 26 +++++++++++++-------------
 internal/home/options_test.go   | 12 +++++++-----
 internal/home/web.go            |  9 ++-------
 10 files changed, 41 insertions(+), 45 deletions(-)

diff --git a/internal/aghnet/net.go b/internal/aghnet/net.go
index 25133027..910177ac 100644
--- a/internal/aghnet/net.go
+++ b/internal/aghnet/net.go
@@ -130,7 +130,7 @@ func NetInterfaceFrom(iface *net.Interface) (niface *NetInterface, err error) {
 	for _, addr := range addrs {
 		n, ok := addr.(*net.IPNet)
 		if !ok {
-			// Should be net.IPNet, this is weird.
+			// Should be *net.IPNet, this is weird.
 			return nil, fmt.Errorf("expected %[2]s to be %[1]T, got %[2]T", n, addr)
 		} else if ip4 := n.IP.To4(); ip4 != nil {
 			n.IP = ip4
@@ -141,13 +141,13 @@ func NetInterfaceFrom(iface *net.Interface) (niface *NetInterface, err error) {
 			return nil, fmt.Errorf("bad address %s", n.IP)
 		}
 
-		ip = ip.WithZone(iface.Name)
+		if ip.IsLinkLocalUnicast() {
+			// Ignore link-local IPv4.
+			if ip.Is4() {
+				continue
+			}
 
-		// Ignore link-local IPv4.
-		//
-		// TODO(e.burkov):  !! or not??
-		if ip.Is4() && ip.IsLinkLocalUnicast() {
-			continue
+			ip = ip.WithZone(iface.Name)
 		}
 
 		ones, _ := n.Mask.Size()
diff --git a/internal/aghnet/net_test.go b/internal/aghnet/net_test.go
index a8f46fa0..8b4046f4 100644
--- a/internal/aghnet/net_test.go
+++ b/internal/aghnet/net_test.go
@@ -199,7 +199,7 @@ func TestBroadcastFromIPNet(t *testing.T) {
 }
 
 func TestCheckPort(t *testing.T) {
-	laddr := netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 0)
+	laddr := netip.AddrPortFrom(IPv4Localhost(), 0)
 
 	t.Run("tcp_bound", func(t *testing.T) {
 		l, err := net.Listen("tcp", laddr.String())
diff --git a/internal/home/config.go b/internal/home/config.go
index b65949a8..e3629b13 100644
--- a/internal/home/config.go
+++ b/internal/home/config.go
@@ -3,7 +3,6 @@ package home
 import (
 	"bytes"
 	"fmt"
-	"net"
 	"net/netip"
 	"os"
 	"path/filepath"
@@ -86,10 +85,10 @@ type configuration struct {
 	// It's reset after config is parsed
 	fileData []byte
 
-	BindHost     net.IP `yaml:"bind_host"`      // BindHost is the IP address of the HTTP server to bind to
-	BindPort     int    `yaml:"bind_port"`      // BindPort is the port the HTTP server
-	BetaBindPort int    `yaml:"beta_bind_port"` // BetaBindPort is the port for new client
-	Users        []User `yaml:"users"`          // Users that can access HTTP server
+	BindHost     netip.Addr `yaml:"bind_host"`      // BindHost is the IP address of the HTTP server to bind to
+	BindPort     int        `yaml:"bind_port"`      // BindPort is the port the HTTP server
+	BetaBindPort int        `yaml:"beta_bind_port"` // BetaBindPort is the port for new client
+	Users        []User     `yaml:"users"`          // Users that can access HTTP server
 	// AuthAttempts is the maximum number of failed login attempts a user
 	// can do before being blocked.
 	AuthAttempts uint `yaml:"auth_attempts"`
@@ -199,7 +198,7 @@ type tlsConfigSettings struct {
 var config = &configuration{
 	BindPort:           3000,
 	BetaBindPort:       0,
-	BindHost:           net.IP{0, 0, 0, 0},
+	BindHost:           netip.IPv4Unspecified(),
 	AuthAttempts:       5,
 	AuthBlockMin:       15,
 	WebSessionTTLHours: 30 * 24,
diff --git a/internal/home/control.go b/internal/home/control.go
index 4428ae12..fd411bc5 100644
--- a/internal/home/control.go
+++ b/internal/home/control.go
@@ -71,7 +71,7 @@ func appendDNSAddrsWithIfaces(dst []string, src []netip.Addr) (res []string, err
 // on, including the addresses on all interfaces in cases of unspecified IPs.
 func collectDNSAddresses() (addrs []string, err error) {
 	if hosts := config.DNS.BindHosts; len(hosts) == 0 {
-		addr := netip.AddrFrom4([4]byte{127, 0, 0, 1})
+		addr := aghnet.IPv4Localhost()
 
 		addrs = appendDNSAddrs(addrs, addr)
 	} else {
diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go
index 40137377..88acfa7a 100644
--- a/internal/home/controlinstall.go
+++ b/internal/home/controlinstall.go
@@ -406,7 +406,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
 	copyInstallSettings(curConfig, config)
 
 	Context.firstRun = false
-	config.BindHost = req.Web.IP.AsSlice()
+	config.BindHost = req.Web.IP
 	config.BindPort = req.Web.Port
 	config.DNS.BindHosts = []netip.Addr{req.DNS.IP}
 	config.DNS.Port = req.DNS.Port
@@ -439,7 +439,7 @@ func (web *Web) handleInstallConfigure(w http.ResponseWriter, r *http.Request) {
 	}
 
 	web.conf.firstRun = false
-	web.conf.BindHost = req.Web.IP.AsSlice()
+	web.conf.BindHost = req.Web.IP
 	web.conf.BindPort = req.Web.Port
 
 	registerControlHandlers()
@@ -481,7 +481,7 @@ func decodeApplyConfigReq(r io.Reader) (req *applyConfigReq, restartHTTP bool, e
 		return nil, false, errors.Error("ports cannot be 0")
 	}
 
-	restartHTTP = !config.BindHost.Equal(req.Web.IP.AsSlice()) || config.BindPort != req.Web.Port
+	restartHTTP = config.BindHost != req.Web.IP || config.BindPort != req.Web.Port
 	if restartHTTP {
 		err = aghnet.CheckPort("tcp", netip.AddrPortFrom(req.Web.IP, uint16(req.Web.Port)))
 		if err != nil {
diff --git a/internal/home/dns.go b/internal/home/dns.go
index 7f89a052..d4dd3d50 100644
--- a/internal/home/dns.go
+++ b/internal/home/dns.go
@@ -195,7 +195,7 @@ func generateServerConfig() (newConf dnsforward.ServerConfig, err error) {
 	dnsConf := config.DNS
 	hosts := dnsConf.BindHosts
 	if len(hosts) == 0 {
-		hosts = []netip.Addr{netip.AddrFrom4([4]byte{127, 0, 0, 1})}
+		hosts = []netip.Addr{aghnet.IPv4Localhost()}
 	}
 
 	newConf = dnsforward.ServerConfig{
diff --git a/internal/home/home.go b/internal/home/home.go
index c49142d7..1d418712 100644
--- a/internal/home/home.go
+++ b/internal/home/home.go
@@ -340,7 +340,7 @@ func setupConfig(args options) (err error) {
 	}
 
 	// override bind host/port from the console
-	if args.bindHost != nil {
+	if args.bindHost.IsValid() {
 		config.BindHost = args.bindHost
 	}
 	if len(args.pidFile) != 0 && writePIDFile(args.pidFile) {
diff --git a/internal/home/options.go b/internal/home/options.go
index 6f5a4d8d..b8410442 100644
--- a/internal/home/options.go
+++ b/internal/home/options.go
@@ -2,7 +2,7 @@ package home
 
 import (
 	"fmt"
-	"net"
+	"net/netip"
 	"os"
 	"strconv"
 
@@ -12,15 +12,15 @@ import (
 
 // options passed from command-line arguments
 type options struct {
-	verbose        bool   // is verbose logging enabled
-	configFilename string // path to the config file
-	workDir        string // path to the working directory where we will store the filters data and the querylog
-	bindHost       net.IP // host address to bind HTTP server on
-	bindPort       int    // port to serve HTTP pages on
-	logFile        string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
-	pidFile        string // File name to save PID to
-	checkConfig    bool   // Check configuration and exit
-	disableUpdate  bool   // If set, don't check for updates
+	verbose        bool       // is verbose logging enabled
+	configFilename string     // path to the config file
+	workDir        string     // path to the working directory where we will store the filters data and the querylog
+	bindHost       netip.Addr // host address to bind HTTP server on
+	bindPort       int        // port to serve HTTP pages on
+	logFile        string     // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
+	pidFile        string     // File name to save PID to
+	checkConfig    bool       // Check configuration and exit
+	disableUpdate  bool       // If set, don't check for updates
 
 	// service control action (see service.ControlAction array + "status" command)
 	serviceControlAction string
@@ -60,8 +60,8 @@ type arg struct {
 // against its zero value and return nil if the parameter value is
 // zero otherwise they return a string slice of the parameter
 
-func ipSliceOrNil(ip net.IP) []string {
-	if ip == nil {
+func ipSliceOrNil(ip netip.Addr) []string {
+	if !ip.IsValid() {
 		return nil
 	}
 
@@ -113,7 +113,7 @@ var workDirArg = arg{
 var hostArg = arg{
 	"Host address to bind HTTP server on.",
 	"host", "h",
-	func(o options, v string) (options, error) { o.bindHost = net.ParseIP(v); return o, nil }, nil, nil,
+	func(o options, v string) (options, error) { o.bindHost, _ = netip.ParseAddr(v); return o, nil }, nil, nil,
 	func(o options) []string { return ipSliceOrNil(o.bindHost) },
 }
 
diff --git a/internal/home/options_test.go b/internal/home/options_test.go
index 21972b0a..bc35a899 100644
--- a/internal/home/options_test.go
+++ b/internal/home/options_test.go
@@ -2,7 +2,7 @@ package home
 
 import (
 	"fmt"
-	"net"
+	"net/netip"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -56,11 +56,13 @@ func TestParseWorkDir(t *testing.T) {
 }
 
 func TestParseBindHost(t *testing.T) {
-	assert.Nil(t, testParseOK(t).bindHost, "empty is not host")
-	assert.Equal(t, net.IPv4(1, 2, 3, 4), testParseOK(t, "-h", "1.2.3.4").bindHost, "-h is host")
+	wantAddr := netip.AddrFrom4([4]byte{1, 2, 3, 4})
+
+	assert.Zero(t, testParseOK(t).bindHost, "empty is not host")
+	assert.Equal(t, wantAddr, testParseOK(t, "-h", "1.2.3.4").bindHost, "-h is host")
 	testParseParamMissing(t, "-h")
 
-	assert.Equal(t, net.IPv4(1, 2, 3, 4), testParseOK(t, "--host", "1.2.3.4").bindHost, "--host is host")
+	assert.Equal(t, wantAddr, testParseOK(t, "--host", "1.2.3.4").bindHost, "--host is host")
 	testParseParamMissing(t, "--host")
 }
 
@@ -149,7 +151,7 @@ func TestSerialize(t *testing.T) {
 		ss:   []string{"-w", "path"},
 	}, {
 		name: "bind_host",
-		opts: options{bindHost: net.IP{1, 2, 3, 4}},
+		opts: options{bindHost: netip.AddrFrom4([4]byte{1, 2, 3, 4})},
 		ss:   []string{"-h", "1.2.3.4"},
 	}, {
 		name: "bind_port",
diff --git a/internal/home/web.go b/internal/home/web.go
index d379abe7..feef33e7 100644
--- a/internal/home/web.go
+++ b/internal/home/web.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"crypto/tls"
 	"io/fs"
-	"net"
 	"net/http"
 	"net/netip"
 	"sync"
@@ -36,8 +35,7 @@ type webConfig struct {
 	clientFS     fs.FS
 	clientBetaFS fs.FS
 
-	// TODO(e.burkov):  !! use netip
-	BindHost     net.IP
+	BindHost     netip.Addr
 	BindPort     int
 	BetaBindPort int
 	PortHTTPS    int
@@ -120,10 +118,7 @@ func WebCheckPortAvailable(port int) bool {
 		return true
 	}
 
-	// TODO(e.burkov):  !! use netip
-	addr, ok := netip.AddrFromSlice(config.BindHost)
-
-	return ok && aghnet.CheckPort("tcp", netip.AddrPortFrom(addr, uint16(port))) == nil
+	return aghnet.CheckPort("tcp", netip.AddrPortFrom(config.BindHost, uint16(port))) == nil
 }
 
 // TLSConfigChanged updates the TLS configuration and restarts the HTTPS server