feat: add DoT

This commit is contained in:
源文雨 2024-04-15 17:10:06 +09:00
parent 9cf8968a82
commit 43281f26fe
9 changed files with 325 additions and 60 deletions

View File

@ -1,3 +1,6 @@
# Tested under Apple M1.
# Edit it if you use different platform.
PROJECT_NAME := comandy PROJECT_NAME := comandy
BUILD_PATH := build BUILD_PATH := build
GOOS := android GOOS := android
@ -11,12 +14,12 @@ CGO_ENABLED := 1
GO_SRC := $(shell find . -name '*.go') GO_SRC := $(shell find . -name '*.go')
NDK_TOOLCHAIN := ~/Library/Android/sdk/ndk/$(NDK_VERSION)/toolchains/llvm/prebuilt/$(BUILD_MACHINE)-$(BUILD_ARCH) NDK_TOOLCHAIN := ~/Library/Android/sdk/ndk/$(NDK_VERSION)/toolchains/llvm/prebuilt/$(BUILD_MACHINE)-$(BUILD_ARCH)
CC := $(NDK_TOOLCHAIN)/bin/aarch64-linux-$(TARGET_SDK)-clang CC := $(NDK_TOOLCHAIN)/bin/aarch64-linux-$(TARGET_SDK)-clang
TEST_OUTPUT = '$(shell cd $(BUILD_PATH) && ./test)' TEST_OUTPUT = '$(shell cd $(BUILD_PATH) && ./test | head -c 12)'
TEST_EXPECTED = '{"code":500,"data":"aW52YWxpZCB1cmwgJyc="}' TEST_EXPECTED = '{"code":200,'
all: shared all: shared
shared: $(GO_SRC) dir shared: $(GO_SRC) dir tidy
GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=$(CGO_ENABLED) NDK_TOOLCHAIN=$(NDK_TOOLCHAIN) CC=$(CC) go build -buildmode=c-shared -o $(BUILD_PATH)/lib$(PROJECT_NAME).so $(GO_SRC) GOOS=$(GOOS) GOARCH=$(GOARCH) CGO_ENABLED=$(CGO_ENABLED) NDK_TOOLCHAIN=$(NDK_TOOLCHAIN) CC=$(CC) go build -buildmode=c-shared -o $(BUILD_PATH)/lib$(PROJECT_NAME).so $(GO_SRC)
test: dir test: dir
@GOOS=$(BUILD_MACHINE) CC=cc NDK_TOOLCHAIN="" $(MAKE) -e shared @GOOS=$(BUILD_MACHINE) CC=cc NDK_TOOLCHAIN="" $(MAKE) -e shared
@ -27,7 +30,12 @@ runtest: test
else \ else \
echo "test failed, expected:" $(TEST_EXPECTED) "but got:" $(TEST_OUTPUT); \ echo "test failed, expected:" $(TEST_EXPECTED) "but got:" $(TEST_OUTPUT); \
fi fi
tidy:
go mod tidy
dir: dir:
@if [ ! -d "$(BUILD_PATH)" ]; then mkdir $(BUILD_PATH); fi @if [ ! -d "$(BUILD_PATH)" ]; then mkdir $(BUILD_PATH); fi
clean: clean:
@if [ -d "$(BUILD_PATH)" ]; then rm -rf $(BUILD_PATH)/lib$(PROJECT_NAME).*; fi @if [ -d "$(BUILD_PATH)" ]; then \
rm -rf $(BUILD_PATH)/lib$(PROJECT_NAME).*; \
rm -rf $(BUILD_PATH)/test; \
fi

View File

@ -3,6 +3,11 @@
#include "libcomandy.h" #include "libcomandy.h"
int main() { int main() {
char* msg = request("{}"); puts(request("{\"method\":\"GET\","
puts(msg); "\"url\":\"https://i.pximg.net/img-master/img/2012/04/04/21/24/46/26339586_p0_master1200.jpg\","
"\"headers\":{"
"\"Referer\":\"https://www.pixiv.net/\","
"\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0\""
"}"
"}"));
} }

138
dns.go Normal file
View File

@ -0,0 +1,138 @@
package main
import (
"context"
"crypto/tls"
"net"
"sync"
"github.com/fumiama/terasu"
)
type dnsstat struct {
A string
E bool
}
type dnsservers 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 *dnsservers) 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 *dnsservers) dial(ctx context.Context) (tlsConn *tls.Conn, err error) {
ds.RLock()
defer ds.RUnlock()
var conn net.Conn
for host, addrs := range ds.m {
for _, addr := range addrs {
if !addr.E {
continue
}
conn, err = net.Dial("tcp", addr.A)
if err != nil {
continue
}
tlsConn = terasu.Use(tls.Client(conn, &tls.Config{ServerName: host}))
err = tlsConn.HandshakeContext(ctx)
if err == nil {
return
}
addr.E = false // no need to acquire write lock
}
}
return
}
var dotv6servers = dnsservers{
m: map[string][]*dnsstat{
"dns.alidns.com": {
{"[2400:3200::1]:853", true},
{"[2400:3200:baba::1]:853", true},
},
"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.umbrella.com": {
{"[2620:0:ccc::2]:853", true},
{"[2620:0:ccd::2]:853", true},
},
"dns10.quad9.net": {
{"[2620:fe::10]:853", true},
{"[2620:fe::fe:10]:853", true},
},
},
}
var dotv4servers = dnsservers{
m: map[string][]*dnsstat{
"dns.alidns.com": {
{"223.5.5.5:853", true},
{"223.6.6.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.umbrella.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 resolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
if canUseIPv6.Get() {
return dotv6servers.dial(ctx)
}
return dotv4servers.dial(ctx)
},
}

51
dns_test.go Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"context"
"crypto/tls"
"net"
"testing"
"github.com/fumiama/terasu"
)
func TestResolver(t *testing.T) {
t.Log("canUseIPv6:", canUseIPv6.Get())
addrs, err := resolver.LookupHost(context.TODO(), "dns.google")
if err != nil {
t.Fatal(err)
}
t.Log(addrs)
t.Fail()
}
func TestDNS(t *testing.T) {
if canUseIPv6.Get() {
dotv6servers.test(t)
}
dotv4servers.test(t)
}
func (ds *dnsservers) test(t *testing.T) {
ds.RLock()
defer ds.RUnlock()
for host, addrs := range ds.m {
for _, addr := range addrs {
if !addr.E {
continue
}
conn, err := net.Dial("tcp", addr.A)
if err != nil {
continue
}
tlsConn := terasu.Use(tls.Client(conn, &tls.Config{ServerName: host}))
err = tlsConn.Handshake()
_ = tlsConn.Close()
if err == nil {
t.Log("succ:", host, addr.A)
continue
}
t.Fatal("fail:", host, addr.A)
}
}
}

5
go.mod
View File

@ -2,4 +2,7 @@ module comandy
go 1.22.1 go 1.22.1
require github.com/fumiama/terasu v0.0.0-20240414143030-44fae3a81905 require (
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e
github.com/fumiama/terasu v0.0.0-20240414143030-44fae3a81905
)

2
go.sum
View File

@ -1,2 +1,4 @@
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e h1:wR3MXQ3VbUlPKOOUwLOYgh/QaJThBTYtsl673O3lqSA=
github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e/go.mod h1:vD7Ra3Q9onRtojoY5sMCLQ7JBgjUsrXDnDKyFxqpf9w=
github.com/fumiama/terasu v0.0.0-20240414143030-44fae3a81905 h1:PHf84+ujLpFGJbfytrwZT6/D7KojmjFm5Itv6te6WUA= github.com/fumiama/terasu v0.0.0-20240414143030-44fae3a81905 h1:PHf84+ujLpFGJbfytrwZT6/D7KojmjFm5Itv6te6WUA=
github.com/fumiama/terasu v0.0.0-20240414143030-44fae3a81905/go.mod h1:BFl0X1+rGJf8bLHl/kO+v05ryHrj/R4kyCrK89NvegA= github.com/fumiama/terasu v0.0.0-20240414143030-44fae3a81905/go.mod h1:BFl0X1+rGJf8bLHl/kO+v05ryHrj/R4kyCrK89NvegA=

61
http.go Normal file
View File

@ -0,0 +1,61 @@
package main
import (
"context"
"crypto/tls"
"encoding/base64"
"encoding/json"
"net"
"net/http"
"strings"
"time"
"github.com/fumiama/terasu"
)
var dialer = net.Dialer{
Timeout: time.Minute,
}
var cli = http.Client{
Transport: &http.Transport{
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, "tcp", addr)
if err != nil {
return nil, err
}
host, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
return terasu.Use(tls.Client(conn, &tls.Config{
ServerName: host,
InsecureSkipVerify: true,
})), nil
},
},
}
type capsule struct {
C int `json:"code,omitempty"`
M string `json:"method,omitempty"`
U string `json:"url,omitempty"`
H map[string]any `json:"headers,omitempty"`
D string `json:"data,omitempty"`
}
func (r *capsule) printerr(err error) string {
buf := strings.Builder{}
r.C = http.StatusInternalServerError
r.D = base64.StdEncoding.EncodeToString(stringToBytes(err.Error()))
_ = json.NewEncoder(&buf).Encode(r)
return buf.String()
}
func (r *capsule) printstrerr(err string) string {
buf := strings.Builder{}
r.C = http.StatusInternalServerError
r.D = base64.StdEncoding.EncodeToString(stringToBytes(err))
_ = json.NewEncoder(&buf).Encode(r)
return buf.String()
}

16
ipv6.go Normal file
View File

@ -0,0 +1,16 @@
package main
import (
"net/http"
"github.com/RomiChan/syncx"
)
var canUseIPv6 = syncx.Lazy[bool]{Init: func() bool {
resp, err := http.Get("http://v6.ipv6-test.com/json/widgetdata.php?callback=?")
if err != nil {
return false
}
_ = resp.Body.Close()
return true
}}

81
main.go
View File

@ -3,69 +3,46 @@ package main
import "C" import "C"
import ( import (
"context"
"crypto/tls"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"io" "io"
"net"
"net/http" "net/http"
"reflect" "reflect"
"strings" "strings"
"time"
"github.com/fumiama/terasu"
) )
func main() {} func main() {}
var dialer = net.Dialer{ // para: json of map[host string][]addr:port string
Timeout: time.Minute, //
} //export add_dns
func add_dns(para *C.char, is_ipv6 C.int) *C.char {
var cli = http.Client{ m := map[string][]string{}
Transport: &http.Transport{ err := json.Unmarshal(stringToBytes(C.GoString(para)), &m)
DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
conn, err := dialer.DialContext(ctx, "tcp", addr)
if err != nil { if err != nil {
return nil, err return C.CString(err.Error())
} }
host, _, err := net.SplitHostPort(addr) if is_ipv6 != 0 {
if err != nil { if !canUseIPv6.Get() {
return nil, err return C.CString("cannot use ipv6")
} }
return terasu.Use(tls.Client(conn, &tls.Config{ dotv6servers.add(m)
ServerName: host, return nil
InsecureSkipVerify: true, }
})), nil dotv4servers.add(m)
}, return nil
},
}
type capsule struct {
C int `json:"code,omitempty"`
M string `json:"method,omitempty"`
U string `json:"url,omitempty"`
H map[string]any `json:"headers,omitempty"`
D string `json:"data,omitempty"`
}
func (r *capsule) printerr(err error) string {
buf := strings.Builder{}
r.C = http.StatusInternalServerError
r.D = base64.StdEncoding.EncodeToString(stringToBytes(err.Error()))
_ = json.NewEncoder(&buf).Encode(r)
return buf.String()
}
func (r *capsule) printstrerr(err string) string {
buf := strings.Builder{}
r.C = http.StatusInternalServerError
r.D = base64.StdEncoding.EncodeToString(stringToBytes(err))
_ = json.NewEncoder(&buf).Encode(r)
return buf.String()
} }
// para:
//
// request("{\"method\":\"GET\","
// "\"url\":\"https://i.pximg.net/img-master/img/2012/04/04/21/24/46/26339586_p0_master1200.jpg\","
// "\"headers\":{"
// "\"Referer\":\"https://www.pixiv.net/\","
// "\"User-Agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0\""
// "}"
// "}");
//
//export request //export request
func request(para *C.char) *C.char { func request(para *C.char) *C.char {
r := capsule{} r := capsule{}
@ -73,13 +50,17 @@ func request(para *C.char) *C.char {
if err != nil { if err != nil {
return C.CString(r.printerr(err)) return C.CString(r.printerr(err))
} }
if r.U == "" || !strings.HasPrefix(r.U, "http") { if r.U == "" || !strings.HasPrefix(r.U, "https://") {
return C.CString(r.printstrerr("invalid url '" + r.U + "'")) return C.CString(r.printstrerr("invalid url '" + r.U + "'"))
} }
if r.M != "GET" && r.M != "POST" && r.M != "DELETE" { if r.M != "GET" && r.M != "POST" && r.M != "DELETE" {
return C.CString(r.printstrerr("invalid method '" + r.U + "'")) return C.CString(r.printstrerr("invalid method '" + r.U + "'"))
} }
req, err := http.NewRequest(r.M, r.U, strings.NewReader(r.D)) var body io.Reader
if len(r.D) > 0 {
body = strings.NewReader(r.D)
}
req, err := http.NewRequest(r.M, r.U, body)
if err != nil { if err != nil {
return C.CString(r.printerr(err)) return C.CString(r.printerr(err))
} }