Skip to content

Commit 91324b7

Browse files
committed
feat: inbound support trojan
1 parent e23f40a commit 91324b7

File tree

11 files changed

+512
-28
lines changed

11 files changed

+512
-28
lines changed

constant/metadata.go

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const (
2828
VLESS
2929
REDIR
3030
TPROXY
31+
TROJAN
3132
TUNNEL
3233
TUN
3334
TUIC
@@ -77,6 +78,8 @@ func (t Type) String() string {
7778
return "Redir"
7879
case TPROXY:
7980
return "TProxy"
81+
case TROJAN:
82+
return "Trojan"
8083
case TUNNEL:
8184
return "Tunnel"
8285
case TUN:
@@ -115,6 +118,8 @@ func ParseType(t string) (*Type, error) {
115118
res = REDIR
116119
case "TPROXY":
117120
res = TPROXY
121+
case "TROJAN":
122+
res = TROJAN
118123
case "TUNNEL":
119124
res = TUNNEL
120125
case "TUN":

docs/config.yaml

+27
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,33 @@ listeners:
12381238
private-key: ./server.key
12391239
# padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme
12401240

1241+
- name: trojan-in-1
1242+
type: trojan
1243+
port: 10819
1244+
listen: 0.0.0.0
1245+
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
1246+
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
1247+
users:
1248+
- username: 1
1249+
password: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
1250+
# ws-path: "/" # 如果不为空则开启 websocket 传输层
1251+
# 下面两项如果填写则开启 tls(需要同时填写)
1252+
certificate: ./server.crt
1253+
private-key: ./server.key
1254+
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
1255+
# reality-config:
1256+
# dest: test.com:443
1257+
# private-key: jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0 # 可由 mihomo generate reality-keypair 命令生成
1258+
# short-id:
1259+
# - 0123456789abcdef
1260+
# server-names:
1261+
# - test.com
1262+
# ss-option: # like trojan-go's `shadowsocks` config
1263+
# enabled: false
1264+
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
1265+
# password: "example"
1266+
### 注意,对于trojan listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “ss-option” 的其中一项 ###
1267+
12411268
- name: tun-in-1
12421269
type: tun
12431270
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ require (
4444
github.com/sagernet/sing v0.5.1
4545
github.com/sagernet/sing-mux v0.2.1
4646
github.com/sagernet/sing-shadowtls v0.1.5
47+
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
4748
github.com/samber/lo v1.49.1
4849
github.com/shirou/gopsutil/v4 v4.25.1
4950
github.com/sirupsen/logrus v1.9.3
@@ -98,7 +99,6 @@ require (
9899
github.com/quic-go/qpack v0.4.0 // indirect
99100
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
100101
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
101-
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
102102
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
103103
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
104104
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect

listener/config/trojan.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package config
2+
3+
import (
4+
"encoding/json"
5+
6+
"github.com/metacubex/mihomo/listener/reality"
7+
"github.com/metacubex/mihomo/listener/sing"
8+
)
9+
10+
type TrojanUser struct {
11+
Username string
12+
Password string
13+
}
14+
15+
type TrojanServer struct {
16+
Enable bool
17+
Listen string
18+
Users []TrojanUser
19+
WsPath string
20+
Certificate string
21+
PrivateKey string
22+
RealityConfig reality.Config
23+
MuxOption sing.MuxOption
24+
TrojanSSOption TrojanSSOption
25+
}
26+
27+
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
28+
type TrojanSSOption struct {
29+
Enabled bool
30+
Method string
31+
Password string
32+
}
33+
34+
func (t TrojanServer) String() string {
35+
b, _ := json.Marshal(t)
36+
return string(b)
37+
}

listener/inbound/trojan.go

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package inbound
2+
3+
import (
4+
C "github.com/metacubex/mihomo/constant"
5+
LC "github.com/metacubex/mihomo/listener/config"
6+
"github.com/metacubex/mihomo/listener/trojan"
7+
"github.com/metacubex/mihomo/log"
8+
)
9+
10+
type TrojanOption struct {
11+
BaseOption
12+
Users []TrojanUser `inbound:"users"`
13+
WsPath string `inbound:"ws-path,omitempty"`
14+
Certificate string `inbound:"certificate,omitempty"`
15+
PrivateKey string `inbound:"private-key,omitempty"`
16+
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
17+
MuxOption MuxOption `inbound:"mux-option,omitempty"`
18+
SSOption TrojanSSOption `inbound:"ss-option,omitempty"`
19+
}
20+
21+
type TrojanUser struct {
22+
Username string `inbound:"username,omitempty"`
23+
Password string `inbound:"password"`
24+
}
25+
26+
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
27+
type TrojanSSOption struct {
28+
Enabled bool `inbound:"enabled,omitempty"`
29+
Method string `inbound:"method,omitempty"`
30+
Password string `inbound:"password,omitempty"`
31+
}
32+
33+
func (o TrojanOption) Equal(config C.InboundConfig) bool {
34+
return optionToString(o) == optionToString(config)
35+
}
36+
37+
type Trojan struct {
38+
*Base
39+
config *TrojanOption
40+
l C.MultiAddrListener
41+
vs LC.TrojanServer
42+
}
43+
44+
func NewTrojan(options *TrojanOption) (*Trojan, error) {
45+
base, err := NewBase(&options.BaseOption)
46+
if err != nil {
47+
return nil, err
48+
}
49+
users := make([]LC.TrojanUser, len(options.Users))
50+
for i, v := range options.Users {
51+
users[i] = LC.TrojanUser{
52+
Username: v.Username,
53+
Password: v.Password,
54+
}
55+
}
56+
return &Trojan{
57+
Base: base,
58+
config: options,
59+
vs: LC.TrojanServer{
60+
Enable: true,
61+
Listen: base.RawAddress(),
62+
Users: users,
63+
WsPath: options.WsPath,
64+
Certificate: options.Certificate,
65+
PrivateKey: options.PrivateKey,
66+
RealityConfig: options.RealityConfig.Build(),
67+
MuxOption: options.MuxOption.Build(),
68+
TrojanSSOption: LC.TrojanSSOption{
69+
Enabled: options.SSOption.Enabled,
70+
Method: options.SSOption.Method,
71+
Password: options.SSOption.Password,
72+
},
73+
},
74+
}, nil
75+
}
76+
77+
// Config implements constant.InboundListener
78+
func (v *Trojan) Config() C.InboundConfig {
79+
return v.config
80+
}
81+
82+
// Address implements constant.InboundListener
83+
func (v *Trojan) Address() string {
84+
if v.l != nil {
85+
for _, addr := range v.l.AddrList() {
86+
return addr.String()
87+
}
88+
}
89+
return ""
90+
}
91+
92+
// Listen implements constant.InboundListener
93+
func (v *Trojan) Listen(tunnel C.Tunnel) error {
94+
var err error
95+
v.l, err = trojan.New(v.vs, tunnel, v.Additions()...)
96+
if err != nil {
97+
return err
98+
}
99+
log.Infoln("Trojan[%s] proxy listening at: %s", v.Name(), v.Address())
100+
return nil
101+
}
102+
103+
// Close implements constant.InboundListener
104+
func (v *Trojan) Close() error {
105+
return v.l.Close()
106+
}
107+
108+
var _ C.InboundListener = (*Trojan)(nil)

listener/inbound/vless.go

-8
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,6 @@ func (v *Vless) Address() string {
8181
// Listen implements constant.InboundListener
8282
func (v *Vless) Listen(tunnel C.Tunnel) error {
8383
var err error
84-
users := make([]LC.VlessUser, len(v.config.Users))
85-
for i, v := range v.config.Users {
86-
users[i] = LC.VlessUser{
87-
Username: v.Username,
88-
UUID: v.UUID,
89-
Flow: v.Flow,
90-
}
91-
}
9284
v.l, err = sing_vless.New(v.vs, tunnel, v.Additions()...)
9385
if err != nil {
9486
return err

listener/inbound/vmess.go

-8
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,6 @@ func (v *Vmess) Address() string {
8181
// Listen implements constant.InboundListener
8282
func (v *Vmess) Listen(tunnel C.Tunnel) error {
8383
var err error
84-
users := make([]LC.VmessUser, len(v.config.Users))
85-
for i, v := range v.config.Users {
86-
users[i] = LC.VmessUser{
87-
Username: v.Username,
88-
UUID: v.UUID,
89-
AlterID: v.AlterID,
90-
}
91-
}
9284
v.l, err = sing_vmess.New(v.vs, tunnel, v.Additions()...)
9385
if err != nil {
9486
return err

listener/parse.go

+7
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,13 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) {
9393
return nil, err
9494
}
9595
listener, err = IN.NewVless(vlessOption)
96+
case "trojan":
97+
trojanOption := &IN.TrojanOption{}
98+
err = decoder.Decode(mapping, trojanOption)
99+
if err != nil {
100+
return nil, err
101+
}
102+
listener, err = IN.NewTrojan(trojanOption)
96103
case "hysteria2":
97104
hysteria2Option := &IN.Hysteria2Option{}
98105
err = decoder.Decode(mapping, hysteria2Option)

listener/trojan/packet.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package trojan
2+
3+
import (
4+
"errors"
5+
"net"
6+
)
7+
8+
type packet struct {
9+
pc net.PacketConn
10+
rAddr net.Addr
11+
payload []byte
12+
put func()
13+
}
14+
15+
func (c *packet) Data() []byte {
16+
return c.payload
17+
}
18+
19+
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
20+
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
21+
if addr == nil {
22+
err = errors.New("address is invalid")
23+
return
24+
}
25+
return c.pc.WriteTo(b, addr)
26+
}
27+
28+
// LocalAddr returns the source IP/Port of UDP Packet
29+
func (c *packet) LocalAddr() net.Addr {
30+
return c.rAddr
31+
}
32+
33+
func (c *packet) Drop() {
34+
if c.put != nil {
35+
c.put()
36+
c.put = nil
37+
}
38+
c.payload = nil
39+
}
40+
41+
func (c *packet) InAddr() net.Addr {
42+
return c.pc.LocalAddr()
43+
}

0 commit comments

Comments
 (0)