fix: Add IPv6 handling to handlers.FailSaveAddress()

On IPv6-only deployment I encountered two problem with collaboration service
and CheckHandler:
* getOutBoundIP() did not allow IPv6 addresses to be used as outbound
* FailSaveAddress() did double interpolation of the "::" literal on
  GUA IPv6 addresses containing "::"
This commit is contained in:
oidq
2025-11-10 14:13:05 +01:00
parent 173347d9e7
commit 356e98e2bf
3 changed files with 81 additions and 16 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"time"
@@ -11,21 +12,26 @@ import (
)
// NewHTTPCheck checks the reachability of a http server.
func NewHTTPCheck(url string) func(context.Context) error {
func NewHTTPCheck(rawUrl string) func(context.Context) error {
return func(_ context.Context) error {
url, err := handlers.FailSaveAddress(url)
if !strings.HasPrefix(rawUrl, "http://") && !strings.HasPrefix(rawUrl, "https://") {
rawUrl = "http://" + rawUrl
}
parsedUrl, err := url.Parse(rawUrl)
if err != nil {
return err
}
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
url = "http://" + url
parsedUrl.Host, err = handlers.FailSaveAddress(parsedUrl.Host)
if err != nil {
return err
}
c := http.Client{
Timeout: 3 * time.Second,
}
resp, err := c.Get(url)
resp, err := c.Get(parsedUrl.String())
if err != nil {
return fmt.Errorf("could not connect to http server: %v", err)
}

View File

@@ -115,34 +115,75 @@ func (h *CheckHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
// FailSaveAddress replaces wildcard addresses with the outbound IP.
// FailSaveAddress replaces unspecified addresses with the outbound IP.
func FailSaveAddress(address string) (string, error) {
if strings.Contains(address, "0.0.0.0") || strings.Contains(address, "::") {
outboundIp, err := getOutBoundIP()
host, port := SplitHostPort(address)
hostIP := net.ParseIP(host)
if host == "" || (hostIP != nil && hostIP.IsUnspecified()) {
outboundIP, err := getOutBoundIP()
if err != nil {
return "", err
}
address = strings.Replace(address, "0.0.0.0", outboundIp, 1)
address = strings.Replace(address, "::", "["+outboundIp+"]", 1)
address = strings.Replace(address, "[::]", "["+outboundIp+"]", 1)
host = outboundIP.String()
}
return address, nil
if port != "" {
if strings.Contains(host, ":") {
host = "[" + host + "]"
}
return host + ":" + port, nil
}
return host, nil
}
// SplitHostPort returns host and port of the address.
// Contrary to the net.SplitHostPort the port is not mandatory.
func SplitHostPort(address string) (string, string) {
columns := strings.Split(address, ":")
brackets := strings.Split(address, "]")
switch {
case len(columns) == 1 && len(brackets) == 1: // 10.10.10.10
return address, ""
case len(columns) == 2 && len(brackets) == 1: // 10.10.10.10:80
return columns[0], columns[1]
case len(columns) > 2 && len(brackets) == 1: // 2a01::a
return address, ""
case len(brackets) == 2 && brackets[1] == "": // [2a01::a]
return brackets[0][1:], ""
case len(brackets) == 2: // [2a01::a]:10
return brackets[0][1:], columns[len(columns)-1]
}
return address, ""
}
// getOutBoundIP returns the outbound IP address.
func getOutBoundIP() (string, error) {
func getOutBoundIP() (net.IP, error) {
interfacesAddresses, err := net.InterfaceAddrs()
if err != nil {
return "", err
return nil, err
}
var fallbackIpv6 net.IP
for _, address := range interfacesAddresses {
if ipNet, ok := address.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
if ipNet.IP.To4() != nil {
return ipNet.IP.String(), nil
return ipNet.IP, nil
}
if ipNet.IP.To16() != nil && !ipNet.IP.IsLinkLocalUnicast() {
fallbackIpv6 = ipNet.IP.To16()
}
}
}
return "", fmt.Errorf("no IP found")
if fallbackIpv6 != nil {
return fallbackIpv6, nil
}
return nil, fmt.Errorf("no IP found")
}

View File

@@ -125,3 +125,21 @@ func TestCheckHandler(t *testing.T) {
require.Equal(t, 2, len(slices.DeleteFunc(errs, func(err error) bool { return err != nil })))
})
}
func TestSplitHostPort(t *testing.T) {
address, port := handlers.SplitHostPort("10.10.10.10")
require.Equal(t, address, "10.10.10.10")
require.Equal(t, port, "")
address, port = handlers.SplitHostPort("10.10.10.10:20")
require.Equal(t, address, "10.10.10.10")
require.Equal(t, port, "20")
address, port = handlers.SplitHostPort("2a01::1")
require.Equal(t, address, "2a01::1")
require.Equal(t, port, "")
address, port = handlers.SplitHostPort("[2a01::1]:10")
require.Equal(t, address, "2a01::1")
require.Equal(t, port, "10")
}