mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 04:38:28 +10:00
Compare commits
3 Commits
copilot/im
...
v1.12.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0cd3422c1 | ||
|
|
e385a98ced | ||
|
|
670f32baee |
2
.github/setup_go_for_windows7.sh
vendored
2
.github/setup_go_for_windows7.sh
vendored
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
VERSION="1.25.4"
|
VERSION="1.25.5"
|
||||||
|
|
||||||
mkdir -p $HOME/go
|
mkdir -p $HOME/go
|
||||||
cd $HOME/go
|
cd $HOME/go
|
||||||
|
|||||||
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ^1.25.5
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -110,7 +110,7 @@ jobs:
|
|||||||
if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }}
|
if: ${{ ! (matrix.legacy_win7 || matrix.legacy_go124) }}
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ^1.25.5
|
||||||
- name: Setup Go 1.24
|
- name: Setup Go 1.24
|
||||||
if: matrix.legacy_go124
|
if: matrix.legacy_go124
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
@@ -123,7 +123,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/go/go_win7
|
~/go/go_win7
|
||||||
key: go_win7_1254
|
key: go_win7_1255
|
||||||
- name: Setup Go for Windows 7
|
- name: Setup Go for Windows 7
|
||||||
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
if: matrix.legacy_win7 && steps.cache-go-for-windows7.outputs.cache-hit != 'true'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -300,7 +300,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ^1.25.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -380,7 +380,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ^1.25.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
@@ -479,7 +479,7 @@ jobs:
|
|||||||
if: matrix.if
|
if: matrix.if
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ^1.25.5
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
|
|||||||
4
.github/workflows/linux.yml
vendored
4
.github/workflows/linux.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ^1.25.5
|
||||||
- name: Check input version
|
- name: Check input version
|
||||||
if: github.event_name == 'workflow_dispatch'
|
if: github.event_name == 'workflow_dispatch'
|
||||||
run: |-
|
run: |-
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ^1.25.4
|
go-version: ^1.25.5
|
||||||
- name: Setup Android NDK
|
- name: Setup Android NDK
|
||||||
if: matrix.os == 'android'
|
if: matrix.os == 'android'
|
||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
|
|||||||
Submodule clients/android updated: a4e3c00f0f...f3763ba71d
Submodule clients/apple updated: 84d8cf1757...532c140f05
@@ -2,6 +2,17 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### 1.12.13
|
||||||
|
|
||||||
|
* Fix naive inbound
|
||||||
|
* Fixes and improvements
|
||||||
|
|
||||||
|
__Unfortunately, for non-technical reasons, we are currently unable to notarize the standalone version of the macOS client:
|
||||||
|
because system extensions require signatures to function, we have had to temporarily halt its release.__
|
||||||
|
|
||||||
|
__We plan to fix the App Store release issue and launch a new standalone desktop client, but until then,
|
||||||
|
only clients on TestFlight will be available (unless you have an Apple Developer Program and compile from source code).__
|
||||||
|
|
||||||
#### 1.12.12
|
#### 1.12.12
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ platform-specific function implementation, such as TUN transparent proxy impleme
|
|||||||
|
|
||||||
!!! failure ""
|
!!! failure ""
|
||||||
|
|
||||||
We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we violated the rules (TestFlight users are not affected).
|
Due to non-technical reasons, we are temporarily unable to update the sing-box app on the App Store and release the standalone version of the macOS client (TestFlight users are not affected)
|
||||||
|
|
||||||
## :material-graph: Requirements
|
## :material-graph: Requirements
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ platform-specific function implementation, such as TUN transparent proxy impleme
|
|||||||
|
|
||||||
## :material-download: Download
|
## :material-download: Download
|
||||||
|
|
||||||
* [App Store](https://apps.apple.com/app/sing-box-vt/id6673731168)
|
* ~~[App Store](https://apps.apple.com/app/sing-box-vt/id6673731168)~~
|
||||||
* TestFlight (Beta)
|
* TestFlight (Beta)
|
||||||
|
|
||||||
TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)
|
TestFlight quota is only available to [sponsors](https://github.com/sponsors/nekohasekai)
|
||||||
@@ -26,15 +26,15 @@ TestFlight quota is only available to [sponsors](https://github.com/sponsors/nek
|
|||||||
Once you donate, you can get an invitation by join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot)
|
Once you donate, you can get an invitation by join our Telegram group for sponsors from [@yet_another_sponsor_bot](https://t.me/yet_another_sponsor_bot)
|
||||||
or sending us your Apple ID [via email](mailto:contact@sagernet.org).
|
or sending us your Apple ID [via email](mailto:contact@sagernet.org).
|
||||||
|
|
||||||
## :material-file-download: Download (macOS standalone version)
|
## ~~:material-file-download: Download (macOS standalone version)~~
|
||||||
|
|
||||||
* [Homebrew Cask](https://formulae.brew.sh/cask/sfm)
|
* ~~[Homebrew Cask](https://formulae.brew.sh/cask/sfm)~~
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install sfm
|
# brew install sfm
|
||||||
```
|
```
|
||||||
|
|
||||||
* [GitHub Releases](https://github.com/SagerNet/sing-box/releases)
|
* ~~[GitHub Releases](https://github.com/SagerNet/sing-box/releases)~~
|
||||||
|
|
||||||
## :material-source-repository: Source code
|
## :material-source-repository: Source code
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package naive
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@@ -22,7 +22,11 @@ import (
|
|||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
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"
|
||||||
|
aTLS "github.com/sagernet/sing/common/tls"
|
||||||
sHttp "github.com/sagernet/sing/protocol/http"
|
sHttp "github.com/sagernet/sing/protocol/http"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ConfigureHTTP3ListenerFunc func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error)
|
var ConfigureHTTP3ListenerFunc func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error)
|
||||||
@@ -82,16 +86,11 @@ func (n *Inbound) Start(stage adapter.StartStage) error {
|
|||||||
if stage != adapter.StartStateStart {
|
if stage != adapter.StartStateStart {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var tlsConfig *tls.STDConfig
|
|
||||||
if n.tlsConfig != nil {
|
if n.tlsConfig != nil {
|
||||||
err := n.tlsConfig.Start()
|
err := n.tlsConfig.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "create TLS config")
|
return E.Cause(err, "create TLS config")
|
||||||
}
|
}
|
||||||
tlsConfig, err = n.tlsConfig.Config()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if common.Contains(n.network, N.NetworkTCP) {
|
if common.Contains(n.network, N.NetworkTCP) {
|
||||||
tcpListener, err := n.listener.ListenTCP()
|
tcpListener, err := n.listener.ListenTCP()
|
||||||
@@ -99,20 +98,23 @@ func (n *Inbound) Start(stage adapter.StartStage) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
n.httpServer = &http.Server{
|
n.httpServer = &http.Server{
|
||||||
Handler: n,
|
Handler: h2c.NewHandler(n, &http2.Server{}),
|
||||||
TLSConfig: tlsConfig,
|
|
||||||
BaseContext: func(listener net.Listener) context.Context {
|
BaseContext: func(listener net.Listener) context.Context {
|
||||||
return n.ctx
|
return n.ctx
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
var sErr error
|
listener := net.Listener(tcpListener)
|
||||||
if tlsConfig != nil {
|
if n.tlsConfig != nil {
|
||||||
sErr = n.httpServer.ServeTLS(tcpListener, "", "")
|
if len(n.tlsConfig.NextProtos()) == 0 {
|
||||||
} else {
|
n.tlsConfig.SetNextProtos([]string{http2.NextProtoTLS, "http/1.1"})
|
||||||
sErr = n.httpServer.Serve(tcpListener)
|
} else if !common.Contains(n.tlsConfig.NextProtos(), http2.NextProtoTLS) {
|
||||||
|
n.tlsConfig.SetNextProtos(append([]string{http2.NextProtoTLS}, n.tlsConfig.NextProtos()...))
|
||||||
|
}
|
||||||
|
listener = aTLS.NewListener(tcpListener, n.tlsConfig)
|
||||||
}
|
}
|
||||||
if sErr != nil && !E.IsClosedOrCanceled(sErr) {
|
sErr := n.httpServer.Serve(listener)
|
||||||
|
if sErr != nil && !errors.Is(sErr, http.ErrServerClosed) {
|
||||||
n.logger.Error("http server serve error: ", sErr)
|
n.logger.Error("http server serve error: ", sErr)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -161,13 +163,16 @@ func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|||||||
n.badRequest(ctx, request, E.New("authorization failed"))
|
n.badRequest(ctx, request, E.New("authorization failed"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writer.Header().Set("Padding", generateNaivePaddingHeader())
|
writer.Header().Set("Padding", generatePaddingHeader())
|
||||||
writer.WriteHeader(http.StatusOK)
|
writer.WriteHeader(http.StatusOK)
|
||||||
writer.(http.Flusher).Flush()
|
writer.(http.Flusher).Flush()
|
||||||
|
|
||||||
hostPort := request.URL.Host
|
hostPort := request.Header.Get("-connect-authority")
|
||||||
if hostPort == "" {
|
if hostPort == "" {
|
||||||
hostPort = request.Host
|
hostPort = request.URL.Host
|
||||||
|
if hostPort == "" {
|
||||||
|
hostPort = request.Host
|
||||||
|
}
|
||||||
}
|
}
|
||||||
source := sHttp.SourceAddress(request)
|
source := sHttp.SourceAddress(request)
|
||||||
destination := M.ParseSocksaddr(hostPort).Unwrap()
|
destination := M.ParseSocksaddr(hostPort).Unwrap()
|
||||||
@@ -178,9 +183,14 @@ func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
|||||||
n.badRequest(ctx, request, E.New("hijack failed"))
|
n.badRequest(ctx, request, E.New("hijack failed"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
n.newConnection(ctx, false, &naiveH1Conn{Conn: conn}, userName, source, destination)
|
n.newConnection(ctx, false, &naiveConn{Conn: conn}, userName, source, destination)
|
||||||
} else {
|
} else {
|
||||||
n.newConnection(ctx, true, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination)
|
n.newConnection(ctx, true, &naiveH2Conn{
|
||||||
|
reader: request.Body,
|
||||||
|
writer: writer,
|
||||||
|
flusher: writer.(http.Flusher),
|
||||||
|
remoteAddress: source,
|
||||||
|
}, userName, source, destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,18 +246,3 @@ func rejectHTTP(writer http.ResponseWriter, statusCode int) {
|
|||||||
}
|
}
|
||||||
conn.Close()
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,417 +7,242 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
|
"github.com/sagernet/sing/common/baderror"
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
"github.com/sagernet/sing/common/rw"
|
"github.com/sagernet/sing/common/rw"
|
||||||
)
|
)
|
||||||
|
|
||||||
const kFirstPaddings = 8
|
const paddingCount = 8
|
||||||
|
|
||||||
type naiveH1Conn struct {
|
func generatePaddingHeader() string {
|
||||||
net.Conn
|
paddingLen := rand.Intn(32) + 30
|
||||||
|
padding := make([]byte, paddingLen)
|
||||||
|
bits := rand.Uint64()
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
padding[i] = "!#$()+<>?@[]^`{}"[bits&15]
|
||||||
|
bits >>= 4
|
||||||
|
}
|
||||||
|
for i := 16; i < paddingLen; i++ {
|
||||||
|
padding[i] = '~'
|
||||||
|
}
|
||||||
|
return string(padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
type paddingConn struct {
|
||||||
readPadding int
|
readPadding int
|
||||||
writePadding int
|
writePadding int
|
||||||
readRemaining int
|
readRemaining int
|
||||||
paddingRemaining int
|
paddingRemaining int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH1Conn) Read(p []byte) (n int, err error) {
|
func (p *paddingConn) readWithPadding(reader io.Reader, buffer []byte) (n int, err error) {
|
||||||
n, err = c.read(p)
|
if p.readRemaining > 0 {
|
||||||
return n, wrapHttpError(err)
|
if len(buffer) > p.readRemaining {
|
||||||
}
|
buffer = buffer[:p.readRemaining]
|
||||||
|
|
||||||
func (c *naiveH1Conn) read(p []byte) (n int, err error) {
|
|
||||||
if c.readRemaining > 0 {
|
|
||||||
if len(p) > c.readRemaining {
|
|
||||||
p = p[:c.readRemaining]
|
|
||||||
}
|
}
|
||||||
n, err = c.Conn.Read(p)
|
n, err = reader.Read(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.readRemaining -= n
|
p.readRemaining -= n
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.paddingRemaining > 0 {
|
if p.paddingRemaining > 0 {
|
||||||
err = rw.SkipN(c.Conn, c.paddingRemaining)
|
err = rw.SkipN(reader, p.paddingRemaining)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.paddingRemaining = 0
|
p.paddingRemaining = 0
|
||||||
}
|
}
|
||||||
if c.readPadding < kFirstPaddings {
|
if p.readPadding < paddingCount {
|
||||||
var paddingHdr []byte
|
var paddingHeader []byte
|
||||||
if len(p) >= 3 {
|
if len(buffer) >= 3 {
|
||||||
paddingHdr = p[:3]
|
paddingHeader = buffer[:3]
|
||||||
} else {
|
} else {
|
||||||
paddingHdr = make([]byte, 3)
|
paddingHeader = make([]byte, 3)
|
||||||
}
|
}
|
||||||
_, err = io.ReadFull(c.Conn, paddingHdr)
|
_, err = io.ReadFull(reader, paddingHeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
originalDataSize := int(binary.BigEndian.Uint16(paddingHdr[:2]))
|
originalDataSize := int(binary.BigEndian.Uint16(paddingHeader[:2]))
|
||||||
paddingSize := int(paddingHdr[2])
|
paddingSize := int(paddingHeader[2])
|
||||||
if len(p) > originalDataSize {
|
if len(buffer) > originalDataSize {
|
||||||
p = p[:originalDataSize]
|
buffer = buffer[:originalDataSize]
|
||||||
}
|
}
|
||||||
n, err = c.Conn.Read(p)
|
n, err = reader.Read(buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.readPadding++
|
p.readPadding++
|
||||||
c.readRemaining = originalDataSize - n
|
p.readRemaining = originalDataSize - n
|
||||||
c.paddingRemaining = paddingSize
|
p.paddingRemaining = paddingSize
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return c.Conn.Read(p)
|
return reader.Read(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH1Conn) Write(p []byte) (n int, err error) {
|
func (p *paddingConn) writeWithPadding(writer io.Writer, data []byte) (n int, err error) {
|
||||||
for pLen := len(p); pLen > 0; {
|
if p.writePadding < paddingCount {
|
||||||
var data []byte
|
|
||||||
if pLen > 65535 {
|
|
||||||
data = p[:65535]
|
|
||||||
p = p[65535:]
|
|
||||||
pLen -= 65535
|
|
||||||
} else {
|
|
||||||
data = p
|
|
||||||
pLen = 0
|
|
||||||
}
|
|
||||||
var writeN int
|
|
||||||
writeN, err = c.write(data)
|
|
||||||
n += writeN
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) write(p []byte) (n int, err error) {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
paddingSize := rand.Intn(256)
|
paddingSize := rand.Intn(256)
|
||||||
|
buffer := buf.NewSize(3 + len(data) + paddingSize)
|
||||||
buffer := buf.NewSize(3 + len(p) + paddingSize)
|
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
header := buffer.Extend(3)
|
header := buffer.Extend(3)
|
||||||
binary.BigEndian.PutUint16(header, uint16(len(p)))
|
binary.BigEndian.PutUint16(header, uint16(len(data)))
|
||||||
header[2] = byte(paddingSize)
|
header[2] = byte(paddingSize)
|
||||||
|
common.Must1(buffer.Write(data))
|
||||||
common.Must1(buffer.Write(p))
|
_, err = writer.Write(buffer.Bytes())
|
||||||
_, err = c.Conn.Write(buffer.Bytes())
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
n = len(p)
|
n = len(data)
|
||||||
}
|
}
|
||||||
c.writePadding++
|
p.writePadding++
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return c.Conn.Write(p)
|
return writer.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH1Conn) FrontHeadroom() int {
|
func (p *paddingConn) writeBufferWithPadding(writer io.Writer, buffer *buf.Buffer) error {
|
||||||
if c.writePadding < kFirstPaddings {
|
if p.writePadding < paddingCount {
|
||||||
return 3
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) RearHeadroom() int {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
return 255
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) WriterMTU() int {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
return 65535
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|
||||||
defer buffer.Release()
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
bufferLen := buffer.Len()
|
bufferLen := buffer.Len()
|
||||||
if bufferLen > 65535 {
|
if bufferLen > 65535 {
|
||||||
return common.Error(c.Write(buffer.Bytes()))
|
_, err := p.writeChunked(writer, buffer.Bytes())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
paddingSize := rand.Intn(256)
|
paddingSize := rand.Intn(256)
|
||||||
header := buffer.ExtendHeader(3)
|
header := buffer.ExtendHeader(3)
|
||||||
binary.BigEndian.PutUint16(header, uint16(bufferLen))
|
binary.BigEndian.PutUint16(header, uint16(bufferLen))
|
||||||
header[2] = byte(paddingSize)
|
header[2] = byte(paddingSize)
|
||||||
buffer.Extend(paddingSize)
|
buffer.Extend(paddingSize)
|
||||||
c.writePadding++
|
p.writePadding++
|
||||||
}
|
}
|
||||||
return wrapHttpError(common.Error(c.Conn.Write(buffer.Bytes())))
|
return common.Error(writer.Write(buffer.Bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME
|
func (p *paddingConn) writeChunked(writer io.Writer, data []byte) (n int, err error) {
|
||||||
/*func (c *naiveH1Conn) WriteTo(w io.Writer) (n int64, err error) {
|
for len(data) > 0 {
|
||||||
if c.readPadding < kFirstPaddings {
|
var chunk []byte
|
||||||
n, err = bufio.WriteToN(c, w, kFirstPaddings-c.readPadding)
|
if len(data) > 65535 {
|
||||||
} else {
|
chunk = data[:65535]
|
||||||
n, err = bufio.Copy(w, c.Conn)
|
data = data[65535:]
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
n, err = bufio.ReadFromN(c, r, kFirstPaddings-c.writePadding)
|
|
||||||
} else {
|
|
||||||
n, err = bufio.Copy(c.Conn, r)
|
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) Upstream() any {
|
|
||||||
return c.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) ReaderReplaceable() bool {
|
|
||||||
return c.readPadding == kFirstPaddings
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH1Conn) WriterReplaceable() bool {
|
|
||||||
return c.writePadding == kFirstPaddings
|
|
||||||
}
|
|
||||||
|
|
||||||
type naiveH2Conn struct {
|
|
||||||
reader io.Reader
|
|
||||||
writer io.Writer
|
|
||||||
flusher http.Flusher
|
|
||||||
rAddr net.Addr
|
|
||||||
readPadding int
|
|
||||||
writePadding int
|
|
||||||
readRemaining int
|
|
||||||
paddingRemaining int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) Read(p []byte) (n int, err error) {
|
|
||||||
n, err = c.read(p)
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) read(p []byte) (n int, err error) {
|
|
||||||
if c.readRemaining > 0 {
|
|
||||||
if len(p) > c.readRemaining {
|
|
||||||
p = p[:c.readRemaining]
|
|
||||||
}
|
|
||||||
n, err = c.reader.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.paddingRemaining = 0
|
|
||||||
}
|
|
||||||
if c.readPadding < kFirstPaddings {
|
|
||||||
var paddingHdr []byte
|
|
||||||
if len(p) >= 3 {
|
|
||||||
paddingHdr = p[:3]
|
|
||||||
} else {
|
} else {
|
||||||
paddingHdr = make([]byte, 3)
|
chunk = data
|
||||||
|
data = nil
|
||||||
}
|
}
|
||||||
_, err = io.ReadFull(c.reader, paddingHdr)
|
var written int
|
||||||
|
written, err = p.writeWithPadding(writer, chunk)
|
||||||
|
n += written
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
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)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) Write(p []byte) (n int, err error) {
|
func (p *paddingConn) frontHeadroom() int {
|
||||||
for pLen := len(p); pLen > 0; {
|
if p.writePadding < paddingCount {
|
||||||
var data []byte
|
|
||||||
if pLen > 65535 {
|
|
||||||
data = p[:65535]
|
|
||||||
p = p[65535:]
|
|
||||||
pLen -= 65535
|
|
||||||
} else {
|
|
||||||
data = p
|
|
||||||
pLen = 0
|
|
||||||
}
|
|
||||||
var writeN int
|
|
||||||
writeN, err = c.write(data)
|
|
||||||
n += writeN
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
c.flusher.Flush()
|
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) write(p []byte) (n int, err error) {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
paddingSize := rand.Intn(256)
|
|
||||||
|
|
||||||
buffer := buf.NewSize(3 + len(p) + paddingSize)
|
|
||||||
defer buffer.Release()
|
|
||||||
header := buffer.Extend(3)
|
|
||||||
binary.BigEndian.PutUint16(header, uint16(len(p)))
|
|
||||||
header[2] = byte(paddingSize)
|
|
||||||
|
|
||||||
common.Must1(buffer.Write(p))
|
|
||||||
_, err = c.writer.Write(buffer.Bytes())
|
|
||||||
if err == nil {
|
|
||||||
n = len(p)
|
|
||||||
}
|
|
||||||
c.writePadding++
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.writer.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) FrontHeadroom() int {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
return 3
|
return 3
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) RearHeadroom() int {
|
func (p *paddingConn) rearHeadroom() int {
|
||||||
if c.writePadding < kFirstPaddings {
|
if p.writePadding < paddingCount {
|
||||||
return 255
|
return 255
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) WriterMTU() int {
|
func (p *paddingConn) writerMTU() int {
|
||||||
if c.writePadding < kFirstPaddings {
|
if p.writePadding < paddingCount {
|
||||||
return 65535
|
return 65535
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *paddingConn) readerReplaceable() bool {
|
||||||
|
return p.readPadding == paddingCount
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *paddingConn) writerReplaceable() bool {
|
||||||
|
return p.writePadding == paddingCount
|
||||||
|
}
|
||||||
|
|
||||||
|
type naiveConn struct {
|
||||||
|
net.Conn
|
||||||
|
paddingConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveConn) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = c.readWithPadding(c.Conn, p)
|
||||||
|
return n, baderror.WrapH2(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveConn) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = c.writeChunked(c.Conn, p)
|
||||||
|
return n, baderror.WrapH2(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveConn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
|
defer buffer.Release()
|
||||||
|
err := c.writeBufferWithPadding(c.Conn, buffer)
|
||||||
|
return baderror.WrapH2(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveConn) FrontHeadroom() int { return c.frontHeadroom() }
|
||||||
|
func (c *naiveConn) RearHeadroom() int { return c.rearHeadroom() }
|
||||||
|
func (c *naiveConn) WriterMTU() int { return c.writerMTU() }
|
||||||
|
func (c *naiveConn) Upstream() any { return c.Conn }
|
||||||
|
func (c *naiveConn) ReaderReplaceable() bool { return c.readerReplaceable() }
|
||||||
|
func (c *naiveConn) WriterReplaceable() bool { return c.writerReplaceable() }
|
||||||
|
|
||||||
|
type naiveH2Conn struct {
|
||||||
|
reader io.Reader
|
||||||
|
writer io.Writer
|
||||||
|
flusher http.Flusher
|
||||||
|
remoteAddress net.Addr
|
||||||
|
paddingConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveH2Conn) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = c.readWithPadding(c.reader, p)
|
||||||
|
return n, baderror.WrapH2(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *naiveH2Conn) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = c.writeChunked(c.writer, p)
|
||||||
|
if err == nil {
|
||||||
|
c.flusher.Flush()
|
||||||
|
}
|
||||||
|
return n, baderror.WrapH2(err)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
|
func (c *naiveH2Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
defer buffer.Release()
|
defer buffer.Release()
|
||||||
if c.writePadding < kFirstPaddings {
|
err := c.writeBufferWithPadding(c.writer, buffer)
|
||||||
bufferLen := buffer.Len()
|
|
||||||
if bufferLen > 65535 {
|
|
||||||
return common.Error(c.Write(buffer.Bytes()))
|
|
||||||
}
|
|
||||||
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 {
|
if err == nil {
|
||||||
c.flusher.Flush()
|
c.flusher.Flush()
|
||||||
}
|
}
|
||||||
return wrapHttpError(err)
|
return baderror.WrapH2(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME
|
|
||||||
/*func (c *naiveH2Conn) WriteTo(w io.Writer) (n int64, err error) {
|
|
||||||
if c.readPadding < kFirstPaddings {
|
|
||||||
n, err = bufio.WriteToN(c, w, kFirstPaddings-c.readPadding)
|
|
||||||
} else {
|
|
||||||
n, err = bufio.Copy(w, c.reader)
|
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) ReadFrom(r io.Reader) (n int64, err error) {
|
|
||||||
if c.writePadding < kFirstPaddings {
|
|
||||||
n, err = bufio.ReadFromN(c, r, kFirstPaddings-c.writePadding)
|
|
||||||
} else {
|
|
||||||
n, err = bufio.Copy(c.writer, r)
|
|
||||||
}
|
|
||||||
return n, wrapHttpError(err)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) Close() error {
|
func (c *naiveH2Conn) Close() error {
|
||||||
return common.Close(
|
return common.Close(c.reader, c.writer)
|
||||||
c.reader,
|
|
||||||
c.writer,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *naiveH2Conn) LocalAddr() net.Addr {
|
func (c *naiveH2Conn) LocalAddr() net.Addr { return M.Socksaddr{} }
|
||||||
return M.Socksaddr{}
|
func (c *naiveH2Conn) RemoteAddr() net.Addr { return c.remoteAddress }
|
||||||
}
|
func (c *naiveH2Conn) SetDeadline(t time.Time) error { return os.ErrInvalid }
|
||||||
|
func (c *naiveH2Conn) SetReadDeadline(t time.Time) error { return os.ErrInvalid }
|
||||||
func (c *naiveH2Conn) RemoteAddr() net.Addr {
|
func (c *naiveH2Conn) SetWriteDeadline(t time.Time) error { return os.ErrInvalid }
|
||||||
return c.rAddr
|
func (c *naiveH2Conn) NeedAdditionalReadDeadline() bool { return true }
|
||||||
}
|
func (c *naiveH2Conn) UpstreamReader() any { return c.reader }
|
||||||
|
func (c *naiveH2Conn) UpstreamWriter() any { return c.writer }
|
||||||
func (c *naiveH2Conn) SetDeadline(t time.Time) error {
|
func (c *naiveH2Conn) FrontHeadroom() int { return c.frontHeadroom() }
|
||||||
return os.ErrInvalid
|
func (c *naiveH2Conn) RearHeadroom() int { return c.rearHeadroom() }
|
||||||
}
|
func (c *naiveH2Conn) WriterMTU() int { return c.writerMTU() }
|
||||||
|
func (c *naiveH2Conn) ReaderReplaceable() bool { return c.readerReplaceable() }
|
||||||
func (c *naiveH2Conn) SetReadDeadline(t time.Time) error {
|
func (c *naiveH2Conn) WriterReplaceable() bool { return c.writerReplaceable() }
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) SetWriteDeadline(t time.Time) error {
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) NeedAdditionalReadDeadline() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) UpstreamReader() any {
|
|
||||||
return c.reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) UpstreamWriter() any {
|
|
||||||
return c.writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) ReaderReplaceable() bool {
|
|
||||||
return c.readPadding == kFirstPaddings
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *naiveH2Conn) WriterReplaceable() bool {
|
|
||||||
return c.writePadding == kFirstPaddings
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapHttpError(err error) error {
|
|
||||||
if err == nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if strings.Contains(err.Error(), "client disconnected") {
|
|
||||||
return net.ErrClosed
|
|
||||||
}
|
|
||||||
if strings.Contains(err.Error(), "body closed by handler") {
|
|
||||||
return net.ErrClosed
|
|
||||||
}
|
|
||||||
if strings.Contains(err.Error(), "canceled with error code 268") {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user