feat: support independent tcp4, tcp6, udp4, udp6 connectivity check

This commit is contained in:
mzz2017
2023-02-08 20:15:24 +08:00
committed by mzz
parent 551e79d9e5
commit 5e7b68822a
26 changed files with 738 additions and 222 deletions

View File

@ -0,0 +1,39 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io>
*/
package netutils
import (
"context"
"golang.org/x/net/proxy"
"net"
)
type ContextDialer struct {
Dialer proxy.Dialer
}
func (d *ContextDialer) DialContext(ctx context.Context, network, addr string) (c net.Conn, err error) {
var done = make(chan struct{})
go func() {
c, err = d.Dialer.Dial(network, addr)
if err != nil {
close(done)
return
}
select {
case <-ctx.Done():
_ = c.Close()
default:
close(done)
}
}()
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-done:
return c, err
}
}

102
common/netutils/dns.go Normal file
View File

@ -0,0 +1,102 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io>
*/
package netutils
import (
"context"
"fmt"
"github.com/mzz2017/softwind/pool"
"golang.org/x/net/dns/dnsmessage"
"golang.org/x/net/proxy"
"net/netip"
"strings"
)
func ResolveNetip(ctx context.Context, d proxy.Dialer, dns netip.AddrPort, host string, typ dnsmessage.Type) (addrs []netip.Addr, err error) {
if addr, err := netip.ParseAddr(host); err == nil {
if (addr.Is4() || addr.Is4In6()) && typ == dnsmessage.TypeA {
return []netip.Addr{addr}, nil
} else if addr.Is6() && typ == dnsmessage.TypeAAAA {
return []netip.Addr{addr}, nil
}
return nil, nil
}
switch typ {
case dnsmessage.TypeA, dnsmessage.TypeAAAA:
default:
return nil, fmt.Errorf("only support to lookup A/AAAA record")
}
// Build DNS req.
builder := dnsmessage.NewBuilder(nil, dnsmessage.Header{})
if err = builder.StartQuestions(); err != nil {
return nil, err
}
fqdn := host
if !strings.HasSuffix(fqdn, ".") {
fqdn += "."
}
if err = builder.Question(dnsmessage.Question{
Name: dnsmessage.MustNewName(fqdn),
Type: typ,
Class: dnsmessage.ClassINET,
}); err != nil {
return nil, err
}
b, err := builder.Finish()
if err != nil {
return nil, err
}
// Dial and write.
cd := ContextDialer{d}
c, err := cd.DialContext(ctx, "udp", dns.String())
if err != nil {
return nil, err
}
defer c.Close()
_, err = c.Write(b)
if err != nil {
return nil, err
}
ch := make(chan error, 1)
go func() {
buf := pool.Get(512)
n, err := c.Read(buf)
if err != nil {
ch <- err
return
}
// Resolve DNS response and extract A/AAAA record.
var msg dnsmessage.Message
if err = msg.Unpack(buf[:n]); err != nil {
ch <- err
return
}
for _, ans := range msg.Answers {
if ans.Header.Type != typ {
continue
}
switch typ {
case dnsmessage.TypeA:
a := ans.Body.(*dnsmessage.AResource)
addrs = append(addrs, netip.AddrFrom4(a.A))
case dnsmessage.TypeAAAA:
a := ans.Body.(*dnsmessage.AAAAResource)
addrs = append(addrs, netip.AddrFrom16(a.AAAA))
}
}
ch <- nil
}()
select {
case <-ctx.Done():
return nil, fmt.Errorf("timeout")
case err = <-ch:
if err != nil {
return nil, err
}
return addrs, nil
}
}

25
common/netutils/url.go Normal file
View File

@ -0,0 +1,25 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) since 2023, mzz2017 <mzz@tuta.io>
*/
package netutils
import "net/url"
type URL struct {
*url.URL
}
func (u *URL) Port() string {
if port := u.URL.Port(); port != "" {
return port
}
switch u.Scheme {
case "http":
return "80"
case "https":
return "443"
}
return ""
}