Fix iOS brdige
This commit is contained in:
@@ -121,6 +121,12 @@ func (b *backendDarwin) start() error {
|
||||
if err != nil {
|
||||
return E.Cause(err, "enable pf")
|
||||
}
|
||||
dropRules := bridgeDropRules(b.tunName, b.inet4Port, b.inet6Port)
|
||||
err = b.pfDevice.LoadAnchor(b.anchorName, dropRules)
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize bridge pf rules")
|
||||
}
|
||||
b.currentRules = dropRules
|
||||
b.batchTUN = tunInterface.(tun.DarwinTUN)
|
||||
b.closed = make(chan struct{})
|
||||
b.readDone = make(chan struct{})
|
||||
@@ -324,11 +330,15 @@ func (b *backendDarwin) syncEgress() {
|
||||
default:
|
||||
}
|
||||
egress := b.resolveEgress()
|
||||
var rules []pfAnchorRule
|
||||
rules := bridgeDropRules(b.tunName, b.inet4Port, b.inet6Port)
|
||||
var buildErr error
|
||||
if egress != "" {
|
||||
rules = buildBridgeAnchorRules(b.logger, b.tunName, egress, b.boundInterface, b.inet4Port, b.inet6Port)
|
||||
rules, buildErr = buildBridgeAnchorRules(b.tunName, egress, b.boundInterface, b.inet4Port, b.inet6Port)
|
||||
}
|
||||
if slices.Equal(rules, b.currentRules) {
|
||||
if buildErr != nil {
|
||||
b.logger.Debug(buildErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
err := b.pfDevice.LoadAnchor(b.anchorName, rules)
|
||||
@@ -337,11 +347,14 @@ func (b *backendDarwin) syncEgress() {
|
||||
return
|
||||
}
|
||||
b.currentRules = rules
|
||||
if len(rules) == 0 {
|
||||
if buildErr != nil || egress == "" {
|
||||
b.logger.Debug("bridge egress unavailable, dropping forwarded traffic")
|
||||
} else {
|
||||
b.logger.Debug("bridge egress ", egress)
|
||||
}
|
||||
if buildErr != nil {
|
||||
b.logger.Debug(buildErr)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backendDarwin) enablePf() error {
|
||||
|
||||
@@ -19,6 +19,7 @@ const (
|
||||
pfRulesetNat = 2
|
||||
|
||||
pfActionPass = 0
|
||||
pfActionDrop = 1
|
||||
pfActionScrub = 2
|
||||
pfActionNat = 4
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
@@ -12,26 +13,28 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func buildBridgeAnchorRules(ruleLogger logger.ContextLogger, tunName string, egress string, boundInterface string, inet4Port netip.Addr, inet6Port netip.Addr) []pfAnchorRule {
|
||||
func buildBridgeAnchorRules(tunName string, egress string, boundInterface string, inet4Port netip.Addr, inet6Port netip.Addr) ([]pfAnchorRule, error) {
|
||||
rules := bridgeDropRules(tunName, inet4Port, inet6Port)
|
||||
egressInterface, err := net.InterfaceByName(egress)
|
||||
if err != nil {
|
||||
return nil
|
||||
return rules, E.Cause(err, "find bridge egress ", egress)
|
||||
}
|
||||
isCellular := strings.HasPrefix(egressInterface.Name, "pdp_ip")
|
||||
isPhysical := egressInterface.Flags&net.FlagBroadcast != 0 && egressInterface.Flags&net.FlagLoopback == 0 &&
|
||||
egressInterface.Flags&net.FlagPointToPoint == 0
|
||||
if boundInterface == "" && !isCellular && !isPhysical {
|
||||
return rules, E.New("bridge egress ", egress, " is not a physical or cellular interface")
|
||||
}
|
||||
routeWithoutGateway := isCellular || boundInterface != "" && egressInterface.Flags&net.FlagPointToPoint != 0
|
||||
// The flowswitch aggregates forwarded TCP into packets larger than the tun
|
||||
// MTU, and pf_route() only fragments when they exceed the egress MTU: a
|
||||
// large-MTU utun target feeds them whole into the overflow described at
|
||||
// bridgeTunMTUDarwin.
|
||||
if egressInterface.Flags&net.FlagBroadcast == 0 || egressInterface.Flags&net.FlagLoopback != 0 ||
|
||||
egressInterface.Flags&net.FlagPointToPoint != 0 {
|
||||
ruleLogger.Error("bridge egress ", egress, " is not a physical interface, dropping forwarded traffic")
|
||||
return nil
|
||||
}
|
||||
mtu := egressInterface.MTU
|
||||
if mtu < 576 || mtu > bridgeTunMTUDarwin {
|
||||
mtu = bridgeTunMTUDarwin
|
||||
}
|
||||
localPrefixes, inet4Interfaces, inet6Interfaces := collectLocalSegments(egress, boundInterface, inet4Port.IsValid(), inet6Port.IsValid())
|
||||
var rules []pfAnchorRule
|
||||
if inet4Port.IsValid() {
|
||||
rules = append(rules, pfScrubRule(egress, inet4Port, uint16(mtu-40)))
|
||||
}
|
||||
@@ -57,20 +60,16 @@ func buildBridgeAnchorRules(ruleLogger logger.ContextLogger, tunName string, egr
|
||||
// leaves via the egress and the nat rule applies there.
|
||||
if inet4Port.IsValid() {
|
||||
gateway := interfaceGateway(egressInterface.Index, true)
|
||||
if gateway.IsValid() {
|
||||
if gateway.IsValid() || routeWithoutGateway {
|
||||
rules = append(rules, pfRouteToRule(tunName, egress, gateway, inet4Port))
|
||||
rules = append(rules, pfReplyToRule(tunName, egress, inet4Port))
|
||||
} else {
|
||||
ruleLogger.Debug("no IPv4 gateway on ", egress, ", relying on the default route")
|
||||
}
|
||||
}
|
||||
if inet6Port.IsValid() {
|
||||
gateway := interfaceGateway(egressInterface.Index, false)
|
||||
if gateway.IsValid() {
|
||||
if gateway.IsValid() || routeWithoutGateway {
|
||||
rules = append(rules, pfRouteToRule(tunName, egress, gateway, inet6Port))
|
||||
rules = append(rules, pfReplyToRule(tunName, egress, inet6Port))
|
||||
} else {
|
||||
ruleLogger.Debug("no IPv6 gateway on ", egress, ", relying on the default route")
|
||||
}
|
||||
}
|
||||
// pf rules are last-match: the pass rules below override the route-to pin
|
||||
@@ -83,6 +82,17 @@ func buildBridgeAnchorRules(ruleLogger logger.ContextLogger, tunName string, egr
|
||||
}
|
||||
rules = append(rules, pfPassInRule(tunName, port, prefix))
|
||||
}
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func bridgeDropRules(tunName string, inet4Port netip.Addr, inet6Port netip.Addr) []pfAnchorRule {
|
||||
var rules []pfAnchorRule
|
||||
if inet4Port.IsValid() {
|
||||
rules = append(rules, pfDropInRule(tunName, inet4Port))
|
||||
}
|
||||
if inet6Port.IsValid() {
|
||||
rules = append(rules, pfDropInRule(tunName, inet6Port))
|
||||
}
|
||||
return rules
|
||||
}
|
||||
|
||||
@@ -196,11 +206,24 @@ func pfPassInRule(tunName string, port netip.Addr, destination netip.Prefix) pfA
|
||||
return pfAnchorRule{RulesetIndex: pfRulesetFilter, Rule: rule}
|
||||
}
|
||||
|
||||
func pfDropInRule(tunName string, port netip.Addr) pfAnchorRule {
|
||||
rule := pfRule{
|
||||
Action: pfActionDrop,
|
||||
Direction: pfDirectionIn,
|
||||
AF: pfFamily(port.Is4()),
|
||||
}
|
||||
copy(rule.IfName[:], tunName)
|
||||
rule.Src.Addr = pfHostAddress(port)
|
||||
return pfAnchorRule{RulesetIndex: pfRulesetFilter, Rule: rule}
|
||||
}
|
||||
|
||||
func pfRouteToRule(tunName string, egress string, gateway netip.Addr, port netip.Addr) pfAnchorRule {
|
||||
anchorRule := pfPassInRule(tunName, port, netip.Prefix{})
|
||||
anchorRule.Rule.RouteAction = pfRouteActionRouteTo
|
||||
copy(anchorRule.Rule.TagName[:], bridgeTagName(tunName))
|
||||
anchorRule.Pool = pfPoolAddr{Addr: pfHostAddress(gateway)}
|
||||
if gateway.IsValid() {
|
||||
anchorRule.Pool.Addr = pfHostAddress(gateway)
|
||||
}
|
||||
copy(anchorRule.Pool.IfName[:], egress)
|
||||
return anchorRule
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ type serviceBase struct {
|
||||
access sync.Mutex
|
||||
egressName string
|
||||
closed bool
|
||||
applyEgress func()
|
||||
applyEgress func() error
|
||||
}
|
||||
|
||||
func (s *serviceBase) FileDescriptor() int {
|
||||
@@ -50,8 +50,7 @@ func (s *serviceBase) SetEgress(interfaceName string) error {
|
||||
return os.ErrClosed
|
||||
}
|
||||
s.egressName = interfaceName
|
||||
s.applyEgress()
|
||||
return nil
|
||||
return s.applyEgress()
|
||||
}
|
||||
|
||||
func (s *serviceBase) syncEgress() {
|
||||
@@ -60,7 +59,10 @@ func (s *serviceBase) syncEgress() {
|
||||
if s.closed {
|
||||
return
|
||||
}
|
||||
s.applyEgress()
|
||||
err := s.applyEgress()
|
||||
if err != nil {
|
||||
s.logger.Debug(E.Cause(err, "update bridge egress"))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serviceBase) startNetworkMonitor() {
|
||||
|
||||
@@ -101,29 +101,36 @@ func (s *Service) start() error {
|
||||
return E.Cause(err, "enable pf")
|
||||
}
|
||||
s.pfToken = token
|
||||
dropRules := bridgeDropRules(s.tunName, s.inet4Port, s.inet6Port)
|
||||
err = s.pfDevice.LoadAnchor(s.anchorName, dropRules)
|
||||
if err != nil {
|
||||
return E.Cause(err, "initialize bridge pf rules")
|
||||
}
|
||||
s.currentRules = dropRules
|
||||
s.startNetworkMonitor()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) syncEgressLocked() {
|
||||
var rules []pfAnchorRule
|
||||
func (s *Service) syncEgressLocked() error {
|
||||
rules := bridgeDropRules(s.tunName, s.inet4Port, s.inet6Port)
|
||||
var buildErr error
|
||||
if s.egressName != "" {
|
||||
rules = buildBridgeAnchorRules(s.logger, s.tunName, s.egressName, s.boundInterface, s.inet4Port, s.inet6Port)
|
||||
rules, buildErr = buildBridgeAnchorRules(s.tunName, s.egressName, s.boundInterface, s.inet4Port, s.inet6Port)
|
||||
}
|
||||
if slices.Equal(rules, s.currentRules) {
|
||||
return
|
||||
return buildErr
|
||||
}
|
||||
err := s.pfDevice.LoadAnchor(s.anchorName, rules)
|
||||
if err != nil {
|
||||
s.logger.Debug(E.Cause(err, "apply bridge egress ", s.egressName))
|
||||
return
|
||||
return E.Cause(err, "apply bridge egress ", s.egressName)
|
||||
}
|
||||
s.currentRules = rules
|
||||
if len(rules) == 0 {
|
||||
if buildErr != nil || s.egressName == "" {
|
||||
s.logger.Debug("bridge egress unavailable, dropping forwarded traffic")
|
||||
} else {
|
||||
s.logger.Debug("bridge egress ", s.egressName)
|
||||
}
|
||||
return buildErr
|
||||
}
|
||||
|
||||
func (s *Service) Close() error {
|
||||
|
||||
@@ -116,13 +116,13 @@ func (s *Service) start(bridgeName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) syncEgressLocked() {
|
||||
func (s *Service) syncEgressLocked() error {
|
||||
flushBridgeRouteTable(s.routeTable)
|
||||
if s.egressName == "" {
|
||||
for _, family := range activeBridgeFamilies(s.inet6Port) {
|
||||
blackholeBridgeDefault(s.routeTable, family)
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
link, err := netlink.LinkByName(s.egressName)
|
||||
if err != nil {
|
||||
@@ -130,12 +130,13 @@ func (s *Service) syncEgressLocked() {
|
||||
blackholeBridgeDefault(s.routeTable, family)
|
||||
}
|
||||
s.logger.Debug("bridge egress ", s.egressName, " absent, dropping forwarded traffic")
|
||||
return
|
||||
return nil
|
||||
}
|
||||
for _, family := range activeBridgeFamilies(s.inet6Port) {
|
||||
s.syncEgressFamilyLocked(family, link.Attrs().Index)
|
||||
}
|
||||
s.updateClampLocked(link.Attrs().MTU)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlike the in-process backend this copies routes from every table: on Android
|
||||
|
||||
Reference in New Issue
Block a user