Fix iOS brdige

This commit is contained in:
世界
2026-07-16 13:15:33 +08:00
parent 4ae5d4a0ca
commit 653b81cfd9
6 changed files with 79 additions and 32 deletions
+16 -3
View File
@@ -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 {
+1
View File
@@ -19,6 +19,7 @@ const (
pfRulesetNat = 2
pfActionPass = 0
pfActionDrop = 1
pfActionScrub = 2
pfActionNat = 4
+38 -15
View File
@@ -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
}
+6 -4
View File
@@ -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() {
+14 -7
View File
@@ -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 {
+4 -3
View File
@@ -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