mirror of
https://github.com/cmz0228/hysteria-dev.git
synced 2025-06-08 13:29:53 +00:00
commit
4c58b35ee5
45
ACL.md
Normal file
45
ACL.md
Normal file
@ -0,0 +1,45 @@
|
||||
# ACL File Format
|
||||
|
||||
ACL files describe how to process incoming requests. Both the server and the client support ACL and follow the identical syntax.
|
||||
|
||||
```
|
||||
action condition_type condition argument
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
direct domain evil.corp
|
||||
proxy domain-suffix google.com
|
||||
block ip 1.2.3.4
|
||||
hijack cidr 192.168.1.1/24 127.0.0.1
|
||||
|
||||
direct all
|
||||
```
|
||||
|
||||
A real-life ACL example of directly connecting to all China IPs (and its generator Python script) [can be found here](docs/acl).
|
||||
|
||||
Hysteria acts according to the first matching rule in the file for each request. When there is no match, the default behavior is to proxy all connections. You can override this by adding a rule at the end of the file with the condition "all".
|
||||
|
||||
4 actions:
|
||||
|
||||
`direct` - connect directly to the target server without going through the proxy
|
||||
|
||||
`proxy` - connect to the target server through the proxy (only available on the client)
|
||||
|
||||
`block` - block the connection from establishing
|
||||
|
||||
`hijack` - hijack the connection to another target address (must be specified in the argument)
|
||||
|
||||
5 condition types:
|
||||
|
||||
`domain` - match a specific domain (does NOT match subdomains! e.g. `apple.com` will not match `cdn.apple.com`)
|
||||
|
||||
`domain-suffix` - match a domain suffix (match subdomains, but `apple.com` will still not match `fakeapple.com`)
|
||||
|
||||
`cidr` - IPv4 or IPv6 CIDR
|
||||
|
||||
`ip` - IPv4 or IPv6 address
|
||||
|
||||
`all` - match anything (usually placed at the end of the file as a default rule)
|
||||
|
||||
For domain requests, Hysteria will try to resolve the domains and match both domain & IP rules. In other words, an IP rule covers all connections that would end up connecting to this IP, regardless of whether the client requests with IP or domain.
|
45
ACL.zh.md
Normal file
45
ACL.zh.md
Normal file
@ -0,0 +1,45 @@
|
||||
# ACL 文件格式
|
||||
|
||||
ACL 文件描述如何处理传入请求。服务器和客户端都支持 ACL,并且遵循相同的语法。
|
||||
|
||||
```
|
||||
处理方式 条件类型 条件 参数
|
||||
```
|
||||
|
||||
例子:
|
||||
```
|
||||
direct domain evil.corp
|
||||
proxy domain-suffix google.com
|
||||
block ip 1.2.3.4
|
||||
hijack cidr 192.168.1.1/24 127.0.0.1
|
||||
|
||||
direct all
|
||||
```
|
||||
|
||||
一个直连所有中国 IP 的规则和 Python 生成脚本 [在这里](docs/acl)。
|
||||
|
||||
Hysteria 根据文件中第一个匹配到规则对每个请求进行操作。当没有匹配时默认的行为是代理连接。可以通过在文件的末尾添加一个规则加上条件 "all" 来设置默认行为。
|
||||
|
||||
4 种处理方式:
|
||||
|
||||
`direct` - 直接连接到目标服务器,不经过代理
|
||||
|
||||
`proxy` - 通过代理连接到目标服务器(仅在客户端上可用)
|
||||
|
||||
`block` - 拒绝连接建立
|
||||
|
||||
`hijack` - 把连接劫持到另一个目的地 (必须在参数中指定)
|
||||
|
||||
5 种条件类型:
|
||||
|
||||
`domain` - 匹配特定的域名(不匹配子域名!例如:`apple.com` 不匹配 `cdn.apple.com`)
|
||||
|
||||
`domain-suffix` - 匹配域名后缀(包含子域名,但 `apple.com` 仍不会匹配 `fakeapple.com`)
|
||||
|
||||
`cidr` - IPv4 / IPv6 CIDR
|
||||
|
||||
`ip` - IPv4 / IPv6 地址
|
||||
|
||||
`all` - 匹配所有地址 (通常放在文件尾作为默认规则)
|
||||
|
||||
对于域名请求,Hysteria 将尝试解析域名并同时匹配域名规则和 IP 规则。换句话说,IP 规则能覆盖到所有连接,无论客户端是用 IP 还是域名请求。
|
@ -67,6 +67,7 @@ The command line program supports loading configurations from both JSON files an
|
||||
| Description | JSON config field | CLI argument |
|
||||
| --- | --- | --- |
|
||||
| Server listen address | listen | -listen |
|
||||
| Access control list | acl | -acl |
|
||||
| TLS certificate file | cert | -cert |
|
||||
| TLS key file | key | -key |
|
||||
| Authentication file | auth | -auth |
|
||||
@ -83,6 +84,7 @@ The command line program supports loading configurations from both JSON files an
|
||||
| --- | --- | --- |
|
||||
| SOCKS5 listen address | socks5_addr | -socks5-addr |
|
||||
| SOCKS5 connection timeout in seconds | socks5_timeout | -socks5-timeout |
|
||||
| Access control list | acl | -acl |
|
||||
| Server address | server | -server |
|
||||
| Authentication username | username | -username |
|
||||
| Authentication password | password | -password |
|
||||
@ -98,6 +100,10 @@ The command line program supports loading configurations from both JSON files an
|
||||
|
||||
Supports TCP (CONNECT) and UDP (ASSOCIATE) commands. BIND is not supported and is not planned to be supported.
|
||||
|
||||
#### About ACL
|
||||
|
||||
[ACL File Format](ACL.md)
|
||||
|
||||
#### About proxy authentication
|
||||
|
||||
Proxy supports username and password authentication (sent encrypted with TLS). If the server starts with an authentication file, it will check for the existence of the corresponding username and password in this file when each user connects. A valid authentication file is a text file with a pair of username and password per line (separated by a space). Example:
|
||||
|
@ -65,6 +65,7 @@ Hysteria 是专门针对恶劣网络环境(常见于在中国访问海外服
|
||||
| 描述 | JSON 字段 | 命令行参数 |
|
||||
| --- | --- | --- |
|
||||
| 服务端监听地址 | listen | -listen |
|
||||
| ACL 规则文件 | acl | -acl |
|
||||
| TLS 证书文件 | cert | -cert |
|
||||
| TLS 密钥文件 | key | -key |
|
||||
| 用户名密码验证文件 | auth | -auth |
|
||||
@ -81,6 +82,7 @@ Hysteria 是专门针对恶劣网络环境(常见于在中国访问海外服
|
||||
| --- | --- | --- |
|
||||
| SOCKS5 监听地址 | socks5_addr | -socks5-addr |
|
||||
| SOCKS5 超时时间(秒) | socks5_timeout | -socks5-timeout |
|
||||
| ACL 规则文件 | acl | -acl |
|
||||
| 服务端地址 | server | -server |
|
||||
| 验证用户名 | username | -username |
|
||||
| 验证密码 | password | -password |
|
||||
@ -96,6 +98,10 @@ Hysteria 是专门针对恶劣网络环境(常见于在中国访问海外服
|
||||
|
||||
支持 TCP (CONNECT) 和 UDP (ASSOCIATE),不支持 BIND 也无计划支持。
|
||||
|
||||
#### 关于 ACL
|
||||
|
||||
[ACL 文件格式](ACL.zh.md)
|
||||
|
||||
#### 关于用户名密码验证
|
||||
|
||||
代理支持用户名和密码认证(经过 TLS 加密发送)。如果服务器启动时指定了一个验证文件,当每个用户连接时,服务器会检查该文件中是否存在相应的用户名和密码。验证文件是一个文本文件,每行有一对用户名和密码(用空格分割)。比如:
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"crypto/x509"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/acl"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"github.com/tobyxdd/hysteria/pkg/obfs"
|
||||
@ -59,6 +60,14 @@ func proxyClient(args []string) {
|
||||
obfuscator = obfs.XORObfuscator(config.Obfs)
|
||||
}
|
||||
|
||||
var aclEngine *acl.Engine
|
||||
if len(config.ACLFile) > 0 {
|
||||
aclEngine, err = acl.LoadFromFile(config.ACLFile)
|
||||
if err != nil {
|
||||
log.Fatalln("Unable to parse ACL:", err)
|
||||
}
|
||||
}
|
||||
|
||||
client, err := core.NewClient(config.ServerAddr, config.Username, config.Password, tlsConfig, quicConfig,
|
||||
uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps,
|
||||
func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos {
|
||||
@ -70,9 +79,9 @@ func proxyClient(args []string) {
|
||||
defer client.Close()
|
||||
log.Println("Connected to", config.ServerAddr)
|
||||
|
||||
socks5server, err := socks5.NewServer(client, config.SOCKS5Addr, nil, config.SOCKS5Timeout,
|
||||
func(addr net.Addr, reqAddr string) {
|
||||
log.Printf("[TCP] %s <-> %s\n", addr.String(), reqAddr)
|
||||
socks5server, err := socks5.NewServer(client, config.SOCKS5Addr, nil, config.SOCKS5Timeout, aclEngine,
|
||||
func(addr net.Addr, reqAddr string, action acl.Action, arg string) {
|
||||
log.Printf("[TCP] [%s] %s <-> %s\n", actionToString(action, arg), addr.String(), reqAddr)
|
||||
},
|
||||
func(addr net.Addr, reqAddr string, err error) {
|
||||
log.Printf("Closed [TCP] %s <-> %s: %s\n", addr.String(), reqAddr, err.Error())
|
||||
@ -83,8 +92,8 @@ func proxyClient(args []string) {
|
||||
func(addr net.Addr, err error) {
|
||||
log.Printf("Closed [UDP] Associate %s: %s\n", addr.String(), err.Error())
|
||||
},
|
||||
func(addr net.Addr, reqAddr string) {
|
||||
log.Printf("[UDP] %s <-> %s\n", addr.String(), reqAddr)
|
||||
func(addr net.Addr, reqAddr string, action acl.Action, arg string) {
|
||||
log.Printf("[UDP] [%s] %s <-> %s\n", actionToString(action, arg), addr.String(), reqAddr)
|
||||
},
|
||||
func(addr net.Addr, reqAddr string, err error) {
|
||||
log.Printf("Closed [UDP] %s <-> %s: %s\n", addr.String(), reqAddr, err.Error())
|
||||
@ -96,3 +105,18 @@ func proxyClient(args []string) {
|
||||
|
||||
log.Fatalln(socks5server.ListenAndServe())
|
||||
}
|
||||
|
||||
func actionToString(action acl.Action, arg string) string {
|
||||
switch action {
|
||||
case acl.ActionDirect:
|
||||
return "Direct"
|
||||
case acl.ActionProxy:
|
||||
return "Proxy"
|
||||
case acl.ActionBlock:
|
||||
return "Block"
|
||||
case acl.ActionHijack:
|
||||
return "Hijack to " + arg
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ const proxyTLSProtocol = "hysteria-proxy"
|
||||
type proxyClientConfig struct {
|
||||
SOCKS5Addr string `json:"socks5_addr" desc:"SOCKS5 listen address"`
|
||||
SOCKS5Timeout int `json:"socks5_timeout" desc:"SOCKS5 connection timeout in seconds"`
|
||||
ACLFile string `json:"acl" desc:"Access control list"`
|
||||
ServerAddr string `json:"server" desc:"Server address"`
|
||||
Username string `json:"username" desc:"Authentication username"`
|
||||
Password string `json:"password" desc:"Authentication password"`
|
||||
@ -41,6 +42,7 @@ func (c *proxyClientConfig) Check() error {
|
||||
|
||||
type proxyServerConfig struct {
|
||||
ListenAddr string `json:"listen" desc:"Server listen address"`
|
||||
ACLFile string `json:"acl" desc:"Access control list"`
|
||||
CertFile string `json:"cert" desc:"TLS certificate file"`
|
||||
KeyFile string `json:"key" desc:"TLS key file"`
|
||||
AuthFile string `json:"auth" desc:"Authentication file"`
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"crypto/tls"
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/lucas-clemente/quic-go/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/acl"
|
||||
hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"github.com/tobyxdd/hysteria/pkg/obfs"
|
||||
@ -61,6 +62,15 @@ func proxyServer(args []string) {
|
||||
obfuscator = obfs.XORObfuscator(config.Obfs)
|
||||
}
|
||||
|
||||
var aclEngine *acl.Engine
|
||||
if len(config.ACLFile) > 0 {
|
||||
aclEngine, err = acl.LoadFromFile(config.ACLFile)
|
||||
if err != nil {
|
||||
log.Fatalln("Unable to parse ACL:", err)
|
||||
}
|
||||
aclEngine.DefaultAction = acl.ActionDirect
|
||||
}
|
||||
|
||||
server, err := core.NewServer(config.ListenAddr, tlsConfig, quicConfig,
|
||||
uint64(config.UpMbps)*mbpsToBps, uint64(config.DownMbps)*mbpsToBps,
|
||||
func(refBPS uint64) congestion.SendAlgorithmWithDebugInfos {
|
||||
@ -93,24 +103,73 @@ func proxyServer(args []string) {
|
||||
log.Printf("%s (%s) disconnected: %s\n", addr.String(), username, err.Error())
|
||||
},
|
||||
func(addr net.Addr, username string, id int, packet bool, reqAddr string) (core.ConnectResult, string, io.ReadWriteCloser) {
|
||||
if !packet {
|
||||
// TCP
|
||||
log.Printf("%s (%s): [TCP] %s\n", addr.String(), username, reqAddr)
|
||||
conn, err := net.DialTimeout("tcp", reqAddr, dialTimeout)
|
||||
if err != nil {
|
||||
log.Printf("TCP error %s: %s\n", reqAddr, err.Error())
|
||||
return core.ConnFailed, err.Error(), nil
|
||||
host, port, err := net.SplitHostPort(reqAddr)
|
||||
if err != nil {
|
||||
return core.ConnFailed, err.Error(), nil
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip != nil {
|
||||
// IP request, clear host for ACL engine
|
||||
host = ""
|
||||
}
|
||||
action, arg := acl.ActionDirect, ""
|
||||
if aclEngine != nil {
|
||||
action, arg = aclEngine.Lookup(host, ip)
|
||||
}
|
||||
switch action {
|
||||
case acl.ActionDirect, acl.ActionProxy: // Treat proxy as direct on server side
|
||||
if !packet {
|
||||
// TCP
|
||||
log.Printf("%s (%s): [TCP] [Direct] %s\n", addr.String(), username, reqAddr)
|
||||
conn, err := net.DialTimeout("tcp", reqAddr, dialTimeout)
|
||||
if err != nil {
|
||||
log.Printf("TCP error %s: %s\n", reqAddr, err.Error())
|
||||
return core.ConnFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnSuccess, "", conn
|
||||
} else {
|
||||
// UDP
|
||||
log.Printf("%s (%s): [UDP] [Direct] %s\n", addr.String(), username, reqAddr)
|
||||
conn, err := net.Dial("udp", reqAddr)
|
||||
if err != nil {
|
||||
log.Printf("UDP error %s: %s\n", reqAddr, err.Error())
|
||||
return core.ConnFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnSuccess, "", conn
|
||||
}
|
||||
return core.ConnSuccess, "", conn
|
||||
} else {
|
||||
// UDP
|
||||
log.Printf("%s (%s): [UDP] %s\n", addr.String(), username, reqAddr)
|
||||
conn, err := net.Dial("udp", reqAddr)
|
||||
if err != nil {
|
||||
log.Printf("UDP error %s: %s\n", reqAddr, err.Error())
|
||||
return core.ConnFailed, err.Error(), nil
|
||||
case acl.ActionBlock:
|
||||
if !packet {
|
||||
// TCP
|
||||
log.Printf("%s (%s): [TCP] [Block] %s\n", addr.String(), username, reqAddr)
|
||||
return core.ConnBlocked, "blocked by ACL", nil
|
||||
} else {
|
||||
// UDP
|
||||
log.Printf("%s (%s): [UDP] [Block] %s\n", addr.String(), username, reqAddr)
|
||||
return core.ConnBlocked, "blocked by ACL", nil
|
||||
}
|
||||
return core.ConnSuccess, "", conn
|
||||
case acl.ActionHijack:
|
||||
hijackAddr := net.JoinHostPort(arg, port)
|
||||
if !packet {
|
||||
// TCP
|
||||
log.Printf("%s (%s): [TCP] [Hijack to %s] %s\n", addr.String(), username, arg, reqAddr)
|
||||
conn, err := net.DialTimeout("tcp", hijackAddr, dialTimeout)
|
||||
if err != nil {
|
||||
log.Printf("TCP error %s: %s\n", hijackAddr, err.Error())
|
||||
return core.ConnFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnSuccess, "", conn
|
||||
} else {
|
||||
// UDP
|
||||
log.Printf("%s (%s): [UDP] [Hijack to %s] %s\n", addr.String(), username, arg, reqAddr)
|
||||
conn, err := net.Dial("udp", hijackAddr)
|
||||
if err != nil {
|
||||
log.Printf("UDP error %s: %s\n", hijackAddr, err.Error())
|
||||
return core.ConnFailed, err.Error(), nil
|
||||
}
|
||||
return core.ConnSuccess, "", conn
|
||||
}
|
||||
default:
|
||||
return core.ConnFailed, "server ACL error", nil
|
||||
}
|
||||
},
|
||||
func(addr net.Addr, username string, id int, packet bool, reqAddr string, err error) {
|
||||
|
7030
docs/acl/chnroutes.acl
Normal file
7030
docs/acl/chnroutes.acl
Normal file
File diff suppressed because it is too large
Load Diff
20
docs/acl/chnroutes.py
Normal file
20
docs/acl/chnroutes.py
Normal file
@ -0,0 +1,20 @@
|
||||
#! /usr/bin/env python3
|
||||
|
||||
import urllib.request
|
||||
from itertools import chain
|
||||
from datetime import date
|
||||
|
||||
data_ipv4 = urllib.request.urlopen(
|
||||
'https://www.ipdeny.com/ipblocks/data/aggregated/cn-aggregated.zone')
|
||||
data_ipv6 = urllib.request.urlopen(
|
||||
'https://www.ipdeny.com/ipv6/ipaddresses/aggregated/cn-aggregated.zone')
|
||||
|
||||
data = chain(data_ipv4, data_ipv6)
|
||||
|
||||
with open('chnroutes.acl', 'w') as out:
|
||||
out.write('# chnroutes\n# Generated on %s\n\n' %
|
||||
date.today().strftime("%B %d, %Y"))
|
||||
for l in data:
|
||||
ls = str(l, 'UTF8').strip()
|
||||
if ls:
|
||||
out.write('direct cidr %s\n' % ls)
|
5
go.mod
5
go.mod
@ -5,9 +5,10 @@ go 1.14
|
||||
require github.com/golang/protobuf v1.3.1
|
||||
|
||||
require (
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/lucas-clemente/quic-go v0.15.2
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/txthinking/runnergroup v0.0.0-20200327135940-540a793bb997 // indirect
|
||||
github.com/txthinking/socks5 v0.0.0-20200327133705-caf148ab5e9d
|
||||
github.com/txthinking/x v0.0.0-20200330144832-5ad2416896a9 // indirect
|
||||
)
|
||||
|
2
go.sum
2
go.sum
@ -50,6 +50,8 @@ github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE0
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||
|
90
pkg/acl/engine.go
Normal file
90
pkg/acl/engine.go
Normal file
@ -0,0 +1,90 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const entryCacheSize = 1024
|
||||
|
||||
type Engine struct {
|
||||
DefaultAction Action
|
||||
Entries []Entry
|
||||
Cache *lru.ARCCache
|
||||
}
|
||||
|
||||
type cacheEntry struct {
|
||||
Action Action
|
||||
Arg string
|
||||
}
|
||||
|
||||
func LoadFromFile(filename string) (*Engine, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
entries := make([]Entry, 0, 1024)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if len(line) == 0 || strings.HasPrefix(line, "#") {
|
||||
// Ignore empty lines & comments
|
||||
continue
|
||||
}
|
||||
entry, err := ParseEntry(line)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
cache, err := lru.NewARC(entryCacheSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Engine{
|
||||
DefaultAction: ActionProxy,
|
||||
Entries: entries,
|
||||
Cache: cache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *Engine) Lookup(domain string, ip net.IP) (Action, string) {
|
||||
if len(domain) > 0 {
|
||||
// Domain
|
||||
if v, ok := e.Cache.Get(domain); ok {
|
||||
// Cache hit
|
||||
ce := v.(cacheEntry)
|
||||
return ce.Action, ce.Arg
|
||||
}
|
||||
ips, _ := net.LookupIP(domain)
|
||||
for _, entry := range e.Entries {
|
||||
if entry.MatchDomain(domain) || (len(ips) > 0 && entry.MatchIPs(ips)) {
|
||||
e.Cache.Add(domain, cacheEntry{entry.Action, entry.ActionArg})
|
||||
return entry.Action, entry.ActionArg
|
||||
}
|
||||
}
|
||||
e.Cache.Add(domain, cacheEntry{e.DefaultAction, ""})
|
||||
return e.DefaultAction, ""
|
||||
} else if ip != nil {
|
||||
// IP
|
||||
if v, ok := e.Cache.Get(ip.String()); ok {
|
||||
// Cache hit
|
||||
ce := v.(cacheEntry)
|
||||
return ce.Action, ce.Arg
|
||||
}
|
||||
for _, entry := range e.Entries {
|
||||
if entry.MatchIP(ip) {
|
||||
e.Cache.Add(ip.String(), cacheEntry{entry.Action, entry.ActionArg})
|
||||
return entry.Action, entry.ActionArg
|
||||
}
|
||||
}
|
||||
e.Cache.Add(ip.String(), cacheEntry{e.DefaultAction, ""})
|
||||
return e.DefaultAction, ""
|
||||
} else {
|
||||
return e.DefaultAction, ""
|
||||
}
|
||||
}
|
110
pkg/acl/engine_test.go
Normal file
110
pkg/acl/engine_test.go
Normal file
@ -0,0 +1,110 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEngine_Lookup(t *testing.T) {
|
||||
cache, _ := lru.NewARC(4)
|
||||
e := &Engine{
|
||||
DefaultAction: ActionDirect,
|
||||
Entries: []Entry{
|
||||
{
|
||||
Net: nil,
|
||||
Domain: "google.com",
|
||||
Suffix: false,
|
||||
All: false,
|
||||
Action: ActionProxy,
|
||||
ActionArg: "",
|
||||
},
|
||||
{
|
||||
Net: nil,
|
||||
Domain: "evil.corp",
|
||||
Suffix: true,
|
||||
All: false,
|
||||
Action: ActionHijack,
|
||||
ActionArg: "good.org",
|
||||
},
|
||||
{
|
||||
Net: &net.IPNet{
|
||||
IP: net.ParseIP("10.0.0.0"),
|
||||
Mask: net.CIDRMask(8, 32),
|
||||
},
|
||||
Domain: "",
|
||||
Suffix: false,
|
||||
All: false,
|
||||
Action: ActionProxy,
|
||||
ActionArg: "",
|
||||
},
|
||||
{
|
||||
Net: nil,
|
||||
Domain: "",
|
||||
Suffix: false,
|
||||
All: true,
|
||||
Action: ActionBlock,
|
||||
ActionArg: "",
|
||||
},
|
||||
},
|
||||
Cache: cache,
|
||||
}
|
||||
type args struct {
|
||||
domain string
|
||||
ip net.IP
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Action
|
||||
want1 string
|
||||
}{
|
||||
{
|
||||
name: "domain direct",
|
||||
args: args{"google.com", nil},
|
||||
want: ActionProxy,
|
||||
want1: "",
|
||||
},
|
||||
{
|
||||
name: "domain suffix 1",
|
||||
args: args{"evil.corp", nil},
|
||||
want: ActionHijack,
|
||||
want1: "good.org",
|
||||
},
|
||||
{
|
||||
name: "domain suffix 2",
|
||||
args: args{"notevil.corp", nil},
|
||||
want: ActionBlock,
|
||||
want1: "",
|
||||
},
|
||||
{
|
||||
name: "domain suffix 3",
|
||||
args: args{"im.real.evil.corp", nil},
|
||||
want: ActionHijack,
|
||||
want1: "good.org",
|
||||
},
|
||||
{
|
||||
name: "ip match",
|
||||
args: args{"", net.ParseIP("10.2.3.4")},
|
||||
want: ActionProxy,
|
||||
want1: "",
|
||||
},
|
||||
{
|
||||
name: "ip mismatch",
|
||||
args: args{"", net.ParseIP("100.5.6.0")},
|
||||
want: ActionBlock,
|
||||
want1: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1 := e.Lookup(tt.args.domain, tt.args.ip)
|
||||
if got != tt.want {
|
||||
t.Errorf("Lookup() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
if got1 != tt.want1 {
|
||||
t.Errorf("Lookup() got1 = %v, want %v", got1, tt.want1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
150
pkg/acl/entry.go
Normal file
150
pkg/acl/entry.go
Normal file
@ -0,0 +1,150 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Action byte
|
||||
|
||||
const (
|
||||
ActionDirect = Action(iota)
|
||||
ActionProxy
|
||||
ActionBlock
|
||||
ActionHijack
|
||||
)
|
||||
|
||||
type Entry struct {
|
||||
Net *net.IPNet
|
||||
Domain string
|
||||
Suffix bool
|
||||
All bool
|
||||
Action Action
|
||||
ActionArg string
|
||||
}
|
||||
|
||||
func (e Entry) MatchDomain(domain string) bool {
|
||||
if e.All {
|
||||
return true
|
||||
}
|
||||
if len(e.Domain) > 0 && len(domain) > 0 {
|
||||
ld := strings.ToLower(domain)
|
||||
if e.Suffix {
|
||||
return e.Domain == ld || strings.HasSuffix(ld, "."+e.Domain)
|
||||
} else {
|
||||
return e.Domain == ld
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e Entry) MatchIP(ip net.IP) bool {
|
||||
if e.All {
|
||||
return true
|
||||
}
|
||||
if e.Net != nil && ip != nil {
|
||||
return e.Net.Contains(ip)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e Entry) MatchIPs(ips []net.IP) bool {
|
||||
if e.All {
|
||||
return true
|
||||
}
|
||||
if e.Net != nil && len(ips) > 0 {
|
||||
for _, ip := range ips {
|
||||
if e.Net.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Format: action cond_type cond arg
|
||||
// Examples:
|
||||
// proxy domain-suffix google.com
|
||||
// block ip 8.8.8.8
|
||||
// hijack cidr 192.168.1.1/24 127.0.0.1
|
||||
func ParseEntry(s string) (Entry, error) {
|
||||
fields := strings.Fields(s)
|
||||
if len(fields) < 2 {
|
||||
return Entry{}, fmt.Errorf("expecting at least 2 fields, got %d", len(fields))
|
||||
}
|
||||
args := fields[1:]
|
||||
if len(args) == 1 {
|
||||
// Make sure there are at least 2 args
|
||||
args = append(args, "")
|
||||
}
|
||||
ipNet, domain, suffix, all, err := parseCond(args[0], args[1])
|
||||
if err != nil {
|
||||
return Entry{}, err
|
||||
}
|
||||
e := Entry{
|
||||
Net: ipNet,
|
||||
Domain: domain,
|
||||
Suffix: suffix,
|
||||
All: all,
|
||||
}
|
||||
switch strings.ToLower(fields[0]) {
|
||||
case "direct":
|
||||
e.Action = ActionDirect
|
||||
case "proxy":
|
||||
e.Action = ActionProxy
|
||||
case "block":
|
||||
e.Action = ActionBlock
|
||||
case "hijack":
|
||||
if len(args) < 3 {
|
||||
return Entry{}, fmt.Errorf("no hijack destination for %s %s", args[0], args[1])
|
||||
}
|
||||
e.Action = ActionHijack
|
||||
e.ActionArg = args[2]
|
||||
default:
|
||||
return Entry{}, fmt.Errorf("invalid action %s", fields[0])
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func parseCond(typ, cond string) (*net.IPNet, string, bool, bool, error) {
|
||||
switch strings.ToLower(typ) {
|
||||
case "domain":
|
||||
if len(cond) == 0 {
|
||||
return nil, "", false, false, errors.New("empty domain")
|
||||
}
|
||||
return nil, strings.ToLower(cond), false, false, nil
|
||||
case "domain-suffix":
|
||||
if len(cond) == 0 {
|
||||
return nil, "", false, false, errors.New("empty domain suffix")
|
||||
}
|
||||
return nil, strings.ToLower(cond), true, false, nil
|
||||
case "cidr":
|
||||
_, ipNet, err := net.ParseCIDR(cond)
|
||||
if err != nil {
|
||||
return nil, "", false, false, err
|
||||
}
|
||||
return ipNet, "", false, false, nil
|
||||
case "ip":
|
||||
ip := net.ParseIP(cond)
|
||||
if ip == nil {
|
||||
return nil, "", false, false, fmt.Errorf("invalid ip %s", cond)
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
return &net.IPNet{
|
||||
IP: ip,
|
||||
Mask: net.CIDRMask(32, 32),
|
||||
}, "", false, false, nil
|
||||
} else {
|
||||
return &net.IPNet{
|
||||
IP: ip,
|
||||
Mask: net.CIDRMask(128, 128),
|
||||
}, "", false, false, nil
|
||||
}
|
||||
case "all":
|
||||
return nil, "", false, true, nil
|
||||
default:
|
||||
return nil, "", false, false, fmt.Errorf("invalid condition type %s", typ)
|
||||
}
|
||||
}
|
55
pkg/acl/entry_test.go
Normal file
55
pkg/acl/entry_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseEntry(t *testing.T) {
|
||||
_, ok4ipnet, _ := net.ParseCIDR("8.8.8.0/24")
|
||||
|
||||
type args struct {
|
||||
s string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want Entry
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "empty", args: args{""}, want: Entry{}, wantErr: true},
|
||||
{name: "ok 1", args: args{"direct domain-suffix google.com"},
|
||||
want: Entry{nil, "google.com", true, false, ActionDirect, ""},
|
||||
wantErr: false},
|
||||
{name: "ok 2", args: args{"proxy ip 8.8.8.8"},
|
||||
want: Entry{&net.IPNet{net.ParseIP("8.8.8.8"), net.CIDRMask(32, 32)},
|
||||
"", false, false, ActionProxy, ""}, wantErr: false},
|
||||
{name: "ok 3", args: args{"hijack domain mad.bad 127.0.0.1"},
|
||||
want: Entry{nil, "mad.bad", false, false, ActionHijack, "127.0.0.1"},
|
||||
wantErr: false},
|
||||
{name: "ok 4", args: args{"block cidr 8.8.8.0/24"},
|
||||
want: Entry{ok4ipnet, "", false, false, ActionBlock, ""},
|
||||
wantErr: false},
|
||||
{name: "ok 5", args: args{"block all"},
|
||||
want: Entry{nil, "", false, true, ActionBlock, ""},
|
||||
wantErr: false},
|
||||
{name: "invalid 1", args: args{"proxy domain"}, want: Entry{}, wantErr: true},
|
||||
{name: "invalid 2", args: args{"proxy dom google.com"}, want: Entry{}, wantErr: true},
|
||||
{name: "invalid 3", args: args{"hijack ip 1.1.1.1"}, want: Entry{}, wantErr: true},
|
||||
{name: "invalid 4", args: args{"direct cidr"}, want: Entry{}, wantErr: true},
|
||||
{name: "invalid 5", args: args{"oxy ip 8.8.8.8"}, want: Entry{}, wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseEntry(tt.args.s)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseEntry() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ParseEntry() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/tobyxdd/hysteria/internal/utils"
|
||||
"github.com/tobyxdd/hysteria/pkg/acl"
|
||||
"github.com/tobyxdd/hysteria/pkg/core"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
import (
|
||||
@ -24,21 +28,26 @@ type Server struct {
|
||||
Method byte
|
||||
TCPAddr *net.TCPAddr
|
||||
TCPDeadline int
|
||||
ACLEngine *acl.Engine
|
||||
|
||||
NewRequestFunc func(addr net.Addr, reqAddr string)
|
||||
NewRequestFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string)
|
||||
RequestClosedFunc func(addr net.Addr, reqAddr string, err error)
|
||||
NewUDPAssociateFunc func(addr net.Addr)
|
||||
UDPAssociateClosedFunc func(addr net.Addr, err error)
|
||||
NewUDPTunnelFunc func(addr net.Addr, reqAddr string)
|
||||
NewUDPTunnelFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string)
|
||||
UDPTunnelClosedFunc func(addr net.Addr, reqAddr string, err error)
|
||||
|
||||
tcpListener *net.TCPListener
|
||||
}
|
||||
|
||||
func NewServer(hyClient core.Client, addr string, authFunc func(username, password string) bool, tcpDeadline int,
|
||||
newReqFunc func(addr net.Addr, reqAddr string), reqClosedFunc func(addr net.Addr, reqAddr string, err error),
|
||||
newUDPAssociateFunc func(addr net.Addr), udpAssociateClosedFunc func(addr net.Addr, err error),
|
||||
newUDPTunnelFunc func(addr net.Addr, reqAddr string), udpTunnelClosedFunc func(addr net.Addr, reqAddr string, err error)) (*Server, error) {
|
||||
aclEngine *acl.Engine,
|
||||
newReqFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string),
|
||||
reqClosedFunc func(addr net.Addr, reqAddr string, err error),
|
||||
newUDPAssociateFunc func(addr net.Addr),
|
||||
udpAssociateClosedFunc func(addr net.Addr, err error),
|
||||
newUDPTunnelFunc func(addr net.Addr, reqAddr string, action acl.Action, arg string),
|
||||
udpTunnelClosedFunc func(addr net.Addr, reqAddr string, err error)) (*Server, error) {
|
||||
|
||||
taddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
@ -54,6 +63,7 @@ func NewServer(hyClient core.Client, addr string, authFunc func(username, passwo
|
||||
Method: m,
|
||||
TCPAddr: taddr,
|
||||
TCPDeadline: tcpDeadline,
|
||||
ACLEngine: aclEngine,
|
||||
NewRequestFunc: newReqFunc,
|
||||
RequestClosedFunc: reqClosedFunc,
|
||||
NewUDPAssociateFunc: newUDPAssociateFunc,
|
||||
@ -141,69 +151,115 @@ func (s *Server) ListenAndServe() error {
|
||||
func (s *Server) handle(c *net.TCPConn, r *socks5.Request) error {
|
||||
if r.Cmd == socks5.CmdConnect {
|
||||
// TCP
|
||||
s.NewRequestFunc(c.RemoteAddr(), r.Address())
|
||||
var closeErr error
|
||||
defer func() {
|
||||
s.RequestClosedFunc(c.RemoteAddr(), r.Address(), closeErr)
|
||||
}()
|
||||
rc, err := s.HyClient.Dial(false, r.Address())
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
// All good
|
||||
_ = sendReply(c, socks5.RepSuccess)
|
||||
closeErr = pipePair(c, rc, s.TCPDeadline)
|
||||
return nil
|
||||
return s.handleTCP(c, r)
|
||||
} else if r.Cmd == socks5.CmdUDP {
|
||||
// UDP
|
||||
s.NewUDPAssociateFunc(c.RemoteAddr())
|
||||
var closeErr error
|
||||
defer func() {
|
||||
s.UDPAssociateClosedFunc(c.RemoteAddr(), closeErr)
|
||||
}()
|
||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{
|
||||
IP: s.TCPAddr.IP,
|
||||
Zone: s.TCPAddr.Zone,
|
||||
})
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer udpConn.Close()
|
||||
// Send UDP server addr to the client
|
||||
atyp, addr, port, err := socks5.ParseAddress(udpConn.LocalAddr().String())
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
_, _ = socks5.NewReply(socks5.RepSuccess, atyp, addr, port).WriteTo(c)
|
||||
// Let UDP server do its job, we hold the TCP connection here
|
||||
go s.handleUDP(udpConn)
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
if s.TCPDeadline != 0 {
|
||||
_ = c.SetDeadline(time.Now().Add(time.Duration(s.TCPDeadline) * time.Second))
|
||||
}
|
||||
_, err := c.Read(buf)
|
||||
if err != nil {
|
||||
closeErr = err
|
||||
break
|
||||
}
|
||||
}
|
||||
// As the TCP connection closes, so does the UDP listener
|
||||
return nil
|
||||
return s.handleUDP(c, r)
|
||||
} else {
|
||||
_ = sendReply(c, socks5.RepCommandNotSupported)
|
||||
return ErrUnsupportedCmd
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleUDP(c *net.UDPConn) {
|
||||
func (s *Server) handleTCP(c *net.TCPConn, r *socks5.Request) error {
|
||||
domain, ip, port, addr := parseRequestAddress(r)
|
||||
action, arg := acl.ActionProxy, ""
|
||||
if s.ACLEngine != nil {
|
||||
action, arg = s.ACLEngine.Lookup(domain, ip)
|
||||
}
|
||||
s.NewRequestFunc(c.RemoteAddr(), addr, action, arg)
|
||||
var closeErr error
|
||||
defer func() {
|
||||
s.RequestClosedFunc(c.RemoteAddr(), addr, closeErr)
|
||||
}()
|
||||
// Handle according to the action
|
||||
switch action {
|
||||
case acl.ActionDirect:
|
||||
rc, err := net.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
_ = sendReply(c, socks5.RepSuccess)
|
||||
closeErr = pipePair(c, rc, s.TCPDeadline)
|
||||
return nil
|
||||
case acl.ActionProxy:
|
||||
rc, err := s.HyClient.Dial(false, addr)
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
_ = sendReply(c, socks5.RepSuccess)
|
||||
closeErr = pipePair(c, rc, s.TCPDeadline)
|
||||
return nil
|
||||
case acl.ActionBlock:
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = errors.New("blocked in ACL")
|
||||
return nil
|
||||
case acl.ActionHijack:
|
||||
rc, err := net.Dial("tcp", net.JoinHostPort(arg, port))
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepHostUnreachable)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer rc.Close()
|
||||
_ = sendReply(c, socks5.RepSuccess)
|
||||
closeErr = pipePair(c, rc, s.TCPDeadline)
|
||||
return nil
|
||||
default:
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = fmt.Errorf("unknown action %d", action)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleUDP(c *net.TCPConn, r *socks5.Request) error {
|
||||
s.NewUDPAssociateFunc(c.RemoteAddr())
|
||||
var closeErr error
|
||||
defer func() {
|
||||
s.UDPAssociateClosedFunc(c.RemoteAddr(), closeErr)
|
||||
}()
|
||||
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{
|
||||
IP: s.TCPAddr.IP,
|
||||
Zone: s.TCPAddr.Zone,
|
||||
})
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
defer udpConn.Close()
|
||||
// Send UDP server addr to the client
|
||||
atyp, addr, port, err := socks5.ParseAddress(udpConn.LocalAddr().String())
|
||||
if err != nil {
|
||||
_ = sendReply(c, socks5.RepServerFailure)
|
||||
closeErr = err
|
||||
return err
|
||||
}
|
||||
_, _ = socks5.NewReply(socks5.RepSuccess, atyp, addr, port).WriteTo(c)
|
||||
// Let UDP server do its job, we hold the TCP connection here
|
||||
go s.udpServer(udpConn)
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
if s.TCPDeadline != 0 {
|
||||
_ = c.SetDeadline(time.Now().Add(time.Duration(s.TCPDeadline) * time.Second))
|
||||
}
|
||||
_, err := c.Read(buf)
|
||||
if err != nil {
|
||||
closeErr = err
|
||||
break
|
||||
}
|
||||
}
|
||||
// As the TCP connection closes, so does the UDP listener
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) udpServer(c *net.UDPConn) {
|
||||
var clientAddr *net.UDPAddr
|
||||
remoteMap := make(map[string]io.ReadWriteCloser) // Remote addr <-> Remote conn
|
||||
buf := make([]byte, utils.PipeBufferSize)
|
||||
@ -227,25 +283,58 @@ func (s *Server) handleUDP(c *net.UDPConn) {
|
||||
// We already have a client and you're not it!
|
||||
continue
|
||||
}
|
||||
rc := remoteMap[d.Address()]
|
||||
domain, ip, port, addr := parseDatagramRequestAddress(d)
|
||||
rc := remoteMap[addr]
|
||||
if rc == nil {
|
||||
// Need a new entry
|
||||
rc, err = s.HyClient.Dial(true, d.Address())
|
||||
if err != nil {
|
||||
// Failed to establish a connection, silently ignore
|
||||
action, arg := acl.ActionProxy, ""
|
||||
if s.ACLEngine != nil {
|
||||
action, arg = s.ACLEngine.Lookup(domain, ip)
|
||||
}
|
||||
s.NewUDPTunnelFunc(clientAddr, addr, action, arg)
|
||||
// Handle according to the action
|
||||
switch action {
|
||||
case acl.ActionDirect:
|
||||
rc, err = net.Dial("udp", addr)
|
||||
if err != nil {
|
||||
// Failed to establish a connection, silently ignore
|
||||
continue
|
||||
}
|
||||
// The other direction
|
||||
go udpReversePipe(clientAddr, c, rc)
|
||||
remoteMap[addr] = rc
|
||||
case acl.ActionProxy:
|
||||
rc, err = s.HyClient.Dial(true, addr)
|
||||
if err != nil {
|
||||
// Failed to establish a connection, silently ignore
|
||||
continue
|
||||
}
|
||||
// The other direction
|
||||
go udpReversePipe(clientAddr, c, rc)
|
||||
remoteMap[addr] = rc
|
||||
case acl.ActionBlock:
|
||||
// Silently ignore
|
||||
continue
|
||||
case acl.ActionHijack:
|
||||
rc, err = net.Dial("udp", net.JoinHostPort(arg, port))
|
||||
if err != nil {
|
||||
// Failed to establish a connection, silently ignore
|
||||
continue
|
||||
}
|
||||
// The other direction
|
||||
go udpReversePipe(clientAddr, c, rc)
|
||||
remoteMap[addr] = rc
|
||||
default:
|
||||
// Silently ignore
|
||||
continue
|
||||
}
|
||||
// The other direction
|
||||
go udpReversePipe(clientAddr, c, rc)
|
||||
remoteMap[d.Address()] = rc
|
||||
s.NewUDPTunnelFunc(clientAddr, d.Address())
|
||||
}
|
||||
_, err = rc.Write(d.Data)
|
||||
if err != nil {
|
||||
// The connection is no longer valid, close & remove from map
|
||||
_ = rc.Close()
|
||||
delete(remoteMap, d.Address())
|
||||
s.UDPTunnelClosedFunc(clientAddr, d.Address(), err)
|
||||
delete(remoteMap, addr)
|
||||
s.UDPTunnelClosedFunc(clientAddr, addr, err)
|
||||
}
|
||||
}
|
||||
// Close all remote connections
|
||||
@ -261,6 +350,26 @@ func sendReply(conn *net.TCPConn, rep byte) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func parseRequestAddress(r *socks5.Request) (domain string, ip net.IP, port string, addr string) {
|
||||
p := strconv.Itoa(int(binary.BigEndian.Uint16(r.DstPort)))
|
||||
if r.Atyp == socks5.ATYPDomain {
|
||||
d := string(r.DstAddr[1:])
|
||||
return d, nil, p, net.JoinHostPort(d, p)
|
||||
} else {
|
||||
return "", r.DstAddr, p, net.JoinHostPort(net.IP(r.DstAddr).String(), p)
|
||||
}
|
||||
}
|
||||
|
||||
func parseDatagramRequestAddress(r *socks5.Datagram) (domain string, ip net.IP, port string, addr string) {
|
||||
p := strconv.Itoa(int(binary.BigEndian.Uint16(r.DstPort)))
|
||||
if r.Atyp == socks5.ATYPDomain {
|
||||
d := string(r.DstAddr[1:])
|
||||
return d, nil, p, net.JoinHostPort(d, p)
|
||||
} else {
|
||||
return "", r.DstAddr, p, net.JoinHostPort(net.IP(r.DstAddr).String(), p)
|
||||
}
|
||||
}
|
||||
|
||||
func pipePair(conn *net.TCPConn, stream io.ReadWriteCloser, deadline int) error {
|
||||
errChan := make(chan error, 2)
|
||||
// TCP to stream
|
||||
|
Loading…
x
Reference in New Issue
Block a user