Skip to content

Commit 9b70b7f

Browse files
committed
Add support for kTLS
Reference: https://gitlab.com/go-extension/tls
1 parent f13327f commit 9b70b7f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2565
-217
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ jobs:
154154
set -xeuo pipefail
155155
mkdir -p dist
156156
go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
157-
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
157+
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
158158
./cmd/sing-box
159159
env:
160160
CGO_ENABLED: "0"
@@ -174,7 +174,7 @@ jobs:
174174
export CXX="${CC}++"
175175
mkdir -p dist
176176
GOOS=$BUILD_GOOS GOARCH=$BUILD_GOARCH build go build -v -trimpath -o dist/sing-box -tags "${BUILD_TAGS}" \
177-
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }}' \
177+
-ldflags '-s -buildid= -X github.com/sagernet/sing-box/constant.Version=${{ needs.calculate_version.outputs.version }} -checklinkname=0' \
178178
./cmd/sing-box
179179
env:
180180
CGO_ENABLED: "1"

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ RUN set -ex \
1515
&& go build -v -trimpath -tags \
1616
"with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale" \
1717
-o /go/bin/sing-box \
18-
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid=" \
18+
-ldflags "-X \"github.com/sagernet/sing-box/constant.Version=$VERSION\" -s -w -buildid= -checklinkname=0" \
1919
./cmd/sing-box
2020
FROM --platform=$TARGETPLATFORM alpine AS dist
2121
LABEL maintainer="nekohasekai <[email protected]>"

cmd/internal/build_libbox/main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ func init() {
5959
if err != nil {
6060
currentTag = "unknown"
6161
}
62-
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
63-
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
62+
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid= -checklinkname=0")
63+
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -checklinkname=0")
6464

6565
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
6666
macOSTags = append(macOSTags, "with_dhcp")
@@ -107,10 +107,10 @@ func buildAndroid() {
107107
}
108108

109109
if !debugEnabled {
110-
sharedFlags[3] = sharedFlags[3] + " -checklinkname=0"
110+
// sharedFlags[3] = sharedFlags[3] + " -checklinkname=0"
111111
args = append(args, sharedFlags...)
112112
} else {
113-
debugFlags[1] = debugFlags[1] + " -checklinkname=0"
113+
// debugFlags[1] = debugFlags[1] + " -checklinkname=0"
114114
args = append(args, debugFlags...)
115115
}
116116

common/badtls/raw_conn.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
//go:build go1.25 && !without_badtls
2+
3+
package badtls
4+
5+
import (
6+
"bytes"
7+
"os"
8+
"reflect"
9+
"sync/atomic"
10+
"unsafe"
11+
12+
E "github.com/sagernet/sing/common/exceptions"
13+
"github.com/sagernet/sing/common/tls"
14+
)
15+
16+
type RawConn struct {
17+
pointer unsafe.Pointer
18+
methods *Methods
19+
20+
IsClient *bool
21+
IsHandshakeComplete *atomic.Bool
22+
Vers *uint16
23+
CipherSuite *uint16
24+
25+
RawInput *bytes.Buffer
26+
Input *bytes.Reader
27+
Hand *bytes.Buffer
28+
29+
CloseNotifySent *bool
30+
CloseNotifyErr *error
31+
32+
In *RawHalfConn
33+
Out *RawHalfConn
34+
35+
BytesSent *int64
36+
PacketsSent *int64
37+
38+
ActiveCall *atomic.Int32
39+
Tmp *[16]byte
40+
}
41+
42+
func NewRawConn(rawTLSConn tls.Conn) (*RawConn, error) {
43+
var (
44+
pointer unsafe.Pointer
45+
methods *Methods
46+
loaded bool
47+
)
48+
for _, tlsCreator := range methodRegistry {
49+
pointer, methods, loaded = tlsCreator(rawTLSConn)
50+
if loaded {
51+
break
52+
}
53+
}
54+
if !loaded {
55+
return nil, os.ErrInvalid
56+
}
57+
58+
conn := &RawConn{
59+
pointer: pointer,
60+
methods: methods,
61+
}
62+
63+
rawConn := reflect.Indirect(reflect.ValueOf(rawTLSConn))
64+
65+
rawIsClient := rawConn.FieldByName("isClient")
66+
if !rawIsClient.IsValid() || rawIsClient.Kind() != reflect.Bool {
67+
return nil, E.New("invalid Conn.isClient")
68+
}
69+
conn.IsClient = (*bool)(unsafe.Pointer(rawIsClient.UnsafeAddr()))
70+
71+
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
72+
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
73+
return nil, E.New("invalid Conn.isHandshakeComplete")
74+
}
75+
conn.IsHandshakeComplete = (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
76+
77+
rawVers := rawConn.FieldByName("vers")
78+
if !rawVers.IsValid() || rawVers.Kind() != reflect.Uint16 {
79+
return nil, E.New("invalid Conn.vers")
80+
}
81+
conn.Vers = (*uint16)(unsafe.Pointer(rawVers.UnsafeAddr()))
82+
83+
rawCipherSuite := rawConn.FieldByName("cipherSuite")
84+
if !rawCipherSuite.IsValid() || rawCipherSuite.Kind() != reflect.Uint16 {
85+
return nil, E.New("invalid Conn.cipherSuite")
86+
}
87+
conn.CipherSuite = (*uint16)(unsafe.Pointer(rawCipherSuite.UnsafeAddr()))
88+
89+
rawRawInput := rawConn.FieldByName("rawInput")
90+
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
91+
return nil, E.New("invalid Conn.rawInput")
92+
}
93+
conn.RawInput = (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
94+
95+
rawInput := rawConn.FieldByName("input")
96+
if !rawInput.IsValid() || rawInput.Kind() != reflect.Struct {
97+
return nil, E.New("invalid Conn.input")
98+
}
99+
conn.Input = (*bytes.Reader)(unsafe.Pointer(rawInput.UnsafeAddr()))
100+
101+
rawHand := rawConn.FieldByName("hand")
102+
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
103+
return nil, E.New("invalid Conn.hand")
104+
}
105+
conn.Hand = (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
106+
107+
rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
108+
if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
109+
return nil, E.New("invalid Conn.closeNotifySent")
110+
}
111+
conn.CloseNotifySent = (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
112+
113+
rawCloseNotifyErr := rawConn.FieldByName("closeNotifyErr")
114+
if !rawCloseNotifyErr.IsValid() || rawCloseNotifyErr.Kind() != reflect.Interface {
115+
return nil, E.New("invalid Conn.closeNotifyErr")
116+
}
117+
conn.CloseNotifyErr = (*error)(unsafe.Pointer(rawCloseNotifyErr.UnsafeAddr()))
118+
119+
rawIn := rawConn.FieldByName("in")
120+
if !rawIn.IsValid() || rawIn.Kind() != reflect.Struct {
121+
return nil, E.New("invalid Conn.in")
122+
}
123+
halfIn, err := NewRawHalfConn(rawIn, methods)
124+
if err != nil {
125+
return nil, E.Cause(err, "invalid Conn.in")
126+
}
127+
conn.In = halfIn
128+
129+
rawOut := rawConn.FieldByName("out")
130+
if !rawOut.IsValid() || rawOut.Kind() != reflect.Struct {
131+
return nil, E.New("invalid Conn.out")
132+
}
133+
halfOut, err := NewRawHalfConn(rawOut, methods)
134+
if err != nil {
135+
return nil, E.Cause(err, "invalid Conn.out")
136+
}
137+
conn.Out = halfOut
138+
139+
rawBytesSent := rawConn.FieldByName("bytesSent")
140+
if !rawBytesSent.IsValid() || rawBytesSent.Kind() != reflect.Int64 {
141+
return nil, E.New("invalid Conn.bytesSent")
142+
}
143+
conn.BytesSent = (*int64)(unsafe.Pointer(rawBytesSent.UnsafeAddr()))
144+
145+
rawPacketsSent := rawConn.FieldByName("packetsSent")
146+
if !rawPacketsSent.IsValid() || rawPacketsSent.Kind() != reflect.Int64 {
147+
return nil, E.New("invalid Conn.packetsSent")
148+
}
149+
conn.PacketsSent = (*int64)(unsafe.Pointer(rawPacketsSent.UnsafeAddr()))
150+
151+
rawActiveCall := rawConn.FieldByName("activeCall")
152+
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
153+
return nil, E.New("invalid Conn.activeCall")
154+
}
155+
conn.ActiveCall = (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
156+
157+
rawTmp := rawConn.FieldByName("tmp")
158+
if !rawTmp.IsValid() || rawTmp.Kind() != reflect.Array || rawTmp.Len() != 16 || rawTmp.Type().Elem().Kind() != reflect.Uint8 {
159+
return nil, E.New("invalid Conn.tmp")
160+
}
161+
conn.Tmp = (*[16]byte)(unsafe.Pointer(rawTmp.UnsafeAddr()))
162+
163+
return conn, nil
164+
}
165+
166+
func (c *RawConn) ReadRecord() error {
167+
return c.methods.readRecord(c.pointer)
168+
}
169+
170+
func (c *RawConn) HandlePostHandshakeMessage() error {
171+
return c.methods.handlePostHandshakeMessage(c.pointer)
172+
}
173+
174+
func (c *RawConn) WriteRecordLocked(typ uint16, data []byte) (int, error) {
175+
return c.methods.writeRecordLocked(c.pointer, typ, data)
176+
}

common/badtls/raw_half_conn.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//go:build go1.25 && !without_badtls
2+
3+
package badtls
4+
5+
import (
6+
"hash"
7+
"reflect"
8+
"sync"
9+
"unsafe"
10+
11+
E "github.com/sagernet/sing/common/exceptions"
12+
)
13+
14+
type RawHalfConn struct {
15+
pointer unsafe.Pointer
16+
methods *Methods
17+
*sync.Mutex
18+
Err *error
19+
Version *uint16
20+
Cipher *any
21+
Seq *[8]byte
22+
ScratchBuf *[13]byte
23+
TrafficSecret *[]byte
24+
Mac *hash.Hash
25+
RawKey *[]byte
26+
RawIV *[]byte
27+
RawMac *[]byte
28+
}
29+
30+
func NewRawHalfConn(rawHalfConn reflect.Value, methods *Methods) (*RawHalfConn, error) {
31+
halfConn := &RawHalfConn{
32+
pointer: (unsafe.Pointer)(rawHalfConn.UnsafeAddr()),
33+
methods: methods,
34+
}
35+
36+
rawMutex := rawHalfConn.FieldByName("Mutex")
37+
if !rawMutex.IsValid() || rawMutex.Kind() != reflect.Struct {
38+
return nil, E.New("badtls: invalid halfConn.Mutex")
39+
}
40+
halfConn.Mutex = (*sync.Mutex)(unsafe.Pointer(rawMutex.UnsafeAddr()))
41+
42+
rawErr := rawHalfConn.FieldByName("err")
43+
if !rawErr.IsValid() || rawErr.Kind() != reflect.Interface {
44+
return nil, E.New("badtls: invalid halfConn.err")
45+
}
46+
halfConn.Err = (*error)(unsafe.Pointer(rawErr.UnsafeAddr()))
47+
48+
rawVersion := rawHalfConn.FieldByName("version")
49+
if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {
50+
return nil, E.New("badtls: invalid halfConn.version")
51+
}
52+
halfConn.Version = (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))
53+
54+
rawCipher := rawHalfConn.FieldByName("cipher")
55+
if !rawCipher.IsValid() || rawCipher.Kind() != reflect.Interface {
56+
return nil, E.New("badtls: invalid halfConn.cipher")
57+
}
58+
halfConn.Cipher = (*any)(unsafe.Pointer(rawCipher.UnsafeAddr()))
59+
60+
rawSeq := rawHalfConn.FieldByName("seq")
61+
if !rawSeq.IsValid() || rawSeq.Kind() != reflect.Array || rawSeq.Len() != 8 || rawSeq.Type().Elem().Kind() != reflect.Uint8 {
62+
return nil, E.New("badtls: invalid halfConn.seq")
63+
}
64+
halfConn.Seq = (*[8]byte)(unsafe.Pointer(rawSeq.UnsafeAddr()))
65+
66+
rawScratchBuf := rawHalfConn.FieldByName("scratchBuf")
67+
if !rawScratchBuf.IsValid() || rawScratchBuf.Kind() != reflect.Array || rawScratchBuf.Len() != 13 || rawScratchBuf.Type().Elem().Kind() != reflect.Uint8 {
68+
return nil, E.New("badtls: invalid halfConn.scratchBuf")
69+
}
70+
halfConn.ScratchBuf = (*[13]byte)(unsafe.Pointer(rawScratchBuf.UnsafeAddr()))
71+
72+
rawTrafficSecret := rawHalfConn.FieldByName("trafficSecret")
73+
if !rawTrafficSecret.IsValid() || rawTrafficSecret.Kind() != reflect.Slice || rawTrafficSecret.Type().Elem().Kind() != reflect.Uint8 {
74+
return nil, E.New("badtls: invalid halfConn.trafficSecret")
75+
}
76+
halfConn.TrafficSecret = (*[]byte)(unsafe.Pointer(rawTrafficSecret.UnsafeAddr()))
77+
78+
rawMac := rawHalfConn.FieldByName("mac")
79+
if !rawMac.IsValid() || rawMac.Kind() != reflect.Interface {
80+
return nil, E.New("badtls: invalid halfConn.mac")
81+
}
82+
halfConn.Mac = (*hash.Hash)(unsafe.Pointer(rawMac.UnsafeAddr()))
83+
84+
rawKey := rawHalfConn.FieldByName("rawKey")
85+
if rawKey.IsValid() {
86+
if /*!rawKey.IsValid() || */ rawKey.Kind() != reflect.Slice || rawKey.Type().Elem().Kind() != reflect.Uint8 {
87+
return nil, E.New("badtls: invalid halfConn.rawKey")
88+
}
89+
halfConn.RawKey = (*[]byte)(unsafe.Pointer(rawKey.UnsafeAddr()))
90+
91+
rawIV := rawHalfConn.FieldByName("rawIV")
92+
if !rawIV.IsValid() || rawIV.Kind() != reflect.Slice || rawIV.Type().Elem().Kind() != reflect.Uint8 {
93+
return nil, E.New("badtls: invalid halfConn.rawIV")
94+
}
95+
halfConn.RawIV = (*[]byte)(unsafe.Pointer(rawIV.UnsafeAddr()))
96+
97+
rawMAC := rawHalfConn.FieldByName("rawMac")
98+
if !rawMAC.IsValid() || rawMAC.Kind() != reflect.Slice || rawMAC.Type().Elem().Kind() != reflect.Uint8 {
99+
return nil, E.New("badtls: invalid halfConn.rawMac")
100+
}
101+
halfConn.RawMac = (*[]byte)(unsafe.Pointer(rawMAC.UnsafeAddr()))
102+
}
103+
104+
return halfConn, nil
105+
}
106+
107+
func (hc *RawHalfConn) Decrypt(record []byte) ([]byte, uint8, error) {
108+
return hc.methods.decrypt(hc.pointer, record)
109+
}
110+
111+
func (hc *RawHalfConn) SetErrorLocked(err error) error {
112+
return hc.methods.setErrorLocked(hc.pointer, err)
113+
}
114+
115+
func (hc *RawHalfConn) SetTrafficSecret(suite unsafe.Pointer, level int, secret []byte) {
116+
hc.methods.setTrafficSecret(hc.pointer, suite, level, secret)
117+
}
118+
119+
func (hc *RawHalfConn) ExplicitNonceLen() int {
120+
return hc.methods.explicitNonceLen(hc.pointer)
121+
}

0 commit comments

Comments
 (0)