Skip to content

Commit bc31f24

Browse files
authored
check if client is using clientcontext tracker (#75)
* check if client is using clientcontext tracker * remove redundant check
1 parent 838677d commit bc31f24

File tree

2 files changed

+150
-61
lines changed

2 files changed

+150
-61
lines changed

tracker/clientcontext/tracker.go

Lines changed: 111 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,20 @@
99
package clientcontext
1010

1111
import (
12+
stdbufio "bufio"
13+
"bytes"
1214
"context"
1315
"encoding/json"
1416
"fmt"
17+
"io"
1518
"net"
19+
"net/http"
1620

1721
"github.com/sagernet/sing-box/adapter"
1822
"github.com/sagernet/sing-box/log"
1923
"github.com/sagernet/sing/common/buf"
24+
"github.com/sagernet/sing/common/bufio"
25+
"github.com/sagernet/sing/common/metadata"
2026
N "github.com/sagernet/sing/common/network"
2127
"github.com/sagernet/sing/service"
2228
)
@@ -161,36 +167,68 @@ func (b *boundsRule) match(tag string) bool {
161167
type readConn struct {
162168
net.Conn
163169
ctx context.Context
164-
info *ClientInfo
170+
info ClientInfo
165171
logger log.ContextLogger
172+
173+
reader io.Reader
174+
n int
175+
readErr error
166176
}
167177

168178
// newReadConn creates a readConn and reads client info from it. If successful, the info is stored
169179
// in the context.
170180
func newReadConn(ctx context.Context, conn net.Conn, logger log.ContextLogger) net.Conn {
171-
c := &readConn{Conn: conn, ctx: ctx}
172-
info, err := c.readInfo()
173-
if err != nil {
174-
logger.Error("reading client info ", err)
175-
return conn
181+
c := &readConn{
182+
Conn: conn,
183+
ctx: ctx,
184+
reader: conn,
185+
logger: logger,
186+
}
187+
if err := c.readInfo(); err != nil {
188+
logger.Warn("reading client info: ", err)
176189
}
177-
service.ContextWithPtr(ctx, info)
178190
return c
179191
}
180192

181-
// readInfo reads and decodes client info, then sends an OK response.
182-
func (c *readConn) readInfo() (*ClientInfo, error) {
193+
func (c *readConn) Read(b []byte) (n int, err error) {
194+
if c.readErr != nil {
195+
return c.n, c.readErr
196+
}
197+
return c.reader.Read(b)
198+
}
199+
200+
// readInfo reads and decodes client info, then sends an HTTP 200 OK response.
201+
func (c *readConn) readInfo() error {
202+
var buf [32]byte
203+
n, err := c.Conn.Read(buf[:])
204+
if err != nil {
205+
c.readErr = err
206+
c.n = n
207+
return err
208+
}
209+
reader := io.MultiReader(bytes.NewReader(buf[:n]), c.Conn)
210+
if !bytes.HasPrefix(buf[:n], []byte("POST /clientinfo")) {
211+
c.reader = reader
212+
return nil
213+
}
214+
183215
var info ClientInfo
184-
if err := json.NewDecoder(c).Decode(&info); err != nil {
185-
return nil, fmt.Errorf("decoding client info: %w", err)
216+
req, err := http.ReadRequest(stdbufio.NewReader(reader))
217+
if err != nil {
218+
return fmt.Errorf("reading HTTP request: %w", err)
186219
}
187-
c.info = &info
220+
defer req.Body.Close()
221+
if err := json.NewDecoder(req.Body).Decode(&info); err != nil {
222+
return fmt.Errorf("decoding client info: %w", err)
223+
}
224+
c.info = info
188225

189-
// send `OK` response
190-
if _, err := c.Write([]byte("OK")); err != nil {
191-
return nil, fmt.Errorf("writing OK response to client: %w", err)
226+
resp := "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
227+
if _, err := c.Write([]byte(resp)); err != nil {
228+
return fmt.Errorf("writing HTTP response: %w", err)
192229
}
193-
return &info, nil
230+
service.ContextWithPtr(c.ctx, &info)
231+
return nil
194232
}
195233

196234
// writeConn sends client info after handshake.
@@ -213,70 +251,101 @@ func (c *writeConn) ConnHandshakeSuccess(conn net.Conn) error {
213251
return nil
214252
}
215253

216-
// sendInfo marshals and sends client info, then waits for OK.
254+
// sendInfo marshals and sends client info as an HTTP POST, then waits for HTTP 200 OK.
217255
func (c *writeConn) sendInfo(conn net.Conn) error {
218256
buf, err := json.Marshal(c.info)
219257
if err != nil {
220258
return fmt.Errorf("marshaling client info: %w", err)
221259
}
222-
if _, err = conn.Write(buf); err != nil {
260+
// Write HTTP POST request
261+
req := bytes.NewBuffer(nil)
262+
fmt.Fprintf(req, "POST /clientinfo HTTP/1.1\r\n")
263+
fmt.Fprintf(req, "Host: localhost\r\n")
264+
fmt.Fprintf(req, "Content-Type: application/json\r\n")
265+
fmt.Fprintf(req, "Content-Length: %d\r\n", len(buf))
266+
fmt.Fprintf(req, "\r\n")
267+
req.Write(buf)
268+
if _, err = conn.Write(req.Bytes()); err != nil {
223269
return fmt.Errorf("writing client info: %w", err)
224270
}
225271

226-
// wait for `OK` response
227-
resp := make([]byte, 2)
228-
if _, err := conn.Read(resp); err != nil {
229-
return fmt.Errorf("reading server response: %w", err)
272+
// wait for HTTP 200 OK response
273+
reader := stdbufio.NewReader(conn)
274+
resp, err := http.ReadResponse(reader, nil)
275+
if err != nil {
276+
return fmt.Errorf("reading HTTP response: %w", err)
230277
}
231-
if string(resp) != "OK" {
232-
return fmt.Errorf("invalid server response: %s", resp)
278+
defer resp.Body.Close()
279+
if resp.StatusCode != 200 {
280+
return fmt.Errorf("invalid server response: %s", resp.Status)
233281
}
234282
return nil
235283
}
236284

285+
const prefix = "CLIENTINFO "
286+
237287
type readPacketConn struct {
238288
N.PacketConn
239289
ctx context.Context
240290
info *ClientInfo
241291
logger log.ContextLogger
292+
293+
reader io.Reader
294+
destination metadata.Socksaddr
295+
readErr error
242296
}
243297

244298
// newReadPacketConn creates a readPacketConn and reads client info from it. If successful, the
245299
// info is stored in the context.
246300
func newReadPacketConn(ctx context.Context, conn N.PacketConn, logger log.ContextLogger) N.PacketConn {
247-
c := &readPacketConn{PacketConn: conn, ctx: ctx, logger: logger}
248-
info, err := c.readInfo()
249-
if err != nil {
250-
logger.Error("reading client info ", err)
251-
return conn
301+
c := &readPacketConn{
302+
PacketConn: conn,
303+
ctx: ctx,
304+
logger: logger,
305+
}
306+
if err := c.readInfo(); err != nil {
307+
logger.Warn("reading client info: ", err)
252308
}
253-
254-
service.ContextWithPtr(ctx, info)
255309
return c
256310
}
257311

258-
// readInfo reads and decodes client info, then sends an OK response.
259-
func (c *readPacketConn) readInfo() (*ClientInfo, error) {
312+
func (c *readPacketConn) ReadPacket(b *buf.Buffer) (destination metadata.Socksaddr, err error) {
313+
if c.readErr != nil {
314+
return c.destination, c.readErr
315+
}
316+
return c.PacketConn.ReadPacket(b)
317+
}
318+
319+
// readInfo reads and decodes client info if the first packet is a CLIENTINFO packet, then sends an
320+
// OK response.
321+
func (c *readPacketConn) readInfo() error {
260322
buffer := buf.NewPacket()
261323
defer buffer.Release()
262324

263325
destination, err := c.ReadPacket(buffer)
264326
if err != nil {
265-
return nil, fmt.Errorf("reading packet from client: %w", err)
327+
c.readErr = err
328+
return err
329+
}
330+
data := buffer.Bytes()
331+
if !bytes.HasPrefix(data, []byte(prefix)) {
332+
// not a client info packet, wrap with cached packet conn so the packet can be read again
333+
c.PacketConn = bufio.NewCachedPacketConn(c.PacketConn, buffer, destination)
334+
return nil
266335
}
267336
var info ClientInfo
268-
if err := json.Unmarshal(buffer.Bytes(), &info); err != nil {
269-
return nil, fmt.Errorf("decoding client info: %w", err)
337+
if err := json.Unmarshal(data[len(prefix):], &info); err != nil {
338+
return fmt.Errorf("unmarshaling client info: %w", err)
270339
}
271340
c.info = &info
272341

273-
// send `OK` response
274342
buffer.Reset()
275343
buffer.WriteString("OK")
276344
if err := c.WritePacket(buffer, destination); err != nil {
277-
return nil, fmt.Errorf("writing OK response to client: %w", err)
345+
return fmt.Errorf("writing OK response: %w", err)
278346
}
279-
return &info, nil
347+
service.ContextWithPtr(c.ctx, &info)
348+
return nil
280349
}
281350

282351
type writePacketConn struct {
@@ -311,13 +380,14 @@ func (c *writePacketConn) PacketConnHandshakeSuccess(conn net.PacketConn) error
311380
return nil
312381
}
313382

314-
// sendInfo marshals and sends client info, then waits for OK.
383+
// sendInfo marshals and sends client info as a CLIENTINFO packet, then waits for OK.
315384
func (c *writePacketConn) sendInfo(conn net.PacketConn) error {
316385
buf, err := json.Marshal(c.info)
317386
if err != nil {
318387
return fmt.Errorf("marshaling client info: %w", err)
319388
}
320-
_, err = conn.WriteTo(buf, c.metadata.Destination)
389+
packet := append([]byte(prefix), buf...)
390+
_, err = conn.WriteTo(packet, c.metadata.Destination)
321391
if err != nil {
322392
return fmt.Errorf("writing packet: %w", err)
323393
}

tracker/clientcontext/tracker_test.go

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,51 +28,68 @@ const testOptionsPath = "../../testdata/options"
2828

2929
func TestIntegration(t *testing.T) {
3030
cInfo := ClientInfo{
31-
DeviceID: "sing-box-extensions",
31+
DeviceID: "lantern-box",
3232
Platform: "linux",
3333
IsPro: false,
3434
CountryCode: "US",
3535
Version: "9.0",
3636
}
37-
ctx := box.BoxContext()
37+
ctx := box.BaseContext()
3838
logger := log.NewNOPFactory().NewLogger("")
39-
clientTracker := NewClientContextTracker(cInfo, MatchBounds{[]string{"any"}, []string{"any"}}, logger)
40-
clientOpts, clientBox := newTestBox(ctx, t, testOptionsPath+"/http_client.json", clientTracker)
41-
42-
httpInbound, exists := clientBox.Inbound().Get("http-client")
43-
require.True(t, exists, "http-client inbound should exist")
44-
require.Equal(t, constant.TypeHTTP, httpInbound.Type(), "http-client should be a HTTP inbound")
45-
46-
// this cannot actually be empty or we would have failed to create the box instance
47-
proxyAddr := getProxyAddress(clientOpts.Inbounds)
48-
4939
serverTracker := NewClientContextReader(MatchBounds{[]string{"any"}, []string{"any"}}, logger)
5040
_, serverBox := newTestBox(ctx, t, testOptionsPath+"/http_server.json", serverTracker)
5141

5242
mTracker := &mockTracker{}
5343
serverBox.Router().AppendTracker(mTracker)
5444

55-
require.NoError(t, clientBox.Start())
56-
defer clientBox.Close()
5745
require.NoError(t, serverBox.Start())
5846
defer serverBox.Close()
5947

6048
httpServer := startHTTPServer()
6149
defer httpServer.Close()
6250

51+
clientOpts, clientBox := newTestBox(ctx, t, testOptionsPath+"/http_client.json", nil)
52+
53+
httpInbound, exists := clientBox.Inbound().Get("http-client")
54+
require.True(t, exists, "http-client inbound should exist")
55+
require.Equal(t, constant.TypeHTTP, httpInbound.Type(), "http-client should be a HTTP inbound")
56+
57+
// this cannot actually be empty or we would have failed to create the box instance
58+
proxyAddr := getProxyAddress(clientOpts.Inbounds)
59+
60+
require.NoError(t, clientBox.Start())
61+
defer clientBox.Close()
62+
6363
proxyURL, _ := url.Parse("http://" + proxyAddr)
6464
httpClient := &http.Client{
6565
Transport: &http.Transport{
6666
Proxy: http.ProxyURL(proxyURL),
6767
},
6868
}
69-
req, err := http.NewRequest("GET", httpServer.URL, nil)
70-
require.NoError(t, err)
69+
addr := httpServer.URL
7170

72-
_, err = httpClient.Do(req)
73-
require.NoError(t, err)
71+
t.Run("without ClientContext tracker", func(t *testing.T) {
72+
req, err := http.NewRequest("GET", addr+"/ip", nil)
73+
require.NoError(t, err)
74+
75+
_, err = httpClient.Do(req)
76+
require.NoError(t, err)
7477

75-
require.Equal(t, cInfo, *mTracker.info)
78+
require.Nil(t, mTracker.info)
79+
})
80+
t.Run("with ClientContext tracker", func(t *testing.T) {
81+
clientTracker := NewClientContextTracker(cInfo, MatchBounds{[]string{"any"}, []string{"any"}}, logger)
82+
clientBox.Router().AppendTracker(clientTracker)
83+
req, err := http.NewRequest("GET", addr+"/ip", nil)
84+
require.NoError(t, err)
85+
86+
_, err = httpClient.Do(req)
87+
require.NoError(t, err)
88+
89+
info := mTracker.info
90+
require.NotNil(t, info)
91+
require.Equal(t, cInfo, *info)
92+
})
7693
}
7794

7895
func getProxyAddress(inbounds []option.Inbound) string {
@@ -106,7 +123,9 @@ func newTestBox(ctx context.Context, t *testing.T, configPath string, tracker *C
106123
})
107124
require.NoError(t, err)
108125

109-
instance.Router().AppendTracker(tracker)
126+
if tracker != nil {
127+
instance.Router().AppendTracker(tracker)
128+
}
110129
return options, instance
111130
}
112131

0 commit comments

Comments
 (0)