Skip to content

Commit

Permalink
net: initial SCTP support
Browse files Browse the repository at this point in the history
Fixes golang#22191

Change-Id: Icbebf73acd6f9965e554b780d726add0a4c61036
  • Loading branch information
higebu committed Oct 10, 2017
1 parent bb0bfd0 commit 5ec01eb
Show file tree
Hide file tree
Showing 23 changed files with 1,631 additions and 6 deletions.
15 changes: 15 additions & 0 deletions src/net/dial.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func parseNetwork(ctx context.Context, network string, needsProto bool) (afnet s
return "", 0, UnknownNetworkError(network)
}
case "unix", "unixgram", "unixpacket":
case "sctp", "sctp4", "sctp6":
default:
return "", 0, UnknownNetworkError(network)
}
Expand Down Expand Up @@ -198,6 +199,7 @@ func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string
tcp *TCPAddr
udp *UDPAddr
ip *IPAddr
sctp *SCTPAddr
wildcard bool
)
switch hint := hint.(type) {
Expand All @@ -210,6 +212,9 @@ func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string
case *IPAddr:
ip = hint
wildcard = ip.isWildcard()
case *SCTPAddr:
sctp = hint
wildcard = sctp.isWildcard()
}
naddrs := addrs[:0]
for _, addr := range addrs {
Expand All @@ -232,6 +237,11 @@ func (r *Resolver) resolveAddrList(ctx context.Context, op, network, addr string
continue
}
naddrs = append(naddrs, addr)
case *SCTPAddr:
if !wildcard && !addr.isWildcard() && !addr.IP.matchAddrFamily(sctp.IP) {
continue
}
naddrs = append(naddrs, addr)
}
}
if len(naddrs) == 0 {
Expand Down Expand Up @@ -554,6 +564,9 @@ func dialSingle(ctx context.Context, dp *dialParam, ra Addr) (c Conn, err error)
case *UnixAddr:
la, _ := la.(*UnixAddr)
c, err = dialUnix(ctx, dp.network, la, ra)
case *SCTPAddr:
la, _ := la.(*SCTPAddr)
c, err = dialSCTP(ctx, dp.network, la, ra)
default:
return nil, &OpError{Op: "dial", Net: dp.network, Source: la, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: dp.address}}
}
Expand Down Expand Up @@ -592,6 +605,8 @@ func Listen(network, address string) (Listener, error) {
l, err = ListenTCP(network, la)
case *UnixAddr:
l, err = ListenUnix(network, la)
case *SCTPAddr:
l, err = ListenSCTP(network, la)
default:
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
}
Expand Down
3 changes: 3 additions & 0 deletions src/net/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ var (
// if non-nil, overrides dialTCP.
testHookDialTCP func(ctx context.Context, net string, laddr, raddr *TCPAddr) (*TCPConn, error)

// if non-nil, overrides dialSCTP.
testHookDialSCTP func(ctx context.Context, net string, laddr, raddr *SCTPAddr) (*SCTPConn, error)

testHookHostsPath = "/etc/hosts"
testHookLookupIP = func(
ctx context.Context,
Expand Down
8 changes: 6 additions & 2 deletions src/net/ipsock.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ func isIPv4(addr Addr) bool {
return addr.IP.To4() != nil
case *IPAddr:
return addr.IP.To4() != nil
case *SCTPAddr:
return addr.IP.To4() != nil
}
return false
}
Expand All @@ -75,7 +77,7 @@ func (addrs addrList) forResolve(network, addr string) Addr {
case "ip":
// IPv6 literal (addr does NOT contain a port)
want6 = count(addr, ':') > 0
case "tcp", "udp":
case "tcp", "udp", "sctp":
// IPv6 literal. (addr contains a port, so look for '[')
want6 = count(addr, '[') > 0
}
Expand Down Expand Up @@ -243,7 +245,7 @@ func (r *Resolver) internetAddrList(ctx context.Context, net, addr string) (addr
portnum int
)
switch net {
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "sctp", "sctp4", "sctp6":
if addr != "" {
if host, port, err = SplitHostPort(addr); err != nil {
return nil, err
Expand All @@ -267,6 +269,8 @@ func (r *Resolver) internetAddrList(ctx context.Context, net, addr string) (addr
return &UDPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone}
case "ip", "ip4", "ip6":
return &IPAddr{IP: ip.IP, Zone: ip.Zone}
case "sctp", "sctp4", "sctp6":
return &SCTPAddr{IP: ip.IP, Port: portnum, Zone: ip.Zone}
default:
panic("unexpected network: " + net)
}
Expand Down
37 changes: 37 additions & 0 deletions src/net/listen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,21 @@ func checkFirstListener(network string, ln interface{}) error {
if fd.family != syscall.AF_INET6 {
return fmt.Errorf("%v got %v; want %v", fd.laddr, fd.family, syscall.AF_INET6)
}
case "sctp":
fd := ln.(*SCTPListener).fd
if err := checkDualStackAddrFamily(fd); err != nil {
return err
}
case "sctp4":
fd := ln.(*SCTPListener).fd
if fd.family != syscall.AF_INET {
return fmt.Errorf("%v got %v; want %v", fd.laddr, fd.family, syscall.AF_INET)
}
case "sctp6":
fd := ln.(*SCTPListener).fd
if fd.family != syscall.AF_INET6 {
return fmt.Errorf("%v got %v; want %v", fd.laddr, fd.family, syscall.AF_INET6)
}
default:
return UnknownNetworkError(network)
}
Expand All @@ -426,6 +441,10 @@ func checkSecondListener(network, address string, err error) error {
if err == nil {
return fmt.Errorf("%s should fail", network+" "+address)
}
case "sctp", "sctp4", "sctp6":
if err == nil {
return fmt.Errorf("%s should fail", network+" "+address)
}
default:
return UnknownNetworkError(network)
}
Expand All @@ -442,6 +461,10 @@ func checkDualStackSecondListener(network, address string, err, xerr error) erro
if xerr == nil && err != nil || xerr != nil && err == nil {
return fmt.Errorf("%s got %v; want %v", network+" "+address, err, xerr)
}
case "sctp", "sctp4", "sctp6":
if xerr == nil && err != nil || xerr != nil && err == nil {
return fmt.Errorf("%s got %v; want %v", network+" "+address, err, xerr)
}
default:
return UnknownNetworkError(network)
}
Expand Down Expand Up @@ -478,6 +501,20 @@ func checkDualStackAddrFamily(fd *netFD) error {
return fmt.Errorf("ListenPacket(%s, %v) returns %v; want %v", fd.net, fd.laddr, fd.family, a.family())
}
}
case *SCTPAddr:
// If a node under test supports both IPv6 capability
// and IPv6 IPv4-mapping capability, we can assume
// that the node listens on a wildcard address with an
// AF_INET6 socket.
if supportsIPv4map() && fd.laddr.(*SCTPAddr).isWildcard() {
if fd.family != syscall.AF_INET6 {
return fmt.Errorf("Listen(%s, %v) returns %v; want %v", fd.net, fd.laddr, fd.family, syscall.AF_INET6)
}
} else {
if fd.family != a.family() {
return fmt.Errorf("Listen(%s, %v) returns %v; want %v", fd.net, fd.laddr, fd.family, a.family())
}
}
default:
return fmt.Errorf("unexpected protocol address type: %T", a)
}
Expand Down
3 changes: 3 additions & 0 deletions src/net/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var protocols = map[string]int{
"tcp": 6,
"udp": 17,
"ipv6-icmp": 58,
"sctp": 132,
}

// services contains minimal mappings between services names and port
Expand Down Expand Up @@ -79,6 +80,8 @@ func lookupPortMap(network, service string) (port int, error error) {
network = "tcp"
case "udp4", "udp6":
network = "udp"
case "sctp4", "sctp6":
network = "sctp"
}

if m, ok := services[network]; ok {
Expand Down
22 changes: 20 additions & 2 deletions src/net/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ type ipv6LinkLocalUnicastTest struct {
}

var (
ipv6LinkLocalUnicastTCPTests []ipv6LinkLocalUnicastTest
ipv6LinkLocalUnicastUDPTests []ipv6LinkLocalUnicastTest
ipv6LinkLocalUnicastTCPTests []ipv6LinkLocalUnicastTest
ipv6LinkLocalUnicastUDPTests []ipv6LinkLocalUnicastTest
ipv6LinkLocalUnicastSCTPTests []ipv6LinkLocalUnicastTest
)

func setupTestData() {
Expand All @@ -83,18 +84,24 @@ func setupTestData() {
{"ip", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil},
{"ip4", "localhost", &IPAddr{IP: IPv4(127, 0, 0, 1)}, nil},
}...)
resolveSCTPAddrTests = append(resolveSCTPAddrTests, []resolveSCTPAddrTest{
{"sctp", "localhost:1", &SCTPAddr{IP: IPv4(127, 0, 0, 1), Port: 1}, nil},
{"sctp4", "localhost:2", &SCTPAddr{IP: IPv4(127, 0, 0, 1), Port: 2}, nil},
}...)
}

if supportsIPv6() {
resolveTCPAddrTests = append(resolveTCPAddrTests, resolveTCPAddrTest{"tcp6", "localhost:3", &TCPAddr{IP: IPv6loopback, Port: 3}, nil})
resolveUDPAddrTests = append(resolveUDPAddrTests, resolveUDPAddrTest{"udp6", "localhost:3", &UDPAddr{IP: IPv6loopback, Port: 3}, nil})
resolveIPAddrTests = append(resolveIPAddrTests, resolveIPAddrTest{"ip6", "localhost", &IPAddr{IP: IPv6loopback}, nil})
resolveSCTPAddrTests = append(resolveSCTPAddrTests, resolveSCTPAddrTest{"sctp6", "localhost:3", &SCTPAddr{IP: IPv6loopback, Port: 3}, nil})

// Issue 20911: don't return IPv4 addresses for
// Resolve*Addr calls of the IPv6 unspecified address.
resolveTCPAddrTests = append(resolveTCPAddrTests, resolveTCPAddrTest{"tcp", "[::]:4", &TCPAddr{IP: IPv6unspecified, Port: 4}, nil})
resolveUDPAddrTests = append(resolveUDPAddrTests, resolveUDPAddrTest{"udp", "[::]:4", &UDPAddr{IP: IPv6unspecified, Port: 4}, nil})
resolveIPAddrTests = append(resolveIPAddrTests, resolveIPAddrTest{"ip", "::", &IPAddr{IP: IPv6unspecified}, nil})
resolveSCTPAddrTests = append(resolveSCTPAddrTests, resolveSCTPAddrTest{"sctp", "[::]:4", &SCTPAddr{IP: IPv6unspecified, Port: 4}, nil})
}

ifi := loopbackInterface()
Expand All @@ -112,6 +119,10 @@ func setupTestData() {
{"ip6", "fe80::1%" + ifi.Name, &IPAddr{IP: ParseIP("fe80::1"), Zone: zoneCache.name(ifi.Index)}, nil},
{"ip6", "fe80::1%" + index, &IPAddr{IP: ParseIP("fe80::1"), Zone: index}, nil},
}...)
resolveSCTPAddrTests = append(resolveSCTPAddrTests, []resolveSCTPAddrTest{
{"sctp6", "[fe80::1%" + ifi.Name + "]:1", &SCTPAddr{IP: ParseIP("fe80::1"), Port: 1, Zone: zoneCache.name(ifi.Index)}, nil},
{"sctp6", "[fe80::1%" + index + "]:2", &SCTPAddr{IP: ParseIP("fe80::1"), Port: 2, Zone: index}, nil},
}...)
}

addr := ipv6LinkLocalUnicastAddr(ifi)
Expand All @@ -130,6 +141,9 @@ func setupTestData() {
ipv6LinkLocalUnicastUDPTests = append(ipv6LinkLocalUnicastUDPTests, []ipv6LinkLocalUnicastTest{
{"udp6", "[" + addr + "%" + ifi.Name + "]:0", false},
}...)
ipv6LinkLocalUnicastSCTPTests = append(ipv6LinkLocalUnicastSCTPTests, []ipv6LinkLocalUnicastTest{
{"sctp6", "[" + addr + "%" + ifi.Name + "]:0", false},
}...)
switch runtime.GOOS {
case "darwin", "dragonfly", "freebsd", "openbsd", "netbsd":
ipv6LinkLocalUnicastTCPTests = append(ipv6LinkLocalUnicastTCPTests, []ipv6LinkLocalUnicastTest{
Expand All @@ -149,6 +163,10 @@ func setupTestData() {
{"udp", "[ip6-localhost%" + ifi.Name + "]:0", true},
{"udp6", "[ip6-localhost%" + ifi.Name + "]:0", true},
}...)
ipv6LinkLocalUnicastSCTPTests = append(ipv6LinkLocalUnicastSCTPTests, []ipv6LinkLocalUnicastTest{
{"sctp", "[ip6-localhost%" + ifi.Name + "]:0", true},
{"sctp6", "[ip6-localhost%" + ifi.Name + "]:0", true},
}...)
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions src/net/mockserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ func newLocalListener(network string) (Listener, error) {
}
case "unix", "unixpacket":
return Listen(network, testUnixAddr())
case "sctp":
if supportsIPv4() {
if ln, err := Listen("sctp4", "127.0.0.1:0"); err == nil {
return ln, nil
}
}
if supportsIPv6() {
return Listen("sctp6", "[::1]:0")
}
case "sctp4":
if supportsIPv4() {
return Listen("sctp4", "127.0.0.1:0")
}
case "sctp6":
if supportsIPv6() {
return Listen("sctp6", "[::1]:0")
}
}
return nil, fmt.Errorf("%s is not supported", network)
}
Expand Down
81 changes: 81 additions & 0 deletions src/net/sctp_listen_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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.

// +build freebsd linux netbsd openbsd

package net

import (
"runtime"
"testing"
)

func (ln *SCTPListener) port() string {
_, port, err := SplitHostPort(ln.Addr().String())
if err != nil {
return ""
}
return port
}

var sctpListenerTests = []struct {
network string
address string
}{
{"sctp", ""},
{"sctp", "0.0.0.0"},
{"sctp", "::ffff:0.0.0.0"},
{"sctp", "::"},

{"sctp", "127.0.0.1"},
{"sctp", "::ffff:127.0.0.1"},
{"sctp", "::1"},

{"sctp4", ""},
{"sctp4", "0.0.0.0"},
{"sctp4", "::ffff:0.0.0.0"},

{"sctp4", "127.0.0.1"},
{"sctp4", "::ffff:127.0.0.1"},

{"sctp6", ""},
{"sctp6", "::"},

{"sctp6", "::1"},
}

// TestSCTPListener tests both single and double listen to a test
// listener with same address family, same listening address and
// same port.
func TestSCTPListener(t *testing.T) {
switch runtime.GOOS {
case "android", "darwin", "dragonfly", "plan9", "solaris", "nacl", "windows":
t.Skipf("not supported on %s", runtime.GOOS)
}

for _, tt := range sctpListenerTests {
if !testableListenArgs(tt.network, JoinHostPort(tt.address, "0"), "") {
t.Logf("skipping %s test", tt.network+" "+tt.address)
continue
}

ln1, err := Listen(tt.network, JoinHostPort(tt.address, "0"))
if err != nil {
t.Fatal(err)
}
if err := checkFirstListener(tt.network, ln1); err != nil {
ln1.Close()
t.Fatal(err)
}
ln2, err := Listen(tt.network, JoinHostPort(tt.address, ln1.(*SCTPListener).port()))
if err == nil {
ln2.Close()
}
if err := checkSecondListener(tt.network, tt.address, err); err != nil {
ln1.Close()
t.Fatal(err)
}
ln1.Close()
}
}
Loading

0 comments on commit 5ec01eb

Please sign in to comment.