mirror of
https://github.com/fumiama/terasu.git
synced 2026-06-10 21:24:46 +08:00
feat: add plugin
This commit is contained in:
39
dns/dns.go
39
dns/dns.go
@@ -13,6 +13,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/fumiama/terasu"
|
||||
"github.com/fumiama/terasu/dialer"
|
||||
"github.com/fumiama/terasu/doh"
|
||||
"github.com/fumiama/terasu/ip"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -24,15 +26,6 @@ var (
|
||||
ErrSuccess = errors.New("success")
|
||||
)
|
||||
|
||||
var dnsDialer = net.Dialer{
|
||||
Timeout: time.Second * 4,
|
||||
}
|
||||
|
||||
// SetTimeout ...
|
||||
func SetTimeout(t time.Duration) {
|
||||
dnsDialer.Timeout = t
|
||||
}
|
||||
|
||||
type dnsstat struct {
|
||||
addr string
|
||||
en bool
|
||||
@@ -173,9 +166,9 @@ func (ds *List) lookupHostDoH(ctx context.Context, host string) (hosts []string,
|
||||
if !addr.enabled() || !addr.ishttps() { // disabled or is not DoH
|
||||
continue
|
||||
}
|
||||
jr, err := lookupdoh(ctx, addr.addr, host)
|
||||
jr, err := doh.LookupDoH(ctx, addr.addr, host)
|
||||
if err == nil {
|
||||
hosts = jr.hosts()
|
||||
hosts = jr.Hosts()
|
||||
if len(hosts) > 0 {
|
||||
// this is a successful server, keep it
|
||||
addr.keepit()
|
||||
@@ -203,11 +196,11 @@ func (ds *List) lookupHostDoH(ctx context.Context, host string) (hosts []string,
|
||||
}
|
||||
|
||||
// DialContext ...
|
||||
func (ds *List) DialContext(ctx context.Context, dialer *net.Dialer) (tlsConn *tls.Conn, err error) {
|
||||
func (ds *List) DialContext(ctx context.Context, d *net.Dialer) (tlsConn *tls.Conn, err error) {
|
||||
err = ErrNoDNSAvailable
|
||||
|
||||
if dialer == nil {
|
||||
dialer = &dnsDialer
|
||||
if d == nil {
|
||||
d = &dialer.DefaultDialer
|
||||
}
|
||||
|
||||
ds.RLock()
|
||||
@@ -220,16 +213,16 @@ func (ds *List) DialContext(ctx context.Context, dialer *net.Dialer) (tlsConn *t
|
||||
continue
|
||||
}
|
||||
logrus.Debugln("[terasu.dns] -> dial", host, addr)
|
||||
if dialer.Timeout != 0 {
|
||||
if d.Timeout != 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(context.Background(), dialer.Timeout)
|
||||
ctx, cancel = context.WithTimeout(context.Background(), d.Timeout)
|
||||
defer cancel()
|
||||
} else if !dialer.Deadline.IsZero() {
|
||||
} else if !d.Deadline.IsZero() {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithDeadline(context.Background(), dialer.Deadline)
|
||||
ctx, cancel = context.WithDeadline(context.Background(), d.Deadline)
|
||||
defer cancel()
|
||||
}
|
||||
conn, err = dialer.DialContext(ctx, "tcp", addr.addr)
|
||||
conn, err = d.DialContext(ctx, "tcp", addr.addr)
|
||||
if err != nil {
|
||||
logrus.Debugln("[terasu.dns] -- dial tcp", host, addr, "err:", err)
|
||||
if !errors.Is(err, context.Canceled) &&
|
||||
@@ -247,13 +240,13 @@ func (ds *List) DialContext(ctx context.Context, dialer *net.Dialer) (tlsConn *t
|
||||
NextProtos: []string{"dns"},
|
||||
})
|
||||
// re-init ctx due to deadline settings in tcp dial
|
||||
if dialer.Timeout != 0 {
|
||||
if d.Timeout != 0 {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithTimeout(context.Background(), dialer.Timeout)
|
||||
ctx, cancel = context.WithTimeout(context.Background(), d.Timeout)
|
||||
defer cancel()
|
||||
} else if !dialer.Deadline.IsZero() {
|
||||
} else if !d.Deadline.IsZero() {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithDeadline(context.Background(), dialer.Deadline)
|
||||
ctx, cancel = context.WithDeadline(context.Background(), d.Deadline)
|
||||
defer cancel()
|
||||
}
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fumiama/terasu"
|
||||
"github.com/fumiama/terasu/dialer"
|
||||
"github.com/fumiama/terasu/ip"
|
||||
)
|
||||
|
||||
@@ -110,7 +110,7 @@ func (ds *List) test() {
|
||||
continue
|
||||
}
|
||||
fmt.Println("dial:", host, addr.addr)
|
||||
conn, err := net.Dial("tcp", addr.addr)
|
||||
conn, err := dialer.DefaultDialer.Dial("tcp", addr.addr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
179
dns/doh.go
179
dns/doh.go
@@ -1,179 +0,0 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
|
||||
"github.com/fumiama/terasu"
|
||||
"github.com/fumiama/terasu/ip"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEmptyHostAddress ...
|
||||
ErrEmptyHostAddress = errors.New("empty host addr")
|
||||
)
|
||||
|
||||
type recordType uint16
|
||||
|
||||
const (
|
||||
recordTypeNone recordType = 0
|
||||
recordTypeA recordType = 1
|
||||
recordTypeAAAA recordType = 28
|
||||
)
|
||||
|
||||
// dohjsonresponse represents the JSON response structure for DNS over HTTPS (DoH) queries.
|
||||
// It contains DNS query results and metadata about the response.
|
||||
type dohjsonresponse struct {
|
||||
// Status indicates the DNS query status code (0 = NOERROR, etc.)
|
||||
Status uint32
|
||||
// TC indicates whether the response was truncated (true if truncated)
|
||||
TC bool
|
||||
// RD indicates whether recursion was requested in the query
|
||||
RD bool
|
||||
// RA indicates whether the server supports recursion
|
||||
RA bool
|
||||
// AD indicates whether the response was authenticated (DNSSEC)
|
||||
AD bool
|
||||
// CD indicates whether the client requested that DNSSEC validation be disabled
|
||||
CD bool
|
||||
// Question contains the DNS query question section with name and type
|
||||
Question []struct {
|
||||
// Name is the domain name being queried
|
||||
Name string `json:"name"`
|
||||
// Type is the DNS record type being requested (A, AAAA, etc.)
|
||||
Type recordType `json:"type"`
|
||||
}
|
||||
// Answer contains the DNS response answer section with resource records
|
||||
Answer []struct {
|
||||
// Name is the domain name for this resource record
|
||||
Name string `json:"name"`
|
||||
// Type is the DNS record type (A, AAAA, etc.)
|
||||
Type recordType `json:"type"`
|
||||
// TTL is the time-to-live value for this resource record in seconds
|
||||
TTL uint16
|
||||
// Data is the textual representation of the resource record data
|
||||
Data string `json:"data"`
|
||||
}
|
||||
// EdnsClientSubnet is the EDNS client subnet information for geolocation
|
||||
EdnsClientSubnet string `json:"edns_client_subnet"`
|
||||
// Comment is an optional comment field for additional information
|
||||
Comment string
|
||||
}
|
||||
|
||||
func (jr *dohjsonresponse) hosts() []string {
|
||||
if len(jr.Answer) == 0 {
|
||||
return nil
|
||||
}
|
||||
hosts := make([]string, 0, len(jr.Answer))
|
||||
for _, ans := range jr.Answer {
|
||||
if ans.Type == recordTypeA || ans.Type == recordTypeAAAA {
|
||||
hosts = append(hosts, ans.Data)
|
||||
}
|
||||
}
|
||||
return hosts
|
||||
}
|
||||
|
||||
var trsHTTP2ClientWithSystemDNS = http.Client{
|
||||
Transport: &http2.Transport{
|
||||
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrs := lookupTable.Get(host)
|
||||
if len(addrs) == 0 {
|
||||
addrs, err = net.DefaultResolver.LookupHost(ctx, host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lookupTable.Set(host, addrs)
|
||||
}
|
||||
if len(addr) == 0 {
|
||||
return nil, ErrEmptyHostAddress
|
||||
}
|
||||
var conn net.Conn
|
||||
var tlsConn *tls.Conn
|
||||
for _, a := range addrs {
|
||||
conn, err = dnsDialer.DialContext(ctx, network, net.JoinHostPort(a, port))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
tlsConn = tls.Client(terasu.NewConn(conn), cfg)
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
_ = tlsConn.Close()
|
||||
tlsConn = nil
|
||||
conn, err = dnsDialer.DialContext(ctx, network, net.JoinHostPort(a, port))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
tlsConn = tls.Client(terasu.NewConn(conn), cfg)
|
||||
err = tlsConn.HandshakeContext(ctx)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
_ = tlsConn.Close()
|
||||
tlsConn = nil
|
||||
}
|
||||
return tlsConn, err
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func lookupdoh(ctx context.Context, server, u string) (jr dohjsonresponse, err error) {
|
||||
jr, err = lookupdohwithtype(ctx, server, u, preferreddohtype())
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if ip.IsIPv6Available {
|
||||
jr, err = lookupdohwithtype(ctx, server, u, recordTypeA)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func lookupdohwithtype(ctx context.Context, server, u string, typ recordType) (jr dohjsonresponse, err error) {
|
||||
sb := strings.Builder{}
|
||||
sb.WriteString(server)
|
||||
sb.WriteString("?name=")
|
||||
sb.WriteString(url.QueryEscape(u))
|
||||
if typ != recordTypeNone {
|
||||
sb.WriteString("&type=")
|
||||
sb.WriteString(strconv.Itoa(int(typ)))
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", sb.String(), nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Add("accept", "application/dns-json")
|
||||
resp, err := trsHTTP2ClientWithSystemDNS.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
err = json.NewDecoder(resp.Body).Decode(&jr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if jr.Status != 0 {
|
||||
err = errors.New("comment: " + jr.Comment)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func preferreddohtype() recordType {
|
||||
if ip.IsIPv6Available {
|
||||
return recordTypeAAAA
|
||||
}
|
||||
return recordTypeA
|
||||
}
|
||||
19
dns/tls.go
Normal file
19
dns/tls.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
mtls "github.com/fumiama/terasu/tls"
|
||||
)
|
||||
|
||||
// DialTLSContext fills http.Transport method with terasu and DNS
|
||||
func DialTLSContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return DialTLSContextWithConfig(ctx, network, addr, nil)
|
||||
}
|
||||
|
||||
// DialTLSContextWithConfig fills http2.Transport method with terasu and DNS
|
||||
func DialTLSContextWithConfig(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||
return mtls.DialTLSContextCL(ctx, network, addr, cfg, nil)
|
||||
}
|
||||
Reference in New Issue
Block a user