mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-03 04:27:39 +03:00
121 lines
3.2 KiB
Go
121 lines
3.2 KiB
Go
package outbounds
|
|
|
|
import (
|
|
"errors"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/apernet/hysteria/extras/v2/outbounds/acl"
|
|
)
|
|
|
|
const (
|
|
aclCacheSize = 1024
|
|
)
|
|
|
|
var errRejected = errors.New("rejected")
|
|
|
|
// aclEngine is a PluggableOutbound that dispatches connections to different
|
|
// outbounds based on ACL rules.
|
|
// There are 3 built-in outbounds:
|
|
// - direct: directOutbound, auto mode
|
|
// - reject: reject the connection
|
|
// - default: first outbound in the list, or if the list is empty, equal to direct
|
|
// If the user-defined outbounds contain any of the above names, they will
|
|
// override the built-in outbounds.
|
|
type aclEngine struct {
|
|
RuleSet acl.CompiledRuleSet[PluggableOutbound]
|
|
Default PluggableOutbound
|
|
}
|
|
|
|
type OutboundEntry struct {
|
|
Name string
|
|
Outbound PluggableOutbound
|
|
}
|
|
|
|
func NewACLEngineFromString(rules string, outbounds []OutboundEntry, geoLoader acl.GeoLoader) (PluggableOutbound, error) {
|
|
trs, err := acl.ParseTextRules(rules)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
obMap := outboundsToMap(outbounds)
|
|
rs, err := acl.Compile[PluggableOutbound](trs, obMap, aclCacheSize, geoLoader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &aclEngine{rs, obMap["default"]}, nil
|
|
}
|
|
|
|
func NewACLEngineFromFile(filename string, outbounds []OutboundEntry, geoLoader acl.GeoLoader) (PluggableOutbound, error) {
|
|
bs, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NewACLEngineFromString(string(bs), outbounds, geoLoader)
|
|
}
|
|
|
|
func outboundsToMap(outbounds []OutboundEntry) map[string]PluggableOutbound {
|
|
obMap := make(map[string]PluggableOutbound)
|
|
for _, ob := range outbounds {
|
|
obMap[strings.ToLower(ob.Name)] = ob.Outbound
|
|
}
|
|
// Add built-in outbounds if not overridden
|
|
if _, ok := obMap["direct"]; !ok {
|
|
obMap["direct"] = NewDirectOutboundSimple(DirectOutboundModeAuto)
|
|
}
|
|
if _, ok := obMap["reject"]; !ok {
|
|
obMap["reject"] = &aclRejectOutbound{}
|
|
}
|
|
if _, ok := obMap["default"]; !ok {
|
|
if len(outbounds) > 0 {
|
|
obMap["default"] = outbounds[0].Outbound
|
|
} else {
|
|
obMap["default"] = obMap["direct"]
|
|
}
|
|
}
|
|
return obMap
|
|
}
|
|
|
|
func (a *aclEngine) handle(reqAddr *AddrEx, proto acl.Protocol) PluggableOutbound {
|
|
hostInfo := acl.HostInfo{Name: reqAddr.Host}
|
|
if reqAddr.ResolveInfo != nil {
|
|
hostInfo.IPv4 = reqAddr.ResolveInfo.IPv4
|
|
hostInfo.IPv6 = reqAddr.ResolveInfo.IPv6
|
|
}
|
|
ob, hijackIP := a.RuleSet.Match(hostInfo, proto, reqAddr.Port)
|
|
if ob == nil {
|
|
// No match, use default outbound
|
|
return a.Default
|
|
}
|
|
if hijackIP != nil {
|
|
// We must rewrite both Host & ResolveInfo,
|
|
// as some outbounds only care about Host.
|
|
reqAddr.Host = hijackIP.String()
|
|
if ip4 := hijackIP.To4(); ip4 != nil {
|
|
reqAddr.ResolveInfo = &ResolveInfo{IPv4: ip4}
|
|
} else {
|
|
reqAddr.ResolveInfo = &ResolveInfo{IPv6: hijackIP}
|
|
}
|
|
}
|
|
return ob
|
|
}
|
|
|
|
func (a *aclEngine) TCP(reqAddr *AddrEx) (net.Conn, error) {
|
|
ob := a.handle(reqAddr, acl.ProtocolTCP)
|
|
return ob.TCP(reqAddr)
|
|
}
|
|
|
|
func (a *aclEngine) UDP(reqAddr *AddrEx) (UDPConn, error) {
|
|
ob := a.handle(reqAddr, acl.ProtocolUDP)
|
|
return ob.UDP(reqAddr)
|
|
}
|
|
|
|
type aclRejectOutbound struct{}
|
|
|
|
func (a *aclRejectOutbound) TCP(reqAddr *AddrEx) (net.Conn, error) {
|
|
return nil, errRejected
|
|
}
|
|
|
|
func (a *aclRejectOutbound) UDP(reqAddr *AddrEx) (UDPConn, error) {
|
|
return nil, errRejected
|
|
}
|