使用热重载
This commit is contained in:
22
api.md
22
api.md
@@ -331,6 +331,26 @@ Same endpoints as V1 but under `/api/v2/passport/` prefix.
|
|||||||
- **Purpose**: Get server configuration
|
- **Purpose**: Get server configuration
|
||||||
- **Returns**: Server configuration
|
- **Returns**: Server configuration
|
||||||
- **Data**: Server settings and parameters
|
- **Data**: Server settings and parameters
|
||||||
|
- **Example Response**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"protocol": "vless",
|
||||||
|
"listen_ip": "0.0.0.0",
|
||||||
|
"server_port": 18443,
|
||||||
|
"network": "tcp",
|
||||||
|
"tls": 2,
|
||||||
|
"server_name": "git.example.com",
|
||||||
|
"dest": "www.cloudflare.com:443",
|
||||||
|
"private_key": "YOUR_REALITY_PRIVATE_KEY",
|
||||||
|
"short_id": "01234567",
|
||||||
|
"accept_proxy_protocol": true,
|
||||||
|
"base_config": {
|
||||||
|
"push_interval": 60,
|
||||||
|
"pull_interval": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Proxy Protocol Note**: Set `accept_proxy_protocol` to `true` only when this node is behind an L4 proxy or load balancer that really sends PROXY protocol headers. Direct client connections will fail if this is enabled without an upstream PROXY sender.
|
||||||
|
|
||||||
- **GET** `/api/v1/server/UniProxy/user`
|
- **GET** `/api/v1/server/UniProxy/user`
|
||||||
- **Purpose**: Get user data for server
|
- **Purpose**: Get user data for server
|
||||||
@@ -952,4 +972,4 @@ Same endpoints as V1 but under `/api/v2/passport/` prefix.
|
|||||||
|
|
||||||
6. **Filtering**: Admin endpoints often support filtering and sorting parameters.
|
6. **Filtering**: Admin endpoints often support filtering and sorting parameters.
|
||||||
|
|
||||||
This documentation provides a comprehensive overview of all available API endpoints in the Xboard system. Each endpoint serves specific functionality within the VPN service management platform.
|
This documentation provides a comprehensive overview of all available API endpoints in the Xboard system. Each endpoint serves specific functionality within the VPN service management platform.
|
||||||
|
|||||||
@@ -21,10 +21,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (l *Listener) ListenTCP() (net.Listener, error) {
|
func (l *Listener) ListenTCP() (net.Listener, error) {
|
||||||
//nolint:staticcheck
|
|
||||||
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
|
|
||||||
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
|
|
||||||
}
|
|
||||||
var err error
|
var err error
|
||||||
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(netip.AddrFrom4([4]byte{127, 0, 0, 1})), l.listenOptions.ListenPort)
|
||||||
var listenConfig net.ListenConfig
|
var listenConfig net.ListenConfig
|
||||||
@@ -100,6 +96,18 @@ func (l *Listener) loopTCPIn() {
|
|||||||
l.logger.Error("tcp listener closed: ", err)
|
l.logger.Error("tcp listener closed: ", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
remoteAddr := conn.RemoteAddr()
|
||||||
|
//nolint:staticcheck
|
||||||
|
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
|
||||||
|
//nolint:staticcheck
|
||||||
|
wrappedConn, wrapErr := wrapProxyProtocolConn(conn, l.listenOptions.ProxyProtocolAcceptNoHeader)
|
||||||
|
if wrapErr != nil {
|
||||||
|
conn.Close()
|
||||||
|
l.logger.Error("process connection from ", remoteAddr, ": PROXY protocol: ", wrapErr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
conn = wrappedConn
|
||||||
|
}
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
metadata.InboundDetour = l.listenOptions.Detour
|
metadata.InboundDetour = l.listenOptions.Detour
|
||||||
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
|
||||||
|
|||||||
186
common/listener/proxy_protocol.go
Normal file
186
common/listener/proxy_protocol.go
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errProxyProtocolHeaderNotPresent = errors.New("proxy protocol header not present")
|
||||||
|
|
||||||
|
var proxyProtocolV2Signature = []byte{
|
||||||
|
0x0d, 0x0a, 0x0d, 0x0a,
|
||||||
|
0x00, 0x0d, 0x0a, 0x51,
|
||||||
|
0x55, 0x49, 0x54, 0x0a,
|
||||||
|
}
|
||||||
|
|
||||||
|
type proxyProtocolConn struct {
|
||||||
|
net.Conn
|
||||||
|
reader *bufio.Reader
|
||||||
|
remoteAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *proxyProtocolConn) Read(p []byte) (int, error) {
|
||||||
|
return c.reader.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *proxyProtocolConn) RemoteAddr() net.Addr {
|
||||||
|
return c.remoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapProxyProtocolConn(conn net.Conn, allowNoHeader bool) (net.Conn, error) {
|
||||||
|
reader := bufio.NewReader(conn)
|
||||||
|
remoteAddr, err := readProxyProtocolRemoteAddr(reader)
|
||||||
|
if err != nil {
|
||||||
|
if allowNoHeader && errors.Is(err, errProxyProtocolHeaderNotPresent) {
|
||||||
|
return &proxyProtocolConn{
|
||||||
|
Conn: conn,
|
||||||
|
reader: reader,
|
||||||
|
remoteAddr: conn.RemoteAddr(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if remoteAddr == nil {
|
||||||
|
remoteAddr = conn.RemoteAddr()
|
||||||
|
}
|
||||||
|
return &proxyProtocolConn{
|
||||||
|
Conn: conn,
|
||||||
|
reader: reader,
|
||||||
|
remoteAddr: remoteAddr,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readProxyProtocolRemoteAddr(reader *bufio.Reader) (net.Addr, error) {
|
||||||
|
firstByte, err := reader.Peek(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch firstByte[0] {
|
||||||
|
case 'P':
|
||||||
|
return readProxyProtocolV1RemoteAddr(reader)
|
||||||
|
case '\r':
|
||||||
|
signature, err := reader.Peek(len(proxyProtocolV2Signature))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(signature, proxyProtocolV2Signature) {
|
||||||
|
return nil, errProxyProtocolHeaderNotPresent
|
||||||
|
}
|
||||||
|
return readProxyProtocolV2RemoteAddr(reader)
|
||||||
|
default:
|
||||||
|
return nil, errProxyProtocolHeaderNotPresent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readProxyProtocolV1RemoteAddr(reader *bufio.Reader) (net.Addr, error) {
|
||||||
|
prefix, err := reader.Peek(6)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(prefix, []byte("PROXY ")) {
|
||||||
|
return nil, errProxyProtocolHeaderNotPresent
|
||||||
|
}
|
||||||
|
|
||||||
|
line, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(line) < 2 || line[len(line)-2:] != "\r\n" {
|
||||||
|
return nil, fmt.Errorf("invalid PROXY protocol v1 line ending")
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := bytes.Fields([]byte(line[:len(line)-2]))
|
||||||
|
if len(fields) < 2 {
|
||||||
|
return nil, fmt.Errorf("invalid PROXY protocol v1 header")
|
||||||
|
}
|
||||||
|
if string(fields[1]) == "UNKNOWN" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if len(fields) != 6 {
|
||||||
|
return nil, fmt.Errorf("invalid PROXY protocol v1 field count")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceIP := net.ParseIP(string(fields[2]))
|
||||||
|
if sourceIP == nil {
|
||||||
|
return nil, fmt.Errorf("invalid PROXY protocol source ip")
|
||||||
|
}
|
||||||
|
sourcePort, err := parseProxyProtocolPort(fields[4])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &net.TCPAddr{
|
||||||
|
IP: sourceIP,
|
||||||
|
Port: sourcePort,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readProxyProtocolV2RemoteAddr(reader *bufio.Reader) (net.Addr, error) {
|
||||||
|
header := make([]byte, 16)
|
||||||
|
if _, err := io.ReadFull(reader, header); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(header[:12], proxyProtocolV2Signature) {
|
||||||
|
return nil, errProxyProtocolHeaderNotPresent
|
||||||
|
}
|
||||||
|
|
||||||
|
version := header[12] >> 4
|
||||||
|
command := header[12] & 0x0f
|
||||||
|
if version != 0x2 {
|
||||||
|
return nil, fmt.Errorf("invalid PROXY protocol v2 version")
|
||||||
|
}
|
||||||
|
|
||||||
|
addressDataLen := int(binary.BigEndian.Uint16(header[14:16]))
|
||||||
|
addressData := make([]byte, addressDataLen)
|
||||||
|
if _, err := io.ReadFull(reader, addressData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if command == 0x0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if command != 0x1 {
|
||||||
|
return nil, fmt.Errorf("unsupported PROXY protocol v2 command")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch header[13] {
|
||||||
|
case 0x11, 0x12:
|
||||||
|
if len(addressData) < 12 {
|
||||||
|
return nil, fmt.Errorf("short PROXY protocol v2 ipv4 header")
|
||||||
|
}
|
||||||
|
return &net.TCPAddr{
|
||||||
|
IP: net.IP(addressData[:4]),
|
||||||
|
Port: int(binary.BigEndian.Uint16(addressData[8:10])),
|
||||||
|
}, nil
|
||||||
|
case 0x21, 0x22:
|
||||||
|
if len(addressData) < 36 {
|
||||||
|
return nil, fmt.Errorf("short PROXY protocol v2 ipv6 header")
|
||||||
|
}
|
||||||
|
return &net.TCPAddr{
|
||||||
|
IP: net.IP(addressData[:16]),
|
||||||
|
Port: int(binary.BigEndian.Uint16(addressData[32:34])),
|
||||||
|
}, nil
|
||||||
|
case 0x31, 0x32:
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported PROXY protocol v2 family")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseProxyProtocolPort(raw []byte) (int, error) {
|
||||||
|
port := 0
|
||||||
|
for _, ch := range raw {
|
||||||
|
if ch < '0' || ch > '9' {
|
||||||
|
return 0, fmt.Errorf("invalid PROXY protocol port")
|
||||||
|
}
|
||||||
|
port = port*10 + int(ch-'0')
|
||||||
|
if port > 65535 {
|
||||||
|
return 0, fmt.Errorf("invalid PROXY protocol port")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return port, nil
|
||||||
|
}
|
||||||
114
common/listener/proxy_protocol_test.go
Normal file
114
common/listener/proxy_protocol_test.go
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReadProxyProtocolV1RemoteAddr(t *testing.T) {
|
||||||
|
reader := bufio.NewReaderSize(newStaticConn("PROXY TCP4 203.0.113.10 192.0.2.1 45678 443\r\npayload"), 128)
|
||||||
|
remoteAddr, err := readProxyProtocolRemoteAddr(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
tcpAddr, ok := remoteAddr.(*net.TCPAddr)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unexpected addr type: %T", remoteAddr)
|
||||||
|
}
|
||||||
|
if got := tcpAddr.IP.String(); got != "203.0.113.10" {
|
||||||
|
t.Fatalf("unexpected ip: %s", got)
|
||||||
|
}
|
||||||
|
if tcpAddr.Port != 45678 {
|
||||||
|
t.Fatalf("unexpected port: %d", tcpAddr.Port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadProxyProtocolV2RemoteAddr(t *testing.T) {
|
||||||
|
header := make([]byte, 28)
|
||||||
|
copy(header[:12], proxyProtocolV2Signature)
|
||||||
|
header[12] = 0x21
|
||||||
|
header[13] = 0x11
|
||||||
|
binary.BigEndian.PutUint16(header[14:16], 12)
|
||||||
|
copy(header[16:20], net.ParseIP("198.51.100.12").To4())
|
||||||
|
copy(header[20:24], net.ParseIP("192.0.2.8").To4())
|
||||||
|
binary.BigEndian.PutUint16(header[24:26], 50000)
|
||||||
|
binary.BigEndian.PutUint16(header[26:28], 443)
|
||||||
|
|
||||||
|
reader := bufio.NewReaderSize(newStaticConn(string(header)+"payload"), 128)
|
||||||
|
remoteAddr, err := readProxyProtocolRemoteAddr(reader)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
tcpAddr, ok := remoteAddr.(*net.TCPAddr)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unexpected addr type: %T", remoteAddr)
|
||||||
|
}
|
||||||
|
if got := tcpAddr.IP.String(); got != "198.51.100.12" {
|
||||||
|
t.Fatalf("unexpected ip: %s", got)
|
||||||
|
}
|
||||||
|
if tcpAddr.Port != 50000 {
|
||||||
|
t.Fatalf("unexpected port: %d", tcpAddr.Port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrapProxyProtocolConnAllowNoHeader(t *testing.T) {
|
||||||
|
rawConn := newStaticConn("hello")
|
||||||
|
conn, err := wrapProxyProtocolConn(rawConn, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if conn.RemoteAddr().String() != rawConn.RemoteAddr().String() {
|
||||||
|
t.Fatalf("remote addr changed unexpectedly: %s", conn.RemoteAddr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type staticConn struct {
|
||||||
|
net.Conn
|
||||||
|
reader *bufio.Reader
|
||||||
|
local net.Addr
|
||||||
|
remote net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStaticConn(payload string) *staticConn {
|
||||||
|
return &staticConn{
|
||||||
|
reader: bufio.NewReaderSize(strings.NewReader(payload), len(payload)+16),
|
||||||
|
local: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 443},
|
||||||
|
remote: &net.TCPAddr{IP: net.ParseIP("10.0.0.1"), Port: 12345},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *staticConn) Read(p []byte) (int, error) {
|
||||||
|
return c.reader.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *staticConn) Write(p []byte) (int, error) {
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *staticConn) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *staticConn) LocalAddr() net.Addr {
|
||||||
|
return c.local
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *staticConn) RemoteAddr() net.Addr {
|
||||||
|
return c.remote
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *staticConn) SetDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *staticConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *staticConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
15
install.sh
15
install.sh
@@ -145,6 +145,9 @@ PANEL_URL=${INPUT_URL:-$PANEL_URL}
|
|||||||
read -p "Enter Panel Token (Node Key) [${PANEL_TOKEN}]: " INPUT_TOKEN
|
read -p "Enter Panel Token (Node Key) [${PANEL_TOKEN}]: " INPUT_TOKEN
|
||||||
PANEL_TOKEN=${INPUT_TOKEN:-$PANEL_TOKEN}
|
PANEL_TOKEN=${INPUT_TOKEN:-$PANEL_TOKEN}
|
||||||
|
|
||||||
|
read -p "This node is behind an L4 proxy/LB that sends PROXY protocol? [${ENABLE_PROXY_PROTOCOL_HINT:-n}]: " INPUT_PROXY_PROTOCOL
|
||||||
|
ENABLE_PROXY_PROTOCOL_HINT=${INPUT_PROXY_PROTOCOL:-${ENABLE_PROXY_PROTOCOL_HINT:-n}}
|
||||||
|
|
||||||
read -p "Enter Node Count [${NODE_COUNT:-1}]: " INPUT_COUNT
|
read -p "Enter Node Count [${NODE_COUNT:-1}]: " INPUT_COUNT
|
||||||
NODE_COUNT=${INPUT_COUNT:-${NODE_COUNT:-1}}
|
NODE_COUNT=${INPUT_COUNT:-${NODE_COUNT:-1}}
|
||||||
|
|
||||||
@@ -284,6 +287,17 @@ EOF
|
|||||||
|
|
||||||
echo -e "${GREEN}Configuration written to $CONFIG_FILE${NC}"
|
echo -e "${GREEN}Configuration written to $CONFIG_FILE${NC}"
|
||||||
|
|
||||||
|
if [[ "$ENABLE_PROXY_PROTOCOL_HINT" =~ ^([yY][eE][sS]|[yY]|1|true|TRUE)$ ]]; then
|
||||||
|
echo -e "${YELLOW}Proxy Protocol deployment hint enabled.${NC}"
|
||||||
|
echo -e "${YELLOW}To make real client IP reporting work, your panel node config response must include:${NC}"
|
||||||
|
echo -e "${YELLOW} \"accept_proxy_protocol\": true${NC}"
|
||||||
|
echo -e "${YELLOW}Only enable this when the upstream L4 proxy or load balancer actually sends PROXY protocol headers.${NC}"
|
||||||
|
echo -e "${YELLOW}If clients connect directly without a PROXY header, connections will fail after enabling it on the panel.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}Proxy Protocol is not expected for this deployment.${NC}"
|
||||||
|
echo -e "${YELLOW}Keep panel field \"accept_proxy_protocol\" disabled or absent unless you are using an L4 proxy/LB that sends it.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
# Create Systemd Service
|
# Create Systemd Service
|
||||||
echo -e "${YELLOW}Creating systemd service...${NC}"
|
echo -e "${YELLOW}Creating systemd service...${NC}"
|
||||||
cat > "$SERVICE_FILE" <<EOF
|
cat > "$SERVICE_FILE" <<EOF
|
||||||
@@ -311,3 +325,4 @@ systemctl restart "$SERVICE_NAME"
|
|||||||
echo -e "${GREEN}Service installed and started successfully.${NC}"
|
echo -e "${GREEN}Service installed and started successfully.${NC}"
|
||||||
echo -e "${GREEN}Check status with: systemctl status ${SERVICE_NAME}${NC}"
|
echo -e "${GREEN}Check status with: systemctl status ${SERVICE_NAME}${NC}"
|
||||||
echo -e "${GREEN}View logs with: journalctl -u ${SERVICE_NAME} -f${NC}"
|
echo -e "${GREEN}View logs with: journalctl -u ${SERVICE_NAME} -f${NC}"
|
||||||
|
echo -e "${GREEN}Panel config endpoint must control PROXY protocol via accept_proxy_protocol when needed.${NC}"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
@@ -42,6 +43,7 @@ type MultiInbound struct {
|
|||||||
service shadowsocks.MultiService[int]
|
service shadowsocks.MultiService[int]
|
||||||
users []option.ShadowsocksUser
|
users []option.ShadowsocksUser
|
||||||
tracker adapter.SSMTracker
|
tracker adapter.SSMTracker
|
||||||
|
ssmMutex sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMultiInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*MultiInbound, error) {
|
func newMultiInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*MultiInbound, error) {
|
||||||
@@ -119,10 +121,14 @@ func (h *MultiInbound) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *MultiInbound) SetTracker(tracker adapter.SSMTracker) {
|
func (h *MultiInbound) SetTracker(tracker adapter.SSMTracker) {
|
||||||
|
h.ssmMutex.Lock()
|
||||||
|
defer h.ssmMutex.Unlock()
|
||||||
h.tracker = tracker
|
h.tracker = tracker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MultiInbound) UpdateUsers(users []string, uPSKs []string, flows []string) error {
|
func (h *MultiInbound) UpdateUsers(users []string, uPSKs []string, flows []string) error {
|
||||||
|
h.ssmMutex.Lock()
|
||||||
|
defer h.ssmMutex.Unlock()
|
||||||
err := h.service.UpdateUsersWithPasswords(common.MapIndexed(users, func(index int, user string) int {
|
err := h.service.UpdateUsersWithPasswords(common.MapIndexed(users, func(index int, user string) int {
|
||||||
return index
|
return index
|
||||||
}), uPSKs)
|
}), uPSKs)
|
||||||
@@ -163,7 +169,15 @@ func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadat
|
|||||||
if !loaded {
|
if !loaded {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
user := h.users[userIndex].Name
|
h.ssmMutex.RLock()
|
||||||
|
if userIndex < 0 || userIndex >= len(h.users) {
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
userEntry := h.users[userIndex]
|
||||||
|
tracker := h.tracker
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
user := userEntry.Name
|
||||||
if user == "" {
|
if user == "" {
|
||||||
user = F.ToString(userIndex)
|
user = F.ToString(userIndex)
|
||||||
} else {
|
} else {
|
||||||
@@ -175,8 +189,8 @@ func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadat
|
|||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
metadata.InboundDetour = h.listener.ListenOptions().Detour
|
metadata.InboundDetour = h.listener.ListenOptions().Detour
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if h.tracker != nil {
|
if tracker != nil {
|
||||||
conn = h.tracker.TrackConnection(conn, metadata)
|
conn = tracker.TrackConnection(conn, metadata)
|
||||||
}
|
}
|
||||||
return h.router.RouteConnection(ctx, conn, metadata)
|
return h.router.RouteConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
@@ -186,7 +200,15 @@ func (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketCon
|
|||||||
if !loaded {
|
if !loaded {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
user := h.users[userIndex].Name
|
h.ssmMutex.RLock()
|
||||||
|
if userIndex < 0 || userIndex >= len(h.users) {
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
userEntry := h.users[userIndex]
|
||||||
|
tracker := h.tracker
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
user := userEntry.Name
|
||||||
if user == "" {
|
if user == "" {
|
||||||
user = F.ToString(userIndex)
|
user = F.ToString(userIndex)
|
||||||
} else {
|
} else {
|
||||||
@@ -200,8 +222,8 @@ func (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketCon
|
|||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
metadata.InboundDetour = h.listener.ListenOptions().Detour
|
metadata.InboundDetour = h.listener.ListenOptions().Detour
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
if h.tracker != nil {
|
if tracker != nil {
|
||||||
conn = h.tracker.TrackPacketConnection(conn, metadata)
|
conn = tracker.TrackPacketConnection(conn, metadata)
|
||||||
}
|
}
|
||||||
return h.router.RoutePacketConnection(ctx, conn, metadata)
|
return h.router.RoutePacketConnection(ctx, conn, metadata)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,16 +209,22 @@ func (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata a
|
|||||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := h.users[userIndex].Name
|
h.ssmMutex.RLock()
|
||||||
|
if userIndex < 0 || userIndex >= len(h.users) {
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userEntry := h.users[userIndex]
|
||||||
|
tracker := h.tracker
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
user := userEntry.Name
|
||||||
if user == "" {
|
if user == "" {
|
||||||
user = F.ToString(userIndex)
|
user = F.ToString(userIndex)
|
||||||
} else {
|
} else {
|
||||||
metadata.User = user
|
metadata.User = user
|
||||||
}
|
}
|
||||||
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
|
h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
|
||||||
h.ssmMutex.RLock()
|
|
||||||
tracker := h.tracker
|
|
||||||
h.ssmMutex.RUnlock()
|
|
||||||
if tracker != nil {
|
if tracker != nil {
|
||||||
conn = tracker.TrackConnection(conn, metadata)
|
conn = tracker.TrackConnection(conn, metadata)
|
||||||
}
|
}
|
||||||
@@ -233,7 +239,16 @@ func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := h.users[userIndex].Name
|
h.ssmMutex.RLock()
|
||||||
|
if userIndex < 0 || userIndex >= len(h.users) {
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userEntry := h.users[userIndex]
|
||||||
|
tracker := h.tracker
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
user := userEntry.Name
|
||||||
if user == "" {
|
if user == "" {
|
||||||
user = F.ToString(userIndex)
|
user = F.ToString(userIndex)
|
||||||
} else {
|
} else {
|
||||||
@@ -246,9 +261,6 @@ func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
} else {
|
} else {
|
||||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||||
}
|
}
|
||||||
h.ssmMutex.RLock()
|
|
||||||
tracker := h.tracker
|
|
||||||
h.ssmMutex.RUnlock()
|
|
||||||
if tracker != nil {
|
if tracker != nil {
|
||||||
conn = tracker.TrackPacketConnection(conn, metadata)
|
conn = tracker.TrackPacketConnection(conn, metadata)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -215,7 +215,15 @@ func (h *Inbound) newConnectionEx(ctx context.Context, conn net.Conn, metadata a
|
|||||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := h.users[userIndex].Name
|
h.ssmMutex.RLock()
|
||||||
|
if userIndex < 0 || userIndex >= len(h.users) {
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userEntry := h.users[userIndex]
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
user := userEntry.Name
|
||||||
if user == "" {
|
if user == "" {
|
||||||
user = F.ToString(userIndex)
|
user = F.ToString(userIndex)
|
||||||
} else {
|
} else {
|
||||||
@@ -233,7 +241,16 @@ func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := h.users[userIndex].Name
|
h.ssmMutex.RLock()
|
||||||
|
if userIndex < 0 || userIndex >= len(h.users) {
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
N.CloseOnHandshakeFailure(conn, onClose, os.ErrInvalid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userEntry := h.users[userIndex]
|
||||||
|
tracker := h.tracker
|
||||||
|
h.ssmMutex.RUnlock()
|
||||||
|
user := userEntry.Name
|
||||||
if user == "" {
|
if user == "" {
|
||||||
user = F.ToString(userIndex)
|
user = F.ToString(userIndex)
|
||||||
} else {
|
} else {
|
||||||
@@ -246,9 +263,6 @@ func (h *Inbound) newPacketConnectionEx(ctx context.Context, conn N.PacketConn,
|
|||||||
} else {
|
} else {
|
||||||
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
|
||||||
}
|
}
|
||||||
h.ssmMutex.RLock()
|
|
||||||
tracker := h.tracker
|
|
||||||
h.ssmMutex.RUnlock()
|
|
||||||
if tracker != nil {
|
if tracker != nil {
|
||||||
conn = tracker.TrackPacketConnection(conn, metadata)
|
conn = tracker.TrackPacketConnection(conn, metadata)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ type XNodeConfig struct {
|
|||||||
ServerName string `json:"server_name,omitempty"`
|
ServerName string `json:"server_name,omitempty"`
|
||||||
ServerPortText string `json:"server_port_text,omitempty"`
|
ServerPortText string `json:"server_port_text,omitempty"`
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
|
AcceptProxyProtocol bool `json:"accept_proxy_protocol,omitempty"`
|
||||||
|
AcceptProxyProtocol_ bool `json:"acceptProxyProtocol,omitempty"`
|
||||||
Multiplex *XMultiplexConfig `json:"multiplex,omitempty"`
|
Multiplex *XMultiplexConfig `json:"multiplex,omitempty"`
|
||||||
NetworkSettings json.RawMessage `json:"network_settings"`
|
NetworkSettings json.RawMessage `json:"network_settings"`
|
||||||
NetworkSettings_ json.RawMessage `json:"networkSettings"`
|
NetworkSettings_ json.RawMessage `json:"networkSettings"`
|
||||||
@@ -162,6 +164,8 @@ type XInnerConfig struct {
|
|||||||
Dest string `json:"dest,omitempty"`
|
Dest string `json:"dest,omitempty"`
|
||||||
ServerName string `json:"server_name,omitempty"`
|
ServerName string `json:"server_name,omitempty"`
|
||||||
Network string `json:"network"`
|
Network string `json:"network"`
|
||||||
|
AcceptProxyProtocol bool `json:"accept_proxy_protocol,omitempty"`
|
||||||
|
AcceptProxyProtocol_ bool `json:"acceptProxyProtocol,omitempty"`
|
||||||
Multiplex *XMultiplexConfig `json:"multiplex,omitempty"`
|
Multiplex *XMultiplexConfig `json:"multiplex,omitempty"`
|
||||||
NetworkSettings json.RawMessage `json:"network_settings"`
|
NetworkSettings json.RawMessage `json:"network_settings"`
|
||||||
NetworkSettings_ json.RawMessage `json:"networkSettings"`
|
NetworkSettings_ json.RawMessage `json:"networkSettings"`
|
||||||
@@ -212,6 +216,67 @@ type GrpcNetworkConfig struct {
|
|||||||
ServiceName string `json:"serviceName"`
|
ServiceName string `json:"serviceName"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unmarshalNetworkSettings(settings json.RawMessage) map[string]any {
|
||||||
|
if len(settings) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var raw map[string]any
|
||||||
|
if err := json.Unmarshal(settings, &raw); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNetworkString(raw map[string]any, keys ...string) string {
|
||||||
|
for _, key := range keys {
|
||||||
|
value, exists := raw[key]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if stringValue, ok := value.(string); ok && stringValue != "" {
|
||||||
|
return stringValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNetworkBool(raw map[string]any, keys ...string) (bool, bool) {
|
||||||
|
for _, key := range keys {
|
||||||
|
value, exists := raw[key]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if boolValue, ok := value.(bool); ok {
|
||||||
|
return boolValue, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNetworkDuration(raw map[string]any, keys ...string) (badoption.Duration, bool) {
|
||||||
|
for _, key := range keys {
|
||||||
|
value, exists := raw[key]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch typedValue := value.(type) {
|
||||||
|
case float64:
|
||||||
|
return badoption.Duration(time.Duration(typedValue) * time.Second), true
|
||||||
|
case string:
|
||||||
|
if typedValue == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if durationValue, err := time.ParseDuration(typedValue); err == nil {
|
||||||
|
return badoption.Duration(durationValue), true
|
||||||
|
}
|
||||||
|
if secondsValue, err := strconv.ParseInt(typedValue, 10, 64); err == nil {
|
||||||
|
return badoption.Duration(time.Duration(secondsValue) * time.Second), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
type HttpupgradeNetworkConfig struct {
|
type HttpupgradeNetworkConfig struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
@@ -466,6 +531,7 @@ func getInboundTransport(network string, settings json.RawMessage) (*option.V2Ra
|
|||||||
t := &option.V2RayTransportOptions{
|
t := &option.V2RayTransportOptions{
|
||||||
Type: network,
|
Type: network,
|
||||||
}
|
}
|
||||||
|
rawSettings := unmarshalNetworkSettings(settings)
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
if len(settings) != 0 {
|
if len(settings) != 0 {
|
||||||
@@ -535,6 +601,18 @@ func getInboundTransport(network string, settings json.RawMessage) (*option.V2Ra
|
|||||||
t.GRPCOptions = option.V2RayGRPCOptions{
|
t.GRPCOptions = option.V2RayGRPCOptions{
|
||||||
ServiceName: networkConfig.ServiceName,
|
ServiceName: networkConfig.ServiceName,
|
||||||
}
|
}
|
||||||
|
if serviceName := readNetworkString(rawSettings, "service_name"); serviceName != "" && t.GRPCOptions.ServiceName == "" {
|
||||||
|
t.GRPCOptions.ServiceName = serviceName
|
||||||
|
}
|
||||||
|
if idleTimeout, ok := readNetworkDuration(rawSettings, "idle_timeout", "idleTimeout"); ok {
|
||||||
|
t.GRPCOptions.IdleTimeout = idleTimeout
|
||||||
|
}
|
||||||
|
if pingTimeout, ok := readNetworkDuration(rawSettings, "ping_timeout", "pingTimeout"); ok {
|
||||||
|
t.GRPCOptions.PingTimeout = pingTimeout
|
||||||
|
}
|
||||||
|
if permitWithoutStream, ok := readNetworkBool(rawSettings, "permit_without_stream", "permitWithoutStream"); ok {
|
||||||
|
t.GRPCOptions.PermitWithoutStream = permitWithoutStream
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "httpupgrade":
|
case "httpupgrade":
|
||||||
if len(settings) != 0 {
|
if len(settings) != 0 {
|
||||||
@@ -548,10 +626,106 @@ func getInboundTransport(network string, settings json.RawMessage) (*option.V2Ra
|
|||||||
Host: networkConfig.Host,
|
Host: networkConfig.Host,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "h2", "http":
|
||||||
|
t.Type = "http"
|
||||||
|
if rawSettings != nil {
|
||||||
|
t.HTTPOptions = option.V2RayHTTPOptions{
|
||||||
|
Path: readNetworkString(rawSettings, "path"),
|
||||||
|
Method: readNetworkString(rawSettings, "method"),
|
||||||
|
Headers: nil,
|
||||||
|
}
|
||||||
|
if idleTimeout, ok := readNetworkDuration(rawSettings, "idle_timeout", "idleTimeout"); ok {
|
||||||
|
t.HTTPOptions.IdleTimeout = idleTimeout
|
||||||
|
}
|
||||||
|
if pingTimeout, ok := readNetworkDuration(rawSettings, "ping_timeout", "pingTimeout"); ok {
|
||||||
|
t.HTTPOptions.PingTimeout = pingTimeout
|
||||||
|
}
|
||||||
|
if hostValue, exists := rawSettings["host"]; exists {
|
||||||
|
switch typedHost := hostValue.(type) {
|
||||||
|
case string:
|
||||||
|
if typedHost != "" {
|
||||||
|
t.HTTPOptions.Host = badoption.Listable[string]{typedHost}
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
hostList := make(badoption.Listable[string], 0, len(typedHost))
|
||||||
|
for _, item := range typedHost {
|
||||||
|
if host, ok := item.(string); ok && host != "" {
|
||||||
|
hostList = append(hostList, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(hostList) > 0 {
|
||||||
|
t.HTTPOptions.Host = hostList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if headersValue, exists := rawSettings["headers"]; exists {
|
||||||
|
if headersMap, ok := headersValue.(map[string]any); ok {
|
||||||
|
headerOptions := make(badoption.HTTPHeader)
|
||||||
|
for headerKey, headerValue := range headersMap {
|
||||||
|
switch typedHeader := headerValue.(type) {
|
||||||
|
case string:
|
||||||
|
if typedHeader != "" {
|
||||||
|
headerOptions[headerKey] = badoption.Listable[string]{typedHeader}
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
values := make(badoption.Listable[string], 0, len(typedHeader))
|
||||||
|
for _, item := range typedHeader {
|
||||||
|
if headerString, ok := item.(string); ok && headerString != "" {
|
||||||
|
values = append(values, headerString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(values) > 0 {
|
||||||
|
headerOptions[headerKey] = values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(headerOptions) > 0 {
|
||||||
|
t.HTTPOptions.Headers = headerOptions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func networkAcceptProxyProtocol(settings json.RawMessage) bool {
|
||||||
|
if len(settings) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var raw map[string]any
|
||||||
|
if err := json.Unmarshal(settings, &raw); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, key := range []string{"acceptProxyProtocol", "accept_proxy_protocol"} {
|
||||||
|
value, exists := raw[key]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
enabled, ok := value.(bool)
|
||||||
|
if ok && enabled {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func acceptProxyProtocolEnabled(inner XInnerConfig, config *XNodeConfig) bool {
|
||||||
|
if inner.AcceptProxyProtocol || inner.AcceptProxyProtocol_ {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if config != nil && (config.AcceptProxyProtocol || config.AcceptProxyProtocol_) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if networkAcceptProxyProtocol(inner.NetworkSettings) || networkAcceptProxyProtocol(inner.NetworkSettings_) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if config != nil && (networkAcceptProxyProtocol(config.NetworkSettings) || networkAcceptProxyProtocol(config.NetworkSettings_)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) setupNode() error {
|
func (s *Service) setupNode() error {
|
||||||
s.logger.Info("Xboard fetching node config...")
|
s.logger.Info("Xboard fetching node config...")
|
||||||
config, err := s.fetchConfig()
|
config, err := s.fetchConfig()
|
||||||
@@ -675,6 +849,10 @@ func (s *Service) setupNode() error {
|
|||||||
Listen: &listenAddr,
|
Listen: &listenAddr,
|
||||||
ListenPort: uint16(inner.Port),
|
ListenPort: uint16(inner.Port),
|
||||||
}
|
}
|
||||||
|
if acceptProxyProtocolEnabled(inner, config) {
|
||||||
|
listen.ProxyProtocol = true
|
||||||
|
s.logger.Info("Xboard PROXY protocol enabled for inbound on ", inner.ListenIP, ":", inner.Port)
|
||||||
|
}
|
||||||
|
|
||||||
// ── TLS / Reality handling (matching V2bX panel.Security constants) ──
|
// ── TLS / Reality handling (matching V2bX panel.Security constants) ──
|
||||||
// V2bX: 0=None, 1=TLS, 2=Reality
|
// V2bX: 0=None, 1=TLS, 2=Reality
|
||||||
@@ -1264,6 +1442,11 @@ func (s *Service) syncUsers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sameUserSet(s.localUsers, newUsers) {
|
||||||
|
s.logger.Trace("Xboard sync skipped: users unchanged")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for tag, server := range s.servers {
|
for tag, server := range s.servers {
|
||||||
// Update users in each manager
|
// Update users in each manager
|
||||||
users := make([]string, 0, len(newUsers))
|
users := make([]string, 0, len(newUsers))
|
||||||
@@ -1286,6 +1469,22 @@ func (s *Service) syncUsers() {
|
|||||||
s.logger.Info("Xboard sync completed, total users: ", len(users))
|
s.logger.Info("Xboard sync completed, total users: ", len(users))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sameUserSet(current map[string]userData, next map[string]userData) bool {
|
||||||
|
if len(current) != len(next) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for userName, currentUser := range current {
|
||||||
|
nextUser, exists := next[userName]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if currentUser != nextUser {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) reportTraffic() {
|
func (s *Service) reportTraffic() {
|
||||||
s.logger.Trace("Xboard reporting traffic...")
|
s.logger.Trace("Xboard reporting traffic...")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user