mirror of
https://github.com/daeuniverse/dae.git
synced 2025-07-10 07:48:48 +07:00
feat: support independent tcp4, tcp6, udp4, udp6 connectivity check
This commit is contained in:
39
common/netutils/context_dialer.go
Normal file
39
common/netutils/context_dialer.go
Normal 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
102
common/netutils/dns.go
Normal 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
25
common/netutils/url.go
Normal 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 ""
|
||||
}
|
Reference in New Issue
Block a user