mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
Update On Sun Apr 5 20:57:47 CEST 2026
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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"
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
@@ -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
@@ -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{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user