mirror of
https://gitclone.com/github.com/MetaCubeX/Clash.Meta
synced 2025-05-15 22:48:02 +08:00
feat: inbound support trojan
This commit is contained in:
parent
e23f40a56b
commit
91324b76d2
@ -28,6 +28,7 @@ const (
|
|||||||
VLESS
|
VLESS
|
||||||
REDIR
|
REDIR
|
||||||
TPROXY
|
TPROXY
|
||||||
|
TROJAN
|
||||||
TUNNEL
|
TUNNEL
|
||||||
TUN
|
TUN
|
||||||
TUIC
|
TUIC
|
||||||
@ -77,6 +78,8 @@ func (t Type) String() string {
|
|||||||
return "Redir"
|
return "Redir"
|
||||||
case TPROXY:
|
case TPROXY:
|
||||||
return "TProxy"
|
return "TProxy"
|
||||||
|
case TROJAN:
|
||||||
|
return "Trojan"
|
||||||
case TUNNEL:
|
case TUNNEL:
|
||||||
return "Tunnel"
|
return "Tunnel"
|
||||||
case TUN:
|
case TUN:
|
||||||
@ -115,6 +118,8 @@ func ParseType(t string) (*Type, error) {
|
|||||||
res = REDIR
|
res = REDIR
|
||||||
case "TPROXY":
|
case "TPROXY":
|
||||||
res = TPROXY
|
res = TPROXY
|
||||||
|
case "TROJAN":
|
||||||
|
res = TROJAN
|
||||||
case "TUNNEL":
|
case "TUNNEL":
|
||||||
res = TUNNEL
|
res = TUNNEL
|
||||||
case "TUN":
|
case "TUN":
|
||||||
|
@ -1238,6 +1238,33 @@ listeners:
|
|||||||
private-key: ./server.key
|
private-key: ./server.key
|
||||||
# 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: trojan-in-1
|
||||||
|
type: trojan
|
||||||
|
port: 10819
|
||||||
|
listen: 0.0.0.0
|
||||||
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||||
|
users:
|
||||||
|
- username: 1
|
||||||
|
password: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||||
|
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||||
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
|
certificate: ./server.crt
|
||||||
|
private-key: ./server.key
|
||||||
|
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
||||||
|
# reality-config:
|
||||||
|
# dest: test.com:443
|
||||||
|
# private-key: jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0 # 可由 mihomo generate reality-keypair 命令生成
|
||||||
|
# short-id:
|
||||||
|
# - 0123456789abcdef
|
||||||
|
# server-names:
|
||||||
|
# - test.com
|
||||||
|
# ss-option: # like trojan-go's `shadowsocks` config
|
||||||
|
# enabled: false
|
||||||
|
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
|
||||||
|
# password: "example"
|
||||||
|
### 注意,对于trojan listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “ss-option” 的其中一项 ###
|
||||||
|
|
||||||
- name: tun-in-1
|
- name: tun-in-1
|
||||||
type: tun
|
type: tun
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
|
2
go.mod
2
go.mod
@ -44,6 +44,7 @@ require (
|
|||||||
github.com/sagernet/sing v0.5.1
|
github.com/sagernet/sing v0.5.1
|
||||||
github.com/sagernet/sing-mux v0.2.1
|
github.com/sagernet/sing-mux v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.1.5
|
github.com/sagernet/sing-shadowtls v0.1.5
|
||||||
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||||
github.com/samber/lo v1.49.1
|
github.com/samber/lo v1.49.1
|
||||||
github.com/shirou/gopsutil/v4 v4.25.1
|
github.com/shirou/gopsutil/v4 v4.25.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
@ -98,7 +99,6 @@ require (
|
|||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
|
|
||||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
||||||
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
|
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
|
||||||
|
37
listener/config/trojan.go
Normal file
37
listener/config/trojan.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/listener/reality"
|
||||||
|
"github.com/metacubex/mihomo/listener/sing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TrojanUser struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrojanServer struct {
|
||||||
|
Enable bool
|
||||||
|
Listen string
|
||||||
|
Users []TrojanUser
|
||||||
|
WsPath string
|
||||||
|
Certificate string
|
||||||
|
PrivateKey string
|
||||||
|
RealityConfig reality.Config
|
||||||
|
MuxOption sing.MuxOption
|
||||||
|
TrojanSSOption TrojanSSOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
|
||||||
|
type TrojanSSOption struct {
|
||||||
|
Enabled bool
|
||||||
|
Method string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TrojanServer) String() string {
|
||||||
|
b, _ := json.Marshal(t)
|
||||||
|
return string(b)
|
||||||
|
}
|
108
listener/inbound/trojan.go
Normal file
108
listener/inbound/trojan.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
|
"github.com/metacubex/mihomo/listener/trojan"
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TrojanOption struct {
|
||||||
|
BaseOption
|
||||||
|
Users []TrojanUser `inbound:"users"`
|
||||||
|
WsPath string `inbound:"ws-path,omitempty"`
|
||||||
|
Certificate string `inbound:"certificate,omitempty"`
|
||||||
|
PrivateKey string `inbound:"private-key,omitempty"`
|
||||||
|
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||||
|
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
||||||
|
SSOption TrojanSSOption `inbound:"ss-option,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrojanUser struct {
|
||||||
|
Username string `inbound:"username,omitempty"`
|
||||||
|
Password string `inbound:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
|
||||||
|
type TrojanSSOption struct {
|
||||||
|
Enabled bool `inbound:"enabled,omitempty"`
|
||||||
|
Method string `inbound:"method,omitempty"`
|
||||||
|
Password string `inbound:"password,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o TrojanOption) Equal(config C.InboundConfig) bool {
|
||||||
|
return optionToString(o) == optionToString(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Trojan struct {
|
||||||
|
*Base
|
||||||
|
config *TrojanOption
|
||||||
|
l C.MultiAddrListener
|
||||||
|
vs LC.TrojanServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrojan(options *TrojanOption) (*Trojan, error) {
|
||||||
|
base, err := NewBase(&options.BaseOption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
users := make([]LC.TrojanUser, len(options.Users))
|
||||||
|
for i, v := range options.Users {
|
||||||
|
users[i] = LC.TrojanUser{
|
||||||
|
Username: v.Username,
|
||||||
|
Password: v.Password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Trojan{
|
||||||
|
Base: base,
|
||||||
|
config: options,
|
||||||
|
vs: LC.TrojanServer{
|
||||||
|
Enable: true,
|
||||||
|
Listen: base.RawAddress(),
|
||||||
|
Users: users,
|
||||||
|
WsPath: options.WsPath,
|
||||||
|
Certificate: options.Certificate,
|
||||||
|
PrivateKey: options.PrivateKey,
|
||||||
|
RealityConfig: options.RealityConfig.Build(),
|
||||||
|
MuxOption: options.MuxOption.Build(),
|
||||||
|
TrojanSSOption: LC.TrojanSSOption{
|
||||||
|
Enabled: options.SSOption.Enabled,
|
||||||
|
Method: options.SSOption.Method,
|
||||||
|
Password: options.SSOption.Password,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config implements constant.InboundListener
|
||||||
|
func (v *Trojan) Config() C.InboundConfig {
|
||||||
|
return v.config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address implements constant.InboundListener
|
||||||
|
func (v *Trojan) Address() string {
|
||||||
|
if v.l != nil {
|
||||||
|
for _, addr := range v.l.AddrList() {
|
||||||
|
return addr.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen implements constant.InboundListener
|
||||||
|
func (v *Trojan) Listen(tunnel C.Tunnel) error {
|
||||||
|
var err error
|
||||||
|
v.l, err = trojan.New(v.vs, tunnel, v.Additions()...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infoln("Trojan[%s] proxy listening at: %s", v.Name(), v.Address())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements constant.InboundListener
|
||||||
|
func (v *Trojan) Close() error {
|
||||||
|
return v.l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ C.InboundListener = (*Trojan)(nil)
|
@ -81,14 +81,6 @@ func (v *Vless) Address() string {
|
|||||||
// Listen implements constant.InboundListener
|
// Listen implements constant.InboundListener
|
||||||
func (v *Vless) Listen(tunnel C.Tunnel) error {
|
func (v *Vless) Listen(tunnel C.Tunnel) error {
|
||||||
var err error
|
var err error
|
||||||
users := make([]LC.VlessUser, len(v.config.Users))
|
|
||||||
for i, v := range v.config.Users {
|
|
||||||
users[i] = LC.VlessUser{
|
|
||||||
Username: v.Username,
|
|
||||||
UUID: v.UUID,
|
|
||||||
Flow: v.Flow,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.l, err = sing_vless.New(v.vs, tunnel, v.Additions()...)
|
v.l, err = sing_vless.New(v.vs, tunnel, v.Additions()...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -81,14 +81,6 @@ func (v *Vmess) Address() string {
|
|||||||
// Listen implements constant.InboundListener
|
// Listen implements constant.InboundListener
|
||||||
func (v *Vmess) Listen(tunnel C.Tunnel) error {
|
func (v *Vmess) Listen(tunnel C.Tunnel) error {
|
||||||
var err error
|
var err error
|
||||||
users := make([]LC.VmessUser, len(v.config.Users))
|
|
||||||
for i, v := range v.config.Users {
|
|
||||||
users[i] = LC.VmessUser{
|
|
||||||
Username: v.Username,
|
|
||||||
UUID: v.UUID,
|
|
||||||
AlterID: v.AlterID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v.l, err = sing_vmess.New(v.vs, tunnel, v.Additions()...)
|
v.l, err = sing_vmess.New(v.vs, tunnel, v.Additions()...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -93,6 +93,13 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
listener, err = IN.NewVless(vlessOption)
|
listener, err = IN.NewVless(vlessOption)
|
||||||
|
case "trojan":
|
||||||
|
trojanOption := &IN.TrojanOption{}
|
||||||
|
err = decoder.Decode(mapping, trojanOption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
listener, err = IN.NewTrojan(trojanOption)
|
||||||
case "hysteria2":
|
case "hysteria2":
|
||||||
hysteria2Option := &IN.Hysteria2Option{}
|
hysteria2Option := &IN.Hysteria2Option{}
|
||||||
err = decoder.Decode(mapping, hysteria2Option)
|
err = decoder.Decode(mapping, hysteria2Option)
|
||||||
|
43
listener/trojan/packet.go
Normal file
43
listener/trojan/packet.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package trojan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type packet struct {
|
||||||
|
pc net.PacketConn
|
||||||
|
rAddr net.Addr
|
||||||
|
payload []byte
|
||||||
|
put func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) Data() []byte {
|
||||||
|
return c.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
|
||||||
|
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||||
|
if addr == nil {
|
||||||
|
err = errors.New("address is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return c.pc.WriteTo(b, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the source IP/Port of UDP Packet
|
||||||
|
func (c *packet) LocalAddr() net.Addr {
|
||||||
|
return c.rAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) Drop() {
|
||||||
|
if c.put != nil {
|
||||||
|
c.put()
|
||||||
|
c.put = nil
|
||||||
|
}
|
||||||
|
c.payload = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *packet) InAddr() net.Addr {
|
||||||
|
return c.pc.LocalAddr()
|
||||||
|
}
|
271
listener/trojan/server.go
Normal file
271
listener/trojan/server.go
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
package trojan
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
|
"github.com/metacubex/mihomo/listener/reality"
|
||||||
|
"github.com/metacubex/mihomo/listener/sing"
|
||||||
|
"github.com/metacubex/mihomo/transport/shadowsocks/core"
|
||||||
|
"github.com/metacubex/mihomo/transport/socks5"
|
||||||
|
"github.com/metacubex/mihomo/transport/trojan"
|
||||||
|
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
|
||||||
|
|
||||||
|
"github.com/sagernet/smux"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Listener struct {
|
||||||
|
closed bool
|
||||||
|
config LC.TrojanServer
|
||||||
|
listeners []net.Listener
|
||||||
|
keys map[[trojan.KeyLength]byte]string
|
||||||
|
pickCipher core.Cipher
|
||||||
|
handler *sing.ListenerHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
|
||||||
|
if len(additions) == 0 {
|
||||||
|
additions = []inbound.Addition{
|
||||||
|
inbound.WithInName("DEFAULT-TROJAN"),
|
||||||
|
inbound.WithSpecialRules(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h, err := sing.NewListenerHandler(sing.ListenerConfig{
|
||||||
|
Tunnel: tunnel,
|
||||||
|
Type: C.TROJAN,
|
||||||
|
Additions: additions,
|
||||||
|
MuxOption: config.MuxOption,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := make(map[[trojan.KeyLength]byte]string)
|
||||||
|
for _, user := range config.Users {
|
||||||
|
keys[trojan.Key(user.Password)] = user.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
var pickCipher core.Cipher
|
||||||
|
if config.TrojanSSOption.Enabled {
|
||||||
|
if config.TrojanSSOption.Password == "" {
|
||||||
|
return nil, errors.New("empty password")
|
||||||
|
}
|
||||||
|
if config.TrojanSSOption.Method == "" {
|
||||||
|
config.TrojanSSOption.Method = "AES-128-GCM"
|
||||||
|
}
|
||||||
|
pickCipher, err = core.PickCipher(config.TrojanSSOption.Method, nil, config.TrojanSSOption.Password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sl = &Listener{false, config, nil, keys, pickCipher, h}
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{}
|
||||||
|
var realityBuilder *reality.Builder
|
||||||
|
var httpMux *http.ServeMux
|
||||||
|
|
||||||
|
if config.Certificate != "" && config.PrivateKey != "" {
|
||||||
|
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||||
|
}
|
||||||
|
if config.RealityConfig.PrivateKey != "" {
|
||||||
|
if tlsConfig.Certificates != nil {
|
||||||
|
return nil, errors.New("certificate is unavailable in reality")
|
||||||
|
}
|
||||||
|
realityBuilder, err = config.RealityConfig.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if config.WsPath != "" {
|
||||||
|
httpMux = http.NewServeMux()
|
||||||
|
httpMux.HandleFunc(config.WsPath, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
conn, err := mihomoVMess.StreamUpgradedWebsocketConn(w, r)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sl.HandleConn(conn, tunnel)
|
||||||
|
})
|
||||||
|
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range strings.Split(config.Listen, ",") {
|
||||||
|
addr := addr
|
||||||
|
|
||||||
|
//TCP
|
||||||
|
l, err := inbound.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if realityBuilder != nil {
|
||||||
|
l = realityBuilder.NewListener(l)
|
||||||
|
} else if len(tlsConfig.Certificates) > 0 {
|
||||||
|
l = tls.NewListener(l, tlsConfig)
|
||||||
|
} else if !config.TrojanSSOption.Enabled {
|
||||||
|
return nil, errors.New("disallow using Trojan without both certificates/reality/ss config")
|
||||||
|
}
|
||||||
|
sl.listeners = append(sl.listeners, l)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if httpMux != nil {
|
||||||
|
_ = http.Serve(l, httpMux)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if sl.closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
go sl.HandleConn(c, tunnel)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return sl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.closed = true
|
||||||
|
var retErr error
|
||||||
|
for _, lis := range l.listeners {
|
||||||
|
err := lis.Close()
|
||||||
|
if err != nil {
|
||||||
|
retErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Config() string {
|
||||||
|
return l.config.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) AddrList() (addrList []net.Addr) {
|
||||||
|
for _, lis := range l.listeners {
|
||||||
|
addrList = append(addrList, lis.Addr())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
if l.pickCipher != nil {
|
||||||
|
conn = l.pickCipher.StreamConn(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
var key [trojan.KeyLength]byte
|
||||||
|
if _, err := io.ReadFull(conn, key[:]); err != nil {
|
||||||
|
//log.Warnln("read key error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if user, ok := l.keys[key]; ok {
|
||||||
|
additions = append(additions, inbound.WithInUser(user))
|
||||||
|
} else {
|
||||||
|
//log.Warnln("no such key")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var crlf [2]byte
|
||||||
|
if _, err := io.ReadFull(conn, crlf[:]); err != nil {
|
||||||
|
//log.Warnln("read crlf error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.handleConn(false, conn, tunnel, additions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) handleConn(inMux bool, conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
|
||||||
|
if inMux {
|
||||||
|
defer conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
command, err := socks5.ReadByte(conn)
|
||||||
|
if err != nil {
|
||||||
|
//log.Warnln("read command error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch command {
|
||||||
|
case trojan.CommandTCP, trojan.CommandUDP, trojan.CommandMux:
|
||||||
|
default:
|
||||||
|
//log.Warnln("unknown command: %d", command)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err := socks5.ReadAddr0(conn)
|
||||||
|
if err != nil {
|
||||||
|
//log.Warnln("read target error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !inMux {
|
||||||
|
var crlf [2]byte
|
||||||
|
if _, err := io.ReadFull(conn, crlf[:]); err != nil {
|
||||||
|
//log.Warnln("read crlf error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch command {
|
||||||
|
case trojan.CommandTCP:
|
||||||
|
//tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.TROJAN, additions...))
|
||||||
|
l.handler.HandleSocket(target, conn, additions...)
|
||||||
|
case trojan.CommandUDP:
|
||||||
|
pc := trojan.NewPacketConn(conn)
|
||||||
|
for {
|
||||||
|
data, put, remoteAddr, err := pc.WaitReadFrom()
|
||||||
|
if err != nil {
|
||||||
|
if put != nil {
|
||||||
|
put()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cPacket := &packet{
|
||||||
|
pc: pc,
|
||||||
|
rAddr: remoteAddr,
|
||||||
|
payload: data,
|
||||||
|
put: put,
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel.HandleUDPPacket(inbound.NewPacket(target, cPacket, C.TROJAN, additions...))
|
||||||
|
}
|
||||||
|
case trojan.CommandMux:
|
||||||
|
if inMux {
|
||||||
|
//log.Warnln("invalid command: %d", command)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
smuxConfig := smux.DefaultConfig()
|
||||||
|
smuxConfig.KeepAliveDisabled = true
|
||||||
|
session, err := smux.Server(conn, smuxConfig)
|
||||||
|
if err != nil {
|
||||||
|
//log.Warnln("smux server error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer session.Close()
|
||||||
|
for {
|
||||||
|
stream, err := session.AcceptStream()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go l.handleConn(true, stream, tunnel, additions...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -38,10 +38,9 @@ type Command = byte
|
|||||||
const (
|
const (
|
||||||
CommandTCP byte = 1
|
CommandTCP byte = 1
|
||||||
CommandUDP byte = 3
|
CommandUDP byte = 3
|
||||||
|
CommandMux byte = 0x7f
|
||||||
|
|
||||||
// deprecated XTLS commands, as souvenirs
|
KeyLength = 56
|
||||||
commandXRD byte = 0xf0 // XTLS direct mode
|
|
||||||
commandXRO byte = 0xf1 // XTLS origin mode
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Option struct {
|
type Option struct {
|
||||||
@ -65,7 +64,7 @@ type WebsocketOption struct {
|
|||||||
|
|
||||||
type Trojan struct {
|
type Trojan struct {
|
||||||
option *Option
|
option *Option
|
||||||
hexPassword []byte
|
hexPassword [KeyLength]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trojan) StreamConn(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
func (t *Trojan) StreamConn(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
||||||
@ -152,7 +151,7 @@ func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) er
|
|||||||
buf := pool.GetBuffer()
|
buf := pool.GetBuffer()
|
||||||
defer pool.PutBuffer(buf)
|
defer pool.PutBuffer(buf)
|
||||||
|
|
||||||
buf.Write(t.hexPassword)
|
buf.Write(t.hexPassword[:])
|
||||||
buf.Write(crlf)
|
buf.Write(crlf)
|
||||||
|
|
||||||
buf.WriteByte(command)
|
buf.WriteByte(command)
|
||||||
@ -245,7 +244,7 @@ func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(option *Option) *Trojan {
|
func New(option *Option) *Trojan {
|
||||||
return &Trojan{option, hexSha224([]byte(option.Password))}
|
return &Trojan{option, Key(option.Password)}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ N.EnhancePacketConn = (*PacketConn)(nil)
|
var _ N.EnhancePacketConn = (*PacketConn)(nil)
|
||||||
@ -340,9 +339,12 @@ func (pc *PacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func hexSha224(data []byte) []byte {
|
func NewPacketConn(conn net.Conn) *PacketConn {
|
||||||
buf := make([]byte, 56)
|
return &PacketConn{Conn: conn}
|
||||||
hash := sha256.Sum224(data)
|
}
|
||||||
hex.Encode(buf, hash[:])
|
|
||||||
return buf
|
func Key(password string) (key [56]byte) {
|
||||||
|
hash := sha256.Sum224([]byte(password))
|
||||||
|
hex.Encode(key[:], hash[:])
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user