1
0
mirror of https://github.com/fumiama/terasu.git synced 2026-06-05 01:00:23 +08:00

feat: new implementations

This commit is contained in:
源文雨
2024-04-19 00:12:45 +09:00
parent b7e45bc58e
commit 719e0c1683
15 changed files with 682 additions and 162 deletions

View File

@@ -13,32 +13,5 @@
## Usage
```go
cli := http.Client{
Transport: &http.Transport{
DialTLS: func(network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
addrs, err := net.DefaultResolver.LookupHost(ctx, host)
if err != nil {
return nil, err
}
conn, err := net.Dial(network, net.JoinHostPort(addrs[0], port))
if err != nil {
return nil, err
}
tlsConn := tls.Client(conn, &tls.Config{
ServerName: host,
})
err = terasu.Use(tlsConn).Handshake()
if err != nil {
_ = tlsConn.Close()
return nil, err
}
return tlsConn, nil
},
},
}
resp, err := cli.Get(url)
terasu.Use(tlsConn).Handshake()
```

View File

@@ -1,47 +1,25 @@
package main
import (
"crypto/tls"
"flag"
"fmt"
"io"
"net"
"net/http"
"os"
"strings"
"github.com/fumiama/terasu"
"github.com/fumiama/terasu/http2"
)
func main() {
u := flag.String("url", "https://huggingface.co/", "the url to get")
ipport := flag.String("dest", "18.65.159.2:443", "host:port")
flag.Parse()
if !strings.HasPrefix(*u, "https://") {
if len(os.Args) != 2 {
fmt.Println("Usage:", os.Args[0], "url")
return
}
if !strings.HasPrefix(os.Args[1], "https://") {
fmt.Println("ERROR: invalid url")
return
}
host := (*u)[8:]
host, _, _ = strings.Cut(host, "/")
cli := http.Client{
Transport: &http.Transport{
DialTLS: func(network, addr string) (net.Conn, error) {
conn, err := net.Dial("tcp", *ipport)
if err != nil {
return nil, err
}
tlsConn := tls.Client(conn, &tls.Config{
ServerName: host,
})
err = terasu.Use(tlsConn).Handshake()
if err != nil {
_ = tlsConn.Close()
return nil, err
}
return tlsConn, nil
},
},
}
resp, err := cli.Get(*u)
resp, err := http2.Get(os.Args[1])
if err != nil {
fmt.Println("ERROR:", err)
return

170
dns/dns.go Normal file
View File

@@ -0,0 +1,170 @@
package dns
import (
"context"
"crypto/tls"
"errors"
"net"
"sync"
"time"
"github.com/fumiama/terasu"
"github.com/fumiama/terasu/ip"
)
var (
ErrNoDNSAvailable = errors.New("no dns available")
)
var DefaultDialer = net.Dialer{
Timeout: time.Second * 8,
}
type dnsstat struct {
A string
E bool
}
type DNSList struct {
sync.RWMutex
m map[string][]*dnsstat
}
// hasrecord no lock, use under lock
func hasrecord(lst []*dnsstat, a string) bool {
for _, addr := range lst {
if addr.A == a {
return true
}
}
return false
}
func (ds *DNSList) Add(m map[string][]string) {
ds.Lock()
defer ds.Unlock()
addList := map[string][]*dnsstat{}
for host, addrs := range m {
for _, addr := range addrs {
if !hasrecord(ds.m[host], addr) && !hasrecord(addList[host], addr) {
addList[host] = append(addList[host], &dnsstat{addr, true})
}
}
}
for host, addrs := range addList {
ds.m[host] = append(ds.m[host], addrs...)
}
}
func (ds *DNSList) DialContext(ctx context.Context, dialer *net.Dialer, firstFragmentLen uint8) (tlsConn *tls.Conn, err error) {
err = ErrNoDNSAvailable
if dialer == nil {
dialer = &DefaultDialer
}
ds.RLock()
defer ds.RUnlock()
if dialer.Timeout != 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, dialer.Timeout)
defer cancel()
}
if !dialer.Deadline.IsZero() {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, dialer.Deadline)
defer cancel()
}
var conn net.Conn
for host, addrs := range ds.m {
for _, addr := range addrs {
if !addr.E {
continue
}
conn, err = dialer.DialContext(ctx, "tcp", addr.A)
if err != nil {
addr.E = false // no need to acquire write lock
continue
}
tlsConn = tls.Client(conn, &tls.Config{ServerName: host})
err = terasu.Use(tlsConn).HandshakeContext(ctx, firstFragmentLen)
if err == nil {
return
}
_ = tlsConn.Close()
addr.E = false // no need to acquire write lock
}
}
return
}
var IPv6Servers = DNSList{
m: map[string][]*dnsstat{
"dot.sb": {
{"[2a09::]:853", true},
{"[2a11::]:853", true},
},
"dns.google": {
{"[2001:4860:4860::8888]:853", true},
{"[2001:4860:4860::8844]:853", true},
},
"cloudflare-dns.com": {
{"[2606:4700:4700::1111]:853", true},
{"[2606:4700:4700::1001]:853", true},
},
"dns.opendns.com": {
{"[2620:119:35::35]:853", true},
{"[2620:119:53::53]:853", true},
},
"dns10.quad9.net": {
{"[2620:fe::10]:853", true},
{"[2620:fe::fe:10]:853", true},
},
},
}
var IPv4Servers = DNSList{
m: map[string][]*dnsstat{
"dot.360.cn": {
{"101.198.192.33:853", true},
{"112.65.69.15:853", true},
{"101.226.4.6:853", true},
{"218.30.118.6:853", true},
{"123.125.81.6:853", true},
{"140.207.198.6:853", true},
},
"dot.sb": {
{"185.222.222.222:853", true},
{"45.11.45.11:853", true},
},
"dns.google": {
{"8.8.8.8:853", true},
{"8.8.4.4:853", true},
},
"cloudflare-dns.com": {
{"1.1.1.1:853", true},
{"1.0.0.1:853", true},
},
"dns.opendns.com": {
{"208.67.222.222:853", true},
{"208.67.220.220:853", true},
},
"dns10.quad9.net": {
{"9.9.9.10:853", true},
{"149.112.112.10:853", true},
},
},
}
var DefaultResolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
if ip.IsIPv6Available.Get() {
return IPv6Servers.DialContext(ctx, nil, terasu.DefaultFirstFragmentLen)
}
return IPv4Servers.DialContext(ctx, nil, terasu.DefaultFirstFragmentLen)
},
}

96
dns/dns_test.go Normal file
View File

@@ -0,0 +1,96 @@
package dns
import (
"context"
"crypto/tls"
"fmt"
"net"
"testing"
"time"
"github.com/fumiama/terasu"
"github.com/fumiama/terasu/ip"
)
func TestResolver(t *testing.T) {
t.Log("IsIPv6Available:", ip.IsIPv6Available.Get())
addrs, err := DefaultResolver.LookupHost(context.TODO(), "huggingface.co")
if err != nil {
t.Fatal(err)
}
t.Log(addrs)
if len(addrs) == 0 {
t.Fail()
}
}
func TestDNS(t *testing.T) {
if ip.IsIPv6Available.Get() {
IPv6Servers.test()
}
IPv4Servers.test()
for i := 0; i < 100; i++ {
addrs, err := DefaultResolver.LookupHost(context.TODO(), "huggingface.co")
if err != nil {
t.Fatal(err)
}
t.Log(addrs)
if len(addrs) == 0 {
t.Fail()
}
time.Sleep(time.Millisecond * 50)
}
}
func TestBadDNS(t *testing.T) {
dotv6serversbak := IPv6Servers.m
dotv4serversbak := IPv4Servers.m
defer func() {
IPv6Servers.m = dotv6serversbak
IPv4Servers.m = dotv4serversbak
}()
if ip.IsIPv6Available.Get() {
IPv6Servers = DNSList{
m: map[string][]*dnsstat{},
}
IPv6Servers.Add(map[string][]string{"test.bad.host": {"169.254.122.111"}})
} else {
IPv4Servers = DNSList{
m: map[string][]*dnsstat{},
}
IPv4Servers.Add(map[string][]string{"test.bad.host": {"169.254.122.111:853"}})
}
for i := 0; i < 10; i++ {
addrs, err := DefaultResolver.LookupHost(context.TODO(), "api.mangacopy.com")
t.Log(err)
if err == nil && len(addrs) > 0 {
t.Fatal("unexpected")
}
time.Sleep(time.Millisecond * 50)
}
}
func (ds *DNSList) test() {
ds.RLock()
defer ds.RUnlock()
for host, addrs := range ds.m {
for _, addr := range addrs {
if !addr.E {
continue
}
fmt.Println("dial:", host, addr.A)
conn, err := net.Dial("tcp", addr.A)
if err != nil {
continue
}
tlsConn := tls.Client(conn, &tls.Config{ServerName: host})
err = terasu.Use(tlsConn).Handshake(4)
_ = tlsConn.Close()
if err == nil {
fmt.Println("succ:", host, addr.A)
continue
}
fmt.Println("fail:", host, addr.A)
}
}
}

8
go.mod
View File

@@ -1,3 +1,11 @@
module github.com/fumiama/terasu
go 1.22.1
require (
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7
golang.org/x/net v0.24.0
)
require golang.org/x/text v0.14.0 // indirect

16
go.sum Normal file
View File

@@ -0,0 +1,16 @@
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1 h1:g4pTnDJUW4VbJ9NvoRfUvdjDrHz/6QhfN/LoIIpICbo=
github.com/FloatTech/ttl v0.0.0-20230307105452-d6f7b2b647d1/go.mod h1:fHZFWGquNXuHttu9dUYoKuNbm3dzLETnIOnm1muSfDs=
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7 h1:S/ferNiehVjNaBMNNBxUjLtVmP/YWD6Yh79RfPv4ehU=
github.com/RomiChan/syncx v0.0.0-20240418144900-b7402ffdebc7/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -293,7 +293,7 @@ func (hs *clientHandshakeState) handshake() error {
// writeHandshakeRecord writes a handshake message to the connection and updates
// the record layer state. If transcript is non-nil the marshalled message is
// written to it.
func (c *_trsconn) writeHandshakeRecord(msg handshakeMessage, transcript transcriptHash) (int, error) {
func (c *_trsconn) writeHandshakeRecord(msg handshakeMessage, transcript transcriptHash, firstFragmentLen uint8) (int, error) {
c.out.Lock()
defer c.out.Unlock()
@@ -305,118 +305,120 @@ func (c *_trsconn) writeHandshakeRecord(msg handshakeMessage, transcript transcr
transcript.Write(data)
}
return c.writeRecordLocked(recordTypeHandshake, data)
return c.writeRecordLocked(recordTypeHandshake, firstFragmentLen, data)
}
func (cout *Conn) clientHandshake(ctx context.Context) (err error) {
c := (*_trsconn)(unsafe.Pointer(cout))
func (cout *Conn) clientHandshake(firstFragmentLen uint8) func(context.Context) error {
return func(ctx context.Context) (err error) {
c := (*_trsconn)(unsafe.Pointer(cout))
if c.config == nil {
c.config = defaultConfig()
}
if c.config == nil {
c.config = defaultConfig()
}
// This may be a renegotiation handshake, in which case some fields
// need to be reset.
c.didResume = false
// This may be a renegotiation handshake, in which case some fields
// need to be reset.
c.didResume = false
hello, ecdheKey, err := c.makeClientHello()
if err != nil {
return err
}
c.serverName = hello.serverName
session, earlySecret, binderKey, err := c.loadSession(hello)
if err != nil {
return err
}
if session != nil {
defer func() {
// If we got a handshake failure when resuming a session, throw away
// the session ticket. See RFC 5077, Section 3.2.
//
// RFC 8446 makes no mention of dropping tickets on failure, but it
// does require servers to abort on invalid binders, so we need to
// delete tickets to recover from a corrupted PSK.
if err != nil {
if cacheKey := c.clientSessionCacheKey(); cacheKey != "" {
c.config.ClientSessionCache.Put(cacheKey, nil)
}
}
}()
}
if _, err := c.writeHandshakeRecord(hello, nil); err != nil {
return err
}
if hello.earlyData {
suite := cipherSuiteTLS13ByID(session.cipherSuite)
transcript := suite.hash.New()
if err := transcriptMsg(hello, transcript); err != nil {
hello, ecdheKey, err := c.makeClientHello()
if err != nil {
return err
}
earlyTrafficSecret := suite.deriveSecret(earlySecret, clientEarlyTrafficLabel, transcript)
quicSetWriteSecret(c, tls.QUICEncryptionLevelEarly, suite.id, earlyTrafficSecret)
}
c.serverName = hello.serverName
// serverHelloMsg is not included in the transcript
msg, err := c.readHandshake(nil)
if err != nil {
return err
}
session, earlySecret, binderKey, err := c.loadSession(hello)
if err != nil {
return err
}
if session != nil {
defer func() {
// If we got a handshake failure when resuming a session, throw away
// the session ticket. See RFC 5077, Section 3.2.
//
// RFC 8446 makes no mention of dropping tickets on failure, but it
// does require servers to abort on invalid binders, so we need to
// delete tickets to recover from a corrupted PSK.
if err != nil {
if cacheKey := c.clientSessionCacheKey(); cacheKey != "" {
c.config.ClientSessionCache.Put(cacheKey, nil)
}
}
}()
}
var serverHello *serverHelloMsg
if !isTypeEqual(msg, "*tls.serverHelloMsg") {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(serverHello, msg)
}
serverHello = (*serverHelloMsg)(*(*unsafe.Pointer)(
unsafe.Add(unsafe.Pointer(&msg), unsafe.Sizeof(uintptr(0))),
))
if _, err := c.writeHandshakeRecord(hello, nil, firstFragmentLen); err != nil {
return err
}
if err := c.pickTLSVersion(serverHello); err != nil {
return err
}
if hello.earlyData {
suite := cipherSuiteTLS13ByID(session.cipherSuite)
transcript := suite.hash.New()
if err := transcriptMsg(hello, transcript); err != nil {
return err
}
earlyTrafficSecret := suite.deriveSecret(earlySecret, clientEarlyTrafficLabel, transcript)
quicSetWriteSecret(c, tls.QUICEncryptionLevelEarly, suite.id, earlyTrafficSecret)
}
// If we are negotiating a protocol version that's lower than what we
// support, check for the server downgrade canaries.
// See RFC 8446, Section 4.1.3.
maxVers := maxSupportedVersion(c.config, roleClient)
tls12Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS12
tls11Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS11
if maxVers == tls.VersionTLS13 && c.vers <= tls.VersionTLS12 && (tls12Downgrade || tls11Downgrade) ||
maxVers == tls.VersionTLS12 && c.vers <= tls.VersionTLS11 && tls11Downgrade {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: downgrade attempt detected, possibly due to a MitM attack or a broken middlebox")
}
// serverHelloMsg is not included in the transcript
msg, err := c.readHandshake(nil)
if err != nil {
return err
}
if c.vers == tls.VersionTLS13 {
hs := &clientHandshakeStateTLS13{
var serverHello *serverHelloMsg
if !isTypeEqual(msg, "*tls.serverHelloMsg") {
c.sendAlert(alertUnexpectedMessage)
return unexpectedMessageError(serverHello, msg)
}
serverHello = (*serverHelloMsg)(*(*unsafe.Pointer)(
unsafe.Add(unsafe.Pointer(&msg), unsafe.Sizeof(uintptr(0))),
))
if err := c.pickTLSVersion(serverHello); err != nil {
return err
}
// If we are negotiating a protocol version that's lower than what we
// support, check for the server downgrade canaries.
// See RFC 8446, Section 4.1.3.
maxVers := maxSupportedVersion(c.config, roleClient)
tls12Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS12
tls11Downgrade := string(serverHello.random[24:]) == downgradeCanaryTLS11
if maxVers == tls.VersionTLS13 && c.vers <= tls.VersionTLS12 && (tls12Downgrade || tls11Downgrade) ||
maxVers == tls.VersionTLS12 && c.vers <= tls.VersionTLS11 && tls11Downgrade {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: downgrade attempt detected, possibly due to a MitM attack or a broken middlebox")
}
if c.vers == tls.VersionTLS13 {
hs := &clientHandshakeStateTLS13{
c: cout,
ctx: ctx,
serverHello: serverHello,
hello: hello,
ecdheKey: ecdheKey,
session: session,
earlySecret: earlySecret,
binderKey: binderKey,
}
// In TLS 1.3, session tickets are delivered after the handshake.
return hs.handshake()
}
hs := &clientHandshakeState{
c: cout,
ctx: ctx,
serverHello: serverHello,
hello: hello,
ecdheKey: ecdheKey,
session: session,
earlySecret: earlySecret,
binderKey: binderKey,
}
// In TLS 1.3, session tickets are delivered after the handshake.
return hs.handshake()
}
if err := hs.handshake(); err != nil {
return err
}
hs := &clientHandshakeState{
c: cout,
ctx: ctx,
serverHello: serverHello,
hello: hello,
session: session,
return nil
}
if err := hs.handshake(); err != nil {
return err
}
return nil
}

97
http/http.go Normal file
View File

@@ -0,0 +1,97 @@
package http
import (
"context"
"crypto/tls"
"errors"
"io"
"net"
"net/http"
"net/url"
"time"
"github.com/FloatTech/ttl"
"github.com/fumiama/terasu"
"github.com/fumiama/terasu/dns"
)
var (
ErrEmptyHostAddress = errors.New("empty host addr")
)
var DefaultDialer = net.Dialer{
Timeout: time.Minute,
}
var lookupTable = ttl.NewCache[string, []string](time.Hour)
var DefaultClient = http.Client{
Transport: &http.Transport{
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
if DefaultDialer.Timeout != 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, DefaultDialer.Timeout)
defer cancel()
}
if !DefaultDialer.Deadline.IsZero() {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, DefaultDialer.Deadline)
defer cancel()
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
addrs := lookupTable.Get(host)
if len(addrs) == 0 {
addrs, err = dns.DefaultResolver.LookupHost(ctx, host)
if err != nil {
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 tlsConn *tls.Conn
for _, a := range addrs {
conn, err := DefaultDialer.DialContext(ctx, network, net.JoinHostPort(a, port))
if err != nil {
continue
}
tlsConn = tls.Client(conn, &tls.Config{
ServerName: host,
})
err = terasu.Use(tlsConn).HandshakeContext(ctx, terasu.DefaultFirstFragmentLen)
if err == nil {
break
}
_ = tlsConn.Close()
tlsConn = nil
}
return tlsConn, err
},
},
}
func Get(url string) (resp *http.Response, err error) {
return DefaultClient.Get(url)
}
func Head(url string) (resp *http.Response, err error) {
return DefaultClient.Head(url)
}
func Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) {
return DefaultClient.Post(url, contentType, body)
}
func PostForm(url string, data url.Values) (resp *http.Response, err error) {
return DefaultClient.PostForm(url, data)
}

28
http/http_test.go Normal file
View File

@@ -0,0 +1,28 @@
package http
import (
"io"
"testing"
)
func TestClientGet(t *testing.T) {
resp, err := Get("https://huggingface.co/")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
t.Log("[T] response code", resp.StatusCode)
for k, vs := range resp.Header {
for _, v := range vs {
t.Log("[T] response header", k+":", v)
}
}
data, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if len(data) == 0 {
t.Fail()
}
t.Log(string(data))
}

96
http2/http2.go Normal file
View File

@@ -0,0 +1,96 @@
package http2
import (
"context"
"crypto/tls"
"errors"
"io"
"net"
"net/http"
"net/url"
"time"
"github.com/FloatTech/ttl"
"golang.org/x/net/http2"
"github.com/fumiama/terasu"
"github.com/fumiama/terasu/dns"
)
var (
ErrEmptyHostAddress = errors.New("empty host addr")
)
var DefaultDialer = net.Dialer{
Timeout: time.Minute,
}
var lookupTable = ttl.NewCache[string, []string](time.Hour)
var DefaultClient = http.Client{
Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
if DefaultDialer.Timeout != 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, DefaultDialer.Timeout)
defer cancel()
}
if !DefaultDialer.Deadline.IsZero() {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, DefaultDialer.Deadline)
defer cancel()
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
addrs := lookupTable.Get(host)
if len(addrs) == 0 {
addrs, err = dns.DefaultResolver.LookupHost(ctx, host)
if err != nil {
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 tlsConn *tls.Conn
for _, a := range addrs {
conn, err := DefaultDialer.DialContext(ctx, network, net.JoinHostPort(a, port))
if err != nil {
continue
}
tlsConn = tls.Client(conn, cfg)
err = terasu.Use(tlsConn).HandshakeContext(ctx, terasu.DefaultFirstFragmentLen)
if err == nil {
break
}
_ = tlsConn.Close()
tlsConn = nil
}
return tlsConn, err
},
},
}
func Get(url string) (resp *http.Response, err error) {
return DefaultClient.Get(url)
}
func Head(url string) (resp *http.Response, err error) {
return DefaultClient.Head(url)
}
func Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) {
return DefaultClient.Post(url, contentType, body)
}
func PostForm(url string, data url.Values) (resp *http.Response, err error) {
return DefaultClient.PostForm(url, data)
}

28
http2/http2_test.go Normal file
View File

@@ -0,0 +1,28 @@
package http2
import (
"io"
"testing"
)
func TestClientGet(t *testing.T) {
resp, err := Get("https://huggingface.co/")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
t.Log("[T] response code", resp.StatusCode)
for k, vs := range resp.Header {
for _, v := range vs {
t.Log("[T] response header", k+":", v)
}
}
data, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatal(err)
}
if len(data) == 0 {
t.Fail()
}
t.Log(string(data))
}

28
ip/ipv6.go Normal file
View File

@@ -0,0 +1,28 @@
package ip
import (
"context"
"net/http"
"time"
"github.com/RomiChan/syncx"
)
var IsIPv6Available = syncx.Lazy[bool]{Init: func() bool {
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", "http://v6.ipv6-test.com/json/widgetdata.php?callback=?", nil)
if err != nil {
return false
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return false
}
_ = resp.Body.Close()
return true
}}
func init() {
go IsIPv6Available.Get()
}

View File

@@ -6,25 +6,27 @@ import (
"unsafe"
)
var DefaultFirstFragmentLen uint8 = 4
// Use terasu in this TLS conn
func Use(conn *tls.Conn) *Conn {
return (*Conn)(conn)
}
// Handshake do terasu handshake in this TLS conn
func (conn *Conn) Handshake() error {
func (conn *Conn) Handshake(firstFragmentLen uint8) error {
expose := (*_trsconn)(unsafe.Pointer(conn))
fnbak := expose.handshakeFn
expose.handshakeFn = conn.clientHandshake
expose.handshakeFn = conn.clientHandshake(firstFragmentLen)
defer func() { expose.handshakeFn = fnbak }()
return (*tls.Conn)(conn).Handshake()
}
// Handshake do terasu handshake with ctx in this TLS conn
func (conn *Conn) HandshakeContext(ctx context.Context) error {
func (conn *Conn) HandshakeContext(ctx context.Context, firstFragmentLen uint8) error {
expose := (*_trsconn)(unsafe.Pointer(conn))
fnbak := expose.handshakeFn
expose.handshakeFn = conn.clientHandshake
expose.handshakeFn = conn.clientHandshake(firstFragmentLen)
defer func() { expose.handshakeFn = fnbak }()
return (*tls.Conn)(conn).HandshakeContext(ctx)
}

View File

@@ -21,7 +21,7 @@ func TestHTTPDialTLS13(t *testing.T) {
ServerName: "huggingface.co",
InsecureSkipVerify: true,
})
err = Use(tlsConn).Handshake()
err = Use(tlsConn).Handshake(4)
if err != nil {
_ = tlsConn.Close()
return nil, err
@@ -59,7 +59,7 @@ func TestHTTPDialTLS12(t *testing.T) {
InsecureSkipVerify: true,
MaxVersion: tls.VersionTLS12,
})
err = Use(tlsConn).Handshake()
err = Use(tlsConn).Handshake(4)
if err != nil {
_ = tlsConn.Close()
return nil, err

6
tls.go
View File

@@ -13,8 +13,6 @@ import (
_ "unsafe"
)
const firstFragmentLen = 4
type recordType uint8
const (
@@ -193,7 +191,7 @@ func (c *_trsconn) sendAlertLocked(err alert) error {
// writeRecordLocked writes a TLS record with the given type and payload to the
// connection and updates the record layer state.
func (c *_trsconn) writeRecordLocked(typ recordType, data []byte) (int, error) {
func (c *_trsconn) writeRecordLocked(typ recordType, firstFragmentLen uint8, data []byte) (int, error) {
if c.quic != nil {
return tlsWriteRecordLocked(c, typ, data)
}
@@ -219,7 +217,7 @@ func (c *_trsconn) writeRecordLocked(typ recordType, data []byte) (int, error) {
m = maxPayload
}
} else {
m = firstFragmentLen
m = int(firstFragmentLen)
}
_, outBuf = sliceForAppend(outBuf[:0], recordHeaderLen)