| // 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" |
| "strings" |
| "sync" |
| "syscall" |
| "syscall/zx" |
| "syscall/zx/fdio" |
| "syscall/zx/fidl" |
| "syscall/zx/mxnet" |
| zxnet "syscall/zx/net" |
| "syscall/zx/zxsocket" |
| "time" |
| ) |
| |
| func (p *ipStackCapabilities) probe() { |
| p.ipv4Enabled = true |
| p.ipv6Enabled = true |
| p.ipv4MappedIPv6Enabled = 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 |
| } |
| |
| var socketProvider struct { |
| mu sync.Mutex |
| h zx.Handle |
| } |
| |
| 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/"+zxnet.SocketProviderName, 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 strings.HasPrefix(net, "udp") { |
| sotype = syscall.SOCK_DGRAM |
| } |
| |
| var sp zxnet.SocketProviderInterface |
| // 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++ { |
| if h := getSocketProvider(); h.IsValid() { |
| sp = zxnet.SocketProviderInterface(fidl.ChannelProxy{Channel: zx.Channel(h)}) |
| break |
| } |
| time.Sleep(250 * time.Millisecond) |
| } |
| code, s, err := sp.Socket(int16(family), int16(sotype), syscall.IPPROTO_IP) |
| if err != nil { |
| return nil, err |
| } |
| if code != 0 { |
| return nil, syscall.Errno(code) |
| } |
| sock := zxsocket.NewSocket(s) |
| if sotype == syscall.SOCK_DGRAM { |
| sock.SetDgram() |
| } |
| |
| fd = newFD(sock, family, sotype, net) |
| if laddr != nil { |
| addr, port, err := laddr.sockaddr(family) |
| if err != nil { |
| return nil, err |
| } |
| if addr != "" || port != 0 { |
| b := make([]byte, mxnet.SockaddrLen) |
| addrlen, err := mxnet.EncodeSockaddr(b, addr, port) |
| if err != nil { |
| return nil, err |
| } |
| b = b[:addrlen] |
| if code, err := fd.sock.Bind(b); err != nil { |
| return nil, err |
| } else if code != 0 { |
| return nil, syscall.Errno(code) |
| } |
| } |
| if raddr == nil { |
| switch sotype { |
| case syscall.SOCK_STREAM: |
| code, err := fd.sock.Listen(int16(listenerBacklog())) |
| if err != nil { |
| return nil, err |
| } |
| if code != 0 { |
| return nil, syscall.Errno(code) |
| } |
| 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 |
| } |
| b := make([]byte, mxnet.SockaddrLen) |
| addrlen, err := mxnet.EncodeSockaddr(b, addr, port) |
| if err != nil { |
| return nil, err |
| } |
| b = b[:addrlen] |
| code, err := fd.sock.Connect(b) |
| if code == syscall.EINPROGRESS { |
| obs, err := fd.sock.Wait(mxnet.MXSIO_SIGNAL_OUTGOING|zx.SignalSocketPeerClosed, zx.TimensecInfinite) |
| if err != nil { |
| return nil, err |
| } |
| if obs&zx.SignalSocketPeerClosed != 0 { |
| return nil, &zx.Error{Status: zx.ErrPeerClosed, Text: "zxsocket"} |
| } |
| if obs&mxnet.MXSIO_SIGNAL_OUTGOING != 0 { |
| // Call connect again to learn the result. |
| code, err = fd.sock.Connect(b) |
| } else { |
| panic("unreachable") |
| } |
| } |
| if err != nil { |
| return nil, err |
| } |
| if code != 0 { |
| return nil, syscall.Errno(code) |
| } |
| 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()} |
| } |