feat: add DoT
This commit is contained in:
parent
9cf8968a82
commit
43281f26fe
16
Makefile
16
Makefile
@ -1,3 +1,6 @@
|
||||
# Tested under Apple M1.
|
||||
# Edit it if you use different platform.
|
||||
|
||||
PROJECT_NAME := comandy
|
||||
BUILD_PATH := build
|
||||
GOOS := android
|
||||
@ -11,12 +14,12 @@ CGO_ENABLED := 1
|
||||
GO_SRC := $(shell find . -name '*.go')
|
||||
NDK_TOOLCHAIN := ~/Library/Android/sdk/ndk/$(NDK_VERSION)/toolchains/llvm/prebuilt/$(BUILD_MACHINE)-$(BUILD_ARCH)
|
||||
CC := $(NDK_TOOLCHAIN)/bin/aarch64-linux-$(TARGET_SDK)-clang
|
||||
TEST_OUTPUT = '$(shell cd $(BUILD_PATH) && ./test)'
|
||||
TEST_EXPECTED = '{"code":500,"data":"aW52YWxpZCB1cmwgJyc="}'
|
||||
TEST_OUTPUT = '$(shell cd $(BUILD_PATH) && ./test | head -c 12)'
|
||||
TEST_EXPECTED = '{"code":200,'
|
||||
|
||||
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)
|
||||
test: dir
|
||||
@GOOS=$(BUILD_MACHINE) CC=cc NDK_TOOLCHAIN="" $(MAKE) -e shared
|
||||
@ -27,7 +30,12 @@ runtest: test
|
||||
else \
|
||||
echo "test failed, expected:" $(TEST_EXPECTED) "but got:" $(TEST_OUTPUT); \
|
||||
fi
|
||||
tidy:
|
||||
go mod tidy
|
||||
dir:
|
||||
@if [ ! -d "$(BUILD_PATH)" ]; then mkdir $(BUILD_PATH); fi
|
||||
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
|
||||
|
@ -3,6 +3,11 @@
|
||||
#include "libcomandy.h"
|
||||
|
||||
int main() {
|
||||
char* msg = request("{}");
|
||||
puts(msg);
|
||||
puts(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\""
|
||||
"}"
|
||||
"}"));
|
||||
}
|
||||
|
138
dns.go
Normal file
138
dns.go
Normal 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
51
dns_test.go
Normal 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
5
go.mod
@ -2,4 +2,7 @@ module comandy
|
||||
|
||||
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
2
go.sum
@ -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/go.mod h1:BFl0X1+rGJf8bLHl/kO+v05ryHrj/R4kyCrK89NvegA=
|
||||
|
61
http.go
Normal file
61
http.go
Normal 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
16
ipv6.go
Normal 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
|
||||
}}
|
87
main.go
87
main.go
@ -3,69 +3,46 @@ package main
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fumiama/terasu"
|
||||
)
|
||||
|
||||
func main() {}
|
||||
|
||||
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()
|
||||
// para: json of map[host string][]addr:port string
|
||||
//
|
||||
//export add_dns
|
||||
func add_dns(para *C.char, is_ipv6 C.int) *C.char {
|
||||
m := map[string][]string{}
|
||||
err := json.Unmarshal(stringToBytes(C.GoString(para)), &m)
|
||||
if err != nil {
|
||||
return C.CString(err.Error())
|
||||
}
|
||||
if is_ipv6 != 0 {
|
||||
if !canUseIPv6.Get() {
|
||||
return C.CString("cannot use ipv6")
|
||||
}
|
||||
dotv6servers.add(m)
|
||||
return nil
|
||||
}
|
||||
dotv4servers.add(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
func request(para *C.char) *C.char {
|
||||
r := capsule{}
|
||||
@ -73,13 +50,17 @@ func request(para *C.char) *C.char {
|
||||
if err != nil {
|
||||
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 + "'"))
|
||||
}
|
||||
if r.M != "GET" && r.M != "POST" && r.M != "DELETE" {
|
||||
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 {
|
||||
return C.CString(r.printerr(err))
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user