| // 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. |
| |
| // `go mod` ignores file names for the purpose of resolving |
| // dependencies, and zxsocket doesn't build on not-Fuchsia. |
| //go:build fuchsia |
| |
| package net |
| |
| import ( |
| "context" |
| "fmt" |
| "sync" |
| "syscall" |
| "syscall/zx" |
| "syscall/zx/fdio" |
| "syscall/zx/net" |
| "syscall/zx/posix/socket" |
| "syscall/zx/zxsocket" |
| ) |
| |
| const signalStreamConnected = zx.Signals(socket.SignalStreamConnected) |
| |
| 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 |
| domain() socket.Domain |
| isWildcard() bool |
| sockaddr(domain socket.Domain) (addr net.SocketAddress, err error) |
| } |
| |
| func favoriteAddrDomain(net string, laddr, raddr sockaddr, mode string) socket.Domain { |
| switch net[len(net)-1] { |
| case '4': |
| return socket.DomainIpv4 |
| case '6': |
| return socket.DomainIpv6 |
| } |
| |
| if mode == "listen" && (laddr == nil || laddr.isWildcard()) { |
| if supportsIPv4map() || !supportsIPv4() { |
| return socket.DomainIpv6 |
| } |
| if laddr == nil { |
| return socket.DomainIpv4 |
| } |
| return laddr.domain() |
| } |
| |
| if (laddr == nil || laddr.domain() == socket.DomainIpv4) && |
| (raddr == nil || raddr.domain() == socket.DomainIpv4) { |
| return socket.DomainIpv4 |
| } |
| return socket.DomainIpv6 |
| } |
| |
| var socketProvider struct { |
| once sync.Once |
| provider socket.ProviderWithCtxInterface |
| err error |
| } |
| |
| func getProvider() (*socket.ProviderWithCtxInterface, error) { |
| socketProvider.once.Do(func() { |
| socketProvider.err = func() error { |
| c0, c1, err := zx.NewChannel(0) |
| if err != nil { |
| return err |
| } |
| if err := fdio.ServiceConnect("/svc/"+socket.ProviderName, zx.Handle(c0)); err != nil { |
| _ = c1.Close() |
| return err |
| } |
| socketProvider.provider.Channel = c1 |
| return nil |
| }() |
| }) |
| return &socketProvider.provider, socketProvider.err |
| } |
| |
| func dialFuchsia(_ context.Context, netName string, laddr, raddr sockaddr) (*netFD, error) { |
| domain := favoriteAddrDomain(netName, laddr, raddr, "dial") |
| |
| provider, err := getProvider() |
| if err != nil { |
| return nil, err |
| } |
| |
| var sock zxsocket.Socket |
| if stringsHasPrefix(netName, "udp") { |
| result, err := provider.DatagramSocketDeprecated(context.Background(), domain, socket.DatagramSocketProtocolUdp) |
| if err != nil { |
| return nil, err |
| } |
| switch result.Which() { |
| case socket.ProviderDatagramSocketResultErr: |
| return nil, syscall.Errno(result.Err) |
| case socket.ProviderDatagramSocketResultResponse: |
| sock, err = zxsocket.NewDatagramSocket(&result.Response.S) |
| if err != nil { |
| return nil, err |
| } |
| default: |
| panic(fmt.Sprintf("unrecognized fuchsia.posix.socket/Provider.DatagramSocket result %d", result.Which())) |
| } |
| } else { |
| result, err := provider.StreamSocket(context.Background(), domain, socket.StreamSocketProtocolTcp) |
| if err != nil { |
| return nil, err |
| } |
| switch result.Which() { |
| case socket.ProviderStreamSocketResultErr: |
| return nil, syscall.Errno(result.Err) |
| case socket.ProviderStreamSocketResultResponse: |
| sock, err = zxsocket.NewStreamSocket(&result.Response.S) |
| if err != nil { |
| return nil, err |
| } |
| default: |
| panic(fmt.Sprintf("unrecognized fuchsia.posix.socket/Provider.StreamSocket result %d", result.Which())) |
| } |
| } |
| |
| fd := newFD(sock, domain, netName) |
| if laddr != nil { |
| addr, err := laddr.sockaddr(domain) |
| if err != nil { |
| return nil, err |
| } |
| var port uint16 |
| isSet := false |
| switch addr.Which() { |
| case net.SocketAddressIpv4: |
| port = addr.Ipv4.Port |
| isSet = true |
| case net.SocketAddressIpv6: |
| port = addr.Ipv6.Port |
| isSet = true |
| } |
| if isSet || port != 0 { |
| if err := fd.sock.Bind(addr); err != nil { |
| return nil, err |
| } |
| } |
| if raddr == nil { |
| switch s := fd.sock.(type) { |
| case *zxsocket.DatagramSocket: |
| case *zxsocket.StreamSocket: |
| if err := s.Listen(int16(listenerBacklog())); err != nil { |
| return nil, err |
| } |
| default: |
| panic(fmt.Sprintf("unrecognized socket type %T", fd.sock)) |
| } |
| } |
| } |
| if raddr != nil { |
| addr, err := raddr.sockaddr(domain) |
| if err != nil { |
| return nil, err |
| } |
| for i := 0; i < 2; i++ { |
| err = fd.sock.Connect(addr) |
| if sock, ok := fd.sock.(*zxsocket.StreamSocket); ok && err == syscall.EINPROGRESS { |
| obs, err := sock.Wait(context.Background(), signalStreamConnected|zx.SignalSocketPeerClosed) |
| if err != nil { |
| return nil, err |
| } |
| switch { |
| case obs&zx.SignalSocketPeerClosed != 0: |
| return nil, &zx.Error{Status: zx.ErrPeerClosed, Text: "zxsocket"} |
| case obs&(signalStreamConnected) != 0: |
| // Call connect again to learn the result. |
| continue |
| default: |
| panic(fmt.Sprintf("unexpected observed signals %08X", obs)) |
| } |
| } |
| break |
| } |
| if err != nil { |
| return nil, err |
| } |
| fd.isConnected = true |
| } |
| fd.setAddr(fd.asAddr(fd.sock.GetPeerName())) |
| |
| return fd, nil |
| } |
| |
| func ipToSockaddr(domain socket.Domain, ip IP, port int, zone_str string) (address net.SocketAddress, err error) { |
| switch domain { |
| case socket.DomainIpv4: |
| if len(ip) == 0 { |
| ip = IPv4zero |
| } |
| ip4 := ip.To4() |
| if ip4 == nil { |
| return net.SocketAddress{}, &AddrError{Err: "non-IPv4 address", Addr: ip.String()} |
| } |
| addr := net.SocketAddressWithIpv4(net.Ipv4SocketAddress{ |
| Port: uint16(port), |
| }) |
| copy(addr.Ipv4.Address.Addr[:], ip4) |
| return addr, nil |
| case socket.DomainIpv6: |
| if len(ip) == 0 || ip.Equal(IPv4zero) { |
| ip = IPv6zero |
| } |
| ip6 := ip.To16() |
| if ip6 == nil { |
| return net.SocketAddress{}, &AddrError{Err: "non-IPv6 address", Addr: ip.String()} |
| } |
| addr := net.SocketAddressWithIpv6(net.Ipv6SocketAddress{ |
| Port: uint16(port), |
| ZoneIndex: uint64(zoneCache.index(zone_str)), |
| }) |
| copy(addr.Ipv6.Address.Addr[:], ip6) |
| return addr, nil |
| } |
| return net.SocketAddress{}, &AddrError{Err: "invalid address family", Addr: ip.String()} |
| } |