| // Copyright 2016 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. |
| |
| package net |
| |
| import ( |
| "context" |
| "errors" |
| "os" |
| "sync" |
| "syscall" |
| "syscall/zx" |
| "syscall/zx/fdio" |
| "syscall/zx/fidl" |
| "syscall/zx/mxnet" |
| zxnet "syscall/zx/net" |
| "syscall/zx/zxsocket" |
| "time" |
| ) |
| |
| func probeIPv4Stack() bool { |
| return true |
| } |
| |
| func probeIPv6Stack() (supportsIPv6, supportsIPv4map bool) { |
| return true, true |
| } |
| |
| // A sockaddr represents a TCP, UDP, IP or Unix network endpoint |
| // address that can be converted into an fdio sockaddr message. |
| type sockaddr interface { |
| Addr |
| |
| family() int |
| isWildcard() bool |
| sockaddr(family int) (addr mxnet.Addr, port uint16, err error) |
| } |
| |
| func favoriteAddrFamily(net string, laddr, raddr sockaddr, mode string) (family int, ipv6only bool) { |
| switch net[len(net)-1] { |
| case '4': |
| return syscall.AF_INET, false |
| case '6': |
| return syscall.AF_INET6, true |
| } |
| |
| if mode == "listen" && (laddr == nil || laddr.isWildcard()) { |
| if supportsIPv4map || !supportsIPv4 { |
| return syscall.AF_INET6, false |
| } |
| if laddr == nil { |
| return syscall.AF_INET, false |
| } |
| return laddr.family(), false |
| } |
| |
| if (laddr == nil || laddr.family() == syscall.AF_INET) && |
| (raddr == nil || raddr.family() == syscall.AF_INET) { |
| return syscall.AF_INET, false |
| } |
| return syscall.AF_INET6, false |
| } |
| |
| type socketProviderInfo struct { |
| mu sync.Mutex |
| h zx.Handle |
| } |
| |
| var socketProvider socketProviderInfo |
| |
| func getSocketProvider() zx.Handle { |
| socketProvider.mu.Lock() |
| defer socketProvider.mu.Unlock() |
| if socketProvider.h == zx.HandleInvalid { |
| c0, c1, err := zx.NewChannel(0) |
| if err == nil { |
| err = fdio.ServiceConnect("/svc/fuchsia.net.LegacySocketProvider", zx.Handle(c0)) |
| if err == nil { |
| socketProvider.h = zx.Handle(c1) |
| } |
| } |
| } |
| return socketProvider.h |
| } |
| |
| func dialFuchsia(ctx context.Context, net string, laddr, raddr sockaddr) (fd *netFD, err error) { |
| family, _ := favoriteAddrFamily(net, laddr, raddr, "dial") |
| |
| sotype := syscall.SOCK_STREAM |
| if len(net) >= 3 && net[:3] == "udp" { |
| sotype = syscall.SOCK_DGRAM |
| } |
| proto := syscall.IPPROTO_IP |
| |
| var m fdio.FDIO |
| // Wait for the network stack to publish the socket device. |
| // See similar logic in zircon/system/ulib/fdio/bsdsocket.c. |
| for i := 0; i < 40; i++ { |
| c := zx.Channel(getSocketProvider()) |
| if c.Handle().IsValid() { |
| sp := zxnet.LegacySocketProviderInterface(fidl.Proxy{Channel: c}) |
| sock, _, err := sp.OpenSocket(zxnet.SocketDomain(family), zxnet.SocketType(sotype), zxnet.SocketProtocol(proto)) |
| if err == nil { |
| m, _ = zxsocket.NewSocket(*sock.Handle()) |
| break |
| } |
| } |
| time.Sleep(250 * time.Millisecond) |
| } |
| if err != nil { |
| return nil, err |
| } |
| if sotype == syscall.SOCK_DGRAM { |
| zxsocket.SetDgram(m) |
| } |
| |
| fd = &netFD{ |
| hsrc: os.NewFile(uintptr(syscall.OpenFDIO(m)), "socket"), |
| m: m, |
| family: family, |
| sotype: sotype, |
| net: net, |
| } |
| if laddr != nil { |
| addr, port, err := laddr.sockaddr(family) |
| if err != nil { |
| return nil, err |
| } |
| if addr != "" || port != 0 { |
| if err := zxsocket.Bind(fd.m, addr, port); err != nil { |
| return nil, err |
| } |
| } |
| if raddr == nil { |
| switch sotype { |
| case syscall.SOCK_STREAM: |
| if err := zxsocket.ListenStream(m, listenerBacklog); err != nil { |
| return nil, err |
| } |
| case syscall.SOCK_DGRAM: |
| return nil, errors.New("net: TODO listen datagram") |
| } |
| } |
| } |
| if raddr != nil { |
| addr, port, err := raddr.sockaddr(family) |
| if err != nil { |
| return nil, err |
| } |
| if err := zxsocket.Connect(fd.m, addr, port); err != nil { |
| return nil, err |
| } |
| fd.isConnected = true |
| } |
| fd.setAddr() |
| |
| return fd, nil |
| } |
| |
| func ipToSockaddr(family int, ip IP, port int, zone string) (addr mxnet.Addr, portres uint16, err error) { |
| switch family { |
| case syscall.AF_INET: |
| if len(ip) == 0 { |
| ip = IPv4zero |
| } |
| ip4 := ip.To4() |
| if ip4 == nil { |
| return "", 0, &AddrError{Err: "non-IPv4 address", Addr: ip.String()} |
| } |
| return mxnet.Addr(ip4)[:4], uint16(port), nil |
| case syscall.AF_INET6: |
| if len(ip) == 0 || ip.Equal(IPv4zero) { |
| ip = IPv6zero |
| } |
| ip6 := ip.To16() |
| if ip6 == nil { |
| return "", 0, &AddrError{Err: "non-IPv6 address", Addr: ip.String()} |
| } |
| return mxnet.Addr(ip6), uint16(port), nil |
| } |
| return "", 0, &AddrError{Err: "invalid address family", Addr: ip.String()} |
| } |