github.com/binkynet/BinkyNet@v1.12.1-0.20240421190447-da4e34c20be0/apis/v1/serviceinfo.go (about)

     1  // Copyright 2020 Ewout Prangsma
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // Author Ewout Prangsma
    16  //
    17  
    18  package v1
    19  
    20  import (
    21  	context "context"
    22  	"crypto/sha1"
    23  	fmt "fmt"
    24  	"io/ioutil"
    25  	"net"
    26  	"os"
    27  	"runtime"
    28  	"sort"
    29  	"strconv"
    30  	"strings"
    31  
    32  	"github.com/grandcat/zeroconf"
    33  )
    34  
    35  const (
    36  	textVersion    = "version"
    37  	textAPIVersion = "api_version"
    38  	textSecure     = "secure"
    39  )
    40  
    41  // ParseServiceInfo parses a ServiceEntry into a ServiceInfo message.
    42  func ParseServiceInfo(se *zeroconf.ServiceEntry) (*ServiceInfo, error) {
    43  	result := &ServiceInfo{
    44  		ApiPort:    int32(se.Port),
    45  		ApiAddress: se.HostName,
    46  		Secure:     true,
    47  	}
    48  	if len(se.AddrIPv4) > 0 {
    49  		result.ApiAddress = se.AddrIPv4[0].String()
    50  	} else if len(se.AddrIPv6) > 0 {
    51  		result.ApiAddress = se.AddrIPv6[0].String()
    52  	}
    53  	for _, t := range se.Text {
    54  		parts := strings.SplitN(t, "=", 2)
    55  		if len(parts) != 2 {
    56  			continue
    57  		}
    58  		switch parts[0] {
    59  		case textVersion:
    60  			result.Version = parts[1]
    61  		case textAPIVersion:
    62  			result.ApiVersion = parts[1]
    63  		case textSecure:
    64  			if b, err := strconv.ParseBool(parts[1]); err == nil {
    65  				result.Secure = b
    66  			}
    67  		}
    68  	}
    69  	return result, nil
    70  }
    71  
    72  // RegisterServiceEntry runs a registration service for the given service entry
    73  // until the given context is canceled.
    74  func RegisterServiceEntry(ctx context.Context, serviceType string, info ServiceInfo) error {
    75  	text := []string{
    76  		textVersion + "=" + info.GetVersion(),
    77  		textAPIVersion + "=" + info.GetApiVersion(),
    78  		textSecure + "=" + strconv.FormatBool(info.GetSecure()),
    79  	}
    80  	instance, err := os.Hostname()
    81  	if err != nil {
    82  		instance, err = CreateHostID()
    83  		if err != nil {
    84  			return err
    85  		}
    86  	}
    87  
    88  	var ifaces []net.Interface
    89  	if host, found := GetServiceInfoHost(ctx); found && host != "" {
    90  		// Find interfaces with given IP
    91  		var selectedIFaces []net.Interface
    92  		allInterfaces, err := net.Interfaces()
    93  		if err != nil {
    94  			return err
    95  		}
    96  		for _, ifi := range allInterfaces {
    97  			if (ifi.Flags & net.FlagUp) == 0 {
    98  				continue
    99  			}
   100  			if (ifi.Flags & net.FlagMulticast) == 0 {
   101  				continue
   102  			}
   103  			addrs, _ := ifi.Addrs()
   104  			for _, addr := range addrs {
   105  				addrStr := addr.String()
   106  				if addrStr == host || strings.HasPrefix(addrStr, host+"/") {
   107  					selectedIFaces = append(selectedIFaces, ifi)
   108  					break
   109  				}
   110  			}
   111  		}
   112  		if len(selectedIFaces) == 0 && host != "0.0.0.0" {
   113  			return fmt.Errorf("Did not find interface with address '%s'", host)
   114  		}
   115  		ifaces = selectedIFaces
   116  	}
   117  
   118  	srv, err := zeroconf.Register(instance, serviceType, "local.", int(info.GetApiPort()), text, ifaces)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	<-ctx.Done()
   123  	srv.Shutdown()
   124  	return nil
   125  }
   126  
   127  // CreateHostID creates a host ID based on network hardware addresses.
   128  func CreateHostID() (string, error) {
   129  	if content, err := ioutil.ReadFile("/etc/machine-id"); err == nil {
   130  		content = []byte(strings.TrimSpace(string(content)))
   131  		id := fmt.Sprintf("%x", sha1.Sum(content))
   132  		return id[:10], nil
   133  	}
   134  
   135  	ifs, err := net.Interfaces()
   136  	if err != nil {
   137  		return "", err
   138  	}
   139  	list := make([]string, 0, len(ifs))
   140  	for _, v := range ifs {
   141  		f := v.Flags
   142  		if f&net.FlagUp != 0 && f&net.FlagLoopback == 0 {
   143  			fmt.Printf("Using intf %s with addr %s\n", v.Name, v.HardwareAddr.String())
   144  			h := v.HardwareAddr.String()
   145  			if len(h) > 0 {
   146  				list = append(list, h)
   147  			}
   148  		}
   149  	}
   150  	sort.Strings(list) // sort host IDs
   151  	list = append(list, runtime.GOOS, runtime.GOARCH)
   152  	data := []byte(strings.Join(list, ","))
   153  	id := fmt.Sprintf("%x", sha1.Sum(data))
   154  	return id[:10], nil
   155  }