2024-01-22 19:48:12 +02:00
package client
import (
"fmt"
"net/url"
"strconv"
"strings"
"time"
"github.com/hashicorp/mdns"
2025-09-18 14:29:48 +03:00
"github.com/kairos-io/kairos-sdk/types"
2024-01-22 19:48:12 +02:00
)
const (
MDNSServiceType = "_kcrypt._tcp"
MDNSTimeout = 15 * time . Second
)
// queryMDNS will make an mdns query on local network to find a kcrypt challenger server
// instance. If none is found, the original URL is returned and no additional headers.
// If a response is received, the IP address and port from the response will be returned// and an additional "Host" header pointing to the original host.
2025-09-18 14:29:48 +03:00
func queryMDNS ( originalURL string , logger types . KairosLogger ) ( string , map [ string ] string , error ) {
2024-01-22 19:48:12 +02:00
additionalHeaders := map [ string ] string { }
var err error
parsedURL , err := url . Parse ( originalURL )
if err != nil {
return originalURL , additionalHeaders , fmt . Errorf ( "parsing the original host: %w" , err )
}
host := parsedURL . Host
if ! strings . HasSuffix ( host , ".local" ) { // sanity check
return "" , additionalHeaders , fmt . Errorf ( "domain should end in \".local\" when using mdns" )
}
2025-09-18 14:29:48 +03:00
mdnsIP , mdnsPort := discoverMDNSServer ( host , logger )
2024-01-22 19:48:12 +02:00
if mdnsIP == "" { // no reply
2025-09-18 14:29:48 +03:00
logger . Debugf ( "no reply from mdns" )
2024-01-22 19:48:12 +02:00
return originalURL , additionalHeaders , nil
}
additionalHeaders [ "Host" ] = parsedURL . Host
newURL := strings . ReplaceAll ( originalURL , host , mdnsIP )
// Remove any port in the original url
if port := parsedURL . Port ( ) ; port != "" {
newURL = strings . ReplaceAll ( newURL , port , "" )
}
// Add any possible port from the mdns response
if mdnsPort != "" {
newURL = strings . ReplaceAll ( newURL , mdnsIP , fmt . Sprintf ( "%s:%s" , mdnsIP , mdnsPort ) )
}
return newURL , additionalHeaders , nil
}
// discoverMDNSServer performs an mDNS query to discover any running kcrypt challenger
// servers on the same network that matches the given hostname.
// If a response if received, the IP address and the Port from the response are returned.
2025-09-18 14:29:48 +03:00
func discoverMDNSServer ( hostname string , logger types . KairosLogger ) ( string , string ) {
2024-01-22 19:48:12 +02:00
// Make a channel for results and start listening
entriesCh := make ( chan * mdns . ServiceEntry , 4 )
defer close ( entriesCh )
2025-09-18 14:29:48 +03:00
logger . Debugf ( "Will now wait for some mdns server to respond" )
2024-01-22 19:48:12 +02:00
// Start the lookup. It will block until we read from the chan.
mdns . Lookup ( MDNSServiceType , entriesCh )
expectedHost := hostname + "." // FQDN
// Wait until a matching server is found or we reach a timeout
for {
select {
case entry := <- entriesCh :
2025-09-18 14:29:48 +03:00
logger . Debugf ( "mdns response received" )
2024-01-22 19:48:12 +02:00
if entry . Host == expectedHost {
2025-09-18 14:29:48 +03:00
logger . Debugf ( "%s matches %s" , entry . Host , expectedHost )
2024-01-22 19:48:12 +02:00
return entry . AddrV4 . String ( ) , strconv . Itoa ( entry . Port ) // TODO: v6?
} else {
2025-09-18 14:29:48 +03:00
logger . Debugf ( "%s didn't match %s" , entry . Host , expectedHost )
2024-01-22 19:48:12 +02:00
}
case <- time . After ( MDNSTimeout ) :
2025-09-18 14:29:48 +03:00
logger . Debugf ( "timed out waiting for mdns" )
2024-01-22 19:48:12 +02:00
return "" , ""
}
}
}