blob: 38bf15f79e551f6c75dbd00a27c8ad7b3e51d8ed [file] [log] [blame]
// 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.
//+build fuchsia
package net
import (
"context"
"fmt"
"sync"
"syscall"
"syscall/zx"
"syscall/zx/fdio"
"syscall/zx/net"
"syscall/zx/posix/socket"
"syscall/zx/zxsocket"
)
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 base *socket.BaseSocketWithCtxInterface
if stringsHasPrefix(netName, "udp") {
result, err := provider.DatagramSocket(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:
base = &socket.BaseSocketWithCtxInterface{Channel: result.Response.S.Channel}
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:
base = &socket.BaseSocketWithCtxInterface{Channel: result.Response.S.Channel}
default:
panic(fmt.Sprintf("unrecognized fuchsia.posix.socket/Provider.StreamSocket result %d", result.Which()))
}
}
sock, err := zxsocket.NewSocket(base)
if err != nil {
return nil, err
}
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(zxsocket.SignalOutgoing|zx.SignalSocketPeerClosed, zx.TimensecInfinite)
if err != nil {
return nil, err
}
switch {
case obs&zx.SignalSocketPeerClosed != 0:
return nil, &zx.Error{Status: zx.ErrPeerClosed, Text: "zxsocket"}
case obs&zxsocket.SignalOutgoing != 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()
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()}
}