Skip to content

Commit d9188e9

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

37 files changed

+2420
-178
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: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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+
}
40+
41+
func NewRawConn(rawTLSConn tls.Conn) (*RawConn, error) {
42+
var (
43+
pointer unsafe.Pointer
44+
methods *Methods
45+
loaded bool
46+
)
47+
for _, tlsCreator := range methodRegistry {
48+
pointer, methods, loaded = tlsCreator(rawTLSConn)
49+
if loaded {
50+
break
51+
}
52+
}
53+
if !loaded {
54+
return nil, os.ErrInvalid
55+
}
56+
57+
conn := &RawConn{
58+
pointer: pointer,
59+
methods: methods,
60+
}
61+
62+
rawConn := reflect.Indirect(reflect.ValueOf(rawTLSConn))
63+
64+
rawIsClient := rawConn.FieldByName("isClient")
65+
if !rawIsClient.IsValid() || rawIsClient.Kind() != reflect.Bool {
66+
return nil, E.New("invalid Conn.isClient")
67+
}
68+
conn.IsClient = (*bool)(unsafe.Pointer(rawIsClient.UnsafeAddr()))
69+
70+
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
71+
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
72+
return nil, E.New("invalid Conn.isHandshakeComplete")
73+
}
74+
conn.IsHandshakeComplete = (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
75+
76+
rawVers := rawConn.FieldByName("vers")
77+
if !rawVers.IsValid() || rawVers.Kind() != reflect.Uint16 {
78+
return nil, E.New("invalid Conn.vers")
79+
}
80+
conn.Vers = (*uint16)(unsafe.Pointer(rawVers.UnsafeAddr()))
81+
82+
rawCipherSuite := rawConn.FieldByName("cipherSuite")
83+
if !rawCipherSuite.IsValid() || rawCipherSuite.Kind() != reflect.Uint16 {
84+
return nil, E.New("invalid Conn.cipherSuite")
85+
}
86+
conn.CipherSuite = (*uint16)(unsafe.Pointer(rawCipherSuite.UnsafeAddr()))
87+
88+
rawRawInput := rawConn.FieldByName("rawInput")
89+
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
90+
return nil, E.New("invalid Conn.rawInput")
91+
}
92+
conn.RawInput = (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
93+
94+
rawInput := rawConn.FieldByName("input")
95+
if !rawInput.IsValid() || rawInput.Kind() != reflect.Struct {
96+
return nil, E.New("invalid Conn.input")
97+
}
98+
conn.Input = (*bytes.Reader)(unsafe.Pointer(rawInput.UnsafeAddr()))
99+
100+
rawHand := rawConn.FieldByName("hand")
101+
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
102+
return nil, E.New("invalid Conn.hand")
103+
}
104+
conn.Hand = (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
105+
106+
rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
107+
if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
108+
return nil, E.New("invalid Conn.closeNotifySent")
109+
}
110+
conn.CloseNotifySent = (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
111+
112+
rawCloseNotifyErr := rawConn.FieldByName("closeNotifyErr")
113+
if !rawCloseNotifyErr.IsValid() || rawCloseNotifyErr.Kind() != reflect.Interface {
114+
return nil, E.New("invalid Conn.closeNotifyErr")
115+
}
116+
conn.CloseNotifyErr = (*error)(unsafe.Pointer(rawCloseNotifyErr.UnsafeAddr()))
117+
118+
rawIn := rawConn.FieldByName("in")
119+
if !rawIn.IsValid() || rawIn.Kind() != reflect.Struct {
120+
return nil, E.New("invalid Conn.in")
121+
}
122+
halfIn, err := NewRawHalfConn(rawIn, methods)
123+
if err != nil {
124+
return nil, E.Cause(err, "invalid Conn.in")
125+
}
126+
conn.In = halfIn
127+
128+
rawOut := rawConn.FieldByName("out")
129+
if !rawOut.IsValid() || rawOut.Kind() != reflect.Struct {
130+
return nil, E.New("invalid Conn.out")
131+
}
132+
halfOut, err := NewRawHalfConn(rawOut, methods)
133+
if err != nil {
134+
return nil, E.Cause(err, "invalid Conn.out")
135+
}
136+
conn.Out = halfOut
137+
138+
rawBytesSent := rawConn.FieldByName("bytesSent")
139+
if !rawBytesSent.IsValid() || rawBytesSent.Kind() != reflect.Int64 {
140+
return nil, E.New("invalid Conn.bytesSent")
141+
}
142+
conn.BytesSent = (*int64)(unsafe.Pointer(rawBytesSent.UnsafeAddr()))
143+
144+
rawPacketsSent := rawConn.FieldByName("packetsSent")
145+
if !rawPacketsSent.IsValid() || rawPacketsSent.Kind() != reflect.Int64 {
146+
return nil, E.New("invalid Conn.packetsSent")
147+
}
148+
conn.PacketsSent = (*int64)(unsafe.Pointer(rawPacketsSent.UnsafeAddr()))
149+
150+
rawActiveCall := rawConn.FieldByName("activeCall")
151+
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
152+
return nil, E.New("invalid Conn.activeCall")
153+
}
154+
conn.ActiveCall = (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
155+
156+
return conn, nil
157+
}
158+
159+
func (c *RawConn) ReadRecord() error {
160+
return c.methods.readRecord(c.pointer)
161+
}
162+
163+
func (c *RawConn) HandlePostHandshakeMessage() error {
164+
return c.methods.handlePostHandshakeMessage(c.pointer)
165+
}
166+
167+
func (c *RawConn) WriteRecordLocked(typ uint16, data []byte) (int, error) {
168+
return c.methods.writeRecordLocked(c.pointer, typ, data)
169+
}

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)