mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-20 00:50:06 +08:00
feat: support mieru inbound (#2347)
This commit is contained in:
parent
ff76576cbe
commit
a4b76809ac
@ -38,6 +38,7 @@ const (
|
|||||||
TUIC
|
TUIC
|
||||||
HYSTERIA2
|
HYSTERIA2
|
||||||
ANYTLS
|
ANYTLS
|
||||||
|
MIERU
|
||||||
INNER
|
INNER
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -109,6 +110,8 @@ func (t Type) String() string {
|
|||||||
return "Hysteria2"
|
return "Hysteria2"
|
||||||
case ANYTLS:
|
case ANYTLS:
|
||||||
return "AnyTLS"
|
return "AnyTLS"
|
||||||
|
case MIERU:
|
||||||
|
return "Mieru"
|
||||||
case INNER:
|
case INNER:
|
||||||
return "Inner"
|
return "Inner"
|
||||||
default:
|
default:
|
||||||
@ -149,6 +152,8 @@ func ParseType(t string) (*Type, error) {
|
|||||||
res = HYSTERIA2
|
res = HYSTERIA2
|
||||||
case "ANYTLS":
|
case "ANYTLS":
|
||||||
res = ANYTLS
|
res = ANYTLS
|
||||||
|
case "MIERU":
|
||||||
|
res = MIERU
|
||||||
case "INNER":
|
case "INNER":
|
||||||
res = INNER
|
res = INNER
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -1556,6 +1556,15 @@ listeners:
|
|||||||
# -----END ECH KEYS-----
|
# -----END ECH KEYS-----
|
||||||
# padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme
|
# padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme
|
||||||
|
|
||||||
|
- name: mieru-in-1
|
||||||
|
type: mieru
|
||||||
|
port: 10818 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
|
listen: 0.0.0.0
|
||||||
|
transport: TCP # 支持 TCP 或者 UDP
|
||||||
|
users:
|
||||||
|
username1: password1
|
||||||
|
username2: password2
|
||||||
|
|
||||||
- name: trojan-in-1
|
- name: trojan-in-1
|
||||||
type: trojan
|
type: trojan
|
||||||
port: 10819 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
port: 10819 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
|
|||||||
2
go.mod
2
go.mod
@ -7,7 +7,7 @@ require (
|
|||||||
github.com/coreos/go-iptables v0.8.0
|
github.com/coreos/go-iptables v0.8.0
|
||||||
github.com/dlclark/regexp2 v1.11.5
|
github.com/dlclark/regexp2 v1.11.5
|
||||||
github.com/ebitengine/purego v0.9.0
|
github.com/ebitengine/purego v0.9.0
|
||||||
github.com/enfein/mieru/v3 v3.20.0
|
github.com/enfein/mieru/v3 v3.22.1
|
||||||
github.com/go-chi/chi/v5 v5.2.3
|
github.com/go-chi/chi/v5 v5.2.3
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/gobwas/ws v1.4.0
|
github.com/gobwas/ws v1.4.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -25,8 +25,8 @@ github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZ
|
|||||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
|
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
|
||||||
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/enfein/mieru/v3 v3.20.0 h1:1ob7pCIVSH5FYFAfYvim8isLW1vBOS4cFOUF9exJS38=
|
github.com/enfein/mieru/v3 v3.22.1 h1:/XGYYXpEhEJlxosmtbpEJkhtRLHB8IToG7LB8kU2ZDY=
|
||||||
github.com/enfein/mieru/v3 v3.20.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
github.com/enfein/mieru/v3 v3.22.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
|
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
|
||||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||||
|
|||||||
181
listener/inbound/mieru.go
Normal file
181
listener/inbound/mieru.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
|
"github.com/metacubex/mihomo/common/utils"
|
||||||
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
"github.com/metacubex/mihomo/listener/mieru"
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
|
mieruserver "github.com/enfein/mieru/v3/apis/server"
|
||||||
|
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mieru struct {
|
||||||
|
*Base
|
||||||
|
option *MieruOption
|
||||||
|
server mieruserver.Server
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type MieruOption struct {
|
||||||
|
BaseOption
|
||||||
|
Transport string `inbound:"transport"`
|
||||||
|
Users map[string]string `inbound:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type mieruListenerFactory struct{}
|
||||||
|
|
||||||
|
func (mieruListenerFactory) Listen(ctx context.Context, network, address string) (net.Listener, error) {
|
||||||
|
return inbound.ListenContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mieruListenerFactory) ListenPacket(ctx context.Context, network, address string) (net.PacketConn, error) {
|
||||||
|
return inbound.ListenPacketContext(ctx, network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMieru(option *MieruOption) (*Mieru, error) {
|
||||||
|
base, err := NewBase(&option.BaseOption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := buildMieruServerConfig(option, base.ports)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to build mieru server config: %w", err)
|
||||||
|
}
|
||||||
|
s := mieruserver.NewServer()
|
||||||
|
if err := s.Store(config); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to store mieru server config: %w", err)
|
||||||
|
}
|
||||||
|
// Server is started lazily when Listen() is called for the first time.
|
||||||
|
return &Mieru{
|
||||||
|
Base: base,
|
||||||
|
option: option,
|
||||||
|
server: s,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mieru) Config() C.InboundConfig {
|
||||||
|
return m.option
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mieru) Listen(tunnel C.Tunnel) error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if !m.server.IsRunning() {
|
||||||
|
if err := m.server.Start(); err != nil {
|
||||||
|
return fmt.Errorf("failed to start mieru server: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
additions := m.config.Additions()
|
||||||
|
if len(additions) == 0 {
|
||||||
|
additions = []inbound.Addition{
|
||||||
|
inbound.WithInName("DEFAULT-MIERU"),
|
||||||
|
inbound.WithSpecialRules(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
c, req, err := m.server.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if !m.server.IsRunning() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go mieru.Handle(c, tunnel, req, additions...)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
log.Infoln("Mieru[%s] proxy listening at: %s", m.Name(), m.Address())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mieru) Close() error {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.server.IsRunning() {
|
||||||
|
return m.server.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ C.InboundListener = (*Mieru)(nil)
|
||||||
|
|
||||||
|
func (o MieruOption) Equal(config C.InboundConfig) bool {
|
||||||
|
return optionToString(o) == optionToString(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMieruServerConfig(option *MieruOption, ports utils.IntRanges[uint16]) (*mieruserver.ServerConfig, error) {
|
||||||
|
if err := validateMieruOption(option); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to validate mieru option: %w", err)
|
||||||
|
}
|
||||||
|
if len(ports) == 0 {
|
||||||
|
return nil, fmt.Errorf("port is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
var transportProtocol *mierupb.TransportProtocol
|
||||||
|
switch option.Transport {
|
||||||
|
case "TCP":
|
||||||
|
transportProtocol = mierupb.TransportProtocol_TCP.Enum()
|
||||||
|
case "UDP":
|
||||||
|
transportProtocol = mierupb.TransportProtocol_UDP.Enum()
|
||||||
|
}
|
||||||
|
var portBindings []*mierupb.PortBinding
|
||||||
|
for _, portRange := range ports {
|
||||||
|
if portRange.Start() == portRange.End() {
|
||||||
|
portBindings = append(portBindings, &mierupb.PortBinding{
|
||||||
|
Port: proto.Int32(int32(portRange.Start())),
|
||||||
|
Protocol: transportProtocol,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
portBindings = append(portBindings, &mierupb.PortBinding{
|
||||||
|
PortRange: proto.String(fmt.Sprintf("%d-%d", portRange.Start(), portRange.End())),
|
||||||
|
Protocol: transportProtocol,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var users []*mierupb.User
|
||||||
|
for username, password := range option.Users {
|
||||||
|
users = append(users, &mierupb.User{
|
||||||
|
Name: proto.String(username),
|
||||||
|
Password: proto.String(password),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &mieruserver.ServerConfig{
|
||||||
|
Config: &mierupb.ServerConfig{
|
||||||
|
PortBindings: portBindings,
|
||||||
|
Users: users,
|
||||||
|
},
|
||||||
|
StreamListenerFactory: mieruListenerFactory{},
|
||||||
|
PacketListenerFactory: mieruListenerFactory{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateMieruOption(option *MieruOption) error {
|
||||||
|
if option.Transport != "TCP" && option.Transport != "UDP" {
|
||||||
|
return fmt.Errorf("transport must be TCP or UDP")
|
||||||
|
}
|
||||||
|
if len(option.Users) == 0 {
|
||||||
|
return fmt.Errorf("users is empty")
|
||||||
|
}
|
||||||
|
for username, password := range option.Users {
|
||||||
|
if username == "" {
|
||||||
|
return fmt.Errorf("username is empty")
|
||||||
|
}
|
||||||
|
if password == "" {
|
||||||
|
return fmt.Errorf("password is empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
212
listener/inbound/mieru_test.go
Normal file
212
listener/inbound/mieru_test.go
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
package inbound_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
|
"github.com/metacubex/mihomo/listener/inbound"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewMieru(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
option *inbound.MieruOption
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid with port",
|
||||||
|
args: args{
|
||||||
|
option: &inbound.MieruOption{
|
||||||
|
BaseOption: inbound.BaseOption{
|
||||||
|
Port: "8080",
|
||||||
|
},
|
||||||
|
Transport: "TCP",
|
||||||
|
Users: map[string]string{"user": "pass"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid with port range",
|
||||||
|
args: args{
|
||||||
|
option: &inbound.MieruOption{
|
||||||
|
BaseOption: inbound.BaseOption{
|
||||||
|
Port: "8090-8099",
|
||||||
|
},
|
||||||
|
Transport: "UDP",
|
||||||
|
Users: map[string]string{"user": "pass"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid mix of port and port-range",
|
||||||
|
args: args{
|
||||||
|
option: &inbound.MieruOption{
|
||||||
|
BaseOption: inbound.BaseOption{
|
||||||
|
Port: "8080,8090-8099",
|
||||||
|
},
|
||||||
|
Transport: "TCP",
|
||||||
|
Users: map[string]string{"user": "pass"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid - no port",
|
||||||
|
args: args{
|
||||||
|
option: &inbound.MieruOption{
|
||||||
|
Transport: "TCP",
|
||||||
|
Users: map[string]string{"user": "pass"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid - transport",
|
||||||
|
args: args{
|
||||||
|
option: &inbound.MieruOption{
|
||||||
|
BaseOption: inbound.BaseOption{
|
||||||
|
Port: "8080",
|
||||||
|
},
|
||||||
|
Transport: "INVALID",
|
||||||
|
Users: map[string]string{"user": "pass"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid - no transport",
|
||||||
|
args: args{
|
||||||
|
option: &inbound.MieruOption{
|
||||||
|
BaseOption: inbound.BaseOption{
|
||||||
|
Port: "8080",
|
||||||
|
},
|
||||||
|
Users: map[string]string{"user": "pass"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid - no users",
|
||||||
|
args: args{
|
||||||
|
option: &inbound.MieruOption{
|
||||||
|
BaseOption: inbound.BaseOption{
|
||||||
|
Port: "8080",
|
||||||
|
},
|
||||||
|
Transport: "TCP",
|
||||||
|
Users: map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid - empty username",
|
||||||
|
args: args{
|
||||||
|
option: &inbound.MieruOption{
|
||||||
|
BaseOption: inbound.BaseOption{
|
||||||
|
Port: "8080",
|
||||||
|
},
|
||||||
|
Transport: "TCP",
|
||||||
|
Users: map[string]string{"": "pass"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid - empty password",
|
||||||
|
args: args{
|
||||||
|
option: &inbound.MieruOption{
|
||||||
|
BaseOption: inbound.BaseOption{
|
||||||
|
Port: "8080",
|
||||||
|
},
|
||||||
|
Transport: "TCP",
|
||||||
|
Users: map[string]string{"user": ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := inbound.NewMieru(tt.args.option)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("NewMieru() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
got.Close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundMieru(t *testing.T) {
|
||||||
|
t.Run("HANDSHAKE_STANDARD", func(t *testing.T) {
|
||||||
|
testInboundMieruTCP(t, "HANDSHAKE_STANDARD")
|
||||||
|
})
|
||||||
|
t.Run("HANDSHAKE_NO_WAIT", func(t *testing.T) {
|
||||||
|
testInboundMieruTCP(t, "HANDSHAKE_NO_WAIT")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInboundMieruTCP(t *testing.T, handshakeMode string) {
|
||||||
|
t.Parallel()
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
port := l.Addr().(*net.TCPAddr).Port
|
||||||
|
l.Close()
|
||||||
|
|
||||||
|
inboundOptions := inbound.MieruOption{
|
||||||
|
BaseOption: inbound.BaseOption{
|
||||||
|
NameStr: "mieru_inbound",
|
||||||
|
Listen: "127.0.0.1",
|
||||||
|
Port: strconv.Itoa(port),
|
||||||
|
},
|
||||||
|
Transport: "TCP",
|
||||||
|
Users: map[string]string{"test": "password"},
|
||||||
|
}
|
||||||
|
in, err := inbound.NewMieru(&inboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel := NewHttpTestTunnel()
|
||||||
|
defer tunnel.Close()
|
||||||
|
|
||||||
|
err = in.Listen(tunnel)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
addrPort, err := netip.ParseAddrPort(in.Address())
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.MieruOption{
|
||||||
|
Name: "mieru_outbound",
|
||||||
|
Server: addrPort.Addr().String(),
|
||||||
|
Port: int(addrPort.Port()),
|
||||||
|
Transport: "TCP",
|
||||||
|
UserName: "test",
|
||||||
|
Password: "password",
|
||||||
|
HandshakeMode: handshakeMode,
|
||||||
|
}
|
||||||
|
out, err := outbound.NewMieru(outboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
tunnel.DoTest(t, out)
|
||||||
|
}
|
||||||
124
listener/mieru/server.go
Normal file
124
listener/mieru/server.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package mieru
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
"github.com/metacubex/mihomo/transport/socks5"
|
||||||
|
|
||||||
|
mierucommon "github.com/enfein/mieru/v3/apis/common"
|
||||||
|
mieruconstant "github.com/enfein/mieru/v3/apis/constant"
|
||||||
|
mierumodel "github.com/enfein/mieru/v3/apis/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Handle(conn net.Conn, tunnel C.Tunnel, request *mierumodel.Request, additions ...inbound.Addition) {
|
||||||
|
// Return a fake response to the client.
|
||||||
|
resp := &mierumodel.Response{
|
||||||
|
Reply: mieruconstant.Socks5ReplySuccess,
|
||||||
|
BindAddr: mierumodel.AddrSpec{
|
||||||
|
IP: net.IPv4zero,
|
||||||
|
Port: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := resp.WriteToSocks5(conn); err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the connection with tunnel.
|
||||||
|
metadata := mieruRequestToMetadata(request)
|
||||||
|
inbound.ApplyAdditions(&metadata, additions...)
|
||||||
|
switch metadata.NetWork {
|
||||||
|
case C.TCP:
|
||||||
|
tunnel.HandleTCPConn(conn, &metadata)
|
||||||
|
case C.UDP:
|
||||||
|
pc := mierucommon.NewPacketOverStreamTunnel(conn)
|
||||||
|
ep := N.NewEnhancePacketConn(pc)
|
||||||
|
for {
|
||||||
|
data, put, addr, err := ep.WaitReadFrom()
|
||||||
|
if err != nil {
|
||||||
|
if put != nil {
|
||||||
|
// Unresolved UDP packet, return buffer to the pool.
|
||||||
|
put()
|
||||||
|
}
|
||||||
|
// mieru returns EOF or ErrUnexpectedEOF when a session is closed.
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.ErrClosedPipe) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
target, payload, err := socks5.DecodeUDPPacket(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
packet := &packet{
|
||||||
|
pc: ep,
|
||||||
|
addr: addr,
|
||||||
|
payload: payload,
|
||||||
|
put: put,
|
||||||
|
}
|
||||||
|
tunnel.HandleUDPPacket(inbound.NewPacket(target, packet, C.MIERU, additions...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mieruRequestToMetadata(request *mierumodel.Request) C.Metadata {
|
||||||
|
m := C.Metadata{
|
||||||
|
DstPort: uint16(request.DstAddr.Port),
|
||||||
|
}
|
||||||
|
switch request.Command {
|
||||||
|
case mieruconstant.Socks5ConnectCmd:
|
||||||
|
m.NetWork = C.TCP
|
||||||
|
case mieruconstant.Socks5UDPAssociateCmd:
|
||||||
|
m.NetWork = C.UDP
|
||||||
|
}
|
||||||
|
if request.DstAddr.FQDN != "" {
|
||||||
|
m.Host = request.DstAddr.FQDN
|
||||||
|
} else if request.DstAddr.IP != nil {
|
||||||
|
m.DstIP, _ = netip.AddrFromSlice(request.DstAddr.IP)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
type packet struct {
|
||||||
|
pc net.PacketConn
|
||||||
|
addr net.Addr // source (i.e. remote) IP & Port of the packet
|
||||||
|
payload []byte
|
||||||
|
put func()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ C.UDPPacket = (*packet)(nil)
|
||||||
|
var _ C.UDPPacketInAddr = (*packet)(nil)
|
||||||
|
|
||||||
|
func (c *packet) Data() []byte {
|
||||||
|
return c.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||||
|
packet, err := socks5.EncodeUDPPacket(socks5.ParseAddrToSocksAddr(addr), b)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return c.pc.WriteTo(packet, c.addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) Drop() {
|
||||||
|
if c.put != nil {
|
||||||
|
c.put()
|
||||||
|
c.put = nil
|
||||||
|
}
|
||||||
|
c.payload = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) LocalAddr() net.Addr {
|
||||||
|
return c.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) InAddr() net.Addr {
|
||||||
|
return c.pc.LocalAddr()
|
||||||
|
}
|
||||||
@ -127,6 +127,13 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
listener, err = IN.NewAnyTLS(anytlsOption)
|
listener, err = IN.NewAnyTLS(anytlsOption)
|
||||||
|
case "mieru":
|
||||||
|
mieruOption := &IN.MieruOption{}
|
||||||
|
err = decoder.Decode(mapping, mieruOption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
listener, err = IN.NewMieru(mieruOption)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user