mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-22 00:21:30 +00:00
Add naive inbound and test
This commit is contained in:
parent
ccdfab378a
commit
b79b19c470
|
@ -13,6 +13,7 @@ const (
|
||||||
TypeShadowsocks = "shadowsocks"
|
TypeShadowsocks = "shadowsocks"
|
||||||
TypeVMess = "vmess"
|
TypeVMess = "vmess"
|
||||||
TypeTrojan = "trojan"
|
TypeTrojan = "trojan"
|
||||||
|
TypeNaive = "naive"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
5
constant/quic.go
Normal file
5
constant/quic.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build with_quic
|
||||||
|
|
||||||
|
package constant
|
||||||
|
|
||||||
|
const QUIC_AVAILABLE = true
|
5
constant/quic_stub.go
Normal file
5
constant/quic_stub.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build !with_quic
|
||||||
|
|
||||||
|
package constant
|
||||||
|
|
||||||
|
const QUIC_AVAILABLE = false
|
4
go.mod
4
go.mod
|
@ -12,6 +12,7 @@ require (
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.0
|
||||||
github.com/hashicorp/yamux v0.1.1
|
github.com/hashicorp/yamux v0.1.1
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
|
github.com/lucas-clemente/quic-go v0.28.1
|
||||||
github.com/oschwald/maxminddb-golang v1.10.0
|
github.com/oschwald/maxminddb-golang v1.10.0
|
||||||
github.com/sagernet/sing v0.0.0-20220808004927-21369d10810d
|
github.com/sagernet/sing v0.0.0-20220808004927-21369d10810d
|
||||||
github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91
|
github.com/sagernet/sing-dns v0.0.0-20220803121532-9e1ffb850d91
|
||||||
|
@ -23,7 +24,7 @@ require (
|
||||||
github.com/xtaci/smux v1.5.16
|
github.com/xtaci/smux v1.5.16
|
||||||
go.uber.org/atomic v1.9.0
|
go.uber.org/atomic v1.9.0
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
|
||||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770
|
golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced
|
||||||
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664
|
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,7 +37,6 @@ require (
|
||||||
github.com/google/btree v1.0.1 // indirect
|
github.com/google/btree v1.0.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||||
github.com/lucas-clemente/quic-go v0.28.1 // indirect
|
|
||||||
github.com/marten-seemann/qpack v0.2.1 // indirect
|
github.com/marten-seemann/qpack v0.2.1 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
|
github.com/marten-seemann/qtls-go1-17 v0.1.2 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -247,8 +247,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770 h1:dIi4qVdvjZEjiMDv7vhokAZNGnz3kepwuXqFKYDdDMs=
|
golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced h1:3dYNDff0VT5xj+mbj2XucFst9WKk6PdGOrb9n+SbIvw=
|
||||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
|
|
@ -35,6 +35,8 @@ func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, o
|
||||||
return NewVMess(ctx, router, logger, options.Tag, options.VMessOptions)
|
return NewVMess(ctx, router, logger, options.Tag, options.VMessOptions)
|
||||||
case C.TypeTrojan:
|
case C.TypeTrojan:
|
||||||
return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions)
|
return NewTrojan(ctx, router, logger, options.Tag, options.TrojanOptions)
|
||||||
|
case C.TypeNaive:
|
||||||
|
return NewNaive(ctx, router, logger, options.Tag, options.NaiveOptions)
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown inbound type: ", options.Type)
|
return nil, E.New("unknown inbound type: ", options.Type)
|
||||||
}
|
}
|
||||||
|
|
398
inbound/naive.go
Normal file
398
inbound/naive.go
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing-dns"
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/auth"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ adapter.Inbound = (*Naive)(nil)
|
||||||
|
|
||||||
|
type Naive struct {
|
||||||
|
ctx context.Context
|
||||||
|
router adapter.Router
|
||||||
|
logger log.ContextLogger
|
||||||
|
tag string
|
||||||
|
listenOptions option.ListenOptions
|
||||||
|
network []string
|
||||||
|
authenticator auth.Authenticator
|
||||||
|
tlsConfig *TLSConfig
|
||||||
|
httpServer *http.Server
|
||||||
|
h3Server any
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNaiveTLSRequired = E.New("TLS required")
|
||||||
|
ErrNaiveMissingUsers = E.New("missing users")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (*Naive, error) {
|
||||||
|
inbound := &Naive{
|
||||||
|
ctx: ctx,
|
||||||
|
router: router,
|
||||||
|
logger: logger,
|
||||||
|
tag: tag,
|
||||||
|
listenOptions: options.ListenOptions,
|
||||||
|
network: options.Network.Build(),
|
||||||
|
authenticator: auth.NewAuthenticator(options.Users),
|
||||||
|
}
|
||||||
|
if options.TLS == nil || !options.TLS.Enabled {
|
||||||
|
return nil, ErrNaiveTLSRequired
|
||||||
|
}
|
||||||
|
if len(options.Users) == 0 {
|
||||||
|
return nil, ErrNaiveMissingUsers
|
||||||
|
}
|
||||||
|
tlsConfig, err := NewTLSConfig(logger, common.PtrValueOrDefault(options.TLS))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
inbound.tlsConfig = tlsConfig
|
||||||
|
return inbound, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Naive) Type() string {
|
||||||
|
return C.TypeNaive
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Naive) Tag() string {
|
||||||
|
return n.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Naive) Start() error {
|
||||||
|
err := n.tlsConfig.Start()
|
||||||
|
if err != nil {
|
||||||
|
return E.Cause(err, "create TLS config")
|
||||||
|
}
|
||||||
|
|
||||||
|
n.httpServer = &http.Server{
|
||||||
|
Handler: n,
|
||||||
|
TLSConfig: n.tlsConfig.Config(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var listenAddr string
|
||||||
|
if nAddr := netip.Addr(n.listenOptions.Listen); nAddr.IsValid() {
|
||||||
|
if n.listenOptions.ListenPort != 0 {
|
||||||
|
listenAddr = M.SocksaddrFrom(netip.Addr(n.listenOptions.Listen), n.listenOptions.ListenPort).String()
|
||||||
|
} else {
|
||||||
|
listenAddr = net.JoinHostPort(nAddr.String(), ":https")
|
||||||
|
}
|
||||||
|
} else if n.listenOptions.ListenPort != 0 {
|
||||||
|
listenAddr = ":" + F.ToString(n.listenOptions.ListenPort)
|
||||||
|
} else {
|
||||||
|
listenAddr = ":https"
|
||||||
|
}
|
||||||
|
|
||||||
|
if common.Contains(n.network, N.NetworkTCP) {
|
||||||
|
tcpListener, err := net.Listen(M.NetworkFromNetAddr("tcp", netip.Addr(n.listenOptions.Listen)), listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.logger.Info("tcp server started at ", tcpListener.Addr())
|
||||||
|
go func() {
|
||||||
|
sErr := n.httpServer.ServeTLS(tcpListener, "", "")
|
||||||
|
if sErr == http.ErrServerClosed {
|
||||||
|
} else if sErr != nil {
|
||||||
|
n.logger.Error("http server serve error: ", sErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if common.Contains(n.network, N.NetworkUDP) {
|
||||||
|
err = n.configureHTTP3Listener(listenAddr)
|
||||||
|
if !C.QUIC_AVAILABLE && len(n.network) > 1 {
|
||||||
|
log.Warn(E.Cause(err, "naive http3 disabled"))
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Naive) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
common.PtrOrNil(n.httpServer),
|
||||||
|
n.h3Server,
|
||||||
|
common.PtrOrNil(n.tlsConfig),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Naive) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
ctx := log.ContextWithNewID(request.Context())
|
||||||
|
if request.Method != "CONNECT" {
|
||||||
|
n.logger.ErrorContext(ctx, "bad request: not connect")
|
||||||
|
rejectHTTP(writer, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
} else if request.Header.Get("Padding") == "" {
|
||||||
|
n.logger.ErrorContext(ctx, "bad request: missing padding")
|
||||||
|
rejectHTTP(writer, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var authOk bool
|
||||||
|
authorization := request.Header.Get("Proxy-Authorization")
|
||||||
|
if strings.HasPrefix(authorization, "BASIC ") || strings.HasPrefix(authorization, "Basic ") {
|
||||||
|
userPassword, _ := base64.URLEncoding.DecodeString(authorization[6:])
|
||||||
|
userPswdArr := strings.SplitN(string(userPassword), ":", 2)
|
||||||
|
authOk = n.authenticator.Verify(userPswdArr[0], userPswdArr[1])
|
||||||
|
if authOk {
|
||||||
|
ctx = auth.ContextWithUser(ctx, userPswdArr[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !authOk {
|
||||||
|
n.logger.ErrorContext(ctx, "bad request: authorization failed")
|
||||||
|
rejectHTTP(writer, http.StatusProxyAuthRequired)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writer.Header().Set("Padding", generateNaivePaddingHeader())
|
||||||
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
writer.(http.Flusher).Flush()
|
||||||
|
|
||||||
|
if request.ProtoMajor == 1 {
|
||||||
|
n.logger.ErrorContext(ctx, "bad request: http1")
|
||||||
|
rejectHTTP(writer, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hostPort := request.URL.Host
|
||||||
|
if hostPort == "" {
|
||||||
|
hostPort = request.Host
|
||||||
|
}
|
||||||
|
source := M.ParseSocksaddr(request.RemoteAddr)
|
||||||
|
destination := M.ParseSocksaddr(hostPort)
|
||||||
|
n.newConnection(ctx, &naivePaddingConn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, source, destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Naive) newConnection(ctx context.Context, conn net.Conn, source, destination M.Socksaddr) {
|
||||||
|
var metadata adapter.InboundContext
|
||||||
|
metadata.Inbound = n.tag
|
||||||
|
metadata.InboundType = C.TypeNaive
|
||||||
|
metadata.SniffEnabled = n.listenOptions.SniffEnabled
|
||||||
|
metadata.SniffOverrideDestination = n.listenOptions.SniffOverrideDestination
|
||||||
|
metadata.DomainStrategy = dns.DomainStrategy(n.listenOptions.DomainStrategy)
|
||||||
|
metadata.Network = N.NetworkTCP
|
||||||
|
metadata.Source = source
|
||||||
|
metadata.Destination = destination
|
||||||
|
hErr := n.router.RouteConnection(ctx, conn, metadata)
|
||||||
|
if hErr != nil {
|
||||||
|
conn.Close()
|
||||||
|
NewError(n.logger, ctx, E.Cause(hErr, "process connection from ", metadata.Source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rejectHTTP(writer http.ResponseWriter, statusCode int) {
|
||||||
|
hijacker, ok := writer.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
writer.WriteHeader(statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, _, err := hijacker.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
writer.WriteHeader(statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP {
|
||||||
|
tcpConn.SetLinger(0)
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateNaivePaddingHeader() string {
|
||||||
|
paddingLen := rand.Intn(32) + 30
|
||||||
|
padding := make([]byte, paddingLen)
|
||||||
|
bits := rand.Uint64()
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
// Codes that won't be Huffman coded.
|
||||||
|
padding[i] = "!#$()+<>?@[]^`{}"[bits&15]
|
||||||
|
bits >>= 4
|
||||||
|
}
|
||||||
|
for i := 16; i < paddingLen; i++ {
|
||||||
|
padding[i] = '~'
|
||||||
|
}
|
||||||
|
return string(padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
const kFirstPaddings = 8
|
||||||
|
|
||||||
|
var _ net.Conn = (*naivePaddingConn)(nil)
|
||||||
|
|
||||||
|
type naivePaddingConn struct {
|
||||||
|
reader io.Reader
|
||||||
|
writer io.Writer
|
||||||
|
flusher http.Flusher
|
||||||
|
rAddr net.Addr
|
||||||
|
readPadding int
|
||||||
|
writePadding int
|
||||||
|
readRemaining int
|
||||||
|
paddingRemaining int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = c.read(p)
|
||||||
|
err = wrapHttpError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) read(p []byte) (n int, err error) {
|
||||||
|
if c.readRemaining > 0 {
|
||||||
|
if len(p) > c.readRemaining {
|
||||||
|
p = p[:c.readRemaining]
|
||||||
|
}
|
||||||
|
n, err = c.read(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.readRemaining -= n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.paddingRemaining > 0 {
|
||||||
|
err = rw.SkipN(c.reader, c.paddingRemaining)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.readRemaining = 0
|
||||||
|
}
|
||||||
|
if c.readPadding < kFirstPaddings {
|
||||||
|
paddingHdr := p[:3]
|
||||||
|
_, err = io.ReadFull(c.reader, paddingHdr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2]))
|
||||||
|
paddingSize := int(paddingHdr[2])
|
||||||
|
if len(p) > originalDataSize {
|
||||||
|
p = p[:originalDataSize]
|
||||||
|
}
|
||||||
|
n, err = c.reader.Read(p)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.readPadding++
|
||||||
|
c.readRemaining = originalDataSize - n
|
||||||
|
c.paddingRemaining = paddingSize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return c.reader.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = c.write(p)
|
||||||
|
if err == nil {
|
||||||
|
c.flusher.Flush()
|
||||||
|
}
|
||||||
|
err = wrapHttpError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) write(p []byte) (n int, err error) {
|
||||||
|
if c.writePadding < kFirstPaddings {
|
||||||
|
paddingSize := rand.Intn(256)
|
||||||
|
_buffer := buf.Make(3 + len(p) + paddingSize)
|
||||||
|
defer runtime.KeepAlive(_buffer)
|
||||||
|
buffer := common.Dup(_buffer)
|
||||||
|
binary.BigEndian.PutUint16(buffer, uint16(len(p)))
|
||||||
|
buffer[2] = byte(paddingSize)
|
||||||
|
copy(buffer[3:], p)
|
||||||
|
_, err = c.writer.Write(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.writePadding++
|
||||||
|
}
|
||||||
|
return c.writer.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
defer buffer.Release()
|
||||||
|
if c.writePadding < kFirstPaddings {
|
||||||
|
bufferLen := buffer.Len()
|
||||||
|
paddingSize := rand.Intn(256)
|
||||||
|
header := buffer.ExtendHeader(3)
|
||||||
|
binary.BigEndian.PutUint16(header, uint16(bufferLen))
|
||||||
|
header[2] = byte(paddingSize)
|
||||||
|
buffer.Extend(paddingSize)
|
||||||
|
c.writePadding++
|
||||||
|
}
|
||||||
|
err := common.Error(c.writer.Write(buffer.Bytes()))
|
||||||
|
if err == nil {
|
||||||
|
c.flusher.Flush()
|
||||||
|
}
|
||||||
|
return wrapHttpError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
if c.readPadding < kFirstPaddings {
|
||||||
|
return bufio.WriteTo0(c, w)
|
||||||
|
}
|
||||||
|
return bufio.Copy(w, c.reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) ReadFrom(r io.Reader) (n int64, err error) {
|
||||||
|
if c.writePadding < kFirstPaddings {
|
||||||
|
return bufio.ReadFrom0(c, r)
|
||||||
|
}
|
||||||
|
return bufio.Copy(c.writer, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) Close() error {
|
||||||
|
return common.Close(
|
||||||
|
c.reader,
|
||||||
|
c.writer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) LocalAddr() net.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) RemoteAddr() net.Addr {
|
||||||
|
return c.rAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) SetDeadline(t time.Time) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naivePaddingConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return os.ErrInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
var http2errClientDisconnected = "client disconnected"
|
||||||
|
|
||||||
|
func wrapHttpError(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch err.Error() {
|
||||||
|
case http2errClientDisconnected:
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
40
inbound/naive_quic.go
Normal file
40
inbound/naive_quic.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
//go:build with_quic
|
||||||
|
|
||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
|
"github.com/lucas-clemente/quic-go"
|
||||||
|
"github.com/lucas-clemente/quic-go/http3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (n *Naive) configureHTTP3Listener(listenAddr string) error {
|
||||||
|
h3Server := &http3.Server{
|
||||||
|
Port: int(n.listenOptions.ListenPort),
|
||||||
|
TLSConfig: n.tlsConfig.Config(),
|
||||||
|
Handler: n,
|
||||||
|
}
|
||||||
|
|
||||||
|
udpListener, err := net.ListenPacket(M.NetworkFromNetAddr("udp", netip.Addr(n.listenOptions.Listen)), listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n.logger.Info("udp server started at ", udpListener.LocalAddr())
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
sErr := h3Server.Serve(udpListener)
|
||||||
|
if sErr == quic.ErrServerClosed {
|
||||||
|
return
|
||||||
|
} else if sErr != nil {
|
||||||
|
n.logger.Error("http3 server serve error: ", sErr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
n.h3Server = h3Server
|
||||||
|
return nil
|
||||||
|
}
|
9
inbound/naive_quic_stub.go
Normal file
9
inbound/naive_quic_stub.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
//go:build !with_quic
|
||||||
|
|
||||||
|
package inbound
|
||||||
|
|
||||||
|
import E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
|
func (n *Naive) configureHTTP3Listener(listenAddr string) error {
|
||||||
|
return E.New("QUIC is not included in this build, rebuild with -tags with_quic")
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ type _Inbound struct {
|
||||||
ShadowsocksOptions ShadowsocksInboundOptions `json:"-"`
|
ShadowsocksOptions ShadowsocksInboundOptions `json:"-"`
|
||||||
VMessOptions VMessInboundOptions `json:"-"`
|
VMessOptions VMessInboundOptions `json:"-"`
|
||||||
TrojanOptions TrojanInboundOptions `json:"-"`
|
TrojanOptions TrojanInboundOptions `json:"-"`
|
||||||
|
NaiveOptions NaiveInboundOptions `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Inbound _Inbound
|
type Inbound _Inbound
|
||||||
|
@ -46,6 +47,8 @@ func (h Inbound) MarshalJSON() ([]byte, error) {
|
||||||
v = h.VMessOptions
|
v = h.VMessOptions
|
||||||
case C.TypeTrojan:
|
case C.TypeTrojan:
|
||||||
v = h.TrojanOptions
|
v = h.TrojanOptions
|
||||||
|
case C.TypeNaive:
|
||||||
|
v = h.NaiveOptions
|
||||||
default:
|
default:
|
||||||
return nil, E.New("unknown inbound type: ", h.Type)
|
return nil, E.New("unknown inbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
|
@ -79,6 +82,8 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
|
||||||
v = &h.VMessOptions
|
v = &h.VMessOptions
|
||||||
case C.TypeTrojan:
|
case C.TypeTrojan:
|
||||||
v = &h.TrojanOptions
|
v = &h.TrojanOptions
|
||||||
|
case C.TypeNaive:
|
||||||
|
v = &h.NaiveOptions
|
||||||
default:
|
default:
|
||||||
return E.New("unknown inbound type: ", h.Type)
|
return E.New("unknown inbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
|
|
10
option/naive.go
Normal file
10
option/naive.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package option
|
||||||
|
|
||||||
|
import "github.com/sagernet/sing/common/auth"
|
||||||
|
|
||||||
|
type NaiveInboundOptions struct {
|
||||||
|
ListenOptions
|
||||||
|
Users []auth.User `json:"users,omitempty"`
|
||||||
|
Network NetworkList `json:"network,omitempty"`
|
||||||
|
TLS *InboundTLSOptions `json:"tls,omitempty"`
|
||||||
|
}
|
|
@ -8,8 +8,6 @@ import (
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/protocol/socks"
|
"github.com/sagernet/sing/protocol/socks"
|
||||||
|
@ -17,23 +15,6 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mkPort(t *testing.T) uint16 {
|
|
||||||
var lc net.ListenConfig
|
|
||||||
lc.Control = control.ReuseAddr()
|
|
||||||
for {
|
|
||||||
tcpListener, err := lc.Listen(context.Background(), "tcp", ":0")
|
|
||||||
require.NoError(t, err)
|
|
||||||
listenPort := M.SocksaddrFromNet(tcpListener.Addr()).Port
|
|
||||||
tcpListener.Close()
|
|
||||||
udpListener, err := lc.Listen(context.Background(), "tcp", F.ToString(":", listenPort))
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
udpListener.Close()
|
|
||||||
return listenPort
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startInstance(t *testing.T, options option.Options) {
|
func startInstance(t *testing.T, options option.Options) {
|
||||||
var instance *box.Box
|
var instance *box.Box
|
||||||
var err error
|
var err error
|
||||||
|
@ -54,6 +35,14 @@ func startInstance(t *testing.T, options option.Options) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testTCP(t *testing.T, clientPort uint16, testPort uint16) {
|
||||||
|
dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
|
||||||
|
dialTCP := func() (net.Conn, error) {
|
||||||
|
return dialer.DialContext(context.Background(), "tcp", M.ParseSocksaddrHostPort("127.0.0.1", testPort))
|
||||||
|
}
|
||||||
|
require.NoError(t, testPingPongWithConn(t, testPort, dialTCP))
|
||||||
|
}
|
||||||
|
|
||||||
func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
|
func testSuit(t *testing.T, clientPort uint16, testPort uint16) {
|
||||||
dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
|
dialer := socks.NewClient(N.SystemDialer, M.ParseSocksaddrHostPort("127.0.0.1", clientPort), socks.Version5, "", "")
|
||||||
dialTCP := func() (net.Conn, error) {
|
dialTCP := func() (net.Conn, error) {
|
||||||
|
|
|
@ -30,6 +30,7 @@ const (
|
||||||
ImageShadowsocksRustClient = "ghcr.io/shadowsocks/sslocal-rust:latest"
|
ImageShadowsocksRustClient = "ghcr.io/shadowsocks/sslocal-rust:latest"
|
||||||
ImageV2RayCore = "v2fly/v2fly-core:latest"
|
ImageV2RayCore = "v2fly/v2fly-core:latest"
|
||||||
ImageTrojan = "trojangfw/trojan:latest"
|
ImageTrojan = "trojangfw/trojan:latest"
|
||||||
|
ImageNaive = "pocat/naiveproxy:client"
|
||||||
)
|
)
|
||||||
|
|
||||||
var allImages = []string{
|
var allImages = []string{
|
||||||
|
@ -37,6 +38,7 @@ var allImages = []string{
|
||||||
ImageShadowsocksRustClient,
|
ImageShadowsocksRustClient,
|
||||||
ImageV2RayCore,
|
ImageV2RayCore,
|
||||||
ImageTrojan,
|
ImageTrojan,
|
||||||
|
ImageNaive,
|
||||||
}
|
}
|
||||||
|
|
||||||
var localIP = netip.MustParseAddr("127.0.0.1")
|
var localIP = netip.MustParseAddr("127.0.0.1")
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDQ+c++LkDTdaw5
|
|
||||||
5spCu9MWMcvVdrYBZZ5qZy7DskphSUSQp25cIu34GJXVPNxtbWx1CQCmdLlwqXvo
|
|
||||||
PfUt5/pz9qsfhdAbzFduZQgGd7GTQOTJBDrAhm2+iVsQyGHHhF68muN+SgT+AtRE
|
|
||||||
sJyZoHNYtjjWEIHQ++FHEDqwUVnj6Ut99LHlyfCjOZ5+WyBiKCjyMNots/gDep7R
|
|
||||||
i4X2kMTqNMIIqPUcAaP5EQk41bJbFhKe915qN9b1dRISKFKmiWeOsxgTB/O/EaL5
|
|
||||||
LsBYwZ/BiIMDk30aZvzRJeloasIR3z4hrKQqBfB0lfeIdiPpJIs5rXJQEiWH89ge
|
|
||||||
gplsLbfrAgMBAAECggEBAKpMGaZzDPMF/v8Ee6lcZM2+cMyZPALxa+JsCakCvyh+
|
|
||||||
y7hSKVY+RM0cQ+YM/djTBkJtvrDniEMuasI803PAitI7nwJGSuyMXmehP6P9oKFO
|
|
||||||
jeLeZn6ETiSqzKJlmYE89vMeCevdqCnT5mW/wy5Smg0eGj0gIJpM2S3PJPSQpv9Z
|
|
||||||
ots0JXkwooJcpGWzlwPkjSouY2gDbE4Coi+jmYLNjA1k5RbggcutnUCZZkJ6yMNv
|
|
||||||
H52VjnkffpAFHRouK/YgF+5nbMyyw5YTLOyTWBq7qfBMsXynkWLU73GC/xDZa3yG
|
|
||||||
o/Ph2knXCjgLmCRessTOObdOXedjnGWIjiqF8fVboDECgYEA6x5CteYiwthDBULZ
|
|
||||||
CG5nE9VKkRHJYdArm+VjmGbzK51tKli112avmU4r3ol907+mEa4tWLkPqdZrrL49
|
|
||||||
aHltuHizZJixJcw0rcI302ot/Ov0gkF9V55gnAQS/Kemvx9FHWm5NHdYvbObzj33
|
|
||||||
bYRLJBtJWzYg9M8Bw9ZrUnegc/MCgYEA44kq5OSYCbyu3eaX8XHTtFhuQHNFjwl7
|
|
||||||
Xk/Oel6PVZzmt+oOlDHnOfGSB/KpR3YXxFRngiiPZzbrOwFyPGe7HIfg03HAXiJh
|
|
||||||
ivEfrPHbQqQUI/4b44GpDy6bhNtz777ivFGYEt21vpwd89rFiye+RkqF8eL/evxO
|
|
||||||
pUayDZYvwikCgYEA07wFoZ/lkAiHmpZPsxsRcrfzFd+pto9splEWtumHdbCo3ajT
|
|
||||||
4W5VFr9iHF8/VFDT8jokFjFaXL1/bCpKTOqFl8oC68XiSkKy8gPkmFyXm5y2LhNi
|
|
||||||
GGTFZdr5alRkgttbN5i9M/WCkhvMZRhC2Xp43MRB9IUzeqNtWHqhXbvjYGcCgYEA
|
|
||||||
vTMOztviLJ6PjYa0K5lp31l0+/SeD21j/y0/VPOSHi9kjeN7EfFZAw6DTkaSShDB
|
|
||||||
fIhutYVCkSHSgfMW6XGb3gKCiW/Z9KyEDYOowicuGgDTmoYu7IOhbzVjLhtJET7Z
|
|
||||||
zJvQZ0eiW4f3RBFTF/4JMuu+6z7FD6ADSV06qx+KQNkCgYBw26iQxmT5e/4kVv8X
|
|
||||||
DzBJ1HuliKBnnzZA1YRjB4H8F6Yrq+9qur1Lurez4YlbkGV8yPFt+Iu82ViUWL28
|
|
||||||
9T7Jgp3TOpf8qOqsWFv8HldpEZbE0Tcib4x6s+zOg/aw0ac/xOPY1sCVFB81VODP
|
|
||||||
XCar+uxMBXI1zbXqd9QdEwy4Ig==
|
|
||||||
-----END PRIVATE KEY-----
|
|
|
@ -1,25 +0,0 @@
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIESzCCArOgAwIBAgIQIi5xRZvFZaSweWU9Y5mExjANBgkqhkiG9w0BAQsFADCB
|
|
||||||
hzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMS4wLAYDVQQLDCVkcmVh
|
|
||||||
bWFjcm9ARHJlYW1hY3JvLmxvY2FsIChEcmVhbWFjcm8pMTUwMwYDVQQDDCxta2Nl
|
|
||||||
cnQgZHJlYW1hY3JvQERyZWFtYWNyby5sb2NhbCAoRHJlYW1hY3JvKTAeFw0yMTAz
|
|
||||||
MTcxNDQwMzZaFw0yMzA2MTcxNDQwMzZaMFkxJzAlBgNVBAoTHm1rY2VydCBkZXZl
|
|
||||||
bG9wbWVudCBjZXJ0aWZpY2F0ZTEuMCwGA1UECwwlZHJlYW1hY3JvQERyZWFtYWNy
|
|
||||||
by5sb2NhbCAoRHJlYW1hY3JvKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
|
|
||||||
ggEBAND5z74uQNN1rDnmykK70xYxy9V2tgFlnmpnLsOySmFJRJCnblwi7fgYldU8
|
|
||||||
3G1tbHUJAKZ0uXCpe+g99S3n+nP2qx+F0BvMV25lCAZ3sZNA5MkEOsCGbb6JWxDI
|
|
||||||
YceEXrya435KBP4C1ESwnJmgc1i2ONYQgdD74UcQOrBRWePpS330seXJ8KM5nn5b
|
|
||||||
IGIoKPIw2i2z+AN6ntGLhfaQxOo0wgio9RwBo/kRCTjVslsWEp73Xmo31vV1EhIo
|
|
||||||
UqaJZ46zGBMH878RovkuwFjBn8GIgwOTfRpm/NEl6WhqwhHfPiGspCoF8HSV94h2
|
|
||||||
I+kkizmtclASJYfz2B6CmWwtt+sCAwEAAaNgMF4wDgYDVR0PAQH/BAQDAgWgMBMG
|
|
||||||
A1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFO800LQ6Pa85RH4EbMmFH6ln
|
|
||||||
F150MBYGA1UdEQQPMA2CC2V4YW1wbGUub3JnMA0GCSqGSIb3DQEBCwUAA4IBgQAP
|
|
||||||
TsF53h7bvJcUXT3Y9yZ2vnW6xr9r92tNnM1Gfo3D2Yyn9oLf2YrfJng6WZ04Fhqa
|
|
||||||
Wh0HOvE0n6yPNpm/Q7mh64DrgolZ8Ce5H4RTJDAabHU9XhEzfGSVtzRSFsz+szu1
|
|
||||||
Y30IV+08DxxqMmNPspYdpAET2Lwyk2WhnARGiGw11CRkQCEkVEe6d702vS9UGBUz
|
|
||||||
Du6lmCYCm0SbFrZ0CGgmHSHoTcCtf3EjVam7dPg3yWiPbWjvhXxgip6hz9sCqkhG
|
|
||||||
WA5f+fPgSZ1I9U4i+uYnqjfrzwgC08RwUYordm15F6gPvXw+KVwDO8yUYQoEH0b6
|
|
||||||
AFJtbzoAXDysvBC6kWYFFOr62EaisaEkELTS/NrPD9ux1eKbxcxHCwEtVjgC0CL6
|
|
||||||
gAxEAQ+9maJMbrAFhsOBbGGFC+mMCGg4eEyx6+iMB0oQe0W7QFeRUAFi7Ptc/ocS
|
|
||||||
tZ9lbrfX1/wrcTTWIYWE+xH6oeb4fhs29kxjHcf2l+tQzmpl0aP3Z/bMW4BSB+w=
|
|
||||||
-----END CERTIFICATE-----
|
|
6
test/config/naive-quic.json
Normal file
6
test/config/naive-quic.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"listen": "socks://127.0.0.1:10001",
|
||||||
|
"proxy": "quic://sekai:password@example.org:10000",
|
||||||
|
"host-resolver-rules": "MAP example.org 127.0.0.1",
|
||||||
|
"log": ""
|
||||||
|
}
|
6
test/config/naive.json
Normal file
6
test/config/naive.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"listen": "socks://127.0.0.1:10001",
|
||||||
|
"proxy": "https://sekai:password@example.org:10000",
|
||||||
|
"host-resolver-rules": "MAP example.org 127.0.0.1",
|
||||||
|
"log": ""
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ import (
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing/common/debug"
|
"github.com/sagernet/sing/common/debug"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
|
@ -71,7 +72,9 @@ func startDockerContainer(t *testing.T, options DockerOptions) {
|
||||||
if len(options.Bind) > 0 {
|
if len(options.Bind) > 0 {
|
||||||
hostOptions.Binds = []string{}
|
hostOptions.Binds = []string{}
|
||||||
for path, internalPath := range options.Bind {
|
for path, internalPath := range options.Bind {
|
||||||
path = filepath.Join("config", path)
|
if !rw.FileExists(path) {
|
||||||
|
path = filepath.Join("config", path)
|
||||||
|
}
|
||||||
path, _ = filepath.Abs(path)
|
path, _ = filepath.Abs(path)
|
||||||
hostOptions.Binds = append(hostOptions.Binds, path+":"+internalPath)
|
hostOptions.Binds = append(hostOptions.Binds, path+":"+internalPath)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ require (
|
||||||
github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1
|
github.com/sagernet/sing-shadowsocks v0.0.0-20220801112336-a91eacdd01e1
|
||||||
github.com/spyzhov/ajson v0.7.1
|
github.com/spyzhov/ajson v0.7.1
|
||||||
github.com/stretchr/testify v1.8.0
|
github.com/stretchr/testify v1.8.0
|
||||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770
|
golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
|
@ -277,8 +277,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770 h1:dIi4qVdvjZEjiMDv7vhokAZNGnz3kepwuXqFKYDdDMs=
|
golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced h1:3dYNDff0VT5xj+mbj2XucFst9WKk6PdGOrb9n+SbIvw=
|
||||||
golang.org/x/net v0.0.0-20220809012201-f428fae20770/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
golang.org/x/net v0.0.0-20220809184613-07c6da5e1ced/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
|
88
test/mkcert.go
Normal file
88
test/mkcert.go
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/asn1"
|
||||||
|
"encoding/pem"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/rw"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func createSelfSignedCertificate(t *testing.T, domain string) (caPem, certPem, keyPem string) {
|
||||||
|
const userAndHostname = "sekai@nekohasekai.local"
|
||||||
|
tempDir, err := os.MkdirTemp("", "sing-box-test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
os.RemoveAll(tempDir)
|
||||||
|
})
|
||||||
|
caKey, err := rsa.GenerateKey(rand.Reader, 3072)
|
||||||
|
require.NoError(t, err)
|
||||||
|
spkiASN1, err := x509.MarshalPKIXPublicKey(caKey.Public())
|
||||||
|
var spki struct {
|
||||||
|
Algorithm pkix.AlgorithmIdentifier
|
||||||
|
SubjectPublicKey asn1.BitString
|
||||||
|
}
|
||||||
|
_, err = asn1.Unmarshal(spkiASN1, &spki)
|
||||||
|
require.NoError(t, err)
|
||||||
|
skid := sha1.Sum(spki.SubjectPublicKey.Bytes)
|
||||||
|
caTpl := &x509.Certificate{
|
||||||
|
SerialNumber: randomSerialNumber(t),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{"sing-box test CA"},
|
||||||
|
OrganizationalUnit: []string{userAndHostname},
|
||||||
|
CommonName: "sing-box " + userAndHostname,
|
||||||
|
},
|
||||||
|
SubjectKeyId: skid[:],
|
||||||
|
NotAfter: time.Now().AddDate(10, 0, 0),
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
KeyUsage: x509.KeyUsageCertSign,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: true,
|
||||||
|
MaxPathLenZero: true,
|
||||||
|
}
|
||||||
|
caCert, err := x509.CreateCertificate(rand.Reader, caTpl, caTpl, caKey.Public(), caKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = rw.WriteFile(filepath.Join(tempDir, "ca.pem"), pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: caCert}))
|
||||||
|
require.NoError(t, err)
|
||||||
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
domainTpl := &x509.Certificate{
|
||||||
|
SerialNumber: randomSerialNumber(t),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{"sing-box test certificate"},
|
||||||
|
OrganizationalUnit: []string{"sing-box " + userAndHostname},
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(), NotAfter: time.Now().AddDate(0, 0, 30),
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
}
|
||||||
|
domainTpl.DNSNames = append(domainTpl.DNSNames, domain)
|
||||||
|
cert, err := x509.CreateCertificate(rand.Reader, domainTpl, caTpl, key.Public(), caKey)
|
||||||
|
require.NoError(t, err)
|
||||||
|
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert})
|
||||||
|
privDER, err := x509.MarshalPKCS8PrivateKey(key)
|
||||||
|
require.NoError(t, err)
|
||||||
|
privPEM := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privDER})
|
||||||
|
err = rw.WriteFile(filepath.Join(tempDir, domain+".pem"), certPEM)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = rw.WriteFile(filepath.Join(tempDir, domain+".key.pem"), privPEM)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return filepath.Join(tempDir, "ca.pem"), filepath.Join(tempDir, domain+".pem"), filepath.Join(tempDir, domain+".key.pem")
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomSerialNumber(t *testing.T) *big.Int {
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return serialNumber
|
||||||
|
}
|
104
test/naive_test.go
Normal file
104
test/naive_test.go
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/auth"
|
||||||
|
"github.com/sagernet/sing/common/network"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNaiveInbound(t *testing.T) {
|
||||||
|
caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||||
|
startInstance(t, option.Options{
|
||||||
|
Log: &option.LogOptions{
|
||||||
|
Level: "error",
|
||||||
|
},
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeNaive,
|
||||||
|
NaiveOptions: option.NaiveInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: serverPort,
|
||||||
|
},
|
||||||
|
Users: []auth.User{
|
||||||
|
{
|
||||||
|
Username: "sekai",
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Network: network.NetworkTCP,
|
||||||
|
TLS: &option.InboundTLSOptions{
|
||||||
|
Enabled: true,
|
||||||
|
ServerName: "example.org",
|
||||||
|
CertificatePath: certPem,
|
||||||
|
KeyPath: keyPem,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
startDockerContainer(t, DockerOptions{
|
||||||
|
Image: ImageNaive,
|
||||||
|
Ports: []uint16{serverPort, clientPort},
|
||||||
|
Bind: map[string]string{
|
||||||
|
"naive.json": "/etc/naiveproxy/config.json",
|
||||||
|
caPem: "/etc/naiveproxy/ca.pem",
|
||||||
|
},
|
||||||
|
Env: []string{
|
||||||
|
"SSL_CERT_FILE=/etc/naiveproxy/ca.pem",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testTCP(t, clientPort, testPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNaiveHTTP3Inbound(t *testing.T) {
|
||||||
|
if !C.QUIC_AVAILABLE {
|
||||||
|
t.Skip("QUIC not included")
|
||||||
|
}
|
||||||
|
caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||||
|
startInstance(t, option.Options{
|
||||||
|
Log: &option.LogOptions{
|
||||||
|
Level: "error",
|
||||||
|
},
|
||||||
|
Inbounds: []option.Inbound{
|
||||||
|
{
|
||||||
|
Type: C.TypeNaive,
|
||||||
|
NaiveOptions: option.NaiveInboundOptions{
|
||||||
|
ListenOptions: option.ListenOptions{
|
||||||
|
Listen: option.ListenAddress(netip.IPv4Unspecified()),
|
||||||
|
ListenPort: serverPort,
|
||||||
|
},
|
||||||
|
Users: []auth.User{
|
||||||
|
{
|
||||||
|
Username: "sekai",
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Network: network.NetworkUDP,
|
||||||
|
TLS: &option.InboundTLSOptions{
|
||||||
|
Enabled: true,
|
||||||
|
ServerName: "example.org",
|
||||||
|
CertificatePath: certPem,
|
||||||
|
KeyPath: keyPem,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
startDockerContainer(t, DockerOptions{
|
||||||
|
Image: ImageNaive,
|
||||||
|
Ports: []uint16{serverPort, clientPort},
|
||||||
|
Bind: map[string]string{
|
||||||
|
"naive-quic.json": "/etc/naiveproxy/config.json",
|
||||||
|
caPem: "/etc/naiveproxy/ca.pem",
|
||||||
|
},
|
||||||
|
Env: []string{
|
||||||
|
"SSL_CERT_FILE=/etc/naiveproxy/ca.pem",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
testTCP(t, clientPort, testPort)
|
||||||
|
}
|
|
@ -9,13 +9,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTrojanOutbound(t *testing.T) {
|
func TestTrojanOutbound(t *testing.T) {
|
||||||
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||||
startDockerContainer(t, DockerOptions{
|
startDockerContainer(t, DockerOptions{
|
||||||
Image: ImageTrojan,
|
Image: ImageTrojan,
|
||||||
Ports: []uint16{serverPort, testPort},
|
Ports: []uint16{serverPort, testPort},
|
||||||
Bind: map[string]string{
|
Bind: map[string]string{
|
||||||
"trojan.json": "/config/config.json",
|
"trojan.json": "/config/config.json",
|
||||||
"example.org.pem": "/path/to/certificate.crt",
|
certPem: "/path/to/certificate.crt",
|
||||||
"example.org-key.pem": "/path/to/private.key",
|
keyPem: "/path/to/private.key",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
startInstance(t, option.Options{
|
startInstance(t, option.Options{
|
||||||
|
@ -45,7 +46,7 @@ func TestTrojanOutbound(t *testing.T) {
|
||||||
TLSOptions: &option.OutboundTLSOptions{
|
TLSOptions: &option.OutboundTLSOptions{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
ServerName: "example.org",
|
ServerName: "example.org",
|
||||||
CertificatePath: "config/example.org.pem",
|
CertificatePath: certPem,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -55,6 +56,7 @@ func TestTrojanOutbound(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTrojanSelf(t *testing.T) {
|
func TestTrojanSelf(t *testing.T) {
|
||||||
|
_, certPem, keyPem := createSelfSignedCertificate(t, "example.org")
|
||||||
startInstance(t, option.Options{
|
startInstance(t, option.Options{
|
||||||
Log: &option.LogOptions{
|
Log: &option.LogOptions{
|
||||||
Level: "error",
|
Level: "error",
|
||||||
|
@ -87,8 +89,8 @@ func TestTrojanSelf(t *testing.T) {
|
||||||
TLS: &option.InboundTLSOptions{
|
TLS: &option.InboundTLSOptions{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
ServerName: "example.org",
|
ServerName: "example.org",
|
||||||
CertificatePath: "config/example.org.pem",
|
CertificatePath: certPem,
|
||||||
KeyPath: "config/example.org-key.pem",
|
KeyPath: keyPem,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -109,7 +111,7 @@ func TestTrojanSelf(t *testing.T) {
|
||||||
TLSOptions: &option.OutboundTLSOptions{
|
TLSOptions: &option.OutboundTLSOptions{
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
ServerName: "example.org",
|
ServerName: "example.org",
|
||||||
CertificatePath: "config/example.org.pem",
|
CertificatePath: certPem,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue