mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-06 14:07:39 +03:00
commit
0626a3e505
6 changed files with 228 additions and 36 deletions
22
README.md
22
README.md
|
@ -16,7 +16,7 @@
|
|||
|
||||
[中文 README](README.zh.md)
|
||||
|
||||
Hysteria is a TCP relay & SOCKS5/HTTP proxy tool optimized for networks of poor quality (e.g. satellite connections,
|
||||
Hysteria is a TCP/UDP relay & SOCKS5/HTTP proxy tool optimized for networks of poor quality (e.g. satellite connections,
|
||||
congested public Wi-Fi, connecting from China to servers abroad) powered by a custom version of QUIC protocol.
|
||||
|
||||
It is essentially a spiritual successor of my abandoned project https://github.com/dragonite-network/dragonite-java
|
||||
|
@ -87,14 +87,19 @@ Same as the server side, create a `config.json` under the root directory of the
|
|||
"http": {
|
||||
"listen": "127.0.0.1:8080"
|
||||
},
|
||||
"relay": {
|
||||
"relay_tcp": {
|
||||
"listen": "127.0.0.1:2222",
|
||||
"remote": "123.123.123.123:22"
|
||||
},
|
||||
"relay_udp": {
|
||||
"listen": "127.0.0.1:5333",
|
||||
"remote": "8.8.8.8:53"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This config enables a SOCKS5 proxy (with both TCP & UDP support), an HTTP proxy, and a TCP relay to `123.123.123.123:22`
|
||||
This config enables a SOCKS5 proxy (with both TCP & UDP support), an HTTP proxy, a TCP relay to `123.123.123.123:22` and
|
||||
a UDP relay to `8.8.8.8:53`
|
||||
at the same time. Please modify or remove these entries according to your actual needs.
|
||||
|
||||
If your server certificate is not issued by a trusted CA, you need to specify the CA used
|
||||
|
@ -217,11 +222,16 @@ hysteria_traffic_uplink_bytes_total{auth="aGFja2VyISE="} 37452
|
|||
"cert": "/home/ubuntu/my_cert.crt", // Cert file (HTTPS proxy)
|
||||
"key": "/home/ubuntu/my_key.crt" // Key file (HTTPS proxy)
|
||||
},
|
||||
"relay": {
|
||||
"listen": "127.0.0.1:2222", // Relay listen address
|
||||
"remote": "123.123.123.123:22", // Relay remote address
|
||||
"relay_tcp": {
|
||||
"listen": "127.0.0.1:2222", // TCP relay Listen address
|
||||
"remote": "123.123.123.123:22", // TCP relay remote address
|
||||
"timeout": 300 // TCP timeout in seconds
|
||||
},
|
||||
"relay_udp": {
|
||||
"listen": "127.0.0.1:5333", // UDP relay Listen address
|
||||
"remote": "8.8.8.8:53", // UDP relay remote address
|
||||
"timeout": 60 // UDP session timeout in seconds
|
||||
},
|
||||
"acl": "my_list.acl", // See ACL below
|
||||
"obfs": "AMOGUS", // Obfuscation password
|
||||
"auth": "[BASE64]", // Authentication payload in Base64
|
||||
|
|
22
README.zh.md
22
README.zh.md
|
@ -14,7 +14,7 @@
|
|||
|
||||
[6]: https://t.me/hysteria_github
|
||||
|
||||
Hysteria 是专门针对恶劣网络环境进行优化的 TCP 连接转发和代理工具(双边加速),比如卫星网络、拥挤的公共 Wi-Fi、在中国连接国外服务器等。
|
||||
Hysteria 是专门针对恶劣网络环境进行优化的 TCP/UDP 转发和代理工具(双边加速),比如卫星网络、拥挤的公共 Wi-Fi、在中国连接国外服务器等。
|
||||
基于修改版的 QUIC 协议。
|
||||
|
||||
是我此前弃坑的项目 https://github.com/dragonite-network/dragonite-java 的续作。
|
||||
|
@ -80,14 +80,19 @@ Hysteria 是专门针对恶劣网络环境进行优化的 TCP 连接转发和代
|
|||
"http": {
|
||||
"listen": "127.0.0.1:8080"
|
||||
},
|
||||
"relay": {
|
||||
"relay_tcp": {
|
||||
"listen": "127.0.0.1:2222",
|
||||
"remote": "123.123.123.123:22"
|
||||
},
|
||||
"relay_udp": {
|
||||
"listen": "127.0.0.1:5333",
|
||||
"remote": "8.8.8.8:53"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个配置同时开了 SOCK5 (支持 TCP & UDP) 代理,HTTP 代理和到 `123.123.123.123:22` 的 TCP 转发。请根据自己实际需要修改和删减。
|
||||
这个配置同时开了 SOCK5 (支持 TCP & UDP) 代理,HTTP 代理,到 `123.123.123.123:22` 的 TCP 转发和到 `8.8.8.8:53` 的 UDP 转发。
|
||||
请根据自己实际需要修改和删减。
|
||||
|
||||
如果你的服务端证书不是由受信任的 CA 签发的,需要用 `"ca": "/path/to/file.ca"` 指定使用的 CA 或者用 `"insecure": true` 忽略所有
|
||||
证书错误(不推荐)。
|
||||
|
@ -205,11 +210,16 @@ hysteria_traffic_uplink_bytes_total{auth="aGFja2VyISE="} 37452
|
|||
"cert": "/home/ubuntu/my_cert.crt", // 证书 (变为 HTTPS 代理)
|
||||
"key": "/home/ubuntu/my_key.crt" // 证书密钥 (变为 HTTPS 代理)
|
||||
},
|
||||
"relay": {
|
||||
"listen": "127.0.0.1:2222", // 转发监听地址
|
||||
"remote": "123.123.123.123:22", // 转发目标地址
|
||||
"relay_tcp": {
|
||||
"listen": "127.0.0.1:2222", // TCP 转发监听地址
|
||||
"remote": "123.123.123.123:22", // TCP 转发目标地址
|
||||
"timeout": 300 // TCP 超时秒数
|
||||
},
|
||||
"relay_udp": {
|
||||
"listen": "127.0.0.1:5333", // UDP 转发监听地址
|
||||
"remote": "8.8.8.8:53", // UDP 转发目标地址
|
||||
"timeout": 60 // UDP 超时秒数
|
||||
},
|
||||
"acl": "my_list.acl", // 见下文 ACL
|
||||
"obfs": "AMOGUS", // 混淆密码
|
||||
"auth": "[BASE64]", // Base64 验证密钥
|
||||
|
|
|
@ -181,10 +181,10 @@ func client(config *clientConfig) {
|
|||
}()
|
||||
}
|
||||
|
||||
if len(config.Relay.Listen) > 0 {
|
||||
if len(config.TCPRelay.Listen) > 0 {
|
||||
go func() {
|
||||
rl, err := relay.NewRelay(client, config.Relay.Listen, config.Relay.Remote,
|
||||
time.Duration(config.Relay.Timeout)*time.Second,
|
||||
rl, err := relay.NewTCPRelay(client, config.TCPRelay.Listen, config.TCPRelay.Remote,
|
||||
time.Duration(config.TCPRelay.Timeout)*time.Second,
|
||||
func(addr net.Addr) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
|
@ -201,12 +201,40 @@ func client(config *clientConfig) {
|
|||
"src": addr.String(),
|
||||
}).Debug("TCP relay EOF")
|
||||
}
|
||||
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize TCP relay")
|
||||
}
|
||||
logrus.WithField("addr", config.Relay.Listen).Info("TCP relay up and running")
|
||||
logrus.WithField("addr", config.TCPRelay.Listen).Info("TCP relay up and running")
|
||||
errChan <- rl.ListenAndServe()
|
||||
}()
|
||||
}
|
||||
|
||||
if len(config.UDPRelay.Listen) > 0 {
|
||||
go func() {
|
||||
rl, err := relay.NewUDPRelay(client, config.UDPRelay.Listen, config.UDPRelay.Remote,
|
||||
time.Duration(config.UDPRelay.Timeout)*time.Second,
|
||||
func(addr net.Addr) {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
}).Debug("UDP relay request")
|
||||
},
|
||||
func(addr net.Addr, err error) {
|
||||
if err != relay.ErrTimeout {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"error": err,
|
||||
"src": addr.String(),
|
||||
}).Info("UDP relay error")
|
||||
} else {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"src": addr.String(),
|
||||
}).Debug("UDP relay session closed")
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithField("error", err).Fatal("Failed to initialize UDP relay")
|
||||
}
|
||||
logrus.WithField("addr", config.UDPRelay.Listen).Info("UDP relay up and running")
|
||||
errChan <- rl.ListenAndServe()
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -80,11 +80,16 @@ type clientConfig struct {
|
|||
Cert string `json:"cert"`
|
||||
Key string `json:"key"`
|
||||
} `json:"http"`
|
||||
Relay struct {
|
||||
TCPRelay struct {
|
||||
Listen string `json:"listen"`
|
||||
Remote string `json:"remote"`
|
||||
Timeout int `json:"timeout"`
|
||||
} `json:"relay"`
|
||||
} `json:"relay_tcp"`
|
||||
UDPRelay struct {
|
||||
Listen string `json:"listen"`
|
||||
Remote string `json:"remote"`
|
||||
Timeout int `json:"timeout"`
|
||||
} `json:"relay_udp"`
|
||||
ACL string `json:"acl"`
|
||||
Obfs string `json:"obfs"`
|
||||
Auth []byte `json:"auth"`
|
||||
|
@ -96,11 +101,15 @@ type clientConfig struct {
|
|||
}
|
||||
|
||||
func (c *clientConfig) Check() error {
|
||||
if len(c.SOCKS5.Listen) == 0 && len(c.HTTP.Listen) == 0 && len(c.Relay.Listen) == 0 {
|
||||
return errors.New("no SOCKS5, HTTP or relay listen address")
|
||||
if len(c.SOCKS5.Listen) == 0 && len(c.HTTP.Listen) == 0 &&
|
||||
len(c.TCPRelay.Listen) == 0 && len(c.UDPRelay.Listen) == 0 {
|
||||
return errors.New("no SOCKS5, HTTP, TCP relay or UDP relay listen address")
|
||||
}
|
||||
if len(c.Relay.Listen) > 0 && len(c.Relay.Remote) == 0 {
|
||||
return errors.New("no relay remote address")
|
||||
if len(c.TCPRelay.Listen) > 0 && len(c.TCPRelay.Remote) == 0 {
|
||||
return errors.New("no TCP relay remote address")
|
||||
}
|
||||
if len(c.UDPRelay.Listen) > 0 && len(c.UDPRelay.Remote) == 0 {
|
||||
return errors.New("no UDP relay remote address")
|
||||
}
|
||||
if c.SOCKS5.Timeout != 0 && c.SOCKS5.Timeout <= 4 {
|
||||
return errors.New("invalid SOCKS5 timeout")
|
||||
|
@ -108,8 +117,11 @@ func (c *clientConfig) Check() error {
|
|||
if c.HTTP.Timeout != 0 && c.HTTP.Timeout <= 4 {
|
||||
return errors.New("invalid HTTP timeout")
|
||||
}
|
||||
if c.Relay.Timeout != 0 && c.Relay.Timeout <= 4 {
|
||||
return errors.New("invalid relay timeout")
|
||||
if c.TCPRelay.Timeout != 0 && c.TCPRelay.Timeout <= 4 {
|
||||
return errors.New("invalid TCP relay timeout")
|
||||
}
|
||||
if c.UDPRelay.Timeout != 0 && c.UDPRelay.Timeout <= 4 {
|
||||
return errors.New("invalid UDP relay timeout")
|
||||
}
|
||||
if len(c.Server) == 0 {
|
||||
return errors.New("no server address")
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
type TCPRelay struct {
|
||||
HyClient *core.Client
|
||||
ListenAddr *net.TCPAddr
|
||||
Remote string
|
||||
|
@ -15,17 +15,15 @@ type Relay struct {
|
|||
|
||||
ConnFunc func(addr net.Addr)
|
||||
ErrorFunc func(addr net.Addr, err error)
|
||||
|
||||
tcpListener *net.TCPListener
|
||||
}
|
||||
|
||||
func NewRelay(hyClient *core.Client, listen, remote string, timeout time.Duration,
|
||||
connFunc func(addr net.Addr), errorFunc func(addr net.Addr, err error)) (*Relay, error) {
|
||||
func NewTCPRelay(hyClient *core.Client, listen, remote string, timeout time.Duration,
|
||||
connFunc func(addr net.Addr), errorFunc func(addr net.Addr, err error)) (*TCPRelay, error) {
|
||||
tAddr, err := net.ResolveTCPAddr("tcp", listen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &Relay{
|
||||
r := &TCPRelay{
|
||||
HyClient: hyClient,
|
||||
ListenAddr: tAddr,
|
||||
Remote: remote,
|
||||
|
@ -36,15 +34,14 @@ func NewRelay(hyClient *core.Client, listen, remote string, timeout time.Duratio
|
|||
return r, nil
|
||||
}
|
||||
|
||||
func (r *Relay) ListenAndServe() error {
|
||||
var err error
|
||||
r.tcpListener, err = net.ListenTCP("tcp", r.ListenAddr)
|
||||
func (r *TCPRelay) ListenAndServe() error {
|
||||
listener, err := net.ListenTCP("tcp", r.ListenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.tcpListener.Close()
|
||||
defer listener.Close()
|
||||
for {
|
||||
c, err := r.tcpListener.AcceptTCP()
|
||||
c, err := listener.AcceptTCP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
135
pkg/relay/udp.go
Normal file
135
pkg/relay/udp.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
package relay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const udpBufferSize = 65535
|
||||
|
||||
const udpMinTimeout = 4 * time.Second
|
||||
|
||||
var ErrTimeout = errors.New("inactivity timeout")
|
||||
|
||||
type UDPRelay struct {
|
||||
HyClient *core.Client
|
||||
ListenAddr *net.UDPAddr
|
||||
Remote string
|
||||
Timeout time.Duration
|
||||
|
||||
ConnFunc func(addr net.Addr)
|
||||
ErrorFunc func(addr net.Addr, err error)
|
||||
}
|
||||
|
||||
func NewUDPRelay(hyClient *core.Client, listen, remote string, timeout time.Duration,
|
||||
connFunc func(addr net.Addr), errorFunc func(addr net.Addr, err error)) (*UDPRelay, error) {
|
||||
uAddr, err := net.ResolveUDPAddr("udp", listen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &UDPRelay{
|
||||
HyClient: hyClient,
|
||||
ListenAddr: uAddr,
|
||||
Remote: remote,
|
||||
Timeout: timeout,
|
||||
ConnFunc: connFunc,
|
||||
ErrorFunc: errorFunc,
|
||||
}
|
||||
if timeout == 0 {
|
||||
r.Timeout = 1 * time.Minute
|
||||
} else if timeout < udpMinTimeout {
|
||||
r.Timeout = udpMinTimeout
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
type cmEntry struct {
|
||||
HyConn core.UDPConn
|
||||
Addr *net.UDPAddr
|
||||
LastActiveTime atomic.Value
|
||||
}
|
||||
|
||||
func (r *UDPRelay) ListenAndServe() error {
|
||||
conn, err := net.ListenUDP("udp", r.ListenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
// src <-> HyClient UDPConn
|
||||
connMap := make(map[string]*cmEntry)
|
||||
var connMapMutex sync.RWMutex
|
||||
// Timeout cleanup routine
|
||||
stopChan := make(chan bool)
|
||||
defer close(stopChan)
|
||||
go func() {
|
||||
ticker := time.NewTicker(udpMinTimeout)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-stopChan:
|
||||
return
|
||||
case t := <-ticker.C:
|
||||
allowedLAT := t.Add(-r.Timeout)
|
||||
connMapMutex.Lock()
|
||||
for k, v := range connMap {
|
||||
if v.LastActiveTime.Load().(time.Time).Before(allowedLAT) {
|
||||
// Timeout
|
||||
r.ErrorFunc(v.Addr, ErrTimeout)
|
||||
_ = v.HyConn.Close()
|
||||
delete(connMap, k)
|
||||
}
|
||||
}
|
||||
connMapMutex.Unlock()
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Read loop
|
||||
buf := make([]byte, udpBufferSize)
|
||||
for {
|
||||
n, rAddr, err := conn.ReadFromUDP(buf)
|
||||
if n > 0 {
|
||||
connMapMutex.RLock()
|
||||
cme := connMap[rAddr.String()]
|
||||
connMapMutex.RUnlock()
|
||||
if cme != nil {
|
||||
// Existing conn
|
||||
cme.LastActiveTime.Store(time.Now())
|
||||
_ = cme.HyConn.WriteTo(buf[:n], r.Remote)
|
||||
} else {
|
||||
// New
|
||||
r.ConnFunc(rAddr)
|
||||
hyConn, err := r.HyClient.DialUDP()
|
||||
if err != nil {
|
||||
r.ErrorFunc(rAddr, err)
|
||||
} else {
|
||||
// Add it to the map
|
||||
ent := &cmEntry{HyConn: hyConn, Addr: rAddr}
|
||||
ent.LastActiveTime.Store(time.Now())
|
||||
connMapMutex.Lock()
|
||||
connMap[rAddr.String()] = ent
|
||||
connMapMutex.Unlock()
|
||||
// Start remote to local
|
||||
go func() {
|
||||
for {
|
||||
bs, _, err := hyConn.ReadFrom()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
ent.LastActiveTime.Store(time.Now())
|
||||
_, _ = conn.WriteToUDP(bs, rAddr)
|
||||
}
|
||||
}()
|
||||
// Send the packet
|
||||
_ = hyConn.WriteTo(buf[:n], r.Remote)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue