Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPv6 relay support #462

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 74 additions & 13 deletions gather.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import (
)

const (
stunGatherTimeout = time.Second * 5
stunGatherTimeout = time.Second * 5
maxIPv4LookupsPerRelay = 5
maxIPv6LookupsPerRelay = 5
)

type closeable interface {
Expand Down Expand Up @@ -377,7 +379,7 @@ func (a *Agent) gatherCandidatesSrflxUDPMux(ctx context.Context, urls []*URL, ne
go func(url URL, network string, isIPv6 bool) {
defer wg.Done()

hostPort := fmt.Sprintf("%s:%d", url.Host, url.Port)
hostPort := url.HostPortString()
serverAddr, err := a.net.ResolveUDPAddr(network, hostPort)
if err != nil {
a.log.Warnf("failed to resolve stun host: %s: %v", hostPort, err)
Expand Down Expand Up @@ -444,7 +446,7 @@ func (a *Agent) gatherCandidatesSrflx(ctx context.Context, urls []*URL, networkT
go func(url URL, network string) {
defer wg.Done()

hostPort := fmt.Sprintf("%s:%d", url.Host, url.Port)
hostPort := url.HostPortString()
serverAddr, err := a.net.ResolveUDPAddr(network, hostPort)
if err != nil {
a.log.Warnf("failed to resolve stun host: %s: %v", hostPort, err)
Expand Down Expand Up @@ -508,7 +510,6 @@ func (a *Agent) gatherCandidatesRelay(ctx context.Context, urls []*URL) { //noli
var wg sync.WaitGroup
defer wg.Wait()

network := NetworkTypeUDP4.String()
for i := range urls {
switch {
case urls[i].Scheme != SchemeTypeTURN && urls[i].Scheme != SchemeTypeTURNS:
Expand All @@ -521,21 +522,38 @@ func (a *Agent) gatherCandidatesRelay(ctx context.Context, urls []*URL) { //noli
return
}

wg.Add(1)
go func(url URL) {
generateCandidate := func(url URL, ipv6 bool) {
defer wg.Done()
TURNServerAddr := fmt.Sprintf("%s:%d", url.Host, url.Port)

var (
TURNServerAddr = url.HostPortString()

udpNetworkType string
tcpNetworkType string
localAddress string
network string

locConn net.PacketConn
err error
RelAddr string
RelPort int
relayProtocol string
)

if ipv6 {
udpNetworkType = NetworkTypeUDP6.String()
tcpNetworkType = NetworkTypeTCP6.String()
localAddress = ":"
} else {
udpNetworkType = NetworkTypeUDP4.String()
tcpNetworkType = NetworkTypeTCP4.String()
localAddress = "0.0.0.0:0"
}

switch {
case url.Proto == ProtoTypeUDP && url.Scheme == SchemeTypeTURN:
if locConn, err = a.net.ListenPacket(network, "0.0.0.0:0"); err != nil {
network = udpNetworkType
if locConn, err = a.net.ListenPacket(network, localAddress); err != nil {
a.log.Warnf("Failed to listen %s: %v", network, err)
return
}
Expand All @@ -545,7 +563,8 @@ func (a *Agent) gatherCandidatesRelay(ctx context.Context, urls []*URL) { //noli
relayProtocol = udp
case a.proxyDialer != nil && url.Proto == ProtoTypeTCP &&
(url.Scheme == SchemeTypeTURN || url.Scheme == SchemeTypeTURNS):
conn, connectErr := a.proxyDialer.Dial(NetworkTypeTCP4.String(), TURNServerAddr)
network = tcpNetworkType
conn, connectErr := a.proxyDialer.Dial(network, TURNServerAddr)
if connectErr != nil {
a.log.Warnf("Failed to Dial TCP Addr %s via proxy dialer: %v", TURNServerAddr, connectErr)
return
Expand All @@ -561,13 +580,14 @@ func (a *Agent) gatherCandidatesRelay(ctx context.Context, urls []*URL) { //noli
locConn = turn.NewSTUNConn(conn)

case url.Proto == ProtoTypeTCP && url.Scheme == SchemeTypeTURN:
tcpAddr, connectErr := net.ResolveTCPAddr(NetworkTypeTCP4.String(), TURNServerAddr)
network = tcpNetworkType
tcpAddr, connectErr := net.ResolveTCPAddr(network, TURNServerAddr)
if connectErr != nil {
a.log.Warnf("Failed to resolve TCP Addr %s: %v", TURNServerAddr, connectErr)
return
}

conn, connectErr := net.DialTCP(NetworkTypeTCP4.String(), nil, tcpAddr)
conn, connectErr := net.DialTCP(network, nil, tcpAddr)
if connectErr != nil {
a.log.Warnf("Failed to Dial TCP Addr %s: %v", TURNServerAddr, connectErr)
return
Expand All @@ -578,6 +598,7 @@ func (a *Agent) gatherCandidatesRelay(ctx context.Context, urls []*URL) { //noli
relayProtocol = tcp
locConn = turn.NewSTUNConn(conn)
case url.Proto == ProtoTypeUDP && url.Scheme == SchemeTypeTURNS:
network = udpNetworkType
udpAddr, connectErr := net.ResolveUDPAddr(network, TURNServerAddr)
if connectErr != nil {
a.log.Warnf("Failed to resolve UDP Addr %s: %v", TURNServerAddr, connectErr)
Expand All @@ -598,7 +619,8 @@ func (a *Agent) gatherCandidatesRelay(ctx context.Context, urls []*URL) { //noli
relayProtocol = "dtls"
locConn = &fakePacketConn{conn}
case url.Proto == ProtoTypeTCP && url.Scheme == SchemeTypeTURNS:
conn, connectErr := tls.Dial(NetworkTypeTCP4.String(), TURNServerAddr, &tls.Config{
network = tcpNetworkType
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, network was hardcoded to NetworkTypeUDP4 even if the url.Proto == ProtoTypeTCP.
Not sure if this was correct?

conn, connectErr := tls.Dial(network, TURNServerAddr, &tls.Config{
InsecureSkipVerify: a.insecureSkipVerify, //nolint:gosec
})
if connectErr != nil {
Expand All @@ -621,6 +643,7 @@ func (a *Agent) gatherCandidatesRelay(ctx context.Context, urls []*URL) { //noli
Password: url.Password,
LoggerFactory: a.loggerFactory,
Net: a.net,
IPv6: ipv6,
})
if err != nil {
closeConnAndLog(locConn, a.log, fmt.Sprintf("Failed to build new turn.Client %s %s", TURNServerAddr, err))
Expand Down Expand Up @@ -676,6 +699,44 @@ func (a *Agent) gatherCandidatesRelay(ctx context.Context, urls []*URL) { //noli
}
a.log.Warnf("Failed to append to localCandidates and run onCandidateHdlr: %v", err)
}
}(*urls[i])
}

url := *urls[i]

var targetIPs []net.IP
var err error
if hostIP := net.ParseIP(url.Host); hostIP != nil {
// literal IP provided
targetIPs = []net.IP{hostIP}
} else {
// hostname provided, perform DNS lookup
targetIPs, err = net.LookupIP(url.Host)
if err != nil {
a.log.Warnf("Failed to lookup host IPs: %v", err)
continue
}
}

ipv4Lookups := 0
ipv6Lookups := 0
for _, ip := range targetIPs {
if ipv4 := ip.To4(); ipv4 != nil && ipv4Lookups < maxIPv4LookupsPerRelay {
ipv4Lookups += 1
var urlCopy URL = url
urlCopy.Host = ipv4.String()

wg.Add(1)
go generateCandidate(urlCopy, false)
} else if ipv6 := ip.To16(); ipv6 != nil && ipv6Lookups < maxIPv6LookupsPerRelay {
ipv6Lookups += 1
var urlCopy URL = url
urlCopy.Host = ipv6.String()

wg.Add(1)
go generateCandidate(urlCopy, true)
} else if ipv6Lookups >= maxIPv6LookupsPerRelay && ipv4Lookups >= maxIPv4LookupsPerRelay {
break
}
}
}
}
6 changes: 6 additions & 0 deletions url.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@ func parseProto(raw string) (ProtoType, error) {
return proto, nil
}

// HostPortString returns a string in the format "host:port" or "[host]:port" if
// a literal IPv6 address is given
func (u URL) HostPortString() string {
return net.JoinHostPort(u.Host, strconv.Itoa(u.Port))
}

func (u URL) String() string {
rawURL := u.Scheme.String() + ":" + net.JoinHostPort(u.Host, strconv.Itoa(u.Port))
if u.Scheme == SchemeTypeTURN || u.Scheme == SchemeTypeTURNS {
Expand Down