golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/internal/socket/sys_posix.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows || zos
     6  
     7  package socket
     8  
     9  import (
    10  	"encoding/binary"
    11  	"errors"
    12  	"net"
    13  	"runtime"
    14  	"strconv"
    15  	"sync"
    16  	"time"
    17  )
    18  
    19  // marshalInetAddr writes a in sockaddr format into the buffer b.
    20  // The buffer must be sufficiently large (sizeofSockaddrInet4/6).
    21  // Returns the number of bytes written.
    22  func marshalInetAddr(a net.Addr, b []byte) int {
    23  	switch a := a.(type) {
    24  	case *net.TCPAddr:
    25  		return marshalSockaddr(a.IP, a.Port, a.Zone, b)
    26  	case *net.UDPAddr:
    27  		return marshalSockaddr(a.IP, a.Port, a.Zone, b)
    28  	case *net.IPAddr:
    29  		return marshalSockaddr(a.IP, 0, a.Zone, b)
    30  	default:
    31  		return 0
    32  	}
    33  }
    34  
    35  func marshalSockaddr(ip net.IP, port int, zone string, b []byte) int {
    36  	if ip4 := ip.To4(); ip4 != nil {
    37  		switch runtime.GOOS {
    38  		case "android", "illumos", "linux", "solaris", "windows":
    39  			NativeEndian.PutUint16(b[:2], uint16(sysAF_INET))
    40  		default:
    41  			b[0] = sizeofSockaddrInet4
    42  			b[1] = sysAF_INET
    43  		}
    44  		binary.BigEndian.PutUint16(b[2:4], uint16(port))
    45  		copy(b[4:8], ip4)
    46  		return sizeofSockaddrInet4
    47  	}
    48  	if ip6 := ip.To16(); ip6 != nil && ip.To4() == nil {
    49  		switch runtime.GOOS {
    50  		case "android", "illumos", "linux", "solaris", "windows":
    51  			NativeEndian.PutUint16(b[:2], uint16(sysAF_INET6))
    52  		default:
    53  			b[0] = sizeofSockaddrInet6
    54  			b[1] = sysAF_INET6
    55  		}
    56  		binary.BigEndian.PutUint16(b[2:4], uint16(port))
    57  		copy(b[8:24], ip6)
    58  		if zone != "" {
    59  			NativeEndian.PutUint32(b[24:28], uint32(zoneCache.index(zone)))
    60  		}
    61  		return sizeofSockaddrInet6
    62  	}
    63  	return 0
    64  }
    65  
    66  func parseInetAddr(b []byte, network string) (net.Addr, error) {
    67  	if len(b) < 2 {
    68  		return nil, errors.New("invalid address")
    69  	}
    70  	var af int
    71  	switch runtime.GOOS {
    72  	case "android", "illumos", "linux", "solaris", "windows":
    73  		af = int(NativeEndian.Uint16(b[:2]))
    74  	default:
    75  		af = int(b[1])
    76  	}
    77  	var ip net.IP
    78  	var zone string
    79  	if af == sysAF_INET {
    80  		if len(b) < sizeofSockaddrInet4 {
    81  			return nil, errors.New("short address")
    82  		}
    83  		ip = make(net.IP, net.IPv4len)
    84  		copy(ip, b[4:8])
    85  	}
    86  	if af == sysAF_INET6 {
    87  		if len(b) < sizeofSockaddrInet6 {
    88  			return nil, errors.New("short address")
    89  		}
    90  		ip = make(net.IP, net.IPv6len)
    91  		copy(ip, b[8:24])
    92  		if id := int(NativeEndian.Uint32(b[24:28])); id > 0 {
    93  			zone = zoneCache.name(id)
    94  		}
    95  	}
    96  	switch network {
    97  	case "tcp", "tcp4", "tcp6":
    98  		return &net.TCPAddr{IP: ip, Port: int(binary.BigEndian.Uint16(b[2:4])), Zone: zone}, nil
    99  	case "udp", "udp4", "udp6":
   100  		return &net.UDPAddr{IP: ip, Port: int(binary.BigEndian.Uint16(b[2:4])), Zone: zone}, nil
   101  	default:
   102  		return &net.IPAddr{IP: ip, Zone: zone}, nil
   103  	}
   104  }
   105  
   106  // An ipv6ZoneCache represents a cache holding partial network
   107  // interface information. It is used for reducing the cost of IPv6
   108  // addressing scope zone resolution.
   109  //
   110  // Multiple names sharing the index are managed by first-come
   111  // first-served basis for consistency.
   112  type ipv6ZoneCache struct {
   113  	sync.RWMutex                // guard the following
   114  	lastFetched  time.Time      // last time routing information was fetched
   115  	toIndex      map[string]int // interface name to its index
   116  	toName       map[int]string // interface index to its name
   117  }
   118  
   119  var zoneCache = ipv6ZoneCache{
   120  	toIndex: make(map[string]int),
   121  	toName:  make(map[int]string),
   122  }
   123  
   124  // update refreshes the network interface information if the cache was last
   125  // updated more than 1 minute ago, or if force is set. It returns whether the
   126  // cache was updated.
   127  func (zc *ipv6ZoneCache) update(ift []net.Interface, force bool) (updated bool) {
   128  	zc.Lock()
   129  	defer zc.Unlock()
   130  	now := time.Now()
   131  	if !force && zc.lastFetched.After(now.Add(-60*time.Second)) {
   132  		return false
   133  	}
   134  	zc.lastFetched = now
   135  	if len(ift) == 0 {
   136  		var err error
   137  		if ift, err = net.Interfaces(); err != nil {
   138  			return false
   139  		}
   140  	}
   141  	zc.toIndex = make(map[string]int, len(ift))
   142  	zc.toName = make(map[int]string, len(ift))
   143  	for _, ifi := range ift {
   144  		zc.toIndex[ifi.Name] = ifi.Index
   145  		if _, ok := zc.toName[ifi.Index]; !ok {
   146  			zc.toName[ifi.Index] = ifi.Name
   147  		}
   148  	}
   149  	return true
   150  }
   151  
   152  func (zc *ipv6ZoneCache) name(zone int) string {
   153  	updated := zoneCache.update(nil, false)
   154  	zoneCache.RLock()
   155  	name, ok := zoneCache.toName[zone]
   156  	zoneCache.RUnlock()
   157  	if !ok && !updated {
   158  		zoneCache.update(nil, true)
   159  		zoneCache.RLock()
   160  		name, ok = zoneCache.toName[zone]
   161  		zoneCache.RUnlock()
   162  	}
   163  	if !ok { // last resort
   164  		name = strconv.Itoa(zone)
   165  	}
   166  	return name
   167  }
   168  
   169  func (zc *ipv6ZoneCache) index(zone string) int {
   170  	updated := zoneCache.update(nil, false)
   171  	zoneCache.RLock()
   172  	index, ok := zoneCache.toIndex[zone]
   173  	zoneCache.RUnlock()
   174  	if !ok && !updated {
   175  		zoneCache.update(nil, true)
   176  		zoneCache.RLock()
   177  		index, ok = zoneCache.toIndex[zone]
   178  		zoneCache.RUnlock()
   179  	}
   180  	if !ok { // last resort
   181  		index, _ = strconv.Atoi(zone)
   182  	}
   183  	return index
   184  }