Inbound rule support
This commit is contained in:
@@ -4,8 +4,12 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
|
||||
"github.com/oschwald/geoip2-golang"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
@@ -15,24 +19,18 @@ type Router struct {
|
||||
logger log.Logger
|
||||
defaultOutbound adapter.Outbound
|
||||
outboundByTag map[string]adapter.Outbound
|
||||
|
||||
rules []adapter.Rule
|
||||
geoReader *geoip2.Reader
|
||||
}
|
||||
|
||||
func NewRouter(logger log.Logger) *Router {
|
||||
return &Router{
|
||||
logger: logger,
|
||||
logger: logger.WithPrefix("router: "),
|
||||
outboundByTag: make(map[string]adapter.Outbound),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) AddOutbound(outbound adapter.Outbound) {
|
||||
if outbound.Tag() != "" {
|
||||
r.outboundByTag[outbound.Tag()] = outbound
|
||||
}
|
||||
if r.defaultOutbound == nil {
|
||||
r.defaultOutbound = outbound
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) DefaultOutbound() adapter.Outbound {
|
||||
if r.defaultOutbound == nil {
|
||||
panic("missing default outbound")
|
||||
@@ -46,13 +44,60 @@ func (r *Router) Outbound(tag string) (adapter.Outbound, bool) {
|
||||
}
|
||||
|
||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
r.logger.WithContext(ctx).Debug("no match")
|
||||
r.logger.WithContext(ctx).Debug("route connection to default outbound")
|
||||
for _, rule := range r.rules {
|
||||
if rule.Match(metadata) {
|
||||
r.logger.WithContext(ctx).Info("match ", rule.String())
|
||||
if outbound, loaded := r.Outbound(rule.Outbound()); loaded {
|
||||
return outbound.NewConnection(ctx, conn, metadata.Destination)
|
||||
}
|
||||
r.logger.WithContext(ctx).Error("outbound ", rule.Outbound(), " not found")
|
||||
}
|
||||
}
|
||||
r.logger.WithContext(ctx).Info("no match => ", r.defaultOutbound.Tag())
|
||||
return r.defaultOutbound.NewConnection(ctx, conn, metadata.Destination)
|
||||
}
|
||||
|
||||
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||
r.logger.WithContext(ctx).Debug("no match")
|
||||
r.logger.WithContext(ctx).Debug("route packet connection to default outbound")
|
||||
for _, rule := range r.rules {
|
||||
if rule.Match(metadata) {
|
||||
r.logger.WithContext(ctx).Info("match ", rule.String())
|
||||
if outbound, loaded := r.Outbound(rule.Outbound()); loaded {
|
||||
return outbound.NewPacketConnection(ctx, conn, metadata.Destination)
|
||||
}
|
||||
r.logger.WithContext(ctx).Error("outbound ", rule.Outbound(), " not found")
|
||||
}
|
||||
}
|
||||
r.logger.WithContext(ctx).Info("no match => ", r.defaultOutbound.Tag())
|
||||
return r.defaultOutbound.NewPacketConnection(ctx, conn, metadata.Destination)
|
||||
}
|
||||
|
||||
func (r *Router) Close() error {
|
||||
return common.Close(
|
||||
common.PtrOrNil(r.geoReader),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *Router) UpdateOutbounds(outbounds []adapter.Outbound) {
|
||||
var defaultOutbound adapter.Outbound
|
||||
outboundByTag := make(map[string]adapter.Outbound)
|
||||
if len(outbounds) > 0 {
|
||||
defaultOutbound = outbounds[0]
|
||||
}
|
||||
for _, outbound := range outbounds {
|
||||
outboundByTag[outbound.Tag()] = outbound
|
||||
}
|
||||
r.defaultOutbound = defaultOutbound
|
||||
r.outboundByTag = outboundByTag
|
||||
}
|
||||
|
||||
func (r *Router) UpdateRules(options []option.Rule) error {
|
||||
rules := make([]adapter.Rule, 0, len(options))
|
||||
for i, rule := range options {
|
||||
switch rule.Type {
|
||||
case "", C.RuleTypeDefault:
|
||||
rules = append(rules, NewDefaultRule(i, rule.DefaultOptions))
|
||||
}
|
||||
}
|
||||
r.rules = rules
|
||||
return nil
|
||||
}
|
||||
|
||||
55
adapter/route/rule.go
Normal file
55
adapter/route/rule.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
var _ adapter.Rule = (*DefaultRule)(nil)
|
||||
|
||||
type DefaultRule struct {
|
||||
index int
|
||||
outbound string
|
||||
items []RuleItem
|
||||
}
|
||||
|
||||
type RuleItem interface {
|
||||
Match(metadata adapter.InboundContext) bool
|
||||
String() string
|
||||
}
|
||||
|
||||
func NewDefaultRule(index int, options option.DefaultRule) *DefaultRule {
|
||||
rule := &DefaultRule{
|
||||
index: index,
|
||||
outbound: options.Outbound,
|
||||
}
|
||||
if len(options.Inbound) > 0 {
|
||||
rule.items = append(rule.items, NewInboundRule(options.Inbound))
|
||||
}
|
||||
return rule
|
||||
}
|
||||
|
||||
func (r *DefaultRule) Match(metadata adapter.InboundContext) bool {
|
||||
for _, item := range r.items {
|
||||
if item.Match(metadata) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *DefaultRule) Outbound() string {
|
||||
return r.outbound
|
||||
}
|
||||
|
||||
func (r *DefaultRule) String() string {
|
||||
var description string
|
||||
description = F.ToString("[", r.index, "]")
|
||||
for _, item := range r.items {
|
||||
description += " "
|
||||
description += item.String()
|
||||
}
|
||||
description += " => " + r.outbound
|
||||
return description
|
||||
}
|
||||
35
adapter/route/rule_inbound.go
Normal file
35
adapter/route/rule_inbound.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
)
|
||||
|
||||
var _ RuleItem = (*InboundRule)(nil)
|
||||
|
||||
type InboundRule struct {
|
||||
inbounds []string
|
||||
inboundMap map[string]bool
|
||||
}
|
||||
|
||||
func NewInboundRule(inbounds []string) RuleItem {
|
||||
rule := &InboundRule{inbounds, make(map[string]bool)}
|
||||
for _, inbound := range inbounds {
|
||||
rule.inboundMap[inbound] = true
|
||||
}
|
||||
return rule
|
||||
}
|
||||
|
||||
func (r *InboundRule) Match(metadata adapter.InboundContext) bool {
|
||||
return r.inboundMap[metadata.Inbound]
|
||||
}
|
||||
|
||||
func (r *InboundRule) String() string {
|
||||
if len(r.inbounds) == 1 {
|
||||
return F.ToString("inbound=", r.inbounds[0])
|
||||
} else {
|
||||
return F.ToString("inbound=[", strings.Join(r.inbounds, " "), "]")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user