├── .gitignore ├── README.md ├── boxapi ├── go_http.go ├── go_net.go ├── v2ray_server.go └── v2ray_stats_service.go ├── boxbox ├── api.go ├── box.go ├── box_outbound.go ├── debug.go ├── version.go └── wtf.go ├── boxdns ├── boxdns.go ├── monitor_windows.go └── underlying_dns.go ├── boxmain ├── cmd_check.go ├── cmd_format.go ├── cmd_generate.go ├── cmd_generate_tls.go ├── cmd_generate_vapid.go ├── cmd_generate_wireguard.go ├── cmd_run.go ├── cmd_tools.go ├── cmd_tools_connect.go ├── cmd_tools_fetch.go ├── cmd_tools_synctime.go ├── cmd_version.go ├── color.go ├── debug.go ├── debug_linux.go ├── debug_stub.go └── main.go ├── distro └── all │ └── all.go ├── go.mod ├── go.sum ├── libs ├── get_source.sh └── get_source_env.sh └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | *.exe 4 | /datagen 5 | /sing-box* 6 | *.json 7 | /test 8 | *.db 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sing-box-extra 2 | 3 | sing-box and some additional functionality or API 4 | 5 | ## Components 6 | 7 | see distro/all 8 | 9 | ### boxapi 10 | 11 | - v2ray service API (An implementation that replaces the sing-box v2ray option, the caller can set it after obtaining the router) 12 | - golang http.Client API 13 | 14 | ### boxdns 15 | 16 | - underlying DNS for Linux & Windows 17 | 18 | ### boxbox 19 | 20 | Custom Box 21 | 22 | Use this instead of `github.com/sagernet/sing-box` 23 | 24 | `boxbox.New(Options,PlatformOptions)` is used in `libgojni.so` 25 | 26 | ### boxmain 27 | 28 | Custom CLI tools 29 | 30 | `boxmain.Create(jsonContent)` creates `boxbox.Box` instead of `box.Box` (used in `nekobox_core`) 31 | -------------------------------------------------------------------------------- /boxapi/go_http.go: -------------------------------------------------------------------------------- 1 | package boxapi 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/matsuridayo/sing-box-extra/boxbox" 10 | ) 11 | 12 | func CreateProxyHttpClient(box *boxbox.Box) *http.Client { 13 | transport := &http.Transport{ 14 | TLSHandshakeTimeout: time.Second * 3, 15 | ResponseHeaderTimeout: time.Second * 3, 16 | } 17 | 18 | if box != nil { 19 | transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { 20 | return DialContext(ctx, box, network, addr) 21 | } 22 | } 23 | 24 | client := &http.Client{ 25 | Transport: transport, 26 | } 27 | 28 | return client 29 | } 30 | -------------------------------------------------------------------------------- /boxapi/go_net.go: -------------------------------------------------------------------------------- 1 | package boxapi 2 | 3 | import ( 4 | "context" 5 | "github.com/matsuridayo/sing-box-extra/boxbox" 6 | "github.com/sagernet/sing-box/common/dialer" 7 | "github.com/sagernet/sing/common/metadata" 8 | N "github.com/sagernet/sing/common/network" 9 | "net" 10 | ) 11 | 12 | func DialContext(ctx context.Context, box *boxbox.Box, network, addr string) (net.Conn, error) { 13 | router := box.Router() 14 | conn, err := dialer.NewRouter(router).DialContext(ctx, network, metadata.ParseSocksaddr(addr)) 15 | if err != nil { 16 | return nil, err 17 | } 18 | if vs := router.V2RayServer(); vs != nil { 19 | if ss, ok := vs.StatsService().(*SbStatsService); ok { 20 | outbound, _ := router.DefaultOutbound(N.NetworkName(network)) 21 | conn = ss.RoutedConnectionInternal("", outbound.Tag(), "", conn, false) 22 | } 23 | } 24 | return conn, nil 25 | } 26 | 27 | func DialUDP(ctx context.Context, box *boxbox.Box) (net.PacketConn, error) { 28 | router := box.Router() 29 | return dialer.NewRouter(router).ListenPacket(ctx, metadata.Socksaddr{}) 30 | } 31 | -------------------------------------------------------------------------------- /boxapi/v2ray_server.go: -------------------------------------------------------------------------------- 1 | package boxapi 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | "github.com/sagernet/sing-box/option" 8 | ) 9 | 10 | type SbV2rayServer struct { 11 | ss *SbStatsService 12 | } 13 | 14 | func NewSbV2rayServer(options option.V2RayStatsServiceOptions) *SbV2rayServer { 15 | return &SbV2rayServer{ 16 | ss: NewSbStatsService(options), 17 | } 18 | } 19 | 20 | func (s *SbV2rayServer) Start() error { return nil } 21 | func (s *SbV2rayServer) Close() error { return nil } 22 | func (s *SbV2rayServer) StatsService() adapter.V2RayStatsService { return s.ss } 23 | 24 | // NekoRay style API 25 | 26 | func (s *SbV2rayServer) QueryStats(name string) int64 { 27 | value, err := s.ss.GetStats(context.TODO(), name, true) 28 | if err == nil { 29 | return value 30 | } 31 | return 0 32 | } 33 | -------------------------------------------------------------------------------- /boxapi/v2ray_stats_service.go: -------------------------------------------------------------------------------- 1 | package boxapi 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "sync" 7 | "time" 8 | 9 | "github.com/sagernet/sing-box/adapter" 10 | "github.com/sagernet/sing-box/option" 11 | "github.com/sagernet/sing/common/atomic" 12 | "github.com/sagernet/sing/common/bufio" 13 | E "github.com/sagernet/sing/common/exceptions" 14 | N "github.com/sagernet/sing/common/network" 15 | ) 16 | 17 | var ( 18 | _ adapter.V2RayStatsService = (*SbStatsService)(nil) 19 | ) 20 | 21 | type SbStatsService struct { 22 | createdAt time.Time 23 | inbounds map[string]bool 24 | outbounds map[string]bool 25 | users map[string]bool 26 | access sync.Mutex 27 | counters map[string]*atomic.Int64 28 | } 29 | 30 | func NewSbStatsService(options option.V2RayStatsServiceOptions) *SbStatsService { 31 | if !options.Enabled { 32 | return nil 33 | } 34 | inbounds := make(map[string]bool) 35 | outbounds := make(map[string]bool) 36 | users := make(map[string]bool) 37 | for _, inbound := range options.Inbounds { 38 | inbounds[inbound] = true 39 | } 40 | for _, outbound := range options.Outbounds { 41 | outbounds[outbound] = true 42 | } 43 | for _, user := range options.Users { 44 | users[user] = true 45 | } 46 | return &SbStatsService{ 47 | createdAt: time.Now(), 48 | inbounds: inbounds, 49 | outbounds: outbounds, 50 | users: users, 51 | counters: make(map[string]*atomic.Int64), 52 | } 53 | } 54 | 55 | func (s *SbStatsService) RoutedConnection(inbound string, outbound string, user string, conn net.Conn) net.Conn { 56 | return s.RoutedConnectionInternal(inbound, outbound, user, conn, true) 57 | } 58 | 59 | func (s *SbStatsService) RoutedConnectionInternal(inbound string, outbound string, user string, conn net.Conn, directIn bool) net.Conn { 60 | var readCounter []*atomic.Int64 61 | var writeCounter []*atomic.Int64 62 | countInbound := inbound != "" && s.inbounds[inbound] 63 | countOutbound := outbound != "" && s.outbounds[outbound] 64 | countUser := user != "" && s.users[user] 65 | if !countInbound && !countOutbound && !countUser { 66 | return conn 67 | } 68 | s.access.Lock() 69 | if countInbound { 70 | readCounter = append(readCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>uplink")) 71 | writeCounter = append(writeCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>downlink")) 72 | } 73 | if countOutbound { 74 | readCounter = append(readCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>uplink")) 75 | writeCounter = append(writeCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>downlink")) 76 | } 77 | if countUser { 78 | readCounter = append(readCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>uplink")) 79 | writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink")) 80 | } 81 | s.access.Unlock() 82 | if directIn { 83 | conn = bufio.NewInt64CounterConn(conn, readCounter, writeCounter) 84 | } else { 85 | conn = bufio.NewInt64CounterConn(conn, writeCounter, readCounter) 86 | } 87 | return conn 88 | } 89 | 90 | func (s *SbStatsService) RoutedPacketConnection(inbound string, outbound string, user string, conn N.PacketConn) N.PacketConn { 91 | var readCounter []*atomic.Int64 92 | var writeCounter []*atomic.Int64 93 | countInbound := inbound != "" && s.inbounds[inbound] 94 | countOutbound := outbound != "" && s.outbounds[outbound] 95 | countUser := user != "" && s.users[user] 96 | if !countInbound && !countOutbound && !countUser { 97 | return conn 98 | } 99 | s.access.Lock() 100 | if countInbound { 101 | readCounter = append(readCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>uplink")) 102 | writeCounter = append(writeCounter, s.loadOrCreateCounter("inbound>>>"+inbound+">>>traffic>>>downlink")) 103 | } 104 | if countOutbound { 105 | readCounter = append(readCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>uplink")) 106 | writeCounter = append(writeCounter, s.loadOrCreateCounter("outbound>>>"+outbound+">>>traffic>>>downlink")) 107 | } 108 | if countUser { 109 | readCounter = append(readCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>uplink")) 110 | writeCounter = append(writeCounter, s.loadOrCreateCounter("user>>>"+user+">>>traffic>>>downlink")) 111 | } 112 | s.access.Unlock() 113 | return bufio.NewInt64CounterPacketConn(conn, readCounter, writeCounter) 114 | } 115 | 116 | func (s *SbStatsService) GetStats(ctx context.Context, name string, reset bool) (int64, error) { 117 | s.access.Lock() 118 | counter, loaded := s.counters[name] 119 | s.access.Unlock() 120 | if !loaded { 121 | return 0, E.New(name, " not found.") 122 | } 123 | var value int64 124 | if reset { 125 | value = counter.Swap(0) 126 | } else { 127 | value = counter.Load() 128 | } 129 | return value, nil 130 | } 131 | 132 | // QueryStats 133 | 134 | // GetSysStats 135 | 136 | //nolint:staticcheck 137 | func (s *SbStatsService) loadOrCreateCounter(name string) *atomic.Int64 { 138 | counter, loaded := s.counters[name] 139 | if loaded { 140 | return counter 141 | } 142 | counter = &atomic.Int64{} 143 | s.counters[name] = counter 144 | return counter 145 | } 146 | -------------------------------------------------------------------------------- /boxbox/api.go: -------------------------------------------------------------------------------- 1 | package boxbox 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "reflect" 8 | "time" 9 | "unsafe" 10 | 11 | "github.com/sagernet/sing-box/log" 12 | ) 13 | 14 | func (s *Box) SetLogWritter(w io.Writer) { 15 | writer_ := reflect.Indirect(reflect.ValueOf(s.logFactory)).FieldByName("writer") 16 | writer_ = reflect.NewAt(writer_.Type(), unsafe.Pointer(writer_.UnsafeAddr())).Elem() 17 | writer_.Set(reflect.ValueOf(w)) 18 | } 19 | 20 | func (s *Box) GetLogPlatformFormatter() *log.Formatter { 21 | platformFormatter_ := reflect.Indirect(reflect.ValueOf(s.logFactory)).FieldByName("platformFormatter") 22 | platformFormatter_ = reflect.NewAt(platformFormatter_.Type(), unsafe.Pointer(platformFormatter_.UnsafeAddr())) 23 | platformFormatter := platformFormatter_.Interface().(*log.Formatter) 24 | return platformFormatter 25 | } 26 | 27 | func (s *Box) CloseWithTimeout(cancal context.CancelFunc, d time.Duration, logFunc func(v ...any)) { 28 | start := time.Now() 29 | t := time.NewTimer(d) 30 | done := make(chan struct{}) 31 | 32 | printCloseTime := func() { 33 | logFunc("[Info] sing-box closed in", fmt.Sprintf("%d ms", time.Since(start).Milliseconds())) 34 | } 35 | 36 | go func(cancel context.CancelFunc, closer io.Closer) { 37 | cancel() 38 | closer.Close() 39 | close(done) 40 | if !t.Stop() { 41 | printCloseTime() 42 | } 43 | }(cancal, s) 44 | 45 | select { 46 | case <-t.C: 47 | logFunc("[Warning] sing-box close takes longer than expected.") 48 | case <-done: 49 | printCloseTime() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /boxbox/box.go: -------------------------------------------------------------------------------- 1 | package boxbox 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "os" 8 | "runtime/debug" 9 | "time" 10 | 11 | "github.com/sagernet/sing-box/adapter" 12 | "github.com/sagernet/sing-box/experimental" 13 | "github.com/sagernet/sing-box/experimental/libbox/platform" 14 | "github.com/sagernet/sing-box/inbound" 15 | "github.com/sagernet/sing-box/log" 16 | "github.com/sagernet/sing-box/option" 17 | "github.com/sagernet/sing-box/outbound" 18 | "github.com/sagernet/sing-box/route" 19 | "github.com/sagernet/sing/common" 20 | E "github.com/sagernet/sing/common/exceptions" 21 | F "github.com/sagernet/sing/common/format" 22 | "github.com/sagernet/sing/service" 23 | "github.com/sagernet/sing/service/pause" 24 | ) 25 | 26 | var _ adapter.Service = (*Box)(nil) 27 | 28 | type Box struct { 29 | createdAt time.Time 30 | router adapter.Router 31 | inbounds []adapter.Inbound 32 | outbounds []adapter.Outbound 33 | logFactory log.Factory 34 | logger log.ContextLogger 35 | preServices map[string]adapter.Service 36 | postServices map[string]adapter.Service 37 | done chan struct{} 38 | } 39 | 40 | type Options struct { 41 | option.Options 42 | Context context.Context 43 | PlatformInterface platform.Interface 44 | } 45 | 46 | func New(options Options) (*Box, error) { 47 | ctx := options.Context 48 | if ctx == nil { 49 | ctx = context.Background() 50 | } 51 | ctx = service.ContextWithDefaultRegistry(ctx) 52 | ctx = pause.ContextWithDefaultManager(ctx) 53 | 54 | createdAt := time.Now() 55 | experimentalOptions := common.PtrValueOrDefault(options.Experimental) 56 | applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) 57 | var needClashAPI bool 58 | var needV2RayAPI bool 59 | if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" { 60 | needClashAPI = true 61 | } 62 | if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { 63 | needV2RayAPI = true 64 | } 65 | var defaultLogWriter io.Writer 66 | if options.PlatformInterface != nil { 67 | defaultLogWriter = io.Discard 68 | } 69 | logFactory, err := log.New(log.Options{ 70 | Context: ctx, 71 | Options: common.PtrValueOrDefault(options.Log), 72 | Observable: needClashAPI, 73 | DefaultWriter: defaultLogWriter, 74 | BaseTime: createdAt, 75 | }) 76 | if err != nil { 77 | return nil, E.Cause(err, "create log factory") 78 | } 79 | router, err := route.NewRouter( 80 | ctx, 81 | logFactory, 82 | common.PtrValueOrDefault(options.Route), 83 | common.PtrValueOrDefault(options.DNS), 84 | common.PtrValueOrDefault(options.NTP), 85 | options.Inbounds, 86 | options.PlatformInterface, 87 | ) 88 | if err != nil { 89 | return nil, E.Cause(err, "parse route options") 90 | } 91 | inbounds := make([]adapter.Inbound, 0, len(options.Inbounds)) 92 | outbounds := make([]adapter.Outbound, 0, len(options.Outbounds)) 93 | for i, inboundOptions := range options.Inbounds { 94 | var in adapter.Inbound 95 | var tag string 96 | if inboundOptions.Tag != "" { 97 | tag = inboundOptions.Tag 98 | } else { 99 | tag = F.ToString(i) 100 | } 101 | in, err = inbound.New( 102 | ctx, 103 | router, 104 | logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), 105 | inboundOptions, 106 | options.PlatformInterface, 107 | ) 108 | if err != nil { 109 | return nil, E.Cause(err, "parse inbound[", i, "]") 110 | } 111 | inbounds = append(inbounds, in) 112 | } 113 | for i, outboundOptions := range options.Outbounds { 114 | var out adapter.Outbound 115 | var tag string 116 | if outboundOptions.Tag != "" { 117 | tag = outboundOptions.Tag 118 | } else { 119 | tag = F.ToString(i) 120 | } 121 | out, err = outbound.New( 122 | ctx, 123 | router, 124 | logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), 125 | tag, 126 | outboundOptions) 127 | if err != nil { 128 | return nil, E.Cause(err, "parse outbound[", i, "]") 129 | } 130 | outbounds = append(outbounds, out) 131 | } 132 | err = router.Initialize(inbounds, outbounds, func() adapter.Outbound { 133 | out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"}) 134 | common.Must(oErr) 135 | outbounds = append(outbounds, out) 136 | return out 137 | }) 138 | if err != nil { 139 | return nil, err 140 | } 141 | if options.PlatformInterface != nil { 142 | err = options.PlatformInterface.Initialize(ctx, router) 143 | if err != nil { 144 | return nil, E.Cause(err, "initialize platform interface") 145 | } 146 | } 147 | preServices := make(map[string]adapter.Service) 148 | postServices := make(map[string]adapter.Service) 149 | if needClashAPI { 150 | clashServer, err := experimental.NewClashServer(ctx, router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI)) 151 | if err != nil { 152 | return nil, E.Cause(err, "create clash api server") 153 | } 154 | router.SetClashServer(clashServer) 155 | preServices["clash api"] = clashServer 156 | } 157 | if needV2RayAPI { 158 | v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(options.Experimental.V2RayAPI)) 159 | if err != nil { 160 | return nil, E.Cause(err, "create v2ray api server") 161 | } 162 | router.SetV2RayServer(v2rayServer) 163 | preServices["v2ray api"] = v2rayServer 164 | } 165 | return &Box{ 166 | router: router, 167 | inbounds: inbounds, 168 | outbounds: outbounds, 169 | createdAt: createdAt, 170 | logFactory: logFactory, 171 | logger: logFactory.Logger(), 172 | preServices: preServices, 173 | postServices: postServices, 174 | done: make(chan struct{}), 175 | }, nil 176 | } 177 | 178 | func (s *Box) PreStart() error { 179 | err := s.preStart() 180 | if err != nil { 181 | // TODO: remove catch error 182 | defer func() { 183 | v := recover() 184 | if v != nil { 185 | log.Error(E.Cause(err, "origin error")) 186 | debug.PrintStack() 187 | panic("panic on early close: " + fmt.Sprint(v)) 188 | } 189 | }() 190 | s.Close() 191 | return err 192 | } 193 | s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)") 194 | return nil 195 | } 196 | 197 | func (s *Box) Start() error { 198 | err := s.start() 199 | if err != nil { 200 | // TODO: remove catch error 201 | defer func() { 202 | v := recover() 203 | if v != nil { 204 | log.Error(E.Cause(err, "origin error")) 205 | debug.PrintStack() 206 | panic("panic on early close: " + fmt.Sprint(v)) 207 | } 208 | }() 209 | s.Close() 210 | return err 211 | } 212 | s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)") 213 | return nil 214 | } 215 | 216 | func (s *Box) preStart() error { 217 | for serviceName, service := range s.preServices { 218 | if preService, isPreService := service.(adapter.PreStarter); isPreService { 219 | s.logger.Trace("pre-start ", serviceName) 220 | err := preService.PreStart() 221 | if err != nil { 222 | return E.Cause(err, "pre-starting ", serviceName) 223 | } 224 | } 225 | } 226 | err := s.router.PreStart() 227 | if err != nil { 228 | return E.Cause(err, "pre-start router") 229 | } 230 | err = s.startOutbounds() 231 | if err != nil { 232 | return err 233 | } 234 | return s.router.Start() 235 | } 236 | 237 | func (s *Box) start() error { 238 | err := s.preStart() 239 | if err != nil { 240 | return err 241 | } 242 | for serviceName, service := range s.preServices { 243 | s.logger.Trace("starting ", serviceName) 244 | err = service.Start() 245 | if err != nil { 246 | return E.Cause(err, "start ", serviceName) 247 | } 248 | } 249 | for i, in := range s.inbounds { 250 | var tag string 251 | if in.Tag() == "" { 252 | tag = F.ToString(i) 253 | } else { 254 | tag = in.Tag() 255 | } 256 | s.logger.Trace("initializing inbound/", in.Type(), "[", tag, "]") 257 | err = in.Start() 258 | if err != nil { 259 | return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]") 260 | } 261 | } 262 | return nil 263 | } 264 | 265 | func (s *Box) postStart() error { 266 | for serviceName, service := range s.postServices { 267 | s.logger.Trace("starting ", service) 268 | err := service.Start() 269 | if err != nil { 270 | return E.Cause(err, "start ", serviceName) 271 | } 272 | } 273 | for serviceName, service := range s.outbounds { 274 | if lateService, isLateService := service.(adapter.PostStarter); isLateService { 275 | s.logger.Trace("post-starting ", service) 276 | err := lateService.PostStart() 277 | if err != nil { 278 | return E.Cause(err, "post-start ", serviceName) 279 | } 280 | } 281 | } 282 | return nil 283 | } 284 | 285 | func (s *Box) Close() error { 286 | select { 287 | case <-s.done: 288 | return os.ErrClosed 289 | default: 290 | close(s.done) 291 | } 292 | // Close() may timeout, close early to prevent listen port 293 | s.logger.Trace("closeClashApi:", s.closeClashApi()) 294 | s.logger.Trace("closeInboundListeners:", s.closeInboundListeners()) 295 | // 296 | var errors error 297 | for serviceName, service := range s.postServices { 298 | s.logger.Trace("closing ", serviceName) 299 | errors = E.Append(errors, service.Close(), func(err error) error { 300 | return E.Cause(err, "close ", serviceName) 301 | }) 302 | } 303 | for i, in := range s.inbounds { 304 | s.logger.Trace("closing inbound/", in.Type(), "[", i, "]") 305 | errors = E.Append(errors, in.Close(), func(err error) error { 306 | return E.Cause(err, "close inbound/", in.Type(), "[", i, "]") 307 | }) 308 | } 309 | for i, out := range s.outbounds { 310 | s.logger.Trace("closing outbound/", out.Type(), "[", i, "]") 311 | errors = E.Append(errors, common.Close(out), func(err error) error { 312 | return E.Cause(err, "close outbound/", out.Type(), "[", i, "]") 313 | }) 314 | } 315 | s.logger.Trace("closing router") 316 | if err := common.Close(s.router); err != nil { 317 | errors = E.Append(errors, err, func(err error) error { 318 | return E.Cause(err, "close router") 319 | }) 320 | } 321 | for serviceName, service := range s.preServices { 322 | s.logger.Trace("closing ", serviceName) 323 | errors = E.Append(errors, service.Close(), func(err error) error { 324 | return E.Cause(err, "close ", serviceName) 325 | }) 326 | } 327 | s.logger.Trace("closing log factory") 328 | if err := common.Close(s.logFactory); err != nil { 329 | errors = E.Append(errors, err, func(err error) error { 330 | return E.Cause(err, "close log factory") 331 | }) 332 | } 333 | return errors 334 | } 335 | 336 | func (s *Box) Router() adapter.Router { 337 | return s.router 338 | } 339 | -------------------------------------------------------------------------------- /boxbox/box_outbound.go: -------------------------------------------------------------------------------- 1 | package boxbox 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | "github.com/sagernet/sing/common" 8 | E "github.com/sagernet/sing/common/exceptions" 9 | F "github.com/sagernet/sing/common/format" 10 | ) 11 | 12 | func (s *Box) startOutbounds() error { 13 | outboundTags := make(map[adapter.Outbound]string) 14 | outbounds := make(map[string]adapter.Outbound) 15 | for i, outboundToStart := range s.outbounds { 16 | var outboundTag string 17 | if outboundToStart.Tag() == "" { 18 | outboundTag = F.ToString(i) 19 | } else { 20 | outboundTag = outboundToStart.Tag() 21 | } 22 | if _, exists := outbounds[outboundTag]; exists { 23 | return E.New("outbound tag ", outboundTag, " duplicated") 24 | } 25 | outboundTags[outboundToStart] = outboundTag 26 | outbounds[outboundTag] = outboundToStart 27 | } 28 | started := make(map[string]bool) 29 | for { 30 | canContinue := false 31 | startOne: 32 | for _, outboundToStart := range s.outbounds { 33 | outboundTag := outboundTags[outboundToStart] 34 | if started[outboundTag] { 35 | continue 36 | } 37 | dependencies := outboundToStart.Dependencies() 38 | for _, dependency := range dependencies { 39 | if !started[dependency] { 40 | continue startOne 41 | } 42 | } 43 | started[outboundTag] = true 44 | canContinue = true 45 | if starter, isStarter := outboundToStart.(common.Starter); isStarter { 46 | s.logger.Trace("initializing outbound/", outboundToStart.Type(), "[", outboundTag, "]") 47 | err := starter.Start() 48 | if err != nil { 49 | return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") 50 | } 51 | } 52 | } 53 | if len(started) == len(s.outbounds) { 54 | break 55 | } 56 | if canContinue { 57 | continue 58 | } 59 | currentOutbound := common.Find(s.outbounds, func(it adapter.Outbound) bool { 60 | return !started[outboundTags[it]] 61 | }) 62 | var lintOutbound func(oTree []string, oCurrent adapter.Outbound) error 63 | lintOutbound = func(oTree []string, oCurrent adapter.Outbound) error { 64 | problemOutboundTag := common.Find(oCurrent.Dependencies(), func(it string) bool { 65 | return !started[it] 66 | }) 67 | if common.Contains(oTree, problemOutboundTag) { 68 | return E.New("circular outbound dependency: ", strings.Join(oTree, " -> "), " -> ", problemOutboundTag) 69 | } 70 | problemOutbound := outbounds[problemOutboundTag] 71 | if problemOutbound == nil { 72 | return E.New("dependency[", problemOutbound, "] not found for outbound[", outboundTags[oCurrent], "]") 73 | } 74 | return lintOutbound(append(oTree, problemOutboundTag), problemOutbound) 75 | } 76 | return lintOutbound([]string{outboundTags[currentOutbound]}, currentOutbound) 77 | } 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /boxbox/debug.go: -------------------------------------------------------------------------------- 1 | //go:build go1.19 2 | 3 | package boxbox 4 | 5 | import ( 6 | "runtime/debug" 7 | 8 | "github.com/sagernet/sing-box/common/conntrack" 9 | "github.com/sagernet/sing-box/option" 10 | ) 11 | 12 | func applyDebugOptions(options option.DebugOptions) { 13 | if options.GCPercent != nil { 14 | debug.SetGCPercent(*options.GCPercent) 15 | } 16 | if options.MaxStack != nil { 17 | debug.SetMaxStack(*options.MaxStack) 18 | } 19 | if options.MaxThreads != nil { 20 | debug.SetMaxThreads(*options.MaxThreads) 21 | } 22 | if options.PanicOnFault != nil { 23 | debug.SetPanicOnFault(*options.PanicOnFault) 24 | } 25 | if options.TraceBack != "" { 26 | debug.SetTraceback(options.TraceBack) 27 | } 28 | if options.MemoryLimit != 0 { 29 | debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5)) 30 | conntrack.MemoryLimit = uint64(options.MemoryLimit) 31 | } 32 | if options.OOMKiller != nil { 33 | conntrack.KillerEnabled = *options.OOMKiller 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /boxbox/version.go: -------------------------------------------------------------------------------- 1 | package boxbox 2 | 3 | import "github.com/sagernet/sing-box/constant" 4 | 5 | func init() { 6 | constant.Version = Version + "-matsuridayo" 7 | } 8 | 9 | var Version = "1.8.14" 10 | -------------------------------------------------------------------------------- /boxbox/wtf.go: -------------------------------------------------------------------------------- 1 | package boxbox 2 | 3 | import ( 4 | "github.com/sagernet/sing-box/experimental/clashapi" 5 | E "github.com/sagernet/sing/common/exceptions" 6 | ) 7 | 8 | func (s *Box) closeClashApi() error { 9 | if c, ok := s.router.ClashServer().(*clashapi.Server); ok { 10 | return c.Close() 11 | } 12 | return nil 13 | } 14 | 15 | func (s *Box) closeInboundListeners() error { 16 | var errors error 17 | for i, in := range s.inbounds { 18 | inType := in.Type() 19 | if inType == "tun" { 20 | continue 21 | } 22 | s.logger.Trace("closeInboundListener inbound/", inType, "[", i, "]") 23 | errors = E.Append(errors, in.Close(), func(err error) error { 24 | return E.Cause(err, "closeInboundListener inbound/", inType, "[", i, "]") 25 | }) 26 | } 27 | return errors 28 | } 29 | -------------------------------------------------------------------------------- /boxdns/boxdns.go: -------------------------------------------------------------------------------- 1 | package boxdns 2 | -------------------------------------------------------------------------------- /boxdns/monitor_windows.go: -------------------------------------------------------------------------------- 1 | package boxdns 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net/netip" 7 | "strings" 8 | "time" 9 | 10 | "github.com/matsuridayo/libneko/iphlpapi" 11 | 12 | L "github.com/sagernet/sing-box/log" 13 | tun "github.com/sagernet/sing-tun" 14 | 15 | "github.com/gofrs/uuid/v5" 16 | "golang.org/x/sys/windows/registry" 17 | ) 18 | 19 | var monitorNU tun.NetworkUpdateMonitor 20 | var monitorDI tun.DefaultInterfaceMonitor 21 | 22 | func init() { 23 | defer func() { 24 | if err := recover(); err != nil { 25 | log.Println("[Warning] failed to start sing-tun monitor:", err) 26 | } 27 | }() 28 | 29 | logFactory, _ := L.New(L.Options{ 30 | Context: context.Background(), 31 | BaseTime: time.Now(), 32 | }) 33 | logger := logFactory.NewLogger("windows-dns") 34 | 35 | monitorNU, _ = tun.NewNetworkUpdateMonitor(logger) 36 | monitorDI, _ = tun.NewDefaultInterfaceMonitor(monitorNU, logger, tun.DefaultInterfaceMonitorOptions{}) 37 | monitorDI.RegisterCallback(monitorForUnderlyingDNS) 38 | monitorDI.Start() 39 | monitorNU.Start() 40 | } 41 | 42 | func monitorForUnderlyingDNS(event int) { 43 | index := monitorDI.DefaultInterfaceIndex(netip.IPv4Unspecified()) 44 | var guid iphlpapi.GUID 45 | if errno := iphlpapi.Index2GUID(uint64(index), &guid); errno != 0 { 46 | return 47 | } 48 | u, _ := uuid.FromBytes([]byte{ 49 | guid.Data1[3], guid.Data1[2], guid.Data1[1], guid.Data1[0], 50 | guid.Data2[1], guid.Data2[0], 51 | guid.Data3[1], guid.Data3[0], 52 | guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], 53 | guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7], 54 | }) 55 | guidStr := "{" + u.String() + "}" 56 | underlyingDNS = getFirstDNS(guidStr) 57 | log.Println("underlyingDNS:", guidStr, underlyingDNS) 58 | } 59 | 60 | func getFirstDNS(guid string) string { 61 | dns, err := getNameServersForInterface(guid) 62 | if err != nil || len(dns) == 0 { 63 | return "" 64 | } 65 | return dns[0] 66 | } 67 | 68 | func getNameServersForInterface(guid string) ([]string, error) { 69 | key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Interfaces\\`+guid, registry.QUERY_VALUE) 70 | if err != nil { 71 | log.Println("getNameServersForInterface OpenKey:", err) 72 | return nil, err 73 | } 74 | defer key.Close() 75 | 76 | nameservers := make([]string, 0, 4) 77 | for _, name := range []string{`NameServer`, `DhcpNameServer`} { 78 | s, _, err := key.GetStringValue(name) 79 | if err != nil { 80 | log.Println("getNameServersForInterface GetStringValue:", name, err) 81 | continue 82 | } 83 | s = strings.ReplaceAll(s, ",", " ") 84 | for _, server := range strings.Split(s, " ") { 85 | if server != "" { 86 | nameservers = append(nameservers, server) 87 | } 88 | } 89 | } 90 | 91 | return nameservers, nil 92 | } 93 | -------------------------------------------------------------------------------- /boxdns/underlying_dns.go: -------------------------------------------------------------------------------- 1 | //go:build !android 2 | 3 | package boxdns 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "net" 9 | "reflect" 10 | "runtime" 11 | "unsafe" 12 | 13 | D "github.com/sagernet/sing-dns" 14 | "github.com/sagernet/sing/common/logger" 15 | M "github.com/sagernet/sing/common/metadata" 16 | N "github.com/sagernet/sing/common/network" 17 | ) 18 | 19 | var underlyingDNS string 20 | 21 | func init() { 22 | D.RegisterTransport([]string{"underlying"}, createUnderlyingTransport) 23 | } 24 | 25 | func createUnderlyingTransport(name string, ctx context.Context, logger logger.ContextLogger, dialer N.Dialer, link string) (D.Transport, error) { 26 | if runtime.GOOS != "windows" { 27 | // Linux no resolv.conf change 28 | return D.CreateLocalTransport(name, ctx, logger, dialer, "local") 29 | } 30 | // Windows Underlying DNS hook 31 | t, _ := D.CreateUDPTransport(name, ctx, logger, dialer, link) 32 | udp := t.(*D.UDPTransport) 33 | handler_ := reflect.Indirect(reflect.ValueOf(udp)).FieldByName("handler") 34 | handler_ = reflect.NewAt(handler_.Type(), unsafe.Pointer(handler_.UnsafeAddr())).Elem() 35 | handler_.Set(reflect.ValueOf(&myTransportHandler{udp, dialer})) 36 | return t, nil 37 | } 38 | 39 | type myTransportHandler struct { 40 | *D.UDPTransport 41 | dialer N.Dialer 42 | } 43 | 44 | func (t *myTransportHandler) DialContext(ctx context.Context, queryCtx context.Context) (net.Conn, error) { 45 | if underlyingDNS == "" { 46 | return nil, errors.New("no underlyingDNS") 47 | } 48 | return t.dialer.DialContext(ctx, "udp", M.ParseSocksaddrHostPort(underlyingDNS, 53)) 49 | } 50 | -------------------------------------------------------------------------------- /boxmain/cmd_check.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/matsuridayo/sing-box-extra/boxbox" 7 | "github.com/sagernet/sing-box/log" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var commandCheck = &cobra.Command{ 13 | Use: "check", 14 | Short: "Check configuration", 15 | Run: func(cmd *cobra.Command, args []string) { 16 | err := check() 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | }, 21 | Args: cobra.NoArgs, 22 | } 23 | 24 | func init() { 25 | mainCommand.AddCommand(commandCheck) 26 | } 27 | 28 | func check() error { 29 | options, err := readConfigAndMerge() 30 | if err != nil { 31 | return err 32 | } 33 | ctx, cancel := context.WithCancel(context.Background()) 34 | instance, err := boxbox.New(boxbox.Options{ 35 | Context: ctx, 36 | Options: options, 37 | }) 38 | if err == nil { 39 | instance.Close() 40 | } 41 | cancel() 42 | return err 43 | } 44 | -------------------------------------------------------------------------------- /boxmain/cmd_format.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "bytes" 5 | "github.com/sagernet/sing/common/json" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/sagernet/sing-box/log" 10 | "github.com/sagernet/sing-box/option" 11 | E "github.com/sagernet/sing/common/exceptions" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var commandFormatFlagWrite bool 17 | 18 | var commandFormat = &cobra.Command{ 19 | Use: "format", 20 | Short: "Format configuration", 21 | Run: func(cmd *cobra.Command, args []string) { 22 | err := format() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | }, 27 | Args: cobra.NoArgs, 28 | } 29 | 30 | func init() { 31 | commandFormat.Flags().BoolVarP(&commandFormatFlagWrite, "write", "w", false, "write result to (source) file instead of stdout") 32 | mainCommand.AddCommand(commandFormat) 33 | } 34 | 35 | func format() error { 36 | optionsList, err := readConfig() 37 | if err != nil { 38 | return err 39 | } 40 | for _, optionsEntry := range optionsList { 41 | buffer := new(bytes.Buffer) 42 | encoder := json.NewEncoder(buffer) 43 | encoder.SetIndent("", " ") 44 | err = encoder.Encode(optionsEntry.options) 45 | if err != nil { 46 | return E.Cause(err, "encode config") 47 | } 48 | outputPath, _ := filepath.Abs(optionsEntry.path) 49 | if !commandFormatFlagWrite { 50 | if len(optionsList) > 1 { 51 | os.Stdout.WriteString(outputPath + "\n") 52 | } 53 | os.Stdout.WriteString(buffer.String() + "\n") 54 | continue 55 | } 56 | if bytes.Equal(optionsEntry.content, buffer.Bytes()) { 57 | continue 58 | } 59 | output, err := os.Create(optionsEntry.path) 60 | if err != nil { 61 | return E.Cause(err, "open output") 62 | } 63 | _, err = output.Write(buffer.Bytes()) 64 | output.Close() 65 | if err != nil { 66 | return E.Cause(err, "write output") 67 | } 68 | os.Stderr.WriteString(outputPath + "\n") 69 | } 70 | return nil 71 | } 72 | 73 | func formatOne(configPath string) error { 74 | configContent, err := os.ReadFile(configPath) 75 | if err != nil { 76 | return E.Cause(err, "read config") 77 | } 78 | var options option.Options 79 | err = options.UnmarshalJSON(configContent) 80 | if err != nil { 81 | return E.Cause(err, "decode config") 82 | } 83 | buffer := new(bytes.Buffer) 84 | encoder := json.NewEncoder(buffer) 85 | encoder.SetIndent("", " ") 86 | err = encoder.Encode(options) 87 | if err != nil { 88 | return E.Cause(err, "encode config") 89 | } 90 | if !commandFormatFlagWrite { 91 | os.Stdout.WriteString(buffer.String() + "\n") 92 | return nil 93 | } 94 | if bytes.Equal(configContent, buffer.Bytes()) { 95 | return nil 96 | } 97 | output, err := os.Create(configPath) 98 | if err != nil { 99 | return E.Cause(err, "open output") 100 | } 101 | _, err = output.Write(buffer.Bytes()) 102 | output.Close() 103 | if err != nil { 104 | return E.Cause(err, "write output") 105 | } 106 | outputPath, _ := filepath.Abs(configPath) 107 | os.Stderr.WriteString(outputPath + "\n") 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /boxmain/cmd_generate.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "encoding/hex" 7 | "os" 8 | "strconv" 9 | 10 | "github.com/sagernet/sing-box/log" 11 | 12 | "github.com/gofrs/uuid/v5" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var commandGenerate = &cobra.Command{ 17 | Use: "generate", 18 | Short: "Generate things", 19 | } 20 | 21 | func init() { 22 | commandGenerate.AddCommand(commandGenerateUUID) 23 | commandGenerate.AddCommand(commandGenerateRandom) 24 | mainCommand.AddCommand(commandGenerate) 25 | } 26 | 27 | var ( 28 | outputBase64 bool 29 | outputHex bool 30 | ) 31 | 32 | var commandGenerateRandom = &cobra.Command{ 33 | Use: "rand ", 34 | Short: "Generate random bytes", 35 | Args: cobra.ExactArgs(1), 36 | Run: func(cmd *cobra.Command, args []string) { 37 | err := generateRandom(args) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | }, 42 | } 43 | 44 | func init() { 45 | commandGenerateRandom.Flags().BoolVar(&outputBase64, "base64", false, "Generate base64 string") 46 | commandGenerateRandom.Flags().BoolVar(&outputHex, "hex", false, "Generate hex string") 47 | } 48 | 49 | func generateRandom(args []string) error { 50 | length, err := strconv.Atoi(args[0]) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | randomBytes := make([]byte, length) 56 | _, err = rand.Read(randomBytes) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | if outputBase64 { 62 | _, err = os.Stdout.WriteString(base64.StdEncoding.EncodeToString(randomBytes) + "\n") 63 | } else if outputHex { 64 | _, err = os.Stdout.WriteString(hex.EncodeToString(randomBytes) + "\n") 65 | } else { 66 | _, err = os.Stdout.Write(randomBytes) 67 | } 68 | 69 | return err 70 | } 71 | 72 | var commandGenerateUUID = &cobra.Command{ 73 | Use: "uuid", 74 | Short: "Generate UUID string", 75 | Args: cobra.NoArgs, 76 | Run: func(cmd *cobra.Command, args []string) { 77 | err := generateUUID() 78 | if err != nil { 79 | log.Fatal(err) 80 | } 81 | }, 82 | } 83 | 84 | func generateUUID() error { 85 | newUUID, err := uuid.NewV4() 86 | if err != nil { 87 | return err 88 | } 89 | _, err = os.Stdout.WriteString(newUUID.String() + "\n") 90 | return err 91 | } 92 | -------------------------------------------------------------------------------- /boxmain/cmd_generate_tls.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "os" 5 | "time" 6 | 7 | "github.com/sagernet/sing-box/common/tls" 8 | "github.com/sagernet/sing-box/log" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var flagGenerateTLSKeyPairMonths int 14 | 15 | var commandGenerateTLSKeyPair = &cobra.Command{ 16 | Use: "tls-keypair ", 17 | Short: "Generate TLS self sign key pair", 18 | Args: cobra.ExactArgs(1), 19 | Run: func(cmd *cobra.Command, args []string) { 20 | err := generateTLSKeyPair(args[0]) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | }, 25 | } 26 | 27 | func init() { 28 | commandGenerateTLSKeyPair.Flags().IntVarP(&flagGenerateTLSKeyPairMonths, "months", "m", 1, "Valid months") 29 | commandGenerate.AddCommand(commandGenerateTLSKeyPair) 30 | } 31 | 32 | func generateTLSKeyPair(serverName string) error { 33 | privateKeyPem, publicKeyPem, err := tls.GenerateKeyPair(time.Now, serverName, time.Now().AddDate(0, flagGenerateTLSKeyPairMonths, 0)) 34 | if err != nil { 35 | return err 36 | } 37 | os.Stdout.WriteString(string(privateKeyPem) + "\n") 38 | os.Stdout.WriteString(string(publicKeyPem) + "\n") 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /boxmain/cmd_generate_vapid.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "crypto/ecdh" 5 | "crypto/rand" 6 | "encoding/base64" 7 | "os" 8 | 9 | "github.com/sagernet/sing-box/log" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var commandGenerateVAPIDKeyPair = &cobra.Command{ 15 | Use: "vapid-keypair", 16 | Short: "Generate VAPID key pair", 17 | Run: func(cmd *cobra.Command, args []string) { 18 | err := generateVAPIDKeyPair() 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | }, 23 | } 24 | 25 | func init() { 26 | commandGenerate.AddCommand(commandGenerateVAPIDKeyPair) 27 | } 28 | 29 | func generateVAPIDKeyPair() error { 30 | privateKey, err := ecdh.P256().GenerateKey(rand.Reader) 31 | if err != nil { 32 | return err 33 | } 34 | publicKey := privateKey.PublicKey() 35 | os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey.Bytes()) + "\n") 36 | os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey.Bytes()) + "\n") 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /boxmain/cmd_generate_wireguard.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "encoding/base64" 5 | "os" 6 | 7 | "github.com/sagernet/sing-box/log" 8 | 9 | "github.com/spf13/cobra" 10 | "golang.zx2c4.com/wireguard/wgctrl/wgtypes" 11 | ) 12 | 13 | func init() { 14 | commandGenerate.AddCommand(commandGenerateWireGuardKeyPair) 15 | commandGenerate.AddCommand(commandGenerateRealityKeyPair) 16 | } 17 | 18 | var commandGenerateWireGuardKeyPair = &cobra.Command{ 19 | Use: "wg-keypair", 20 | Short: "Generate WireGuard key pair", 21 | Args: cobra.NoArgs, 22 | Run: func(cmd *cobra.Command, args []string) { 23 | err := generateWireGuardKey() 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | }, 28 | } 29 | 30 | func generateWireGuardKey() error { 31 | privateKey, err := wgtypes.GeneratePrivateKey() 32 | if err != nil { 33 | return err 34 | } 35 | os.Stdout.WriteString("PrivateKey: " + privateKey.String() + "\n") 36 | os.Stdout.WriteString("PublicKey: " + privateKey.PublicKey().String() + "\n") 37 | return nil 38 | } 39 | 40 | var commandGenerateRealityKeyPair = &cobra.Command{ 41 | Use: "reality-keypair", 42 | Short: "Generate reality key pair", 43 | Args: cobra.NoArgs, 44 | Run: func(cmd *cobra.Command, args []string) { 45 | err := generateRealityKey() 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | }, 50 | } 51 | 52 | func generateRealityKey() error { 53 | privateKey, err := wgtypes.GeneratePrivateKey() 54 | if err != nil { 55 | return err 56 | } 57 | publicKey := privateKey.PublicKey() 58 | os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]) + "\n") 59 | os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]) + "\n") 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /boxmain/cmd_run.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "context" 5 | "github.com/sagernet/sing/common/json" 6 | "github.com/sagernet/sing/common/json/badjson" 7 | "io" 8 | "os" 9 | "os/signal" 10 | "path/filepath" 11 | runtimeDebug "runtime/debug" 12 | "sort" 13 | "strings" 14 | "syscall" 15 | "time" 16 | 17 | "github.com/matsuridayo/libneko/protect_server" 18 | "github.com/matsuridayo/sing-box-extra/boxbox" 19 | "github.com/sagernet/sing-box/log" 20 | "github.com/sagernet/sing-box/option" 21 | E "github.com/sagernet/sing/common/exceptions" 22 | 23 | "github.com/spf13/cobra" 24 | ) 25 | 26 | var commandRun = &cobra.Command{ 27 | Use: "run", 28 | Short: "Run service", 29 | Run: func(cmd *cobra.Command, args []string) { 30 | if protectListenPath != "" { 31 | // for v2ray now 32 | go protect_server.ServeProtect(protectListenPath, true, protectFwMark, nil) 33 | } 34 | err := run() 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | }, 39 | } 40 | 41 | func init() { 42 | mainCommand.AddCommand(commandRun) 43 | } 44 | 45 | type OptionsEntry struct { 46 | content []byte 47 | path string 48 | options option.Options 49 | } 50 | 51 | func readConfigAt(path string) (*OptionsEntry, error) { 52 | var ( 53 | configContent []byte 54 | err error 55 | ) 56 | if path == "stdin" { 57 | configContent, err = io.ReadAll(os.Stdin) 58 | } else { 59 | configContent, err = os.ReadFile(path) 60 | } 61 | if err != nil { 62 | return nil, E.Cause(err, "read config at ", path) 63 | } 64 | var options option.Options 65 | err = options.UnmarshalJSON(configContent) 66 | if err != nil { 67 | return nil, E.Cause(err, "decode config at ", path) 68 | } 69 | return &OptionsEntry{ 70 | content: configContent, 71 | path: path, 72 | options: options, 73 | }, nil 74 | } 75 | 76 | func readConfig() ([]*OptionsEntry, error) { 77 | var optionsList []*OptionsEntry 78 | for _, path := range configPaths { 79 | optionsEntry, err := readConfigAt(path) 80 | if err != nil { 81 | return nil, err 82 | } 83 | optionsList = append(optionsList, optionsEntry) 84 | } 85 | for _, directory := range configDirectories { 86 | entries, err := os.ReadDir(directory) 87 | if err != nil { 88 | return nil, E.Cause(err, "read config directory at ", directory) 89 | } 90 | for _, entry := range entries { 91 | if !strings.HasSuffix(entry.Name(), ".json") || entry.IsDir() { 92 | continue 93 | } 94 | optionsEntry, err := readConfigAt(filepath.Join(directory, entry.Name())) 95 | if err != nil { 96 | return nil, err 97 | } 98 | optionsList = append(optionsList, optionsEntry) 99 | } 100 | } 101 | sort.Slice(optionsList, func(i, j int) bool { 102 | return optionsList[i].path < optionsList[j].path 103 | }) 104 | return optionsList, nil 105 | } 106 | 107 | func readConfigAndMerge() (option.Options, error) { 108 | optionsList, err := readConfig() 109 | if err != nil { 110 | return option.Options{}, err 111 | } 112 | if len(optionsList) == 1 { 113 | return optionsList[0].options, nil 114 | } 115 | 116 | var mergedMessage json.RawMessage 117 | for _, options := range optionsList { 118 | mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage) 119 | if err != nil { 120 | return option.Options{}, E.Cause(err, "merge config at ", options.path) 121 | } 122 | } 123 | 124 | var mergedOptions option.Options 125 | err = mergedOptions.UnmarshalJSON(mergedMessage) 126 | if err != nil { 127 | return option.Options{}, E.Cause(err, "unmarshal merged config") 128 | } 129 | 130 | return mergedOptions, nil 131 | } 132 | 133 | func Create(nekoConfigContent []byte) (*boxbox.Box, context.CancelFunc, error) { 134 | var options option.Options 135 | var err error 136 | // 137 | if nekoConfigContent == nil { 138 | options, err = readConfigAndMerge() 139 | } else { 140 | err = options.UnmarshalJSON(nekoConfigContent) 141 | } 142 | if err != nil { 143 | return nil, nil, err 144 | } 145 | // 146 | if disableColor { 147 | if options.Log == nil { 148 | options.Log = &option.LogOptions{} 149 | } 150 | options.Log.DisableColor = true 151 | } 152 | ctx, cancel := context.WithCancel(context.Background()) 153 | instance, err := boxbox.New(boxbox.Options{ 154 | Context: ctx, 155 | Options: options, 156 | }) 157 | if err != nil { 158 | cancel() 159 | return nil, nil, E.Cause(err, "create service") 160 | } 161 | 162 | osSignals := make(chan os.Signal, 1) 163 | signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) 164 | defer func() { 165 | signal.Stop(osSignals) 166 | close(osSignals) 167 | }() 168 | 169 | go func() { 170 | _, loaded := <-osSignals 171 | if loaded { 172 | cancel() 173 | } 174 | }() 175 | err = instance.Start() 176 | if err != nil { 177 | cancel() 178 | return nil, nil, E.Cause(err, "start service") 179 | } 180 | return instance, cancel, nil 181 | } 182 | 183 | func run() error { 184 | osSignals := make(chan os.Signal, 1) 185 | signal.Notify(osSignals, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP) 186 | defer signal.Stop(osSignals) 187 | for { 188 | instance, cancel, err := Create(nil) 189 | if err != nil { 190 | return err 191 | } 192 | runtimeDebug.FreeOSMemory() 193 | for { 194 | osSignal := <-osSignals 195 | if osSignal == syscall.SIGHUP { 196 | err = check() 197 | if err != nil { 198 | log.Error(E.Cause(err, "reload service")) 199 | continue 200 | } 201 | } 202 | cancel() 203 | closeCtx, closed := context.WithCancel(context.Background()) 204 | go closeMonitor(closeCtx) 205 | instance.Close() 206 | closed() 207 | if osSignal != syscall.SIGHUP { 208 | return nil 209 | } 210 | break 211 | } 212 | } 213 | } 214 | 215 | func closeMonitor(ctx context.Context) { 216 | time.Sleep(3 * time.Second) 217 | select { 218 | case <-ctx.Done(): 219 | return 220 | default: 221 | } 222 | log.Fatal("sing-box did not close!") 223 | } 224 | 225 | func MergeOptions(source option.Options, destination option.Options) (option.Options, error) { 226 | rawSource, err := json.Marshal(source) 227 | if err != nil { 228 | return option.Options{}, E.Cause(err, "marshal source") 229 | } 230 | rawDestination, err := json.Marshal(destination) 231 | if err != nil { 232 | return option.Options{}, E.Cause(err, "marshal destination") 233 | } 234 | rawMerged, err := badjson.MergeJSON(rawSource, rawDestination) 235 | if err != nil { 236 | return option.Options{}, E.Cause(err, "merge options") 237 | } 238 | var merged option.Options 239 | err = json.Unmarshal(rawMerged, &merged) 240 | if err != nil { 241 | return option.Options{}, E.Cause(err, "unmarshal merged options") 242 | } 243 | return merged, nil 244 | } 245 | -------------------------------------------------------------------------------- /boxmain/cmd_tools.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "github.com/matsuridayo/sing-box-extra/boxbox" 5 | E "github.com/sagernet/sing/common/exceptions" 6 | N "github.com/sagernet/sing/common/network" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var commandToolsFlagOutbound string 12 | 13 | var commandTools = &cobra.Command{ 14 | Use: "tools", 15 | Short: "Experimental tools", 16 | } 17 | 18 | func init() { 19 | commandTools.PersistentFlags().StringVarP(&commandToolsFlagOutbound, "outbound", "o", "", "Use specified tag instead of default outbound") 20 | mainCommand.AddCommand(commandTools) 21 | } 22 | 23 | func createPreStartedClient() (*boxbox.Box, error) { 24 | options, err := readConfigAndMerge() 25 | if err != nil { 26 | return nil, err 27 | } 28 | instance, err := boxbox.New(boxbox.Options{Options: options}) 29 | if err != nil { 30 | return nil, E.Cause(err, "create service") 31 | } 32 | err = instance.PreStart() 33 | if err != nil { 34 | return nil, E.Cause(err, "start service") 35 | } 36 | return instance, nil 37 | } 38 | 39 | func createDialer(instance *boxbox.Box, network string, outboundTag string) (N.Dialer, error) { 40 | if outboundTag == "" { 41 | outbound, _ := instance.Router().DefaultOutbound(N.NetworkName(network)) 42 | if outbound == nil { 43 | return nil, E.New("missing default outbound") 44 | } 45 | return outbound, nil 46 | } else { 47 | outbound, loaded := instance.Router().Outbound(outboundTag) 48 | if !loaded { 49 | return nil, E.New("outbound not found: ", outboundTag) 50 | } 51 | return outbound, nil 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /boxmain/cmd_tools_connect.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/sagernet/sing-box/log" 8 | "github.com/sagernet/sing/common" 9 | "github.com/sagernet/sing/common/bufio" 10 | E "github.com/sagernet/sing/common/exceptions" 11 | M "github.com/sagernet/sing/common/metadata" 12 | N "github.com/sagernet/sing/common/network" 13 | "github.com/sagernet/sing/common/task" 14 | 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var commandConnectFlagNetwork string 19 | 20 | var commandConnect = &cobra.Command{ 21 | Use: "connect [address]", 22 | Short: "Connect to an address", 23 | Args: cobra.ExactArgs(1), 24 | Run: func(cmd *cobra.Command, args []string) { 25 | err := connect(args[0]) 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | }, 30 | } 31 | 32 | func init() { 33 | commandConnect.Flags().StringVarP(&commandConnectFlagNetwork, "network", "n", "tcp", "network type") 34 | commandTools.AddCommand(commandConnect) 35 | } 36 | 37 | func connect(address string) error { 38 | switch N.NetworkName(commandConnectFlagNetwork) { 39 | case N.NetworkTCP, N.NetworkUDP: 40 | default: 41 | return E.Cause(N.ErrUnknownNetwork, commandConnectFlagNetwork) 42 | } 43 | instance, err := createPreStartedClient() 44 | if err != nil { 45 | return err 46 | } 47 | defer instance.Close() 48 | dialer, err := createDialer(instance, commandConnectFlagNetwork, commandToolsFlagOutbound) 49 | if err != nil { 50 | return err 51 | } 52 | conn, err := dialer.DialContext(context.Background(), commandConnectFlagNetwork, M.ParseSocksaddr(address)) 53 | if err != nil { 54 | return E.Cause(err, "connect to server") 55 | } 56 | var group task.Group 57 | group.Append("upload", func(ctx context.Context) error { 58 | return common.Error(bufio.Copy(conn, os.Stdin)) 59 | }) 60 | group.Append("download", func(ctx context.Context) error { 61 | return common.Error(bufio.Copy(os.Stdout, conn)) 62 | }) 63 | group.Cleanup(func() { 64 | conn.Close() 65 | }) 66 | err = group.Run(context.Background()) 67 | if E.IsClosed(err) { 68 | log.Info(err) 69 | } else { 70 | log.Error(err) 71 | } 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /boxmain/cmd_tools_fetch.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "io" 7 | "net" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | 12 | "github.com/sagernet/sing-box/log" 13 | "github.com/sagernet/sing/common/bufio" 14 | M "github.com/sagernet/sing/common/metadata" 15 | 16 | "github.com/spf13/cobra" 17 | ) 18 | 19 | var commandFetch = &cobra.Command{ 20 | Use: "fetch", 21 | Short: "Fetch an URL", 22 | Args: cobra.MinimumNArgs(1), 23 | Run: func(cmd *cobra.Command, args []string) { 24 | err := fetch(args) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | }, 29 | } 30 | 31 | func init() { 32 | commandTools.AddCommand(commandFetch) 33 | } 34 | 35 | var httpClient *http.Client 36 | 37 | func fetch(args []string) error { 38 | instance, err := createPreStartedClient() 39 | if err != nil { 40 | return err 41 | } 42 | defer instance.Close() 43 | httpClient = &http.Client{ 44 | Transport: &http.Transport{ 45 | DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 46 | dialer, err := createDialer(instance, network, commandToolsFlagOutbound) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr)) 51 | }, 52 | ForceAttemptHTTP2: true, 53 | }, 54 | } 55 | defer httpClient.CloseIdleConnections() 56 | for _, urlString := range args { 57 | parsedURL, err := url.Parse(urlString) 58 | if err != nil { 59 | return err 60 | } 61 | switch parsedURL.Scheme { 62 | case "": 63 | parsedURL.Scheme = "http" 64 | fallthrough 65 | case "http", "https": 66 | err = fetchHTTP(parsedURL) 67 | if err != nil { 68 | return err 69 | } 70 | } 71 | } 72 | return nil 73 | } 74 | 75 | func fetchHTTP(parsedURL *url.URL) error { 76 | request, err := http.NewRequest("GET", parsedURL.String(), nil) 77 | if err != nil { 78 | return err 79 | } 80 | request.Header.Add("User-Agent", "curl/7.88.0") 81 | response, err := httpClient.Do(request) 82 | if err != nil { 83 | return err 84 | } 85 | defer response.Body.Close() 86 | _, err = bufio.Copy(os.Stdout, response.Body) 87 | if errors.Is(err, io.EOF) { 88 | return nil 89 | } 90 | return err 91 | } 92 | -------------------------------------------------------------------------------- /boxmain/cmd_tools_synctime.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/sagernet/sing-box/common/settings" 8 | C "github.com/sagernet/sing-box/constant" 9 | "github.com/sagernet/sing-box/log" 10 | E "github.com/sagernet/sing/common/exceptions" 11 | M "github.com/sagernet/sing/common/metadata" 12 | N "github.com/sagernet/sing/common/network" 13 | "github.com/sagernet/sing/common/ntp" 14 | 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var ( 19 | commandSyncTimeFlagServer string 20 | commandSyncTimeOutputFormat string 21 | commandSyncTimeWrite bool 22 | ) 23 | 24 | var commandSyncTime = &cobra.Command{ 25 | Use: "synctime", 26 | Short: "Sync time using the NTP protocol", 27 | Args: cobra.NoArgs, 28 | Run: func(cmd *cobra.Command, args []string) { 29 | err := syncTime() 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | }, 34 | } 35 | 36 | func init() { 37 | commandSyncTime.Flags().StringVarP(&commandSyncTimeFlagServer, "server", "s", "time.apple.com", "Set NTP server") 38 | commandSyncTime.Flags().StringVarP(&commandSyncTimeOutputFormat, "format", "f", C.TimeLayout, "Set output format") 39 | commandSyncTime.Flags().BoolVarP(&commandSyncTimeWrite, "write", "w", false, "Write time to system") 40 | commandTools.AddCommand(commandSyncTime) 41 | } 42 | 43 | func syncTime() error { 44 | instance, err := createPreStartedClient() 45 | if err != nil { 46 | return err 47 | } 48 | dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound) 49 | if err != nil { 50 | return err 51 | } 52 | defer instance.Close() 53 | serverAddress := M.ParseSocksaddr(commandSyncTimeFlagServer) 54 | if serverAddress.Port == 0 { 55 | serverAddress.Port = 123 56 | } 57 | response, err := ntp.Exchange(context.Background(), dialer, serverAddress) 58 | if err != nil { 59 | return err 60 | } 61 | if commandSyncTimeWrite { 62 | err = settings.SetSystemTime(response.Time) 63 | if err != nil { 64 | return E.Cause(err, "write time to system") 65 | } 66 | } 67 | os.Stdout.WriteString(response.Time.Local().Format(commandSyncTimeOutputFormat)) 68 | return nil 69 | } 70 | -------------------------------------------------------------------------------- /boxmain/cmd_version.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "runtime/debug" 7 | 8 | C "github.com/sagernet/sing-box/constant" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var commandVersion = &cobra.Command{ 14 | Use: "version", 15 | Short: "Print current version of sing-box", 16 | Run: printVersion, 17 | Args: cobra.NoArgs, 18 | } 19 | 20 | var nameOnly bool 21 | 22 | func init() { 23 | commandVersion.Flags().BoolVarP(&nameOnly, "name", "n", false, "print version name only") 24 | mainCommand.AddCommand(commandVersion) 25 | } 26 | 27 | func printVersion(cmd *cobra.Command, args []string) { 28 | if nameOnly { 29 | os.Stdout.WriteString(C.Version + "\n") 30 | return 31 | } 32 | version := "sing-box version " + C.Version + "\n\n" 33 | version += "Environment: " + runtime.Version() + " " + runtime.GOOS + "/" + runtime.GOARCH + "\n" 34 | 35 | var tags string 36 | var revision string 37 | 38 | debugInfo, loaded := debug.ReadBuildInfo() 39 | if loaded { 40 | for _, setting := range debugInfo.Settings { 41 | switch setting.Key { 42 | case "-tags": 43 | tags = setting.Value 44 | case "vcs.revision": 45 | revision = setting.Value 46 | } 47 | } 48 | } 49 | 50 | if tags != "" { 51 | version += "Tags: " + tags + "\n" 52 | } 53 | if revision != "" { 54 | version += "Revision: " + revision + "\n" 55 | } 56 | 57 | if C.CGO_ENABLED { 58 | version += "CGO: enabled\n" 59 | } else { 60 | version += "CGO: disabled\n" 61 | } 62 | 63 | os.Stdout.WriteString(version) 64 | } 65 | -------------------------------------------------------------------------------- /boxmain/color.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "github.com/sagernet/sing-box/option" 5 | "time" 6 | 7 | "github.com/sagernet/sing-box/log" 8 | ) 9 | 10 | func DisableColor() { 11 | disableColor = true 12 | factory, _ := log.New(log.Options{Options: option.LogOptions{Output: "stderr", DisableColor: disableColor}, BaseTime: time.Now()}) 13 | log.SetStdLogger(factory.Logger()) 14 | } 15 | -------------------------------------------------------------------------------- /boxmain/debug.go: -------------------------------------------------------------------------------- 1 | //go:build debug 2 | 3 | package boxmain 4 | 5 | import ( 6 | "encoding/json" 7 | "github.com/sagernet/sing/common/json/badjson" 8 | "net/http" 9 | _ "net/http/pprof" 10 | "runtime" 11 | "runtime/debug" 12 | 13 | "github.com/sagernet/sing-box/log" 14 | 15 | "github.com/dustin/go-humanize" 16 | ) 17 | 18 | func init() { 19 | http.HandleFunc("/debug/gc", func(writer http.ResponseWriter, request *http.Request) { 20 | writer.WriteHeader(http.StatusNoContent) 21 | go debug.FreeOSMemory() 22 | }) 23 | http.HandleFunc("/debug/memory", func(writer http.ResponseWriter, request *http.Request) { 24 | var memStats runtime.MemStats 25 | runtime.ReadMemStats(&memStats) 26 | 27 | var memObject badjson.JSONObject 28 | memObject.Put("heap", humanize.IBytes(memStats.HeapInuse)) 29 | memObject.Put("stack", humanize.IBytes(memStats.StackInuse)) 30 | memObject.Put("idle", humanize.IBytes(memStats.HeapIdle-memStats.HeapReleased)) 31 | memObject.Put("goroutines", runtime.NumGoroutine()) 32 | memObject.Put("rss", rusageMaxRSS()) 33 | 34 | encoder := json.NewEncoder(writer) 35 | encoder.SetIndent("", " ") 36 | encoder.Encode(memObject) 37 | }) 38 | go func() { 39 | err := http.ListenAndServe("0.0.0.0:8964", nil) 40 | if err != nil { 41 | log.Debug(err) 42 | } 43 | }() 44 | } 45 | -------------------------------------------------------------------------------- /boxmain/debug_linux.go: -------------------------------------------------------------------------------- 1 | //go:build debug 2 | 3 | package boxmain 4 | 5 | import ( 6 | "runtime" 7 | "syscall" 8 | ) 9 | 10 | func rusageMaxRSS() float64 { 11 | ru := syscall.Rusage{} 12 | err := syscall.Getrusage(syscall.RUSAGE_SELF, &ru) 13 | if err != nil { 14 | return 0 15 | } 16 | 17 | rss := float64(ru.Maxrss) 18 | if runtime.GOOS == "darwin" || runtime.GOOS == "ios" { 19 | rss /= 1 << 20 // ru_maxrss is bytes on darwin 20 | } else { 21 | // ru_maxrss is kilobytes elsewhere (linux, openbsd, etc) 22 | rss /= 1 << 10 23 | } 24 | return rss 25 | } 26 | -------------------------------------------------------------------------------- /boxmain/debug_stub.go: -------------------------------------------------------------------------------- 1 | //go:build debug && !linux 2 | 3 | package boxmain 4 | 5 | func rusageMaxRSS() float64 { 6 | return -1 7 | } 8 | -------------------------------------------------------------------------------- /boxmain/main.go: -------------------------------------------------------------------------------- 1 | package boxmain 2 | 3 | import ( 4 | "github.com/sagernet/sing-box/option" 5 | "os" 6 | "time" 7 | 8 | _ "github.com/sagernet/sing-box/include" 9 | "github.com/sagernet/sing-box/log" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var ( 15 | configPaths []string 16 | configDirectories []string 17 | workingDir string 18 | disableColor bool 19 | // 20 | protectListenPath string 21 | protectFwMark int 22 | ) 23 | 24 | var mainCommand = &cobra.Command{ 25 | Use: "sing-box", 26 | PersistentPreRun: preRun, 27 | } 28 | 29 | func init() { 30 | mainCommand.PersistentFlags().StringArrayVarP(&configPaths, "config", "c", nil, "set configuration file path") 31 | mainCommand.PersistentFlags().StringArrayVarP(&configDirectories, "config-directory", "C", nil, "set configuration directory path") 32 | mainCommand.PersistentFlags().StringVarP(&workingDir, "directory", "D", "", "set working directory") 33 | mainCommand.PersistentFlags().BoolVarP(&disableColor, "disable-color", "", false, "disable color output") 34 | mainCommand.PersistentFlags().StringVarP(&protectListenPath, "protect-listen-path", "", "", "Linux Only") 35 | mainCommand.PersistentFlags().IntVarP(&protectFwMark, "protect-fwmark", "", 0, "Linux Only") 36 | } 37 | 38 | func Main() { 39 | if err := mainCommand.Execute(); err != nil { 40 | log.Fatal(err) 41 | } 42 | } 43 | 44 | func preRun(cmd *cobra.Command, args []string) { 45 | if disableColor { 46 | factory, _ := log.New(log.Options{Options: option.LogOptions{Output: "stderr", DisableColor: true}, BaseTime: time.Now()}) 47 | log.SetStdLogger(factory.Logger()) 48 | } 49 | if workingDir != "" { 50 | _, err := os.Stat(workingDir) 51 | if err != nil { 52 | os.MkdirAll(workingDir, 0o777) 53 | } 54 | if err := os.Chdir(workingDir); err != nil { 55 | log.Fatal(err) 56 | } 57 | } 58 | if len(configPaths) == 0 && len(configDirectories) == 0 { 59 | configPaths = append(configPaths, "config.json") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /distro/all/all.go: -------------------------------------------------------------------------------- 1 | package all 2 | 3 | import ( 4 | _ "github.com/matsuridayo/sing-box-extra/boxapi" 5 | _ "github.com/matsuridayo/sing-box-extra/boxdns" 6 | ) 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/matsuridayo/sing-box-extra 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/dustin/go-humanize v1.0.1 7 | github.com/gofrs/uuid/v5 v5.1.0 8 | github.com/matsuridayo/libneko v1.0.0 // replaced 9 | github.com/miekg/dns v1.1.59 // indirect 10 | github.com/sagernet/sing v0.3.8 11 | github.com/sagernet/sing-box v1.0.0 // replaced 12 | github.com/sagernet/sing-dns v0.1.14 13 | github.com/sagernet/sing-tun v0.2.7 14 | github.com/spf13/cobra v1.8.0 15 | golang.org/x/sys v0.19.0 16 | golang.org/x/tools v0.20.0 // indirect 17 | golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 18 | ) 19 | 20 | require ( 21 | berty.tech/go-libtor v1.0.385 // indirect 22 | github.com/ajg/form v1.5.1 // indirect 23 | github.com/andybalholm/brotli v1.0.6 // indirect 24 | github.com/caddyserver/certmagic v0.20.0 // indirect 25 | github.com/cloudflare/circl v1.3.7 // indirect 26 | github.com/cretz/bine v0.2.0 // indirect 27 | github.com/fsnotify/fsnotify v1.7.0 // indirect 28 | github.com/gaukas/godicttls v0.0.4 // indirect 29 | github.com/go-chi/chi/v5 v5.0.12 // indirect 30 | github.com/go-chi/cors v1.2.1 // indirect 31 | github.com/go-chi/render v1.0.3 // indirect 32 | github.com/go-ole/go-ole v1.3.0 // indirect 33 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 34 | github.com/gobwas/httphead v0.1.0 // indirect 35 | github.com/gobwas/pool v0.2.1 // indirect 36 | github.com/google/btree v1.1.2 // indirect 37 | github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect 38 | github.com/hashicorp/yamux v0.1.1 // indirect 39 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 40 | github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect 41 | github.com/josharian/native v1.1.0 // indirect 42 | github.com/klauspost/compress v1.17.4 // indirect 43 | github.com/klauspost/cpuid/v2 v2.2.5 // indirect 44 | github.com/libdns/alidns v1.0.3 // indirect 45 | github.com/libdns/cloudflare v0.1.1 // indirect 46 | github.com/libdns/libdns v0.2.2 // indirect 47 | github.com/logrusorgru/aurora v2.0.3+incompatible // indirect 48 | github.com/mholt/acmez v1.2.0 // indirect 49 | github.com/onsi/ginkgo/v2 v2.9.7 // indirect 50 | github.com/ooni/go-libtor v1.1.8 // indirect 51 | github.com/oschwald/maxminddb-golang v1.12.0 // indirect 52 | github.com/pierrec/lz4/v4 v4.1.14 // indirect 53 | github.com/quic-go/qpack v0.4.0 // indirect 54 | github.com/quic-go/qtls-go1-20 v0.4.1 // indirect 55 | github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect 56 | github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e // indirect 57 | github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 // indirect 58 | github.com/sagernet/quic-go v0.40.1 // indirect 59 | github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect 60 | github.com/sagernet/sing-mux v0.2.0 // indirect 61 | github.com/sagernet/sing-quic v1.0.0 // indirect 62 | github.com/sagernet/sing-shadowsocks v0.2.6 // indirect 63 | github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect 64 | github.com/sagernet/sing-shadowtls v0.1.4 // indirect 65 | github.com/sagernet/sing-vmess v0.1.8 // indirect 66 | github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect 67 | github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 // indirect 68 | github.com/sagernet/utls v1.5.4 // indirect 69 | github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect 70 | github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect 71 | github.com/spf13/pflag v1.0.5 // indirect 72 | github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect 73 | github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect 74 | github.com/zeebo/blake3 v0.2.3 // indirect 75 | go.uber.org/multierr v1.11.0 // indirect 76 | go.uber.org/zap v1.27.0 // indirect 77 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect 78 | golang.org/x/crypto v0.22.0 // indirect 79 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect 80 | golang.org/x/mod v0.17.0 // indirect 81 | golang.org/x/net v0.24.0 // indirect 82 | golang.org/x/sync v0.7.0 // indirect 83 | golang.org/x/text v0.14.0 // indirect 84 | golang.org/x/time v0.5.0 // indirect 85 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect 86 | google.golang.org/grpc v1.63.2 // indirect 87 | google.golang.org/protobuf v1.33.0 // indirect 88 | lukechampine.com/blake3 v1.2.1 // indirect 89 | ) 90 | 91 | replace github.com/matsuridayo/libneko => ../libneko 92 | 93 | replace github.com/sagernet/sing-box => ../sing-box 94 | 95 | replace github.com/sagernet/sing-quic => ../sing-quic 96 | 97 | // replace github.com/sagernet/sing-dns v1.0.0 => ../sing-dns 98 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= 2 | berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= 3 | github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= 4 | github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= 5 | github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= 6 | github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 7 | github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= 8 | github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= 9 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 10 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 11 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 12 | github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= 13 | github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= 14 | github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 19 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 20 | github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 21 | github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 22 | github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= 23 | github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= 24 | github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= 25 | github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= 26 | github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= 27 | github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= 28 | github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= 29 | github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= 30 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 31 | github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= 32 | github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= 33 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 34 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 35 | github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= 36 | github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= 37 | github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= 38 | github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 39 | github.com/gofrs/uuid/v5 v5.1.0 h1:S5rqVKIigghZTCBKPCw0Y+bXkn26K3TB5mvQq2Ix8dk= 40 | github.com/gofrs/uuid/v5 v5.1.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= 41 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 42 | github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= 43 | github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 44 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 45 | github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= 46 | github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 47 | github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= 48 | github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= 49 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 50 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 51 | github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= 52 | github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= 53 | github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 54 | github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= 55 | github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= 56 | github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= 57 | github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 58 | github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= 59 | github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= 60 | github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 61 | github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= 62 | github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= 63 | github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= 64 | github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= 65 | github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= 66 | github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= 67 | github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= 68 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 69 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 70 | github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= 71 | github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= 72 | github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= 73 | github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= 74 | github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= 75 | github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= 76 | github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= 77 | github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= 78 | github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= 79 | github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= 80 | github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= 81 | github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= 82 | github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 83 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 84 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 85 | github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= 86 | github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= 87 | github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= 88 | github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= 89 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 90 | github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= 91 | github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= 92 | github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e h1:DOkjByVeAR56dkszjnMZke4wr7yM/1xHaJF3G9olkEE= 93 | github.com/sagernet/gvisor v0.0.0-20231209105102-8d27a30e436e/go.mod h1:fLxq/gtp0qzkaEwywlRRiGmjOK5ES/xUzyIKIFP2Asw= 94 | github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97 h1:iL5gZI3uFp0X6EslacyapiRz7LLSJyr4RajF/BhMVyE= 95 | github.com/sagernet/netlink v0.0.0-20220905062125-8043b4a9aa97/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= 96 | github.com/sagernet/quic-go v0.40.1 h1:qLeTIJR0d0JWRmDWo346nLsVN6EWihd1kalJYPEd0TM= 97 | github.com/sagernet/quic-go v0.40.1/go.mod h1:CcKTpzTAISxrM4PA5M20/wYuz9Tj6Tx4DwGbNl9UQrU= 98 | github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= 99 | github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= 100 | github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= 101 | github.com/sagernet/sing v0.3.8 h1:gm4JKalPhydMYX2zFOTnnd4TXtM/16WFRqSjMepYQQk= 102 | github.com/sagernet/sing v0.3.8/go.mod h1:+60H3Cm91RnL9dpVGWDPHt0zTQImO9Vfqt9a4rSambI= 103 | github.com/sagernet/sing-dns v0.1.14 h1:kxE/Ik3jMXmD3sXsdt9MgrNzLFWt64mghV+MQqzyf40= 104 | github.com/sagernet/sing-dns v0.1.14/go.mod h1:AA+vZMNovuPN5i/sPnfF6756Nq94nzb5nXodMWbta5w= 105 | github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= 106 | github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= 107 | github.com/sagernet/sing-shadowsocks v0.2.6 h1:xr7ylAS/q1cQYS8oxKKajhuQcchd5VJJ4K4UZrrpp0s= 108 | github.com/sagernet/sing-shadowsocks v0.2.6/go.mod h1:j2YZBIpWIuElPFL/5sJAj470bcn/3QQ5lxZUNKLDNAM= 109 | github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= 110 | github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= 111 | github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= 112 | github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= 113 | github.com/sagernet/sing-tun v0.2.7 h1:6QtJkeSj6BTTQPGxbbiuV8eh7GdV46w2G0N8CmISwdc= 114 | github.com/sagernet/sing-tun v0.2.7/go.mod h1:MKAAHUzVfj7d9zos4lsz6wjXu86/mJyd/gejiAnWj/w= 115 | github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc= 116 | github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA= 117 | github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= 118 | github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= 119 | github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 h1:z3SJQhVyU63FT26Wn/UByW6b7q8QKB0ZkPqsyqcz2PI= 120 | github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6/go.mod h1:73xRZuxwkFk4aiLw28hG8W6o9cr2UPrGL9pdY2UTbvY= 121 | github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co= 122 | github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s= 123 | github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= 124 | github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= 125 | github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= 126 | github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= 127 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 128 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 129 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 130 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 131 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 132 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 133 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 134 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 135 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 136 | github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= 137 | github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= 138 | github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= 139 | github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 140 | github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= 141 | github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= 142 | github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= 143 | github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= 144 | github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= 145 | github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= 146 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 147 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 148 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 149 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 150 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 151 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= 152 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= 153 | golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= 154 | golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= 155 | golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= 156 | golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= 157 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= 158 | golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= 159 | golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= 160 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 161 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 162 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 163 | golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= 164 | golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= 165 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 166 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 167 | golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 168 | golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 169 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 170 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 171 | golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 172 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 173 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 174 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 175 | golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 176 | golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= 177 | golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 178 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 179 | golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= 180 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 181 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 182 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 183 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 184 | golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= 185 | golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 186 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 187 | golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= 188 | golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= 189 | golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= 190 | golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= 191 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= 192 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= 193 | google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= 194 | google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= 195 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 196 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 197 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 198 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 199 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 200 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 201 | lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI= 202 | lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= 203 | -------------------------------------------------------------------------------- /libs/get_source.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | [ ! -z $NO_ENV ] || source libs/get_source_env.sh 5 | pushd .. 6 | 7 | #### 8 | if [ ! -d "sing-box" ]; then 9 | git clone --no-checkout https://github.com/MatsuriDayo/sing-box.git 10 | fi 11 | pushd sing-box 12 | git fetch --all 13 | git checkout "$COMMIT_SING_BOX" 14 | popd 15 | 16 | #### 17 | if [ ! -d "sing-quic" ]; then 18 | git clone --no-checkout https://github.com/MatsuriDayo/sing-quic.git 19 | fi 20 | pushd sing-quic 21 | git fetch --all 22 | git checkout "$COMMIT_SING_QUIC" 23 | popd 24 | 25 | #### 26 | # if [ ! -d "sing-dns" ]; then 27 | # git clone --no-checkout https://github.com/MatsuriDayo/sing-dns.git 28 | # fi 29 | # pushd sing-dns 30 | # git checkout "$COMMIT_SING_DNS" 31 | # popd 32 | 33 | #### 34 | if [ ! -d "libneko" ]; then 35 | git clone --no-checkout https://github.com/MatsuriDayo/libneko.git 36 | fi 37 | pushd libneko 38 | git fetch --all 39 | git checkout "$COMMIT_LIBNEKO" 40 | popd 41 | 42 | popd 43 | -------------------------------------------------------------------------------- /libs/get_source_env.sh: -------------------------------------------------------------------------------- 1 | export COMMIT_SING_BOX="8923cfc1dd972ae9f63c5f77d1378d372ec30b5a" 2 | # export COMMIT_SING_DNS="63790a1843f8255ef4eae2e35e072c37ba8b72d1" 3 | export COMMIT_SING_QUIC="0bf1f198770e7c0948df48c0f12aab5ff32032df" 4 | export COMMIT_LIBNEKO="5277a5bfc889ee7a89462695b0e678c1bd4909b1" 5 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | _ "unsafe" 6 | 7 | "github.com/matsuridayo/sing-box-extra/boxbox" 8 | "github.com/matsuridayo/sing-box-extra/boxmain" 9 | _ "github.com/matsuridayo/sing-box-extra/distro/all" 10 | ) 11 | 12 | func main() { 13 | fmt.Println("sing-box-extra:", boxbox.Version) 14 | fmt.Println() 15 | 16 | // sing-box 17 | boxmain.Main() 18 | } 19 | --------------------------------------------------------------------------------