Add bridge outbound

This commit is contained in:
世界
2026-07-08 00:10:00 +08:00
parent 8274ce41df
commit e2b25f17db
35 changed files with 3586 additions and 12 deletions
@@ -0,0 +1,69 @@
//go:build darwin
package libbox
import (
"context"
"net/netip"
"os"
"time"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/protocol/bridge"
E "github.com/sagernet/sing/common/exceptions"
)
func NewBridgeService(options *BridgeOptions) (BridgeSession, error) {
if options == nil {
return nil, E.New("missing bridge options")
}
serviceOptions := bridge.ServiceOptions{
MTU: int(options.MTU),
Interface: options.Interface,
}
if options.Inet4Port != "" {
inet4Port, err := netip.ParseAddr(options.Inet4Port)
if err != nil {
return nil, E.Cause(err, "parse inet4 port address")
}
serviceOptions.Inet4Port = inet4Port
}
if options.Inet6Port != "" {
inet6Port, err := netip.ParseAddr(options.Inet6Port)
if err != nil {
return nil, E.Cause(err, "parse inet6 port address")
}
serviceOptions.Inet6Port = inet6Port
}
logFactory := log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now()}, os.Stderr, "", nil, false)
serviceOptions.Logger = logFactory.Logger()
service, err := bridge.NewService(serviceOptions)
if err != nil {
return nil, err
}
return &bridgeServiceSession{service}, nil
}
type bridgeServiceSession struct {
service *bridge.Service
}
func (s *bridgeServiceSession) FileDescriptor() int32 {
return int32(s.service.FileDescriptor())
}
func (s *bridgeServiceSession) Name() string {
return s.service.Name()
}
func (s *bridgeServiceSession) Inet6Active() bool {
return s.service.Inet6Active()
}
func (s *bridgeServiceSession) SetEgress(interfaceName string) error {
return s.service.SetEgress(interfaceName)
}
func (s *bridgeServiceSession) Close() error {
return s.service.Close()
}
@@ -0,0 +1,71 @@
//go:build linux
package libbox
import (
"context"
"net/netip"
"os"
"time"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/protocol/bridge"
E "github.com/sagernet/sing/common/exceptions"
)
func NewBridgeService(options *BridgeOptions) (BridgeSession, error) {
if options == nil {
return nil, E.New("missing bridge options")
}
serviceOptions := bridge.ServiceOptions{
BridgeName: options.BridgeName,
MTU: int(options.MTU),
RuleIndex: int(options.RuleIndex),
RouteTable: int(options.RouteTable),
}
if options.Inet4Port != "" {
inet4Port, err := netip.ParseAddr(options.Inet4Port)
if err != nil {
return nil, E.Cause(err, "parse inet4 port address")
}
serviceOptions.Inet4Port = inet4Port
}
if options.Inet6Port != "" {
inet6Port, err := netip.ParseAddr(options.Inet6Port)
if err != nil {
return nil, E.Cause(err, "parse inet6 port address")
}
serviceOptions.Inet6Port = inet6Port
}
logFactory := log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now()}, os.Stderr, "", nil, false)
serviceOptions.Logger = logFactory.Logger()
service, err := bridge.NewService(serviceOptions)
if err != nil {
return nil, err
}
return &bridgeServiceSession{service}, nil
}
type bridgeServiceSession struct {
service *bridge.Service
}
func (s *bridgeServiceSession) FileDescriptor() int32 {
return int32(s.service.FileDescriptor())
}
func (s *bridgeServiceSession) Name() string {
return s.service.Name()
}
func (s *bridgeServiceSession) Inet6Active() bool {
return s.service.Inet6Active()
}
func (s *bridgeServiceSession) SetEgress(interfaceName string) error {
return s.service.SetEgress(interfaceName)
}
func (s *bridgeServiceSession) Close() error {
return s.service.Close()
}
+9
View File
@@ -0,0 +1,9 @@
//go:build !linux && !darwin
package libbox
import E "github.com/sagernet/sing/common/exceptions"
func NewBridgeService(options *BridgeOptions) (BridgeSession, error) {
return nil, E.New("bridge service not supported on this platform")
}
+8
View File
@@ -181,6 +181,14 @@ func (s *platformInterfaceStub) TailscaleHostname() string {
return ""
}
func (s *platformInterfaceStub) UsePlatformBridge() bool {
return false
}
func (s *platformInterfaceStub) CreateBridge(options adapter.BridgeOptions) (adapter.BridgeSession, error) {
return nil, os.ErrInvalid
}
func (s *platformInterfaceStub) LookupUser(username string) (*adapter.PlatformUser, error) {
return nil, os.ErrInvalid
}
+20
View File
@@ -27,6 +27,26 @@ type PlatformInterface interface {
LookupSFTPServer() (string, error)
ReadSystemSSHHostKey() (string, error)
TailscaleHostname() string
UsePlatformBridge() bool
CreateBridge(options *BridgeOptions) (BridgeSession, error)
}
type BridgeOptions struct {
BridgeName string
MTU int32
Inet4Port string
Inet6Port string
Interface string
RuleIndex int32
RouteTable int32
}
type BridgeSession interface {
FileDescriptor() int32
Name() string
Inet6Active() bool
SetEgress(interfaceName string) error
Close() error
}
type PlatformUser struct {
+49
View File
@@ -285,6 +285,55 @@ func (w *platformInterfaceWrapper) TailscaleHostname() string {
return w.iif.TailscaleHostname()
}
func (w *platformInterfaceWrapper) UsePlatformBridge() bool {
return w.iif.UsePlatformBridge()
}
func (w *platformInterfaceWrapper) CreateBridge(options adapter.BridgeOptions) (adapter.BridgeSession, error) {
bridgeOptions := &BridgeOptions{
BridgeName: options.BridgeName,
MTU: int32(options.MTU),
Interface: options.Interface,
RuleIndex: int32(options.RuleIndex),
RouteTable: int32(options.RouteTable),
}
if options.Inet4Port.IsValid() {
bridgeOptions.Inet4Port = options.Inet4Port.String()
}
if options.Inet6Port.IsValid() {
bridgeOptions.Inet6Port = options.Inet6Port.String()
}
session, err := w.iif.CreateBridge(bridgeOptions)
if err != nil {
return nil, err
}
return &bridgeSessionWrapper{session}, nil
}
type bridgeSessionWrapper struct {
session BridgeSession
}
func (w *bridgeSessionWrapper) FileDescriptor() int {
return int(w.session.FileDescriptor())
}
func (w *bridgeSessionWrapper) Name() string {
return w.session.Name()
}
func (w *bridgeSessionWrapper) Inet6Active() bool {
return w.session.Inet6Active()
}
func (w *bridgeSessionWrapper) SetEgress(interfaceName string) error {
return w.session.SetEgress(interfaceName)
}
func (w *bridgeSessionWrapper) Close() error {
return w.session.Close()
}
func (w *platformInterfaceWrapper) LookupUser(username string) (*adapter.PlatformUser, error) {
platformUser, err := w.iif.LookupUser(username)
if err != nil {