Update On Sun Apr 5 20:57:47 CEST 2026

This commit is contained in:
github-action[bot]
2026-04-05 20:57:47 +02:00
parent c7a2f2077f
commit dbff9aa53f
107 changed files with 2624 additions and 1022 deletions
+1
View File
@@ -1320,3 +1320,4 @@ Update On Wed Apr 1 21:14:50 CEST 2026
Update On Thu Apr 2 21:09:36 CEST 2026
Update On Fri Apr 3 21:00:13 CEST 2026
Update On Sat Apr 4 20:56:37 CEST 2026
Update On Sun Apr 5 20:57:38 CEST 2026
+17 -7
View File
@@ -27,7 +27,7 @@ type Trojan struct {
hexPassword [trojan.KeyLength]byte
// for gun mux
gunTransport *gun.Transport
gunClient *gun.Client
realityConfig *tlsC.RealityConfig
echConfig *ech.Config
@@ -115,7 +115,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
case "grpc":
break // already handle in gun transport
break // already handle in dialContext
default:
// default tcp network
// handle TLS
@@ -175,7 +175,7 @@ func (t *Trojan) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C
func (t *Trojan) dialContext(ctx context.Context) (c net.Conn, err error) {
switch t.option.Network {
case "grpc": // gun transport
return t.gunTransport.Dial()
return t.gunClient.Dial()
default:
}
return t.dialer.DialContext(ctx, "tcp", t.addr)
@@ -236,10 +236,13 @@ func (t *Trojan) ProxyInfo() C.ProxyInfo {
// Close implements C.ProxyAdapter
func (t *Trojan) Close() error {
if t.gunTransport != nil {
return t.gunTransport.Close()
var errs []error
if t.gunClient != nil {
if err := t.gunClient.Close(); err != nil {
errs = append(errs, err)
}
}
return nil
return errors.Join(errs...)
}
func NewTrojan(option TrojanOption) (*Trojan, error) {
@@ -320,7 +323,14 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
PingInterval: option.GrpcOpts.PingInterval,
}
t.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
t.gunClient = gun.NewClient(
func() *gun.Transport {
return gun.NewTransport(dialFn, tlsConfig, gunConfig)
},
option.GrpcOpts.MaxConnections,
option.GrpcOpts.MinStreams,
option.GrpcOpts.MaxStreams,
)
}
return t, nil
+14 -7
View File
@@ -36,7 +36,7 @@ type Vless struct {
encryption *encryption.ClientInstance
// for gun mux
gunTransport *gun.Transport
gunClient *gun.Client
// for xhttp
xhttpClient *xhttp.Client
@@ -196,9 +196,9 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
case "grpc":
break // already handle in gun transport
break // already handle in dialContext
case "xhttp":
break // already handle in xhttp client
break // already handle in dialContext
default:
// default tcp network
// handle TLS
@@ -280,7 +280,7 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
func (v *Vless) dialContext(ctx context.Context) (c net.Conn, err error) {
switch v.option.Network {
case "grpc": // gun transport
return v.gunTransport.Dial()
return v.gunClient.Dial()
case "xhttp":
return v.xhttpClient.Dial()
default:
@@ -358,8 +358,8 @@ func (v *Vless) ProxyInfo() C.ProxyInfo {
// Close implements C.ProxyAdapter
func (v *Vless) Close() error {
var errs []error
if v.gunTransport != nil {
if err := v.gunTransport.Close(); err != nil {
if v.gunClient != nil {
if err := v.gunClient.Close(); err != nil {
errs = append(errs, err)
}
}
@@ -505,7 +505,14 @@ func NewVless(option VlessOption) (*Vless, error) {
}
}
v.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
v.gunClient = gun.NewClient(
func() *gun.Transport {
return gun.NewTransport(dialFn, tlsConfig, gunConfig)
},
option.GrpcOpts.MaxConnections,
option.GrpcOpts.MinStreams,
option.GrpcOpts.MaxStreams,
)
case "xhttp":
requestHost := v.option.XHTTPOpts.Host
if requestHost == "" {
+20 -7
View File
@@ -34,7 +34,7 @@ type Vmess struct {
option *VmessOption
// for gun mux
gunTransport *gun.Transport
gunClient *gun.Client
realityConfig *tlsC.RealityConfig
echConfig *ech.Config
@@ -86,6 +86,9 @@ type GrpcOptions struct {
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
GrpcUserAgent string `proxy:"grpc-user-agent,omitempty"`
PingInterval int `proxy:"ping-interval,omitempty"`
MaxConnections int `proxy:"max-connections,omitempty"`
MinStreams int `proxy:"min-streams,omitempty"`
MaxStreams int `proxy:"max-streams,omitempty"`
}
type WSOptions struct {
@@ -172,7 +175,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts)
case "grpc":
break // already handle in gun transport
break // already handle in dialContext
default:
// default tcp network
// handle TLS
@@ -274,7 +277,7 @@ func (v *Vmess) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
func (v *Vmess) dialContext(ctx context.Context) (c net.Conn, err error) {
switch v.option.Network {
case "grpc": // gun transport
return v.gunTransport.Dial()
return v.gunClient.Dial()
default:
}
return v.dialer.DialContext(ctx, "tcp", v.addr)
@@ -331,10 +334,13 @@ func (v *Vmess) ProxyInfo() C.ProxyInfo {
// Close implements C.ProxyAdapter
func (v *Vmess) Close() error {
if v.gunTransport != nil {
return v.gunTransport.Close()
var errs []error
if v.gunClient != nil {
if err := v.gunClient.Close(); err != nil {
errs = append(errs, err)
}
}
return nil
return errors.Join(errs...)
}
// SupportUOT implements C.ProxyAdapter
@@ -438,7 +444,14 @@ func NewVmess(option VmessOption) (*Vmess, error) {
}
}
v.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
v.gunClient = gun.NewClient(
func() *gun.Transport {
return gun.NewTransport(dialFn, tlsConfig, gunConfig)
},
option.GrpcOpts.MaxConnections,
option.GrpcOpts.MinStreams,
option.GrpcOpts.MaxStreams,
)
}
return v, nil
+39
View File
@@ -167,6 +167,29 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
// parseXHTTPExtra maps xray-core extra JSON fields to mihomo xhttp-opts fields.
func parseXHTTPExtra(extra map[string]any, opts map[string]any) {
// xmuxToReuse converts an xmux map to mihomo reuse-settings.
xmuxToReuse := func(xmux map[string]any) map[string]any {
reuse := make(map[string]any)
set := func(src, dst string) {
if v, ok := xmux[src]; ok {
switch val := v.(type) {
case string:
if val != "" {
reuse[dst] = val
}
case float64:
reuse[dst] = strconv.FormatInt(int64(val), 10)
}
}
}
set("maxConnections", "max-connections")
set("maxConcurrency", "max-concurrency")
set("cMaxReuseTimes", "c-max-reuse-times")
set("hMaxRequestTimes", "h-max-request-times")
set("hMaxReusableSecs", "h-max-reusable-secs")
return reuse
}
if v, ok := extra["noGRPCHeader"].(bool); ok && v {
opts["no-grpc-header"] = true
}
@@ -175,6 +198,13 @@ func parseXHTTPExtra(extra map[string]any, opts map[string]any) {
opts["x-padding-bytes"] = v
}
// xmux in root extra → reuse-settings
if xmuxAny, ok := extra["xmux"].(map[string]any); ok && len(xmuxAny) > 0 {
if reuse := xmuxToReuse(xmuxAny); len(reuse) > 0 {
opts["reuse-settings"] = reuse
}
}
if dsAny, ok := extra["downloadSettings"].(map[string]any); ok {
ds := make(map[string]any)
@@ -223,6 +253,15 @@ func parseXHTTPExtra(extra map[string]any, opts map[string]any) {
if v, ok := xhttpAny["xPaddingBytes"].(string); ok && v != "" {
ds["x-padding-bytes"] = v
}
// xmux inside downloadSettings.xhttpSettings.extra → download-settings.reuse-settings
if dsExtraAny, ok := xhttpAny["extra"].(map[string]any); ok {
if xmuxAny, ok := dsExtraAny["xmux"].(map[string]any); ok && len(xmuxAny) > 0 {
if reuse := xmuxToReuse(xmuxAny); len(reuse) > 0 {
ds["reuse-settings"] = reuse
}
}
}
}
if len(ds) > 0 {
+9
View File
@@ -670,6 +670,9 @@ proxies: # socks5
grpc-service-name: "example"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
# max-connections: 1 # Maximum connections. Conflict with max-streams.
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
# ip-version: ipv4
# vless
@@ -761,6 +764,9 @@ proxies: # socks5
grpc-service-name: "grpc"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
# max-connections: 1 # Maximum connections. Conflict with max-streams.
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
reality-opts:
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
@@ -896,6 +902,9 @@ proxies: # socks5
grpc-service-name: "example"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
# max-connections: 1 # Maximum connections. Conflict with max-streams.
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
- name: trojan-ws
server: server
+82
View File
@@ -13,6 +13,7 @@ import (
"net/url"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/metacubex/mihomo/common/buf"
@@ -51,6 +52,7 @@ type Conn struct {
closeMutex sync.Mutex
closed bool
onClose func()
// deadlines
deadline *time.Timer
@@ -209,6 +211,10 @@ func (g *Conn) Close() error {
}
}
if g.onClose != nil {
g.onClose()
}
return errors.Join(errorArr...)
}
@@ -240,6 +246,7 @@ type Transport struct {
ctx context.Context
cancel context.CancelFunc
closeOnce sync.Once
count atomic.Int64
}
func (t *Transport) Close() error {
@@ -349,6 +356,9 @@ func (t *Transport) Dial() (net.Conn, error) {
writer: writer,
}
t.count.Add(1)
conn.onClose = func() { t.count.Add(-1) }
go conn.Init()
// ensure conn.initOnce.Do has been called before return
@@ -358,6 +368,78 @@ func (t *Transport) Dial() (net.Conn, error) {
return conn, nil
}
type Client struct {
mutex sync.Mutex
maxConnections int
minStreams int
maxStreams int
transports []*Transport
maker func() *Transport
}
func NewClient(maker func() *Transport, maxConnections, minStreams, maxStreams int) *Client {
if maxConnections == 0 && minStreams == 0 && maxStreams == 0 {
maxConnections = 1
}
return &Client{
maxConnections: maxConnections,
minStreams: minStreams,
maxStreams: maxStreams,
maker: maker,
}
}
func (c *Client) Dial() (net.Conn, error) {
return c.getTransport().Dial()
}
func (c *Client) Close() error {
c.mutex.Lock()
defer c.mutex.Unlock()
var errs []error
for _, t := range c.transports {
if err := t.Close(); err != nil {
errs = append(errs, err)
}
}
c.transports = nil
return errors.Join(errs...)
}
func (c *Client) getTransport() *Transport {
c.mutex.Lock()
defer c.mutex.Unlock()
var transport *Transport
for _, t := range c.transports {
if transport == nil || t.count.Load() < transport.count.Load() {
transport = t
}
}
if transport == nil {
return c.newTransportLocked()
}
numStreams := int(transport.count.Load())
if numStreams == 0 {
return transport
}
if c.maxConnections > 0 {
if len(c.transports) >= c.maxConnections || numStreams < c.minStreams {
return transport
}
} else {
if c.maxStreams > 0 && numStreams < c.maxStreams {
return transport
}
}
return c.newTransportLocked()
}
func (c *Client) newTransportLocked() *Transport {
transport := c.maker()
c.transports = append(c.transports, transport)
return transport
}
func StreamGunWithConn(conn net.Conn, tlsConfig *vmess.TLSConfig, gunCfg *Config) (net.Conn, error) {
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
return conn, nil
-5
View File
@@ -112,11 +112,6 @@ func (w *h2ConnWrapper) CloseWrapper() {
w.closed = true
}
func (w *h2ConnWrapper) Close() error {
w.CloseWrapper()
return w.ExtendedConn.Close()
}
func (w *h2ConnWrapper) Upstream() any {
return w.ExtendedConn
}
+1 -7
View File
@@ -66,13 +66,7 @@ func WriteKIPMessage(w io.Writer, typ byte, payload []byte) error {
hdr[3] = typ
binary.BigEndian.PutUint16(hdr[4:], uint16(len(payload)))
if err := writeFull(w, hdr[:]); err != nil {
return err
}
if len(payload) == 0 {
return nil
}
return writeFull(w, payload)
return writeAllChunks(w, hdr[:], payload)
}
func ReadKIPMessage(r io.Reader) (*KIPMessage, error) {
@@ -173,16 +173,10 @@ func (s *Session) sendFrame(frameType byte, streamID uint32, payload []byte) err
s.writeMu.Lock()
defer s.writeMu.Unlock()
if err := writeFull(s.conn, header[:]); err != nil {
if err := writeAllChunks(s.conn, header[:], payload); err != nil {
s.closeWithError(err)
return err
}
if len(payload) > 0 {
if err := writeFull(s.conn, payload); err != nil {
s.closeWithError(err)
return err
}
}
return nil
}
@@ -315,17 +309,6 @@ func (s *Session) readLoop() {
}
}
func writeFull(w io.Writer, b []byte) error {
for len(b) > 0 {
n, err := w.Write(b)
if err != nil {
return err
}
b = b[n:]
}
return nil
}
func trimASCII(b []byte) string {
i := 0
j := len(b)
@@ -0,0 +1,19 @@
package multiplex
import "io"
func writeAllChunks(w io.Writer, chunks ...[]byte) error {
for _, chunk := range chunks {
for len(chunk) > 0 {
n, err := w.Write(chunk)
if err != nil {
return err
}
if n == 0 {
return io.ErrShortWrite
}
chunk = chunk[n:]
}
}
return nil
}
+36 -70
View File
@@ -3,12 +3,9 @@ package sudoku
import (
"bufio"
"bytes"
crypto_rand "crypto/rand"
"encoding/binary"
"errors"
"math/rand"
"net"
"sync"
"sync/atomic"
)
const IOBufferSize = 32 * 1024
@@ -45,14 +42,17 @@ type Conn struct {
table *Table
reader *bufio.Reader
recorder *bytes.Buffer
recording bool
recording atomic.Bool
recordLock sync.Mutex
rawBuf []byte
pendingData []byte
hintBuf []byte
pendingData pendingBuffer
hintBuf [4]byte
hintCount int
writeMu sync.Mutex
writeBuf []byte
rng *rand.Rand
rng randomSource
paddingThreshold uint64
}
@@ -77,33 +77,28 @@ func (sc *Conn) CloseRead() error {
}
func NewConn(c net.Conn, table *Table, pMin, pMax int, record bool) *Conn {
var seedBytes [8]byte
if _, err := crypto_rand.Read(seedBytes[:]); err != nil {
binary.BigEndian.PutUint64(seedBytes[:], uint64(rand.Int63()))
}
seed := int64(binary.BigEndian.Uint64(seedBytes[:]))
localRng := rand.New(rand.NewSource(seed))
localRng := newSeededRand()
sc := &Conn{
Conn: c,
table: table,
reader: bufio.NewReaderSize(c, IOBufferSize),
rawBuf: make([]byte, IOBufferSize),
pendingData: make([]byte, 0, 4096),
hintBuf: make([]byte, 0, 4),
pendingData: newPendingBuffer(4096),
writeBuf: make([]byte, 0, 4096),
rng: localRng,
paddingThreshold: pickPaddingThreshold(localRng, pMin, pMax),
}
if record {
sc.recorder = new(bytes.Buffer)
sc.recording = true
sc.recording.Store(true)
}
return sc
}
func (sc *Conn) StopRecording() {
sc.recordLock.Lock()
sc.recording = false
sc.recording.Store(false)
sc.recorder = nil
sc.recordLock.Unlock()
}
@@ -137,74 +132,50 @@ func (sc *Conn) Write(p []byte) (n int, err error) {
return 0, nil
}
outCapacity := len(p) * 6
out := make([]byte, 0, outCapacity)
pads := sc.table.PaddingPool
padLen := len(pads)
sc.writeMu.Lock()
defer sc.writeMu.Unlock()
for _, b := range p {
if shouldPad(sc.rng, sc.paddingThreshold) {
out = append(out, pads[sc.rng.Intn(padLen)])
}
puzzles := sc.table.EncodeTable[b]
puzzle := puzzles[sc.rng.Intn(len(puzzles))]
perm := perm4[sc.rng.Intn(len(perm4))]
for _, idx := range perm {
if shouldPad(sc.rng, sc.paddingThreshold) {
out = append(out, pads[sc.rng.Intn(padLen)])
}
out = append(out, puzzle[idx])
}
}
if shouldPad(sc.rng, sc.paddingThreshold) {
out = append(out, pads[sc.rng.Intn(padLen)])
}
return len(p), writeFull(sc.Conn, out)
sc.writeBuf = encodeSudokuPayload(sc.writeBuf[:0], sc.table, sc.rng, sc.paddingThreshold, p)
return len(p), writeFull(sc.Conn, sc.writeBuf)
}
func (sc *Conn) Read(p []byte) (n int, err error) {
if len(sc.pendingData) > 0 {
n = copy(p, sc.pendingData)
if n == len(sc.pendingData) {
sc.pendingData = sc.pendingData[:0]
} else {
sc.pendingData = sc.pendingData[n:]
}
if n, ok := drainPending(p, &sc.pendingData); ok {
return n, nil
}
for {
if len(sc.pendingData) > 0 {
if sc.pendingData.available() > 0 {
break
}
nr, rErr := sc.reader.Read(sc.rawBuf)
if nr > 0 {
chunk := sc.rawBuf[:nr]
sc.recordLock.Lock()
if sc.recording {
sc.recorder.Write(chunk)
if sc.recording.Load() {
sc.recordLock.Lock()
if sc.recording.Load() && sc.recorder != nil {
sc.recorder.Write(chunk)
}
sc.recordLock.Unlock()
}
sc.recordLock.Unlock()
layout := sc.table.layout
for _, b := range chunk {
if !sc.table.layout.isHint(b) {
if !layout.hintTable[b] {
continue
}
sc.hintBuf = append(sc.hintBuf, b)
if len(sc.hintBuf) == 4 {
key := packHintsToKey([4]byte{sc.hintBuf[0], sc.hintBuf[1], sc.hintBuf[2], sc.hintBuf[3]})
sc.hintBuf[sc.hintCount] = b
sc.hintCount++
if sc.hintCount == len(sc.hintBuf) {
key := packHintsToKey(sc.hintBuf)
val, ok := sc.table.DecodeMap[key]
if !ok {
return 0, errors.New("INVALID_SUDOKU_MAP_MISS")
return 0, ErrInvalidSudokuMapMiss
}
sc.pendingData = append(sc.pendingData, val)
sc.hintBuf = sc.hintBuf[:0]
sc.pendingData.appendByte(val)
sc.hintCount = 0
}
}
}
@@ -212,16 +183,11 @@ func (sc *Conn) Read(p []byte) (n int, err error) {
if rErr != nil {
return 0, rErr
}
if len(sc.pendingData) > 0 {
if sc.pendingData.available() > 0 {
break
}
}
n = copy(p, sc.pendingData)
if n == len(sc.pendingData) {
sc.pendingData = sc.pendingData[:0]
} else {
sc.pendingData = sc.pendingData[n:]
}
n, _ = drainPending(p, &sc.pendingData)
return n, nil
}
@@ -0,0 +1,36 @@
package sudoku
func encodeSudokuPayload(dst []byte, table *Table, rng randomSource, paddingThreshold uint64, p []byte) []byte {
if len(p) == 0 {
return dst[:0]
}
outCapacity := len(p)*6 + 1
if cap(dst) < outCapacity {
dst = make([]byte, 0, outCapacity)
}
out := dst[:0]
pads := table.PaddingPool
padLen := len(pads)
for _, b := range p {
if shouldPad(rng, paddingThreshold) {
out = append(out, pads[rng.Intn(padLen)])
}
puzzles := table.EncodeTable[b]
puzzle := puzzles[rng.Intn(len(puzzles))]
perm := perm4[rng.Intn(len(perm4))]
for _, idx := range perm {
if shouldPad(rng, paddingThreshold) {
out = append(out, pads[rng.Intn(padLen)])
}
out = append(out, puzzle[idx])
}
}
if shouldPad(rng, paddingThreshold) {
out = append(out, pads[rng.Intn(padLen)])
}
return out
}
+112 -78
View File
@@ -14,17 +14,30 @@ type byteLayout struct {
padMarker byte
paddingPool []byte
encodeHint func(val, pos byte) byte
encodeGroup func(group byte) byte
decodeGroup func(b byte) (byte, bool)
hintTable [256]bool
encodeHint [4][16]byte
encodeGroup [64]byte
decodeGroup [256]byte
groupValid [256]bool
}
func (l *byteLayout) isHint(b byte) bool {
if (b & l.hintMask) == l.hintValue {
return true
return l != nil && l.hintTable[b]
}
func (l *byteLayout) hintByte(val, pos byte) byte {
return l.encodeHint[val&0x03][pos&0x0F]
}
func (l *byteLayout) groupByte(group byte) byte {
return l.encodeGroup[group&0x3F]
}
func (l *byteLayout) decodePackedGroup(b byte) (byte, bool) {
if l == nil {
return 0, false
}
// ASCII layout maps the single non-printable marker (0x7F) to '\n' on the wire.
return l.name == "ascii" && b == '\n'
return l.decodeGroup[b], l.groupValid[b]
}
// resolveLayout picks the byte layout for a single traffic direction.
@@ -50,38 +63,44 @@ func newASCIILayout() *byteLayout {
for i := 0; i < 32; i++ {
padding = append(padding, byte(0x20+i))
}
return &byteLayout{
layout := &byteLayout{
name: "ascii",
hintMask: 0x40,
hintValue: 0x40,
padMarker: 0x3F,
paddingPool: padding,
encodeHint: func(val, pos byte) byte {
b := 0x40 | ((val & 0x03) << 4) | (pos & 0x0F)
// Avoid DEL (0x7F) in prefer_ascii mode; map it to '\n' to reduce fingerprint.
if b == 0x7F {
return '\n'
}
return b
},
encodeGroup: func(group byte) byte {
b := 0x40 | (group & 0x3F)
// Avoid DEL (0x7F) in prefer_ascii mode; map it to '\n' to reduce fingerprint.
if b == 0x7F {
return '\n'
}
return b
},
decodeGroup: func(b byte) (byte, bool) {
if b == '\n' {
return 0x3F, true
}
if (b & 0x40) == 0 {
return 0, false
}
return b & 0x3F, true
},
}
for val := 0; val < 4; val++ {
for pos := 0; pos < 16; pos++ {
b := byte(0x40 | (byte(val) << 4) | byte(pos))
if b == 0x7F {
b = '\n'
}
layout.encodeHint[val][pos] = b
}
}
for group := 0; group < 64; group++ {
b := byte(0x40 | byte(group))
if b == 0x7F {
b = '\n'
}
layout.encodeGroup[group] = b
}
for b := 0; b < 256; b++ {
wire := byte(b)
if (wire & 0x40) == 0x40 {
layout.hintTable[wire] = true
layout.decodeGroup[wire] = wire & 0x3F
layout.groupValid[wire] = true
}
}
layout.hintTable['\n'] = true
layout.decodeGroup['\n'] = 0x3F
layout.groupValid['\n'] = true
return layout
}
func newEntropyLayout() *byteLayout {
@@ -90,26 +109,35 @@ func newEntropyLayout() *byteLayout {
padding = append(padding, byte(0x80+i))
padding = append(padding, byte(0x10+i))
}
return &byteLayout{
layout := &byteLayout{
name: "entropy",
hintMask: 0x90,
hintValue: 0x00,
padMarker: 0x80,
paddingPool: padding,
encodeHint: func(val, pos byte) byte {
return ((val & 0x03) << 5) | (pos & 0x0F)
},
encodeGroup: func(group byte) byte {
v := group & 0x3F
return ((v & 0x30) << 1) | (v & 0x0F)
},
decodeGroup: func(b byte) (byte, bool) {
if (b & 0x90) != 0 {
return 0, false
}
return ((b >> 1) & 0x30) | (b & 0x0F), true
},
}
for val := 0; val < 4; val++ {
for pos := 0; pos < 16; pos++ {
layout.encodeHint[val][pos] = (byte(val) << 5) | byte(pos)
}
}
for group := 0; group < 64; group++ {
v := byte(group)
layout.encodeGroup[group] = ((v & 0x30) << 1) | (v & 0x0F)
}
for b := 0; b < 256; b++ {
wire := byte(b)
if (wire & 0x90) != 0 {
continue
}
layout.hintTable[wire] = true
layout.decodeGroup[wire] = ((wire >> 1) & 0x30) | (wire & 0x0F)
layout.groupValid[wire] = true
}
return layout
}
func newCustomLayout(pattern string) (*byteLayout, error) {
@@ -162,26 +190,6 @@ func newCustomLayout(pattern string) (*byteLayout, error) {
return out
}
decodeGroup := func(b byte) (byte, bool) {
if (b & xMask) != xMask {
return 0, false
}
var val, pos byte
if b&(1<<pBits[0]) != 0 {
val |= 0x02
}
if b&(1<<pBits[1]) != 0 {
val |= 0x01
}
for i, bit := range vBits {
if b&(1<<bit) != 0 {
pos |= 1 << (3 - uint8(i))
}
}
group := (val << 4) | (pos & 0x0F)
return group, true
}
paddingSet := make(map[byte]struct{})
var padding []byte
for drop := range xBits {
@@ -202,20 +210,46 @@ func newCustomLayout(pattern string) (*byteLayout, error) {
return nil, fmt.Errorf("custom table produced empty padding pool")
}
return &byteLayout{
layout := &byteLayout{
name: fmt.Sprintf("custom(%s)", cleaned),
hintMask: xMask,
hintValue: xMask,
padMarker: padding[0],
paddingPool: padding,
encodeHint: func(val, pos byte) byte {
return encodeBits(val, pos, -1)
},
encodeGroup: func(group byte) byte {
val := (group >> 4) & 0x03
pos := group & 0x0F
return encodeBits(val, pos, -1)
},
decodeGroup: decodeGroup,
}, nil
}
for val := 0; val < 4; val++ {
for pos := 0; pos < 16; pos++ {
layout.encodeHint[val][pos] = encodeBits(byte(val), byte(pos), -1)
}
}
for group := 0; group < 64; group++ {
val := byte(group>>4) & 0x03
pos := byte(group) & 0x0F
layout.encodeGroup[group] = encodeBits(val, pos, -1)
}
for b := 0; b < 256; b++ {
wire := byte(b)
if (wire & xMask) != xMask {
continue
}
layout.hintTable[wire] = true
var val, pos byte
if wire&(1<<pBits[0]) != 0 {
val |= 0x02
}
if wire&(1<<pBits[1]) != 0 {
val |= 0x01
}
for i, bit := range vBits {
if wire&(1<<bit) != 0 {
pos |= 1 << (3 - uint8(i))
}
}
layout.decodeGroup[wire] = (val << 4) | pos
layout.groupValid[wire] = true
}
return layout, nil
}
@@ -2,10 +2,7 @@ package sudoku
import (
"bufio"
crypto_rand "crypto/rand"
"encoding/binary"
"io"
"math/rand"
"net"
"sync"
)
@@ -25,7 +22,7 @@ type PackedConn struct {
// Read-side buffers.
rawBuf []byte
pendingData []byte
pendingData pendingBuffer
// Write-side state.
writeMu sync.Mutex
@@ -38,7 +35,7 @@ type PackedConn struct {
readBits int
// Padding selection matches Conn's threshold-based model.
rng *rand.Rand
rng randomSource
paddingThreshold uint64
padMarker byte
padPool []byte
@@ -65,19 +62,14 @@ func (pc *PackedConn) CloseRead() error {
}
func NewPackedConn(c net.Conn, table *Table, pMin, pMax int) *PackedConn {
var seedBytes [8]byte
if _, err := crypto_rand.Read(seedBytes[:]); err != nil {
binary.BigEndian.PutUint64(seedBytes[:], uint64(rand.Int63()))
}
seed := int64(binary.BigEndian.Uint64(seedBytes[:]))
localRng := rand.New(rand.NewSource(seed))
localRng := newSeededRand()
pc := &PackedConn{
Conn: c,
table: table,
reader: bufio.NewReaderSize(c, IOBufferSize),
rawBuf: make([]byte, IOBufferSize),
pendingData: make([]byte, 0, 4096),
pendingData: newPendingBuffer(4096),
writeBuf: make([]byte, 0, 4096),
rng: localRng,
paddingThreshold: pickPaddingThreshold(localRng, pMin, pMax),
@@ -104,7 +96,7 @@ func (pc *PackedConn) maybeAddPadding(out []byte) []byte {
func (pc *PackedConn) appendGroup(out []byte, group byte) []byte {
out = pc.maybeAddPadding(out)
return append(out, pc.encodeGroup(group))
return append(out, pc.table.layout.groupByte(group))
}
func (pc *PackedConn) appendForcedPadding(out []byte) []byte {
@@ -156,19 +148,6 @@ func (pc *PackedConn) writeProtectedPrefix(out []byte, p []byte) ([]byte, int) {
return out, limit
}
func (pc *PackedConn) drainPendingData(dst []byte) int {
n := copy(dst, pc.pendingData)
if n == len(pc.pendingData) {
pc.pendingData = pc.pendingData[:0]
return n
}
remaining := len(pc.pendingData) - n
copy(pc.pendingData, pc.pendingData[n:])
pc.pendingData = pc.pendingData[:remaining]
return n
}
func (pc *PackedConn) Write(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
@@ -282,7 +261,7 @@ func (pc *PackedConn) Flush() error {
pc.bitBuf = 0
pc.bitCount = 0
out = append(out, pc.encodeGroup(group&0x3F))
out = append(out, pc.table.layout.groupByte(group&0x3F))
out = append(out, pc.padMarker)
}
@@ -301,14 +280,17 @@ func writeFull(w io.Writer, b []byte) error {
if err != nil {
return err
}
if n == 0 {
return io.ErrShortWrite
}
b = b[n:]
}
return nil
}
func (pc *PackedConn) Read(p []byte) (int, error) {
if len(pc.pendingData) > 0 {
return pc.drainPendingData(p), nil
if n, ok := drainPending(p, &pc.pendingData); ok {
return n, nil
}
for {
@@ -320,7 +302,7 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
layout := pc.table.layout
for _, b := range pc.rawBuf[:nr] {
if !layout.isHint(b) {
if !layout.hintTable[b] {
if b == padMarker {
rBuf = 0
rBits = 0
@@ -328,7 +310,7 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
continue
}
group, ok := layout.decodeGroup(b)
group, ok := layout.decodePackedGroup(b)
if !ok {
return 0, ErrInvalidSudokuMapMiss
}
@@ -339,7 +321,12 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
if rBits >= 8 {
rBits -= 8
val := byte(rBuf >> rBits)
pc.pendingData = append(pc.pendingData, val)
pc.pendingData.appendByte(val)
if rBits == 0 {
rBuf = 0
} else {
rBuf &= (uint64(1) << rBits) - 1
}
}
}
@@ -352,24 +339,21 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
pc.readBitBuf = 0
pc.readBits = 0
}
if len(pc.pendingData) > 0 {
if pc.pendingData.available() > 0 {
break
}
return 0, rErr
}
if len(pc.pendingData) > 0 {
if pc.pendingData.available() > 0 {
break
}
}
return pc.drainPendingData(p), nil
n, _ := drainPending(p, &pc.pendingData)
return n, nil
}
func (pc *PackedConn) getPaddingByte() byte {
return pc.padPool[pc.rng.Intn(len(pc.padPool))]
}
func (pc *PackedConn) encodeGroup(group byte) byte {
return pc.table.layout.encodeGroup(group)
}
@@ -1,10 +1,8 @@
package sudoku
import "math/rand"
const probOne = uint64(1) << 32
func pickPaddingThreshold(r *rand.Rand, pMin, pMax int) uint64 {
func pickPaddingThreshold(r randomSource, pMin, pMax int) uint64 {
if r == nil {
return 0
}
@@ -30,7 +28,7 @@ func pickPaddingThreshold(r *rand.Rand, pMin, pMax int) uint64 {
return min + (u * (max - min) >> 32)
}
func shouldPad(r *rand.Rand, threshold uint64) bool {
func shouldPad(r randomSource, threshold uint64) bool {
if threshold == 0 {
return false
}
@@ -0,0 +1,57 @@
package sudoku
type pendingBuffer struct {
data []byte
off int
}
func newPendingBuffer(capacity int) pendingBuffer {
return pendingBuffer{data: make([]byte, 0, capacity)}
}
func (p *pendingBuffer) available() int {
if p == nil {
return 0
}
return len(p.data) - p.off
}
func (p *pendingBuffer) reset() {
if p == nil {
return
}
p.data = p.data[:0]
p.off = 0
}
func (p *pendingBuffer) ensureAppendCapacity(extra int) {
if p == nil || extra <= 0 || p.off == 0 {
return
}
if cap(p.data)-len(p.data) >= extra {
return
}
unread := len(p.data) - p.off
copy(p.data[:unread], p.data[p.off:])
p.data = p.data[:unread]
p.off = 0
}
func (p *pendingBuffer) appendByte(b byte) {
p.ensureAppendCapacity(1)
p.data = append(p.data, b)
}
func drainPending(dst []byte, pending *pendingBuffer) (int, bool) {
if pending == nil || pending.available() == 0 {
return 0, false
}
n := copy(dst, pending.data[pending.off:])
pending.off += n
if pending.off == len(pending.data) {
pending.reset()
}
return n, true
}
@@ -0,0 +1,56 @@
package sudoku
import (
crypto_rand "crypto/rand"
"encoding/binary"
"time"
)
type randomSource interface {
Uint32() uint32
Uint64() uint64
Intn(n int) int
}
type sudokuRand struct {
state uint64
}
func newSeededRand() *sudokuRand {
seed := time.Now().UnixNano()
var seedBytes [8]byte
if _, err := crypto_rand.Read(seedBytes[:]); err == nil {
seed = int64(binary.BigEndian.Uint64(seedBytes[:]))
}
return newSudokuRand(seed)
}
func newSudokuRand(seed int64) *sudokuRand {
state := uint64(seed)
if state == 0 {
state = 0x9e3779b97f4a7c15
}
return &sudokuRand{state: state}
}
func (r *sudokuRand) Uint64() uint64 {
if r == nil {
return 0
}
r.state += 0x9e3779b97f4a7c15
z := r.state
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9
z = (z ^ (z >> 27)) * 0x94d049bb133111eb
return z ^ (z >> 31)
}
func (r *sudokuRand) Uint32() uint32 {
return uint32(r.Uint64() >> 32)
}
func (r *sudokuRand) Intn(n int) int {
if n <= 1 {
return 0
}
return int((uint64(r.Uint32()) * uint64(n)) >> 32)
}
@@ -146,7 +146,7 @@ func newSingleDirectionTable(key string, mode string, customPattern string) (*Ta
if matchCount == 1 {
// 唯一确定,生成最终编码字节
for i, p := range rawParts {
currentHints[i] = t.layout.encodeHint(p.val-1, p.pos)
currentHints[i] = t.layout.hintByte(p.val-1, p.pos)
}
t.EncodeTable[byteVal] = append(t.EncodeTable[byteVal], currentHints)
+60 -42
View File
@@ -8,7 +8,6 @@ import (
"io"
"net"
"net/netip"
"strconv"
"sync"
"time"
@@ -37,42 +36,15 @@ func WriteDatagram(w io.Writer, addr string, payload []byte) error {
binary.BigEndian.PutUint16(header[:2], uint16(len(addrBuf)))
binary.BigEndian.PutUint16(header[2:], uint16(len(payload)))
if err := writeFull(w, header[:]); err != nil {
return err
}
if err := writeFull(w, addrBuf); err != nil {
return err
}
return writeFull(w, payload)
return writeAllChunks(w, header[:], addrBuf, payload)
}
// ReadDatagram parses a single UDP datagram frame from the reliable stream.
func ReadDatagram(r io.Reader) (string, []byte, error) {
var header [4]byte
if _, err := io.ReadFull(r, header[:]); err != nil {
return "", nil, err
}
addrLen := int(binary.BigEndian.Uint16(header[:2]))
payloadLen := int(binary.BigEndian.Uint16(header[2:]))
if addrLen <= 0 || addrLen > maxUoTPayload {
return "", nil, fmt.Errorf("invalid address length: %d", addrLen)
}
if payloadLen < 0 || payloadLen > maxUoTPayload {
return "", nil, fmt.Errorf("invalid payload length: %d", payloadLen)
}
addrBuf := make([]byte, addrLen)
if _, err := io.ReadFull(r, addrBuf); err != nil {
return "", nil, err
}
addr, err := DecodeAddress(bytes.NewReader(addrBuf))
addr, payloadLen, err := readDatagramHeaderAndAddress(r)
if err != nil {
return "", nil, fmt.Errorf("decode address: %w", err)
return "", nil, err
}
payload := make([]byte, payloadLen)
if _, err := io.ReadFull(r, payload); err != nil {
return "", nil, err
@@ -93,26 +65,29 @@ func NewUoTPacketConn(conn net.Conn) *UoTPacketConn {
func (c *UoTPacketConn) ReadFrom(p []byte) (int, net.Addr, error) {
for {
addrStr, payload, err := ReadDatagram(c.conn)
addrStr, payloadLen, err := readDatagramHeaderAndAddress(c.conn)
if err != nil {
return 0, nil, err
}
if len(payload) > len(p) {
udpAddr, err := parseDatagramUDPAddr(addrStr)
if payloadLen > len(p) {
if discardErr := discardBytes(c.conn, payloadLen); discardErr != nil {
return 0, nil, discardErr
}
return 0, nil, io.ErrShortBuffer
}
host, port, _ := net.SplitHostPort(addrStr)
portInt, _ := strconv.ParseUint(port, 10, 16)
ip, err := netip.ParseAddr(host)
if err != nil { // disallow domain addr at here, just ignore
if err != nil {
if discardErr := discardBytes(c.conn, payloadLen); discardErr != nil {
return 0, nil, discardErr
}
log.Debugln("[Sudoku][UoT] discard datagram with invalid address %s: %v", addrStr, err)
continue
}
udpAddr := net.UDPAddrFromAddrPort(netip.AddrPortFrom(ip.Unmap(), uint16(portInt)))
copy(p, payload)
return len(payload), udpAddr, nil
if _, err := io.ReadFull(c.conn, p[:payloadLen]); err != nil {
return 0, nil, err
}
return payloadLen, udpAddr, nil
}
}
@@ -147,3 +122,46 @@ func (c *UoTPacketConn) SetReadDeadline(t time.Time) error {
func (c *UoTPacketConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
func readDatagramHeaderAndAddress(r io.Reader) (string, int, error) {
var header [4]byte
if _, err := io.ReadFull(r, header[:]); err != nil {
return "", 0, err
}
addrLen := int(binary.BigEndian.Uint16(header[:2]))
payloadLen := int(binary.BigEndian.Uint16(header[2:]))
if addrLen <= 0 || addrLen > maxUoTPayload {
return "", 0, fmt.Errorf("invalid address length: %d", addrLen)
}
if payloadLen < 0 || payloadLen > maxUoTPayload {
return "", 0, fmt.Errorf("invalid payload length: %d", payloadLen)
}
addrBuf := make([]byte, addrLen)
if _, err := io.ReadFull(r, addrBuf); err != nil {
return "", 0, err
}
addr, err := DecodeAddress(bytes.NewReader(addrBuf))
if err != nil {
return "", 0, fmt.Errorf("decode address: %w", err)
}
return addr, payloadLen, nil
}
func parseDatagramUDPAddr(addr string) (*net.UDPAddr, error) {
addrPort, err := netip.ParseAddrPort(addr)
if err != nil {
return nil, err
}
return net.UDPAddrFromAddrPort(netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port())), nil
}
func discardBytes(r io.Reader, n int) error {
if n <= 0 {
return nil
}
_, err := io.CopyN(io.Discard, r, int64(n))
return err
}
@@ -0,0 +1,19 @@
package sudoku
import "io"
func writeAllChunks(w io.Writer, chunks ...[]byte) error {
for _, chunk := range chunks {
for len(chunk) > 0 {
n, err := w.Write(chunk)
if err != nil {
return err
}
if n == 0 {
return io.ErrShortWrite
}
chunk = chunk[n:]
}
}
return nil
}
+2 -2
View File
@@ -2,7 +2,7 @@
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.22",
"mihomo_alpha": "alpha-f3b0581",
"mihomo_alpha": "alpha-3ef8a0f",
"clash_rs": "v0.9.6",
"clash_premium": "2023-09-05-gdcc8d87",
"clash_rs_alpha": "0.9.6-alpha+sha.c414fb7"
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-rs-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2026-04-03T22:23:27.347Z"
"updated_at": "2026-04-04T22:22:52.085Z"
}
+10
View File
@@ -2,6 +2,16 @@
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
## [2.63.1](https://github.com/filebrowser/filebrowser/compare/v2.63.0...v2.63.1) (2026-04-04)
### Bug Fixes
* check download permission in resource handler ([#5891](https://github.com/filebrowser/filebrowser/issues/5891)) ([1e03fea](https://github.com/filebrowser/filebrowser/commit/1e03feadb550e4414b5589a6a8df57f538efba15))
* check share owner permissions on public share access ([#5888](https://github.com/filebrowser/filebrowser/issues/5888)) ([7dbf7a3](https://github.com/filebrowser/filebrowser/commit/7dbf7a3528234b2a9ee9c4115e8ecf58d258ca51))
* enforce directory boundary in rule path matching ([#5889](https://github.com/filebrowser/filebrowser/issues/5889)) ([8adf127](https://github.com/filebrowser/filebrowser/commit/8adf127c7d33585333b8030869f6f318e6517179))
* restrict default permissions for proxy-auth auto-provisioned users ([#5890](https://github.com/filebrowser/filebrowser/issues/5890)) ([f13c7c8](https://github.com/filebrowser/filebrowser/commit/f13c7c8cffd6d58ff29c4a6763ced1385f69961e))
## [2.63.0](https://github.com/filebrowser/filebrowser/compare/v2.62.2...v2.63.0) (2026-04-04)
+3
View File
@@ -46,6 +46,9 @@ func (a ProxyAuth) createUser(usr users.Store, setting *settings.Settings, srv *
LockPassword: true,
}
setting.Defaults.Apply(user)
user.Perm.Admin = false
user.Perm.Execute = false
user.Commands = []string{}
var userHome string
userHome, err = setting.MakeUserDir(user.Username, user.Scope, srv.Root)
+79
View File
@@ -0,0 +1,79 @@
package auth
import (
"net/http"
"testing"
fberrors "github.com/filebrowser/filebrowser/v2/errors"
"github.com/filebrowser/filebrowser/v2/settings"
"github.com/filebrowser/filebrowser/v2/users"
)
type mockUserStore struct {
users map[string]*users.User
}
func (m *mockUserStore) Get(_ string, id interface{}) (*users.User, error) {
if v, ok := id.(string); ok {
if u, ok := m.users[v]; ok {
return u, nil
}
}
return nil, fberrors.ErrNotExist
}
func (m *mockUserStore) Gets(_ string) ([]*users.User, error) { return nil, nil }
func (m *mockUserStore) Update(_ *users.User, _ ...string) error { return nil }
func (m *mockUserStore) Save(user *users.User) error {
m.users[user.Username] = user
return nil
}
func (m *mockUserStore) Delete(_ interface{}) error { return nil }
func (m *mockUserStore) LastUpdate(_ uint) int64 { return 0 }
func TestProxyAuthCreateUserRestrictsDefaults(t *testing.T) {
t.Parallel()
store := &mockUserStore{users: make(map[string]*users.User)}
srv := &settings.Server{Root: t.TempDir()}
s := &settings.Settings{
Key: []byte("key"),
AuthMethod: MethodProxyAuth,
Defaults: settings.UserDefaults{
Perm: users.Permissions{
Admin: true,
Execute: true,
Create: true,
Rename: true,
Modify: true,
Delete: true,
Share: true,
Download: true,
},
Commands: []string{"git", "ls", "cat", "id"},
},
}
auth := ProxyAuth{Header: "X-Remote-User"}
req, _ := http.NewRequest(http.MethodGet, "/", http.NoBody)
req.Header.Set("X-Remote-User", "newproxyuser")
user, err := auth.Auth(req, store, s, srv)
if err != nil {
t.Fatalf("Auth() error: %v", err)
}
if user.Perm.Admin {
t.Error("auto-provisioned proxy user should not have Admin permission")
}
if user.Perm.Execute {
t.Error("auto-provisioned proxy user should not have Execute permission")
}
if len(user.Commands) != 0 {
t.Errorf("auto-provisioned proxy user should have empty Commands, got %v", user.Commands)
}
if !user.Perm.Create {
t.Error("auto-provisioned proxy user should retain Create permission from defaults")
}
}
+4
View File
@@ -33,6 +33,10 @@ var withHashFile = func(fn handleFunc) handleFunc {
return errToStatus(err), err
}
if !user.Perm.Share || !user.Perm.Download {
return http.StatusForbidden, nil
}
d.user = user
file, err := files.NewFileInfo(&files.FileOptions{
+36 -1
View File
@@ -23,38 +23,66 @@ func TestPublicShareHandlerAuthentication(t *testing.T) {
testCases := map[string]struct {
share *share.Link
req *http.Request
sharePerm bool
downloadPerm bool
expectedStatusCode int
}{
"Public share, no auth required": {
share: &share.Link{Hash: "h", UserID: 1},
req: newHTTPRequest(t),
sharePerm: true,
downloadPerm: true,
expectedStatusCode: 200,
},
"Private share, no auth provided, 401": {
share: &share.Link{Hash: "h", UserID: 1, PasswordHash: passwordBcrypt, Token: "123"},
req: newHTTPRequest(t),
sharePerm: true,
downloadPerm: true,
expectedStatusCode: 401,
},
"Private share, authentication via token": {
share: &share.Link{Hash: "h", UserID: 1, PasswordHash: passwordBcrypt, Token: "123"},
req: newHTTPRequest(t, func(r *http.Request) { r.URL.RawQuery = "token=123" }),
sharePerm: true,
downloadPerm: true,
expectedStatusCode: 200,
},
"Private share, authentication via invalid token, 401": {
share: &share.Link{Hash: "h", UserID: 1, PasswordHash: passwordBcrypt, Token: "123"},
req: newHTTPRequest(t, func(r *http.Request) { r.URL.RawQuery = "token=1234" }),
sharePerm: true,
downloadPerm: true,
expectedStatusCode: 401,
},
"Private share, authentication via password": {
share: &share.Link{Hash: "h", UserID: 1, PasswordHash: passwordBcrypt, Token: "123"},
req: newHTTPRequest(t, func(r *http.Request) { r.Header.Set("X-SHARE-PASSWORD", "password") }),
sharePerm: true,
downloadPerm: true,
expectedStatusCode: 200,
},
"Private share, authentication via invalid password, 401": {
share: &share.Link{Hash: "h", UserID: 1, PasswordHash: passwordBcrypt, Token: "123"},
req: newHTTPRequest(t, func(r *http.Request) { r.Header.Set("X-SHARE-PASSWORD", "wrong-password") }),
sharePerm: true,
downloadPerm: true,
expectedStatusCode: 401,
},
"Share owner lost share permission, 403": {
share: &share.Link{Hash: "h", UserID: 1},
req: newHTTPRequest(t),
sharePerm: false,
downloadPerm: true,
expectedStatusCode: 403,
},
"Share owner lost download permission, 403": {
share: &share.Link{Hash: "h", UserID: 1},
req: newHTTPRequest(t),
sharePerm: true,
downloadPerm: false,
expectedStatusCode: 403,
},
}
for name, tc := range testCases {
@@ -82,7 +110,14 @@ func TestPublicShareHandlerAuthentication(t *testing.T) {
if err := storage.Share.Save(tc.share); err != nil {
t.Fatalf("failed to save share: %v", err)
}
if err := storage.Users.Save(&users.User{Username: "username", Password: "pw"}); err != nil {
if err := storage.Users.Save(&users.User{
Username: "username",
Password: "pw",
Perm: users.Permissions{
Share: tc.sharePerm,
Download: tc.downloadPerm,
},
}); err != nil {
t.Fatalf("failed to save user: %v", err)
}
if err := storage.Settings.Save(&settings.Settings{Key: []byte("key")}); err != nil {
+4 -1
View File
@@ -30,7 +30,7 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
Expand: true,
ReadHeader: d.server.TypeDetectionByHeader,
Checker: d,
Content: true,
Content: d.user.Perm.Download,
})
if err != nil {
return errToStatus(err), err
@@ -42,6 +42,9 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d
file.ApplySort()
return renderJSON(w, r, file)
} else if encoding == "true" {
if !d.user.Perm.Download {
return http.StatusAccepted, nil
}
if file.Type != "text" {
return renderJSON(w, r, file)
}
+10 -1
View File
@@ -31,7 +31,16 @@ func (r *Rule) Matches(path string) bool {
return r.Regexp.MatchString(path)
}
return strings.HasPrefix(path, r.Path)
if path == r.Path {
return true
}
prefix := r.Path
if prefix != "/" && !strings.HasSuffix(prefix, "/") {
prefix += "/"
}
return strings.HasPrefix(path, prefix)
}
// Regexp is a wrapper to the native regexp type where we
+31
View File
@@ -2,6 +2,37 @@ package rules
import "testing"
func TestRuleMatches(t *testing.T) {
t.Parallel()
cases := []struct {
name string
rulePath string
testPath string
want bool
}{
{"exact match", "/uploads", "/uploads", true},
{"child path", "/uploads", "/uploads/file.txt", true},
{"sibling prefix", "/uploads", "/uploads_backup/secret.txt", false},
{"root rule", "/", "/anything", true},
{"trailing slash rule", "/uploads/", "/uploads/file.txt", true},
{"trailing slash no sibling", "/uploads/", "/uploads_backup/file.txt", false},
{"nested child", "/data/shared", "/data/shared/docs/file.txt", true},
{"nested sibling", "/data/shared", "/data/shared_private/file.txt", false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
r := &Rule{Path: tc.rulePath}
got := r.Matches(tc.testPath)
if got != tc.want {
t.Errorf("Rule{Path: %q}.Matches(%q) = %v; want %v", tc.rulePath, tc.testPath, got, tc.want)
}
})
}
}
func TestMatchHidden(t *testing.T) {
cases := map[string]bool{
"/": false,
+17 -7
View File
@@ -27,7 +27,7 @@ type Trojan struct {
hexPassword [trojan.KeyLength]byte
// for gun mux
gunTransport *gun.Transport
gunClient *gun.Client
realityConfig *tlsC.RealityConfig
echConfig *ech.Config
@@ -115,7 +115,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
case "grpc":
break // already handle in gun transport
break // already handle in dialContext
default:
// default tcp network
// handle TLS
@@ -175,7 +175,7 @@ func (t *Trojan) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C
func (t *Trojan) dialContext(ctx context.Context) (c net.Conn, err error) {
switch t.option.Network {
case "grpc": // gun transport
return t.gunTransport.Dial()
return t.gunClient.Dial()
default:
}
return t.dialer.DialContext(ctx, "tcp", t.addr)
@@ -236,10 +236,13 @@ func (t *Trojan) ProxyInfo() C.ProxyInfo {
// Close implements C.ProxyAdapter
func (t *Trojan) Close() error {
if t.gunTransport != nil {
return t.gunTransport.Close()
var errs []error
if t.gunClient != nil {
if err := t.gunClient.Close(); err != nil {
errs = append(errs, err)
}
}
return nil
return errors.Join(errs...)
}
func NewTrojan(option TrojanOption) (*Trojan, error) {
@@ -320,7 +323,14 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
PingInterval: option.GrpcOpts.PingInterval,
}
t.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
t.gunClient = gun.NewClient(
func() *gun.Transport {
return gun.NewTransport(dialFn, tlsConfig, gunConfig)
},
option.GrpcOpts.MaxConnections,
option.GrpcOpts.MinStreams,
option.GrpcOpts.MaxStreams,
)
}
return t, nil
+14 -7
View File
@@ -36,7 +36,7 @@ type Vless struct {
encryption *encryption.ClientInstance
// for gun mux
gunTransport *gun.Transport
gunClient *gun.Client
// for xhttp
xhttpClient *xhttp.Client
@@ -196,9 +196,9 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
case "grpc":
break // already handle in gun transport
break // already handle in dialContext
case "xhttp":
break // already handle in xhttp client
break // already handle in dialContext
default:
// default tcp network
// handle TLS
@@ -280,7 +280,7 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
func (v *Vless) dialContext(ctx context.Context) (c net.Conn, err error) {
switch v.option.Network {
case "grpc": // gun transport
return v.gunTransport.Dial()
return v.gunClient.Dial()
case "xhttp":
return v.xhttpClient.Dial()
default:
@@ -358,8 +358,8 @@ func (v *Vless) ProxyInfo() C.ProxyInfo {
// Close implements C.ProxyAdapter
func (v *Vless) Close() error {
var errs []error
if v.gunTransport != nil {
if err := v.gunTransport.Close(); err != nil {
if v.gunClient != nil {
if err := v.gunClient.Close(); err != nil {
errs = append(errs, err)
}
}
@@ -505,7 +505,14 @@ func NewVless(option VlessOption) (*Vless, error) {
}
}
v.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
v.gunClient = gun.NewClient(
func() *gun.Transport {
return gun.NewTransport(dialFn, tlsConfig, gunConfig)
},
option.GrpcOpts.MaxConnections,
option.GrpcOpts.MinStreams,
option.GrpcOpts.MaxStreams,
)
case "xhttp":
requestHost := v.option.XHTTPOpts.Host
if requestHost == "" {
+20 -7
View File
@@ -34,7 +34,7 @@ type Vmess struct {
option *VmessOption
// for gun mux
gunTransport *gun.Transport
gunClient *gun.Client
realityConfig *tlsC.RealityConfig
echConfig *ech.Config
@@ -86,6 +86,9 @@ type GrpcOptions struct {
GrpcServiceName string `proxy:"grpc-service-name,omitempty"`
GrpcUserAgent string `proxy:"grpc-user-agent,omitempty"`
PingInterval int `proxy:"ping-interval,omitempty"`
MaxConnections int `proxy:"max-connections,omitempty"`
MinStreams int `proxy:"min-streams,omitempty"`
MaxStreams int `proxy:"max-streams,omitempty"`
}
type WSOptions struct {
@@ -172,7 +175,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts)
case "grpc":
break // already handle in gun transport
break // already handle in dialContext
default:
// default tcp network
// handle TLS
@@ -274,7 +277,7 @@ func (v *Vmess) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
func (v *Vmess) dialContext(ctx context.Context) (c net.Conn, err error) {
switch v.option.Network {
case "grpc": // gun transport
return v.gunTransport.Dial()
return v.gunClient.Dial()
default:
}
return v.dialer.DialContext(ctx, "tcp", v.addr)
@@ -331,10 +334,13 @@ func (v *Vmess) ProxyInfo() C.ProxyInfo {
// Close implements C.ProxyAdapter
func (v *Vmess) Close() error {
if v.gunTransport != nil {
return v.gunTransport.Close()
var errs []error
if v.gunClient != nil {
if err := v.gunClient.Close(); err != nil {
errs = append(errs, err)
}
}
return nil
return errors.Join(errs...)
}
// SupportUOT implements C.ProxyAdapter
@@ -438,7 +444,14 @@ func NewVmess(option VmessOption) (*Vmess, error) {
}
}
v.gunTransport = gun.NewTransport(dialFn, tlsConfig, gunConfig)
v.gunClient = gun.NewClient(
func() *gun.Transport {
return gun.NewTransport(dialFn, tlsConfig, gunConfig)
},
option.GrpcOpts.MaxConnections,
option.GrpcOpts.MinStreams,
option.GrpcOpts.MaxStreams,
)
}
return v, nil
+39
View File
@@ -167,6 +167,29 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
// parseXHTTPExtra maps xray-core extra JSON fields to mihomo xhttp-opts fields.
func parseXHTTPExtra(extra map[string]any, opts map[string]any) {
// xmuxToReuse converts an xmux map to mihomo reuse-settings.
xmuxToReuse := func(xmux map[string]any) map[string]any {
reuse := make(map[string]any)
set := func(src, dst string) {
if v, ok := xmux[src]; ok {
switch val := v.(type) {
case string:
if val != "" {
reuse[dst] = val
}
case float64:
reuse[dst] = strconv.FormatInt(int64(val), 10)
}
}
}
set("maxConnections", "max-connections")
set("maxConcurrency", "max-concurrency")
set("cMaxReuseTimes", "c-max-reuse-times")
set("hMaxRequestTimes", "h-max-request-times")
set("hMaxReusableSecs", "h-max-reusable-secs")
return reuse
}
if v, ok := extra["noGRPCHeader"].(bool); ok && v {
opts["no-grpc-header"] = true
}
@@ -175,6 +198,13 @@ func parseXHTTPExtra(extra map[string]any, opts map[string]any) {
opts["x-padding-bytes"] = v
}
// xmux in root extra → reuse-settings
if xmuxAny, ok := extra["xmux"].(map[string]any); ok && len(xmuxAny) > 0 {
if reuse := xmuxToReuse(xmuxAny); len(reuse) > 0 {
opts["reuse-settings"] = reuse
}
}
if dsAny, ok := extra["downloadSettings"].(map[string]any); ok {
ds := make(map[string]any)
@@ -223,6 +253,15 @@ func parseXHTTPExtra(extra map[string]any, opts map[string]any) {
if v, ok := xhttpAny["xPaddingBytes"].(string); ok && v != "" {
ds["x-padding-bytes"] = v
}
// xmux inside downloadSettings.xhttpSettings.extra → download-settings.reuse-settings
if dsExtraAny, ok := xhttpAny["extra"].(map[string]any); ok {
if xmuxAny, ok := dsExtraAny["xmux"].(map[string]any); ok && len(xmuxAny) > 0 {
if reuse := xmuxToReuse(xmuxAny); len(reuse) > 0 {
ds["reuse-settings"] = reuse
}
}
}
}
if len(ds) > 0 {
+9
View File
@@ -670,6 +670,9 @@ proxies: # socks5
grpc-service-name: "example"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
# max-connections: 1 # Maximum connections. Conflict with max-streams.
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
# ip-version: ipv4
# vless
@@ -761,6 +764,9 @@ proxies: # socks5
grpc-service-name: "grpc"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
# max-connections: 1 # Maximum connections. Conflict with max-streams.
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
reality-opts:
public-key: CrrQSjAG_YkHLwvM2M-7XkKJilgL5upBKCp0od0tLhE
@@ -896,6 +902,9 @@ proxies: # socks5
grpc-service-name: "example"
# grpc-user-agent: "grpc-go/1.36.0"
# ping-interval: 0 # 默认关闭,单位为秒
# max-connections: 1 # Maximum connections. Conflict with max-streams.
# min-streams: 0 # Minimum multiplexed streams in a connection before opening a new connection. Conflict with max-streams.
# max-streams: 0 # Maximum multiplexed streams in a connection before opening a new connection. Conflict with max-connections and min-streams.
- name: trojan-ws
server: server
+82
View File
@@ -13,6 +13,7 @@ import (
"net/url"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/metacubex/mihomo/common/buf"
@@ -51,6 +52,7 @@ type Conn struct {
closeMutex sync.Mutex
closed bool
onClose func()
// deadlines
deadline *time.Timer
@@ -209,6 +211,10 @@ func (g *Conn) Close() error {
}
}
if g.onClose != nil {
g.onClose()
}
return errors.Join(errorArr...)
}
@@ -240,6 +246,7 @@ type Transport struct {
ctx context.Context
cancel context.CancelFunc
closeOnce sync.Once
count atomic.Int64
}
func (t *Transport) Close() error {
@@ -349,6 +356,9 @@ func (t *Transport) Dial() (net.Conn, error) {
writer: writer,
}
t.count.Add(1)
conn.onClose = func() { t.count.Add(-1) }
go conn.Init()
// ensure conn.initOnce.Do has been called before return
@@ -358,6 +368,78 @@ func (t *Transport) Dial() (net.Conn, error) {
return conn, nil
}
type Client struct {
mutex sync.Mutex
maxConnections int
minStreams int
maxStreams int
transports []*Transport
maker func() *Transport
}
func NewClient(maker func() *Transport, maxConnections, minStreams, maxStreams int) *Client {
if maxConnections == 0 && minStreams == 0 && maxStreams == 0 {
maxConnections = 1
}
return &Client{
maxConnections: maxConnections,
minStreams: minStreams,
maxStreams: maxStreams,
maker: maker,
}
}
func (c *Client) Dial() (net.Conn, error) {
return c.getTransport().Dial()
}
func (c *Client) Close() error {
c.mutex.Lock()
defer c.mutex.Unlock()
var errs []error
for _, t := range c.transports {
if err := t.Close(); err != nil {
errs = append(errs, err)
}
}
c.transports = nil
return errors.Join(errs...)
}
func (c *Client) getTransport() *Transport {
c.mutex.Lock()
defer c.mutex.Unlock()
var transport *Transport
for _, t := range c.transports {
if transport == nil || t.count.Load() < transport.count.Load() {
transport = t
}
}
if transport == nil {
return c.newTransportLocked()
}
numStreams := int(transport.count.Load())
if numStreams == 0 {
return transport
}
if c.maxConnections > 0 {
if len(c.transports) >= c.maxConnections || numStreams < c.minStreams {
return transport
}
} else {
if c.maxStreams > 0 && numStreams < c.maxStreams {
return transport
}
}
return c.newTransportLocked()
}
func (c *Client) newTransportLocked() *Transport {
transport := c.maker()
c.transports = append(c.transports, transport)
return transport
}
func StreamGunWithConn(conn net.Conn, tlsConfig *vmess.TLSConfig, gunCfg *Config) (net.Conn, error) {
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
return conn, nil
-5
View File
@@ -112,11 +112,6 @@ func (w *h2ConnWrapper) CloseWrapper() {
w.closed = true
}
func (w *h2ConnWrapper) Close() error {
w.CloseWrapper()
return w.ExtendedConn.Close()
}
func (w *h2ConnWrapper) Upstream() any {
return w.ExtendedConn
}
+1 -7
View File
@@ -66,13 +66,7 @@ func WriteKIPMessage(w io.Writer, typ byte, payload []byte) error {
hdr[3] = typ
binary.BigEndian.PutUint16(hdr[4:], uint16(len(payload)))
if err := writeFull(w, hdr[:]); err != nil {
return err
}
if len(payload) == 0 {
return nil
}
return writeFull(w, payload)
return writeAllChunks(w, hdr[:], payload)
}
func ReadKIPMessage(r io.Reader) (*KIPMessage, error) {
+1 -18
View File
@@ -173,16 +173,10 @@ func (s *Session) sendFrame(frameType byte, streamID uint32, payload []byte) err
s.writeMu.Lock()
defer s.writeMu.Unlock()
if err := writeFull(s.conn, header[:]); err != nil {
if err := writeAllChunks(s.conn, header[:], payload); err != nil {
s.closeWithError(err)
return err
}
if len(payload) > 0 {
if err := writeFull(s.conn, payload); err != nil {
s.closeWithError(err)
return err
}
}
return nil
}
@@ -315,17 +309,6 @@ func (s *Session) readLoop() {
}
}
func writeFull(w io.Writer, b []byte) error {
for len(b) > 0 {
n, err := w.Write(b)
if err != nil {
return err
}
b = b[n:]
}
return nil
}
func trimASCII(b []byte) string {
i := 0
j := len(b)
@@ -0,0 +1,19 @@
package multiplex
import "io"
func writeAllChunks(w io.Writer, chunks ...[]byte) error {
for _, chunk := range chunks {
for len(chunk) > 0 {
n, err := w.Write(chunk)
if err != nil {
return err
}
if n == 0 {
return io.ErrShortWrite
}
chunk = chunk[n:]
}
}
return nil
}
+36 -70
View File
@@ -3,12 +3,9 @@ package sudoku
import (
"bufio"
"bytes"
crypto_rand "crypto/rand"
"encoding/binary"
"errors"
"math/rand"
"net"
"sync"
"sync/atomic"
)
const IOBufferSize = 32 * 1024
@@ -45,14 +42,17 @@ type Conn struct {
table *Table
reader *bufio.Reader
recorder *bytes.Buffer
recording bool
recording atomic.Bool
recordLock sync.Mutex
rawBuf []byte
pendingData []byte
hintBuf []byte
pendingData pendingBuffer
hintBuf [4]byte
hintCount int
writeMu sync.Mutex
writeBuf []byte
rng *rand.Rand
rng randomSource
paddingThreshold uint64
}
@@ -77,33 +77,28 @@ func (sc *Conn) CloseRead() error {
}
func NewConn(c net.Conn, table *Table, pMin, pMax int, record bool) *Conn {
var seedBytes [8]byte
if _, err := crypto_rand.Read(seedBytes[:]); err != nil {
binary.BigEndian.PutUint64(seedBytes[:], uint64(rand.Int63()))
}
seed := int64(binary.BigEndian.Uint64(seedBytes[:]))
localRng := rand.New(rand.NewSource(seed))
localRng := newSeededRand()
sc := &Conn{
Conn: c,
table: table,
reader: bufio.NewReaderSize(c, IOBufferSize),
rawBuf: make([]byte, IOBufferSize),
pendingData: make([]byte, 0, 4096),
hintBuf: make([]byte, 0, 4),
pendingData: newPendingBuffer(4096),
writeBuf: make([]byte, 0, 4096),
rng: localRng,
paddingThreshold: pickPaddingThreshold(localRng, pMin, pMax),
}
if record {
sc.recorder = new(bytes.Buffer)
sc.recording = true
sc.recording.Store(true)
}
return sc
}
func (sc *Conn) StopRecording() {
sc.recordLock.Lock()
sc.recording = false
sc.recording.Store(false)
sc.recorder = nil
sc.recordLock.Unlock()
}
@@ -137,74 +132,50 @@ func (sc *Conn) Write(p []byte) (n int, err error) {
return 0, nil
}
outCapacity := len(p) * 6
out := make([]byte, 0, outCapacity)
pads := sc.table.PaddingPool
padLen := len(pads)
sc.writeMu.Lock()
defer sc.writeMu.Unlock()
for _, b := range p {
if shouldPad(sc.rng, sc.paddingThreshold) {
out = append(out, pads[sc.rng.Intn(padLen)])
}
puzzles := sc.table.EncodeTable[b]
puzzle := puzzles[sc.rng.Intn(len(puzzles))]
perm := perm4[sc.rng.Intn(len(perm4))]
for _, idx := range perm {
if shouldPad(sc.rng, sc.paddingThreshold) {
out = append(out, pads[sc.rng.Intn(padLen)])
}
out = append(out, puzzle[idx])
}
}
if shouldPad(sc.rng, sc.paddingThreshold) {
out = append(out, pads[sc.rng.Intn(padLen)])
}
return len(p), writeFull(sc.Conn, out)
sc.writeBuf = encodeSudokuPayload(sc.writeBuf[:0], sc.table, sc.rng, sc.paddingThreshold, p)
return len(p), writeFull(sc.Conn, sc.writeBuf)
}
func (sc *Conn) Read(p []byte) (n int, err error) {
if len(sc.pendingData) > 0 {
n = copy(p, sc.pendingData)
if n == len(sc.pendingData) {
sc.pendingData = sc.pendingData[:0]
} else {
sc.pendingData = sc.pendingData[n:]
}
if n, ok := drainPending(p, &sc.pendingData); ok {
return n, nil
}
for {
if len(sc.pendingData) > 0 {
if sc.pendingData.available() > 0 {
break
}
nr, rErr := sc.reader.Read(sc.rawBuf)
if nr > 0 {
chunk := sc.rawBuf[:nr]
sc.recordLock.Lock()
if sc.recording {
sc.recorder.Write(chunk)
if sc.recording.Load() {
sc.recordLock.Lock()
if sc.recording.Load() && sc.recorder != nil {
sc.recorder.Write(chunk)
}
sc.recordLock.Unlock()
}
sc.recordLock.Unlock()
layout := sc.table.layout
for _, b := range chunk {
if !sc.table.layout.isHint(b) {
if !layout.hintTable[b] {
continue
}
sc.hintBuf = append(sc.hintBuf, b)
if len(sc.hintBuf) == 4 {
key := packHintsToKey([4]byte{sc.hintBuf[0], sc.hintBuf[1], sc.hintBuf[2], sc.hintBuf[3]})
sc.hintBuf[sc.hintCount] = b
sc.hintCount++
if sc.hintCount == len(sc.hintBuf) {
key := packHintsToKey(sc.hintBuf)
val, ok := sc.table.DecodeMap[key]
if !ok {
return 0, errors.New("INVALID_SUDOKU_MAP_MISS")
return 0, ErrInvalidSudokuMapMiss
}
sc.pendingData = append(sc.pendingData, val)
sc.hintBuf = sc.hintBuf[:0]
sc.pendingData.appendByte(val)
sc.hintCount = 0
}
}
}
@@ -212,16 +183,11 @@ func (sc *Conn) Read(p []byte) (n int, err error) {
if rErr != nil {
return 0, rErr
}
if len(sc.pendingData) > 0 {
if sc.pendingData.available() > 0 {
break
}
}
n = copy(p, sc.pendingData)
if n == len(sc.pendingData) {
sc.pendingData = sc.pendingData[:0]
} else {
sc.pendingData = sc.pendingData[n:]
}
n, _ = drainPending(p, &sc.pendingData)
return n, nil
}
@@ -0,0 +1,36 @@
package sudoku
func encodeSudokuPayload(dst []byte, table *Table, rng randomSource, paddingThreshold uint64, p []byte) []byte {
if len(p) == 0 {
return dst[:0]
}
outCapacity := len(p)*6 + 1
if cap(dst) < outCapacity {
dst = make([]byte, 0, outCapacity)
}
out := dst[:0]
pads := table.PaddingPool
padLen := len(pads)
for _, b := range p {
if shouldPad(rng, paddingThreshold) {
out = append(out, pads[rng.Intn(padLen)])
}
puzzles := table.EncodeTable[b]
puzzle := puzzles[rng.Intn(len(puzzles))]
perm := perm4[rng.Intn(len(perm4))]
for _, idx := range perm {
if shouldPad(rng, paddingThreshold) {
out = append(out, pads[rng.Intn(padLen)])
}
out = append(out, puzzle[idx])
}
}
if shouldPad(rng, paddingThreshold) {
out = append(out, pads[rng.Intn(padLen)])
}
return out
}
+112 -78
View File
@@ -14,17 +14,30 @@ type byteLayout struct {
padMarker byte
paddingPool []byte
encodeHint func(val, pos byte) byte
encodeGroup func(group byte) byte
decodeGroup func(b byte) (byte, bool)
hintTable [256]bool
encodeHint [4][16]byte
encodeGroup [64]byte
decodeGroup [256]byte
groupValid [256]bool
}
func (l *byteLayout) isHint(b byte) bool {
if (b & l.hintMask) == l.hintValue {
return true
return l != nil && l.hintTable[b]
}
func (l *byteLayout) hintByte(val, pos byte) byte {
return l.encodeHint[val&0x03][pos&0x0F]
}
func (l *byteLayout) groupByte(group byte) byte {
return l.encodeGroup[group&0x3F]
}
func (l *byteLayout) decodePackedGroup(b byte) (byte, bool) {
if l == nil {
return 0, false
}
// ASCII layout maps the single non-printable marker (0x7F) to '\n' on the wire.
return l.name == "ascii" && b == '\n'
return l.decodeGroup[b], l.groupValid[b]
}
// resolveLayout picks the byte layout for a single traffic direction.
@@ -50,38 +63,44 @@ func newASCIILayout() *byteLayout {
for i := 0; i < 32; i++ {
padding = append(padding, byte(0x20+i))
}
return &byteLayout{
layout := &byteLayout{
name: "ascii",
hintMask: 0x40,
hintValue: 0x40,
padMarker: 0x3F,
paddingPool: padding,
encodeHint: func(val, pos byte) byte {
b := 0x40 | ((val & 0x03) << 4) | (pos & 0x0F)
// Avoid DEL (0x7F) in prefer_ascii mode; map it to '\n' to reduce fingerprint.
if b == 0x7F {
return '\n'
}
return b
},
encodeGroup: func(group byte) byte {
b := 0x40 | (group & 0x3F)
// Avoid DEL (0x7F) in prefer_ascii mode; map it to '\n' to reduce fingerprint.
if b == 0x7F {
return '\n'
}
return b
},
decodeGroup: func(b byte) (byte, bool) {
if b == '\n' {
return 0x3F, true
}
if (b & 0x40) == 0 {
return 0, false
}
return b & 0x3F, true
},
}
for val := 0; val < 4; val++ {
for pos := 0; pos < 16; pos++ {
b := byte(0x40 | (byte(val) << 4) | byte(pos))
if b == 0x7F {
b = '\n'
}
layout.encodeHint[val][pos] = b
}
}
for group := 0; group < 64; group++ {
b := byte(0x40 | byte(group))
if b == 0x7F {
b = '\n'
}
layout.encodeGroup[group] = b
}
for b := 0; b < 256; b++ {
wire := byte(b)
if (wire & 0x40) == 0x40 {
layout.hintTable[wire] = true
layout.decodeGroup[wire] = wire & 0x3F
layout.groupValid[wire] = true
}
}
layout.hintTable['\n'] = true
layout.decodeGroup['\n'] = 0x3F
layout.groupValid['\n'] = true
return layout
}
func newEntropyLayout() *byteLayout {
@@ -90,26 +109,35 @@ func newEntropyLayout() *byteLayout {
padding = append(padding, byte(0x80+i))
padding = append(padding, byte(0x10+i))
}
return &byteLayout{
layout := &byteLayout{
name: "entropy",
hintMask: 0x90,
hintValue: 0x00,
padMarker: 0x80,
paddingPool: padding,
encodeHint: func(val, pos byte) byte {
return ((val & 0x03) << 5) | (pos & 0x0F)
},
encodeGroup: func(group byte) byte {
v := group & 0x3F
return ((v & 0x30) << 1) | (v & 0x0F)
},
decodeGroup: func(b byte) (byte, bool) {
if (b & 0x90) != 0 {
return 0, false
}
return ((b >> 1) & 0x30) | (b & 0x0F), true
},
}
for val := 0; val < 4; val++ {
for pos := 0; pos < 16; pos++ {
layout.encodeHint[val][pos] = (byte(val) << 5) | byte(pos)
}
}
for group := 0; group < 64; group++ {
v := byte(group)
layout.encodeGroup[group] = ((v & 0x30) << 1) | (v & 0x0F)
}
for b := 0; b < 256; b++ {
wire := byte(b)
if (wire & 0x90) != 0 {
continue
}
layout.hintTable[wire] = true
layout.decodeGroup[wire] = ((wire >> 1) & 0x30) | (wire & 0x0F)
layout.groupValid[wire] = true
}
return layout
}
func newCustomLayout(pattern string) (*byteLayout, error) {
@@ -162,26 +190,6 @@ func newCustomLayout(pattern string) (*byteLayout, error) {
return out
}
decodeGroup := func(b byte) (byte, bool) {
if (b & xMask) != xMask {
return 0, false
}
var val, pos byte
if b&(1<<pBits[0]) != 0 {
val |= 0x02
}
if b&(1<<pBits[1]) != 0 {
val |= 0x01
}
for i, bit := range vBits {
if b&(1<<bit) != 0 {
pos |= 1 << (3 - uint8(i))
}
}
group := (val << 4) | (pos & 0x0F)
return group, true
}
paddingSet := make(map[byte]struct{})
var padding []byte
for drop := range xBits {
@@ -202,20 +210,46 @@ func newCustomLayout(pattern string) (*byteLayout, error) {
return nil, fmt.Errorf("custom table produced empty padding pool")
}
return &byteLayout{
layout := &byteLayout{
name: fmt.Sprintf("custom(%s)", cleaned),
hintMask: xMask,
hintValue: xMask,
padMarker: padding[0],
paddingPool: padding,
encodeHint: func(val, pos byte) byte {
return encodeBits(val, pos, -1)
},
encodeGroup: func(group byte) byte {
val := (group >> 4) & 0x03
pos := group & 0x0F
return encodeBits(val, pos, -1)
},
decodeGroup: decodeGroup,
}, nil
}
for val := 0; val < 4; val++ {
for pos := 0; pos < 16; pos++ {
layout.encodeHint[val][pos] = encodeBits(byte(val), byte(pos), -1)
}
}
for group := 0; group < 64; group++ {
val := byte(group>>4) & 0x03
pos := byte(group) & 0x0F
layout.encodeGroup[group] = encodeBits(val, pos, -1)
}
for b := 0; b < 256; b++ {
wire := byte(b)
if (wire & xMask) != xMask {
continue
}
layout.hintTable[wire] = true
var val, pos byte
if wire&(1<<pBits[0]) != 0 {
val |= 0x02
}
if wire&(1<<pBits[1]) != 0 {
val |= 0x01
}
for i, bit := range vBits {
if wire&(1<<bit) != 0 {
pos |= 1 << (3 - uint8(i))
}
}
layout.decodeGroup[wire] = (val << 4) | pos
layout.groupValid[wire] = true
}
return layout, nil
}
+23 -39
View File
@@ -2,10 +2,7 @@ package sudoku
import (
"bufio"
crypto_rand "crypto/rand"
"encoding/binary"
"io"
"math/rand"
"net"
"sync"
)
@@ -25,7 +22,7 @@ type PackedConn struct {
// Read-side buffers.
rawBuf []byte
pendingData []byte
pendingData pendingBuffer
// Write-side state.
writeMu sync.Mutex
@@ -38,7 +35,7 @@ type PackedConn struct {
readBits int
// Padding selection matches Conn's threshold-based model.
rng *rand.Rand
rng randomSource
paddingThreshold uint64
padMarker byte
padPool []byte
@@ -65,19 +62,14 @@ func (pc *PackedConn) CloseRead() error {
}
func NewPackedConn(c net.Conn, table *Table, pMin, pMax int) *PackedConn {
var seedBytes [8]byte
if _, err := crypto_rand.Read(seedBytes[:]); err != nil {
binary.BigEndian.PutUint64(seedBytes[:], uint64(rand.Int63()))
}
seed := int64(binary.BigEndian.Uint64(seedBytes[:]))
localRng := rand.New(rand.NewSource(seed))
localRng := newSeededRand()
pc := &PackedConn{
Conn: c,
table: table,
reader: bufio.NewReaderSize(c, IOBufferSize),
rawBuf: make([]byte, IOBufferSize),
pendingData: make([]byte, 0, 4096),
pendingData: newPendingBuffer(4096),
writeBuf: make([]byte, 0, 4096),
rng: localRng,
paddingThreshold: pickPaddingThreshold(localRng, pMin, pMax),
@@ -104,7 +96,7 @@ func (pc *PackedConn) maybeAddPadding(out []byte) []byte {
func (pc *PackedConn) appendGroup(out []byte, group byte) []byte {
out = pc.maybeAddPadding(out)
return append(out, pc.encodeGroup(group))
return append(out, pc.table.layout.groupByte(group))
}
func (pc *PackedConn) appendForcedPadding(out []byte) []byte {
@@ -156,19 +148,6 @@ func (pc *PackedConn) writeProtectedPrefix(out []byte, p []byte) ([]byte, int) {
return out, limit
}
func (pc *PackedConn) drainPendingData(dst []byte) int {
n := copy(dst, pc.pendingData)
if n == len(pc.pendingData) {
pc.pendingData = pc.pendingData[:0]
return n
}
remaining := len(pc.pendingData) - n
copy(pc.pendingData, pc.pendingData[n:])
pc.pendingData = pc.pendingData[:remaining]
return n
}
func (pc *PackedConn) Write(p []byte) (int, error) {
if len(p) == 0 {
return 0, nil
@@ -282,7 +261,7 @@ func (pc *PackedConn) Flush() error {
pc.bitBuf = 0
pc.bitCount = 0
out = append(out, pc.encodeGroup(group&0x3F))
out = append(out, pc.table.layout.groupByte(group&0x3F))
out = append(out, pc.padMarker)
}
@@ -301,14 +280,17 @@ func writeFull(w io.Writer, b []byte) error {
if err != nil {
return err
}
if n == 0 {
return io.ErrShortWrite
}
b = b[n:]
}
return nil
}
func (pc *PackedConn) Read(p []byte) (int, error) {
if len(pc.pendingData) > 0 {
return pc.drainPendingData(p), nil
if n, ok := drainPending(p, &pc.pendingData); ok {
return n, nil
}
for {
@@ -320,7 +302,7 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
layout := pc.table.layout
for _, b := range pc.rawBuf[:nr] {
if !layout.isHint(b) {
if !layout.hintTable[b] {
if b == padMarker {
rBuf = 0
rBits = 0
@@ -328,7 +310,7 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
continue
}
group, ok := layout.decodeGroup(b)
group, ok := layout.decodePackedGroup(b)
if !ok {
return 0, ErrInvalidSudokuMapMiss
}
@@ -339,7 +321,12 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
if rBits >= 8 {
rBits -= 8
val := byte(rBuf >> rBits)
pc.pendingData = append(pc.pendingData, val)
pc.pendingData.appendByte(val)
if rBits == 0 {
rBuf = 0
} else {
rBuf &= (uint64(1) << rBits) - 1
}
}
}
@@ -352,24 +339,21 @@ func (pc *PackedConn) Read(p []byte) (int, error) {
pc.readBitBuf = 0
pc.readBits = 0
}
if len(pc.pendingData) > 0 {
if pc.pendingData.available() > 0 {
break
}
return 0, rErr
}
if len(pc.pendingData) > 0 {
if pc.pendingData.available() > 0 {
break
}
}
return pc.drainPendingData(p), nil
n, _ := drainPending(p, &pc.pendingData)
return n, nil
}
func (pc *PackedConn) getPaddingByte() byte {
return pc.padPool[pc.rng.Intn(len(pc.padPool))]
}
func (pc *PackedConn) encodeGroup(group byte) byte {
return pc.table.layout.encodeGroup(group)
}
@@ -1,10 +1,8 @@
package sudoku
import "math/rand"
const probOne = uint64(1) << 32
func pickPaddingThreshold(r *rand.Rand, pMin, pMax int) uint64 {
func pickPaddingThreshold(r randomSource, pMin, pMax int) uint64 {
if r == nil {
return 0
}
@@ -30,7 +28,7 @@ func pickPaddingThreshold(r *rand.Rand, pMin, pMax int) uint64 {
return min + (u * (max - min) >> 32)
}
func shouldPad(r *rand.Rand, threshold uint64) bool {
func shouldPad(r randomSource, threshold uint64) bool {
if threshold == 0 {
return false
}
@@ -0,0 +1,57 @@
package sudoku
type pendingBuffer struct {
data []byte
off int
}
func newPendingBuffer(capacity int) pendingBuffer {
return pendingBuffer{data: make([]byte, 0, capacity)}
}
func (p *pendingBuffer) available() int {
if p == nil {
return 0
}
return len(p.data) - p.off
}
func (p *pendingBuffer) reset() {
if p == nil {
return
}
p.data = p.data[:0]
p.off = 0
}
func (p *pendingBuffer) ensureAppendCapacity(extra int) {
if p == nil || extra <= 0 || p.off == 0 {
return
}
if cap(p.data)-len(p.data) >= extra {
return
}
unread := len(p.data) - p.off
copy(p.data[:unread], p.data[p.off:])
p.data = p.data[:unread]
p.off = 0
}
func (p *pendingBuffer) appendByte(b byte) {
p.ensureAppendCapacity(1)
p.data = append(p.data, b)
}
func drainPending(dst []byte, pending *pendingBuffer) (int, bool) {
if pending == nil || pending.available() == 0 {
return 0, false
}
n := copy(dst, pending.data[pending.off:])
pending.off += n
if pending.off == len(pending.data) {
pending.reset()
}
return n, true
}
@@ -0,0 +1,56 @@
package sudoku
import (
crypto_rand "crypto/rand"
"encoding/binary"
"time"
)
type randomSource interface {
Uint32() uint32
Uint64() uint64
Intn(n int) int
}
type sudokuRand struct {
state uint64
}
func newSeededRand() *sudokuRand {
seed := time.Now().UnixNano()
var seedBytes [8]byte
if _, err := crypto_rand.Read(seedBytes[:]); err == nil {
seed = int64(binary.BigEndian.Uint64(seedBytes[:]))
}
return newSudokuRand(seed)
}
func newSudokuRand(seed int64) *sudokuRand {
state := uint64(seed)
if state == 0 {
state = 0x9e3779b97f4a7c15
}
return &sudokuRand{state: state}
}
func (r *sudokuRand) Uint64() uint64 {
if r == nil {
return 0
}
r.state += 0x9e3779b97f4a7c15
z := r.state
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9
z = (z ^ (z >> 27)) * 0x94d049bb133111eb
return z ^ (z >> 31)
}
func (r *sudokuRand) Uint32() uint32 {
return uint32(r.Uint64() >> 32)
}
func (r *sudokuRand) Intn(n int) int {
if n <= 1 {
return 0
}
return int((uint64(r.Uint32()) * uint64(n)) >> 32)
}
+1 -1
View File
@@ -146,7 +146,7 @@ func newSingleDirectionTable(key string, mode string, customPattern string) (*Ta
if matchCount == 1 {
// 唯一确定,生成最终编码字节
for i, p := range rawParts {
currentHints[i] = t.layout.encodeHint(p.val-1, p.pos)
currentHints[i] = t.layout.hintByte(p.val-1, p.pos)
}
t.EncodeTable[byteVal] = append(t.EncodeTable[byteVal], currentHints)
+60 -42
View File
@@ -8,7 +8,6 @@ import (
"io"
"net"
"net/netip"
"strconv"
"sync"
"time"
@@ -37,42 +36,15 @@ func WriteDatagram(w io.Writer, addr string, payload []byte) error {
binary.BigEndian.PutUint16(header[:2], uint16(len(addrBuf)))
binary.BigEndian.PutUint16(header[2:], uint16(len(payload)))
if err := writeFull(w, header[:]); err != nil {
return err
}
if err := writeFull(w, addrBuf); err != nil {
return err
}
return writeFull(w, payload)
return writeAllChunks(w, header[:], addrBuf, payload)
}
// ReadDatagram parses a single UDP datagram frame from the reliable stream.
func ReadDatagram(r io.Reader) (string, []byte, error) {
var header [4]byte
if _, err := io.ReadFull(r, header[:]); err != nil {
return "", nil, err
}
addrLen := int(binary.BigEndian.Uint16(header[:2]))
payloadLen := int(binary.BigEndian.Uint16(header[2:]))
if addrLen <= 0 || addrLen > maxUoTPayload {
return "", nil, fmt.Errorf("invalid address length: %d", addrLen)
}
if payloadLen < 0 || payloadLen > maxUoTPayload {
return "", nil, fmt.Errorf("invalid payload length: %d", payloadLen)
}
addrBuf := make([]byte, addrLen)
if _, err := io.ReadFull(r, addrBuf); err != nil {
return "", nil, err
}
addr, err := DecodeAddress(bytes.NewReader(addrBuf))
addr, payloadLen, err := readDatagramHeaderAndAddress(r)
if err != nil {
return "", nil, fmt.Errorf("decode address: %w", err)
return "", nil, err
}
payload := make([]byte, payloadLen)
if _, err := io.ReadFull(r, payload); err != nil {
return "", nil, err
@@ -93,26 +65,29 @@ func NewUoTPacketConn(conn net.Conn) *UoTPacketConn {
func (c *UoTPacketConn) ReadFrom(p []byte) (int, net.Addr, error) {
for {
addrStr, payload, err := ReadDatagram(c.conn)
addrStr, payloadLen, err := readDatagramHeaderAndAddress(c.conn)
if err != nil {
return 0, nil, err
}
if len(payload) > len(p) {
udpAddr, err := parseDatagramUDPAddr(addrStr)
if payloadLen > len(p) {
if discardErr := discardBytes(c.conn, payloadLen); discardErr != nil {
return 0, nil, discardErr
}
return 0, nil, io.ErrShortBuffer
}
host, port, _ := net.SplitHostPort(addrStr)
portInt, _ := strconv.ParseUint(port, 10, 16)
ip, err := netip.ParseAddr(host)
if err != nil { // disallow domain addr at here, just ignore
if err != nil {
if discardErr := discardBytes(c.conn, payloadLen); discardErr != nil {
return 0, nil, discardErr
}
log.Debugln("[Sudoku][UoT] discard datagram with invalid address %s: %v", addrStr, err)
continue
}
udpAddr := net.UDPAddrFromAddrPort(netip.AddrPortFrom(ip.Unmap(), uint16(portInt)))
copy(p, payload)
return len(payload), udpAddr, nil
if _, err := io.ReadFull(c.conn, p[:payloadLen]); err != nil {
return 0, nil, err
}
return payloadLen, udpAddr, nil
}
}
@@ -147,3 +122,46 @@ func (c *UoTPacketConn) SetReadDeadline(t time.Time) error {
func (c *UoTPacketConn) SetWriteDeadline(t time.Time) error {
return c.conn.SetWriteDeadline(t)
}
func readDatagramHeaderAndAddress(r io.Reader) (string, int, error) {
var header [4]byte
if _, err := io.ReadFull(r, header[:]); err != nil {
return "", 0, err
}
addrLen := int(binary.BigEndian.Uint16(header[:2]))
payloadLen := int(binary.BigEndian.Uint16(header[2:]))
if addrLen <= 0 || addrLen > maxUoTPayload {
return "", 0, fmt.Errorf("invalid address length: %d", addrLen)
}
if payloadLen < 0 || payloadLen > maxUoTPayload {
return "", 0, fmt.Errorf("invalid payload length: %d", payloadLen)
}
addrBuf := make([]byte, addrLen)
if _, err := io.ReadFull(r, addrBuf); err != nil {
return "", 0, err
}
addr, err := DecodeAddress(bytes.NewReader(addrBuf))
if err != nil {
return "", 0, fmt.Errorf("decode address: %w", err)
}
return addr, payloadLen, nil
}
func parseDatagramUDPAddr(addr string) (*net.UDPAddr, error) {
addrPort, err := netip.ParseAddrPort(addr)
if err != nil {
return nil, err
}
return net.UDPAddrFromAddrPort(netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port())), nil
}
func discardBytes(r io.Reader, n int) error {
if n <= 0 {
return nil
}
_, err := io.CopyN(io.Discard, r, int64(n))
return err
}
+19
View File
@@ -0,0 +1,19 @@
package sudoku
import "io"
func writeAllChunks(w io.Writer, chunks ...[]byte) error {
for _, chunk := range chunks {
for len(chunk) > 0 {
n, err := w.Write(chunk)
if err != nil {
return err
}
if n == 0 {
return io.ErrShortWrite
}
chunk = chunk[n:]
}
}
return nil
}
+2 -2
View File
@@ -5,12 +5,12 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=filebrowser
PKG_VERSION:=2.63.0
PKG_VERSION:=2.63.1
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://codeload.github.com/filebrowser/filebrowser/tar.gz/v${PKG_VERSION}?
PKG_HASH:=68ef79b4e48aa80a4921f395a55b6ba9efa58c0fa95a6de0915fd27348af59bf
PKG_HASH:=6f1b63bc8add3807d67c09d03ee60d2282255fe11b6ab6fcfc06266e9fb743fd
PKG_LICENSE:=Apache-2.0
PKG_LICENSE_FILES:=LICENSE
@@ -1955,7 +1955,7 @@ function gen_config(var)
}
})
for index, value in ipairs(config.outbounds) do
if not value["_flag_proxy_tag"] and not value.detour and value["_id"] and value.server and value.server_port and not no_run then
if not value["_flag_proxy_tag"] and not value.detour and value["_id"] and value.server and (value.server_port or value.server_ports) and not no_run then
sys.call(string.format("echo '%s' >> %s", value["_id"], api.TMP_PATH .. "/direct_node_list"))
end
for k, v in pairs(config.outbounds[index]) do
@@ -1064,17 +1064,17 @@ function gen_config(var)
local to_node = get_node_by_id(node.to_node)
if to_node then
-- Landing Node not support use special node.
if to_node.protocol:find("^_") then
if to_node.protocol and to_node.protocol:find("^_") then
to_node = nil
end
end
if to_node then
local to_outbound
if to_node.type ~= "Xray" then
local tag = to_node[".name"]
local in_tag = "inbound_" .. to_node[".name"] .. "_" .. tostring(outbound.tag)
local new_port = api.get_new_port()
table.insert(inbounds, {
tag = tag,
tag = in_tag,
listen = "127.0.0.1",
port = new_port,
protocol = "dokodemo-door",
@@ -1086,11 +1086,11 @@ function gen_config(var)
to_node.address = "127.0.0.1"
to_node.port = new_port
table.insert(rules, 1, {
inboundTag = {tag},
inboundTag = {in_tag},
outboundTag = outbound.tag
})
to_outbound = gen_outbound(node[".name"], to_node, tag, {
tag = tag,
to_outbound = gen_outbound(node[".name"], to_node, to_node[".name"], {
tag = to_node[".name"],
run_socks_instance = not no_run
})
else
@@ -1734,14 +1734,10 @@ function gen_config(var)
else
table.insert(outbounds, blackhole_outbound)
end
for index, value in ipairs(config.outbounds) do
local s = value.settings
if not value["_flag_proxy_tag"] and value["_id"] and s and not no_run and
((s.vnext and s.vnext[1] and s.vnext[1].address and s.vnext[1].port) or
(s.servers and s.servers[1] and s.servers[1].address and s.servers[1].port) or
(s.peers and s.peers[1] and s.peers[1].endpoint) or
(s.address and s.port)) then
local pt = value.protocol
local exclude = { blackhole=1, dns=1, freedom=1, loopback=1 }
if not value["_flag_proxy_tag"] and value["_id"] and pt and not exclude[pt] and not no_run then
sys.call(string.format("echo '%s' >> %s", value["_id"], api.TMP_PATH .. "/direct_node_list"))
end
for k, v in pairs(config.outbounds[index]) do
@@ -1101,6 +1101,9 @@ socks_node_switch() {
LOG_FILE="/dev/null"
run_socks flag=$flag node=$new_node bind=$bind socks_port=$port config_file=$config_file http_port=$http_port http_config_file=$http_config_file log_file=$log_file
set_cache_var "socks_${flag}" "$new_node"
local ENABLED_DEFAULT_ACL=$(get_cache_var "ENABLED_DEFAULT_ACL")
local ENABLED_ACLS=$(get_cache_var "ENABLED_ACLS")
[ "$ENABLED_DEFAULT_ACL" != "1" -a "$ENABLED_ACLS" != "1" ] && return
local USE_TABLES=$(get_cache_var "USE_TABLES")
[ -n "$USE_TABLES" ] && source $APP_PATH/${USE_TABLES}.sh filter_direct_node_list
}
@@ -1979,6 +1982,8 @@ get_config() {
[ "$ENABLED_ACLS" = 1 ] && {
[ "$(uci show ${CONFIG} | grep "@acl_rule" | grep "enabled='1'" | wc -l)" == 0 ] && ENABLED_ACLS=0
}
set_cache_var ENABLED_DEFAULT_ACL $ENABLED_DEFAULT_ACL
set_cache_var ENABLED_ACLS $ENABLED_ACLS
TCP_PROXY_WAY=$(config_t_get global_forwarding tcp_proxy_way redirect)
PROXY_IPV6=$(config_t_get global_forwarding ipv6_tproxy 0)
@@ -742,43 +742,50 @@ filter_vpsip() {
}
filter_server_port() {
local address=${1}
local port=${2}
local stream=${3}
stream=$(echo ${3} | tr 'A-Z' 'a-z')
local _is_tproxy ipt_tmp
ipt_tmp=$ipt_n
_is_tproxy=${is_tproxy}
[ "$stream" == "udp" ] && _is_tproxy="TPROXY"
[ -n "${_is_tproxy}" ] && ipt_tmp=$ipt_m
for _ipt in 4 6; do
[ "$_ipt" == "4" ] && _ipt=$ipt_tmp
[ "$_ipt" == "6" ] && _ipt=$ip6t_m
$_ipt -n -L PSW_OUTPUT | grep -q "${address}:${port}"
if [ $? -ne 0 ]; then
$_ipt -I PSW_OUTPUT $(comment "${address}:${port}") -p $stream -d $address --dport $port -j RETURN 2>/dev/null
local address="$1"
local port=$(echo "$2" | tr '-' ':' | tr -d ' ')
local stream=$(echo "$3" | tr 'A-Z' 'a-z')
local ipt_tmp="$ipt_n" _is_tproxy _ipt_cmd _ver multi_ports p ports
[ "$(config_t_get global_forwarding tcp_proxy_way redirect)" = "tproxy" ] && _is_tproxy="TPROXY"
[ "$stream" = "udp" ] && _is_tproxy="TPROXY"
[ -n "$_is_tproxy" ] && ipt_tmp="$ipt_m"
for _ver in 4 6; do
[ "$_ver" = "4" ] && _ipt_cmd="$ipt_tmp"
[ "$_ver" = "6" ] && _ipt_cmd="$ip6t_m"
multi_ports=""
for p in $(echo "$port" | tr ',' ' '); do
case "$p" in
*:* )
$_ipt_cmd -n -L PSW_OUTPUT 2>/dev/null | grep -q "${address}:${p}:${stream}" || \
$_ipt_cmd -I PSW_OUTPUT $(comment "${address}:${p}:${stream}") -p "$stream" -d "$address" --dport "$p" -j RETURN 2>/dev/null
;;
* )
multi_ports="${multi_ports},$p"
;;
esac
done
if [ -n "$multi_ports" ]; then
ports=$(printf "%s\n" "${multi_ports#,}" | tr ',' '\n' | sort -n | tr '\n' ',' | sed 's/,$//')
$_ipt_cmd -n -L PSW_OUTPUT 2>/dev/null | grep -q "${address}:${ports}:${stream}" || \
$_ipt_cmd -I PSW_OUTPUT $(comment "${address}:${ports}:${stream}") -p "$stream" -d "$address" -m multiport --dports "$ports" -j RETURN 2>/dev/null
fi
done
}
filter_node() {
local node=${1}
local stream=${2}
if [ -n "$node" ]; then
local address=$(config_n_get $node address)
local port=$(config_n_get $node port)
[ -z "$address" ] && [ -z "$port" ] && {
return 1
}
filter_server_port $address $port $stream
filter_server_port $address $port $stream
fi
local node="$1" stream="$2"
[ -z "$node" ] && return 1
local address=$(config_n_get "$node" address)
local port=$(config_n_get "$node" port)
local hop=$(config_n_get "$node" hysteria2_hop)
[ -n "$hop" ] && port="${port:+$port,}$hop"
[ -z "$address" -o -z "$port" ] && return 1
filter_server_port "$address" "$port" "$stream"
}
filter_direct_node_list() {
[ ! -s "$TMP_PATH/direct_node_list" ] && return
for _node_id in $(cat $TMP_PATH/direct_node_list | awk '!seen[$0]++'); do
awk '!seen[$0]++' "$TMP_PATH/direct_node_list" | while read -r _node_id; do
filter_node "$_node_id" TCP
filter_node "$_node_id" UDP
unset _node_id
@@ -793,41 +793,40 @@ filter_vpsip() {
}
filter_server_port() {
local address=${1}
local port=${2}
local stream=${3}
stream=$(echo ${3} | tr 'A-Z' 'a-z')
local _is_tproxy
_is_tproxy=${is_tproxy}
[ "$stream" == "udp" ] && _is_tproxy="TPROXY"
for _ipt in 4 6; do
[ "$_ipt" == "4" ] && _ip_type=ip
[ "$_ipt" == "6" ] && _ip_type=ip6
nft "list chain $NFTABLE_NAME $nft_output_chain" 2>/dev/null | grep -q "${address}:${port}"
if [ $? -ne 0 ]; then
nft "insert rule $NFTABLE_NAME $nft_output_chain meta l4proto $stream $_ip_type daddr $address $stream dport $port return comment \"${address}:${port}\"" 2>/dev/null
fi
local address="$1"
local port=$(echo "$2" | tr ':' '-' | tr -d ' ')
local stream=$(echo "$3" | tr 'A-Z' 'a-z')
local _ip_type _port_expr _ver _is_tproxy
local _nft_output_chain="PSW_OUTPUT_NAT"
[ "$(config_t_get global_forwarding tcp_proxy_way redirect)" = "tproxy" ] && _is_tproxy="TPROXY"
[ "$stream" = "udp" ] && _is_tproxy="TPROXY"
[ -n "$_is_tproxy" ] && _nft_output_chain="PSW_OUTPUT_MANGLE"
case "$port" in
*,*) _port_expr="{ $port }" ;;
*) _port_expr="$port" ;;
esac
for _ver in 4 6; do
[ "$_ver" = "4" ] && _ip_type="ip"
[ "$_ver" = "6" ] && _ip_type="ip6" && _nft_output_chain="PSW_OUTPUT_MANGLE_V6"
nft list chain "$NFTABLE_NAME" "$_nft_output_chain" 2>/dev/null | grep -q "comment \"${address}:${port}:${stream}\"" || \
nft insert rule "$NFTABLE_NAME" "$_nft_output_chain" meta l4proto "$stream" $_ip_type daddr "$address" "$stream" dport $_port_expr return comment "\"${address}:${port}:${stream}\"" 2>/dev/null
done
}
filter_node() {
local node=${1}
local stream=${2}
if [ -n "$node" ]; then
local address=$(config_n_get $node address)
local port=$(config_n_get $node port)
[ -z "$address" ] && [ -z "$port" ] && {
return 1
}
filter_server_port $address $port $stream
filter_server_port $address $port $stream
fi
local node="$1" stream="$2"
[ -z "$node" ] && return 1
local address=$(config_n_get "$node" address)
local port=$(config_n_get "$node" port)
local hop=$(config_n_get "$node" hysteria2_hop)
[ -n "$hop" ] && port="${port:+$port,}$hop"
[ -z "$address" -o -z "$port" ] && return 1
filter_server_port "$address" "$port" "$stream"
}
filter_direct_node_list() {
[ ! -s "$TMP_PATH/direct_node_list" ] && return
for _node_id in $(cat $TMP_PATH/direct_node_list | awk '!seen[$0]++'); do
awk '!seen[$0]++' "$TMP_PATH/direct_node_list" | while read -r _node_id; do
filter_node "$_node_id" TCP
filter_node "$_node_id" UDP
unset _node_id
@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.4.2
PKG_VERSION:=26.4.5
PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION)
@@ -150,6 +150,9 @@ o = s:option(Flag, "accept_icmpv6", translate("Hijacking ICMPv6 (IPv6 PING)"))
o:depends("ipv6_tproxy", true)
o.default = 0
o = s:option(DynamicList, "force_proxy_lan_ip", translate("Force Proxy LAN IP"), translate("By default, commonly used internal network IP ranges will be connect directly (not entering the core). If you want a certain network range to go through a proxy, please add it here."))
o.datatype = "or(ipmask4,ipmask6)"
if has_xray then
s_xray = m:section(TypedSection, "global_xray", "Xray " .. translate("Settings"))
s_xray.anonymous = true
@@ -1119,9 +1119,22 @@ function gen_config(var)
end
if node then
if node.protocol ~= "_shunt" then
-- create shunt logic
local tmp_node = {
remarks = node.remarks,
type = "sing-box",
protocol = "_shunt",
default_node = node[".name"],
}
tmp_node.fakedns = remote_dns_fake
tmp_node.default_fakedns = remote_dns_fake
node = tmp_node
end
if server_host and server_port then
node.address = server_host
node.port = server_port
default_node_address = server_host
default_node_port = server_port
end
function gen_socks_config_node(node_id, socks_id, remarks)
@@ -1369,6 +1382,12 @@ function gen_config(var)
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
end
else
if tag == "default" then
if default_node_address and default_node_port then
node.address = default_node_address
node.port = default_node_port
end
end
for _, _outbound in ipairs(outbounds) do
-- Avoid generating duplicate nested processes
if _outbound["_flag_proxy_tag"] and _outbound["_flag_proxy_tag"]:find("socks <- " .. node[".name"], 1, true) then
@@ -1631,12 +1650,6 @@ function gen_config(var)
table.insert(rules, rule)
end
end)
else
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node, nil, {
fragment = singbox_settings.fragment == "1" or nil,
record_fragment = singbox_settings.record_fragment == "1" or nil,
run_socks_instance = not no_run
})
end
for index, value in ipairs(rules) do
@@ -1875,17 +1888,27 @@ function gen_config(var)
end
end
end
if remote_dns_fake and default_dns_flag == "remote" then
-- When default is not direct and enable fakedns, default DNS use FakeDNS.
local fakedns_dns_rule = {
query_type = {
"A", "AAAA"
},
server = fakedns_tag,
disable_cache = true
}
table.insert(dns.rules, fakedns_dns_rule)
if default_dns_flag == "remote" then
if remote_dns_fake then
-- When default is not direct and enable fakedns, default DNS use FakeDNS.
local fakedns_dns_rule = {
query_type = {
"A", "AAAA"
},
server = fakedns_tag,
disable_cache = true,
rewrite_ttl = 30,
strategy = remote_strategy,
}
table.insert(dns.rules, fakedns_dns_rule)
else
local remote_dns_rule = {
server = "remote",
disable_cache = true,
strategy = remote_strategy,
}
table.insert(dns.rules, remote_dns_rule)
end
end
local dns_in_inbound = {
type = "direct",
@@ -1035,14 +1035,25 @@ function gen_config(var)
else
local preproxy_node = get_node_by_id(node.preproxy_node)
if preproxy_node then
local preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
local preproxy_outbound, exist
if preproxy_node.protocol == "_balancing" then
local balancer_tag, loopback_outbound = gen_balancer(preproxy_node)
if loopback_outbound then
preproxy_outbound = loopback_outbound
exist = true
end
else
preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
end
if preproxy_outbound then
outbound.tag = preproxy_outbound.tag .. " -> " .. outbound.tag
outbound.proxySettings = {
tag = preproxy_outbound.tag,
transportLayer = true
}
last_insert_outbound = preproxy_outbound
if not exist then
last_insert_outbound = preproxy_outbound
end
default_outTag = outbound.tag
end
end
@@ -1118,6 +1129,12 @@ function gen_config(var)
proxy_table.preproxy_node = nil
proxy_table.to_node = nil
end
if tag == "default" then
if default_node_address and default_node_port then
node.address = default_node_address
node.port = default_node_port
end
end
local outbound, has_add_outbound
for _, _outbound in ipairs(outbounds) do
-- Avoid generating duplicate nested processes
@@ -1170,10 +1187,24 @@ function gen_config(var)
end
if node then
if server_host and server_port then
node.address = server_host
node.port = server_port
if node.protocol ~= "_shunt" then
-- create shunt logic
local tmp_node = {
remarks = node.remarks,
type = "Xray",
protocol = "_shunt",
default_node = node[".name"],
}
tmp_node.fakedns = remote_dns_fake
tmp_node.default_fakedns = remote_dns_fake
node = tmp_node
end
if server_host and server_port then
default_node_address = server_host
default_node_port = server_port
end
if node.protocol == "_shunt" then
inner_fakedns = node.fakedns or "0"
@@ -1332,25 +1363,6 @@ function gen_config(var)
balancers = #balancers > 0 and balancers or nil,
rules = rules
}
else
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node, nil, {
fragment = xray_settings.fragment == "1" or nil,
noise = xray_settings.noise == "1" or nil,
run_socks_instance = not no_run
})
if COMMON.default_outbound_tag then
routing = {
domainStrategy = "AsIs",
domainMatcher = "hybrid",
balancers = #balancers > 0 and balancers or nil,
rules = rules
}
table.insert(routing.rules, {
ruleTag = "default",
network = "tcp,udp",
outboundTag = COMMON.default_outbound_tag
})
end
end
end
@@ -747,6 +747,12 @@ msgstr "ربودن ICMP (PING)"
msgid "Hijacking ICMPv6 (IPv6 PING)"
msgstr "ربودن ICMPv6 (IPv6 PING)"
msgid "Force Proxy LAN IP"
msgstr "IP پروکسی LAN را مجبور کنید"
msgid "By default, commonly used internal network IP ranges will be connect directly (not entering the core). If you want a certain network range to go through a proxy, please add it here."
msgstr "به طور پیش‌فرض، محدوده‌های IP شبکه داخلی که معمولاً استفاده می‌شوند، مستقیماً متصل می‌شوند (و وارد هسته نمی‌شوند). اگر می‌خواهید محدوده شبکه خاصی از طریق پروکسی عبور کند، لطفاً آن را اینجا اضافه کنید."
msgid "Sniffing"
msgstr "شنود (Sniffing)"
@@ -748,6 +748,12 @@ msgstr "Перехват ICMP (PING)"
msgid "Hijacking ICMPv6 (IPv6 PING)"
msgstr "Перехват ICMPv6 (IPv6 PING)"
msgid "Force Proxy LAN IP"
msgstr "Принудительное использование IP-адреса локальной сети (Local IP)"
msgid "By default, commonly used internal network IP ranges will be connect directly (not entering the core). If you want a certain network range to go through a proxy, please add it here."
msgstr "По умолчанию, часто используемые диапазоны IP-адресов внутренней сети будут подключаться напрямую (без доступа к ядру сети). Если вы хотите, чтобы определенный диапазон IP-адресов сети проходил через прокси-сервер, укажите это здесь."
msgid "Sniffing"
msgstr "Анализ трафика"
@@ -739,6 +739,12 @@ msgstr "劫持ICMP (PING)"
msgid "Hijacking ICMPv6 (IPv6 PING)"
msgstr "劫持ICMPv6 (IPv6 PING)"
msgid "Force Proxy LAN IP"
msgstr "强制代理内网IP段"
msgid "By default, commonly used internal network IP ranges will be connect directly (not entering the core). If you want a certain network range to go through a proxy, please add it here."
msgstr "默认情况下,常用内网IP网段将直连(不进入内核),如果你希望某个网段走代理,请在此添加。"
msgid "Sniffing"
msgstr "流量嗅探"
@@ -739,6 +739,12 @@ msgstr "劫持ICMP (PING)"
msgid "Hijacking ICMPv6 (IPv6 PING)"
msgstr "劫持ICMPv6 (IPv6 PING)"
msgid "Force Proxy LAN IP"
msgstr "強制代理內網IP段"
msgid "By default, commonly used internal network IP ranges will be connect directly (not entering the core). If you want a certain network range to go through a proxy, please add it here."
msgstr "預設情況下,常用內網IP網段將直連(不進入核心),如果你希望某個網段走代理,請在此新增。"
msgid "Sniffing"
msgstr "流量嗅探"
@@ -4,14 +4,16 @@ DIR="$(cd "$(dirname "$0")" && pwd)"
MY_PATH=$DIR/iptables.sh
UTILS_PATH=$DIR/utils.sh
IPSET_LOCAL="passwall2_local"
IPSET_WAN="passwall2_wan"
IPSET_PROXY_LAN="passwall2_proxy_lan"
IPSET_LAN="passwall2_lan"
IPSET_VPS="passwall2_vps"
IPSET_WAN="passwall2_wan"
IPSET_LOCAL6="passwall2_local6"
IPSET_WAN6="passwall2_wan6"
IPSET_PROXY_LAN6="passwall2_proxy_lan6"
IPSET_LAN6="passwall2_lan6"
IPSET_VPS6="passwall2_vps6"
IPSET_WAN6="passwall2_wan6"
FWMARK="0x50535732"
@@ -398,23 +400,27 @@ load_acl() {
fi
[ "$accept_icmp" = "1" ] && {
$ipt_n -I PSW2 $(comment "$remarks") -p icmp ${_ipt_source} $(dst $IPSET_PROXY_LAN) $(REDIRECT)
$ipt_n -A PSW2 $(comment "$remarks") -p icmp ${_ipt_source} -d $FAKE_IP $(REDIRECT)
add_shunt_t_rule "${shunt_list4}" "$ipt_n -A PSW2 $(comment "$remarks") -p icmp ${_ipt_source}" "$(REDIRECT)"
$ipt_n -A PSW2 $(comment "$remarks") -p icmp ${_ipt_source} $(REDIRECT)
}
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
$ip6t_n -I PSW2 $(comment "$remarks") -p ipv6-icmp ${_ipt_source} $(dst $IPSET_PROXY_LAN6) $(REDIRECT) 2>/dev/null
$ip6t_n -A PSW2 $(comment "$remarks") -p ipv6-icmp ${_ipt_source} -d $FAKE_IP_6 $(REDIRECT) 2>/dev/null
add_shunt_t_rule "${shunt_list6}" "$ip6t_n -A PSW2 $(comment "$remarks") -p ipv6-icmp ${_ipt_source}" "$(REDIRECT)" 2>/dev/null
$ip6t_n -A PSW2 $(comment "$remarks") -p ipv6-icmp ${_ipt_source} $(REDIRECT) 2>/dev/null
}
$ipt_tmp -I PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(dst $IPSET_PROXY_LAN) ${ipt_j}
$ipt_tmp -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} -d $FAKE_IP ${ipt_j}
add_shunt_t_rule "${shunt_list4}" "$ipt_tmp -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source}" "${ipt_j}" $tcp_redir_ports
add_port_rules "$ipt_tmp -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source}" $tcp_redir_ports "${ipt_j}"
[ -n "${is_tproxy}" ] && $ipt_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY)
[ "$PROXY_IPV6" == "1" ] && [ "$_ipv4" != "1" ] && {
$ip6t_m -I PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE 2>/dev/null
$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} -d $FAKE_IP_6 -j PSW2_RULE 2>/dev/null
add_shunt_t_rule "${shunt_list6}" "$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source}" "${ipt_j}" $tcp_redir_ports 2>/dev/null
add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source}" $tcp_redir_ports "-j PSW2_RULE" 2>/dev/null
@@ -428,12 +434,14 @@ load_acl() {
[ "$udp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && {
msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "${node_remark}")(TPROXY:${redir_port})"
$ipt_m -I PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(dst $IPSET_PROXY_LAN) -j PSW2_RULE
$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} -d $FAKE_IP -j PSW2_RULE
add_shunt_t_rule "${shunt_list4}" "$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" "-j PSW2_RULE" $udp_redir_ports
add_port_rules "$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" $udp_redir_ports "-j PSW2_RULE"
$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(REDIRECT $redir_port TPROXY)
[ "$PROXY_IPV6" == "1" ] && [ "$_ipv4" != "1" ] && {
$ip6t_m -I PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE 2>/dev/null
$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} -d $FAKE_IP_6 -j PSW2_RULE 2>/dev/null
add_shunt_t_rule "${shunt_list6}" "$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" "-j PSW2_RULE" $udp_redir_ports 2>/dev/null
add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" $udp_redir_ports "-j PSW2_RULE" 2>/dev/null
@@ -498,23 +506,27 @@ load_acl() {
fi
[ "$accept_icmp" = "1" ] && {
$ipt_n -I PSW2 $(comment "${comment_d}") -p icmp $(dst $IPSET_PROXY_LAN) $(REDIRECT)
$ipt_n -A PSW2 $(comment "${comment_d}") -p icmp -d $FAKE_IP $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_n -A PSW2 $(comment "${comment_d}") -p icmp" "$(REDIRECT)"
$ipt_n -A PSW2 $(comment "${comment_d}") -p icmp $(REDIRECT)
}
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
$ip6t_n -I PSW2 $(comment "${comment_d}") -p ipv6-icmp $(dst $IPSET_PROXY_LAN6) $(REDIRECT)
$ip6t_n -A PSW2 $(comment "${comment_d}") -p ipv6-icmp -d $FAKE_IP_6 $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_n -A PSW2 $(comment "${comment_d}") -p ipv6-icmp" "$(REDIRECT)"
$ip6t_n -A PSW2 $(comment "${comment_d}") -p ipv6-icmp $(REDIRECT)
}
$ipt_tmp -I PSW2 $(comment "${comment_d}") -p tcp $(dst $IPSET_PROXY_LAN) ${ipt_j}
$ipt_tmp -A PSW2 $(comment "${comment_d}") -p tcp -d $FAKE_IP ${ipt_j}
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_tmp -A PSW2 $(comment "${comment_d}") -p tcp" "${ipt_j}" $TCP_REDIR_PORTS
add_port_rules "$ipt_tmp -A PSW2 $(comment "${comment_d}") -p tcp" $TCP_REDIR_PORTS "${ipt_j}"
[ -n "${is_tproxy}" ] && $ipt_m -A PSW2 $(comment "${comment_d}") -p tcp $(REDIRECT $REDIR_PORT TPROXY)
[ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -I PSW2 $(comment "${comment_d}") -p tcp $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE
$ip6t_m -A PSW2 $(comment "${comment_d}") -p tcp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2 $(comment "${comment_d}") -p tcp" "-j PSW2_RULE" $TCP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2 $(comment "${comment_d}") -p tcp" $TCP_REDIR_PORTS "-j PSW2_RULE"
@@ -527,12 +539,14 @@ load_acl() {
if [ "$UDP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then
msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "$(config_n_get $NODE remarks)")(TPROXY:${REDIR_PORT})"
$ipt_m -I PSW2 $(comment "${comment_d}") -p udp $(dst $IPSET_PROXY_LAN) -j PSW2_RULE
$ipt_m -A PSW2 $(comment "${comment_d}") -p udp -d $FAKE_IP -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_m -A PSW2 $(comment "${comment_d}") -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ipt_m -A PSW2 $(comment "${comment_d}") -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ipt_m -A PSW2 $(comment "${comment_d}") -p udp $(REDIRECT $REDIR_PORT TPROXY)
[ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -I PSW2 $(comment "${comment_d}") -p udp -d $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE
$ip6t_m -A PSW2 $(comment "${comment_d}") -p udp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2 $(comment "${comment_d}") -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2 $(comment "${comment_d}") -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
@@ -607,14 +621,16 @@ add_firewall_rule() {
log_i18n 0 "Starting to load %s firewall rules..." "iptables"
ipset -! create $IPSET_LOCAL nethash maxelem 1048576
ipset -! create $IPSET_WAN nethash maxelem 1048576
ipset -! create $IPSET_PROXY_LAN nethash maxelem 1048576
ipset -! create $IPSET_LAN nethash maxelem 1048576
ipset -! create $IPSET_VPS nethash maxelem 1048576
ipset -! create $IPSET_WAN nethash maxelem 1048576
ipset -! create $IPSET_LOCAL6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_WAN6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_PROXY_LAN6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_LAN6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_VPS6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_WAN6 nethash family inet6 maxelem 1048576
ipset -! -R <<-EOF
$(ip address show | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | sed -e "s/^/add $IPSET_LOCAL /")
@@ -663,6 +679,16 @@ add_firewall_rule() {
done
}
# Force proxy LAN IP CIDR
force_proxy_lan_ip=$(config_t_get global_forwarding force_proxy_lan_ip)
for ip in $force_proxy_lan_ip; do
if [[ "$ip" == *::* ]]; then
ipset -! add $IPSET_PROXY_LAN6 $ip
else
ipset -! add $IPSET_PROXY_LAN $ip
fi
done
# Shunt rules IP list (import when use shunt node)
gen_shunt_list "${NODE}" SHUNT_LIST4 SHUNT_LIST6
@@ -679,6 +705,14 @@ add_firewall_rule() {
is_tproxy="TPROXY"
fi
if [ -z "${is_tproxy}" ] || [ "$accept_icmp" = "1" ]; then
IPT_N=1
fi
if [ -z "${is_tproxy}" ] || [ "$accept_icmpv6" = "1" ]; then
IP6T_N=1
fi
$ipt_n -N PSW2
$ipt_n -A PSW2 $(dst $IPSET_LAN) -j RETURN
$ipt_n -A PSW2 $(dst $IPSET_VPS) -j RETURN
@@ -851,6 +885,7 @@ add_firewall_rule() {
if [ -n "$NODE" ] && [ "$TCP_LOCALHOST_PROXY" = "1" ]; then
[ "$accept_icmp" = "1" ] && {
$ipt_n -A OUTPUT -p icmp -j PSW2_OUTPUT
$ipt_n -I PSW2_OUTPUT -p icmp $(dst $IPSET_PROXY_LAN) $(REDIRECT)
$ipt_n -A PSW2_OUTPUT -p icmp -d $FAKE_IP $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_n -A PSW2_OUTPUT -p icmp" "$(REDIRECT)"
$ipt_n -A PSW2_OUTPUT -p icmp $(REDIRECT)
@@ -858,6 +893,7 @@ add_firewall_rule() {
[ "$accept_icmpv6" = "1" ] && {
$ip6t_n -A OUTPUT -p ipv6-icmp -j PSW2_OUTPUT
$ip6t_n -I PSW2_OUTPUT -p ipv6-icmp $(dst $IPSET_PROXY_LAN6) $(REDIRECT)
$ip6t_n -A PSW2_OUTPUT -p ipv6-icmp -d $FAKE_IP_6 $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_n -A PSW2_OUTPUT -p ipv6-icmp" "$(REDIRECT)"
$ip6t_n -A PSW2_OUTPUT -p ipv6-icmp $(REDIRECT)
@@ -869,20 +905,24 @@ add_firewall_rule() {
ipt_j="$(REDIRECT $REDIR_PORT)"
fi
$ipt_tmp -I PSW2_OUTPUT -p tcp $(dst $IPSET_PROXY_LAN) ${ipt_j}
$ipt_tmp -A PSW2_OUTPUT -p tcp -d $FAKE_IP ${ipt_j}
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_tmp -A PSW2_OUTPUT -p tcp" "${ipt_j}" $TCP_REDIR_PORTS
add_port_rules "$ipt_tmp -A PSW2_OUTPUT -p tcp" $TCP_REDIR_PORTS "${ipt_j}"
[ -z "${is_tproxy}" ] && $ipt_n -A OUTPUT -p tcp -j PSW2_OUTPUT
[ -n "${is_tproxy}" ] && {
$ipt_m -I PSW2 $(comment "${comment_l}") -p tcp -i lo $(dst $IPSET_PROXY_LAN) $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo -j RETURN
insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p tcp -j PSW2_OUTPUT"
}
[ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -I PSW2_OUTPUT -p tcp $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE
$ip6t_m -A PSW2_OUTPUT -p tcp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2_OUTPUT -p tcp" "-j PSW2_RULE" $TCP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2_OUTPUT -p tcp" $TCP_REDIR_PORTS "-j PSW2_RULE"
$ip6t_m -I PSW2 $(comment "${comment_l}") -p tcp -i lo $(dst $IPSET_PROXY_LAN6) $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo -j RETURN
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p tcp -j PSW2_OUTPUT"
@@ -898,17 +938,21 @@ add_firewall_rule() {
# Loading local router proxy UDP
if [ -n "$NODE" ] && [ "$UDP_LOCALHOST_PROXY" = "1" ]; then
$ipt_m -I PSW2_OUTPUT -p udp $(dst $IPSET_PROXY_LAN) -j PSW2_RULE
$ipt_m -A PSW2_OUTPUT -p udp -d $FAKE_IP -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_m -A PSW2_OUTPUT -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ipt_m -A PSW2_OUTPUT -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ipt_m -I PSW2 $(comment "${comment_l}") -p udp -i lo $(dst $IPSET_PROXY_LAN) $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "${comment_l}") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "${comment_l}") -p udp -i lo -j RETURN
insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p udp -j PSW2_OUTPUT"
[ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -I PSW2_OUTPUT -p udp $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE
$ip6t_m -A PSW2_OUTPUT -p udp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2_OUTPUT -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2_OUTPUT -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ip6t_m -I PSW2 $(comment "${comment_l}") -p udp -i lo $(dst $IPSET_PROXY_LAN6) $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "${comment_l}") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "${comment_l}") -p udp -i lo -j RETURN
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p udp -j PSW2_OUTPUT"
@@ -936,6 +980,19 @@ add_firewall_rule() {
filter_direct_node_list > /dev/null 2>&1 &
[ -z "${IPT_N}" ] && {
for chain in "PSW2" "PSW2_OUTPUT"; do
$ipt_n -F $chain 2>/dev/null
$ipt_n -X $chain 2>/dev/null
done
}
[ -z "${IP6T_N}" ] && {
for chain in "PSW2" "PSW2_OUTPUT"; do
$ip6t_n -F $chain 2>/dev/null
$ip6t_n -X $chain 2>/dev/null
done
}
log_i18n 0 "%s firewall rules load complete!" "iptables"
}
@@ -1062,6 +1119,8 @@ start() {
stop() {
[ -z "$(command -v log_i18n)" ] && . /usr/share/passwall2/utils.sh
del_firewall_rule
destroy_ipset $IPSET_PROXY_LAN
destroy_ipset $IPSET_PROXY_LAN6
[ $(config_t_get global flush_set "0") = "1" ] && {
uci -q delete ${CONFIG}.@global[0].flush_set
uci -q commit ${CONFIG}
@@ -5,14 +5,16 @@ MY_PATH=$DIR/nftables.sh
UTILS_PATH=$DIR/utils.sh
NFTABLE_NAME="inet passwall2"
NFTSET_LOCAL="passwall2_local"
NFTSET_WAN="passwall2_wan"
NFTSET_PROXY_LAN="passwall2_proxy_lan"
NFTSET_LAN="passwall2_lan"
NFTSET_VPS="passwall2_vps"
NFTSET_WAN="passwall2_wan"
NFTSET_LOCAL6="passwall2_local6"
NFTSET_WAN6="passwall2_wan6"
NFTSET_PROXY_LAN6="passwall2_proxy_lan6"
NFTSET_LAN6="passwall2_lan6"
NFTSET_VPS6="passwall2_vps6"
NFTSET_WAN6="passwall2_wan6"
FWMARK="0x50535732"
@@ -425,6 +427,7 @@ load_acl() {
fi
[ "$accept_icmp" = "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ${_ipt_source} ip daddr @$NFTSET_PROXY_LAN $(REDIRECT) comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ${_ipt_source} ip daddr $FAKE_IP $(REDIRECT) comment \"$remarks\""
add_shunt_t_rule "${shunt_list4}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ${_ipt_source} ip daddr" "$(REDIRECT)" "$remarks"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ${_ipt_source} $(REDIRECT) comment \"$remarks\""
@@ -432,18 +435,21 @@ load_acl() {
}
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} ip6 daddr @$NFTSET_PROXY_LAN6 $(REDIRECT) comment \"$remarks\"" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} ip6 daddr $FAKE_IP_6 $(REDIRECT) comment \"$remarks\"" 2>/dev/null
add_shunt_t_rule "${shunt_list6}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} ip6 daddr" "$(REDIRECT)" "$remarks" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} $(REDIRECT) comment \"$remarks\"" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} return comment \"$remarks\"" 2>/dev/null
}
nft "insert rule $NFTABLE_NAME $nft_chain ip protocol tcp ${_ipt_source} ip daddr @$NFTSET_PROXY_LAN ${nft_j} comment \"$remarks\""
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp ${_ipt_source} ip daddr $FAKE_IP ${nft_j} comment \"$remarks\""
add_shunt_t_rule "${shunt_list4}" "nft add rule $NFTABLE_NAME $nft_chain ip protocol tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") ip daddr" "${nft_j}" "$remarks"
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") ${nft_j} comment \"$remarks\""
[ -n "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY4) comment \"$remarks\""
[ "$PROXY_IPV6" == "1" ] && [ "$_ipv4" != "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} ip6 daddr @$NFTSET_PROXY_LAN6 counter jump PSW2_RULE comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} ip6 daddr $FAKE_IP_6 counter jump PSW2_RULE comment \"$remarks\""
add_shunt_t_rule "${shunt_list6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") ip6 daddr" "counter jump PSW2_RULE" "$remarks" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") counter jump PSW2_RULE comment \"$remarks\"" 2>/dev/null
@@ -457,12 +463,14 @@ load_acl() {
[ "$udp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && {
msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "${node_remark}")(TPROXY:${redir_port})"
nft "insert rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} ip daddr @$NFTSET_PROXY_LAN counter jump PSW2_RULE comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} ip daddr $FAKE_IP counter jump PSW2_RULE comment \"$remarks\""
add_shunt_t_rule "${shunt_list4}" "nft add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") ip daddr" "counter jump PSW2_RULE" "$remarks"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") counter jump PSW2_RULE comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} $(REDIRECT $redir_port TPROXY4) comment \"$remarks\""
[ "$PROXY_IPV6" == "1" ] && [ "$_ipv4" != "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} ip6 daddr @$NFTSET_PROXY_LAN6 counter jump PSW2_RULE comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} ip6 daddr $FAKE_IP_6 counter jump PSW2_RULE comment \"$remarks\""
add_shunt_t_rule "${shunt_list6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") ip6 daddr" "counter jump PSW2_RULE" "$remarks" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") counter jump PSW2_RULE comment \"$remarks\"" 2>/dev/null
@@ -527,6 +535,7 @@ load_acl() {
fi
[ "$accept_icmp" = "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ip daddr @$NFTSET_PROXY_LAN $(REDIRECT) comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ip daddr $FAKE_IP $(REDIRECT) comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ip daddr" "$(REDIRECT)" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp $(REDIRECT) comment \"${comment}\""
@@ -534,18 +543,21 @@ load_acl() {
}
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr @$NFTSET_PROXY_LAN6 $(REDIRECT) comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr $FAKE_IP_6 $(REDIRECT) comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr" "$(REDIRECT)" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 $(REDIRECT) comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 return comment \"${comment}\""
}
nft "insert rule $NFTABLE_NAME $nft_chain ip protocol tcp ip daddr @$NFTSET_PROXY_LAN ${nft_j} comment \"${comment}\""
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp ip daddr $FAKE_IP ${nft_j} comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip daddr" "${nft_j}" "${comment}"
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ${nft_j} comment \"${comment}\""
[ -n "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment}\""
[ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ip6 daddr @$NFTSET_PROXY_LAN6 jump PSW2_RULE comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ip6 daddr $FAKE_IP_6 jump PSW2_RULE comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip6 daddr" "${nft_j}" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") counter jump PSW2_RULE comment \"${comment}\""
@@ -558,12 +570,14 @@ load_acl() {
if [ "$UDP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then
msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "$(config_n_get $NODE remarks)")(TPROXY:${REDIR_PORT})"
nft "insert rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ip daddr @$NFTSET_PROXY_LAN counter jump PSW2_RULE comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ip daddr $FAKE_IP counter jump PSW2_RULE comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") ip daddr" "counter jump PSW2_RULE" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment}\""
[ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ip6 daddr @$NFTSET_PROXY_LAN6 jump PSW2_RULE comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ip6 daddr $FAKE_IP_6 jump PSW2_RULE comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") ip6 daddr" "counter jump PSW2_RULE" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE comment \"${comment}\""
@@ -671,15 +685,18 @@ add_firewall_rule() {
gen_nft_tables
add_script_mwan3
mwan3_start
gen_nftset $NFTSET_WAN ipv4_addr 0 "-1"
gen_nftset $NFTSET_LOCAL ipv4_addr 0 "-1"
gen_nftset $NFTSET_PROXY_LAN ipv4_addr 0 "-1"
gen_nftset $NFTSET_LAN ipv4_addr 0 "-1" $(gen_lanlist)
gen_nftset $NFTSET_VPS ipv4_addr 0 "-1"
gen_nftset $NFTSET_WAN ipv4_addr 0 "-1"
gen_nftset $NFTSET_WAN6 ipv6_addr 0 "-1"
gen_nftset $NFTSET_LOCAL6 ipv6_addr 0 "-1"
gen_nftset $NFTSET_PROXY_LAN6 ipv6_addr 0 "-1"
gen_nftset $NFTSET_LAN6 ipv6_addr 0 "-1" $(gen_lanlist_6)
gen_nftset $NFTSET_VPS6 ipv6_addr 0 "-1"
gen_nftset $NFTSET_WAN6 ipv6_addr 0 "-1"
ip address show | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | insert_nftset $NFTSET_LOCAL "-1"
ip address show | grep -w "inet6" | awk '{print $2}' | awk -F '/' '{print $1}' | insert_nftset $NFTSET_LOCAL6 "-1"
@@ -711,6 +728,16 @@ add_firewall_rule() {
done
}
# Force proxy LAN IP CIDR
force_proxy_lan_ip=$(config_t_get global_forwarding force_proxy_lan_ip)
for ip in $force_proxy_lan_ip; do
if [[ "$ip" == *::* ]]; then
echo "$ip" | insert_nftset $NFTSET_PROXY_LAN6 0
else
echo "$ip" | insert_nftset $NFTSET_PROXY_LAN 0
fi
done
# Shunt rules IP list (import when use shunt node)
gen_shunt_list "${NODE}" SHUNT_LIST4 SHUNT_LIST6
@@ -900,6 +927,7 @@ add_firewall_rule() {
# Loading local router proxy TCP
if [ -n "$NODE" ] && [ "$TCP_LOCALHOST_PROXY" = "1" ]; then
[ "$accept_icmp" = "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo ip protocol icmp ip daddr @$NFTSET_PROXY_LAN counter redirect"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo ip protocol icmp ip daddr $FAKE_IP counter redirect"
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo ip protocol icmp ip daddr" "counter redirect"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo ip protocol icmp counter redirect"
@@ -907,6 +935,7 @@ add_firewall_rule() {
}
[ "$accept_icmpv6" = "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo meta l4proto icmpv6 ip6 daddr @$NFTSET_PROXY_LAN6 counter redirect"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo meta l4proto icmpv6 ip6 daddr $FAKE_IP_6 counter redirect"
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo meta l4proto icmpv6 ip6 daddr" "counter redirect"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo meta l4proto icmpv6 counter redirect"
@@ -921,21 +950,25 @@ add_firewall_rule() {
nft_j="$(REDIRECT $REDIR_PORT)"
fi
nft "insert rule $NFTABLE_NAME $nft_chain ip protocol tcp ip daddr @$NFTSET_PROXY_LAN ${nft_j}"
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp ip daddr $FAKE_IP ${nft_j}"
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip daddr" "${nft_j}"
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ${nft_j}"
[ -z "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME nat_output ip protocol tcp counter jump PSW2_OUTPUT_NAT"
[ -n "${is_tproxy}" ] && {
nft "insert rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp iif lo ip daddr @$NFTSET_PROXY_LAN $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp iif lo $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp iif lo counter return comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME mangle_output ip protocol tcp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
}
[ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp ip6 daddr @$NFTSET_PROXY_LAN6 jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp ip6 daddr $FAKE_IP_6 jump PSW2_RULE"
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip6 daddr" "counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"${comment_l}\""
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo ip6 daddr @$NFTSET_PROXY_LAN6 $(REDIRECT $REDIR_PORT TPROXY6) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo $(REDIRECT $REDIR_PORT TPROXY6) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo counter return comment \"${comment_l}\""
}
@@ -949,18 +982,22 @@ add_firewall_rule() {
# Loading local router proxy UDP
if [ -n "$NODE" ] && [ "$UDP_LOCALHOST_PROXY" = "1" ]; then
nft "insert rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp ip daddr @$NFTSET_PROXY_LAN counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp ip daddr $FAKE_IP counter jump PSW2_RULE"
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") ip daddr" "counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE"
nft "insert rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp iif lo ip daddr @$NFTSET_PROXY_LAN $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp iif lo $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp iif lo counter return comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME mangle_output ip protocol udp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
[ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp ip6 daddr @$NFTSET_PROXY_LAN6 jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp ip6 daddr $FAKE_IP_6 jump PSW2_RULE"
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") ip6 daddr" "counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"${comment_l}\""
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo ip6 daddr @$NFTSET_PROXY_LAN6 $(REDIRECT $REDIR_PORT TPROXY6) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo $(REDIRECT $REDIR_PORT TPROXY6) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo counter return comment \"${comment_l}\""
}
@@ -1080,6 +1117,8 @@ start() {
stop() {
[ -z "$(command -v log_i18n)" ] && . /usr/share/passwall2/utils.sh
del_firewall_rule
destroy_nftset $NFTSET_PROXY_LAN
destroy_nftset $NFTSET_PROXY_LAN6
[ $(config_t_get global flush_set "0") = "1" ] && {
uci -q delete ${CONFIG}.@global[0].flush_set
uci -q commit ${CONFIG}
+3 -3
View File
@@ -9,9 +9,9 @@ PKG_RELEASE:=1
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL:=https://gn.googlesource.com/gn.git
PKG_SOURCE_DATE:=2026-03-24
PKG_SOURCE_VERSION:=b2ac0e7a9089039e62b84d246eca83f84c540f76
PKG_MIRROR_HASH:=75a552f5b910a44202f85d03dcf278d81fdeb7f1a0589f9577f83030f5febb1e
PKG_SOURCE_DATE:=2026-03-31
PKG_SOURCE_VERSION:=6e8dcdebbadf4f8aa75e6a4b6e0bdf89dce1513a
PKG_MIRROR_HASH:=746e218a5674d4a4b61ebcf393ce5d4e7dc0068d02084fe5a809c870859fdedb
PKG_LICENSE:=BSD 3-Clause
PKG_LICENSE_FILES:=LICENSE
+2 -2
View File
@@ -3,7 +3,7 @@
#ifndef OUT_LAST_COMMIT_POSITION_H_
#define OUT_LAST_COMMIT_POSITION_H_
#define LAST_COMMIT_POSITION_NUM 2353
#define LAST_COMMIT_POSITION "2353 (b2ac0e7a9089)"
#define LAST_COMMIT_POSITION_NUM 2355
#define LAST_COMMIT_POSITION "2355 (6e8dcdebbadf)"
#endif // OUT_LAST_COMMIT_POSITION_H_
@@ -1955,7 +1955,7 @@ function gen_config(var)
}
})
for index, value in ipairs(config.outbounds) do
if not value["_flag_proxy_tag"] and not value.detour and value["_id"] and value.server and value.server_port and not no_run then
if not value["_flag_proxy_tag"] and not value.detour and value["_id"] and value.server and (value.server_port or value.server_ports) and not no_run then
sys.call(string.format("echo '%s' >> %s", value["_id"], api.TMP_PATH .. "/direct_node_list"))
end
for k, v in pairs(config.outbounds[index]) do
@@ -1064,17 +1064,17 @@ function gen_config(var)
local to_node = get_node_by_id(node.to_node)
if to_node then
-- Landing Node not support use special node.
if to_node.protocol:find("^_") then
if to_node.protocol and to_node.protocol:find("^_") then
to_node = nil
end
end
if to_node then
local to_outbound
if to_node.type ~= "Xray" then
local tag = to_node[".name"]
local in_tag = "inbound_" .. to_node[".name"] .. "_" .. tostring(outbound.tag)
local new_port = api.get_new_port()
table.insert(inbounds, {
tag = tag,
tag = in_tag,
listen = "127.0.0.1",
port = new_port,
protocol = "dokodemo-door",
@@ -1086,11 +1086,11 @@ function gen_config(var)
to_node.address = "127.0.0.1"
to_node.port = new_port
table.insert(rules, 1, {
inboundTag = {tag},
inboundTag = {in_tag},
outboundTag = outbound.tag
})
to_outbound = gen_outbound(node[".name"], to_node, tag, {
tag = tag,
to_outbound = gen_outbound(node[".name"], to_node, to_node[".name"], {
tag = to_node[".name"],
run_socks_instance = not no_run
})
else
@@ -1734,14 +1734,10 @@ function gen_config(var)
else
table.insert(outbounds, blackhole_outbound)
end
for index, value in ipairs(config.outbounds) do
local s = value.settings
if not value["_flag_proxy_tag"] and value["_id"] and s and not no_run and
((s.vnext and s.vnext[1] and s.vnext[1].address and s.vnext[1].port) or
(s.servers and s.servers[1] and s.servers[1].address and s.servers[1].port) or
(s.peers and s.peers[1] and s.peers[1].endpoint) or
(s.address and s.port)) then
local pt = value.protocol
local exclude = { blackhole=1, dns=1, freedom=1, loopback=1 }
if not value["_flag_proxy_tag"] and value["_id"] and pt and not exclude[pt] and not no_run then
sys.call(string.format("echo '%s' >> %s", value["_id"], api.TMP_PATH .. "/direct_node_list"))
end
for k, v in pairs(config.outbounds[index]) do
@@ -1101,6 +1101,9 @@ socks_node_switch() {
LOG_FILE="/dev/null"
run_socks flag=$flag node=$new_node bind=$bind socks_port=$port config_file=$config_file http_port=$http_port http_config_file=$http_config_file log_file=$log_file
set_cache_var "socks_${flag}" "$new_node"
local ENABLED_DEFAULT_ACL=$(get_cache_var "ENABLED_DEFAULT_ACL")
local ENABLED_ACLS=$(get_cache_var "ENABLED_ACLS")
[ "$ENABLED_DEFAULT_ACL" != "1" -a "$ENABLED_ACLS" != "1" ] && return
local USE_TABLES=$(get_cache_var "USE_TABLES")
[ -n "$USE_TABLES" ] && source $APP_PATH/${USE_TABLES}.sh filter_direct_node_list
}
@@ -1979,6 +1982,8 @@ get_config() {
[ "$ENABLED_ACLS" = 1 ] && {
[ "$(uci show ${CONFIG} | grep "@acl_rule" | grep "enabled='1'" | wc -l)" == 0 ] && ENABLED_ACLS=0
}
set_cache_var ENABLED_DEFAULT_ACL $ENABLED_DEFAULT_ACL
set_cache_var ENABLED_ACLS $ENABLED_ACLS
TCP_PROXY_WAY=$(config_t_get global_forwarding tcp_proxy_way redirect)
PROXY_IPV6=$(config_t_get global_forwarding ipv6_tproxy 0)
@@ -742,43 +742,50 @@ filter_vpsip() {
}
filter_server_port() {
local address=${1}
local port=${2}
local stream=${3}
stream=$(echo ${3} | tr 'A-Z' 'a-z')
local _is_tproxy ipt_tmp
ipt_tmp=$ipt_n
_is_tproxy=${is_tproxy}
[ "$stream" == "udp" ] && _is_tproxy="TPROXY"
[ -n "${_is_tproxy}" ] && ipt_tmp=$ipt_m
for _ipt in 4 6; do
[ "$_ipt" == "4" ] && _ipt=$ipt_tmp
[ "$_ipt" == "6" ] && _ipt=$ip6t_m
$_ipt -n -L PSW_OUTPUT | grep -q "${address}:${port}"
if [ $? -ne 0 ]; then
$_ipt -I PSW_OUTPUT $(comment "${address}:${port}") -p $stream -d $address --dport $port -j RETURN 2>/dev/null
local address="$1"
local port=$(echo "$2" | tr '-' ':' | tr -d ' ')
local stream=$(echo "$3" | tr 'A-Z' 'a-z')
local ipt_tmp="$ipt_n" _is_tproxy _ipt_cmd _ver multi_ports p ports
[ "$(config_t_get global_forwarding tcp_proxy_way redirect)" = "tproxy" ] && _is_tproxy="TPROXY"
[ "$stream" = "udp" ] && _is_tproxy="TPROXY"
[ -n "$_is_tproxy" ] && ipt_tmp="$ipt_m"
for _ver in 4 6; do
[ "$_ver" = "4" ] && _ipt_cmd="$ipt_tmp"
[ "$_ver" = "6" ] && _ipt_cmd="$ip6t_m"
multi_ports=""
for p in $(echo "$port" | tr ',' ' '); do
case "$p" in
*:* )
$_ipt_cmd -n -L PSW_OUTPUT 2>/dev/null | grep -q "${address}:${p}:${stream}" || \
$_ipt_cmd -I PSW_OUTPUT $(comment "${address}:${p}:${stream}") -p "$stream" -d "$address" --dport "$p" -j RETURN 2>/dev/null
;;
* )
multi_ports="${multi_ports},$p"
;;
esac
done
if [ -n "$multi_ports" ]; then
ports=$(printf "%s\n" "${multi_ports#,}" | tr ',' '\n' | sort -n | tr '\n' ',' | sed 's/,$//')
$_ipt_cmd -n -L PSW_OUTPUT 2>/dev/null | grep -q "${address}:${ports}:${stream}" || \
$_ipt_cmd -I PSW_OUTPUT $(comment "${address}:${ports}:${stream}") -p "$stream" -d "$address" -m multiport --dports "$ports" -j RETURN 2>/dev/null
fi
done
}
filter_node() {
local node=${1}
local stream=${2}
if [ -n "$node" ]; then
local address=$(config_n_get $node address)
local port=$(config_n_get $node port)
[ -z "$address" ] && [ -z "$port" ] && {
return 1
}
filter_server_port $address $port $stream
filter_server_port $address $port $stream
fi
local node="$1" stream="$2"
[ -z "$node" ] && return 1
local address=$(config_n_get "$node" address)
local port=$(config_n_get "$node" port)
local hop=$(config_n_get "$node" hysteria2_hop)
[ -n "$hop" ] && port="${port:+$port,}$hop"
[ -z "$address" -o -z "$port" ] && return 1
filter_server_port "$address" "$port" "$stream"
}
filter_direct_node_list() {
[ ! -s "$TMP_PATH/direct_node_list" ] && return
for _node_id in $(cat $TMP_PATH/direct_node_list | awk '!seen[$0]++'); do
awk '!seen[$0]++' "$TMP_PATH/direct_node_list" | while read -r _node_id; do
filter_node "$_node_id" TCP
filter_node "$_node_id" UDP
unset _node_id
@@ -793,41 +793,40 @@ filter_vpsip() {
}
filter_server_port() {
local address=${1}
local port=${2}
local stream=${3}
stream=$(echo ${3} | tr 'A-Z' 'a-z')
local _is_tproxy
_is_tproxy=${is_tproxy}
[ "$stream" == "udp" ] && _is_tproxy="TPROXY"
for _ipt in 4 6; do
[ "$_ipt" == "4" ] && _ip_type=ip
[ "$_ipt" == "6" ] && _ip_type=ip6
nft "list chain $NFTABLE_NAME $nft_output_chain" 2>/dev/null | grep -q "${address}:${port}"
if [ $? -ne 0 ]; then
nft "insert rule $NFTABLE_NAME $nft_output_chain meta l4proto $stream $_ip_type daddr $address $stream dport $port return comment \"${address}:${port}\"" 2>/dev/null
fi
local address="$1"
local port=$(echo "$2" | tr ':' '-' | tr -d ' ')
local stream=$(echo "$3" | tr 'A-Z' 'a-z')
local _ip_type _port_expr _ver _is_tproxy
local _nft_output_chain="PSW_OUTPUT_NAT"
[ "$(config_t_get global_forwarding tcp_proxy_way redirect)" = "tproxy" ] && _is_tproxy="TPROXY"
[ "$stream" = "udp" ] && _is_tproxy="TPROXY"
[ -n "$_is_tproxy" ] && _nft_output_chain="PSW_OUTPUT_MANGLE"
case "$port" in
*,*) _port_expr="{ $port }" ;;
*) _port_expr="$port" ;;
esac
for _ver in 4 6; do
[ "$_ver" = "4" ] && _ip_type="ip"
[ "$_ver" = "6" ] && _ip_type="ip6" && _nft_output_chain="PSW_OUTPUT_MANGLE_V6"
nft list chain "$NFTABLE_NAME" "$_nft_output_chain" 2>/dev/null | grep -q "comment \"${address}:${port}:${stream}\"" || \
nft insert rule "$NFTABLE_NAME" "$_nft_output_chain" meta l4proto "$stream" $_ip_type daddr "$address" "$stream" dport $_port_expr return comment "\"${address}:${port}:${stream}\"" 2>/dev/null
done
}
filter_node() {
local node=${1}
local stream=${2}
if [ -n "$node" ]; then
local address=$(config_n_get $node address)
local port=$(config_n_get $node port)
[ -z "$address" ] && [ -z "$port" ] && {
return 1
}
filter_server_port $address $port $stream
filter_server_port $address $port $stream
fi
local node="$1" stream="$2"
[ -z "$node" ] && return 1
local address=$(config_n_get "$node" address)
local port=$(config_n_get "$node" port)
local hop=$(config_n_get "$node" hysteria2_hop)
[ -n "$hop" ] && port="${port:+$port,}$hop"
[ -z "$address" -o -z "$port" ] && return 1
filter_server_port "$address" "$port" "$stream"
}
filter_direct_node_list() {
[ ! -s "$TMP_PATH/direct_node_list" ] && return
for _node_id in $(cat $TMP_PATH/direct_node_list | awk '!seen[$0]++'); do
awk '!seen[$0]++' "$TMP_PATH/direct_node_list" | while read -r _node_id; do
filter_node "$_node_id" TCP
filter_node "$_node_id" UDP
unset _node_id
+1 -1
View File
@@ -6,7 +6,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-passwall2
PKG_VERSION:=26.4.2
PKG_VERSION:=26.4.5
PKG_RELEASE:=1
PKG_PO_VERSION:=$(PKG_VERSION)
@@ -150,6 +150,9 @@ o = s:option(Flag, "accept_icmpv6", translate("Hijacking ICMPv6 (IPv6 PING)"))
o:depends("ipv6_tproxy", true)
o.default = 0
o = s:option(DynamicList, "force_proxy_lan_ip", translate("Force Proxy LAN IP"), translate("By default, commonly used internal network IP ranges will be connect directly (not entering the core). If you want a certain network range to go through a proxy, please add it here."))
o.datatype = "or(ipmask4,ipmask6)"
if has_xray then
s_xray = m:section(TypedSection, "global_xray", "Xray " .. translate("Settings"))
s_xray.anonymous = true
@@ -1119,9 +1119,22 @@ function gen_config(var)
end
if node then
if node.protocol ~= "_shunt" then
-- create shunt logic
local tmp_node = {
remarks = node.remarks,
type = "sing-box",
protocol = "_shunt",
default_node = node[".name"],
}
tmp_node.fakedns = remote_dns_fake
tmp_node.default_fakedns = remote_dns_fake
node = tmp_node
end
if server_host and server_port then
node.address = server_host
node.port = server_port
default_node_address = server_host
default_node_port = server_port
end
function gen_socks_config_node(node_id, socks_id, remarks)
@@ -1369,6 +1382,12 @@ function gen_config(var)
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
end
else
if tag == "default" then
if default_node_address and default_node_port then
node.address = default_node_address
node.port = default_node_port
end
end
for _, _outbound in ipairs(outbounds) do
-- Avoid generating duplicate nested processes
if _outbound["_flag_proxy_tag"] and _outbound["_flag_proxy_tag"]:find("socks <- " .. node[".name"], 1, true) then
@@ -1631,12 +1650,6 @@ function gen_config(var)
table.insert(rules, rule)
end
end)
else
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node, nil, {
fragment = singbox_settings.fragment == "1" or nil,
record_fragment = singbox_settings.record_fragment == "1" or nil,
run_socks_instance = not no_run
})
end
for index, value in ipairs(rules) do
@@ -1875,17 +1888,27 @@ function gen_config(var)
end
end
end
if remote_dns_fake and default_dns_flag == "remote" then
-- When default is not direct and enable fakedns, default DNS use FakeDNS.
local fakedns_dns_rule = {
query_type = {
"A", "AAAA"
},
server = fakedns_tag,
disable_cache = true
}
table.insert(dns.rules, fakedns_dns_rule)
if default_dns_flag == "remote" then
if remote_dns_fake then
-- When default is not direct and enable fakedns, default DNS use FakeDNS.
local fakedns_dns_rule = {
query_type = {
"A", "AAAA"
},
server = fakedns_tag,
disable_cache = true,
rewrite_ttl = 30,
strategy = remote_strategy,
}
table.insert(dns.rules, fakedns_dns_rule)
else
local remote_dns_rule = {
server = "remote",
disable_cache = true,
strategy = remote_strategy,
}
table.insert(dns.rules, remote_dns_rule)
end
end
local dns_in_inbound = {
type = "direct",
@@ -1035,14 +1035,25 @@ function gen_config(var)
else
local preproxy_node = get_node_by_id(node.preproxy_node)
if preproxy_node then
local preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
local preproxy_outbound, exist
if preproxy_node.protocol == "_balancing" then
local balancer_tag, loopback_outbound = gen_balancer(preproxy_node)
if loopback_outbound then
preproxy_outbound = loopback_outbound
exist = true
end
else
preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
end
if preproxy_outbound then
outbound.tag = preproxy_outbound.tag .. " -> " .. outbound.tag
outbound.proxySettings = {
tag = preproxy_outbound.tag,
transportLayer = true
}
last_insert_outbound = preproxy_outbound
if not exist then
last_insert_outbound = preproxy_outbound
end
default_outTag = outbound.tag
end
end
@@ -1118,6 +1129,12 @@ function gen_config(var)
proxy_table.preproxy_node = nil
proxy_table.to_node = nil
end
if tag == "default" then
if default_node_address and default_node_port then
node.address = default_node_address
node.port = default_node_port
end
end
local outbound, has_add_outbound
for _, _outbound in ipairs(outbounds) do
-- Avoid generating duplicate nested processes
@@ -1170,10 +1187,24 @@ function gen_config(var)
end
if node then
if server_host and server_port then
node.address = server_host
node.port = server_port
if node.protocol ~= "_shunt" then
-- create shunt logic
local tmp_node = {
remarks = node.remarks,
type = "Xray",
protocol = "_shunt",
default_node = node[".name"],
}
tmp_node.fakedns = remote_dns_fake
tmp_node.default_fakedns = remote_dns_fake
node = tmp_node
end
if server_host and server_port then
default_node_address = server_host
default_node_port = server_port
end
if node.protocol == "_shunt" then
inner_fakedns = node.fakedns or "0"
@@ -1332,25 +1363,6 @@ function gen_config(var)
balancers = #balancers > 0 and balancers or nil,
rules = rules
}
else
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node, nil, {
fragment = xray_settings.fragment == "1" or nil,
noise = xray_settings.noise == "1" or nil,
run_socks_instance = not no_run
})
if COMMON.default_outbound_tag then
routing = {
domainStrategy = "AsIs",
domainMatcher = "hybrid",
balancers = #balancers > 0 and balancers or nil,
rules = rules
}
table.insert(routing.rules, {
ruleTag = "default",
network = "tcp,udp",
outboundTag = COMMON.default_outbound_tag
})
end
end
end
@@ -747,6 +747,12 @@ msgstr "ربودن ICMP (PING)"
msgid "Hijacking ICMPv6 (IPv6 PING)"
msgstr "ربودن ICMPv6 (IPv6 PING)"
msgid "Force Proxy LAN IP"
msgstr "IP پروکسی LAN را مجبور کنید"
msgid "By default, commonly used internal network IP ranges will be connect directly (not entering the core). If you want a certain network range to go through a proxy, please add it here."
msgstr "به طور پیش‌فرض، محدوده‌های IP شبکه داخلی که معمولاً استفاده می‌شوند، مستقیماً متصل می‌شوند (و وارد هسته نمی‌شوند). اگر می‌خواهید محدوده شبکه خاصی از طریق پروکسی عبور کند، لطفاً آن را اینجا اضافه کنید."
msgid "Sniffing"
msgstr "شنود (Sniffing)"
@@ -748,6 +748,12 @@ msgstr "Перехват ICMP (PING)"
msgid "Hijacking ICMPv6 (IPv6 PING)"
msgstr "Перехват ICMPv6 (IPv6 PING)"
msgid "Force Proxy LAN IP"
msgstr "Принудительное использование IP-адреса локальной сети (Local IP)"
msgid "By default, commonly used internal network IP ranges will be connect directly (not entering the core). If you want a certain network range to go through a proxy, please add it here."
msgstr "По умолчанию, часто используемые диапазоны IP-адресов внутренней сети будут подключаться напрямую (без доступа к ядру сети). Если вы хотите, чтобы определенный диапазон IP-адресов сети проходил через прокси-сервер, укажите это здесь."
msgid "Sniffing"
msgstr "Анализ трафика"
@@ -739,6 +739,12 @@ msgstr "劫持ICMP (PING)"
msgid "Hijacking ICMPv6 (IPv6 PING)"
msgstr "劫持ICMPv6 (IPv6 PING)"
msgid "Force Proxy LAN IP"
msgstr "强制代理内网IP段"
msgid "By default, commonly used internal network IP ranges will be connect directly (not entering the core). If you want a certain network range to go through a proxy, please add it here."
msgstr "默认情况下,常用内网IP网段将直连(不进入内核),如果你希望某个网段走代理,请在此添加。"
msgid "Sniffing"
msgstr "流量嗅探"
@@ -739,6 +739,12 @@ msgstr "劫持ICMP (PING)"
msgid "Hijacking ICMPv6 (IPv6 PING)"
msgstr "劫持ICMPv6 (IPv6 PING)"
msgid "Force Proxy LAN IP"
msgstr "強制代理內網IP段"
msgid "By default, commonly used internal network IP ranges will be connect directly (not entering the core). If you want a certain network range to go through a proxy, please add it here."
msgstr "預設情況下,常用內網IP網段將直連(不進入核心),如果你希望某個網段走代理,請在此新增。"
msgid "Sniffing"
msgstr "流量嗅探"
@@ -4,14 +4,16 @@ DIR="$(cd "$(dirname "$0")" && pwd)"
MY_PATH=$DIR/iptables.sh
UTILS_PATH=$DIR/utils.sh
IPSET_LOCAL="passwall2_local"
IPSET_WAN="passwall2_wan"
IPSET_PROXY_LAN="passwall2_proxy_lan"
IPSET_LAN="passwall2_lan"
IPSET_VPS="passwall2_vps"
IPSET_WAN="passwall2_wan"
IPSET_LOCAL6="passwall2_local6"
IPSET_WAN6="passwall2_wan6"
IPSET_PROXY_LAN6="passwall2_proxy_lan6"
IPSET_LAN6="passwall2_lan6"
IPSET_VPS6="passwall2_vps6"
IPSET_WAN6="passwall2_wan6"
FWMARK="0x50535732"
@@ -398,23 +400,27 @@ load_acl() {
fi
[ "$accept_icmp" = "1" ] && {
$ipt_n -I PSW2 $(comment "$remarks") -p icmp ${_ipt_source} $(dst $IPSET_PROXY_LAN) $(REDIRECT)
$ipt_n -A PSW2 $(comment "$remarks") -p icmp ${_ipt_source} -d $FAKE_IP $(REDIRECT)
add_shunt_t_rule "${shunt_list4}" "$ipt_n -A PSW2 $(comment "$remarks") -p icmp ${_ipt_source}" "$(REDIRECT)"
$ipt_n -A PSW2 $(comment "$remarks") -p icmp ${_ipt_source} $(REDIRECT)
}
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
$ip6t_n -I PSW2 $(comment "$remarks") -p ipv6-icmp ${_ipt_source} $(dst $IPSET_PROXY_LAN6) $(REDIRECT) 2>/dev/null
$ip6t_n -A PSW2 $(comment "$remarks") -p ipv6-icmp ${_ipt_source} -d $FAKE_IP_6 $(REDIRECT) 2>/dev/null
add_shunt_t_rule "${shunt_list6}" "$ip6t_n -A PSW2 $(comment "$remarks") -p ipv6-icmp ${_ipt_source}" "$(REDIRECT)" 2>/dev/null
$ip6t_n -A PSW2 $(comment "$remarks") -p ipv6-icmp ${_ipt_source} $(REDIRECT) 2>/dev/null
}
$ipt_tmp -I PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(dst $IPSET_PROXY_LAN) ${ipt_j}
$ipt_tmp -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} -d $FAKE_IP ${ipt_j}
add_shunt_t_rule "${shunt_list4}" "$ipt_tmp -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source}" "${ipt_j}" $tcp_redir_ports
add_port_rules "$ipt_tmp -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source}" $tcp_redir_ports "${ipt_j}"
[ -n "${is_tproxy}" ] && $ipt_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY)
[ "$PROXY_IPV6" == "1" ] && [ "$_ipv4" != "1" ] && {
$ip6t_m -I PSW2 $(comment "$remarks") -p tcp ${_ipt_source} $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE 2>/dev/null
$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source} -d $FAKE_IP_6 -j PSW2_RULE 2>/dev/null
add_shunt_t_rule "${shunt_list6}" "$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source}" "${ipt_j}" $tcp_redir_ports 2>/dev/null
add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") -p tcp ${_ipt_source}" $tcp_redir_ports "-j PSW2_RULE" 2>/dev/null
@@ -428,12 +434,14 @@ load_acl() {
[ "$udp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && {
msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "${node_remark}")(TPROXY:${redir_port})"
$ipt_m -I PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(dst $IPSET_PROXY_LAN) -j PSW2_RULE
$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} -d $FAKE_IP -j PSW2_RULE
add_shunt_t_rule "${shunt_list4}" "$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" "-j PSW2_RULE" $udp_redir_ports
add_port_rules "$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" $udp_redir_ports "-j PSW2_RULE"
$ipt_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(REDIRECT $redir_port TPROXY)
[ "$PROXY_IPV6" == "1" ] && [ "$_ipv4" != "1" ] && {
$ip6t_m -I PSW2 $(comment "$remarks") -p udp ${_ipt_source} $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE 2>/dev/null
$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source} -d $FAKE_IP_6 -j PSW2_RULE 2>/dev/null
add_shunt_t_rule "${shunt_list6}" "$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" "-j PSW2_RULE" $udp_redir_ports 2>/dev/null
add_port_rules "$ip6t_m -A PSW2 $(comment "$remarks") -p udp ${_ipt_source}" $udp_redir_ports "-j PSW2_RULE" 2>/dev/null
@@ -498,23 +506,27 @@ load_acl() {
fi
[ "$accept_icmp" = "1" ] && {
$ipt_n -I PSW2 $(comment "${comment_d}") -p icmp $(dst $IPSET_PROXY_LAN) $(REDIRECT)
$ipt_n -A PSW2 $(comment "${comment_d}") -p icmp -d $FAKE_IP $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_n -A PSW2 $(comment "${comment_d}") -p icmp" "$(REDIRECT)"
$ipt_n -A PSW2 $(comment "${comment_d}") -p icmp $(REDIRECT)
}
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
$ip6t_n -I PSW2 $(comment "${comment_d}") -p ipv6-icmp $(dst $IPSET_PROXY_LAN6) $(REDIRECT)
$ip6t_n -A PSW2 $(comment "${comment_d}") -p ipv6-icmp -d $FAKE_IP_6 $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_n -A PSW2 $(comment "${comment_d}") -p ipv6-icmp" "$(REDIRECT)"
$ip6t_n -A PSW2 $(comment "${comment_d}") -p ipv6-icmp $(REDIRECT)
}
$ipt_tmp -I PSW2 $(comment "${comment_d}") -p tcp $(dst $IPSET_PROXY_LAN) ${ipt_j}
$ipt_tmp -A PSW2 $(comment "${comment_d}") -p tcp -d $FAKE_IP ${ipt_j}
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_tmp -A PSW2 $(comment "${comment_d}") -p tcp" "${ipt_j}" $TCP_REDIR_PORTS
add_port_rules "$ipt_tmp -A PSW2 $(comment "${comment_d}") -p tcp" $TCP_REDIR_PORTS "${ipt_j}"
[ -n "${is_tproxy}" ] && $ipt_m -A PSW2 $(comment "${comment_d}") -p tcp $(REDIRECT $REDIR_PORT TPROXY)
[ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -I PSW2 $(comment "${comment_d}") -p tcp $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE
$ip6t_m -A PSW2 $(comment "${comment_d}") -p tcp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2 $(comment "${comment_d}") -p tcp" "-j PSW2_RULE" $TCP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2 $(comment "${comment_d}") -p tcp" $TCP_REDIR_PORTS "-j PSW2_RULE"
@@ -527,12 +539,14 @@ load_acl() {
if [ "$UDP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then
msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "$(config_n_get $NODE remarks)")(TPROXY:${REDIR_PORT})"
$ipt_m -I PSW2 $(comment "${comment_d}") -p udp $(dst $IPSET_PROXY_LAN) -j PSW2_RULE
$ipt_m -A PSW2 $(comment "${comment_d}") -p udp -d $FAKE_IP -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_m -A PSW2 $(comment "${comment_d}") -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ipt_m -A PSW2 $(comment "${comment_d}") -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ipt_m -A PSW2 $(comment "${comment_d}") -p udp $(REDIRECT $REDIR_PORT TPROXY)
[ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -I PSW2 $(comment "${comment_d}") -p udp -d $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE
$ip6t_m -A PSW2 $(comment "${comment_d}") -p udp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2 $(comment "${comment_d}") -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2 $(comment "${comment_d}") -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
@@ -607,14 +621,16 @@ add_firewall_rule() {
log_i18n 0 "Starting to load %s firewall rules..." "iptables"
ipset -! create $IPSET_LOCAL nethash maxelem 1048576
ipset -! create $IPSET_WAN nethash maxelem 1048576
ipset -! create $IPSET_PROXY_LAN nethash maxelem 1048576
ipset -! create $IPSET_LAN nethash maxelem 1048576
ipset -! create $IPSET_VPS nethash maxelem 1048576
ipset -! create $IPSET_WAN nethash maxelem 1048576
ipset -! create $IPSET_LOCAL6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_WAN6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_PROXY_LAN6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_LAN6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_VPS6 nethash family inet6 maxelem 1048576
ipset -! create $IPSET_WAN6 nethash family inet6 maxelem 1048576
ipset -! -R <<-EOF
$(ip address show | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | sed -e "s/^/add $IPSET_LOCAL /")
@@ -663,6 +679,16 @@ add_firewall_rule() {
done
}
# Force proxy LAN IP CIDR
force_proxy_lan_ip=$(config_t_get global_forwarding force_proxy_lan_ip)
for ip in $force_proxy_lan_ip; do
if [[ "$ip" == *::* ]]; then
ipset -! add $IPSET_PROXY_LAN6 $ip
else
ipset -! add $IPSET_PROXY_LAN $ip
fi
done
# Shunt rules IP list (import when use shunt node)
gen_shunt_list "${NODE}" SHUNT_LIST4 SHUNT_LIST6
@@ -679,6 +705,14 @@ add_firewall_rule() {
is_tproxy="TPROXY"
fi
if [ -z "${is_tproxy}" ] || [ "$accept_icmp" = "1" ]; then
IPT_N=1
fi
if [ -z "${is_tproxy}" ] || [ "$accept_icmpv6" = "1" ]; then
IP6T_N=1
fi
$ipt_n -N PSW2
$ipt_n -A PSW2 $(dst $IPSET_LAN) -j RETURN
$ipt_n -A PSW2 $(dst $IPSET_VPS) -j RETURN
@@ -851,6 +885,7 @@ add_firewall_rule() {
if [ -n "$NODE" ] && [ "$TCP_LOCALHOST_PROXY" = "1" ]; then
[ "$accept_icmp" = "1" ] && {
$ipt_n -A OUTPUT -p icmp -j PSW2_OUTPUT
$ipt_n -I PSW2_OUTPUT -p icmp $(dst $IPSET_PROXY_LAN) $(REDIRECT)
$ipt_n -A PSW2_OUTPUT -p icmp -d $FAKE_IP $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_n -A PSW2_OUTPUT -p icmp" "$(REDIRECT)"
$ipt_n -A PSW2_OUTPUT -p icmp $(REDIRECT)
@@ -858,6 +893,7 @@ add_firewall_rule() {
[ "$accept_icmpv6" = "1" ] && {
$ip6t_n -A OUTPUT -p ipv6-icmp -j PSW2_OUTPUT
$ip6t_n -I PSW2_OUTPUT -p ipv6-icmp $(dst $IPSET_PROXY_LAN6) $(REDIRECT)
$ip6t_n -A PSW2_OUTPUT -p ipv6-icmp -d $FAKE_IP_6 $(REDIRECT)
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_n -A PSW2_OUTPUT -p ipv6-icmp" "$(REDIRECT)"
$ip6t_n -A PSW2_OUTPUT -p ipv6-icmp $(REDIRECT)
@@ -869,20 +905,24 @@ add_firewall_rule() {
ipt_j="$(REDIRECT $REDIR_PORT)"
fi
$ipt_tmp -I PSW2_OUTPUT -p tcp $(dst $IPSET_PROXY_LAN) ${ipt_j}
$ipt_tmp -A PSW2_OUTPUT -p tcp -d $FAKE_IP ${ipt_j}
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_tmp -A PSW2_OUTPUT -p tcp" "${ipt_j}" $TCP_REDIR_PORTS
add_port_rules "$ipt_tmp -A PSW2_OUTPUT -p tcp" $TCP_REDIR_PORTS "${ipt_j}"
[ -z "${is_tproxy}" ] && $ipt_n -A OUTPUT -p tcp -j PSW2_OUTPUT
[ -n "${is_tproxy}" ] && {
$ipt_m -I PSW2 $(comment "${comment_l}") -p tcp -i lo $(dst $IPSET_PROXY_LAN) $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo -j RETURN
insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p tcp -j PSW2_OUTPUT"
}
[ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -I PSW2_OUTPUT -p tcp $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE
$ip6t_m -A PSW2_OUTPUT -p tcp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2_OUTPUT -p tcp" "-j PSW2_RULE" $TCP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2_OUTPUT -p tcp" $TCP_REDIR_PORTS "-j PSW2_RULE"
$ip6t_m -I PSW2 $(comment "${comment_l}") -p tcp -i lo $(dst $IPSET_PROXY_LAN6) $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "${comment_l}") -p tcp -i lo -j RETURN
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p tcp -j PSW2_OUTPUT"
@@ -898,17 +938,21 @@ add_firewall_rule() {
# Loading local router proxy UDP
if [ -n "$NODE" ] && [ "$UDP_LOCALHOST_PROXY" = "1" ]; then
$ipt_m -I PSW2_OUTPUT -p udp $(dst $IPSET_PROXY_LAN) -j PSW2_RULE
$ipt_m -A PSW2_OUTPUT -p udp -d $FAKE_IP -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST4}" "$ipt_m -A PSW2_OUTPUT -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ipt_m -A PSW2_OUTPUT -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ipt_m -I PSW2 $(comment "${comment_l}") -p udp -i lo $(dst $IPSET_PROXY_LAN) $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "${comment_l}") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ipt_m -A PSW2 $(comment "${comment_l}") -p udp -i lo -j RETURN
insert_rule_before "$ipt_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p udp -j PSW2_OUTPUT"
[ "$PROXY_IPV6" == "1" ] && {
$ip6t_m -I PSW2_OUTPUT -p udp $(dst $IPSET_PROXY_LAN6) -j PSW2_RULE
$ip6t_m -A PSW2_OUTPUT -p udp -d $FAKE_IP_6 -j PSW2_RULE
add_shunt_t_rule "${SHUNT_LIST6}" "$ip6t_m -A PSW2_OUTPUT -p udp" "-j PSW2_RULE" $UDP_REDIR_PORTS
add_port_rules "$ip6t_m -A PSW2_OUTPUT -p udp" $UDP_REDIR_PORTS "-j PSW2_RULE"
$ip6t_m -I PSW2 $(comment "${comment_l}") -p udp -i lo $(dst $IPSET_PROXY_LAN6) $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "${comment_l}") -p udp -i lo $(REDIRECT $REDIR_PORT TPROXY)
$ip6t_m -A PSW2 $(comment "${comment_l}") -p udp -i lo -j RETURN
insert_rule_before "$ip6t_m" "OUTPUT" "mwan3" "$(comment mangle-OUTPUT-PSW2) -p udp -j PSW2_OUTPUT"
@@ -936,6 +980,19 @@ add_firewall_rule() {
filter_direct_node_list > /dev/null 2>&1 &
[ -z "${IPT_N}" ] && {
for chain in "PSW2" "PSW2_OUTPUT"; do
$ipt_n -F $chain 2>/dev/null
$ipt_n -X $chain 2>/dev/null
done
}
[ -z "${IP6T_N}" ] && {
for chain in "PSW2" "PSW2_OUTPUT"; do
$ip6t_n -F $chain 2>/dev/null
$ip6t_n -X $chain 2>/dev/null
done
}
log_i18n 0 "%s firewall rules load complete!" "iptables"
}
@@ -1062,6 +1119,8 @@ start() {
stop() {
[ -z "$(command -v log_i18n)" ] && . /usr/share/passwall2/utils.sh
del_firewall_rule
destroy_ipset $IPSET_PROXY_LAN
destroy_ipset $IPSET_PROXY_LAN6
[ $(config_t_get global flush_set "0") = "1" ] && {
uci -q delete ${CONFIG}.@global[0].flush_set
uci -q commit ${CONFIG}
@@ -5,14 +5,16 @@ MY_PATH=$DIR/nftables.sh
UTILS_PATH=$DIR/utils.sh
NFTABLE_NAME="inet passwall2"
NFTSET_LOCAL="passwall2_local"
NFTSET_WAN="passwall2_wan"
NFTSET_PROXY_LAN="passwall2_proxy_lan"
NFTSET_LAN="passwall2_lan"
NFTSET_VPS="passwall2_vps"
NFTSET_WAN="passwall2_wan"
NFTSET_LOCAL6="passwall2_local6"
NFTSET_WAN6="passwall2_wan6"
NFTSET_PROXY_LAN6="passwall2_proxy_lan6"
NFTSET_LAN6="passwall2_lan6"
NFTSET_VPS6="passwall2_vps6"
NFTSET_WAN6="passwall2_wan6"
FWMARK="0x50535732"
@@ -425,6 +427,7 @@ load_acl() {
fi
[ "$accept_icmp" = "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ${_ipt_source} ip daddr @$NFTSET_PROXY_LAN $(REDIRECT) comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ${_ipt_source} ip daddr $FAKE_IP $(REDIRECT) comment \"$remarks\""
add_shunt_t_rule "${shunt_list4}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ${_ipt_source} ip daddr" "$(REDIRECT)" "$remarks"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ${_ipt_source} $(REDIRECT) comment \"$remarks\""
@@ -432,18 +435,21 @@ load_acl() {
}
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} ip6 daddr @$NFTSET_PROXY_LAN6 $(REDIRECT) comment \"$remarks\"" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} ip6 daddr $FAKE_IP_6 $(REDIRECT) comment \"$remarks\"" 2>/dev/null
add_shunt_t_rule "${shunt_list6}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} ip6 daddr" "$(REDIRECT)" "$remarks" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} $(REDIRECT) comment \"$remarks\"" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ${_ipt_source} return comment \"$remarks\"" 2>/dev/null
}
nft "insert rule $NFTABLE_NAME $nft_chain ip protocol tcp ${_ipt_source} ip daddr @$NFTSET_PROXY_LAN ${nft_j} comment \"$remarks\""
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp ${_ipt_source} ip daddr $FAKE_IP ${nft_j} comment \"$remarks\""
add_shunt_t_rule "${shunt_list4}" "nft add rule $NFTABLE_NAME $nft_chain ip protocol tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") ip daddr" "${nft_j}" "$remarks"
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") ${nft_j} comment \"$remarks\""
[ -n "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp ${_ipt_source} $(REDIRECT $redir_port TPROXY4) comment \"$remarks\""
[ "$PROXY_IPV6" == "1" ] && [ "$_ipv4" != "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} ip6 daddr @$NFTSET_PROXY_LAN6 counter jump PSW2_RULE comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} ip6 daddr $FAKE_IP_6 counter jump PSW2_RULE comment \"$remarks\""
add_shunt_t_rule "${shunt_list6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") ip6 daddr" "counter jump PSW2_RULE" "$remarks" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ${_ipt_source} $(factor $tcp_redir_ports "tcp dport") counter jump PSW2_RULE comment \"$remarks\"" 2>/dev/null
@@ -457,12 +463,14 @@ load_acl() {
[ "$udp_proxy_mode" != "disable" ] && [ -n "$redir_port" ] && {
msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "${node_remark}")(TPROXY:${redir_port})"
nft "insert rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} ip daddr @$NFTSET_PROXY_LAN counter jump PSW2_RULE comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} ip daddr $FAKE_IP counter jump PSW2_RULE comment \"$remarks\""
add_shunt_t_rule "${shunt_list4}" "nft add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") ip daddr" "counter jump PSW2_RULE" "$remarks"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") counter jump PSW2_RULE comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ${_ipt_source} $(REDIRECT $redir_port TPROXY4) comment \"$remarks\""
[ "$PROXY_IPV6" == "1" ] && [ "$_ipv4" != "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} ip6 daddr @$NFTSET_PROXY_LAN6 counter jump PSW2_RULE comment \"$remarks\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} ip6 daddr $FAKE_IP_6 counter jump PSW2_RULE comment \"$remarks\""
add_shunt_t_rule "${shunt_list6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") ip6 daddr" "counter jump PSW2_RULE" "$remarks" 2>/dev/null
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ${_ipt_source} $(factor $udp_redir_ports "udp dport") counter jump PSW2_RULE comment \"$remarks\"" 2>/dev/null
@@ -527,6 +535,7 @@ load_acl() {
fi
[ "$accept_icmp" = "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ip daddr @$NFTSET_PROXY_LAN $(REDIRECT) comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ip daddr $FAKE_IP $(REDIRECT) comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp ip daddr" "$(REDIRECT)" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT ip protocol icmp $(REDIRECT) comment \"${comment}\""
@@ -534,18 +543,21 @@ load_acl() {
}
[ "$accept_icmpv6" = "1" ] && [ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr @$NFTSET_PROXY_LAN6 $(REDIRECT) comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr $FAKE_IP_6 $(REDIRECT) comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 ip6 daddr" "$(REDIRECT)" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 $(REDIRECT) comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT meta l4proto icmpv6 return comment \"${comment}\""
}
nft "insert rule $NFTABLE_NAME $nft_chain ip protocol tcp ip daddr @$NFTSET_PROXY_LAN ${nft_j} comment \"${comment}\""
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp ip daddr $FAKE_IP ${nft_j} comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip daddr" "${nft_j}" "${comment}"
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ${nft_j} comment \"${comment}\""
[ -n "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment}\""
[ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ip6 daddr @$NFTSET_PROXY_LAN6 jump PSW2_RULE comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp ip6 daddr $FAKE_IP_6 jump PSW2_RULE comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip6 daddr" "${nft_j}" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") counter jump PSW2_RULE comment \"${comment}\""
@@ -558,12 +570,14 @@ load_acl() {
if [ "$UDP_PROXY_MODE" != "disable" ] && [ -n "$NODE" ]; then
msg2="${msg}$(i18n "Use the %s node [%s]" "UDP" "$(config_n_get $NODE remarks)")(TPROXY:${REDIR_PORT})"
nft "insert rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ip daddr @$NFTSET_PROXY_LAN counter jump PSW2_RULE comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp ip daddr $FAKE_IP counter jump PSW2_RULE comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") ip daddr" "counter jump PSW2_RULE" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment}\""
[ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ip6 daddr @$NFTSET_PROXY_LAN6 jump PSW2_RULE comment \"${comment}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp ip6 daddr $FAKE_IP_6 jump PSW2_RULE comment \"${comment}\""
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") ip6 daddr" "counter jump PSW2_RULE" "${comment}"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE comment \"${comment}\""
@@ -671,15 +685,18 @@ add_firewall_rule() {
gen_nft_tables
add_script_mwan3
mwan3_start
gen_nftset $NFTSET_WAN ipv4_addr 0 "-1"
gen_nftset $NFTSET_LOCAL ipv4_addr 0 "-1"
gen_nftset $NFTSET_PROXY_LAN ipv4_addr 0 "-1"
gen_nftset $NFTSET_LAN ipv4_addr 0 "-1" $(gen_lanlist)
gen_nftset $NFTSET_VPS ipv4_addr 0 "-1"
gen_nftset $NFTSET_WAN ipv4_addr 0 "-1"
gen_nftset $NFTSET_WAN6 ipv6_addr 0 "-1"
gen_nftset $NFTSET_LOCAL6 ipv6_addr 0 "-1"
gen_nftset $NFTSET_PROXY_LAN6 ipv6_addr 0 "-1"
gen_nftset $NFTSET_LAN6 ipv6_addr 0 "-1" $(gen_lanlist_6)
gen_nftset $NFTSET_VPS6 ipv6_addr 0 "-1"
gen_nftset $NFTSET_WAN6 ipv6_addr 0 "-1"
ip address show | grep -w "inet" | awk '{print $2}' | awk -F '/' '{print $1}' | insert_nftset $NFTSET_LOCAL "-1"
ip address show | grep -w "inet6" | awk '{print $2}' | awk -F '/' '{print $1}' | insert_nftset $NFTSET_LOCAL6 "-1"
@@ -711,6 +728,16 @@ add_firewall_rule() {
done
}
# Force proxy LAN IP CIDR
force_proxy_lan_ip=$(config_t_get global_forwarding force_proxy_lan_ip)
for ip in $force_proxy_lan_ip; do
if [[ "$ip" == *::* ]]; then
echo "$ip" | insert_nftset $NFTSET_PROXY_LAN6 0
else
echo "$ip" | insert_nftset $NFTSET_PROXY_LAN 0
fi
done
# Shunt rules IP list (import when use shunt node)
gen_shunt_list "${NODE}" SHUNT_LIST4 SHUNT_LIST6
@@ -900,6 +927,7 @@ add_firewall_rule() {
# Loading local router proxy TCP
if [ -n "$NODE" ] && [ "$TCP_LOCALHOST_PROXY" = "1" ]; then
[ "$accept_icmp" = "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo ip protocol icmp ip daddr @$NFTSET_PROXY_LAN counter redirect"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo ip protocol icmp ip daddr $FAKE_IP counter redirect"
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo ip protocol icmp ip daddr" "counter redirect"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo ip protocol icmp counter redirect"
@@ -907,6 +935,7 @@ add_firewall_rule() {
}
[ "$accept_icmpv6" = "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo meta l4proto icmpv6 ip6 daddr @$NFTSET_PROXY_LAN6 counter redirect"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo meta l4proto icmpv6 ip6 daddr $FAKE_IP_6 counter redirect"
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo meta l4proto icmpv6 ip6 daddr" "counter redirect"
nft "add rule $NFTABLE_NAME PSW2_ICMP_REDIRECT oif lo meta l4proto icmpv6 counter redirect"
@@ -921,21 +950,25 @@ add_firewall_rule() {
nft_j="$(REDIRECT $REDIR_PORT)"
fi
nft "insert rule $NFTABLE_NAME $nft_chain ip protocol tcp ip daddr @$NFTSET_PROXY_LAN ${nft_j}"
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp ip daddr $FAKE_IP ${nft_j}"
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip daddr" "${nft_j}"
nft "add rule $NFTABLE_NAME $nft_chain ip protocol tcp $(factor $TCP_REDIR_PORTS "tcp dport") ${nft_j}"
[ -z "${is_tproxy}" ] && nft "add rule $NFTABLE_NAME nat_output ip protocol tcp counter jump PSW2_OUTPUT_NAT"
[ -n "${is_tproxy}" ] && {
nft "insert rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp iif lo ip daddr @$NFTSET_PROXY_LAN $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp iif lo $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol tcp iif lo counter return comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME mangle_output ip protocol tcp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
}
[ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp ip6 daddr @$NFTSET_PROXY_LAN6 jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp ip6 daddr $FAKE_IP_6 jump PSW2_RULE"
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") ip6 daddr" "counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto tcp $(factor $TCP_REDIR_PORTS "tcp dport") counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"${comment_l}\""
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo ip6 daddr @$NFTSET_PROXY_LAN6 $(REDIRECT $REDIR_PORT TPROXY6) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo $(REDIRECT $REDIR_PORT TPROXY6) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto tcp iif lo counter return comment \"${comment_l}\""
}
@@ -949,18 +982,22 @@ add_firewall_rule() {
# Loading local router proxy UDP
if [ -n "$NODE" ] && [ "$UDP_LOCALHOST_PROXY" = "1" ]; then
nft "insert rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp ip daddr @$NFTSET_PROXY_LAN counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp ip daddr $FAKE_IP counter jump PSW2_RULE"
add_shunt_t_rule "${SHUNT_LIST4}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") ip daddr" "counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE ip protocol udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE"
nft "insert rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp iif lo ip daddr @$NFTSET_PROXY_LAN $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp iif lo $(REDIRECT $REDIR_PORT TPROXY4) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE ip protocol udp iif lo counter return comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME mangle_output ip protocol udp counter jump PSW2_OUTPUT_MANGLE comment \"PSW2_OUTPUT_MANGLE\""
[ "$PROXY_IPV6" == "1" ] && {
nft "insert rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp ip6 daddr @$NFTSET_PROXY_LAN6 jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp ip6 daddr $FAKE_IP_6 jump PSW2_RULE"
add_shunt_t_rule "${SHUNT_LIST6}" "nft add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") ip6 daddr" "counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_OUTPUT_MANGLE_V6 meta l4proto udp $(factor $UDP_REDIR_PORTS "udp dport") counter jump PSW2_RULE"
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo $(REDIRECT $REDIR_PORT TPROXY) comment \"${comment_l}\""
nft "insert rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo ip6 daddr @$NFTSET_PROXY_LAN6 $(REDIRECT $REDIR_PORT TPROXY6) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo $(REDIRECT $REDIR_PORT TPROXY6) comment \"${comment_l}\""
nft "add rule $NFTABLE_NAME PSW2_MANGLE_V6 meta l4proto udp iif lo counter return comment \"${comment_l}\""
}
@@ -1080,6 +1117,8 @@ start() {
stop() {
[ -z "$(command -v log_i18n)" ] && . /usr/share/passwall2/utils.sh
del_firewall_rule
destroy_nftset $NFTSET_PROXY_LAN
destroy_nftset $NFTSET_PROXY_LAN6
[ $(config_t_get global flush_set "0") = "1" ] && {
uci -q delete ${CONFIG}.@global[0].flush_set
uci -q commit ${CONFIG}
+2 -2
View File
@@ -12,13 +12,13 @@ PKG_MAINTAINER:=Tianling Shen <cnsztl@immortalwrt.org>
include $(INCLUDE_DIR)/package.mk
GEOIP_VER:=202603050223
GEOIP_VER:=202604050243
GEOIP_FILE:=geoip.dat.$(GEOIP_VER)
define Download/geoip
URL:=https://github.com/v2fly/geoip/releases/download/$(GEOIP_VER)/
URL_FILE:=geoip.dat
FILE:=$(GEOIP_FILE)
HASH:=c6c1d1be0d28defef55b153e87cb430f94fb480c8f523bf901c5e4ca18d58a00
HASH:=16dbd19ff8dddb69960f313a3b0c0623cae82dc9725687110c28740226d3b285
endef
GEOSITE_VER:=20260404050103
+1 -1
View File
@@ -3,7 +3,7 @@ module github.com/xtls/xray-core
go 1.26
require (
github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6
github.com/cloudflare/circl v1.6.3
github.com/ghodss/yaml v1.0.1-0.20220118164431-d8423dcdf344
github.com/golang/mock v1.7.0-rc.1
+2 -2
View File
@@ -1,7 +1,7 @@
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22 h1:00ziBGnLWQEcR9LThDwvxOznJJquJ9bYUdmBFnawLMU=
github.com/apernet/quic-go v0.59.1-0.20260217092621-db4786c77a22/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6 h1:cbF95uMsQwCwAzH2i8+2lNO2TReoELLuqeeMfyBjFbY=
github.com/apernet/quic-go v0.59.1-0.20260330051153-c402ee641eb6/go.mod h1:Npbg8qBtAZlsAB3FWmqwlVh5jtVG6a4DlYsOylUpvzA=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
@@ -36,6 +36,7 @@ import (
"github.com/xtls/xray-core/transport/internet/finalmask/xicmp"
"github.com/xtls/xray-core/transport/internet/httpupgrade"
"github.com/xtls/xray-core/transport/internet/hysteria"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
"github.com/xtls/xray-core/transport/internet/kcp"
"github.com/xtls/xray-core/transport/internet/reality"
"github.com/xtls/xray-core/transport/internet/splithttp"
@@ -630,6 +631,7 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
type QuicParamsConfig struct {
Congestion string `json:"congestion"`
Debug bool `json:"debug"`
BbrProfile string `json:"bbrProfile"`
BrutalUp Bandwidth `json:"brutalUp"`
BrutalDown Bandwidth `json:"brutalDown"`
UdpHop UdpHop `json:"udpHop"`
@@ -1894,6 +1896,16 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
config.Udpmasks = append(config.Udpmasks, serial.ToTypedMessage(u))
}
if c.FinalMask.QuicParams != nil {
profile := strings.ToLower(c.FinalMask.QuicParams.BbrProfile)
switch profile {
case "", string(bbr.ProfileConservative), string(bbr.ProfileStandard), string(bbr.ProfileAggressive):
if profile == "" {
profile = string(bbr.ProfileStandard)
}
default:
return nil, errors.New("unknown bbr profile")
}
up, err := c.FinalMask.QuicParams.BrutalUp.Bps()
if err != nil {
return nil, err
@@ -1965,6 +1977,7 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
config.QuicParams = &internet.QuicParams{
Congestion: c.FinalMask.QuicParams.Congestion,
BbrProfile: profile,
BrutalUp: up,
BrutalDown: down,
UdpHop: &internet.UdpHop{
+14 -8
View File
@@ -105,17 +105,23 @@ func (t *stackGVisor) Start() error {
// Use custom UDP packet handler, instead of strict gVisor forwarder, for FullCone NAT support
udpForwarder := newUdpConnectionHandler(t.handler.HandleConnection, t.writeRawUDPPacket)
ipStack.SetTransportProtocolHandler(udp.ProtocolNumber, func(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
data := pkt.Data().AsRange().ToSlice()
if len(data) == 0 {
return false
}
data := pkt.Clone().Data().AsRange().ToSlice()
// if len(data) == 0 {
// return false
// }
// source/destination of the packet we process as incoming, on gVisor side are Remote/Local
// in other terms, src is the side behind tun, dst is the side behind gVisor
// this function handle packets passing from the tun to the gVisor, therefore the src/dst assignement
src := net.UDPDestination(net.IPAddress(id.RemoteAddress.AsSlice()), net.Port(id.RemotePort))
dst := net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort))
return udpForwarder.HandlePacket(src, dst, data)
srcIP := net.IPAddress(id.RemoteAddress.AsSlice())
dstIP := net.IPAddress(id.LocalAddress.AsSlice())
if srcIP == nil || dstIP == nil {
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " > invalid ip address ", id.RemoteAddress.AsSlice(), " ", id.LocalAddress.AsSlice())
return true
}
src := net.UDPDestination(srcIP, net.Port(id.RemotePort))
dst := net.UDPDestination(dstIP, net.Port(id.LocalPort))
udpForwarder.HandlePacket(src, dst, data)
return true
})
t.stack = ipStack
+90 -38
View File
@@ -1,16 +1,24 @@
package tun
import (
"context"
"io"
"sync"
"time"
"github.com/xtls/xray-core/common/buf"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
)
type packet struct {
data []byte
dest *net.Destination
}
// sub-handler specifically for udp connections under main handler
type udpConnectionHandler struct {
sync.Mutex
sync.RWMutex
udpConns map[net.Destination]*udpConn
@@ -30,25 +38,44 @@ func newUdpConnectionHandler(handleConnection func(conn net.Conn, dest net.Desti
// HandlePacket handles UDP packets coming from tun, to forward to the dispatcher
// this custom handler support FullCone NAT of returning packets, binding connection only by the source addr:port
func (u *udpConnectionHandler) HandlePacket(src net.Destination, dst net.Destination, data []byte) bool {
u.Lock()
func (u *udpConnectionHandler) HandlePacket(src net.Destination, dst net.Destination, data []byte) {
u.RLock()
conn, found := u.udpConns[src]
if found {
select {
case conn.egress <- &packet{
data: data,
dest: &dst,
}:
default:
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full")
}
u.RUnlock()
return
}
u.RUnlock()
u.Lock()
defer u.Unlock()
conn, found = u.udpConns[src]
if !found {
egress := make(chan []byte, 16)
egress := make(chan *packet, 1024)
conn = &udpConn{handler: u, egress: egress, src: src, dst: dst}
u.udpConns[src] = conn
go u.handleConnection(conn, dst)
}
u.Unlock()
// send packet data to the egress channel, if it has buffer, or discard
select {
case conn.egress <- data:
case conn.egress <- &packet{
data: data,
dest: &dst,
}:
default:
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", conn.dst.NetAddr(), " > queue full")
}
return true
}
func (u *udpConnectionHandler) connectionFinished(src net.Destination) {
@@ -63,27 +90,64 @@ func (u *udpConnectionHandler) connectionFinished(src net.Destination) {
// udp connection abstraction
type udpConn struct {
net.Conn
buf.Writer
handler *udpConnectionHandler
egress chan []byte
egress chan *packet
src net.Destination
dst net.Destination
}
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
for {
e, ok := <-c.egress
if !ok {
return nil, io.EOF
}
b := buf.New()
_, err := b.Write(e.data)
if err != nil {
errors.LogDebugInner(context.Background(), err, "drop udp with size ", len(e.data), " to ", e.dest.NetAddr(), " original ", c.dst.NetAddr())
b.Release()
continue
}
b.UDP = e.dest
return buf.MultiBuffer{b}, nil
}
}
// Read packets from the connection
func (c *udpConn) Read(p []byte) (int, error) {
data, ok := <-c.egress
e, ok := <-c.egress
if !ok {
return 0, io.EOF
}
n := copy(p, data)
n := copy(p, e.data)
if n != len(e.data) {
return 0, io.ErrShortBuffer
}
return n, nil
}
func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
for i, b := range mb {
dst := c.dst
if b.UDP != nil {
dst = *b.UDP
}
err := c.handler.writePacket(b.Bytes(), dst, c.src)
if err != nil {
buf.ReleaseMulti(mb[i:])
return err
}
b.Release()
}
return nil
}
// Write returning packets back
func (c *udpConn) Write(p []byte) (int, error) {
// sending packets back mean sending payload with source/destination reversed
@@ -102,33 +166,21 @@ func (c *udpConn) Close() error {
}
func (c *udpConn) LocalAddr() net.Addr {
return &net.UDPAddr{IP: c.dst.Address.IP(), Port: int(c.dst.Port.Value())}
return c.dst.RawNetAddr()
}
func (c *udpConn) RemoteAddr() net.Addr {
return &net.UDPAddr{IP: c.src.Address.IP(), Port: int(c.src.Port.Value())}
return c.src.RawNetAddr()
}
// Write returning packets back
func (c *udpConn) WriteMultiBuffer(mb buf.MultiBuffer) error {
for _, b := range mb {
dst := c.dst
if b.UDP != nil {
dst = *b.UDP
}
// validate address family matches between buffer packet and the connection
if dst.Address.Family() != c.dst.Address.Family() {
continue
}
// sending packets back mean sending payload with source/destination reversed
err := c.handler.writePacket(b.Bytes(), dst, c.src)
if err != nil {
// udp doesn't guarantee delivery, so in any failure we just continue to the next packet
continue
}
}
func (c *udpConn) SetDeadline(t time.Time) error {
return nil
}
func (c *udpConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *udpConn) SetWriteDeadline(t time.Time) error {
return nil
}
+14 -2
View File
@@ -370,6 +370,18 @@ func (c *udpConnClient) ReadMultiBuffer() (buf.MultiBuffer, error) {
return buf.MultiBuffer{b}, nil
}
func (c *udpConnClient) Write(p []byte) (int, error) {
return c.Conn.(net.PacketConn).WriteTo(p, c.dest.RawNetAddr())
func (c *udpConnClient) WriteMultiBuffer(mb buf.MultiBuffer) error {
for i, b := range mb {
dst := c.dest
if b.UDP != nil {
dst = *b.UDP
}
_, err := c.Conn.(net.PacketConn).WriteTo(b.Bytes(), dst.RawNetAddr())
if err != nil {
buf.ReleaseMulti(mb[i:])
return err
}
b.Release()
}
return nil
}
+55 -14
View File
@@ -189,8 +189,14 @@ func createGVisorTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
// if len(data) == 0 {
// return false
// }
src := net.UDPDestination(net.IPAddress(id.RemoteAddress.AsSlice()), net.Port(id.RemotePort))
dst := net.UDPDestination(net.IPAddress(id.LocalAddress.AsSlice()), net.Port(id.LocalPort))
srcIP := net.IPAddress(id.RemoteAddress.AsSlice())
dstIP := net.IPAddress(id.LocalAddress.AsSlice())
if srcIP == nil || dstIP == nil {
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " > invalid ip address ", id.RemoteAddress.AsSlice(), " ", id.LocalAddress.AsSlice())
return true
}
src := net.UDPDestination(srcIP, net.Port(id.RemotePort))
dst := net.UDPDestination(dstIP, net.Port(id.LocalPort))
manager.feed(src, dst, data)
return true
})
@@ -212,8 +218,12 @@ func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte)
uc, ok := m.m[src.NetAddr()]
if ok {
select {
case uc.ch <- data:
case uc.queue <- &packet{
p: data,
dest: &dst,
}:
default:
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full")
}
m.mutex.RUnlock()
return
@@ -226,9 +236,9 @@ func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte)
uc, ok = m.m[src.NetAddr()]
if !ok {
uc = &udpConn{
ch: make(chan []byte, 1024),
src: src,
dst: dst,
queue: make(chan *packet, 1024),
src: src,
dst: dst,
}
uc.writeFunc = m.writeRawUDPPacket
uc.closeFunc = func() {
@@ -241,15 +251,19 @@ func (m *udpManager) feed(src net.Destination, dst net.Destination, data []byte)
}
select {
case uc.ch <- data:
case uc.queue <- &packet{
p: data,
dest: &dst,
}:
default:
errors.LogDebug(context.Background(), "drop udp with size ", len(data), " to ", dst.NetAddr(), " original ", uc.dst.NetAddr(), " > queue full")
}
}
func (m *udpManager) close(uc *udpConn) {
if !uc.closed {
uc.closed = true
close(uc.ch)
close(uc.queue)
delete(m.m, uc.src.NetAddr())
}
}
@@ -317,8 +331,13 @@ func (m *udpManager) writeRawUDPPacket(payload []byte, src net.Destination, dst
return nil
}
type packet struct {
p []byte
dest *net.Destination
}
type udpConn struct {
ch chan []byte
queue chan *packet
src net.Destination
dst net.Destination
writeFunc func(payload []byte, src net.Destination, dst net.Destination) error
@@ -326,13 +345,35 @@ type udpConn struct {
closed bool
}
func (c *udpConn) ReadMultiBuffer() (buf.MultiBuffer, error) {
for {
q, ok := <-c.queue
if !ok {
return nil, io.EOF
}
b := buf.New()
_, err := b.Write(q.p)
if err != nil {
errors.LogDebugInner(context.Background(), err, "drop udp with size ", len(q.p), " to ", q.dest.NetAddr(), " original ", c.dst.NetAddr())
b.Release()
continue
}
b.UDP = q.dest
return buf.MultiBuffer{b}, nil
}
}
func (c *udpConn) Read(p []byte) (int, error) {
b, ok := <-c.ch
q, ok := <-c.queue
if !ok {
return 0, io.EOF
}
n := copy(p, b)
if n != len(b) {
n := copy(p, q.p)
if n != len(q.p) {
return 0, io.ErrShortBuffer
}
return n, nil
@@ -368,11 +409,11 @@ func (c *udpConn) Close() error {
}
func (c *udpConn) LocalAddr() net.Addr {
return c.src.RawNetAddr() // fake
return c.dst.RawNetAddr()
}
func (c *udpConn) RemoteAddr() net.Addr {
return c.src.RawNetAddr() // src
return c.src.RawNetAddr()
}
func (c *udpConn) SetDeadline(t time.Time) error {
+40 -11
View File
@@ -10,18 +10,20 @@ import (
"net/netip"
"os"
"sync"
"syscall"
"golang.org/x/sys/unix"
"github.com/sagernet/sing/common/control"
"github.com/vishvananda/netlink"
"github.com/xtls/xray-core/common/errors"
wgtun "golang.zx2c4.com/wireguard/tun"
"github.com/xtls/xray-core/transport/internet"
"golang.zx2c4.com/wireguard/tun"
)
type deviceNet struct {
tunnel
dialer net.Dialer
dialer *net.Dialer
lc *net.ListenConfig
handle *netlink.Handle
linkAddrs []netlink.Addr
@@ -47,10 +49,23 @@ func allocateIPv6TableIndex() int {
}
func newDeviceNet(interfaceName string) *deviceNet {
var dialer net.Dialer
bindControl := control.BindToInterface(control.NewDefaultInterfaceFinder(), interfaceName, -1)
dialer.Control = control.Append(dialer.Control, bindControl)
return &deviceNet{dialer: dialer}
dialer := &net.Dialer{}
dialer.Control = func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
if err := syscall.BindToDevice(int(fd), interfaceName); err != nil {
errors.LogInfoInner(context.Background(), err, "failed to bind to device")
}
})
}
lc := &net.ListenConfig{}
lc.Control = func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
if err := syscall.BindToDevice(int(fd), interfaceName); err != nil {
errors.LogInfoInner(context.Background(), err, "failed to bind to device")
}
})
}
return &deviceNet{dialer: dialer, lc: lc}
}
func (d *deviceNet) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrPort) (
@@ -60,9 +75,23 @@ func (d *deviceNet) DialContextTCPAddrPort(ctx context.Context, addr netip.AddrP
}
func (d *deviceNet) DialUDPAddrPort(laddr, raddr netip.AddrPort) (net.Conn, error) {
dialer := d.dialer
dialer.LocalAddr = &net.UDPAddr{IP: laddr.Addr().AsSlice(), Port: int(laddr.Port())}
return dialer.DialContext(context.Background(), "udp", raddr.String())
var conn net.PacketConn
var err error
if raddr.Addr().Is4() {
conn, err = d.lc.ListenPacket(context.Background(), "udp4", ":0")
} else {
conn, err = d.lc.ListenPacket(context.Background(), "udp6", ":0")
}
if err != nil {
return nil, err
}
return &internet.PacketConnWrapper{
PacketConn: conn,
Dest: &net.UDPAddr{
IP: raddr.Addr().AsSlice(),
Port: int(raddr.Port()),
},
}, nil
}
func (d *deviceNet) Close() (err error) {
@@ -134,7 +163,7 @@ func createKernelTun(localAddresses []netip.Addr, mtu int, handler promiscuousMo
}
n := CalculateInterfaceName("wg")
wgt, err := wgtun.CreateTUN(n, mtu)
wgt, err := tun.CreateTUN(n, mtu)
if err != nil {
return nil, err
}
+35 -25
View File
@@ -445,17 +445,18 @@ func (x *UdpHop) GetIntervalMax() int64 {
type QuicParams struct {
state protoimpl.MessageState `protogen:"open.v1"`
Congestion string `protobuf:"bytes,1,opt,name=congestion,proto3" json:"congestion,omitempty"`
BrutalUp uint64 `protobuf:"varint,2,opt,name=brutal_up,json=brutalUp,proto3" json:"brutal_up,omitempty"`
BrutalDown uint64 `protobuf:"varint,3,opt,name=brutal_down,json=brutalDown,proto3" json:"brutal_down,omitempty"`
UdpHop *UdpHop `protobuf:"bytes,4,opt,name=udp_hop,json=udpHop,proto3" json:"udp_hop,omitempty"`
InitStreamReceiveWindow uint64 `protobuf:"varint,5,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
MaxStreamReceiveWindow uint64 `protobuf:"varint,6,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
InitConnReceiveWindow uint64 `protobuf:"varint,7,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
MaxConnReceiveWindow uint64 `protobuf:"varint,8,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
MaxIdleTimeout int64 `protobuf:"varint,9,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
KeepAlivePeriod int64 `protobuf:"varint,10,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
DisablePathMtuDiscovery bool `protobuf:"varint,11,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
MaxIncomingStreams int64 `protobuf:"varint,12,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
BbrProfile string `protobuf:"bytes,2,opt,name=bbr_profile,json=bbrProfile,proto3" json:"bbr_profile,omitempty"`
BrutalUp uint64 `protobuf:"varint,3,opt,name=brutal_up,json=brutalUp,proto3" json:"brutal_up,omitempty"`
BrutalDown uint64 `protobuf:"varint,4,opt,name=brutal_down,json=brutalDown,proto3" json:"brutal_down,omitempty"`
UdpHop *UdpHop `protobuf:"bytes,5,opt,name=udp_hop,json=udpHop,proto3" json:"udp_hop,omitempty"`
InitStreamReceiveWindow uint64 `protobuf:"varint,6,opt,name=init_stream_receive_window,json=initStreamReceiveWindow,proto3" json:"init_stream_receive_window,omitempty"`
MaxStreamReceiveWindow uint64 `protobuf:"varint,7,opt,name=max_stream_receive_window,json=maxStreamReceiveWindow,proto3" json:"max_stream_receive_window,omitempty"`
InitConnReceiveWindow uint64 `protobuf:"varint,8,opt,name=init_conn_receive_window,json=initConnReceiveWindow,proto3" json:"init_conn_receive_window,omitempty"`
MaxConnReceiveWindow uint64 `protobuf:"varint,9,opt,name=max_conn_receive_window,json=maxConnReceiveWindow,proto3" json:"max_conn_receive_window,omitempty"`
MaxIdleTimeout int64 `protobuf:"varint,10,opt,name=max_idle_timeout,json=maxIdleTimeout,proto3" json:"max_idle_timeout,omitempty"`
KeepAlivePeriod int64 `protobuf:"varint,11,opt,name=keep_alive_period,json=keepAlivePeriod,proto3" json:"keep_alive_period,omitempty"`
DisablePathMtuDiscovery bool `protobuf:"varint,12,opt,name=disable_path_mtu_discovery,json=disablePathMtuDiscovery,proto3" json:"disable_path_mtu_discovery,omitempty"`
MaxIncomingStreams int64 `protobuf:"varint,13,opt,name=max_incoming_streams,json=maxIncomingStreams,proto3" json:"max_incoming_streams,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -497,6 +498,13 @@ func (x *QuicParams) GetCongestion() string {
return ""
}
func (x *QuicParams) GetBbrProfile() string {
if x != nil {
return x.BbrProfile
}
return ""
}
func (x *QuicParams) GetBrutalUp() uint64 {
if x != nil {
return x.BrutalUp
@@ -1028,25 +1036,27 @@ const file_transport_internet_config_proto_rawDesc = "" +
"\x06UdpHop\x12\x14\n" +
"\x05ports\x18\x01 \x03(\rR\x05ports\x12!\n" +
"\finterval_min\x18\x02 \x01(\x03R\vintervalMin\x12!\n" +
"\finterval_max\x18\x03 \x01(\x03R\vintervalMax\"\xd1\x04\n" +
"\finterval_max\x18\x03 \x01(\x03R\vintervalMax\"\xf2\x04\n" +
"\n" +
"QuicParams\x12\x1e\n" +
"\n" +
"congestion\x18\x01 \x01(\tR\n" +
"congestion\x12\x1b\n" +
"\tbrutal_up\x18\x02 \x01(\x04R\bbrutalUp\x12\x1f\n" +
"\vbrutal_down\x18\x03 \x01(\x04R\n" +
"congestion\x12\x1f\n" +
"\vbbr_profile\x18\x02 \x01(\tR\n" +
"bbrProfile\x12\x1b\n" +
"\tbrutal_up\x18\x03 \x01(\x04R\bbrutalUp\x12\x1f\n" +
"\vbrutal_down\x18\x04 \x01(\x04R\n" +
"brutalDown\x128\n" +
"\audp_hop\x18\x04 \x01(\v2\x1f.xray.transport.internet.UdpHopR\x06udpHop\x12;\n" +
"\x1ainit_stream_receive_window\x18\x05 \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
"\x19max_stream_receive_window\x18\x06 \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
"\x18init_conn_receive_window\x18\a \x01(\x04R\x15initConnReceiveWindow\x125\n" +
"\x17max_conn_receive_window\x18\b \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
"\x10max_idle_timeout\x18\t \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
"\x11keep_alive_period\x18\n" +
" \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
"\x1adisable_path_mtu_discovery\x18\v \x01(\bR\x17disablePathMtuDiscovery\x120\n" +
"\x14max_incoming_streams\x18\f \x01(\x03R\x12maxIncomingStreams\"Q\n" +
"\audp_hop\x18\x05 \x01(\v2\x1f.xray.transport.internet.UdpHopR\x06udpHop\x12;\n" +
"\x1ainit_stream_receive_window\x18\x06 \x01(\x04R\x17initStreamReceiveWindow\x129\n" +
"\x19max_stream_receive_window\x18\a \x01(\x04R\x16maxStreamReceiveWindow\x127\n" +
"\x18init_conn_receive_window\x18\b \x01(\x04R\x15initConnReceiveWindow\x125\n" +
"\x17max_conn_receive_window\x18\t \x01(\x04R\x14maxConnReceiveWindow\x12(\n" +
"\x10max_idle_timeout\x18\n" +
" \x01(\x03R\x0emaxIdleTimeout\x12*\n" +
"\x11keep_alive_period\x18\v \x01(\x03R\x0fkeepAlivePeriod\x12;\n" +
"\x1adisable_path_mtu_discovery\x18\f \x01(\bR\x17disablePathMtuDiscovery\x120\n" +
"\x14max_incoming_streams\x18\r \x01(\x03R\x12maxIncomingStreams\"Q\n" +
"\vProxyConfig\x12\x10\n" +
"\x03tag\x18\x01 \x01(\tR\x03tag\x120\n" +
"\x13transportLayerProxy\x18\x02 \x01(\bR\x13transportLayerProxy\"\x93\x01\n" +
+12 -11
View File
@@ -72,17 +72,18 @@ message UdpHop {
message QuicParams {
string congestion = 1;
uint64 brutal_up = 2;
uint64 brutal_down = 3;
UdpHop udp_hop = 4;
uint64 init_stream_receive_window = 5;
uint64 max_stream_receive_window = 6;
uint64 init_conn_receive_window = 7;
uint64 max_conn_receive_window = 8;
int64 max_idle_timeout = 9;
int64 keep_alive_period = 10;
bool disable_path_mtu_discovery = 11;
int64 max_incoming_streams = 12;
string bbr_profile = 2;
uint64 brutal_up = 3;
uint64 brutal_down = 4;
UdpHop udp_hop = 5;
uint64 init_stream_receive_window = 6;
uint64 max_stream_receive_window = 7;
uint64 init_conn_receive_window = 8;
uint64 max_conn_receive_window = 9;
int64 max_idle_timeout = 10;
int64 keep_alive_period = 11;
bool disable_path_mtu_discovery = 12;
int64 max_incoming_streams = 13;
}
message ProxyConfig {
@@ -6,6 +6,7 @@ import (
"net"
"os"
"strconv"
"strings"
"time"
"github.com/apernet/quic-go/congestion"
@@ -28,16 +29,13 @@ const (
invalidPacketNumber = -1
initialCongestionWindowPackets = 32
minCongestionWindowPackets = 4
// Constants based on TCP defaults.
// The minimum CWND to ensure delayed acks don't reduce bandwidth measurements.
// Does not inflate the pacing rate.
defaultMinimumCongestionWindow = 4 * congestion.ByteCount(congestion.InitialPacketSize)
// The gain used for the STARTUP, equal to 2/ln(2).
defaultHighGain = 2.885
// The newly derived gain for STARTUP, equal to 4 * ln(2)
derivedHighGain = 2.773
// The newly derived CWND gain for STARTUP, 2.
derivedHighCWNDGain = 2.0
@@ -66,7 +64,6 @@ const (
// Flag.
defaultStartupFullLossCount = 8
quicBbr2DefaultLossThreshold = 0.02
maxBbrBurstPackets = 10
)
type bbrMode int
@@ -97,6 +94,76 @@ const (
bbrRecoveryStateGrowth
)
type Profile string
const (
ProfileConservative Profile = "conservative"
ProfileStandard Profile = "standard"
ProfileAggressive Profile = "aggressive"
)
type profileConfig struct {
highGain float64
highCwndGain float64
congestionWindowGainConstant float64
numStartupRtts int64
drainToTarget bool
detectOvershooting bool
bytesLostMultiplier uint8
enableAckAggregationStartup bool
expireAckAggregationStartup bool
enableOverestimateAvoidance bool
reduceExtraAckedOnBandwidthIncrease bool
}
func ParseProfile(profile string) (Profile, error) {
switch normalized := strings.ToLower(profile); normalized {
case "", string(ProfileStandard):
return ProfileStandard, nil
case string(ProfileConservative):
return ProfileConservative, nil
case string(ProfileAggressive):
return ProfileAggressive, nil
default:
return "", fmt.Errorf("unsupported BBR profile %q", profile)
}
}
func configForProfile(profile Profile) profileConfig {
switch profile {
case ProfileConservative:
return profileConfig{
highGain: 2.25,
highCwndGain: 1.75,
congestionWindowGainConstant: 1.75,
numStartupRtts: 2,
drainToTarget: true,
detectOvershooting: true,
bytesLostMultiplier: 1,
enableOverestimateAvoidance: true,
reduceExtraAckedOnBandwidthIncrease: true,
}
case ProfileAggressive:
return profileConfig{
highGain: 3.0,
highCwndGain: 2.25,
congestionWindowGainConstant: 2.5,
numStartupRtts: 4,
bytesLostMultiplier: 2,
enableAckAggregationStartup: true,
expireAckAggregationStartup: true,
}
default:
return profileConfig{
highGain: defaultHighGain,
highCwndGain: derivedHighCWNDGain,
congestionWindowGainConstant: 2.0,
numStartupRtts: roundTripsWithoutGrowthBeforeExitingStartup,
bytesLostMultiplier: 2,
}
}
}
type bbrSender struct {
rttStats congestion.RTTStatsProvider
clock Clock
@@ -145,6 +212,9 @@ type bbrSender struct {
// The smallest value the |congestion_window_| can achieve.
minCongestionWindow congestion.ByteCount
// The BBR profile used by the sender.
profile Profile
// The pacing gain applied during the STARTUP phase.
highGain float64
@@ -251,12 +321,14 @@ var _ congestion.CongestionControl = &bbrSender{}
func NewBbrSender(
clock Clock,
initialMaxDatagramSize congestion.ByteCount,
profile Profile,
) *bbrSender {
return newBbrSender(
clock,
initialMaxDatagramSize,
initialCongestionWindowPackets*initialMaxDatagramSize,
congestion.MaxCongestionWindowPackets*initialMaxDatagramSize,
profile,
)
}
@@ -265,6 +337,7 @@ func newBbrSender(
initialMaxDatagramSize,
initialCongestionWindow,
initialMaxCongestionWindow congestion.ByteCount,
profile Profile,
) *bbrSender {
debug, _ := strconv.ParseBool(os.Getenv(debugEnv))
b := &bbrSender{
@@ -277,9 +350,10 @@ func newBbrSender(
congestionWindow: initialCongestionWindow,
initialCongestionWindow: initialCongestionWindow,
maxCongestionWindow: initialMaxCongestionWindow,
minCongestionWindow: defaultMinimumCongestionWindow,
minCongestionWindow: minCongestionWindowForMaxDatagramSize(initialMaxDatagramSize),
profile: ProfileStandard,
highGain: defaultHighGain,
highCwndGain: defaultHighGain,
highCwndGain: derivedHighCWNDGain,
drainGain: 1.0 / defaultHighGain,
pacingGain: 1.0,
congestionWindowGain: 1.0,
@@ -295,20 +369,63 @@ func newBbrSender(
debug: debug,
}
b.pacer = common.NewPacer(b.bandwidthForPacer)
/*
if b.tracer != nil {
b.lastState = logging.CongestionStateStartup
b.tracer.UpdatedCongestionState(logging.CongestionStateStartup)
}
*/
b.applyProfile(profile)
if b.debug {
b.debugPrint("Profile: %s", b.profile)
}
b.enterStartupMode(b.clock.Now())
b.setHighCwndGain(derivedHighCWNDGain)
return b
}
func (b *bbrSender) applyProfile(profile Profile) {
if profile == "" {
profile = ProfileStandard
}
cfg := configForProfile(profile)
b.profile = profile
b.highGain = cfg.highGain
b.highCwndGain = cfg.highCwndGain
b.drainGain = 1.0 / cfg.highGain
b.congestionWindowGainConstant = cfg.congestionWindowGainConstant
b.numStartupRtts = cfg.numStartupRtts
b.drainToTarget = cfg.drainToTarget
b.detectOvershooting = cfg.detectOvershooting
b.bytesLostMultiplierWhileDetectingOvershooting = cfg.bytesLostMultiplier
b.enableAckAggregationDuringStartup = cfg.enableAckAggregationStartup
b.expireAckAggregationInStartup = cfg.expireAckAggregationStartup
if cfg.enableOverestimateAvoidance {
b.sampler.EnableOverestimateAvoidance()
}
b.sampler.SetReduceExtraAckedOnBandwidthIncrease(cfg.reduceExtraAckedOnBandwidthIncrease)
}
func minCongestionWindowForMaxDatagramSize(maxDatagramSize congestion.ByteCount) congestion.ByteCount {
return minCongestionWindowPackets * maxDatagramSize
}
func scaleByteWindowForDatagramSize(window, oldMaxDatagramSize, newMaxDatagramSize congestion.ByteCount) congestion.ByteCount {
if oldMaxDatagramSize == newMaxDatagramSize {
return window
}
return congestion.ByteCount(uint64(window) * uint64(newMaxDatagramSize) / uint64(oldMaxDatagramSize))
}
func (b *bbrSender) rescalePacketSizedWindows(maxDatagramSize congestion.ByteCount) {
oldMaxDatagramSize := b.maxDatagramSize
b.maxDatagramSize = maxDatagramSize
b.initialCongestionWindow = scaleByteWindowForDatagramSize(b.initialCongestionWindow, oldMaxDatagramSize, maxDatagramSize)
b.maxCongestionWindow = scaleByteWindowForDatagramSize(b.maxCongestionWindow, oldMaxDatagramSize, maxDatagramSize)
b.minCongestionWindow = minCongestionWindowForMaxDatagramSize(maxDatagramSize)
b.cwndToCalculateMinPacingRate = scaleByteWindowForDatagramSize(b.cwndToCalculateMinPacingRate, oldMaxDatagramSize, maxDatagramSize)
b.maxCongestionWindowWithNetworkParametersAdjusted = scaleByteWindowForDatagramSize(
b.maxCongestionWindowWithNetworkParametersAdjusted,
oldMaxDatagramSize,
maxDatagramSize,
)
}
func (b *bbrSender) SetRTTStatsProvider(provider congestion.RTTStatsProvider) {
b.rttStats = provider
}
@@ -370,14 +487,24 @@ func (b *bbrSender) OnRetransmissionTimeout(packetsRetransmitted bool) {
// SetMaxDatagramSize implements the SendAlgorithm interface.
func (b *bbrSender) SetMaxDatagramSize(s congestion.ByteCount) {
if b.debug {
b.debugPrint("Max Datagram Size: %d", s)
}
if s < b.maxDatagramSize {
panic(fmt.Sprintf("congestion BUG: decreased max datagram size from %d to %d", b.maxDatagramSize, s))
}
cwndIsMinCwnd := b.congestionWindow == b.minCongestionWindow
b.maxDatagramSize = s
if cwndIsMinCwnd {
oldMinCongestionWindow := b.minCongestionWindow
oldInitialCongestionWindow := b.initialCongestionWindow
b.rescalePacketSizedWindows(s)
switch b.congestionWindow {
case oldMinCongestionWindow:
b.congestionWindow = b.minCongestionWindow
case oldInitialCongestionWindow:
b.congestionWindow = b.initialCongestionWindow
default:
b.congestionWindow = min(b.maxCongestionWindow, max(b.congestionWindow, b.minCongestionWindow))
}
b.recoveryWindow = min(b.maxCongestionWindow, max(b.recoveryWindow, b.minCongestionWindow))
b.pacer.SetMaxDatagramSize(s)
}
@@ -519,22 +646,6 @@ func (b *bbrSender) PacingRate() Bandwidth {
return b.pacingRate
}
func (b *bbrSender) hasGoodBandwidthEstimateForResumption() bool {
return b.hasNonAppLimitedSample()
}
func (b *bbrSender) hasNonAppLimitedSample() bool {
return b.hasNoAppLimitedSample
}
// Sets the pacing gain used in STARTUP. Must be greater than 1.
func (b *bbrSender) setHighGain(highGain float64) {
b.highGain = highGain
if b.mode == bbrModeStartup {
b.pacingGain = highGain
}
}
// Sets the CWND gain used in STARTUP. Must be greater than 1.
func (b *bbrSender) setHighCwndGain(highCwndGain float64) {
b.highCwndGain = highCwndGain
@@ -543,11 +654,6 @@ func (b *bbrSender) setHighCwndGain(highCwndGain float64) {
}
}
// Sets the gain used in DRAIN. Must be less than 1.
func (b *bbrSender) setDrainGain(drainGain float64) {
b.drainGain = drainGain
}
// Get the current bandwidth estimate. Note that Bandwidth is in bits per second.
func (b *bbrSender) bandwidthEstimate() Bandwidth {
return b.maxBandwidth.GetBest()
@@ -0,0 +1,130 @@
package bbr
import (
"testing"
"github.com/apernet/quic-go/congestion"
"github.com/stretchr/testify/require"
)
func TestSetMaxDatagramSizeRescalesPacketSizedWindows(t *testing.T) {
const oldMaxDatagramSize = congestion.ByteCount(1000)
const newMaxDatagramSize = congestion.ByteCount(1400)
const initialCongestionWindowPackets = congestion.ByteCount(20)
const maxCongestionWindowPackets = congestion.ByteCount(80)
b := newBbrSender(
DefaultClock{},
oldMaxDatagramSize,
initialCongestionWindowPackets*oldMaxDatagramSize,
maxCongestionWindowPackets*oldMaxDatagramSize,
ProfileStandard,
)
b.congestionWindow = b.initialCongestionWindow
b.SetMaxDatagramSize(newMaxDatagramSize)
require.Equal(t, initialCongestionWindowPackets*newMaxDatagramSize, b.initialCongestionWindow)
require.Equal(t, maxCongestionWindowPackets*newMaxDatagramSize, b.maxCongestionWindow)
require.Equal(t, minCongestionWindowPackets*newMaxDatagramSize, b.minCongestionWindow)
require.Equal(t, initialCongestionWindowPackets*newMaxDatagramSize, b.congestionWindow)
}
func TestSetMaxDatagramSizeClampsCongestionWindow(t *testing.T) {
const oldMaxDatagramSize = congestion.ByteCount(1000)
const newMaxDatagramSize = congestion.ByteCount(1400)
b := NewBbrSender(DefaultClock{}, oldMaxDatagramSize, ProfileStandard)
b.congestionWindow = b.minCongestionWindow + oldMaxDatagramSize
b.recoveryWindow = b.minCongestionWindow + oldMaxDatagramSize
b.SetMaxDatagramSize(newMaxDatagramSize)
require.Equal(t, b.minCongestionWindow, b.congestionWindow)
require.Equal(t, b.minCongestionWindow, b.recoveryWindow)
}
func TestNewBbrSenderAppliesProfiles(t *testing.T) {
testCases := []struct {
name string
profile Profile
highGain float64
highCwndGain float64
congestionWindowGainConstant float64
numStartupRtts int64
drainToTarget bool
detectOvershooting bool
bytesLostMultiplier uint8
enableAckAggregationDuringStartup bool
expireAckAggregationInStartup bool
enableOverestimateAvoidance bool
reduceExtraAckedOnBandwidthIncrease bool
}{
{
name: "standard",
profile: ProfileStandard,
highGain: defaultHighGain,
highCwndGain: derivedHighCWNDGain,
congestionWindowGainConstant: 2.0,
numStartupRtts: roundTripsWithoutGrowthBeforeExitingStartup,
bytesLostMultiplier: 2,
},
{
name: "conservative",
profile: ProfileConservative,
highGain: 2.25,
highCwndGain: 1.75,
congestionWindowGainConstant: 1.75,
numStartupRtts: 2,
drainToTarget: true,
detectOvershooting: true,
bytesLostMultiplier: 1,
enableOverestimateAvoidance: true,
reduceExtraAckedOnBandwidthIncrease: true,
},
{
name: "aggressive",
profile: ProfileAggressive,
highGain: 3.0,
highCwndGain: 2.25,
congestionWindowGainConstant: 2.5,
numStartupRtts: 4,
bytesLostMultiplier: 2,
enableAckAggregationDuringStartup: true,
expireAckAggregationInStartup: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b := NewBbrSender(DefaultClock{}, congestion.InitialPacketSize, tc.profile)
require.Equal(t, tc.profile, b.profile)
require.Equal(t, tc.highGain, b.highGain)
require.Equal(t, tc.highCwndGain, b.highCwndGain)
require.Equal(t, tc.congestionWindowGainConstant, b.congestionWindowGainConstant)
require.Equal(t, tc.numStartupRtts, b.numStartupRtts)
require.Equal(t, tc.drainToTarget, b.drainToTarget)
require.Equal(t, tc.detectOvershooting, b.detectOvershooting)
require.Equal(t, tc.bytesLostMultiplier, b.bytesLostMultiplierWhileDetectingOvershooting)
require.Equal(t, tc.enableAckAggregationDuringStartup, b.enableAckAggregationDuringStartup)
require.Equal(t, tc.expireAckAggregationInStartup, b.expireAckAggregationInStartup)
require.Equal(t, tc.enableOverestimateAvoidance, b.sampler.IsOverestimateAvoidanceEnabled())
require.Equal(t, tc.reduceExtraAckedOnBandwidthIncrease, b.sampler.maxAckHeightTracker.reduceExtraAckedOnBandwidthIncrease)
require.Equal(t, b.highGain, b.pacingGain)
require.Equal(t, b.highCwndGain, b.congestionWindowGain)
})
}
}
func TestParseProfile(t *testing.T) {
profile, err := ParseProfile("")
require.NoError(t, err)
require.Equal(t, ProfileStandard, profile)
profile, err = ParseProfile("Aggressive")
require.NoError(t, err)
require.Equal(t, ProfileAggressive, profile)
_, err = ParseProfile("turbo")
require.EqualError(t, err, `unsupported BBR profile "turbo"`)
}
@@ -1,18 +1,55 @@
package congestion
import (
"fmt"
"strings"
"github.com/apernet/quic-go"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/brutal"
)
func UseBBR(conn *quic.Conn) {
const (
TypeBBR = "bbr"
TypeReno = "reno"
)
func NormalizeType(congestionType string) (string, error) {
switch normalized := strings.ToLower(congestionType); normalized {
case "", TypeBBR:
return TypeBBR, nil
case TypeReno:
return TypeReno, nil
default:
return "", fmt.Errorf("unsupported congestion type %q", congestionType)
}
}
func NormalizeBBRProfile(profile string) (string, error) {
normalized, err := bbr.ParseProfile(profile)
if err != nil {
return "", err
}
return string(normalized), nil
}
func UseBBR(conn *quic.Conn, profile bbr.Profile) {
conn.SetCongestionControl(bbr.NewBbrSender(
bbr.DefaultClock{},
bbr.GetInitialPacketSize(conn.RemoteAddr()),
profile,
))
}
func UseBrutal(conn *quic.Conn, tx uint64) {
conn.SetCongestionControl(brutal.NewBrutalSender(tx))
}
func UseConfigured(conn *quic.Conn, congestionType, bbrProfile string) {
switch congestionType {
case TypeReno:
return
default:
UseBBR(conn, bbr.Profile(bbrProfile))
}
}
@@ -23,6 +23,7 @@ import (
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/finalmask"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
"github.com/xtls/xray-core/transport/internet/stat"
"github.com/xtls/xray-core/transport/internet/tls"
@@ -157,10 +158,10 @@ func (c *client) dial() error {
quicParams := c.quicParams
if quicParams == nil {
quicParams = &internet.QuicParams{}
}
if quicParams.UdpHop == nil {
quicParams.UdpHop = &internet.UdpHop{}
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
}
}
var index int
@@ -298,12 +299,12 @@ func (c *client) dial() error {
case "reno":
errors.LogDebug(c.ctx, "congestion reno")
case "bbr":
errors.LogDebug(c.ctx, "congestion bbr")
congestion.UseBBR(quicConn)
errors.LogDebug(c.ctx, "congestion bbr ", quicParams.BbrProfile)
congestion.UseBBR(quicConn, bbr.Profile(quicParams.BbrProfile))
case "brutal", "":
if serverAuto == "auto" || quicParams.BrutalUp == 0 || serverDown == 0 {
errors.LogDebug(c.ctx, "congestion bbr")
congestion.UseBBR(quicConn)
errors.LogDebug(c.ctx, "congestion bbr ", quicParams.BbrProfile)
congestion.UseBBR(quicConn, bbr.Profile(quicParams.BbrProfile))
} else {
errors.LogDebug(c.ctx, "congestion brutal bytes per second ", min(quicParams.BrutalUp, serverDown))
congestion.UseBrutal(quicConn, min(quicParams.BrutalUp, serverDown))
+9 -5
View File
@@ -23,6 +23,7 @@ import (
hyCtx "github.com/xtls/xray-core/proxy/hysteria/ctx"
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
"github.com/xtls/xray-core/transport/internet/tls"
)
@@ -188,12 +189,12 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case "reno":
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion reno")
case "bbr":
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr")
congestion.UseBBR(h.conn)
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr ", h.quicParams.BbrProfile)
congestion.UseBBR(h.conn, bbr.Profile(h.quicParams.BbrProfile))
case "brutal", "":
if h.quicParams.BrutalUp == 0 || clientDown == 0 {
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr")
congestion.UseBBR(h.conn)
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion bbr ", h.quicParams.BbrProfile)
congestion.UseBBR(h.conn, bbr.Profile(h.quicParams.BbrProfile))
} else {
errors.LogDebug(context.Background(), h.conn.RemoteAddr(), " ", "congestion brutal bytes per second ", min(h.quicParams.BrutalUp, clientDown))
congestion.UseBrutal(h.conn, min(h.quicParams.BrutalUp, clientDown))
@@ -389,7 +390,10 @@ func Listen(ctx context.Context, address net.Address, port net.Port, streamSetti
quicParams := streamSettings.QuicParams
if quicParams == nil {
quicParams = &internet.QuicParams{}
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
}
}
quicConfig := &quic.Config{
@@ -26,6 +26,7 @@ import (
"github.com/xtls/xray-core/transport/internet"
"github.com/xtls/xray-core/transport/internet/browser_dialer"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion"
"github.com/xtls/xray-core/transport/internet/hysteria/congestion/bbr"
"github.com/xtls/xray-core/transport/internet/hysteria/udphop"
"github.com/xtls/xray-core/transport/internet/reality"
"github.com/xtls/xray-core/transport/internet/stat"
@@ -158,10 +159,10 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
if httpVersion == "3" {
quicParams := streamSettings.QuicParams
if quicParams == nil {
quicParams = &internet.QuicParams{}
}
if quicParams.UdpHop == nil {
quicParams.UdpHop = &internet.UdpHop{}
quicParams = &internet.QuicParams{
BbrProfile: string(bbr.ProfileStandard),
UdpHop: &internet.UdpHop{},
}
}
quicConfig := &quic.Config{
@@ -292,8 +293,8 @@ func createHTTPClient(dest net.Destination, streamSettings *internet.MemoryStrea
case "reno":
errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion reno")
default:
errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion bbr")
congestion.UseBBR(quicConn)
errors.LogDebug(context.Background(), quicConn.RemoteAddr(), " ", "congestion bbr ", quicParams.BbrProfile)
congestion.UseBBR(quicConn, bbr.Profile(quicParams.BbrProfile))
}
return quicConn, nil

Some files were not shown because too many files have changed in this diff Show More