| // Copyright 2017 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows || zos | |
| package socket | |
| import ( | |
| "encoding/binary" | |
| "errors" | |
| "net" | |
| "runtime" | |
| "strconv" | |
| "sync" | |
| "time" | |
| ) | |
| // marshalInetAddr writes a in sockaddr format into the buffer b. | |
| // The buffer must be sufficiently large (sizeofSockaddrInet4/6). | |
| // Returns the number of bytes written. | |
| func marshalInetAddr(a net.Addr, b []byte) int { | |
| switch a := a.(type) { | |
| case *net.TCPAddr: | |
| return marshalSockaddr(a.IP, a.Port, a.Zone, b) | |
| case *net.UDPAddr: | |
| return marshalSockaddr(a.IP, a.Port, a.Zone, b) | |
| case *net.IPAddr: | |
| return marshalSockaddr(a.IP, 0, a.Zone, b) | |
| default: | |
| return 0 | |
| } | |
| } | |
| func marshalSockaddr(ip net.IP, port int, zone string, b []byte) int { | |
| if ip4 := ip.To4(); ip4 != nil { | |
| switch runtime.GOOS { | |
| case "android", "illumos", "linux", "solaris", "windows": | |
| binary.NativeEndian.PutUint16(b[:2], uint16(sysAF_INET)) | |
| default: | |
| b[0] = sizeofSockaddrInet4 | |
| b[1] = sysAF_INET | |
| } | |
| binary.BigEndian.PutUint16(b[2:4], uint16(port)) | |
| copy(b[4:8], ip4) | |
| return sizeofSockaddrInet4 | |
| } | |
| if ip6 := ip.To16(); ip6 != nil && ip.To4() == nil { | |
| switch runtime.GOOS { | |
| case "android", "illumos", "linux", "solaris", "windows": | |
| binary.NativeEndian.PutUint16(b[:2], uint16(sysAF_INET6)) | |
| default: | |
| b[0] = sizeofSockaddrInet6 | |
| b[1] = sysAF_INET6 | |
| } | |
| binary.BigEndian.PutUint16(b[2:4], uint16(port)) | |
| copy(b[8:24], ip6) | |
| if zone != "" { | |
| binary.NativeEndian.PutUint32(b[24:28], uint32(zoneCache.index(zone))) | |
| } | |
| return sizeofSockaddrInet6 | |
| } | |
| return 0 | |
| } | |
| func parseInetAddr(b []byte, network string) (net.Addr, error) { | |
| if len(b) < 2 { | |
| return nil, errors.New("invalid address") | |
| } | |
| var af int | |
| switch runtime.GOOS { | |
| case "android", "illumos", "linux", "solaris", "windows": | |
| af = int(binary.NativeEndian.Uint16(b[:2])) | |
| default: | |
| af = int(b[1]) | |
| } | |
| var ip net.IP | |
| var zone string | |
| if af == sysAF_INET { | |
| if len(b) < sizeofSockaddrInet4 { | |
| return nil, errors.New("short address") | |
| } | |
| ip = make(net.IP, net.IPv4len) | |
| copy(ip, b[4:8]) | |
| } | |
| if af == sysAF_INET6 { | |
| if len(b) < sizeofSockaddrInet6 { | |
| return nil, errors.New("short address") | |
| } | |
| ip = make(net.IP, net.IPv6len) | |
| copy(ip, b[8:24]) | |
| if id := int(binary.NativeEndian.Uint32(b[24:28])); id > 0 { | |
| zone = zoneCache.name(id) | |
| } | |
| } | |
| switch network { | |
| case "tcp", "tcp4", "tcp6": | |
| return &net.TCPAddr{IP: ip, Port: int(binary.BigEndian.Uint16(b[2:4])), Zone: zone}, nil | |
| case "udp", "udp4", "udp6": | |
| return &net.UDPAddr{IP: ip, Port: int(binary.BigEndian.Uint16(b[2:4])), Zone: zone}, nil | |
| default: | |
| return &net.IPAddr{IP: ip, Zone: zone}, nil | |
| } | |
| } | |
| // An ipv6ZoneCache represents a cache holding partial network | |
| // interface information. It is used for reducing the cost of IPv6 | |
| // addressing scope zone resolution. | |
| // | |
| // Multiple names sharing the index are managed by first-come | |
| // first-served basis for consistency. | |
| type ipv6ZoneCache struct { | |
| sync.RWMutex // guard the following | |
| lastFetched time.Time // last time routing information was fetched | |
| toIndex map[string]int // interface name to its index | |
| toName map[int]string // interface index to its name | |
| } | |
| var zoneCache = ipv6ZoneCache{ | |
| toIndex: make(map[string]int), | |
| toName: make(map[int]string), | |
| } | |
| // update refreshes the network interface information if the cache was last | |
| // updated more than 1 minute ago, or if force is set. It returns whether the | |
| // cache was updated. | |
| func (zc *ipv6ZoneCache) update(ift []net.Interface, force bool) (updated bool) { | |
| zc.Lock() | |
| defer zc.Unlock() | |
| now := time.Now() | |
| if !force && zc.lastFetched.After(now.Add(-60*time.Second)) { | |
| return false | |
| } | |
| zc.lastFetched = now | |
| if len(ift) == 0 { | |
| var err error | |
| if ift, err = net.Interfaces(); err != nil { | |
| return false | |
| } | |
| } | |
| zc.toIndex = make(map[string]int, len(ift)) | |
| zc.toName = make(map[int]string, len(ift)) | |
| for _, ifi := range ift { | |
| zc.toIndex[ifi.Name] = ifi.Index | |
| if _, ok := zc.toName[ifi.Index]; !ok { | |
| zc.toName[ifi.Index] = ifi.Name | |
| } | |
| } | |
| return true | |
| } | |
| func (zc *ipv6ZoneCache) name(zone int) string { | |
| updated := zoneCache.update(nil, false) | |
| zoneCache.RLock() | |
| name, ok := zoneCache.toName[zone] | |
| zoneCache.RUnlock() | |
| if !ok && !updated { | |
| zoneCache.update(nil, true) | |
| zoneCache.RLock() | |
| name, ok = zoneCache.toName[zone] | |
| zoneCache.RUnlock() | |
| } | |
| if !ok { // last resort | |
| name = strconv.Itoa(zone) | |
| } | |
| return name | |
| } | |
| func (zc *ipv6ZoneCache) index(zone string) int { | |
| updated := zoneCache.update(nil, false) | |
| zoneCache.RLock() | |
| index, ok := zoneCache.toIndex[zone] | |
| zoneCache.RUnlock() | |
| if !ok && !updated { | |
| zoneCache.update(nil, true) | |
| zoneCache.RLock() | |
| index, ok = zoneCache.toIndex[zone] | |
| zoneCache.RUnlock() | |
| } | |
| if !ok { // last resort | |
| index, _ = strconv.Atoi(zone) | |
| } | |
| return index | |
| } |