// Copyright 2011 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 !plan9

package net

import (
	"fmt"
	"internal/testenv"
	"os"
	"runtime"
	"syscall"
	"testing"
)

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

func (c *UDPConn) port() string {
	_, port, err := SplitHostPort(c.LocalAddr().String())
	if err != nil {
		return ""
	}
	return port
}

var tcpListenerTests = []struct {
	network string
	address string
}{
	{"tcp", ""},
	{"tcp", "0.0.0.0"},
	{"tcp", "::ffff:0.0.0.0"},
	{"tcp", "::"},

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

	{"tcp4", ""},
	{"tcp4", "0.0.0.0"},
	{"tcp4", "::ffff:0.0.0.0"},

	{"tcp4", "127.0.0.1"},
	{"tcp4", "::ffff:127.0.0.1"},

	{"tcp6", ""},
	{"tcp6", "::"},

	{"tcp6", "::1"},
}

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

	for _, tt := range tcpListenerTests {
		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.(*TCPListener).port()))
		if err == nil {
			ln2.Close()
		}
		if err := checkSecondListener(tt.network, tt.address, err); err != nil {
			ln1.Close()
			t.Fatal(err)
		}
		ln1.Close()
	}
}

var udpListenerTests = []struct {
	network string
	address string
}{
	{"udp", ""},
	{"udp", "0.0.0.0"},
	{"udp", "::ffff:0.0.0.0"},
	{"udp", "::"},

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

	{"udp4", ""},
	{"udp4", "0.0.0.0"},
	{"udp4", "::ffff:0.0.0.0"},

	{"udp4", "127.0.0.1"},
	{"udp4", "::ffff:127.0.0.1"},

	{"udp6", ""},
	{"udp6", "::"},

	{"udp6", "::1"},
}

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

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

		c1, err := ListenPacket(tt.network, JoinHostPort(tt.address, "0"))
		if err != nil {
			t.Fatal(err)
		}
		if err := checkFirstListener(tt.network, c1); err != nil {
			c1.Close()
			t.Fatal(err)
		}
		c2, err := ListenPacket(tt.network, JoinHostPort(tt.address, c1.(*UDPConn).port()))
		if err == nil {
			c2.Close()
		}
		if err := checkSecondListener(tt.network, tt.address, err); err != nil {
			c1.Close()
			t.Fatal(err)
		}
		c1.Close()
	}
}

var dualStackTCPListenerTests = []struct {
	network1, address1 string // first listener
	network2, address2 string // second listener
	xerr               error  // expected error value, nil or other
}{
	// Test cases and expected results for the attempting 2nd listen on the same port
	// 1st listen                2nd listen                 darwin  freebsd  linux  openbsd
	// ------------------------------------------------------------------------------------
	// "tcp"  ""                 "tcp"  ""                    -        -       -       -
	// "tcp"  ""                 "tcp"  "0.0.0.0"             -        -       -       -
	// "tcp"  "0.0.0.0"          "tcp"  ""                    -        -       -       -
	// ------------------------------------------------------------------------------------
	// "tcp"  ""                 "tcp"  "[::]"                -        -       -       ok
	// "tcp"  "[::]"             "tcp"  ""                    -        -       -       ok
	// "tcp"  "0.0.0.0"          "tcp"  "[::]"                -        -       -       ok
	// "tcp"  "[::]"             "tcp"  "0.0.0.0"             -        -       -       ok
	// "tcp"  "[::ffff:0.0.0.0]" "tcp"  "[::]"                -        -       -       ok
	// "tcp"  "[::]"             "tcp"  "[::ffff:0.0.0.0]"    -        -       -       ok
	// ------------------------------------------------------------------------------------
	// "tcp4" ""                 "tcp6" ""                    ok       ok      ok      ok
	// "tcp6" ""                 "tcp4" ""                    ok       ok      ok      ok
	// "tcp4" "0.0.0.0"          "tcp6" "[::]"                ok       ok      ok      ok
	// "tcp6" "[::]"             "tcp4" "0.0.0.0"             ok       ok      ok      ok
	// ------------------------------------------------------------------------------------
	// "tcp"  "127.0.0.1"        "tcp"  "[::1]"               ok       ok      ok      ok
	// "tcp"  "[::1]"            "tcp"  "127.0.0.1"           ok       ok      ok      ok
	// "tcp4" "127.0.0.1"        "tcp6" "[::1]"               ok       ok      ok      ok
	// "tcp6" "[::1]"            "tcp4" "127.0.0.1"           ok       ok      ok      ok
	//
	// Platform default configurations:
	// darwin, kernel version 11.3.0
	//	net.inet6.ip6.v6only=0 (overridable by sysctl or IPV6_V6ONLY option)
	// freebsd, kernel version 8.2
	//	net.inet6.ip6.v6only=1 (overridable by sysctl or IPV6_V6ONLY option)
	// linux, kernel version 3.0.0
	//	net.ipv6.bindv6only=0 (overridable by sysctl or IPV6_V6ONLY option)
	// openbsd, kernel version 5.0
	//	net.inet6.ip6.v6only=1 (overriding is prohibited)

	{"tcp", "", "tcp", "", syscall.EADDRINUSE},
	{"tcp", "", "tcp", "0.0.0.0", syscall.EADDRINUSE},
	{"tcp", "0.0.0.0", "tcp", "", syscall.EADDRINUSE},

	{"tcp", "", "tcp", "::", syscall.EADDRINUSE},
	{"tcp", "::", "tcp", "", syscall.EADDRINUSE},
	{"tcp", "0.0.0.0", "tcp", "::", syscall.EADDRINUSE},
	{"tcp", "::", "tcp", "0.0.0.0", syscall.EADDRINUSE},
	{"tcp", "::ffff:0.0.0.0", "tcp", "::", syscall.EADDRINUSE},
	{"tcp", "::", "tcp", "::ffff:0.0.0.0", syscall.EADDRINUSE},

	{"tcp4", "", "tcp6", "", nil},
	{"tcp6", "", "tcp4", "", nil},
	{"tcp4", "0.0.0.0", "tcp6", "::", nil},
	{"tcp6", "::", "tcp4", "0.0.0.0", nil},

	{"tcp", "127.0.0.1", "tcp", "::1", nil},
	{"tcp", "::1", "tcp", "127.0.0.1", nil},
	{"tcp4", "127.0.0.1", "tcp6", "::1", nil},
	{"tcp6", "::1", "tcp4", "127.0.0.1", nil},
}

// TestDualStackTCPListener tests both single and double listen
// to a test listener with various address families, different
// listening address and same port.
//
// On DragonFly BSD, we expect the kernel version of node under test
// to be greater than or equal to 4.4.
func TestDualStackTCPListener(t *testing.T) {
	switch runtime.GOOS {
	case "nacl", "plan9":
		t.Skipf("not supported on %s", runtime.GOOS)
	}
	if !supportsIPv4() || !supportsIPv6() {
		t.Skip("both IPv4 and IPv6 are required")
	}

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

		if !supportsIPv4map() && differentWildcardAddr(tt.address1, tt.address2) {
			tt.xerr = nil
		}
		var firstErr, secondErr error
		for i := 0; i < 5; i++ {
			lns, err := newDualStackListener()
			if err != nil {
				t.Fatal(err)
			}
			port := lns[0].port()
			for _, ln := range lns {
				ln.Close()
			}
			var ln1 Listener
			ln1, firstErr = Listen(tt.network1, JoinHostPort(tt.address1, port))
			if firstErr != nil {
				continue
			}
			if err := checkFirstListener(tt.network1, ln1); err != nil {
				ln1.Close()
				t.Fatal(err)
			}
			ln2, err := Listen(tt.network2, JoinHostPort(tt.address2, ln1.(*TCPListener).port()))
			if err == nil {
				ln2.Close()
			}
			if secondErr = checkDualStackSecondListener(tt.network2, tt.address2, err, tt.xerr); secondErr != nil {
				ln1.Close()
				continue
			}
			ln1.Close()
			break
		}
		if firstErr != nil {
			t.Error(firstErr)
		}
		if secondErr != nil {
			t.Error(secondErr)
		}
	}
}

var dualStackUDPListenerTests = []struct {
	network1, address1 string // first listener
	network2, address2 string // second listener
	xerr               error  // expected error value, nil or other
}{
	{"udp", "", "udp", "", syscall.EADDRINUSE},
	{"udp", "", "udp", "0.0.0.0", syscall.EADDRINUSE},
	{"udp", "0.0.0.0", "udp", "", syscall.EADDRINUSE},

	{"udp", "", "udp", "::", syscall.EADDRINUSE},
	{"udp", "::", "udp", "", syscall.EADDRINUSE},
	{"udp", "0.0.0.0", "udp", "::", syscall.EADDRINUSE},
	{"udp", "::", "udp", "0.0.0.0", syscall.EADDRINUSE},
	{"udp", "::ffff:0.0.0.0", "udp", "::", syscall.EADDRINUSE},
	{"udp", "::", "udp", "::ffff:0.0.0.0", syscall.EADDRINUSE},

	{"udp4", "", "udp6", "", nil},
	{"udp6", "", "udp4", "", nil},
	{"udp4", "0.0.0.0", "udp6", "::", nil},
	{"udp6", "::", "udp4", "0.0.0.0", nil},

	{"udp", "127.0.0.1", "udp", "::1", nil},
	{"udp", "::1", "udp", "127.0.0.1", nil},
	{"udp4", "127.0.0.1", "udp6", "::1", nil},
	{"udp6", "::1", "udp4", "127.0.0.1", nil},
}

// TestDualStackUDPListener tests both single and double listen
// to a test listener with various address families, different
// listening address and same port.
//
// On DragonFly BSD, we expect the kernel version of node under test
// to be greater than or equal to 4.4.
func TestDualStackUDPListener(t *testing.T) {
	switch runtime.GOOS {
	case "nacl", "plan9":
		t.Skipf("not supported on %s", runtime.GOOS)
	}
	if !supportsIPv4() || !supportsIPv6() {
		t.Skip("both IPv4 and IPv6 are required")
	}

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

		if !supportsIPv4map() && differentWildcardAddr(tt.address1, tt.address2) {
			tt.xerr = nil
		}
		var firstErr, secondErr error
		for i := 0; i < 5; i++ {
			cs, err := newDualStackPacketListener()
			if err != nil {
				t.Fatal(err)
			}
			port := cs[0].port()
			for _, c := range cs {
				c.Close()
			}
			var c1 PacketConn
			c1, firstErr = ListenPacket(tt.network1, JoinHostPort(tt.address1, port))
			if firstErr != nil {
				continue
			}
			if err := checkFirstListener(tt.network1, c1); err != nil {
				c1.Close()
				t.Fatal(err)
			}
			c2, err := ListenPacket(tt.network2, JoinHostPort(tt.address2, c1.(*UDPConn).port()))
			if err == nil {
				c2.Close()
			}
			if secondErr = checkDualStackSecondListener(tt.network2, tt.address2, err, tt.xerr); secondErr != nil {
				c1.Close()
				continue
			}
			c1.Close()
			break
		}
		if firstErr != nil {
			t.Error(firstErr)
		}
		if secondErr != nil {
			t.Error(secondErr)
		}
	}
}

func differentWildcardAddr(i, j string) bool {
	if (i == "" || i == "0.0.0.0" || i == "::ffff:0.0.0.0") && (j == "" || j == "0.0.0.0" || j == "::ffff:0.0.0.0") {
		return false
	}
	if i == "[::]" && j == "[::]" {
		return false
	}
	return true
}

func checkFirstListener(network string, ln interface{}) error {
	switch network {
	case "tcp":
		fd := ln.(*TCPListener).fd
		if err := checkDualStackAddrFamily(fd); err != nil {
			return err
		}
	case "tcp4":
		fd := ln.(*TCPListener).fd
		if fd.family != syscall.AF_INET {
			return fmt.Errorf("%v got %v; want %v", fd.laddr, fd.family, syscall.AF_INET)
		}
	case "tcp6":
		fd := ln.(*TCPListener).fd
		if fd.family != syscall.AF_INET6 {
			return fmt.Errorf("%v got %v; want %v", fd.laddr, fd.family, syscall.AF_INET6)
		}
	case "udp":
		fd := ln.(*UDPConn).fd
		if err := checkDualStackAddrFamily(fd); err != nil {
			return err
		}
	case "udp4":
		fd := ln.(*UDPConn).fd
		if fd.family != syscall.AF_INET {
			return fmt.Errorf("%v got %v; want %v", fd.laddr, fd.family, syscall.AF_INET)
		}
	case "udp6":
		fd := ln.(*UDPConn).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)
	}
	return nil
}

func checkSecondListener(network, address string, err error) error {
	switch network {
	case "tcp", "tcp4", "tcp6":
		if err == nil {
			return fmt.Errorf("%s should fail", network+" "+address)
		}
	case "udp", "udp4", "udp6":
		if err == nil {
			return fmt.Errorf("%s should fail", network+" "+address)
		}
	default:
		return UnknownNetworkError(network)
	}
	return nil
}

func checkDualStackSecondListener(network, address string, err, xerr error) error {
	switch network {
	case "tcp", "tcp4", "tcp6":
		if xerr == nil && err != nil || xerr != nil && err == nil {
			return fmt.Errorf("%s got %v; want %v", network+" "+address, err, xerr)
		}
	case "udp", "udp4", "udp6":
		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)
	}
	return nil
}

func checkDualStackAddrFamily(fd *netFD) error {
	switch a := fd.laddr.(type) {
	case *TCPAddr:
		// 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.(*TCPAddr).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())
			}
		}
	case *UDPAddr:
		// 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.(*UDPAddr).isWildcard() {
			if fd.family != syscall.AF_INET6 {
				return fmt.Errorf("ListenPacket(%s, %v) returns %v; want %v", fd.net, fd.laddr, fd.family, syscall.AF_INET6)
			}
		} else {
			if fd.family != a.family() {
				return fmt.Errorf("ListenPacket(%s, %v) returns %v; want %v", fd.net, fd.laddr, fd.family, a.family())
			}
		}
	default:
		return fmt.Errorf("unexpected protocol address type: %T", a)
	}
	return nil
}

func TestWildWildcardListener(t *testing.T) {
	testenv.MustHaveExternalNetwork(t)

	switch runtime.GOOS {
	case "plan9":
		t.Skipf("not supported on %s", runtime.GOOS)
	}

	defer func() {
		if p := recover(); p != nil {
			t.Fatalf("panicked: %v", p)
		}
	}()

	if ln, err := Listen("tcp", ""); err == nil {
		ln.Close()
	}
	if ln, err := ListenPacket("udp", ""); err == nil {
		ln.Close()
	}
	if ln, err := ListenTCP("tcp", nil); err == nil {
		ln.Close()
	}
	if ln, err := ListenUDP("udp", nil); err == nil {
		ln.Close()
	}
	if ln, err := ListenIP("ip:icmp", nil); err == nil {
		ln.Close()
	}
}

var ipv4MulticastListenerTests = []struct {
	net   string
	gaddr *UDPAddr // see RFC 4727
}{
	{"udp", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}},

	{"udp4", &UDPAddr{IP: IPv4(224, 0, 0, 254), Port: 12345}},
}

// TestIPv4MulticastListener tests both single and double listen to a
// test listener with same address family, same group address and same
// port.
func TestIPv4MulticastListener(t *testing.T) {
	testenv.MustHaveExternalNetwork(t)

	switch runtime.GOOS {
	case "android", "nacl", "plan9":
		t.Skipf("not supported on %s", runtime.GOOS)
	case "solaris":
		t.Skipf("not supported on solaris, see golang.org/issue/7399")
	}
	if !supportsIPv4() {
		t.Skip("IPv4 is not supported")
	}

	closer := func(cs []*UDPConn) {
		for _, c := range cs {
			if c != nil {
				c.Close()
			}
		}
	}

	for _, ifi := range []*Interface{loopbackInterface(), nil} {
		// Note that multicast interface assignment by system
		// is not recommended because it usually relies on
		// routing stuff for finding out an appropriate
		// nexthop containing both network and link layer
		// adjacencies.
		if ifi == nil || !*testIPv4 {
			continue
		}
		for _, tt := range ipv4MulticastListenerTests {
			var err error
			cs := make([]*UDPConn, 2)
			if cs[0], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil {
				t.Fatal(err)
			}
			if err := checkMulticastListener(cs[0], tt.gaddr.IP); err != nil {
				closer(cs)
				t.Fatal(err)
			}
			if cs[1], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil {
				closer(cs)
				t.Fatal(err)
			}
			if err := checkMulticastListener(cs[1], tt.gaddr.IP); err != nil {
				closer(cs)
				t.Fatal(err)
			}
			closer(cs)
		}
	}
}

var ipv6MulticastListenerTests = []struct {
	net   string
	gaddr *UDPAddr // see RFC 4727
}{
	{"udp", &UDPAddr{IP: ParseIP("ff01::114"), Port: 12345}},
	{"udp", &UDPAddr{IP: ParseIP("ff02::114"), Port: 12345}},
	{"udp", &UDPAddr{IP: ParseIP("ff04::114"), Port: 12345}},
	{"udp", &UDPAddr{IP: ParseIP("ff05::114"), Port: 12345}},
	{"udp", &UDPAddr{IP: ParseIP("ff08::114"), Port: 12345}},
	{"udp", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}},

	{"udp6", &UDPAddr{IP: ParseIP("ff01::114"), Port: 12345}},
	{"udp6", &UDPAddr{IP: ParseIP("ff02::114"), Port: 12345}},
	{"udp6", &UDPAddr{IP: ParseIP("ff04::114"), Port: 12345}},
	{"udp6", &UDPAddr{IP: ParseIP("ff05::114"), Port: 12345}},
	{"udp6", &UDPAddr{IP: ParseIP("ff08::114"), Port: 12345}},
	{"udp6", &UDPAddr{IP: ParseIP("ff0e::114"), Port: 12345}},
}

// TestIPv6MulticastListener tests both single and double listen to a
// test listener with same address family, same group address and same
// port.
func TestIPv6MulticastListener(t *testing.T) {
	testenv.MustHaveExternalNetwork(t)

	switch runtime.GOOS {
	case "plan9":
		t.Skipf("not supported on %s", runtime.GOOS)
	case "solaris":
		t.Skipf("not supported on solaris, see issue 7399")
	}
	if !supportsIPv6() {
		t.Skip("IPv6 is not supported")
	}
	if os.Getuid() != 0 {
		t.Skip("must be root")
	}

	closer := func(cs []*UDPConn) {
		for _, c := range cs {
			if c != nil {
				c.Close()
			}
		}
	}

	for _, ifi := range []*Interface{loopbackInterface(), nil} {
		// Note that multicast interface assignment by system
		// is not recommended because it usually relies on
		// routing stuff for finding out an appropriate
		// nexthop containing both network and link layer
		// adjacencies.
		if ifi == nil && !*testIPv6 {
			continue
		}
		for _, tt := range ipv6MulticastListenerTests {
			var err error
			cs := make([]*UDPConn, 2)
			if cs[0], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil {
				t.Fatal(err)
			}
			if err := checkMulticastListener(cs[0], tt.gaddr.IP); err != nil {
				closer(cs)
				t.Fatal(err)
			}
			if cs[1], err = ListenMulticastUDP(tt.net, ifi, tt.gaddr); err != nil {
				closer(cs)
				t.Fatal(err)
			}
			if err := checkMulticastListener(cs[1], tt.gaddr.IP); err != nil {
				closer(cs)
				t.Fatal(err)
			}
			closer(cs)
		}
	}
}

func checkMulticastListener(c *UDPConn, ip IP) error {
	if ok, err := multicastRIBContains(ip); err != nil {
		return err
	} else if !ok {
		return fmt.Errorf("%s not found in multicast rib", ip.String())
	}
	la := c.LocalAddr()
	if la, ok := la.(*UDPAddr); !ok || la.Port == 0 {
		return fmt.Errorf("got %v; want a proper address with non-zero port number", la)
	}
	return nil
}

func multicastRIBContains(ip IP) (bool, error) {
	switch runtime.GOOS {
	case "dragonfly", "netbsd", "openbsd", "plan9", "solaris", "windows":
		return true, nil // not implemented yet
	case "linux":
		if runtime.GOARCH == "arm" || runtime.GOARCH == "alpha" {
			return true, nil // not implemented yet
		}
	}
	ift, err := Interfaces()
	if err != nil {
		return false, err
	}
	for _, ifi := range ift {
		ifmat, err := ifi.MulticastAddrs()
		if err != nil {
			return false, err
		}
		for _, ifma := range ifmat {
			if ifma.(*IPAddr).IP.Equal(ip) {
				return true, nil
			}
		}
	}
	return false, nil
}
