mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-06-13 21:54:13 +08:00
Compare commits
99 Commits
2af4882275
...
06393297ba
Author | SHA1 | Date | |
---|---|---|---|
![]() |
06393297ba | ||
![]() |
7ec73b2ed9 | ||
![]() |
59a0534935 | ||
![]() |
589e82e580 | ||
![]() |
921788c68c | ||
![]() |
26c53bae0c | ||
![]() |
1c52b006b7 | ||
![]() |
09a13daa23 | ||
![]() |
d82ad7fedc | ||
![]() |
8402221ba1 | ||
![]() |
2944239794 | ||
![]() |
042b8ff82b | ||
![]() |
25814fe21f | ||
![]() |
123262b186 | ||
![]() |
548b6de50d | ||
![]() |
06b1193d4a | ||
![]() |
b03b545603 | ||
![]() |
7506a29726 | ||
![]() |
25c1c62f0c | ||
![]() |
5500b89995 | ||
![]() |
d8e3f3a60b | ||
![]() |
222dfe8901 | ||
![]() |
fa7637c193 | ||
![]() |
0771862d49 | ||
![]() |
6ecbb9bcc3 | ||
![]() |
0f8fd06113 | ||
![]() |
c1096c16ea | ||
![]() |
e00a962b44 | ||
![]() |
88e652ea7e | ||
![]() |
3c1db46a56 | ||
![]() |
35aebbd0d9 | ||
![]() |
a5d93d5e76 | ||
![]() |
a3072de986 | ||
![]() |
196d3f504d | ||
![]() |
ee94b071ea | ||
![]() |
ee97743179 | ||
![]() |
a6242b71b0 | ||
![]() |
d50b61fe4a | ||
![]() |
c29687000e | ||
![]() |
0dfcb02301 | ||
![]() |
0105ba6abc | ||
![]() |
b0737a64a8 | ||
![]() |
19ccb5177c | ||
![]() |
8236e8e7c1 | ||
![]() |
3320273dc1 | ||
![]() |
2af1ff50af | ||
![]() |
3c33b68945 | ||
![]() |
1268016b69 | ||
![]() |
714288d9a7 | ||
![]() |
4d8a730041 | ||
![]() |
613947c2cb | ||
![]() |
770ed26bd7 | ||
![]() |
429421f790 | ||
![]() |
c7583322fd | ||
![]() |
9a2397e335 | ||
![]() |
0110b5b7a3 | ||
![]() |
d10b1c2f61 | ||
![]() |
8d9d1018bd | ||
![]() |
74da3ab5da | ||
![]() |
08aff5e91f | ||
![]() |
d345d0f9c9 | ||
![]() |
1083a2dc4c | ||
![]() |
a8f545f999 | ||
![]() |
6fa07d175c | ||
![]() |
06eeb2a646 | ||
![]() |
bd7a2a0a23 | ||
![]() |
e7a36bec01 | ||
![]() |
1d14183641 | ||
![]() |
d923ef0f45 | ||
![]() |
d3bd7d28e8 | ||
![]() |
c20997b8a5 | ||
![]() |
31ff14c6fd | ||
![]() |
52519ec11b | ||
![]() |
59da1edf75 | ||
![]() |
093ed494f0 | ||
![]() |
955ec36005 | ||
![]() |
5287e0c27f | ||
![]() |
6337660aa3 | ||
![]() |
a0677f0fc2 | ||
![]() |
e3157a1a08 | ||
![]() |
c251af9008 | ||
![]() |
7c240ea0bd | ||
![]() |
252d9e2e9c | ||
![]() |
e54fce5d35 | ||
![]() |
d5f605c844 | ||
![]() |
0c618d751c | ||
![]() |
31d9d3abc8 | ||
![]() |
e2487e44f8 | ||
![]() |
17689c8895 | ||
![]() |
567fd9f684 | ||
![]() |
9c8ab6ceac | ||
![]() |
75298736bd | ||
![]() |
951490fdde | ||
![]() |
8775bc012e | ||
![]() |
ad4ffc0808 | ||
![]() |
d12a838b8e | ||
![]() |
ce8be9da99 | ||
![]() |
e981e3329a | ||
![]() |
83cf5f5c6a |
58
common/sniff/ntp.go
Normal file
58
common/sniff/ntp.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package sniff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NTP(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error {
|
||||||
|
// NTP packets must be at least 48 bytes long (standard NTP header size).
|
||||||
|
pLen := len(packet)
|
||||||
|
if pLen < 48 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
// Check the LI (Leap Indicator) and Version Number (VN) in the first byte.
|
||||||
|
// We'll primarily focus on ensuring the version is valid for NTP.
|
||||||
|
// Many NTP versions are used, but let's check for generally accepted ones (3 & 4 for IPv4, plus potential extensions/customizations)
|
||||||
|
firstByte := packet[0]
|
||||||
|
li := (firstByte >> 6) & 0x03 // Extract LI
|
||||||
|
vn := (firstByte >> 3) & 0x07 // Extract VN
|
||||||
|
mode := firstByte & 0x07 // Extract Mode
|
||||||
|
|
||||||
|
// Leap Indicator should be a valid value (0-3).
|
||||||
|
if li > 3 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version Check (common NTP versions are 3 and 4)
|
||||||
|
if vn != 3 && vn != 4 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the Mode field for a client request (Mode 3). This validates it *is* a request.
|
||||||
|
if mode != 3 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Root Delay and Root Dispersion. While not strictly *required* for a request,
|
||||||
|
// we can check if they appear to be reasonable values (not excessively large).
|
||||||
|
rootDelay := binary.BigEndian.Uint32(packet[4:8])
|
||||||
|
rootDispersion := binary.BigEndian.Uint32(packet[8:12])
|
||||||
|
|
||||||
|
// Check for unreasonably large root delay and dispersion. NTP RFC specifies max values of approximately 16 seconds.
|
||||||
|
// Convert to milliseconds for easy comparison. Each unit is 1/2^16 seconds.
|
||||||
|
if float64(rootDelay)/65536.0 > 16.0 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
if float64(rootDispersion)/65536.0 > 16.0 {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata.Protocol = C.ProtocolNTP
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
33
common/sniff/ntp_test.go
Normal file
33
common/sniff/ntp_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package sniff_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
"github.com/sagernet/sing-box/common/sniff"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSniffNTP(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
packet, err := hex.DecodeString("1b0006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.NTP(context.Background(), &metadata, packet)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, metadata.Protocol, C.ProtocolNTP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSniffNTPFailed(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
packet, err := hex.DecodeString("400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
require.NoError(t, err)
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
err = sniff.NTP(context.Background(), &metadata, packet)
|
||||||
|
require.ErrorIs(t, err, os.ErrInvalid)
|
||||||
|
}
|
@ -34,6 +34,7 @@ type Client struct {
|
|||||||
disableCache bool
|
disableCache bool
|
||||||
disableExpire bool
|
disableExpire bool
|
||||||
independentCache bool
|
independentCache bool
|
||||||
|
clientSubnet netip.Prefix
|
||||||
rdrc adapter.RDRCStore
|
rdrc adapter.RDRCStore
|
||||||
initRDRCFunc func() adapter.RDRCStore
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
@ -47,6 +48,7 @@ type ClientOptions struct {
|
|||||||
DisableExpire bool
|
DisableExpire bool
|
||||||
IndependentCache bool
|
IndependentCache bool
|
||||||
CacheCapacity uint32
|
CacheCapacity uint32
|
||||||
|
ClientSubnet netip.Prefix
|
||||||
RDRC func() adapter.RDRCStore
|
RDRC func() adapter.RDRCStore
|
||||||
Logger logger.ContextLogger
|
Logger logger.ContextLogger
|
||||||
}
|
}
|
||||||
@ -57,6 +59,7 @@ func NewClient(options ClientOptions) *Client {
|
|||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
disableExpire: options.DisableExpire,
|
disableExpire: options.DisableExpire,
|
||||||
independentCache: options.IndependentCache,
|
independentCache: options.IndependentCache,
|
||||||
|
clientSubnet: options.ClientSubnet,
|
||||||
initRDRCFunc: options.RDRC,
|
initRDRCFunc: options.RDRC,
|
||||||
logger: options.Logger,
|
logger: options.Logger,
|
||||||
}
|
}
|
||||||
@ -104,8 +107,12 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return &responseMessage, nil
|
return &responseMessage, nil
|
||||||
}
|
}
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
if options.ClientSubnet.IsValid() {
|
clientSubnet := options.ClientSubnet
|
||||||
message = SetClientSubnet(message, options.ClientSubnet)
|
if !clientSubnet.IsValid() {
|
||||||
|
clientSubnet = c.clientSubnet
|
||||||
|
}
|
||||||
|
if clientSubnet.IsValid() {
|
||||||
|
message = SetClientSubnet(message, clientSubnet)
|
||||||
}
|
}
|
||||||
isSimpleRequest := len(message.Question) == 1 &&
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
len(message.Ns) == 0 &&
|
len(message.Ns) == 0 &&
|
||||||
|
@ -55,6 +55,7 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
|||||||
DisableExpire: options.DNSClientOptions.DisableExpire,
|
DisableExpire: options.DNSClientOptions.DisableExpire,
|
||||||
IndependentCache: options.DNSClientOptions.IndependentCache,
|
IndependentCache: options.DNSClientOptions.IndependentCache,
|
||||||
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
||||||
|
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
|
||||||
RDRC: func() adapter.RDRCStore {
|
RDRC: func() adapter.RDRCStore {
|
||||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||||
if cacheFile == nil {
|
if cacheFile == nil {
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
---
|
---
|
||||||
icon: material/new-box
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
|
:material-decagram: [servers](#servers)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [cache_capacity](#cache_capacity)
|
:material-plus: [cache_capacity](#cache_capacity)
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
---
|
---
|
||||||
icon: material/new-box
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
|
:material-decagram: [servers](#servers)
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [cache_capacity](#cache_capacity)
|
:material-plus: [cache_capacity](#cache_capacity)
|
||||||
|
@ -22,6 +22,7 @@ If enabled in the inbound, the protocol and domain name (if present) of by the c
|
|||||||
| UDP | `dtls` | / | / |
|
| UDP | `dtls` | / | / |
|
||||||
| TCP | `ssh` | / | SSH Client Name |
|
| TCP | `ssh` | / | SSH Client Name |
|
||||||
| TCP | `rdp` | / | / |
|
| TCP | `rdp` | / | / |
|
||||||
|
| UDP | `ntp` | / | / |
|
||||||
|
|
||||||
| QUIC Client | Type |
|
| QUIC Client | Type |
|
||||||
|:------------------------:|:----------:|
|
|:------------------------:|:----------:|
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
| UDP | `dtls` | / | / |
|
| UDP | `dtls` | / | / |
|
||||||
| TCP | `ssh` | / | SSH 客户端名称 |
|
| TCP | `ssh` | / | SSH 客户端名称 |
|
||||||
| TCP | `rdp` | / | / |
|
| TCP | `rdp` | / | / |
|
||||||
|
| UDP | `ntp` | / | / |
|
||||||
|
|
||||||
| QUIC 客户端 | 类型 |
|
| QUIC 客户端 | 类型 |
|
||||||
|:------------------------:|:----------:|
|
|:------------------------:|:----------:|
|
||||||
|
@ -564,6 +564,7 @@ func (r *Router) actionSniff(
|
|||||||
sniff.UTP,
|
sniff.UTP,
|
||||||
sniff.UDPTracker,
|
sniff.UDPTracker,
|
||||||
sniff.DTLSRecord,
|
sniff.DTLSRecord,
|
||||||
|
sniff.NTP,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
|
@ -379,6 +379,8 @@ func (r *RuleActionSniff) build() error {
|
|||||||
r.StreamSniffers = append(r.StreamSniffers, sniff.SSH)
|
r.StreamSniffers = append(r.StreamSniffers, sniff.SSH)
|
||||||
case C.ProtocolRDP:
|
case C.ProtocolRDP:
|
||||||
r.StreamSniffers = append(r.StreamSniffers, sniff.RDP)
|
r.StreamSniffers = append(r.StreamSniffers, sniff.RDP)
|
||||||
|
case C.ProtocolNTP:
|
||||||
|
r.PacketSniffers = append(r.PacketSniffers, sniff.NTP)
|
||||||
default:
|
default:
|
||||||
return E.New("unknown sniffer: ", name)
|
return E.New("unknown sniffer: ", name)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package v2raywebsocket
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@ -61,7 +62,7 @@ func (c *WebsocketConn) Close() error {
|
|||||||
func (c *WebsocketConn) Read(b []byte) (n int, err error) {
|
func (c *WebsocketConn) Read(b []byte) (n int, err error) {
|
||||||
var header ws.Header
|
var header ws.Header
|
||||||
for {
|
for {
|
||||||
n, err = c.reader.Read(b)
|
n, err = wrapWsError0(c.reader.Read(b))
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
err = nil
|
err = nil
|
||||||
return
|
return
|
||||||
@ -95,7 +96,7 @@ func (c *WebsocketConn) Read(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *WebsocketConn) Write(p []byte) (n int, err error) {
|
func (c *WebsocketConn) Write(p []byte) (n int, err error) {
|
||||||
err = wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p)
|
err = wrapWsError(wsutil.WriteMessage(c.Conn, c.state, ws.OpBinary, p))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -146,7 +147,7 @@ func (c *EarlyWebsocketConn) Read(b []byte) (n int, err error) {
|
|||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.conn.Read(b)
|
return wrapWsError0(c.conn.Read(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
||||||
@ -177,12 +178,12 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
|||||||
conn, err = c.dialContext(c.ctx, &c.requestURL, c.headers)
|
conn, err = c.dialContext(c.ctx, &c.requestURL, c.headers)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return wrapWsError(err)
|
||||||
}
|
}
|
||||||
if len(lateData) > 0 {
|
if len(lateData) > 0 {
|
||||||
_, err = conn.Write(lateData)
|
_, err = conn.Write(lateData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return wrapWsError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.conn = conn
|
c.conn = conn
|
||||||
@ -191,7 +192,7 @@ func (c *EarlyWebsocketConn) writeRequest(content []byte) error {
|
|||||||
|
|
||||||
func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
||||||
if c.conn != nil {
|
if c.conn != nil {
|
||||||
return c.conn.Write(b)
|
return wrapWsError0(c.conn.Write(b))
|
||||||
}
|
}
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
@ -199,9 +200,9 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
|||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
if c.conn != nil {
|
if c.conn != nil {
|
||||||
return c.conn.Write(b)
|
return wrapWsError0(c.conn.Write(b))
|
||||||
}
|
}
|
||||||
err = c.writeRequest(b)
|
err = wrapWsError(c.writeRequest(b))
|
||||||
c.err = err
|
c.err = err
|
||||||
close(c.create)
|
close(c.create)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -212,17 +213,17 @@ func (c *EarlyWebsocketConn) Write(b []byte) (n int, err error) {
|
|||||||
|
|
||||||
func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
|
func (c *EarlyWebsocketConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
if c.conn != nil {
|
if c.conn != nil {
|
||||||
return c.conn.WriteBuffer(buffer)
|
return wrapWsError(c.conn.WriteBuffer(buffer))
|
||||||
}
|
}
|
||||||
c.access.Lock()
|
c.access.Lock()
|
||||||
defer c.access.Unlock()
|
defer c.access.Unlock()
|
||||||
if c.conn != nil {
|
if c.conn != nil {
|
||||||
return c.conn.WriteBuffer(buffer)
|
return wrapWsError(c.conn.WriteBuffer(buffer))
|
||||||
}
|
}
|
||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return c.err
|
return c.err
|
||||||
}
|
}
|
||||||
err := c.writeRequest(buffer.Bytes())
|
err := wrapWsError(c.writeRequest(buffer.Bytes()))
|
||||||
c.err = err
|
c.err = err
|
||||||
close(c.create)
|
close(c.create)
|
||||||
return err
|
return err
|
||||||
@ -272,3 +273,23 @@ func (c *EarlyWebsocketConn) Upstream() any {
|
|||||||
func (c *EarlyWebsocketConn) LazyHeadroom() bool {
|
func (c *EarlyWebsocketConn) LazyHeadroom() bool {
|
||||||
return c.conn == nil
|
return c.conn == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func wrapWsError(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var closedErr *wsutil.ClosedError
|
||||||
|
if errors.As(err, &closedErr) {
|
||||||
|
if closedErr.Code == ws.StatusNormalClosure {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapWsError0[T any](value T, err error) (T, error) {
|
||||||
|
if err == nil {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return common.DefaultValue[T](), wrapWsError(err)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user