├── docs ├── CNAME ├── installation │ ├── tools │ │ ├── sing-box.repo │ │ ├── rpm-install.sh │ │ ├── deb-install.sh │ │ └── arch-install.sh │ ├── docker.zh.md │ └── docker.md ├── configuration │ ├── outbound │ │ ├── block.zh.md │ │ ├── block.md │ │ ├── dns.zh.md │ │ ├── dns.md │ │ ├── direct.zh.md │ │ ├── selector.zh.md │ │ ├── urltest.zh.md │ │ ├── direct.md │ │ ├── tor.zh.md │ │ ├── http.zh.md │ │ ├── selector.md │ │ ├── socks.zh.md │ │ ├── trojan.zh.md │ │ ├── http.md │ │ ├── tor.md │ │ ├── urltest.md │ │ └── ssh.zh.md │ ├── inbound │ │ ├── redirect.zh.md │ │ ├── redirect.md │ │ ├── tproxy.zh.md │ │ ├── socks.zh.md │ │ ├── tproxy.md │ │ ├── direct.zh.md │ │ ├── socks.md │ │ ├── naive.zh.md │ │ ├── direct.md │ │ ├── naive.md │ │ ├── mixed.zh.md │ │ ├── http.zh.md │ │ ├── mixed.md │ │ ├── vless.zh.md │ │ ├── http.md │ │ ├── vmess.zh.md │ │ └── vless.md │ ├── dns │ │ ├── fakeip.zh.md │ │ └── fakeip.md │ ├── shared │ │ ├── tcp-brutal.zh.md │ │ ├── dns01_challenge.zh.md │ │ ├── dns01_challenge.md │ │ └── tcp-brutal.md │ ├── log │ │ ├── index.zh.md │ │ └── index.md │ ├── rule-set │ │ └── source-format.md │ ├── experimental │ │ ├── index.zh.md │ │ ├── index.md │ │ ├── v2ray-api.zh.md │ │ ├── cache-file.zh.md │ │ └── v2ray-api.md │ ├── route │ │ ├── sniff.zh.md │ │ ├── geoip.zh.md │ │ ├── geosite.zh.md │ │ ├── sniff.md │ │ ├── geoip.md │ │ └── geosite.md │ ├── ntp │ │ ├── index.zh.md │ │ └── index.md │ ├── index.zh.md │ └── index.md ├── manual │ ├── proxy │ │ └── server.md │ └── misc │ │ └── tunnelvision.md ├── clients │ ├── privacy.md │ ├── index.zh.md │ ├── android │ │ └── index.md │ └── index.md ├── support.zh.md ├── support.md ├── index.zh.md └── index.md ├── .github ├── FUNDING.yml ├── update_dependencies.sh ├── update_clients.sh ├── workflows │ ├── stale.yml │ └── lint.yml └── renovate.json ├── option ├── v2ray.go ├── redir.go ├── ntp.go ├── naive.go ├── tor.go ├── tun_platform.go ├── shadowsocksr.go ├── direct.go ├── ssh.go ├── group.go ├── udp_over_tcp.go ├── multiplex.go ├── vless.go ├── trojan.go └── shadowtls.go ├── constant ├── speed.go ├── version.go ├── time.go ├── cgo.go ├── quic.go ├── cgo_disabled.go ├── quic_stub.go ├── dhcp.go ├── dns.go ├── err.go ├── v2ray.go ├── protocol.go ├── rule.go ├── path_unix.go ├── goos │ ├── zgoos_aix.go │ ├── zgoos_ios.go │ ├── zgoos_js.go │ ├── zgoos_zos.go │ ├── zgoos_hurd.go │ ├── zgoos_plan9.go │ ├── zgoos_netbsd.go │ ├── zgoos_android.go │ ├── zgoos_freebsd.go │ ├── zgoos_illumos.go │ ├── zgoos_openbsd.go │ ├── zgoos_windows.go │ ├── zgoos_darwin.go │ ├── zgoos_dragonfly.go │ ├── zgoos_linux.go │ ├── zgoos_solaris.go │ └── goos.go ├── timeout.go ├── os.go └── path.go ├── common ├── tls │ ├── acme_contstant.go │ ├── common.go │ ├── acme_stub.go │ ├── reality_stub.go │ ├── utls_stub.go │ ├── ech_stub.go │ └── config.go ├── conntrack │ ├── track_disable.go │ ├── track_enable.go │ ├── killer.go │ ├── track.go │ └── conn.go ├── settings │ ├── system_proxy.go │ ├── time_stub.go │ ├── time_unix.go │ ├── proxy_stub.go │ ├── time_windows.go │ └── proxy_windows.go ├── dialer │ ├── wiregurad_stub.go │ ├── wireguard.go │ ├── default_nongo1.21.go │ ├── default_go1.21.go │ ├── wireguard_control.go │ ├── default_go1.20.go │ ├── default_nongo1.20.go │ ├── tfo_stub.go │ ├── router.go │ └── dialer.go ├── process │ ├── searcher_stub.go │ ├── searcher_linux.go │ └── searcher_android.go ├── badtls │ ├── read_wait_stub.go │ ├── read_wait_utls.go │ └── read_wait_ech.go ├── redir │ ├── redir_other.go │ └── tproxy_other.go ├── badversion │ ├── version_json.go │ └── version_test.go ├── interrupt │ └── context.go ├── sniff │ ├── http.go │ ├── stun.go │ ├── tls.go │ ├── stun_test.go │ ├── dtls.go │ └── http_test.go ├── taskmonitor │ └── monitor.go ├── geoip │ └── reader.go └── pipelistener │ └── listener.go ├── adapter ├── service.go ├── time.go ├── prestart.go ├── outbound.go ├── v2ray.go ├── fakeip.go └── handler.go ├── debug_stub.go ├── include ├── dhcp.go ├── clashapi.go ├── v2rayapi.go ├── quic.go ├── dhcp_stub.go ├── v2rayapi_stub.go ├── clashapi_stub.go ├── tz_android.go ├── tz_ios.go └── quic_stub.go ├── transport ├── simple-obfs │ └── README.md ├── v2rayquic │ ├── init.go │ └── stream.go ├── v2raygrpc │ └── stream.proto ├── wireguard │ ├── device.go │ ├── device_stack_stub.go │ └── endpoint.go ├── v2rayhttp │ └── pool.go ├── v2raywebsocket │ └── deadline.go └── v2ray │ └── grpc_lite.go ├── log ├── platform.go ├── override.go ├── factory.go └── id.go ├── experimental ├── libbox │ ├── service_windows.go │ ├── service_other.go │ ├── tun_name_other.go │ ├── tun_name_darwin.go │ ├── command.go │ ├── tun_name_linux.go │ ├── memory.go │ ├── pprof.go │ ├── command_conntrack.go │ ├── service_error.go │ ├── service_pause.go │ ├── tun_darwin.go │ ├── log.go │ ├── command_shared.go │ ├── platform │ │ └── interface.go │ └── remote_profile.go ├── clashapi │ ├── ctxkeys.go │ ├── common.go │ ├── errors.go │ ├── cache.go │ └── rules.go └── v2rayapi.go ├── release ├── local │ ├── enable.sh │ ├── update.sh │ ├── uninstall.sh │ ├── install_go.sh │ ├── reinstall.sh │ ├── debug.sh │ ├── install.sh │ └── sing-box.service └── config │ ├── sing-box.service │ ├── sing-box@.service │ └── config.json ├── test ├── config │ ├── naive.json │ ├── naive-quic.json │ ├── wireguard.conf │ ├── hysteria-server.json │ ├── hysteria2-client.yml │ ├── hysteria2-server.yml │ ├── tuic-server.json │ ├── hysteria-client.json │ ├── tuic-client.json │ ├── shadowsocksr.json │ ├── vmess-server.json │ ├── vless-server.json │ ├── naive-nginx.conf │ ├── nginx.conf │ ├── vmess-client.json │ ├── vmess-mux-client.json │ ├── vless-tls-server.json │ ├── vmess-grpc-server.json │ └── vmess-ws-server.json ├── clash_other_test.go ├── v2ray_httpupgrade_test.go └── wrapper_test.go ├── outbound ├── tor_external.go ├── tor_embed.go ├── tor_embed_mobile.go ├── lookback.go ├── tuic_stub.go ├── shadowsocksr.go ├── shadowsocksr_stub.go ├── wireguard_stub.go └── hysteria_stub.go ├── inbound ├── default_tcp_nongo1.21.go ├── default_tcp_go1.21.go ├── naive_quic_stub.go ├── default_tcp_nongo1.20.go ├── default_tcp_go1.20.go ├── tuic_stub.go ├── hysteria_stub.go └── naive_quic.go ├── .gitignore ├── .gitmodules ├── cmd ├── sing-box │ ├── cmd_rule_set.go │ ├── cmd_tools_fetch_http3_stub.go │ ├── cmd_geoip_list.go │ ├── cmd_check.go │ ├── cmd_generate_vapid.go │ ├── cmd_geosite.go │ ├── cmd_generate_ech.go │ ├── cmd_geoip.go │ └── cmd_tools_fetch_http3.go └── internal │ ├── read_tag │ └── main.go │ └── build │ └── main.go ├── .golangci.yml ├── debug_unix.go ├── route ├── rule_item_clash_mode.go ├── rule_item_ipversion.go ├── rule_item_inbound.go ├── rule_item_auth_user.go ├── rule_item_protocol.go ├── rule_item_user.go ├── rule_item_wifi_ssid.go ├── rule_item_wifi_bssid.go ├── rule_item_network.go ├── rule_item_ip_is_private.go ├── rule_item_outbound.go └── rule_item_user_id.go ├── LICENSE ├── debug_go118.go └── debug_go119.go /docs/CNAME: -------------------------------------------------------------------------------- 1 | sing-box.sagernet.org -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: nekohasekai -------------------------------------------------------------------------------- /option/v2ray.go: -------------------------------------------------------------------------------- 1 | package option 2 | -------------------------------------------------------------------------------- /constant/speed.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const MbpsToBps = 125000 4 | -------------------------------------------------------------------------------- /constant/version.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | var Version = "unknown" 4 | -------------------------------------------------------------------------------- /common/tls/acme_contstant.go: -------------------------------------------------------------------------------- 1 | package tls 2 | 3 | const ACMETLS1Protocol = "acme-tls/1" 4 | -------------------------------------------------------------------------------- /constant/time.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const TimeLayout = "2006-01-02 15:04:05 -0700" 4 | -------------------------------------------------------------------------------- /constant/cgo.go: -------------------------------------------------------------------------------- 1 | //go:build cgo 2 | 3 | package constant 4 | 5 | const CGO_ENABLED = true 6 | -------------------------------------------------------------------------------- /constant/quic.go: -------------------------------------------------------------------------------- 1 | //go:build with_quic 2 | 3 | package constant 4 | 5 | const WithQUIC = true 6 | -------------------------------------------------------------------------------- /constant/cgo_disabled.go: -------------------------------------------------------------------------------- 1 | //go:build !cgo 2 | 3 | package constant 4 | 5 | const CGO_ENABLED = false 6 | -------------------------------------------------------------------------------- /constant/quic_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_quic 2 | 3 | package constant 4 | 5 | const WithQUIC = false 6 | -------------------------------------------------------------------------------- /adapter/service.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | type Service interface { 4 | Start() error 5 | Close() error 6 | } 7 | -------------------------------------------------------------------------------- /common/conntrack/track_disable.go: -------------------------------------------------------------------------------- 1 | //go:build !with_conntrack 2 | 3 | package conntrack 4 | 5 | const Enabled = false 6 | -------------------------------------------------------------------------------- /common/conntrack/track_enable.go: -------------------------------------------------------------------------------- 1 | //go:build with_conntrack 2 | 3 | package conntrack 4 | 5 | const Enabled = true 6 | -------------------------------------------------------------------------------- /debug_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !(linux || darwin) 2 | 3 | package box 4 | 5 | func rusageMaxRSS() float64 { 6 | return -1 7 | } 8 | -------------------------------------------------------------------------------- /include/dhcp.go: -------------------------------------------------------------------------------- 1 | //go:build with_dhcp 2 | 3 | package include 4 | 5 | import _ "github.com/sagernet/sing-box/transport/dhcp" 6 | -------------------------------------------------------------------------------- /constant/dhcp.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import "time" 4 | 5 | const ( 6 | DHCPTTL = time.Hour 7 | DHCPTimeout = time.Minute 8 | ) 9 | -------------------------------------------------------------------------------- /constant/dns.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | DNSProviderAliDNS = "alidns" 5 | DNSProviderCloudflare = "cloudflare" 6 | ) 7 | -------------------------------------------------------------------------------- /transport/simple-obfs/README.md: -------------------------------------------------------------------------------- 1 | # simple-obfs 2 | 3 | mod from https://github.com/Dreamacro/clash/transport/simple-obfs 4 | version: 1.11.8 -------------------------------------------------------------------------------- /adapter/time.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import "time" 4 | 5 | type TimeService interface { 6 | Service 7 | TimeFunc() func() time.Time 8 | } 9 | -------------------------------------------------------------------------------- /include/clashapi.go: -------------------------------------------------------------------------------- 1 | //go:build with_clash_api 2 | 3 | package include 4 | 5 | import _ "github.com/sagernet/sing-box/experimental/clashapi" 6 | -------------------------------------------------------------------------------- /include/v2rayapi.go: -------------------------------------------------------------------------------- 1 | //go:build with_v2ray_api 2 | 3 | package include 4 | 5 | import _ "github.com/sagernet/sing-box/experimental/v2rayapi" 6 | -------------------------------------------------------------------------------- /log/platform.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type PlatformWriter interface { 4 | DisableColors() bool 5 | WriteMessage(level Level, message string) 6 | } 7 | -------------------------------------------------------------------------------- /common/settings/system_proxy.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | type SystemProxy interface { 4 | IsEnabled() bool 5 | Enable() error 6 | Disable() error 7 | } 8 | -------------------------------------------------------------------------------- /experimental/libbox/service_windows.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | import "os" 4 | 5 | func dup(fd int) (nfd int, err error) { 6 | return 0, os.ErrInvalid 7 | } 8 | -------------------------------------------------------------------------------- /adapter/prestart.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | type PreStarter interface { 4 | PreStart() error 5 | } 6 | 7 | type PostStarter interface { 8 | PostStart() error 9 | } 10 | -------------------------------------------------------------------------------- /.github/update_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROJECTS=$(dirname "$0")/../.. 4 | go get -x github.com/sagernet/$1@$(git -C $PROJECTS/$1 rev-parse HEAD) 5 | go mod tidy 6 | -------------------------------------------------------------------------------- /include/quic.go: -------------------------------------------------------------------------------- 1 | //go:build with_quic 2 | 3 | package include 4 | 5 | import ( 6 | _ "github.com/sagernet/sing-box/transport/v2rayquic" 7 | _ "github.com/sagernet/sing-dns/quic" 8 | ) 9 | -------------------------------------------------------------------------------- /docs/installation/tools/sing-box.repo: -------------------------------------------------------------------------------- 1 | [sing-box] 2 | name=sing-box 3 | baseurl=https://rpm.sagernet.org/ 4 | enabled=1 5 | repo_gpgcheck=1 6 | gpgcheck=1 7 | gpgkey=https://sing-box.app/gpg.key 8 | -------------------------------------------------------------------------------- /release/local/enable.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | 5 | sudo systemctl enable sing-box 6 | sudo systemctl start sing-box 7 | sudo journalctl -u sing-box --output cat -f 8 | -------------------------------------------------------------------------------- /docs/configuration/outbound/block.zh.md: -------------------------------------------------------------------------------- 1 | `block` 出站关闭所有传入请求。 2 | 3 | ### 结构 4 | 5 | ```json 6 | { 7 | "type": "block", 8 | "tag": "block" 9 | } 10 | ``` 11 | 12 | ### 字段 13 | 14 | 无字段。 -------------------------------------------------------------------------------- /experimental/libbox/service_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package libbox 4 | 5 | import "syscall" 6 | 7 | func dup(fd int) (nfd int, err error) { 8 | return syscall.Dup(fd) 9 | } 10 | -------------------------------------------------------------------------------- /common/dialer/wiregurad_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_wireguard 2 | 3 | package dialer 4 | 5 | import ( 6 | "github.com/sagernet/sing/common/control" 7 | ) 8 | 9 | var wgControlFns []control.Func 10 | -------------------------------------------------------------------------------- /test/config/naive.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen": "socks://127.0.0.1:10001", 3 | "proxy": "https://sekai:password@example.org:10000", 4 | "host-resolver-rules": "MAP example.org 127.0.0.1", 5 | "log": "" 6 | } -------------------------------------------------------------------------------- /common/dialer/wireguard.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type WireGuardListener interface { 8 | ListenPacketCompat(network, address string) (net.PacketConn, error) 9 | } 10 | -------------------------------------------------------------------------------- /test/config/naive-quic.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen": "socks://127.0.0.1:10001", 3 | "proxy": "quic://sekai:password@example.org:10000", 4 | "host-resolver-rules": "MAP example.org 127.0.0.1", 5 | "log": "" 6 | } -------------------------------------------------------------------------------- /experimental/libbox/tun_name_other.go: -------------------------------------------------------------------------------- 1 | //go:build !(darwin || linux) 2 | 3 | package libbox 4 | 5 | import "os" 6 | 7 | func getTunnelName(fd int32) (string, error) { 8 | return "", os.ErrInvalid 9 | } 10 | -------------------------------------------------------------------------------- /outbound/tor_external.go: -------------------------------------------------------------------------------- 1 | //go:build !with_embedded_tor 2 | 3 | package outbound 4 | 5 | import "github.com/cretz/bine/tor" 6 | 7 | func newConfig() tor.StartConf { 8 | return tor.StartConf{} 9 | } 10 | -------------------------------------------------------------------------------- /inbound/default_tcp_nongo1.21.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package inbound 4 | 5 | import "net" 6 | 7 | const go121Available = false 8 | 9 | func setMultiPathTCP(listenConfig *net.ListenConfig) { 10 | } 11 | -------------------------------------------------------------------------------- /common/dialer/default_nongo1.21.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 2 | 3 | package dialer 4 | 5 | import ( 6 | "net" 7 | ) 8 | 9 | const go121Available = false 10 | 11 | func setMultiPathTCP(dialer *net.Dialer) { 12 | } 13 | -------------------------------------------------------------------------------- /option/redir.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type RedirectInboundOptions struct { 4 | ListenOptions 5 | } 6 | 7 | type TProxyInboundOptions struct { 8 | ListenOptions 9 | Network NetworkList `json:"network,omitempty"` 10 | } 11 | -------------------------------------------------------------------------------- /transport/v2rayquic/init.go: -------------------------------------------------------------------------------- 1 | //go:build with_quic 2 | 3 | package v2rayquic 4 | 5 | import "github.com/sagernet/sing-box/transport/v2ray" 6 | 7 | func init() { 8 | v2ray.RegisterQUICConstructor(NewServer, NewClient) 9 | } 10 | -------------------------------------------------------------------------------- /common/dialer/default_go1.21.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package dialer 4 | 5 | import "net" 6 | 7 | const go121Available = true 8 | 9 | func setMultiPathTCP(dialer *net.Dialer) { 10 | dialer.SetMultipathTCP(true) 11 | } 12 | -------------------------------------------------------------------------------- /common/process/searcher_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !windows && !darwin 2 | 3 | package process 4 | 5 | import ( 6 | "os" 7 | ) 8 | 9 | func NewSearcher(_ Config) (Searcher, error) { 10 | return nil, os.ErrInvalid 11 | } 12 | -------------------------------------------------------------------------------- /common/settings/time_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !(windows || linux || darwin) 2 | 3 | package settings 4 | 5 | import ( 6 | "os" 7 | "time" 8 | ) 9 | 10 | func SetSystemTime(nowTime time.Time) error { 11 | return os.ErrInvalid 12 | } 13 | -------------------------------------------------------------------------------- /docs/configuration/outbound/block.md: -------------------------------------------------------------------------------- 1 | `block` outbound closes all incoming requests. 2 | 3 | ### Structure 4 | 5 | ```json 6 | { 7 | "type": "block", 8 | "tag": "block" 9 | } 10 | ``` 11 | 12 | ### Fields 13 | 14 | No fields. 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /vendor/ 3 | /*.json 4 | /*.srs 5 | /*.db 6 | /site/ 7 | /bin/ 8 | /dist/ 9 | /sing-box 10 | /sing-box.exe 11 | /build/ 12 | /*.jar 13 | /*.aar 14 | /*.xcframework/ 15 | .DS_Store 16 | /config.d/ 17 | /venv/ 18 | 19 | -------------------------------------------------------------------------------- /test/clash_other_test.go: -------------------------------------------------------------------------------- 1 | //go:build !darwin 2 | 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "net/netip" 8 | ) 9 | 10 | func defaultRouteIP() (netip.Addr, error) { 11 | return netip.Addr{}, errors.New("not supported") 12 | } 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "clients/apple"] 2 | path = clients/apple 3 | url = https://github.com/SagerNet/sing-box-for-apple.git 4 | [submodule "clients/android"] 5 | path = clients/android 6 | url = https://github.com/SagerNet/sing-box-for-android.git 7 | -------------------------------------------------------------------------------- /inbound/default_tcp_go1.21.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 2 | 3 | package inbound 4 | 5 | import "net" 6 | 7 | const go121Available = true 8 | 9 | func setMultiPathTCP(listenConfig *net.ListenConfig) { 10 | listenConfig.SetMultipathTCP(true) 11 | } 12 | -------------------------------------------------------------------------------- /inbound/naive_quic_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_quic 2 | 3 | package inbound 4 | 5 | import ( 6 | C "github.com/sagernet/sing-box/constant" 7 | ) 8 | 9 | func (n *Naive) configureHTTP3Listener() error { 10 | return C.ErrQUICNotIncluded 11 | } 12 | -------------------------------------------------------------------------------- /test/config/wireguard.conf: -------------------------------------------------------------------------------- 1 | [Interface] 2 | PrivateKey = gHWUGzTh5YCEV6k8dneVP537XhVtoQJPIlFNs2zsxlE= 3 | Address = 10.0.0.1/32 4 | ListenPort = 10000 5 | 6 | [Peer] 7 | PublicKey = LV2xr9tzxwbs0ZLUlFN9k/0Or9QWqIInvxc/Cu7/2hA= 8 | AllowedIPs = 10.0.0.2/32 -------------------------------------------------------------------------------- /constant/err.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import E "github.com/sagernet/sing/common/exceptions" 4 | 5 | var ErrTLSRequired = E.New("TLS required") 6 | 7 | var ErrQUICNotIncluded = E.New(`QUIC is not included in this build, rebuild with -tags with_quic`) 8 | -------------------------------------------------------------------------------- /docs/configuration/outbound/dns.zh.md: -------------------------------------------------------------------------------- 1 | `dns` 出站是一个内部 DNS 服务器。 2 | 3 | ### 结构 4 | 5 | ```json 6 | { 7 | "type": "dns", 8 | "tag": "dns-out" 9 | } 10 | ``` 11 | 12 | !!! note "" 13 | 14 | DNS 出站没有出站连接,所有请求均在内部处理。 15 | 16 | ### 字段 17 | 18 | 无字段。 -------------------------------------------------------------------------------- /release/local/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | 5 | DIR=$(dirname "$0") 6 | PROJECT=$DIR/../.. 7 | 8 | pushd $PROJECT 9 | git fetch 10 | git reset FETCH_HEAD --hard 11 | git clean -fdx 12 | popd 13 | 14 | $DIR/reinstall.sh -------------------------------------------------------------------------------- /test/config/hysteria-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "listen": ":10000", 3 | "cert": "/etc/hysteria/cert.pem", 4 | "key": "/etc/hysteria/key.pem", 5 | "auth_str": "password", 6 | "obfs": "fuck me till the daylight", 7 | "up_mbps": 100, 8 | "down_mbps": 100 9 | } -------------------------------------------------------------------------------- /common/dialer/wireguard_control.go: -------------------------------------------------------------------------------- 1 | //go:build with_wireguard 2 | 3 | package dialer 4 | 5 | import ( 6 | "github.com/sagernet/wireguard-go/conn" 7 | ) 8 | 9 | var _ WireGuardListener = (conn.Listener)(nil) 10 | 11 | var wgControlFns = conn.ControlFns 12 | -------------------------------------------------------------------------------- /test/config/hysteria2-client.yml: -------------------------------------------------------------------------------- 1 | server: 127.0.0.1:10000 2 | auth: password 3 | socks5: 4 | listen: 127.0.0.1:10001 5 | tls: 6 | sni: example.org 7 | ca: /etc/hysteria/ca.pem 8 | obfs: 9 | type: salamander 10 | salamander: 11 | password: cry_me_a_r1ver -------------------------------------------------------------------------------- /release/local/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sudo systemctl stop sing-box 4 | sudo rm -rf /var/lib/sing-box 5 | sudo rm -rf /usr/local/bin/sing-box 6 | sudo rm -rf /usr/local/etc/sing-box 7 | sudo rm -rf /etc/systemd/system/sing-box.service 8 | sudo systemctl daemon-reload 9 | -------------------------------------------------------------------------------- /cmd/sing-box/cmd_rule_set.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var commandRuleSet = &cobra.Command{ 8 | Use: "rule-set", 9 | Short: "Manage rule-sets", 10 | } 11 | 12 | func init() { 13 | mainCommand.AddCommand(commandRuleSet) 14 | } 15 | -------------------------------------------------------------------------------- /common/badtls/read_wait_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.21 || without_badtls 2 | 3 | package badtls 4 | 5 | import ( 6 | "os" 7 | 8 | "github.com/sagernet/sing/common/tls" 9 | ) 10 | 11 | func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) { 12 | return nil, os.ErrInvalid 13 | } 14 | -------------------------------------------------------------------------------- /constant/v2ray.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | V2RayTransportTypeHTTP = "http" 5 | V2RayTransportTypeWebsocket = "ws" 6 | V2RayTransportTypeQUIC = "quic" 7 | V2RayTransportTypeGRPC = "grpc" 8 | V2RayTransportTypeHTTPUpgrade = "httpupgrade" 9 | ) 10 | -------------------------------------------------------------------------------- /option/ntp.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type NTPOptions struct { 4 | Enabled bool `json:"enabled,omitempty"` 5 | Interval Duration `json:"interval,omitempty"` 6 | WriteToSystem bool `json:"write_to_system,omitempty"` 7 | ServerOptions 8 | DialerOptions 9 | } 10 | -------------------------------------------------------------------------------- /common/redir/redir_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux && !darwin 2 | 3 | package redir 4 | 5 | import ( 6 | "net" 7 | "net/netip" 8 | "os" 9 | ) 10 | 11 | func GetOriginalDestination(conn net.Conn) (destination netip.AddrPort, err error) { 12 | return netip.AddrPort{}, os.ErrInvalid 13 | } 14 | -------------------------------------------------------------------------------- /option/naive.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import "github.com/sagernet/sing/common/auth" 4 | 5 | type NaiveInboundOptions struct { 6 | ListenOptions 7 | Users []auth.User `json:"users,omitempty"` 8 | Network NetworkList `json:"network,omitempty"` 9 | InboundTLSOptionsContainer 10 | } 11 | -------------------------------------------------------------------------------- /docs/configuration/inbound/redirect.zh.md: -------------------------------------------------------------------------------- 1 | !!! quote "" 2 | 3 | 仅支持 Linux 和 macOS。 4 | 5 | ### 结构 6 | 7 | ```json 8 | { 9 | "type": "redirect", 10 | "tag": "redirect-in", 11 | 12 | ... // 监听字段 13 | } 14 | ``` 15 | ### 监听字段 16 | 17 | 参阅 [监听字段](/zh/configuration/shared/listen/)。 18 | -------------------------------------------------------------------------------- /test/config/hysteria2-server.yml: -------------------------------------------------------------------------------- 1 | listen: 127.0.0.1:10000 2 | auth: 3 | type: password 4 | password: password 5 | tls: 6 | sni: example.org 7 | cert: /etc/hysteria/cert.pem 8 | key: /etc/hysteria/key.pem 9 | obfs: 10 | type: salamander 11 | salamander: 12 | password: cry_me_a_r1ver -------------------------------------------------------------------------------- /.github/update_clients.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROJECTS=$(dirname "$0")/../.. 4 | 5 | function updateClient() { 6 | pushd clients/$1 7 | git fetch 8 | git reset FETCH_HEAD --hard 9 | popd 10 | git add clients/$1 11 | } 12 | 13 | updateClient "apple" 14 | updateClient "android" 15 | -------------------------------------------------------------------------------- /constant/protocol.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | ProtocolTLS = "tls" 5 | ProtocolHTTP = "http" 6 | ProtocolQUIC = "quic" 7 | ProtocolDNS = "dns" 8 | ProtocolSTUN = "stun" 9 | ProtocolBitTorrent = "bittorrent" 10 | ProtocolDTLS = "dtls" 11 | ) 12 | -------------------------------------------------------------------------------- /experimental/libbox/tun_name_darwin.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | import "golang.org/x/sys/unix" 4 | 5 | func getTunnelName(fd int32) (string, error) { 6 | return unix.GetsockoptString( 7 | int(fd), 8 | 2, /* #define SYSPROTO_CONTROL 2 */ 9 | 2, /* #define UTUN_OPT_IFNAME 2 */ 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /transport/v2raygrpc/stream.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package transport.v2raygrpc; 4 | option go_package = "github.com/sagernet/sing-box/transport/v2raygrpc"; 5 | 6 | message Hunk { 7 | bytes data = 1; 8 | } 9 | 10 | service GunService { 11 | rpc Tun (stream Hunk) returns (stream Hunk); 12 | } 13 | -------------------------------------------------------------------------------- /transport/wireguard/device.go: -------------------------------------------------------------------------------- 1 | package wireguard 2 | 3 | import ( 4 | N "github.com/sagernet/sing/common/network" 5 | "github.com/sagernet/wireguard-go/tun" 6 | ) 7 | 8 | type Device interface { 9 | tun.Device 10 | N.Dialer 11 | Start() error 12 | // NewEndpoint() (stack.LinkEndpoint, error) 13 | } 14 | -------------------------------------------------------------------------------- /common/settings/time_unix.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package settings 4 | 5 | import ( 6 | "time" 7 | 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | func SetSystemTime(nowTime time.Time) error { 12 | timeVal := unix.NsecToTimeval(nowTime.UnixNano()) 13 | return unix.Settimeofday(&timeVal) 14 | } 15 | -------------------------------------------------------------------------------- /test/config/tuic-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "[::]:10000", 3 | "users": { 4 | "FE35D05B-8803-45C4-BAE6-723AD2CD5D3D": "tuic" 5 | }, 6 | "certificate": "/etc/tuic/cert.pem", 7 | "private_key": "/etc/tuic/key.pem", 8 | "max_external_packet_size": 65535, 9 | "log_level": "debug" 10 | } -------------------------------------------------------------------------------- /transport/wireguard/device_stack_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_gvisor 2 | 3 | package wireguard 4 | 5 | import ( 6 | "net/netip" 7 | 8 | "github.com/sagernet/sing-tun" 9 | ) 10 | 11 | func NewStackDevice(localAddresses []netip.Prefix, mtu uint32) (Device, error) { 12 | return nil, tun.ErrGVisorNotIncluded 13 | } 14 | -------------------------------------------------------------------------------- /test/config/hysteria-client.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "127.0.0.1:10000", 3 | "auth_str": "password", 4 | "obfs": "fuck me till the daylight", 5 | "up_mbps": 100, 6 | "down_mbps": 100, 7 | "socks5": { 8 | "listen": "127.0.0.1:10001" 9 | }, 10 | "server_name": "example.org", 11 | "ca": "/etc/hysteria/ca.pem" 12 | } -------------------------------------------------------------------------------- /common/tls/common.go: -------------------------------------------------------------------------------- 1 | package tls 2 | 3 | const ( 4 | VersionTLS10 = 0x0301 5 | VersionTLS11 = 0x0302 6 | VersionTLS12 = 0x0303 7 | VersionTLS13 = 0x0304 8 | 9 | // Deprecated: SSLv3 is cryptographically broken, and is no longer 10 | // supported by this package. See golang.org/issue/32716. 11 | VersionSSL30 = 0x0300 12 | ) 13 | -------------------------------------------------------------------------------- /transport/v2rayhttp/pool.go: -------------------------------------------------------------------------------- 1 | package v2rayhttp 2 | 3 | import "net/http" 4 | 5 | type ConnectionPool interface { 6 | CloseIdleConnections() 7 | } 8 | 9 | func CloseIdleConnections(transport http.RoundTripper) { 10 | if connectionPool, ok := transport.(ConnectionPool); ok { 11 | connectionPool.CloseIdleConnections() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/manual/proxy/server.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/server 3 | --- 4 | 5 | # Server 6 | 7 | To use sing-box as a proxy protocol server, you pretty much only need to configure the inbound for that protocol. 8 | 9 | The Proxy Protocol menu below contains descriptions and configuration examples 10 | of recommended protocols for bypassing GFW. 11 | -------------------------------------------------------------------------------- /common/dialer/default_go1.20.go: -------------------------------------------------------------------------------- 1 | //go:build go1.20 2 | 3 | package dialer 4 | 5 | import ( 6 | "net" 7 | 8 | "github.com/sagernet/tfo-go" 9 | ) 10 | 11 | type tcpDialer = tfo.Dialer 12 | 13 | func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { 14 | return tfo.Dialer{Dialer: dialer, DisableTFO: !tfoEnabled}, nil 15 | } 16 | -------------------------------------------------------------------------------- /docs/configuration/outbound/dns.md: -------------------------------------------------------------------------------- 1 | `dns` outbound is a internal DNS server. 2 | 3 | ### Structure 4 | 5 | ```json 6 | { 7 | "type": "dns", 8 | "tag": "dns-out" 9 | } 10 | ``` 11 | 12 | !!! note "" 13 | 14 | There are no outbound connections by the DNS outbound, all requests are handled internally. 15 | 16 | ### Fields 17 | 18 | No fields. -------------------------------------------------------------------------------- /inbound/default_tcp_nongo1.20.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.20 2 | 3 | package inbound 4 | 5 | import ( 6 | "context" 7 | "net" 8 | "os" 9 | ) 10 | 11 | const go120Available = false 12 | 13 | func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) { 14 | return nil, os.ErrInvalid 15 | } 16 | -------------------------------------------------------------------------------- /docs/configuration/inbound/redirect.md: -------------------------------------------------------------------------------- 1 | !!! quote "" 2 | 3 | Only supported on Linux and macOS. 4 | 5 | ### Structure 6 | 7 | ```json 8 | { 9 | "type": "redirect", 10 | "tag": "redirect-in", 11 | 12 | ... // Listen Fields 13 | } 14 | ``` 15 | 16 | ### Listen Fields 17 | 18 | See [Listen Fields](/configuration/shared/listen/) for details. 19 | -------------------------------------------------------------------------------- /outbound/tor_embed.go: -------------------------------------------------------------------------------- 1 | //go:build with_embedded_tor && !(android || ios) 2 | 3 | package outbound 4 | 5 | import ( 6 | "berty.tech/go-libtor" 7 | "github.com/cretz/bine/tor" 8 | ) 9 | 10 | func newConfig() tor.StartConf { 11 | return tor.StartConf{ 12 | ProcessCreator: libtor.Creator, 13 | UseEmbeddedControlConn: true, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /common/settings/proxy_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !(windows || linux || darwin) 2 | 3 | package settings 4 | 5 | import ( 6 | "context" 7 | "os" 8 | 9 | M "github.com/sagernet/sing/common/metadata" 10 | ) 11 | 12 | func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (SystemProxy, error) { 13 | return nil, os.ErrInvalid 14 | } 15 | -------------------------------------------------------------------------------- /outbound/tor_embed_mobile.go: -------------------------------------------------------------------------------- 1 | //go:build with_embedded_tor && (android || ios) 2 | 3 | package outbound 4 | 5 | import ( 6 | "github.com/cretz/bine/tor" 7 | "github.com/ooni/go-libtor" 8 | ) 9 | 10 | func newConfig() tor.StartConf { 11 | return tor.StartConf{ 12 | ProcessCreator: libtor.Creator, 13 | UseEmbeddedControlConn: true, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cmd/sing-box/cmd_tools_fetch_http3_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_quic 2 | 3 | package main 4 | 5 | import ( 6 | "net/url" 7 | "os" 8 | 9 | box "github.com/sagernet/sing-box" 10 | ) 11 | 12 | func initializeHTTP3Client(instance *box.Box) error { 13 | return os.ErrInvalid 14 | } 15 | 16 | func fetchHTTP3(parsedURL *url.URL) error { 17 | return os.ErrInvalid 18 | } 19 | -------------------------------------------------------------------------------- /option/tor.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type TorOutboundOptions struct { 4 | DialerOptions 5 | ExecutablePath string `json:"executable_path,omitempty"` 6 | ExtraArgs []string `json:"extra_args,omitempty"` 7 | DataDirectory string `json:"data_directory,omitempty"` 8 | Options map[string]string `json:"torrc,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /docs/configuration/dns/fakeip.zh.md: -------------------------------------------------------------------------------- 1 | # FakeIP 2 | 3 | ### 结构 4 | 5 | ```json 6 | { 7 | "enabled": true, 8 | "inet4_range": "198.18.0.0/15", 9 | "inet6_range": "fc00::/18" 10 | } 11 | ``` 12 | 13 | ### 字段 14 | 15 | #### enabled 16 | 17 | 启用 FakeIP 服务。 18 | 19 | #### inet4_range 20 | 21 | 用于 FakeIP 的 IPv4 地址范围。 22 | 23 | #### inet6_range 24 | 25 | 用于 FakeIP 的 IPv6 地址范围。 26 | -------------------------------------------------------------------------------- /option/tun_platform.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type TunPlatformOptions struct { 4 | HTTPProxy *HTTPProxyOptions `json:"http_proxy,omitempty"` 5 | } 6 | 7 | type HTTPProxyOptions struct { 8 | Enabled bool `json:"enabled,omitempty"` 9 | ServerOptions 10 | BypassDomain Listable[string] `json:"bypass_domain,omitempty"` 11 | MatchDomain Listable[string] `json:"match_domain,omitempty"` 12 | } 13 | -------------------------------------------------------------------------------- /test/config/tuic-client.json: -------------------------------------------------------------------------------- 1 | { 2 | "relay": { 3 | "server": "example.org:10000", 4 | "ip": "127.0.0.1", 5 | "uuid": "FE35D05B-8803-45C4-BAE6-723AD2CD5D3D", 6 | "password": "tuic", 7 | "certificates": [ 8 | "/etc/tuic/ca.pem" 9 | ] 10 | }, 11 | "local": { 12 | "server": "127.0.0.1:10001", 13 | "max_packet_size": 65535 14 | }, 15 | "log_level": "debug" 16 | } -------------------------------------------------------------------------------- /docs/configuration/inbound/tproxy.zh.md: -------------------------------------------------------------------------------- 1 | !!! quote "" 2 | 3 | 仅支持 Linux。 4 | 5 | ### 结构 6 | 7 | ```json 8 | { 9 | "type": "tproxy", 10 | "tag": "tproxy-in", 11 | 12 | ... // 监听字段 13 | 14 | "network": "udp" 15 | } 16 | ``` 17 | 18 | ### 监听字段 19 | 20 | 参阅 [监听字段](/zh/configuration/shared/listen/)。 21 | 22 | ### 字段 23 | 24 | #### network 25 | 26 | 监听的网络协议,`tcp` `udp` 之一。 27 | 28 | 默认所有。 29 | -------------------------------------------------------------------------------- /experimental/clashapi/ctxkeys.go: -------------------------------------------------------------------------------- 1 | package clashapi 2 | 3 | var ( 4 | CtxKeyProxyName = contextKey("proxy name") 5 | CtxKeyProviderName = contextKey("provider name") 6 | CtxKeyProxy = contextKey("proxy") 7 | CtxKeyProvider = contextKey("provider") 8 | ) 9 | 10 | type contextKey string 11 | 12 | func (c contextKey) String() string { 13 | return "clash context key " + string(c) 14 | } 15 | -------------------------------------------------------------------------------- /test/v2ray_httpupgrade_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | C "github.com/sagernet/sing-box/constant" 7 | "github.com/sagernet/sing-box/option" 8 | ) 9 | 10 | func TestV2RayHTTPUpgrade(t *testing.T) { 11 | t.Run("self", func(t *testing.T) { 12 | testV2RayTransportSelf(t, &option.V2RayTransportOptions{ 13 | Type: C.V2RayTransportTypeHTTPUpgrade, 14 | }) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /docs/configuration/dns/fakeip.md: -------------------------------------------------------------------------------- 1 | # FakeIP 2 | 3 | ### Structure 4 | 5 | ```json 6 | { 7 | "enabled": true, 8 | "inet4_range": "198.18.0.0/15", 9 | "inet6_range": "fc00::/18" 10 | } 11 | ``` 12 | 13 | ### Fields 14 | 15 | #### enabled 16 | 17 | Enable FakeIP service. 18 | 19 | #### inet4_range 20 | 21 | IPv4 address range for FakeIP. 22 | 23 | #### inet6_address 24 | 25 | IPv6 address range for FakeIP. 26 | -------------------------------------------------------------------------------- /include/dhcp_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_dhcp 2 | 3 | package include 4 | 5 | import ( 6 | "github.com/sagernet/sing-dns" 7 | E "github.com/sagernet/sing/common/exceptions" 8 | ) 9 | 10 | func init() { 11 | dns.RegisterTransport([]string{"dhcp"}, func(options dns.TransportOptions) (dns.Transport, error) { 12 | return nil, E.New(`DHCP is not included in this build, rebuild with -tags with_dhcp`) 13 | }) 14 | } 15 | -------------------------------------------------------------------------------- /outbound/lookback.go: -------------------------------------------------------------------------------- 1 | package outbound 2 | 3 | import "context" 4 | 5 | type outboundTagKey struct{} 6 | 7 | func ContextWithTag(ctx context.Context, outboundTag string) context.Context { 8 | return context.WithValue(ctx, outboundTagKey{}, outboundTag) 9 | } 10 | 11 | func TagFromContext(ctx context.Context) (string, bool) { 12 | value, loaded := ctx.Value(outboundTagKey{}).(string) 13 | return value, loaded 14 | } 15 | -------------------------------------------------------------------------------- /transport/v2raywebsocket/deadline.go: -------------------------------------------------------------------------------- 1 | package v2raywebsocket 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | type deadConn struct { 9 | net.Conn 10 | } 11 | 12 | func (c *deadConn) SetDeadline(t time.Time) error { 13 | return nil 14 | } 15 | 16 | func (c *deadConn) SetReadDeadline(t time.Time) error { 17 | return nil 18 | } 19 | 20 | func (c *deadConn) SetWriteDeadline(t time.Time) error { 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /common/badversion/version_json.go: -------------------------------------------------------------------------------- 1 | package badversion 2 | 3 | import "github.com/sagernet/sing/common/json" 4 | 5 | func (v Version) MarshalJSON() ([]byte, error) { 6 | return json.Marshal(v.String()) 7 | } 8 | 9 | func (v *Version) UnmarshalJSON(data []byte) error { 10 | var version string 11 | err := json.Unmarshal(data, &version) 12 | if err != nil { 13 | return err 14 | } 15 | *v = Parse(version) 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /common/interrupt/context.go: -------------------------------------------------------------------------------- 1 | package interrupt 2 | 3 | import "context" 4 | 5 | type contextKeyIsExternalConnection struct{} 6 | 7 | func ContextWithIsExternalConnection(ctx context.Context) context.Context { 8 | return context.WithValue(ctx, contextKeyIsExternalConnection{}, true) 9 | } 10 | 11 | func IsExternalConnectionFromContext(ctx context.Context) bool { 12 | return ctx.Value(contextKeyIsExternalConnection{}) != nil 13 | } 14 | -------------------------------------------------------------------------------- /constant/rule.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | RuleTypeDefault = "default" 5 | RuleTypeLogical = "logical" 6 | ) 7 | 8 | const ( 9 | LogicalTypeAnd = "and" 10 | LogicalTypeOr = "or" 11 | ) 12 | 13 | const ( 14 | RuleSetTypeInline = "inline" 15 | RuleSetTypeLocal = "local" 16 | RuleSetTypeRemote = "remote" 17 | RuleSetVersion1 = 1 18 | RuleSetFormatSource = "source" 19 | RuleSetFormatBinary = "binary" 20 | ) 21 | -------------------------------------------------------------------------------- /common/dialer/default_nongo1.20.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.20 2 | 3 | package dialer 4 | 5 | import ( 6 | "net" 7 | 8 | E "github.com/sagernet/sing/common/exceptions" 9 | ) 10 | 11 | type tcpDialer = net.Dialer 12 | 13 | func newTCPDialer(dialer net.Dialer, tfoEnabled bool) (tcpDialer, error) { 14 | if tfoEnabled { 15 | return dialer, E.New("TCP Fast Open requires go1.20, please recompile your binary.") 16 | } 17 | return dialer, nil 18 | } 19 | -------------------------------------------------------------------------------- /docs/configuration/shared/tcp-brutal.zh.md: -------------------------------------------------------------------------------- 1 | ### 服务器要求 2 | 3 | * Linux 4 | * `brutal` 拥塞控制算法内核模块已安装 5 | 6 | 参阅 [tcp-brutal](https://github.com/apernet/tcp-brutal)。 7 | 8 | ### 结构 9 | 10 | ```json 11 | { 12 | "enabled": true, 13 | "up_mbps": 100, 14 | "down_mbps": 100 15 | } 16 | ``` 17 | 18 | ### 字段 19 | 20 | #### enabled 21 | 22 | 启用 TCP Brutal 拥塞控制算法。 23 | 24 | #### up_mbps, down_mbps 25 | 26 | ==必填== 27 | 28 | 上传和下载带宽,以 Mbps 为单位。 29 | -------------------------------------------------------------------------------- /experimental/clashapi/common.go: -------------------------------------------------------------------------------- 1 | package clashapi 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | 7 | "github.com/go-chi/chi/v5" 8 | ) 9 | 10 | // When name is composed of a partial escape string, Golang does not unescape it 11 | func getEscapeParam(r *http.Request, paramName string) string { 12 | param := chi.URLParam(r, paramName) 13 | if newParam, err := url.PathUnescape(param); err == nil { 14 | param = newParam 15 | } 16 | return param 17 | } 18 | -------------------------------------------------------------------------------- /experimental/libbox/command.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | const ( 4 | CommandLog int32 = iota 5 | CommandStatus 6 | CommandServiceReload 7 | CommandServiceClose 8 | CommandCloseConnections 9 | CommandGroup 10 | CommandSelectOutbound 11 | CommandURLTest 12 | CommandGroupExpand 13 | CommandClashMode 14 | CommandSetClashMode 15 | CommandGetSystemProxyStatus 16 | CommandSetSystemProxyEnabled 17 | CommandConnections 18 | CommandCloseConnection 19 | ) 20 | -------------------------------------------------------------------------------- /release/local/install_go.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | 5 | go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') 6 | curl -Lo go.tar.gz "https://go.dev/dl/go$go_version.linux-amd64.tar.gz" 7 | sudo rm -rf /usr/local/go 8 | sudo tar -C /usr/local -xzf go.tar.gz 9 | rm go.tar.gz 10 | -------------------------------------------------------------------------------- /docs/configuration/shared/dns01_challenge.zh.md: -------------------------------------------------------------------------------- 1 | ### 结构 2 | 3 | ```json 4 | { 5 | "provider": "", 6 | 7 | ... // 提供商字段 8 | } 9 | ``` 10 | 11 | ### 提供商字段 12 | 13 | #### Alibaba Cloud DNS 14 | 15 | ```json 16 | { 17 | "provider": "alidns", 18 | "access_key_id": "", 19 | "access_key_secret": "", 20 | "region_id": "" 21 | } 22 | ``` 23 | 24 | #### Cloudflare 25 | 26 | ```json 27 | { 28 | "provider": "cloudflare", 29 | "api_token": "" 30 | } 31 | ``` -------------------------------------------------------------------------------- /common/redir/tproxy_other.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | 3 | package redir 4 | 5 | import ( 6 | "net/netip" 7 | "os" 8 | 9 | "github.com/sagernet/sing/common/control" 10 | ) 11 | 12 | func TProxy(fd uintptr, isIPv6 bool) error { 13 | return os.ErrInvalid 14 | } 15 | 16 | func TProxyWriteBack() control.Func { 17 | return nil 18 | } 19 | 20 | func GetOriginalDestinationFromOOB(oob []byte) (netip.AddrPort, error) { 21 | return netip.AddrPort{}, os.ErrInvalid 22 | } 23 | -------------------------------------------------------------------------------- /inbound/default_tcp_go1.20.go: -------------------------------------------------------------------------------- 1 | //go:build go1.20 2 | 3 | package inbound 4 | 5 | import ( 6 | "context" 7 | "net" 8 | 9 | "github.com/sagernet/tfo-go" 10 | ) 11 | 12 | const go120Available = true 13 | 14 | func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) { 15 | var tfoConfig tfo.ListenConfig 16 | tfoConfig.ListenConfig = listenConfig 17 | return tfoConfig.Listen(ctx, network, address) 18 | } 19 | -------------------------------------------------------------------------------- /cmd/internal/read_tag/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sagernet/sing-box/cmd/internal/build_shared" 7 | "github.com/sagernet/sing-box/log" 8 | ) 9 | 10 | func main() { 11 | currentTag, err := build_shared.ReadTag() 12 | if err != nil { 13 | log.Error(err) 14 | _, err = os.Stdout.WriteString("unknown\n") 15 | } else { 16 | _, err = os.Stdout.WriteString(currentTag + "\n") 17 | } 18 | if err != nil { 19 | log.Error(err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/clients/privacy.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/security 3 | --- 4 | 5 | # Privacy policy 6 | 7 | sing-box and official graphics clients do not collect or share personal data, 8 | and the data generated by the software is always on your device. 9 | 10 | ## Android 11 | 12 | If your configuration contains `wifi_ssid` or `wifi_bssid` routing rules, 13 | sing-box uses the location permission in the background 14 | to get information about the connected Wi-Fi network to make them work. 15 | -------------------------------------------------------------------------------- /docs/configuration/inbound/socks.zh.md: -------------------------------------------------------------------------------- 1 | `socks` 入站是一个 socks4, socks4a 和 socks5 服务器. 2 | 3 | ### 结构 4 | 5 | ```json 6 | { 7 | "type": "socks", 8 | "tag": "socks-in", 9 | 10 | ... // 监听字段 11 | 12 | "users": [ 13 | { 14 | "username": "admin", 15 | "password": "admin" 16 | } 17 | ] 18 | } 19 | ``` 20 | 21 | ### 监听字段 22 | 23 | 参阅 [监听字段](/zh/configuration/shared/listen/)。 24 | 25 | ### 字段 26 | 27 | #### users 28 | 29 | SOCKS 用户 30 | 31 | 如果为空则不需要验证。 32 | -------------------------------------------------------------------------------- /docs/configuration/inbound/tproxy.md: -------------------------------------------------------------------------------- 1 | !!! quote "" 2 | 3 | Only supported on Linux. 4 | 5 | ### Structure 6 | 7 | ```json 8 | { 9 | "type": "tproxy", 10 | "tag": "tproxy-in", 11 | 12 | ... // Listen Fields 13 | 14 | "network": "udp" 15 | } 16 | ``` 17 | 18 | ### Listen Fields 19 | 20 | See [Listen Fields](/configuration/shared/listen/) for details. 21 | 22 | ### Fields 23 | 24 | #### network 25 | 26 | Listen network, one of `tcp` `udp`. 27 | 28 | Both if empty. 29 | -------------------------------------------------------------------------------- /test/config/shadowsocksr.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": "0.0.0.0", 3 | "server_ipv6": "::", 4 | "server_port": 10000, 5 | "local_address": "127.0.0.1", 6 | "local_port": 1080, 7 | "password": "password0", 8 | "timeout": 120, 9 | "method": "aes-256-cfb", 10 | "protocol": "origin", 11 | "protocol_param": "", 12 | "obfs": "plain", 13 | "obfs_param": "", 14 | "redirect": "", 15 | "dns_ipv6": false, 16 | "fast_open": true, 17 | "workers": 1, 18 | "forbidden_ip": "" 19 | } -------------------------------------------------------------------------------- /docs/configuration/log/index.zh.md: -------------------------------------------------------------------------------- 1 | # 日志 2 | 3 | ### 结构 4 | 5 | ```json 6 | { 7 | "log": { 8 | "disabled": false, 9 | "level": "info", 10 | "output": "box.log", 11 | "timestamp": true 12 | } 13 | } 14 | 15 | ``` 16 | 17 | ### 字段 18 | 19 | #### disabled 20 | 21 | 禁用日志,启动后不输出日志。 22 | 23 | #### level 24 | 25 | 日志等级,可选值:`trace` `debug` `info` `warn` `error` `fatal` `panic`。 26 | 27 | #### output 28 | 29 | 输出文件路径,启动后将不输出到控制台。 30 | 31 | #### timestamp 32 | 33 | 添加时间到每行。 -------------------------------------------------------------------------------- /docs/configuration/shared/dns01_challenge.md: -------------------------------------------------------------------------------- 1 | ### Structure 2 | 3 | ```json 4 | { 5 | "provider": "", 6 | 7 | ... // Provider Fields 8 | } 9 | ``` 10 | 11 | ### Provider Fields 12 | 13 | #### Alibaba Cloud DNS 14 | 15 | ```json 16 | { 17 | "provider": "alidns", 18 | "access_key_id": "", 19 | "access_key_secret": "", 20 | "region_id": "" 21 | } 22 | ``` 23 | 24 | #### Cloudflare 25 | 26 | ```json 27 | { 28 | "provider": "cloudflare", 29 | "api_token": "" 30 | } 31 | ``` -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Mark stale issues and pull requests 2 | 3 | on: 4 | schedule: 5 | - cron: "30 1 * * *" 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/stale@v9 12 | with: 13 | stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days' 14 | days-before-stale: 60 15 | days-before-close: 5 -------------------------------------------------------------------------------- /common/tls/acme_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_acme 2 | 3 | package tls 4 | 5 | import ( 6 | "context" 7 | "crypto/tls" 8 | 9 | "github.com/sagernet/sing-box/adapter" 10 | "github.com/sagernet/sing-box/option" 11 | E "github.com/sagernet/sing/common/exceptions" 12 | ) 13 | 14 | func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) { 15 | return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`) 16 | } 17 | -------------------------------------------------------------------------------- /release/local/reinstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | 5 | if [ -d /usr/local/go ]; then 6 | export PATH="$PATH:/usr/local/go/bin" 7 | fi 8 | 9 | DIR=$(dirname "$0") 10 | PROJECT=$DIR/../.. 11 | 12 | pushd $PROJECT 13 | go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box 14 | popd 15 | 16 | sudo systemctl stop sing-box 17 | sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/ 18 | sudo systemctl start sing-box 19 | -------------------------------------------------------------------------------- /inbound/tuic_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_quic 2 | 3 | package inbound 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | C "github.com/sagernet/sing-box/constant" 10 | "github.com/sagernet/sing-box/log" 11 | "github.com/sagernet/sing-box/option" 12 | ) 13 | 14 | func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { 15 | return nil, C.ErrQUICNotIncluded 16 | } 17 | -------------------------------------------------------------------------------- /constant/path_unix.go: -------------------------------------------------------------------------------- 1 | //go:build unix || linux 2 | 3 | package constant 4 | 5 | import ( 6 | "os" 7 | ) 8 | 9 | func init() { 10 | resourcePaths = append(resourcePaths, "/etc") 11 | resourcePaths = append(resourcePaths, "/usr/share") 12 | resourcePaths = append(resourcePaths, "/usr/local/etc") 13 | resourcePaths = append(resourcePaths, "/usr/local/share") 14 | if homeDir := os.Getenv("HOME"); homeDir != "" { 15 | resourcePaths = append(resourcePaths, homeDir+"/.local/share") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /outbound/tuic_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_quic 2 | 3 | package outbound 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | C "github.com/sagernet/sing-box/constant" 10 | "github.com/sagernet/sing-box/log" 11 | "github.com/sagernet/sing-box/option" 12 | ) 13 | 14 | func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { 15 | return nil, C.ErrQUICNotIncluded 16 | } 17 | -------------------------------------------------------------------------------- /common/tls/reality_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_reality_server 2 | 3 | package tls 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/log" 9 | "github.com/sagernet/sing-box/option" 10 | E "github.com/sagernet/sing/common/exceptions" 11 | ) 12 | 13 | func NewRealityServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { 14 | return nil, E.New(`reality server is not included in this build, rebuild with -tags with_reality_server`) 15 | } 16 | -------------------------------------------------------------------------------- /test/config/vmess-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "inbounds": [ 6 | { 7 | "listen": "0.0.0.0", 8 | "port": 1234, 9 | "protocol": "vmess", 10 | "settings": { 11 | "clients": [ 12 | { 13 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811", 14 | "alterId": 0 15 | } 16 | ] 17 | } 18 | } 19 | ], 20 | "outbounds": [ 21 | { 22 | "protocol": "freedom" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /log/override.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | type overrideLevelKey struct{} 8 | 9 | func ContextWithOverrideLevel(ctx context.Context, level Level) context.Context { 10 | return context.WithValue(ctx, (*overrideLevelKey)(nil), level) 11 | } 12 | 13 | func OverrideLevelFromContext(origin Level, ctx context.Context) Level { 14 | level, loaded := ctx.Value((*overrideLevelKey)(nil)).(Level) 15 | if !loaded || origin > level { 16 | return origin 17 | } 18 | return level 19 | } 20 | -------------------------------------------------------------------------------- /test/config/vless-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "inbounds": [ 6 | { 7 | "listen": "0.0.0.0", 8 | "port": 1234, 9 | "protocol": "vless", 10 | "settings": { 11 | "decryption": "none", 12 | "clients": [ 13 | { 14 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 15 | } 16 | ] 17 | } 18 | } 19 | ], 20 | "outbounds": [ 21 | { 22 | "protocol": "freedom" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /option/shadowsocksr.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type ShadowsocksROutboundOptions struct { 4 | DialerOptions 5 | ServerOptions 6 | Method string `json:"method"` 7 | Password string `json:"password"` 8 | Obfs string `json:"obfs,omitempty"` 9 | ObfsParam string `json:"obfs_param,omitempty"` 10 | Protocol string `json:"protocol,omitempty"` 11 | ProtocolParam string `json:"protocol_param,omitempty"` 12 | Network NetworkList `json:"network,omitempty"` 13 | } 14 | -------------------------------------------------------------------------------- /test/config/naive-nginx.conf: -------------------------------------------------------------------------------- 1 | stream { 2 | server { 3 | listen 10000 ssl; 4 | listen [::]:10000 ssl; 5 | 6 | ssl_certificate /etc/nginx/cert.pem; 7 | ssl_certificate_key /etc/nginx/key.pem; 8 | ssl_session_timeout 1d; 9 | ssl_session_cache shared:MozSSL:10m; # about 40000 sessions 10 | ssl_session_tickets off; 11 | 12 | # modern configuration 13 | ssl_protocols TLSv1.3; 14 | ssl_prefer_server_ciphers off; 15 | 16 | proxy_pass 127.0.0.1:10003; 17 | } 18 | } -------------------------------------------------------------------------------- /option/direct.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type DirectInboundOptions struct { 4 | ListenOptions 5 | Network NetworkList `json:"network,omitempty"` 6 | OverrideAddress string `json:"override_address,omitempty"` 7 | OverridePort uint16 `json:"override_port,omitempty"` 8 | } 9 | 10 | type DirectOutboundOptions struct { 11 | DialerOptions 12 | OverrideAddress string `json:"override_address,omitempty"` 13 | OverridePort uint16 `json:"override_port,omitempty"` 14 | ProxyProtocol uint8 `json:"proxy_protocol,omitempty"` 15 | } 16 | -------------------------------------------------------------------------------- /adapter/outbound.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | N "github.com/sagernet/sing/common/network" 8 | ) 9 | 10 | // Note: for proxy protocols, outbound creates early connections by default. 11 | 12 | type Outbound interface { 13 | Type() string 14 | Tag() string 15 | Network() []string 16 | Dependencies() []string 17 | N.Dialer 18 | NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error 19 | NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error 20 | } 21 | -------------------------------------------------------------------------------- /docs/support.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/forum 3 | --- 4 | 5 | # 支持 6 | 7 | | 通道 | 链接 | 8 | |:--------------|:--------------------------------------------| 9 | | 社区 | https://community.sagernet.org | 10 | | GitHub Issues | https://github.com/SagerNet/sing-box/issues | 11 | | Telegram 通知频道 | https://t.me/yapnc | 12 | | Telegram 用户组 | https://t.me/yapug | 13 | | 邮件 | contact@sagernet.org | 14 | 15 | -------------------------------------------------------------------------------- /constant/goos/zgoos_aix.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build aix 4 | 5 | package goos 6 | 7 | const GOOS = `aix` 8 | 9 | const IsAix = 1 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /constant/goos/zgoos_ios.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build ios 4 | 5 | package goos 6 | 7 | const GOOS = `ios` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 1 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /constant/goos/zgoos_js.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build js 4 | 5 | package goos 6 | 7 | const GOOS = `js` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 1 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /constant/goos/zgoos_zos.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build zos 4 | 5 | package goos 6 | 7 | const GOOS = `zos` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 1 26 | -------------------------------------------------------------------------------- /docs/configuration/inbound/direct.zh.md: -------------------------------------------------------------------------------- 1 | `direct` 入站是一个隧道服务器。 2 | 3 | ### 结构 4 | 5 | ```json 6 | { 7 | "type": "direct", 8 | "tag": "direct-in", 9 | 10 | ... // 监听字段 11 | 12 | "network": "udp", 13 | "override_address": "1.0.0.1", 14 | "override_port": 53 15 | } 16 | ``` 17 | 18 | ### 监听字段 19 | 20 | 参阅 [监听字段](/zh/configuration/shared/listen/)。 21 | 22 | ### 字段 23 | 24 | #### network 25 | 26 | 监听的网络协议,`tcp` `udp` 之一。 27 | 28 | 默认所有。 29 | 30 | #### override_address 31 | 32 | 覆盖连接目标地址。 33 | 34 | #### override_port 35 | 36 | 覆盖连接目标端口。 37 | 38 | -------------------------------------------------------------------------------- /docs/configuration/inbound/socks.md: -------------------------------------------------------------------------------- 1 | `socks` inbound is a socks4, socks4a, socks5 server. 2 | 3 | ### Structure 4 | 5 | ```json 6 | { 7 | "type": "socks", 8 | "tag": "socks-in", 9 | 10 | ... // Listen Fields 11 | 12 | "users": [ 13 | { 14 | "username": "admin", 15 | "password": "admin" 16 | } 17 | ] 18 | } 19 | ``` 20 | 21 | ### Listen Fields 22 | 23 | See [Listen Fields](/configuration/shared/listen/) for details. 24 | 25 | ### Fields 26 | 27 | #### users 28 | 29 | SOCKS users. 30 | 31 | No authentication required if empty. 32 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - gofumpt 5 | - govet 6 | - gci 7 | - staticcheck 8 | - paralleltest 9 | 10 | run: 11 | skip-dirs: 12 | - transport/simple-obfs 13 | - transport/clashssr 14 | - transport/cloudflaretls 15 | - transport/shadowtls/tls 16 | - transport/shadowtls/tls_go119 17 | 18 | linters-settings: 19 | gci: 20 | custom-order: true 21 | sections: 22 | - standard 23 | - prefix(github.com/sagernet/) 24 | - default 25 | staticcheck: 26 | go: '1.20' 27 | -------------------------------------------------------------------------------- /constant/goos/zgoos_hurd.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build hurd 4 | 5 | package goos 6 | 7 | const GOOS = `hurd` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 1 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /constant/goos/zgoos_plan9.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build plan9 4 | 5 | package goos 6 | 7 | const GOOS = `plan9` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 1 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /debug_unix.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | 3 | package box 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 | -------------------------------------------------------------------------------- /docs/configuration/rule-set/source-format.md: -------------------------------------------------------------------------------- 1 | # Source Format 2 | 3 | !!! question "Since sing-box 1.8.0" 4 | 5 | ### Structure 6 | 7 | ```json 8 | { 9 | "version": 1, 10 | "rules": [] 11 | } 12 | ``` 13 | 14 | ### Compile 15 | 16 | Use `sing-box rule-set compile [--output .srs] .json` to compile source to binary rule-set. 17 | 18 | ### Fields 19 | 20 | #### version 21 | 22 | ==Required== 23 | 24 | Version of rule-set, must be `1`. 25 | 26 | #### rules 27 | 28 | ==Required== 29 | 30 | List of [Headless Rule](./headless-rule.md/). 31 | -------------------------------------------------------------------------------- /docs/configuration/shared/tcp-brutal.md: -------------------------------------------------------------------------------- 1 | ### Server Requirements 2 | 3 | * Linux 4 | * `brutal` congestion control algorithm kernel module installed 5 | 6 | See [tcp-brutal](https://github.com/apernet/tcp-brutal) for details. 7 | 8 | ### Structure 9 | 10 | ```json 11 | { 12 | "enabled": true, 13 | "up_mbps": 100, 14 | "down_mbps": 100 15 | } 16 | ``` 17 | 18 | ### Fields 19 | 20 | #### enabled 21 | 22 | Enable TCP Brutal congestion control algorithm。 23 | 24 | #### up_mbps, down_mbps 25 | 26 | ==Required== 27 | 28 | Upload and download bandwidth, in Mbps. -------------------------------------------------------------------------------- /outbound/shadowsocksr.go: -------------------------------------------------------------------------------- 1 | //go:build with_shadowsocksr 2 | 3 | package outbound 4 | 5 | import ( 6 | "context" 7 | "os" 8 | 9 | "github.com/sagernet/sing-box/adapter" 10 | "github.com/sagernet/sing-box/log" 11 | "github.com/sagernet/sing-box/option" 12 | ) 13 | 14 | var _ int = "ShadowsocksR is deprecated and removed in sing-box 1.6.0" 15 | 16 | func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { 17 | return nil, os.ErrInvalid 18 | } 19 | -------------------------------------------------------------------------------- /constant/goos/zgoos_netbsd.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build netbsd 4 | 5 | package goos 6 | 7 | const GOOS = `netbsd` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 1 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /cmd/internal/build/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "go/build" 5 | "os" 6 | "os/exec" 7 | 8 | "github.com/sagernet/sing-box/cmd/internal/build_shared" 9 | "github.com/sagernet/sing-box/log" 10 | ) 11 | 12 | func main() { 13 | build_shared.FindSDK() 14 | 15 | if os.Getenv("GOPATH") == "" { 16 | os.Setenv("GOPATH", build.Default.GOPATH) 17 | } 18 | 19 | command := exec.Command(os.Args[1], os.Args[2:]...) 20 | command.Stdout = os.Stdout 21 | command.Stderr = os.Stderr 22 | err := command.Run() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /constant/goos/zgoos_android.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build android 4 | 5 | package goos 6 | 7 | const GOOS = `android` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 1 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /constant/goos/zgoos_freebsd.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build freebsd 4 | 5 | package goos 6 | 7 | const GOOS = `freebsd` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 1 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /constant/goos/zgoos_illumos.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build illumos 4 | 5 | package goos 6 | 7 | const GOOS = `illumos` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 1 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /constant/goos/zgoos_openbsd.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build openbsd 4 | 5 | package goos 6 | 7 | const GOOS = `openbsd` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 1 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /constant/goos/zgoos_windows.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build windows 4 | 5 | package goos 6 | 7 | const GOOS = `windows` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 1 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /constant/goos/zgoos_darwin.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build !ios && darwin 4 | 5 | package goos 6 | 7 | const GOOS = `darwin` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 1 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /constant/goos/zgoos_dragonfly.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build dragonfly 4 | 5 | package goos 6 | 7 | const GOOS = `dragonfly` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 1 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /constant/goos/zgoos_linux.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build !android && linux 4 | 5 | package goos 6 | 7 | const GOOS = `linux` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 1 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 0 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /outbound/shadowsocksr_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_shadowsocksr 2 | 3 | package outbound 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | "github.com/sagernet/sing-box/log" 10 | "github.com/sagernet/sing-box/option" 11 | E "github.com/sagernet/sing/common/exceptions" 12 | ) 13 | 14 | func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { 15 | return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") 16 | } 17 | -------------------------------------------------------------------------------- /constant/goos/zgoos_solaris.go: -------------------------------------------------------------------------------- 1 | // Code generated by gengoos.go using 'go generate'. DO NOT EDIT. 2 | 3 | //go:build !illumos && solaris 4 | 5 | package goos 6 | 7 | const GOOS = `solaris` 8 | 9 | const IsAix = 0 10 | const IsAndroid = 0 11 | const IsDarwin = 0 12 | const IsDragonfly = 0 13 | const IsFreebsd = 0 14 | const IsHurd = 0 15 | const IsIllumos = 0 16 | const IsIos = 0 17 | const IsJs = 0 18 | const IsLinux = 0 19 | const IsNacl = 0 20 | const IsNetbsd = 0 21 | const IsOpenbsd = 0 22 | const IsPlan9 = 0 23 | const IsSolaris = 1 24 | const IsWindows = 0 25 | const IsZos = 0 26 | -------------------------------------------------------------------------------- /docs/configuration/log/index.md: -------------------------------------------------------------------------------- 1 | # Log 2 | 3 | ### Structure 4 | 5 | ```json 6 | { 7 | "log": { 8 | "disabled": false, 9 | "level": "info", 10 | "output": "box.log", 11 | "timestamp": true 12 | } 13 | } 14 | 15 | ``` 16 | 17 | ### Fields 18 | 19 | #### disabled 20 | 21 | Disable logging, no output after start. 22 | 23 | #### level 24 | 25 | Log level. One of: `trace` `debug` `info` `warn` `error` `fatal` `panic`. 26 | 27 | #### output 28 | 29 | Output file path. Will not write log to console after enable. 30 | 31 | #### timestamp 32 | 33 | Add time to each line. -------------------------------------------------------------------------------- /outbound/wireguard_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_wireguard 2 | 3 | package outbound 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | "github.com/sagernet/sing-box/log" 10 | "github.com/sagernet/sing-box/option" 11 | E "github.com/sagernet/sing/common/exceptions" 12 | ) 13 | 14 | func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { 15 | return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) 16 | } 17 | -------------------------------------------------------------------------------- /constant/goos/goos.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // package goos contains GOOS-specific constants. 6 | package goos 7 | 8 | // The next line makes 'go generate' write the zgoos*.go files with 9 | // per-OS information, including constants named Is$GOOS for every 10 | // known GOOS. The constant is 1 on the current system, 0 otherwise; 11 | // multiplying by them is useful for defining GOOS-specific constants. 12 | //go:generate go run gengoos.go 13 | -------------------------------------------------------------------------------- /adapter/v2ray.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | E "github.com/sagernet/sing/common/exceptions" 8 | N "github.com/sagernet/sing/common/network" 9 | ) 10 | 11 | type V2RayServerTransport interface { 12 | Network() []string 13 | Serve(listener net.Listener) error 14 | ServePacket(listener net.PacketConn) error 15 | Close() error 16 | } 17 | 18 | type V2RayServerTransportHandler interface { 19 | N.TCPConnectionHandler 20 | E.Handler 21 | } 22 | 23 | type V2RayClientTransport interface { 24 | DialContext(ctx context.Context) (net.Conn, error) 25 | } 26 | -------------------------------------------------------------------------------- /release/local/debug.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | 5 | if [ -d /usr/local/go ]; then 6 | export PATH="$PATH:/usr/local/go/bin" 7 | fi 8 | 9 | DIR=$(dirname "$0") 10 | PROJECT=$DIR/../.. 11 | 12 | pushd $PROJECT 13 | git fetch 14 | git reset FETCH_HEAD --hard 15 | git clean -fdx 16 | go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_acme,debug ./cmd/sing-box 17 | popd 18 | 19 | sudo systemctl stop sing-box 20 | sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/ 21 | sudo systemctl start sing-box 22 | sudo journalctl -u sing-box --output cat -f 23 | -------------------------------------------------------------------------------- /common/dialer/tfo_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.20 2 | 3 | package dialer 4 | 5 | import ( 6 | "context" 7 | "net" 8 | 9 | M "github.com/sagernet/sing/common/metadata" 10 | N "github.com/sagernet/sing/common/network" 11 | ) 12 | 13 | func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { 14 | switch N.NetworkName(network) { 15 | case N.NetworkTCP, N.NetworkUDP: 16 | return dialer.DialContext(ctx, network, destination.String()) 17 | default: 18 | return dialer.DialContext(ctx, network, destination.AddrString()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /include/v2rayapi_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_v2ray_api 2 | 3 | package include 4 | 5 | import ( 6 | "github.com/sagernet/sing-box/adapter" 7 | "github.com/sagernet/sing-box/experimental" 8 | "github.com/sagernet/sing-box/log" 9 | "github.com/sagernet/sing-box/option" 10 | E "github.com/sagernet/sing/common/exceptions" 11 | ) 12 | 13 | func init() { 14 | experimental.RegisterV2RayServerConstructor(func(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) { 15 | return nil, E.New(`v2ray api is not included in this build, rebuild with -tags with_v2ray_api`) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /experimental/clashapi/errors.go: -------------------------------------------------------------------------------- 1 | package clashapi 2 | 3 | var ( 4 | ErrUnauthorized = newError("Unauthorized") 5 | ErrBadRequest = newError("Body invalid") 6 | ErrForbidden = newError("Forbidden") 7 | ErrNotFound = newError("Resource not found") 8 | ErrRequestTimeout = newError("Timeout") 9 | ) 10 | 11 | // HTTPError is custom HTTP error for API 12 | type HTTPError struct { 13 | Message string `json:"message"` 14 | } 15 | 16 | func (e *HTTPError) Error() string { 17 | return e.Message 18 | } 19 | 20 | func newError(msg string) *HTTPError { 21 | return &HTTPError{Message: msg} 22 | } 23 | -------------------------------------------------------------------------------- /log/factory.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "github.com/sagernet/sing/common/logger" 5 | "github.com/sagernet/sing/common/observable" 6 | ) 7 | 8 | type ( 9 | Logger logger.Logger 10 | ContextLogger logger.ContextLogger 11 | ) 12 | 13 | type Factory interface { 14 | Start() error 15 | Close() error 16 | Level() Level 17 | SetLevel(level Level) 18 | Logger() ContextLogger 19 | NewLogger(tag string) ContextLogger 20 | } 21 | 22 | type ObservableFactory interface { 23 | Factory 24 | observable.Observable[Entry] 25 | } 26 | 27 | type Entry struct { 28 | Level Level 29 | Message string 30 | } 31 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "commitMessagePrefix": "[dependencies]", 4 | "extends": [ 5 | "config:base", 6 | ":disableRateLimiting" 7 | ], 8 | "baseBranches": [ 9 | "dev-next" 10 | ], 11 | "golang": { 12 | "enabled": false 13 | }, 14 | "packageRules": [ 15 | { 16 | "matchManagers": [ 17 | "github-actions" 18 | ], 19 | "groupName": "github-actions" 20 | }, 21 | { 22 | "matchManagers": [ 23 | "dockerfile" 24 | ], 25 | "groupName": "Dockerfile" 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /docs/configuration/inbound/naive.zh.md: -------------------------------------------------------------------------------- 1 | ### 结构 2 | 3 | ```json 4 | { 5 | "type": "naive", 6 | "tag": "naive-in", 7 | "network": "udp", 8 | 9 | ... // 监听字段 10 | 11 | "users": [ 12 | { 13 | "username": "sekai", 14 | "password": "password" 15 | } 16 | ], 17 | "tls": {} 18 | } 19 | ``` 20 | 21 | ### 监听字段 22 | 23 | 参阅 [监听字段](/zh/configuration/shared/listen/)。 24 | 25 | ### 字段 26 | 27 | #### network 28 | 29 | 监听的网络协议,`tcp` `udp` 之一。 30 | 31 | 默认所有。 32 | 33 | #### users 34 | 35 | ==必填== 36 | 37 | Naive 用户。 38 | 39 | #### tls 40 | 41 | TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 -------------------------------------------------------------------------------- /release/config/sing-box.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=sing-box service 3 | Documentation=https://sing-box.sagernet.org 4 | After=network.target nss-lookup.target network-online.target 5 | 6 | [Service] 7 | CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH 8 | AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH 9 | ExecStart=/usr/bin/sing-box -D /var/lib/sing-box -C /etc/sing-box run 10 | ExecReload=/bin/kill -HUP $MAINPID 11 | Restart=on-failure 12 | RestartSec=10s 13 | LimitNOFILE=infinity 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /docs/configuration/outbound/direct.zh.md: -------------------------------------------------------------------------------- 1 | `direct` 出站直接发送请求。 2 | 3 | ### 结构 4 | 5 | ```json 6 | { 7 | "type": "direct", 8 | "tag": "direct-out", 9 | 10 | "override_address": "1.0.0.1", 11 | "override_port": 53, 12 | "proxy_protocol": 0, 13 | 14 | ... // 拨号字段 15 | } 16 | ``` 17 | 18 | ### 字段 19 | 20 | #### override_address 21 | 22 | 覆盖连接目标地址。 23 | 24 | #### override_port 25 | 26 | 覆盖连接目标端口。 27 | 28 | #### proxy_protocol 29 | 30 | 写出 [代理协议](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) 到连接头。 31 | 32 | 可用协议版本值:`1` 或 `2`。 33 | 34 | ### 拨号字段 35 | 36 | 参阅 [拨号字段](/zh/configuration/shared/dial/)。 37 | -------------------------------------------------------------------------------- /experimental/libbox/tun_name_linux.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | import ( 4 | "fmt" 5 | "syscall" 6 | "unsafe" 7 | 8 | "golang.org/x/sys/unix" 9 | ) 10 | 11 | const ifReqSize = unix.IFNAMSIZ + 64 12 | 13 | func getTunnelName(fd int32) (string, error) { 14 | var ifr [ifReqSize]byte 15 | var errno syscall.Errno 16 | _, _, errno = unix.Syscall( 17 | unix.SYS_IOCTL, 18 | uintptr(fd), 19 | uintptr(unix.TUNGETIFF), 20 | uintptr(unsafe.Pointer(&ifr[0])), 21 | ) 22 | if errno != 0 { 23 | return "", fmt.Errorf("failed to get name of TUN device: %w", errno) 24 | } 25 | return unix.ByteSliceToString(ifr[:]), nil 26 | } 27 | -------------------------------------------------------------------------------- /docs/configuration/experimental/index.zh.md: -------------------------------------------------------------------------------- 1 | # 实验性 2 | 3 | !!! quote "sing-box 1.8.0 中的更改" 4 | 5 | :material-plus: [cache_file](#cache_file) 6 | :material-alert-decagram: [clash_api](#clash_api) 7 | 8 | ### 结构 9 | 10 | ```json 11 | { 12 | "experimental": { 13 | "cache_file": {}, 14 | "clash_api": {}, 15 | "v2ray_api": {} 16 | } 17 | } 18 | ``` 19 | 20 | ### 字段 21 | 22 | | 键 | 格式 | 23 | |--------------|--------------------------| 24 | | `cache_file` | [缓存文件](./cache-file/) | 25 | | `clash_api` | [Clash API](./clash-api/) | 26 | | `v2ray_api` | [V2Ray API](./v2ray-api/) | -------------------------------------------------------------------------------- /release/config/sing-box@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=sing-box service 3 | Documentation=https://sing-box.sagernet.org 4 | After=network.target nss-lookup.target network-online.target 5 | 6 | [Service] 7 | CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH 8 | AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH 9 | ExecStart=/usr/bin/sing-box -D /var/lib/sing-box-%i -c /etc/sing-box/%i.json run 10 | ExecReload=/bin/kill -HUP $MAINPID 11 | Restart=on-failure 12 | RestartSec=10s 13 | LimitNOFILE=infinity 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /common/sniff/http.go: -------------------------------------------------------------------------------- 1 | package sniff 2 | 3 | import ( 4 | std_bufio "bufio" 5 | "context" 6 | "io" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | C "github.com/sagernet/sing-box/constant" 10 | M "github.com/sagernet/sing/common/metadata" 11 | "github.com/sagernet/sing/protocol/http" 12 | ) 13 | 14 | func HTTPHost(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error) { 15 | request, err := http.ReadRequest(std_bufio.NewReader(reader)) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return &adapter.InboundContext{Protocol: C.ProtocolHTTP, Domain: M.ParseSocksaddr(request.Host).AddrString()}, nil 20 | } 21 | -------------------------------------------------------------------------------- /release/local/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e -o pipefail 4 | 5 | if [ -d /usr/local/go ]; then 6 | export PATH="$PATH:/usr/local/go/bin" 7 | fi 8 | 9 | DIR=$(dirname "$0") 10 | PROJECT=$DIR/../.. 11 | 12 | pushd $PROJECT 13 | go install -v -trimpath -ldflags "-s -w -buildid=" -tags with_quic,with_wireguard,with_acme ./cmd/sing-box 14 | popd 15 | 16 | sudo cp $(go env GOPATH)/bin/sing-box /usr/local/bin/ 17 | sudo mkdir -p /usr/local/etc/sing-box 18 | sudo cp $PROJECT/release/config/config.json /usr/local/etc/sing-box/config.json 19 | sudo cp $DIR/sing-box.service /etc/systemd/system 20 | sudo systemctl daemon-reload 21 | -------------------------------------------------------------------------------- /release/local/sing-box.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=sing-box service 3 | Documentation=https://sing-box.sagernet.org 4 | After=network.target nss-lookup.target network-online.target 5 | 6 | [Service] 7 | CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH 8 | AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH 9 | ExecStart=/usr/local/bin/sing-box -D /var/lib/sing-box -C /usr/local/etc/sing-box run 10 | ExecReload=/bin/kill -HUP $MAINPID 11 | Restart=on-failure 12 | RestartSec=10s 13 | LimitNOFILE=infinity 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /docs/clients/index.zh.md: -------------------------------------------------------------------------------- 1 | # :material-cellphone-link: 图形界面客户端 2 | 3 | 由 Project S 维护,提供统一的体验与平台特定的功能。 4 | 5 | | 平台 | 客户端 | 6 | |---------------------------------------|-----------------------------------------| 7 | | :material-android: Android | [sing-box for Android](./android/) | 8 | | :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) | 9 | | :material-laptop: Desktop | 施工中 | 10 | 11 | 此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业 12 | VPN 客户端功能, 但代码质量很差且包含广告。 13 | -------------------------------------------------------------------------------- /experimental/libbox/memory.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | import ( 4 | "math" 5 | runtimeDebug "runtime/debug" 6 | 7 | "github.com/sagernet/sing-box/common/conntrack" 8 | ) 9 | 10 | func SetMemoryLimit(enabled bool) { 11 | const memoryLimit = 45 * 1024 * 1024 12 | const memoryLimitGo = memoryLimit / 1.5 13 | if enabled { 14 | runtimeDebug.SetGCPercent(10) 15 | runtimeDebug.SetMemoryLimit(memoryLimitGo) 16 | conntrack.KillerEnabled = true 17 | conntrack.MemoryLimit = memoryLimit 18 | } else { 19 | runtimeDebug.SetGCPercent(100) 20 | runtimeDebug.SetMemoryLimit(math.MaxInt64) 21 | conntrack.KillerEnabled = false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /docs/configuration/outbound/selector.zh.md: -------------------------------------------------------------------------------- 1 | ### 结构 2 | 3 | ```json 4 | { 5 | "type": "selector", 6 | "tag": "select", 7 | 8 | "outbounds": [ 9 | "proxy-a", 10 | "proxy-b", 11 | "proxy-c" 12 | ], 13 | "default": "proxy-c", 14 | "interrupt_exist_connections": false 15 | } 16 | ``` 17 | 18 | !!! quote "" 19 | 20 | 选择器目前只能通过 [Clash API](/zh/configuration/experimental#clash-api) 来控制。 21 | 22 | ### 字段 23 | 24 | #### outbounds 25 | 26 | ==必填== 27 | 28 | 用于选择的出站标签列表。 29 | 30 | #### default 31 | 32 | 默认的出站标签。默认使用第一个出站。 33 | 34 | #### interrupt_exist_connections 35 | 36 | 当选定的出站发生更改时,中断现有连接。 37 | 38 | 仅入站连接受此设置影响,内部连接将始终被中断。 -------------------------------------------------------------------------------- /experimental/libbox/pprof.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | _ "net/http/pprof" 7 | "strconv" 8 | ) 9 | 10 | type PProfServer struct { 11 | server *http.Server 12 | } 13 | 14 | func NewPProfServer(port int) *PProfServer { 15 | return &PProfServer{ 16 | &http.Server{ 17 | Addr: ":" + strconv.Itoa(port), 18 | }, 19 | } 20 | } 21 | 22 | func (s *PProfServer) Start() error { 23 | ln, err := net.Listen("tcp", s.server.Addr) 24 | if err != nil { 25 | return err 26 | } 27 | go s.server.Serve(ln) 28 | return nil 29 | } 30 | 31 | func (s *PProfServer) Close() error { 32 | return s.server.Close() 33 | } 34 | -------------------------------------------------------------------------------- /cmd/sing-box/cmd_geoip_list.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sagernet/sing-box/log" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var commandGeoipList = &cobra.Command{ 12 | Use: "list", 13 | Short: "List geoip country codes", 14 | Run: func(cmd *cobra.Command, args []string) { 15 | err := listGeoip() 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | }, 20 | } 21 | 22 | func init() { 23 | commandGeoip.AddCommand(commandGeoipList) 24 | } 25 | 26 | func listGeoip() error { 27 | for _, code := range geoipReader.Metadata.Languages { 28 | os.Stdout.WriteString(code + "\n") 29 | } 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /docs/support.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/forum 3 | --- 4 | 5 | # Support 6 | 7 | | Channel | Link | 8 | |:------------------------------|:--------------------------------------------| 9 | | Community | https://community.sagernet.org | 10 | | GitHub Issues | https://github.com/SagerNet/sing-box/issues | 11 | | Telegram notification channel | https://t.me/yapnc | 12 | | Telegram user group | https://t.me/yapug | 13 | | Email | contact@sagernet.org | 14 | -------------------------------------------------------------------------------- /log/id.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "context" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/sagernet/sing/common/random" 9 | ) 10 | 11 | func init() { 12 | random.InitializeSeed() 13 | } 14 | 15 | type idKey struct{} 16 | 17 | type ID struct { 18 | ID uint32 19 | CreatedAt time.Time 20 | } 21 | 22 | func ContextWithNewID(ctx context.Context) context.Context { 23 | return context.WithValue(ctx, (*idKey)(nil), ID{ 24 | ID: rand.Uint32(), 25 | CreatedAt: time.Now(), 26 | }) 27 | } 28 | 29 | func IDFromContext(ctx context.Context) (ID, bool) { 30 | id, loaded := ctx.Value((*idKey)(nil)).(ID) 31 | return id, loaded 32 | } 33 | -------------------------------------------------------------------------------- /docs/configuration/experimental/index.md: -------------------------------------------------------------------------------- 1 | # Experimental 2 | 3 | !!! quote "Changes in sing-box 1.8.0" 4 | 5 | :material-plus: [cache_file](#cache_file) 6 | :material-alert-decagram: [clash_api](#clash_api) 7 | 8 | ### Structure 9 | 10 | ```json 11 | { 12 | "experimental": { 13 | "cache_file": {}, 14 | "clash_api": {}, 15 | "v2ray_api": {} 16 | } 17 | } 18 | ``` 19 | 20 | ### Fields 21 | 22 | | Key | Format | 23 | |--------------|----------------------------| 24 | | `cache_file` | [Cache File](./cache-file/) | 25 | | `clash_api` | [Clash API](./clash-api/) | 26 | | `v2ray_api` | [V2Ray API](./v2ray-api/) | -------------------------------------------------------------------------------- /docs/configuration/route/sniff.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/new-box 3 | --- 4 | 5 | !!! quote "sing-box 1.10.0 中的更改" 6 | 7 | :material-plus: BitTorrent 支持 8 | :material-plus: DTLS 支持 9 | 10 | 如果在入站中启用,则可以嗅探连接的协议和域名(如果存在)。 11 | 12 | #### 支持的协议 13 | 14 | | 网络 | 协议 | 域名 | 15 | |:-------:|:------------:|:-----------:| 16 | | TCP | `http` | Host | 17 | | TCP | `tls` | Server Name | 18 | | UDP | `quic` | Server Name | 19 | | UDP | `stun` | / | 20 | | TCP/UDP | `dns` | / | 21 | | TCP/UDP | `bittorrent` | / | 22 | | UDP | `dtls` | / | 23 | -------------------------------------------------------------------------------- /common/badversion/version_test.go: -------------------------------------------------------------------------------- 1 | package badversion 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestCompareVersion(t *testing.T) { 10 | t.Parallel() 11 | require.Equal(t, "1.3.0-beta.1", Parse("v1.3.0-beta1").String()) 12 | require.Equal(t, "1.3-beta1", Parse("v1.3.0-beta.1").BadString()) 13 | require.True(t, Parse("1.3.0").After(Parse("1.3-beta1"))) 14 | require.True(t, Parse("1.3.0").After(Parse("1.3.0-beta1"))) 15 | require.True(t, Parse("1.3.0-beta1").After(Parse("1.3.0-alpha1"))) 16 | require.True(t, Parse("1.3.1").After(Parse("1.3.0"))) 17 | require.True(t, Parse("1.4").After(Parse("1.3"))) 18 | } 19 | -------------------------------------------------------------------------------- /common/sniff/stun.go: -------------------------------------------------------------------------------- 1 | package sniff 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "os" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | C "github.com/sagernet/sing-box/constant" 10 | ) 11 | 12 | func STUNMessage(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { 13 | pLen := len(packet) 14 | if pLen < 20 { 15 | return nil, os.ErrInvalid 16 | } 17 | if binary.BigEndian.Uint32(packet[4:8]) != 0x2112A442 { 18 | return nil, os.ErrInvalid 19 | } 20 | if len(packet) < 20+int(binary.BigEndian.Uint16(packet[2:4])) { 21 | return nil, os.ErrInvalid 22 | } 23 | return &adapter.InboundContext{Protocol: C.ProtocolSTUN}, nil 24 | } 25 | -------------------------------------------------------------------------------- /include/clashapi_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_clash_api 2 | 3 | package include 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | "github.com/sagernet/sing-box/experimental" 10 | "github.com/sagernet/sing-box/log" 11 | "github.com/sagernet/sing-box/option" 12 | E "github.com/sagernet/sing/common/exceptions" 13 | ) 14 | 15 | func init() { 16 | experimental.RegisterClashServerConstructor(func(ctx context.Context, router adapter.Router, logFactory log.ObservableFactory, options option.ClashAPIOptions) (adapter.ClashServer, error) { 17 | return nil, E.New(`clash api is not included in this build, rebuild with -tags with_clash_api`) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /include/tz_android.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // kanged from https://github.com/golang/mobile/blob/c713f31d574bb632a93f169b2cc99c9e753fef0e/app/android.go#L89 6 | 7 | package include 8 | 9 | // #include 10 | import "C" 11 | import "time" 12 | 13 | func init() { 14 | var currentT C.time_t 15 | var currentTM C.struct_tm 16 | C.time(¤tT) 17 | C.localtime_r(¤tT, ¤tTM) 18 | tzOffset := int(currentTM.tm_gmtoff) 19 | tz := C.GoString(currentTM.tm_zone) 20 | time.Local = time.FixedZone(tz, tzOffset) 21 | } 22 | -------------------------------------------------------------------------------- /common/tls/utls_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_utls 2 | 3 | package tls 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/option" 9 | E "github.com/sagernet/sing/common/exceptions" 10 | ) 11 | 12 | func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { 13 | return nil, E.New(`uTLS is not included in this build, rebuild with -tags with_utls`) 14 | } 15 | 16 | func NewRealityClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { 17 | return nil, E.New(`uTLS, which is required by reality client is not included in this build, rebuild with -tags with_utls`) 18 | } 19 | -------------------------------------------------------------------------------- /docs/installation/docker.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/docker 3 | --- 4 | 5 | # Docker 6 | 7 | ## :material-console: 命令 8 | 9 | ```bash 10 | docker run -d \ 11 | -v /etc/sing-box:/etc/sing-box/ \ 12 | --name=sing-box \ 13 | --restart=always \ 14 | ghcr.io/sagernet/sing-box \ 15 | -D /var/lib/sing-box \ 16 | -C /etc/sing-box/ run 17 | ``` 18 | 19 | ## :material-box-shadow: Compose 20 | 21 | ```yaml 22 | version: "3.8" 23 | services: 24 | sing-box: 25 | image: ghcr.io/sagernet/sing-box 26 | container_name: sing-box 27 | restart: always 28 | volumes: 29 | - /etc/sing-box:/etc/sing-box/ 30 | command: -D /var/lib/sing-box -C /etc/sing-box/ run 31 | ``` 32 | -------------------------------------------------------------------------------- /docs/installation/docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/docker 3 | --- 4 | 5 | # Docker 6 | 7 | ## :material-console: Command 8 | 9 | ```bash 10 | docker run -d \ 11 | -v /etc/sing-box:/etc/sing-box/ \ 12 | --name=sing-box \ 13 | --restart=always \ 14 | ghcr.io/sagernet/sing-box \ 15 | -D /var/lib/sing-box \ 16 | -C /etc/sing-box/ run 17 | ``` 18 | 19 | ## :material-box-shadow: Compose 20 | 21 | ```yaml 22 | version: "3.8" 23 | services: 24 | sing-box: 25 | image: ghcr.io/sagernet/sing-box 26 | container_name: sing-box 27 | restart: always 28 | volumes: 29 | - /etc/sing-box:/etc/sing-box/ 30 | command: -D /var/lib/sing-box -C /etc/sing-box/ run 31 | ``` 32 | -------------------------------------------------------------------------------- /experimental/libbox/command_conntrack.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | import ( 4 | "encoding/binary" 5 | "net" 6 | runtimeDebug "runtime/debug" 7 | "time" 8 | 9 | "github.com/sagernet/sing-box/common/conntrack" 10 | ) 11 | 12 | func (c *CommandClient) CloseConnections() error { 13 | conn, err := c.directConnect() 14 | if err != nil { 15 | return err 16 | } 17 | defer conn.Close() 18 | return binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnections)) 19 | } 20 | 21 | func (s *CommandServer) handleCloseConnections(conn net.Conn) error { 22 | conntrack.Close() 23 | go func() { 24 | time.Sleep(time.Second) 25 | runtimeDebug.FreeOSMemory() 26 | }() 27 | return nil 28 | } 29 | -------------------------------------------------------------------------------- /docs/configuration/inbound/direct.md: -------------------------------------------------------------------------------- 1 | `direct` inbound is a tunnel server. 2 | 3 | ### Structure 4 | 5 | ```json 6 | { 7 | "type": "direct", 8 | "tag": "direct-in", 9 | 10 | ... // Listen Fields 11 | 12 | "network": "udp", 13 | "override_address": "1.0.0.1", 14 | "override_port": 53 15 | } 16 | ``` 17 | 18 | ### Listen Fields 19 | 20 | See [Listen Fields](/configuration/shared/listen/) for details. 21 | 22 | ### Fields 23 | 24 | #### network 25 | 26 | Listen network, one of `tcp` `udp`. 27 | 28 | Both if empty. 29 | 30 | #### override_address 31 | 32 | Override the connection destination address. 33 | 34 | #### override_port 35 | 36 | Override the connection destination port. -------------------------------------------------------------------------------- /docs/configuration/ntp/index.zh.md: -------------------------------------------------------------------------------- 1 | # NTP 2 | 3 | 内建的 NTP 客户端服务。 4 | 5 | 如果启用,它将为像 TLS/Shadowsocks/VMess 这样的协议提供时间,这对于无法进行时间同步的环境很有用。 6 | 7 | ### 结构 8 | 9 | ```json 10 | { 11 | "ntp": { 12 | "enabled": false, 13 | "server": "time.apple.com", 14 | "server_port": 123, 15 | "interval": "30m", 16 | 17 | ... // 拨号字段 18 | } 19 | } 20 | 21 | ``` 22 | 23 | ### 字段 24 | 25 | #### enabled 26 | 27 | 启用 NTP 服务。 28 | 29 | #### server 30 | 31 | ==必填== 32 | 33 | NTP 服务器地址。 34 | 35 | #### server_port 36 | 37 | NTP 服务器端口。 38 | 39 | 默认使用 123。 40 | 41 | #### interval 42 | 43 | 时间同步间隔。 44 | 45 | 默认使用 30 分钟。 46 | 47 | ### 拨号字段 48 | 49 | 参阅 [拨号字段](/zh/configuration/shared/dial/)。 50 | -------------------------------------------------------------------------------- /docs/configuration/inbound/naive.md: -------------------------------------------------------------------------------- 1 | ### Structure 2 | 3 | ```json 4 | { 5 | "type": "naive", 6 | "tag": "naive-in", 7 | "network": "udp", 8 | 9 | ... // Listen Fields 10 | 11 | "users": [ 12 | { 13 | "username": "sekai", 14 | "password": "password" 15 | } 16 | ], 17 | "tls": {} 18 | } 19 | ``` 20 | 21 | ### Listen Fields 22 | 23 | See [Listen Fields](/configuration/shared/listen/) for details. 24 | 25 | ### Fields 26 | 27 | #### network 28 | 29 | Listen network, one of `tcp` `udp`. 30 | 31 | Both if empty. 32 | 33 | #### users 34 | 35 | ==Required== 36 | 37 | Naive users. 38 | 39 | #### tls 40 | 41 | TLS configuration, see [TLS](/configuration/shared/tls/#inbound). -------------------------------------------------------------------------------- /docs/configuration/route/geoip.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/delete-clock 3 | --- 4 | 5 | !!! failure "已在 sing-box 1.8.0 废弃" 6 | 7 | GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。 8 | 9 | ### 结构 10 | 11 | ```json 12 | { 13 | "route": { 14 | "geoip": { 15 | "path": "", 16 | "download_url": "", 17 | "download_detour": "" 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | ### 字段 24 | 25 | #### path 26 | 27 | 指定 GeoIP 资源的路径。 28 | 29 | 默认 `geoip.db`。 30 | 31 | #### download_url 32 | 33 | 指定 GeoIP 资源的下载链接。 34 | 35 | 默认为 `https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db`。 36 | 37 | #### download_detour 38 | 39 | 用于下载 GeoIP 资源的出站的标签。 40 | 41 | 如果为空,将使用默认出站。 -------------------------------------------------------------------------------- /option/ssh.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type SSHOutboundOptions struct { 4 | DialerOptions 5 | ServerOptions 6 | User string `json:"user,omitempty"` 7 | Password string `json:"password,omitempty"` 8 | PrivateKey Listable[string] `json:"private_key,omitempty"` 9 | PrivateKeyPath string `json:"private_key_path,omitempty"` 10 | PrivateKeyPassphrase string `json:"private_key_passphrase,omitempty"` 11 | HostKey Listable[string] `json:"host_key,omitempty"` 12 | HostKeyAlgorithms Listable[string] `json:"host_key_algorithms,omitempty"` 13 | ClientVersion string `json:"client_version,omitempty"` 14 | } 15 | -------------------------------------------------------------------------------- /common/taskmonitor/monitor.go: -------------------------------------------------------------------------------- 1 | package taskmonitor 2 | 3 | import ( 4 | "time" 5 | 6 | F "github.com/sagernet/sing/common/format" 7 | "github.com/sagernet/sing/common/logger" 8 | ) 9 | 10 | type Monitor struct { 11 | logger logger.Logger 12 | timeout time.Duration 13 | timer *time.Timer 14 | } 15 | 16 | func New(logger logger.Logger, timeout time.Duration) *Monitor { 17 | return &Monitor{ 18 | logger: logger, 19 | timeout: timeout, 20 | } 21 | } 22 | 23 | func (m *Monitor) Start(taskName ...any) { 24 | m.timer = time.AfterFunc(m.timeout, func() { 25 | m.logger.Warn(F.ToString(taskName...), " take too much time to finish!") 26 | }) 27 | } 28 | 29 | func (m *Monitor) Finish() { 30 | m.timer.Stop() 31 | } 32 | -------------------------------------------------------------------------------- /docs/configuration/route/geosite.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/delete-clock 3 | --- 4 | 5 | !!! failure "已在 sing-box 1.8.0 废弃" 6 | 7 | Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。 8 | 9 | ### 结构 10 | 11 | ```json 12 | { 13 | "route": { 14 | "geosite": { 15 | "path": "", 16 | "download_url": "", 17 | "download_detour": "" 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | ### 字段 24 | 25 | #### path 26 | 27 | 指定 GeoSite 资源的路径。 28 | 29 | 默认 `geosite.db`。 30 | 31 | #### download_url 32 | 33 | 指定 GeoSite 资源的下载链接。 34 | 35 | 默认为 `https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db`。 36 | 37 | #### download_detour 38 | 39 | 用于下载 GeoSite 资源的出站的标签。 40 | 41 | 如果为空,将使用默认出站。 -------------------------------------------------------------------------------- /inbound/hysteria_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_quic 2 | 3 | package inbound 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | C "github.com/sagernet/sing-box/constant" 10 | "github.com/sagernet/sing-box/log" 11 | "github.com/sagernet/sing-box/option" 12 | ) 13 | 14 | func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { 15 | return nil, C.ErrQUICNotIncluded 16 | } 17 | 18 | func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { 19 | return nil, C.ErrQUICNotIncluded 20 | } 21 | -------------------------------------------------------------------------------- /include/tz_ios.go: -------------------------------------------------------------------------------- 1 | package include 2 | 3 | /* 4 | #cgo CFLAGS: -x objective-c 5 | #cgo LDFLAGS: -framework Foundation 6 | #import 7 | const char* getSystemTimeZone() { 8 | NSTimeZone *timeZone = [NSTimeZone systemTimeZone]; 9 | NSString *timeZoneName = [timeZone description]; 10 | return [timeZoneName UTF8String]; 11 | } 12 | */ 13 | import "C" 14 | 15 | import ( 16 | "strings" 17 | "time" 18 | ) 19 | 20 | func init() { 21 | tzDescription := C.GoString(C.getSystemTimeZone()) 22 | if len(tzDescription) == 0 { 23 | return 24 | } 25 | location, err := time.LoadLocation(strings.Split(tzDescription, " ")[0]) 26 | if err != nil { 27 | return 28 | } 29 | time.Local = location 30 | } 31 | -------------------------------------------------------------------------------- /outbound/hysteria_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_quic 2 | 3 | package outbound 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | C "github.com/sagernet/sing-box/constant" 10 | "github.com/sagernet/sing-box/log" 11 | "github.com/sagernet/sing-box/option" 12 | ) 13 | 14 | func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { 15 | return nil, C.ErrQUICNotIncluded 16 | } 17 | 18 | func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { 19 | return nil, C.ErrQUICNotIncluded 20 | } 21 | -------------------------------------------------------------------------------- /transport/wireguard/endpoint.go: -------------------------------------------------------------------------------- 1 | package wireguard 2 | 3 | import ( 4 | "net/netip" 5 | 6 | "github.com/sagernet/wireguard-go/conn" 7 | ) 8 | 9 | var _ conn.Endpoint = (*Endpoint)(nil) 10 | 11 | type Endpoint netip.AddrPort 12 | 13 | func (e Endpoint) ClearSrc() { 14 | } 15 | 16 | func (e Endpoint) SrcToString() string { 17 | return "" 18 | } 19 | 20 | func (e Endpoint) DstToString() string { 21 | return (netip.AddrPort)(e).String() 22 | } 23 | 24 | func (e Endpoint) DstToBytes() []byte { 25 | b, _ := (netip.AddrPort)(e).MarshalBinary() 26 | return b 27 | } 28 | 29 | func (e Endpoint) DstIP() netip.Addr { 30 | return (netip.AddrPort)(e).Addr() 31 | } 32 | 33 | func (e Endpoint) SrcIP() netip.Addr { 34 | return netip.Addr{} 35 | } 36 | -------------------------------------------------------------------------------- /docs/configuration/inbound/mixed.zh.md: -------------------------------------------------------------------------------- 1 | `mixed` 入站是一个 socks4, socks4a, socks5 和 http 服务器. 2 | 3 | ### 结构 4 | 5 | ```json 6 | { 7 | "type": "mixed", 8 | "tag": "mixed-in", 9 | 10 | ... // 监听字段 11 | 12 | "users": [ 13 | { 14 | "username": "admin", 15 | "password": "admin" 16 | } 17 | ], 18 | "set_system_proxy": false 19 | } 20 | ``` 21 | 22 | ### 监听字段 23 | 24 | 参阅 [监听字段](/zh/configuration/shared/listen/)。 25 | 26 | ### 字段 27 | 28 | #### users 29 | 30 | SOCKS 和 HTTP 用户 31 | 32 | 如果为空则不需要验证。 33 | 34 | #### set_system_proxy 35 | 36 | !!! quote "" 37 | 38 | 仅支持 Linux、Android、Windows 和 macOS。 39 | 40 | !!! warning "" 41 | 42 | 要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。 43 | 44 | 启动时自动设置系统代理,停止时自动清理。 -------------------------------------------------------------------------------- /option/group.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type SelectorOutboundOptions struct { 4 | Outbounds []string `json:"outbounds"` 5 | Default string `json:"default,omitempty"` 6 | InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` 7 | } 8 | 9 | type URLTestOutboundOptions struct { 10 | Outbounds []string `json:"outbounds"` 11 | URL string `json:"url,omitempty"` 12 | Interval Duration `json:"interval,omitempty"` 13 | Tolerance uint16 `json:"tolerance,omitempty"` 14 | IdleTimeout Duration `json:"idle_timeout,omitempty"` 15 | InterruptExistConnections bool `json:"interrupt_exist_connections,omitempty"` 16 | } 17 | -------------------------------------------------------------------------------- /docs/configuration/route/sniff.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/new-box 3 | --- 4 | 5 | !!! quote "Changes in sing-box 1.10.0" 6 | 7 | :material-plus: BitTorrent support 8 | :material-plus: DTLS support 9 | 10 | If enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed. 11 | 12 | #### Supported Protocols 13 | 14 | | Network | Protocol | Domain Name | 15 | |:-------:|:------------:|:-----------:| 16 | | TCP | `http` | Host | 17 | | TCP | `tls` | Server Name | 18 | | UDP | `quic` | Server Name | 19 | | UDP | `stun` | / | 20 | | TCP/UDP | `dns` | / | 21 | | TCP/UDP | `bittorrent` | / | 22 | | UDP | `dtls` | / | 23 | -------------------------------------------------------------------------------- /common/conntrack/killer.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | runtimeDebug "runtime/debug" 5 | "time" 6 | 7 | E "github.com/sagernet/sing/common/exceptions" 8 | "github.com/sagernet/sing/common/memory" 9 | ) 10 | 11 | var ( 12 | KillerEnabled bool 13 | MemoryLimit uint64 14 | killerLastCheck time.Time 15 | ) 16 | 17 | func KillerCheck() error { 18 | if !KillerEnabled { 19 | return nil 20 | } 21 | nowTime := time.Now() 22 | if nowTime.Sub(killerLastCheck) < 3*time.Second { 23 | return nil 24 | } 25 | killerLastCheck = nowTime 26 | if memory.Total() > MemoryLimit { 27 | Close() 28 | go func() { 29 | time.Sleep(time.Second) 30 | runtimeDebug.FreeOSMemory() 31 | }() 32 | return E.New("out of memory") 33 | } 34 | return nil 35 | } 36 | -------------------------------------------------------------------------------- /experimental/libbox/service_error.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | func serviceErrorPath() string { 9 | return filepath.Join(sWorkingPath, "network_extension_error") 10 | } 11 | 12 | func ClearServiceError() { 13 | os.Remove(serviceErrorPath()) 14 | } 15 | 16 | func ReadServiceError() (string, error) { 17 | data, err := os.ReadFile(serviceErrorPath()) 18 | if err == nil { 19 | os.Remove(serviceErrorPath()) 20 | } 21 | return string(data), err 22 | } 23 | 24 | func WriteServiceError(message string) error { 25 | errorFile, err := os.Create(serviceErrorPath()) 26 | if err != nil { 27 | return err 28 | } 29 | errorFile.WriteString(message) 30 | errorFile.Chown(sUserID, sGroupID) 31 | return errorFile.Close() 32 | } 33 | -------------------------------------------------------------------------------- /experimental/v2rayapi.go: -------------------------------------------------------------------------------- 1 | package experimental 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | "github.com/sagernet/sing-box/log" 8 | "github.com/sagernet/sing-box/option" 9 | ) 10 | 11 | type V2RayServerConstructor = func(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) 12 | 13 | var v2rayServerConstructor V2RayServerConstructor 14 | 15 | func RegisterV2RayServerConstructor(constructor V2RayServerConstructor) { 16 | v2rayServerConstructor = constructor 17 | } 18 | 19 | func NewV2RayServer(logger log.Logger, options option.V2RayAPIOptions) (adapter.V2RayServer, error) { 20 | if v2rayServerConstructor == nil { 21 | return nil, os.ErrInvalid 22 | } 23 | return v2rayServerConstructor(logger, options) 24 | } 25 | -------------------------------------------------------------------------------- /docs/configuration/experimental/v2ray-api.zh.md: -------------------------------------------------------------------------------- 1 | !!! quote "" 2 | 3 | 默认安装不包含 V2Ray API,参阅 [安装](/zh/installation/build-from-source/#_5)。 4 | 5 | ### 结构 6 | 7 | ```json 8 | { 9 | "listen": "127.0.0.1:8080", 10 | "stats": { 11 | "enabled": true, 12 | "inbounds": [ 13 | "socks-in" 14 | ], 15 | "outbounds": [ 16 | "proxy", 17 | "direct" 18 | ], 19 | "users": [ 20 | "sekai" 21 | ] 22 | } 23 | } 24 | ``` 25 | 26 | ### 字段 27 | 28 | #### listen 29 | 30 | gRPC API 监听地址。如果为空,则禁用 V2Ray API。 31 | 32 | #### stats 33 | 34 | 流量统计服务设置。 35 | 36 | #### stats.enabled 37 | 38 | 启用统计服务。 39 | 40 | #### stats.inbounds 41 | 42 | 统计流量的入站列表。 43 | 44 | #### stats.outbounds 45 | 46 | 统计流量的出站列表。 47 | 48 | #### stats.users 49 | 50 | 统计流量的用户列表。 -------------------------------------------------------------------------------- /experimental/libbox/service_pause.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type servicePauseFields struct { 9 | pauseAccess sync.Mutex 10 | pauseTimer *time.Timer 11 | } 12 | 13 | func (s *BoxService) Pause() { 14 | s.pauseAccess.Lock() 15 | defer s.pauseAccess.Unlock() 16 | if s.pauseTimer != nil { 17 | s.pauseTimer.Stop() 18 | } 19 | s.pauseTimer = time.AfterFunc(3*time.Second, s.ResetNetwork) 20 | } 21 | 22 | func (s *BoxService) Wake() { 23 | s.pauseAccess.Lock() 24 | defer s.pauseAccess.Unlock() 25 | if s.pauseTimer != nil { 26 | s.pauseTimer.Stop() 27 | } 28 | s.pauseTimer = time.AfterFunc(3*time.Minute, s.ResetNetwork) 29 | } 30 | 31 | func (s *BoxService) ResetNetwork() { 32 | _ = s.instance.Router().ResetNetwork() 33 | } 34 | -------------------------------------------------------------------------------- /experimental/libbox/tun_darwin.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | import ( 4 | "golang.org/x/sys/unix" 5 | ) 6 | 7 | // kanged from wireauard-apple 8 | 9 | const utunControlName = "com.apple.net.utun_control" 10 | 11 | func GetTunnelFileDescriptor() int32 { 12 | ctlInfo := &unix.CtlInfo{} 13 | copy(ctlInfo.Name[:], utunControlName) 14 | for fd := 0; fd < 1024; fd++ { 15 | addr, err := unix.Getpeername(fd) 16 | if err != nil { 17 | continue 18 | } 19 | addrCTL, loaded := addr.(*unix.SockaddrCtl) 20 | if !loaded { 21 | continue 22 | } 23 | if ctlInfo.Id == 0 { 24 | err = unix.IoctlCtlInfo(fd, ctlInfo) 25 | if err != nil { 26 | continue 27 | } 28 | } 29 | if addrCTL.ID == ctlInfo.Id { 30 | return int32(fd) 31 | } 32 | } 33 | return -1 34 | } 35 | -------------------------------------------------------------------------------- /route/rule_item_clash_mode.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | ) 8 | 9 | var _ RuleItem = (*ClashModeItem)(nil) 10 | 11 | type ClashModeItem struct { 12 | router adapter.Router 13 | mode string 14 | } 15 | 16 | func NewClashModeItem(router adapter.Router, mode string) *ClashModeItem { 17 | return &ClashModeItem{ 18 | router: router, 19 | mode: mode, 20 | } 21 | } 22 | 23 | func (r *ClashModeItem) Match(metadata *adapter.InboundContext) bool { 24 | clashServer := r.router.ClashServer() 25 | if clashServer == nil { 26 | return false 27 | } 28 | return strings.EqualFold(clashServer.Mode(), r.mode) 29 | } 30 | 31 | func (r *ClashModeItem) String() string { 32 | return "clash_mode=" + r.mode 33 | } 34 | -------------------------------------------------------------------------------- /route/rule_item_ipversion.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "github.com/sagernet/sing-box/adapter" 5 | ) 6 | 7 | var _ RuleItem = (*IPVersionItem)(nil) 8 | 9 | type IPVersionItem struct { 10 | isIPv6 bool 11 | } 12 | 13 | func NewIPVersionItem(isIPv6 bool) *IPVersionItem { 14 | return &IPVersionItem{isIPv6} 15 | } 16 | 17 | func (r *IPVersionItem) Match(metadata *adapter.InboundContext) bool { 18 | return metadata.IPVersion != 0 && metadata.IPVersion == 6 == r.isIPv6 || 19 | metadata.Destination.IsIP() && metadata.Destination.IsIPv6() == r.isIPv6 20 | } 21 | 22 | func (r *IPVersionItem) String() string { 23 | var versionStr string 24 | if r.isIPv6 { 25 | versionStr = "6" 26 | } else { 27 | versionStr = "4" 28 | } 29 | return "ip_version=" + versionStr 30 | } 31 | -------------------------------------------------------------------------------- /test/config/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes auto; 3 | 4 | error_log /var/log/nginx/error.log notice; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | include /etc/nginx/mime.types; 13 | default_type application/octet-stream; 14 | 15 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 16 | '$status $body_bytes_sent "$http_referer" ' 17 | '"$http_user_agent" "$http_x_forwarded_for"'; 18 | 19 | access_log /var/log/nginx/access.log main; 20 | 21 | sendfile on; 22 | #tcp_nopush on; 23 | 24 | keepalive_timeout 65; 25 | 26 | #gzip on; 27 | } 28 | 29 | include /etc/nginx/conf.d/naive.conf; -------------------------------------------------------------------------------- /docs/configuration/inbound/http.zh.md: -------------------------------------------------------------------------------- 1 | ### 结构 2 | 3 | ```json 4 | { 5 | "type": "http", 6 | "tag": "http-in", 7 | 8 | ... // 监听字段 9 | 10 | "users": [ 11 | { 12 | "username": "admin", 13 | "password": "admin" 14 | } 15 | ], 16 | "tls": {}, 17 | "set_system_proxy": false 18 | } 19 | ``` 20 | 21 | ### 监听字段 22 | 23 | 参阅 [监听字段](/zh/configuration/shared/listen/)。 24 | 25 | ### 字段 26 | 27 | #### tls 28 | 29 | TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 30 | 31 | #### users 32 | 33 | HTTP 用户 34 | 35 | 如果为空则不需要验证。 36 | 37 | #### set_system_proxy 38 | 39 | !!! quote "" 40 | 41 | 仅支持 Linux、Android、Windows 和 macOS。 42 | 43 | !!! warning "" 44 | 45 | 要在无特权的 Android 和 iOS 上工作,请改用 tun.platform.http_proxy。 46 | 47 | 启动时自动设置系统代理,停止时自动清理。 -------------------------------------------------------------------------------- /common/sniff/tls.go: -------------------------------------------------------------------------------- 1 | package sniff 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "io" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | C "github.com/sagernet/sing-box/constant" 10 | "github.com/sagernet/sing/common/bufio" 11 | ) 12 | 13 | func TLSClientHello(ctx context.Context, reader io.Reader) (*adapter.InboundContext, error) { 14 | var clientHello *tls.ClientHelloInfo 15 | err := tls.Server(bufio.NewReadOnlyConn(reader), &tls.Config{ 16 | GetConfigForClient: func(argHello *tls.ClientHelloInfo) (*tls.Config, error) { 17 | clientHello = argHello 18 | return nil, nil 19 | }, 20 | }).HandshakeContext(ctx) 21 | if clientHello != nil { 22 | return &adapter.InboundContext{Protocol: C.ProtocolTLS, Domain: clientHello.ServerName}, nil 23 | } 24 | return nil, err 25 | } 26 | -------------------------------------------------------------------------------- /constant/timeout.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import "time" 4 | 5 | const ( 6 | TCPKeepAliveInitial = 10 * time.Minute 7 | TCPKeepAliveInterval = 75 * time.Second 8 | TCPTimeout = 5 * time.Second 9 | ReadPayloadTimeout = 300 * time.Millisecond 10 | DNSTimeout = 10 * time.Second 11 | QUICTimeout = 30 * time.Second 12 | STUNTimeout = 15 * time.Second 13 | UDPTimeout = 5 * time.Minute 14 | DefaultURLTestInterval = 3 * time.Minute 15 | DefaultURLTestIdleTimeout = 30 * time.Minute 16 | StartTimeout = 10 * time.Second 17 | StopTimeout = 5 * time.Second 18 | FatalStopTimeout = 10 * time.Second 19 | FakeIPMetadataSaveInterval = 10 * time.Second 20 | ) 21 | -------------------------------------------------------------------------------- /docs/configuration/outbound/urltest.zh.md: -------------------------------------------------------------------------------- 1 | ### 结构 2 | 3 | ```json 4 | { 5 | "type": "urltest", 6 | "tag": "auto", 7 | 8 | "outbounds": [ 9 | "proxy-a", 10 | "proxy-b", 11 | "proxy-c" 12 | ], 13 | "url": "", 14 | "interval": "", 15 | "tolerance": 50, 16 | "idle_timeout": "", 17 | "interrupt_exist_connections": false 18 | } 19 | ``` 20 | 21 | ### 字段 22 | 23 | #### outbounds 24 | 25 | ==必填== 26 | 27 | 用于测试的出站标签列表。 28 | 29 | #### url 30 | 31 | 用于测试的链接。默认使用 `https://www.gstatic.com/generate_204`。 32 | 33 | #### interval 34 | 35 | 测试间隔。 默认使用 `3m`。 36 | 37 | #### tolerance 38 | 39 | 以毫秒为单位的测试容差。 默认使用 `50`。 40 | 41 | #### idle_timeout 42 | 43 | 空闲超时。默认使用 `30m`。 44 | 45 | #### interrupt_exist_connections 46 | 47 | 当选定的出站发生更改时,中断现有连接。 48 | 49 | 仅入站连接受此设置影响,内部连接将始终被中断。 -------------------------------------------------------------------------------- /docs/installation/tools/rpm-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -o pipefail 4 | 5 | ARCH_RAW=$(uname -m) 6 | case "${ARCH_RAW}" in 7 | 'x86_64') ARCH='amd64';; 8 | 'x86' | 'i686' | 'i386') ARCH='386';; 9 | 'aarch64' | 'arm64') ARCH='arm64';; 10 | 'armv7l') ARCH='armv7';; 11 | 's390x') ARCH='s390x';; 12 | *) echo "Unsupported architecture: ${ARCH_RAW}"; exit 1;; 13 | esac 14 | 15 | VERSION=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest \ 16 | | grep tag_name \ 17 | | cut -d ":" -f2 \ 18 | | sed 's/\"//g;s/\,//g;s/\ //g;s/v//') 19 | 20 | curl -Lo sing-box.rpm "https://github.com/SagerNet/sing-box/releases/download/v${VERSION}/sing-box_${VERSION}_linux_${ARCH}.rpm" 21 | sudo rpm -i sing-box.rpm 22 | rm sing-box.rpm 23 | -------------------------------------------------------------------------------- /release/config/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "level": "info" 4 | }, 5 | "dns": { 6 | "servers": [ 7 | { 8 | "address": "tls://8.8.8.8" 9 | } 10 | ] 11 | }, 12 | "inbounds": [ 13 | { 14 | "type": "shadowsocks", 15 | "listen": "::", 16 | "listen_port": 8080, 17 | "sniff": true, 18 | "network": "tcp", 19 | "method": "2022-blake3-aes-128-gcm", 20 | "password": "8JCsPssfgS8tiRwiMlhARg==" 21 | } 22 | ], 23 | "outbounds": [ 24 | { 25 | "type": "direct" 26 | }, 27 | { 28 | "type": "dns", 29 | "tag": "dns-out" 30 | } 31 | ], 32 | "route": { 33 | "rules": [ 34 | { 35 | "protocol": "dns", 36 | "outbound": "dns-out" 37 | } 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/configuration/outbound/direct.md: -------------------------------------------------------------------------------- 1 | `direct` outbound send requests directly. 2 | 3 | ### Structure 4 | 5 | ```json 6 | { 7 | "type": "direct", 8 | "tag": "direct-out", 9 | 10 | "override_address": "1.0.0.1", 11 | "override_port": 53, 12 | "proxy_protocol": 0, 13 | 14 | ... // Dial Fields 15 | } 16 | ``` 17 | 18 | ### Fields 19 | 20 | #### override_address 21 | 22 | Override the connection destination address. 23 | 24 | #### override_port 25 | 26 | Override the connection destination port. 27 | 28 | #### proxy_protocol 29 | 30 | Write [Proxy Protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt) in the connection header. 31 | 32 | Protocol value can be `1` or `2`. 33 | 34 | ### Dial Fields 35 | 36 | See [Dial Fields](/configuration/shared/dial/) for details. 37 | -------------------------------------------------------------------------------- /docs/installation/tools/deb-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -o pipefail 4 | 5 | ARCH_RAW=$(uname -m) 6 | case "${ARCH_RAW}" in 7 | 'x86_64') ARCH='amd64';; 8 | 'x86' | 'i686' | 'i386') ARCH='386';; 9 | 'aarch64' | 'arm64') ARCH='arm64';; 10 | 'armv7l') ARCH='armv7';; 11 | 's390x') ARCH='s390x';; 12 | *) echo "Unsupported architecture: ${ARCH_RAW}"; exit 1;; 13 | esac 14 | 15 | VERSION=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest \ 16 | | grep tag_name \ 17 | | cut -d ":" -f2 \ 18 | | sed 's/\"//g;s/\,//g;s/\ //g;s/v//') 19 | 20 | curl -Lo sing-box.deb "https://github.com/SagerNet/sing-box/releases/download/v${VERSION}/sing-box_${VERSION}_linux_${ARCH}.deb" 21 | sudo dpkg -i sing-box.deb 22 | rm sing-box.deb 23 | 24 | -------------------------------------------------------------------------------- /option/udp_over_tcp.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | import ( 4 | "github.com/sagernet/sing/common/json" 5 | "github.com/sagernet/sing/common/uot" 6 | ) 7 | 8 | type _UDPOverTCPOptions struct { 9 | Enabled bool `json:"enabled,omitempty"` 10 | Version uint8 `json:"version,omitempty"` 11 | } 12 | 13 | type UDPOverTCPOptions _UDPOverTCPOptions 14 | 15 | func (o UDPOverTCPOptions) MarshalJSON() ([]byte, error) { 16 | switch o.Version { 17 | case 0, uot.Version: 18 | return json.Marshal(o.Enabled) 19 | default: 20 | return json.Marshal(_UDPOverTCPOptions(o)) 21 | } 22 | } 23 | 24 | func (o *UDPOverTCPOptions) UnmarshalJSON(bytes []byte) error { 25 | err := json.Unmarshal(bytes, &o.Enabled) 26 | if err == nil { 27 | return nil 28 | } 29 | return json.Unmarshal(bytes, (*_UDPOverTCPOptions)(o)) 30 | } 31 | -------------------------------------------------------------------------------- /common/sniff/stun_test.go: -------------------------------------------------------------------------------- 1 | package sniff_test 2 | 3 | import ( 4 | "context" 5 | "encoding/hex" 6 | "testing" 7 | 8 | "github.com/sagernet/sing-box/common/sniff" 9 | C "github.com/sagernet/sing-box/constant" 10 | 11 | "github.com/stretchr/testify/require" 12 | ) 13 | 14 | func TestSniffSTUN(t *testing.T) { 15 | t.Parallel() 16 | packet, err := hex.DecodeString("000100002112a44224b1a025d0c180c484341306") 17 | require.NoError(t, err) 18 | metadata, err := sniff.STUNMessage(context.Background(), packet) 19 | require.NoError(t, err) 20 | require.Equal(t, metadata.Protocol, C.ProtocolSTUN) 21 | } 22 | 23 | func FuzzSniffSTUN(f *testing.F) { 24 | f.Fuzz(func(t *testing.T, data []byte) { 25 | if _, err := sniff.STUNMessage(context.Background(), data); err == nil { 26 | t.Fail() 27 | } 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /common/sniff/dtls.go: -------------------------------------------------------------------------------- 1 | package sniff 2 | 3 | import ( 4 | "context" 5 | "os" 6 | 7 | "github.com/sagernet/sing-box/adapter" 8 | C "github.com/sagernet/sing-box/constant" 9 | ) 10 | 11 | func DTLSRecord(ctx context.Context, packet []byte) (*adapter.InboundContext, error) { 12 | const fixedHeaderSize = 13 13 | if len(packet) < fixedHeaderSize { 14 | return nil, os.ErrInvalid 15 | } 16 | contentType := packet[0] 17 | switch contentType { 18 | case 20, 21, 22, 23, 25: 19 | default: 20 | return nil, os.ErrInvalid 21 | } 22 | versionMajor := packet[1] 23 | if versionMajor != 0xfe { 24 | return nil, os.ErrInvalid 25 | } 26 | versionMinor := packet[2] 27 | if versionMinor != 0xff && versionMinor != 0xfd { 28 | return nil, os.ErrInvalid 29 | } 30 | return &adapter.InboundContext{Protocol: C.ProtocolDTLS}, nil 31 | } 32 | -------------------------------------------------------------------------------- /docs/installation/tools/arch-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -o pipefail 4 | 5 | ARCH_RAW=$(uname -m) 6 | case "${ARCH_RAW}" in 7 | 'x86_64') ARCH='amd64';; 8 | 'x86' | 'i686' | 'i386') ARCH='386';; 9 | 'aarch64' | 'arm64') ARCH='arm64';; 10 | 'armv7l') ARCH='armv7';; 11 | 's390x') ARCH='s390x';; 12 | *) echo "Unsupported architecture: ${ARCH_RAW}"; exit 1;; 13 | esac 14 | 15 | VERSION=$(curl -s https://api.github.com/repos/SagerNet/sing-box/releases/latest \ 16 | | grep tag_name \ 17 | | cut -d ":" -f2 \ 18 | | sed 's/\"//g;s/\,//g;s/\ //g;s/v//') 19 | 20 | curl -Lo sing-box.pkg.tar.zst "https://github.com/SagerNet/sing-box/releases/download/v${VERSION}/sing-box_${VERSION}_linux_${ARCH}.pkg.tar.zst" 21 | sudo pacman -U sing-box.pkg.tar.zst 22 | rm sing-box.pkg.tar.zst 23 | -------------------------------------------------------------------------------- /test/config/vmess-client.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "inbounds": [ 6 | { 7 | "listen": "127.0.0.1", 8 | "port": "1080", 9 | "protocol": "socks", 10 | "settings": { 11 | "auth": "noauth", 12 | "udp": true, 13 | "ip": "127.0.0.1" 14 | } 15 | } 16 | ], 17 | "outbounds": [ 18 | { 19 | "protocol": "vmess", 20 | "settings": { 21 | "vnext": [ 22 | { 23 | "address": "127.0.0.1", 24 | "port": 1234, 25 | "users": [ 26 | { 27 | "id": "", 28 | "alterId": 0, 29 | "security": "none", 30 | "experiments": "" 31 | } 32 | ] 33 | } 34 | ] 35 | } 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /docs/configuration/outbound/tor.zh.md: -------------------------------------------------------------------------------- 1 | ### 结构 2 | 3 | ```json 4 | { 5 | "type": "tor", 6 | "tag": "tor-out", 7 | 8 | "executable_path": "/usr/bin/tor", 9 | "extra_args": [], 10 | "data_directory": "$HOME/.cache/tor", 11 | "torrc": { 12 | "ClientOnly": 1 13 | }, 14 | 15 | ... // 拨号字段 16 | } 17 | ``` 18 | 19 | !!! info "" 20 | 21 | 默认安装不包含嵌入式 Tor, 参阅 [安装](/zh/installation/build-from-source/#_5)。 22 | 23 | ### 字段 24 | 25 | #### executable_path 26 | 27 | Tor 可执行文件路径 28 | 29 | 如果设置,将覆盖嵌入式 Tor。 30 | 31 | #### extra_args 32 | 33 | 启动 Tor 时传递的附加参数列表。 34 | 35 | #### data_directory 36 | 37 | ==推荐== 38 | 39 | Tor 的数据目录。 40 | 41 | 如未设置,每次启动都需要长时间。 42 | 43 | #### torrc 44 | 45 | torrc 参数表。 46 | 47 | 参阅 [tor(1)](https://linux.die.net/man/1/tor)。 48 | 49 | ### 拨号字段 50 | 51 | 参阅 [拨号字段](/zh/configuration/shared/dial/)。 52 | -------------------------------------------------------------------------------- /common/tls/ech_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_ech 2 | 3 | package tls 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/log" 9 | "github.com/sagernet/sing-box/option" 10 | E "github.com/sagernet/sing/common/exceptions" 11 | ) 12 | 13 | var errECHNotIncluded = E.New(`ECH is not included in this build, rebuild with -tags with_ech`) 14 | 15 | func NewECHServer(ctx context.Context, logger log.Logger, options option.InboundTLSOptions) (ServerConfig, error) { 16 | return nil, errECHNotIncluded 17 | } 18 | 19 | func NewECHClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) { 20 | return nil, errECHNotIncluded 21 | } 22 | 23 | func ECHKeygenDefault(host string, pqSignatureSchemesEnabled bool) (configPem string, keyPem string, err error) { 24 | return "", "", errECHNotIncluded 25 | } 26 | -------------------------------------------------------------------------------- /constant/os.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "github.com/sagernet/sing-box/constant/goos" 5 | ) 6 | 7 | const IsAndroid = goos.IsAndroid == 1 8 | 9 | const IsDarwin = goos.IsDarwin == 1 10 | 11 | const IsDragonfly = goos.IsDragonfly == 1 12 | 13 | const IsFreebsd = goos.IsFreebsd == 1 14 | 15 | const IsHurd = goos.IsHurd == 1 16 | 17 | const IsIllumos = goos.IsIllumos == 1 18 | 19 | const IsIos = goos.IsIos == 1 20 | 21 | const IsJs = goos.IsJs == 1 22 | 23 | const IsLinux = goos.IsLinux == 1 || goos.IsAndroid == 1 24 | 25 | const IsNacl = goos.IsNacl == 1 26 | 27 | const IsNetbsd = goos.IsNetbsd == 1 28 | 29 | const IsOpenbsd = goos.IsOpenbsd == 1 30 | 31 | const IsPlan9 = goos.IsPlan9 == 1 32 | 33 | const IsSolaris = goos.IsSolaris == 1 34 | 35 | const IsWindows = goos.IsWindows == 1 36 | 37 | const IsZos = goos.IsZos == 1 38 | -------------------------------------------------------------------------------- /common/sniff/http_test.go: -------------------------------------------------------------------------------- 1 | package sniff_test 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/sagernet/sing-box/common/sniff" 9 | 10 | "github.com/stretchr/testify/require" 11 | ) 12 | 13 | func TestSniffHTTP1(t *testing.T) { 14 | t.Parallel() 15 | pkt := "GET / HTTP/1.1\r\nHost: www.google.com\r\nAccept: */*\r\n\r\n" 16 | metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt)) 17 | require.NoError(t, err) 18 | require.Equal(t, metadata.Domain, "www.google.com") 19 | } 20 | 21 | func TestSniffHTTP1WithPort(t *testing.T) { 22 | t.Parallel() 23 | pkt := "GET / HTTP/1.1\r\nHost: www.gov.cn:8080\r\nAccept: */*\r\n\r\n" 24 | metadata, err := sniff.HTTPHost(context.Background(), strings.NewReader(pkt)) 25 | require.NoError(t, err) 26 | require.Equal(t, metadata.Domain, "www.gov.cn") 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2022 by nekohasekai 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see . 15 | 16 | In addition, no derivative work may use the name or imply association 17 | with this application without prior consent. 18 | -------------------------------------------------------------------------------- /docs/configuration/outbound/http.zh.md: -------------------------------------------------------------------------------- 1 | `http` 出站是一个 HTTP CONNECT 代理客户端 2 | 3 | ### 结构 4 | 5 | ```json 6 | { 7 | "type": "http", 8 | "tag": "http-out", 9 | 10 | "server": "127.0.0.1", 11 | "server_port": 1080, 12 | "username": "sekai", 13 | "password": "admin", 14 | "path": "", 15 | "headers": {}, 16 | "tls": {}, 17 | 18 | ... // 拨号字段 19 | } 20 | ``` 21 | 22 | ### 字段 23 | 24 | #### server 25 | 26 | ==必填== 27 | 28 | 服务器地址。 29 | 30 | #### server_port 31 | 32 | ==必填== 33 | 34 | 服务器端口。 35 | 36 | #### username 37 | 38 | Basic 认证用户名。 39 | 40 | #### password 41 | 42 | Basic 认证密码。 43 | 44 | #### path 45 | 46 | HTTP 请求路径。 47 | 48 | #### headers 49 | 50 | HTTP 请求的额外标头。 51 | 52 | #### tls 53 | 54 | TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 55 | 56 | ### 拨号字段 57 | 58 | 参阅 [拨号字段](/zh/configuration/shared/dial/)。 59 | -------------------------------------------------------------------------------- /docs/clients/android/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/android 3 | --- 4 | 5 | # sing-box for Android 6 | 7 | SFA allows users to manage and run local or remote sing-box configuration files, and provides 8 | platform-specific function implementation, such as TUN transparent proxy implementation. 9 | 10 | ## :material-graph: Requirements 11 | 12 | * Android 5.0+ 13 | 14 | ## :material-download: Download 15 | 16 | * [Play Store](https://play.google.com/store/apps/details?id=io.nekohasekai.sfa) 17 | * [Play Store (Beta)](https://play.google.com/apps/testing/io.nekohasekai.sfa) 18 | * [GitHub Releases](https://github.com/SagerNet/sing-box/releases) 19 | * [F-Droid](https://f-droid.org/packages/io.nekohasekai.sfa/) (Unified signature via reproducible builds) 20 | 21 | ## :material-source-repository: Source code 22 | 23 | * [GitHub](https://github.com/SagerNet/sing-box-for-android) 24 | -------------------------------------------------------------------------------- /experimental/libbox/log.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || linux 2 | 3 | package libbox 4 | 5 | import ( 6 | "os" 7 | "runtime" 8 | 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | var stderrFile *os.File 13 | 14 | func RedirectStderr(path string) error { 15 | if stats, err := os.Stat(path); err == nil && stats.Size() > 0 { 16 | _ = os.Rename(path, path+".old") 17 | } 18 | outputFile, err := os.Create(path) 19 | if err != nil { 20 | return err 21 | } 22 | if runtime.GOOS != "android" { 23 | err = outputFile.Chown(sUserID, sGroupID) 24 | if err != nil { 25 | outputFile.Close() 26 | os.Remove(outputFile.Name()) 27 | return err 28 | } 29 | } 30 | err = unix.Dup2(int(outputFile.Fd()), int(os.Stderr.Fd())) 31 | if err != nil { 32 | outputFile.Close() 33 | os.Remove(outputFile.Name()) 34 | return err 35 | } 36 | stderrFile = outputFile 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /cmd/sing-box/cmd_check.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/sagernet/sing-box" 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 := box.New(box.Options{ 35 | Context: ctx, 36 | Options: options, 37 | }) 38 | if err == nil { 39 | instance.Close() 40 | } 41 | cancel() 42 | return err 43 | } 44 | -------------------------------------------------------------------------------- /docs/configuration/route/geoip.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/delete-clock 3 | --- 4 | 5 | !!! failure "Deprecated in sing-box 1.8.0" 6 | 7 | GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets). 8 | 9 | ### Structure 10 | 11 | ```json 12 | { 13 | "route": { 14 | "geoip": { 15 | "path": "", 16 | "download_url": "", 17 | "download_detour": "" 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | ### Fields 24 | 25 | #### path 26 | 27 | The path to the sing-geoip database. 28 | 29 | `geoip.db` will be used if empty. 30 | 31 | #### download_url 32 | 33 | The download URL of the sing-geoip database. 34 | 35 | Default is `https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db`. 36 | 37 | #### download_detour 38 | 39 | The tag of the outbound to download the database. 40 | 41 | Default outbound will be used if empty. -------------------------------------------------------------------------------- /experimental/clashapi/cache.go: -------------------------------------------------------------------------------- 1 | package clashapi 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | 7 | "github.com/sagernet/sing-box/adapter" 8 | "github.com/sagernet/sing/service" 9 | 10 | "github.com/go-chi/chi/v5" 11 | "github.com/go-chi/render" 12 | ) 13 | 14 | func cacheRouter(ctx context.Context) http.Handler { 15 | r := chi.NewRouter() 16 | r.Post("/fakeip/flush", flushFakeip(ctx)) 17 | return r 18 | } 19 | 20 | func flushFakeip(ctx context.Context) func(w http.ResponseWriter, r *http.Request) { 21 | return func(w http.ResponseWriter, r *http.Request) { 22 | cacheFile := service.FromContext[adapter.CacheFile](ctx) 23 | if cacheFile != nil { 24 | err := cacheFile.FakeIPReset() 25 | if err != nil { 26 | render.Status(r, http.StatusInternalServerError) 27 | render.JSON(w, r, newError(err.Error())) 28 | return 29 | } 30 | } 31 | render.NoContent(w, r) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/config/vmess-mux-client.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "inbounds": [ 6 | { 7 | "listen": "127.0.0.1", 8 | "port": "1080", 9 | "protocol": "socks", 10 | "settings": { 11 | "auth": "noauth", 12 | "udp": true, 13 | "ip": "127.0.0.1" 14 | } 15 | } 16 | ], 17 | "outbounds": [ 18 | { 19 | "protocol": "vmess", 20 | "settings": { 21 | "vnext": [ 22 | { 23 | "address": "127.0.0.1", 24 | "port": 1234, 25 | "users": [ 26 | { 27 | "id": "", 28 | "alterId": 0, 29 | "security": "none", 30 | "experiments": "" 31 | } 32 | ] 33 | } 34 | ] 35 | }, 36 | "mux": { 37 | "enabled": true 38 | } 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /docs/configuration/outbound/selector.md: -------------------------------------------------------------------------------- 1 | ### Structure 2 | 3 | ```json 4 | { 5 | "type": "selector", 6 | "tag": "select", 7 | 8 | "outbounds": [ 9 | "proxy-a", 10 | "proxy-b", 11 | "proxy-c" 12 | ], 13 | "default": "proxy-c", 14 | "interrupt_exist_connections": false 15 | } 16 | ``` 17 | 18 | !!! quote "" 19 | 20 | The selector can only be controlled through the [Clash API](/configuration/experimental#clash-api-fields) currently. 21 | 22 | ### Fields 23 | 24 | #### outbounds 25 | 26 | ==Required== 27 | 28 | List of outbound tags to select. 29 | 30 | #### default 31 | 32 | The default outbound tag. The first outbound will be used if empty. 33 | 34 | #### interrupt_exist_connections 35 | 36 | Interrupt existing connections when the selected outbound has changed. 37 | 38 | Only inbound connections are affected by this setting, internal connections will always be interrupted. 39 | -------------------------------------------------------------------------------- /route/rule_item_inbound.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | F "github.com/sagernet/sing/common/format" 8 | ) 9 | 10 | var _ RuleItem = (*InboundItem)(nil) 11 | 12 | type InboundItem struct { 13 | inbounds []string 14 | inboundMap map[string]bool 15 | } 16 | 17 | func NewInboundRule(inbounds []string) *InboundItem { 18 | rule := &InboundItem{inbounds, make(map[string]bool)} 19 | for _, inbound := range inbounds { 20 | rule.inboundMap[inbound] = true 21 | } 22 | return rule 23 | } 24 | 25 | func (r *InboundItem) Match(metadata *adapter.InboundContext) bool { 26 | return r.inboundMap[metadata.Inbound] 27 | } 28 | 29 | func (r *InboundItem) String() string { 30 | if len(r.inbounds) == 1 { 31 | return F.ToString("inbound=", r.inbounds[0]) 32 | } else { 33 | return F.ToString("inbound=[", strings.Join(r.inbounds, " "), "]") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/configuration/route/geosite.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/delete-clock 3 | --- 4 | 5 | !!! failure "Deprecated in sing-box 1.8.0" 6 | 7 | Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets). 8 | 9 | ### Structure 10 | 11 | ```json 12 | { 13 | "route": { 14 | "geosite": { 15 | "path": "", 16 | "download_url": "", 17 | "download_detour": "" 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | ### Fields 24 | 25 | #### path 26 | 27 | The path to the sing-geosite database. 28 | 29 | `geosite.db` will be used if empty. 30 | 31 | #### download_url 32 | 33 | The download URL of the sing-geoip database. 34 | 35 | Default is `https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db`. 36 | 37 | #### download_detour 38 | 39 | The tag of the outbound to download the database. 40 | 41 | Default outbound will be used if empty. -------------------------------------------------------------------------------- /docs/clients/index.md: -------------------------------------------------------------------------------- 1 | # :material-cellphone-link: Graphical Clients 2 | 3 | Maintained by Project S to provide a unified experience and platform-specific functionality. 4 | 5 | | Platform | Client | 6 | |---------------------------------------|------------------------------------------| 7 | | :material-android: Android | [sing-box for Android](./android/) | 8 | | :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) | 9 | | :material-laptop: Desktop | Working in progress | 10 | 11 | Some third-party projects that claim to use sing-box or use sing-box as a selling point are not listed here. The core 12 | motivation of the maintainers of such projects is to acquire more users, and even though they provide friendly VPN 13 | client features, the code is usually of poor quality and contains ads. 14 | -------------------------------------------------------------------------------- /docs/configuration/ntp/index.md: -------------------------------------------------------------------------------- 1 | # NTP 2 | 3 | Built-in NTP client service. 4 | 5 | If enabled, it will provide time for protocols like TLS/Shadowsocks/VMess, which is useful for environments where time 6 | synchronization is not possible. 7 | 8 | ### Structure 9 | 10 | ```json 11 | { 12 | "ntp": { 13 | "enabled": false, 14 | "server": "time.apple.com", 15 | "server_port": 123, 16 | "interval": "30m", 17 | 18 | ... // Dial Fields 19 | } 20 | } 21 | 22 | ``` 23 | 24 | ### Fields 25 | 26 | #### enabled 27 | 28 | Enable NTP service. 29 | 30 | #### server 31 | 32 | ==Required== 33 | 34 | NTP server address. 35 | 36 | #### server_port 37 | 38 | NTP server port. 39 | 40 | 123 is used by default. 41 | 42 | #### interval 43 | 44 | Time synchronization interval. 45 | 46 | 30 minutes is used by default. 47 | 48 | ### Dial Fields 49 | 50 | See [Dial Fields](/configuration/shared/dial/) for details. -------------------------------------------------------------------------------- /route/rule_item_auth_user.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | F "github.com/sagernet/sing/common/format" 8 | ) 9 | 10 | var _ RuleItem = (*AuthUserItem)(nil) 11 | 12 | type AuthUserItem struct { 13 | users []string 14 | userMap map[string]bool 15 | } 16 | 17 | func NewAuthUserItem(users []string) *AuthUserItem { 18 | userMap := make(map[string]bool) 19 | for _, protocol := range users { 20 | userMap[protocol] = true 21 | } 22 | return &AuthUserItem{ 23 | users: users, 24 | userMap: userMap, 25 | } 26 | } 27 | 28 | func (r *AuthUserItem) Match(metadata *adapter.InboundContext) bool { 29 | return r.userMap[metadata.User] 30 | } 31 | 32 | func (r *AuthUserItem) String() string { 33 | if len(r.users) == 1 { 34 | return F.ToString("auth_user=", r.users[0]) 35 | } 36 | return F.ToString("auth_user=[", strings.Join(r.users, " "), "]") 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - stable-next 7 | - main-next 8 | - dev-next 9 | paths-ignore: 10 | - '**.md' 11 | - '.github/**' 12 | - '!.github/workflows/lint.yml' 13 | pull_request: 14 | branches: 15 | - stable-next 16 | - main-next 17 | - dev-next 18 | 19 | jobs: 20 | build: 21 | name: Build 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4 26 | with: 27 | fetch-depth: 0 28 | - name: Setup Go 29 | uses: actions/setup-go@v5 30 | with: 31 | go-version: ^1.22 32 | - name: golangci-lint 33 | uses: golangci/golangci-lint-action@v6 34 | with: 35 | version: latest 36 | args: --timeout=30m 37 | install-mode: binary -------------------------------------------------------------------------------- /docs/configuration/index.zh.md: -------------------------------------------------------------------------------- 1 | # 引言 2 | 3 | sing-box 使用 JSON 作为配置文件格式。 4 | 5 | ### 结构 6 | 7 | ```json 8 | { 9 | "log": {}, 10 | "dns": {}, 11 | "inbounds": [], 12 | "outbounds": [], 13 | "route": {}, 14 | "experimental": {} 15 | } 16 | ``` 17 | 18 | ### 字段 19 | 20 | | Key | Format | 21 | |----------------|------------------------| 22 | | `log` | [日志](./log/) | 23 | | `dns` | [DNS](./dns/) | 24 | | `inbounds` | [入站](./inbound/) | 25 | | `outbounds` | [出站](./outbound/) | 26 | | `route` | [路由](./route/) | 27 | | `experimental` | [实验性](./experimental/) | 28 | 29 | ### 检查 30 | 31 | ```bash 32 | sing-box check 33 | ``` 34 | 35 | ### 格式化 36 | 37 | ```bash 38 | sing-box format -w -c config.json -D config_directory 39 | ``` 40 | 41 | ### 合并 42 | 43 | ```bash 44 | sing-box merge output.json -c config.json -D config_directory 45 | ``` -------------------------------------------------------------------------------- /common/settings/time_windows.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | import ( 4 | "time" 5 | "unsafe" 6 | 7 | "golang.org/x/sys/windows" 8 | ) 9 | 10 | func SetSystemTime(nowTime time.Time) error { 11 | var systemTime windows.Systemtime 12 | systemTime.Year = uint16(nowTime.Year()) 13 | systemTime.Month = uint16(nowTime.Month()) 14 | systemTime.Day = uint16(nowTime.Day()) 15 | systemTime.Hour = uint16(nowTime.Hour()) 16 | systemTime.Minute = uint16(nowTime.Minute()) 17 | systemTime.Second = uint16(nowTime.Second()) 18 | systemTime.Milliseconds = uint16(nowTime.UnixMilli() - nowTime.Unix()*1000) 19 | 20 | dllKernel32 := windows.NewLazySystemDLL("kernel32.dll") 21 | proc := dllKernel32.NewProc("SetSystemTime") 22 | 23 | _, _, err := proc.Call( 24 | uintptr(unsafe.Pointer(&systemTime)), 25 | ) 26 | 27 | if err != nil && err.Error() != "The operation completed successfully." { 28 | return err 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /option/multiplex.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type InboundMultiplexOptions struct { 4 | Enabled bool `json:"enabled,omitempty"` 5 | Padding bool `json:"padding,omitempty"` 6 | Brutal *BrutalOptions `json:"brutal,omitempty"` 7 | } 8 | 9 | type OutboundMultiplexOptions struct { 10 | Enabled bool `json:"enabled,omitempty"` 11 | Protocol string `json:"protocol,omitempty"` 12 | MaxConnections int `json:"max_connections,omitempty"` 13 | MinStreams int `json:"min_streams,omitempty"` 14 | MaxStreams int `json:"max_streams,omitempty"` 15 | Padding bool `json:"padding,omitempty"` 16 | Brutal *BrutalOptions `json:"brutal,omitempty"` 17 | } 18 | 19 | type BrutalOptions struct { 20 | Enabled bool `json:"enabled,omitempty"` 21 | UpMbps int `json:"up_mbps,omitempty"` 22 | DownMbps int `json:"down_mbps,omitempty"` 23 | } 24 | -------------------------------------------------------------------------------- /test/config/vless-tls-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "inbounds": [ 6 | { 7 | "listen": "0.0.0.0", 8 | "port": 1234, 9 | "protocol": "vless", 10 | "settings": { 11 | "decryption": "none", 12 | "clients": [ 13 | { 14 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811", 15 | "flow": "" 16 | } 17 | ] 18 | }, 19 | "streamSettings": { 20 | "network": "tcp", 21 | "security": "tls", 22 | "tlsSettings": { 23 | "serverName": "example.org", 24 | "certificates": [ 25 | { 26 | "certificateFile": "/path/to/certificate.crt", 27 | "keyFile": "/path/to/private.key" 28 | } 29 | ] 30 | } 31 | } 32 | } 33 | ], 34 | "outbounds": [ 35 | { 36 | "protocol": "freedom" 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /transport/v2rayquic/stream.go: -------------------------------------------------------------------------------- 1 | package v2rayquic 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/sagernet/quic-go" 7 | "github.com/sagernet/sing/common/baderror" 8 | ) 9 | 10 | type StreamWrapper struct { 11 | Conn quic.Connection 12 | quic.Stream 13 | } 14 | 15 | func (s *StreamWrapper) Read(p []byte) (n int, err error) { 16 | n, err = s.Stream.Read(p) 17 | return n, baderror.WrapQUIC(err) 18 | } 19 | 20 | func (s *StreamWrapper) Write(p []byte) (n int, err error) { 21 | n, err = s.Stream.Write(p) 22 | return n, baderror.WrapQUIC(err) 23 | } 24 | 25 | func (s *StreamWrapper) LocalAddr() net.Addr { 26 | return s.Conn.LocalAddr() 27 | } 28 | 29 | func (s *StreamWrapper) RemoteAddr() net.Addr { 30 | return s.Conn.RemoteAddr() 31 | } 32 | 33 | func (s *StreamWrapper) Upstream() any { 34 | return s.Stream 35 | } 36 | 37 | func (s *StreamWrapper) Close() error { 38 | s.CancelRead(0) 39 | s.Stream.Close() 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /experimental/libbox/command_shared.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | 7 | E "github.com/sagernet/sing/common/exceptions" 8 | "github.com/sagernet/sing/common/varbin" 9 | ) 10 | 11 | func readError(reader io.Reader) error { 12 | var hasError bool 13 | err := binary.Read(reader, binary.BigEndian, &hasError) 14 | if err != nil { 15 | return err 16 | } 17 | if hasError { 18 | errorMessage, err := varbin.ReadValue[string](reader, binary.BigEndian) 19 | if err != nil { 20 | return err 21 | } 22 | return E.New(errorMessage) 23 | } 24 | return nil 25 | } 26 | 27 | func writeError(writer io.Writer, wErr error) error { 28 | err := binary.Write(writer, binary.BigEndian, wErr != nil) 29 | if err != nil { 30 | return err 31 | } 32 | if wErr != nil { 33 | err = varbin.Write(writer, binary.BigEndian, wErr.Error()) 34 | if err != nil { 35 | return err 36 | } 37 | } 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /docs/configuration/experimental/cache-file.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/new-box 3 | --- 4 | 5 | !!! question "自 sing-box 1.8.0 起" 6 | 7 | !!! quote "sing-box 1.9.0 中的更改" 8 | 9 | :material-plus: [store_rdrc](#store_rdrc) 10 | :material-plus: [rdrc_timeout](#rdrc_timeout) 11 | 12 | ### 结构 13 | 14 | ```json 15 | { 16 | "enabled": true, 17 | "path": "", 18 | "cache_id": "", 19 | "store_fakeip": false, 20 | "store_rdrc": false, 21 | "rdrc_timeout": "" 22 | } 23 | ``` 24 | 25 | ### 字段 26 | 27 | #### enabled 28 | 29 | 启用缓存文件。 30 | 31 | #### path 32 | 33 | 缓存文件路径,默认使用`cache.db`。 34 | 35 | #### cache_id 36 | 37 | 缓存文件中的标识符。 38 | 39 | 如果不为空,配置特定的数据将使用由其键控的单独存储。 40 | 41 | #### store_fakeip 42 | 43 | 将 fakeip 存储在缓存文件中。 44 | 45 | #### store_rdrc 46 | 47 | 将拒绝的 DNS 响应缓存存储在缓存文件中。 48 | 49 | [地址筛选 DNS 规则项](/zh/configuration/dns/rule/#_3) 的检查结果将被缓存至过期。 50 | 51 | #### rdrc_timeout 52 | 53 | 拒绝的 DNS 响应缓存超时。 54 | 55 | 默认使用 `7d`。 56 | -------------------------------------------------------------------------------- /option/vless.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type VLESSInboundOptions struct { 4 | ListenOptions 5 | Users []VLESSUser `json:"users,omitempty"` 6 | InboundTLSOptionsContainer 7 | Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"` 8 | Transport *V2RayTransportOptions `json:"transport,omitempty"` 9 | } 10 | 11 | type VLESSUser struct { 12 | Name string `json:"name"` 13 | UUID string `json:"uuid"` 14 | Flow string `json:"flow,omitempty"` 15 | } 16 | 17 | type VLESSOutboundOptions struct { 18 | DialerOptions 19 | ServerOptions 20 | UUID string `json:"uuid"` 21 | Flow string `json:"flow,omitempty"` 22 | Network NetworkList `json:"network,omitempty"` 23 | OutboundTLSOptionsContainer 24 | Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"` 25 | Transport *V2RayTransportOptions `json:"transport,omitempty"` 26 | PacketEncoding *string `json:"packet_encoding,omitempty"` 27 | } 28 | -------------------------------------------------------------------------------- /docs/configuration/experimental/v2ray-api.md: -------------------------------------------------------------------------------- 1 | !!! quote "" 2 | 3 | V2Ray API is not included by default, see [Installation](/installation/build-from-source/#build-tags). 4 | 5 | ### Structure 6 | 7 | ```json 8 | { 9 | "listen": "127.0.0.1:8080", 10 | "stats": { 11 | "enabled": true, 12 | "inbounds": [ 13 | "socks-in" 14 | ], 15 | "outbounds": [ 16 | "proxy", 17 | "direct" 18 | ], 19 | "users": [ 20 | "sekai" 21 | ] 22 | } 23 | } 24 | ``` 25 | 26 | ### Fields 27 | 28 | #### listen 29 | 30 | gRPC API listening address. V2Ray API will be disabled if empty. 31 | 32 | #### stats 33 | 34 | Traffic statistics service settings. 35 | 36 | #### stats.enabled 37 | 38 | Enable statistics service. 39 | 40 | #### stats.inbounds 41 | 42 | Inbound list to count traffic. 43 | 44 | #### stats.outbounds 45 | 46 | Outbound list to count traffic. 47 | 48 | #### stats.users 49 | 50 | User list to count traffic. -------------------------------------------------------------------------------- /transport/v2ray/grpc_lite.go: -------------------------------------------------------------------------------- 1 | //go:build !with_grpc 2 | 3 | package v2ray 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | "github.com/sagernet/sing-box/common/tls" 10 | "github.com/sagernet/sing-box/option" 11 | "github.com/sagernet/sing-box/transport/v2raygrpclite" 12 | M "github.com/sagernet/sing/common/metadata" 13 | N "github.com/sagernet/sing/common/network" 14 | ) 15 | 16 | func NewGRPCServer(ctx context.Context, options option.V2RayGRPCOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { 17 | return v2raygrpclite.NewServer(ctx, options, tlsConfig, handler) 18 | } 19 | 20 | func NewGRPCClient(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayGRPCOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { 21 | return v2raygrpclite.NewClient(ctx, dialer, serverAddr, options, tlsConfig), nil 22 | } 23 | -------------------------------------------------------------------------------- /adapter/fakeip.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import ( 4 | "net/netip" 5 | 6 | "github.com/sagernet/sing-dns" 7 | "github.com/sagernet/sing/common/logger" 8 | ) 9 | 10 | type FakeIPStore interface { 11 | Service 12 | Contains(address netip.Addr) bool 13 | Create(domain string, isIPv6 bool) (netip.Addr, error) 14 | Lookup(address netip.Addr) (string, bool) 15 | Reset() error 16 | } 17 | 18 | type FakeIPStorage interface { 19 | FakeIPMetadata() *FakeIPMetadata 20 | FakeIPSaveMetadata(metadata *FakeIPMetadata) error 21 | FakeIPSaveMetadataAsync(metadata *FakeIPMetadata) 22 | FakeIPStore(address netip.Addr, domain string) error 23 | FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger) 24 | FakeIPLoad(address netip.Addr) (string, bool) 25 | FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bool) 26 | FakeIPReset() error 27 | } 28 | 29 | type FakeIPTransport interface { 30 | dns.Transport 31 | Store() FakeIPStore 32 | } 33 | -------------------------------------------------------------------------------- /docs/configuration/inbound/mixed.md: -------------------------------------------------------------------------------- 1 | `mixed` inbound is a socks4, socks4a, socks5 and http server. 2 | 3 | ### Structure 4 | 5 | ```json 6 | { 7 | "type": "mixed", 8 | "tag": "mixed-in", 9 | 10 | ... // Listen Fields 11 | 12 | "users": [ 13 | { 14 | "username": "admin", 15 | "password": "admin" 16 | } 17 | ], 18 | "set_system_proxy": false 19 | } 20 | ``` 21 | 22 | ### Listen Fields 23 | 24 | See [Listen Fields](/configuration/shared/listen/) for details. 25 | 26 | ### Fields 27 | 28 | #### users 29 | 30 | SOCKS and HTTP users. 31 | 32 | No authentication required if empty. 33 | 34 | #### set_system_proxy 35 | 36 | !!! quote "" 37 | 38 | Only supported on Linux, Android, Windows, and macOS. 39 | 40 | !!! warning "" 41 | 42 | To work on Android and Apple platforms without privileges, use tun.platform.http_proxy instead. 43 | 44 | Automatically set system proxy configuration when start and clean up when stop. 45 | -------------------------------------------------------------------------------- /option/trojan.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type TrojanInboundOptions struct { 4 | ListenOptions 5 | Users []TrojanUser `json:"users,omitempty"` 6 | InboundTLSOptionsContainer 7 | Fallback *ServerOptions `json:"fallback,omitempty"` 8 | FallbackForALPN map[string]*ServerOptions `json:"fallback_for_alpn,omitempty"` 9 | Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"` 10 | Transport *V2RayTransportOptions `json:"transport,omitempty"` 11 | } 12 | 13 | type TrojanUser struct { 14 | Name string `json:"name"` 15 | Password string `json:"password"` 16 | } 17 | 18 | type TrojanOutboundOptions struct { 19 | DialerOptions 20 | ServerOptions 21 | Password string `json:"password"` 22 | Network NetworkList `json:"network,omitempty"` 23 | OutboundTLSOptionsContainer 24 | Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"` 25 | Transport *V2RayTransportOptions `json:"transport,omitempty"` 26 | } 27 | -------------------------------------------------------------------------------- /test/config/vmess-grpc-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "inbounds": [ 6 | { 7 | "listen": "0.0.0.0", 8 | "port": 1234, 9 | "protocol": "vmess", 10 | "settings": { 11 | "clients": [ 12 | { 13 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 14 | } 15 | ] 16 | }, 17 | "streamSettings": { 18 | "network": "gun", 19 | "security": "tls", 20 | "tlsSettings": { 21 | "serverName": "example.org", 22 | "certificates": [ 23 | { 24 | "certificateFile": "/path/to/certificate.crt", 25 | "keyFile": "/path/to/private.key" 26 | } 27 | ] 28 | }, 29 | "grpcSettings": { 30 | "serviceName": "TunService" 31 | } 32 | } 33 | } 34 | ], 35 | "outbounds": [ 36 | { 37 | "protocol": "freedom" 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /adapter/handler.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/sagernet/sing/common/buf" 8 | E "github.com/sagernet/sing/common/exceptions" 9 | N "github.com/sagernet/sing/common/network" 10 | ) 11 | 12 | type ConnectionHandler interface { 13 | NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error 14 | } 15 | 16 | type PacketHandler interface { 17 | NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error 18 | } 19 | 20 | type OOBPacketHandler interface { 21 | NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error 22 | } 23 | 24 | type PacketConnectionHandler interface { 25 | NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error 26 | } 27 | 28 | type UpstreamHandlerAdapter interface { 29 | N.TCPConnectionHandler 30 | N.UDPConnectionHandler 31 | E.Handler 32 | } 33 | -------------------------------------------------------------------------------- /common/geoip/reader.go: -------------------------------------------------------------------------------- 1 | package geoip 2 | 3 | import ( 4 | "net/netip" 5 | 6 | E "github.com/sagernet/sing/common/exceptions" 7 | 8 | "github.com/oschwald/maxminddb-golang" 9 | ) 10 | 11 | type Reader struct { 12 | reader *maxminddb.Reader 13 | } 14 | 15 | func Open(path string) (*Reader, []string, error) { 16 | database, err := maxminddb.Open(path) 17 | if err != nil { 18 | return nil, nil, err 19 | } 20 | if database.Metadata.DatabaseType != "sing-geoip" { 21 | database.Close() 22 | return nil, nil, E.New("incorrect database type, expected sing-geoip, got ", database.Metadata.DatabaseType) 23 | } 24 | return &Reader{database}, database.Metadata.Languages, nil 25 | } 26 | 27 | func (r *Reader) Lookup(addr netip.Addr) string { 28 | var code string 29 | _ = r.reader.Lookup(addr.AsSlice(), &code) 30 | if code != "" { 31 | return code 32 | } 33 | return "unknown" 34 | } 35 | 36 | func (r *Reader) Close() error { 37 | return r.reader.Close() 38 | } 39 | -------------------------------------------------------------------------------- /common/process/searcher_linux.go: -------------------------------------------------------------------------------- 1 | //go:build linux && !android 2 | 3 | package process 4 | 5 | import ( 6 | "context" 7 | "net/netip" 8 | 9 | "github.com/sagernet/sing-box/log" 10 | ) 11 | 12 | var _ Searcher = (*linuxSearcher)(nil) 13 | 14 | type linuxSearcher struct { 15 | logger log.ContextLogger 16 | } 17 | 18 | func NewSearcher(config Config) (Searcher, error) { 19 | return &linuxSearcher{config.Logger}, nil 20 | } 21 | 22 | func (s *linuxSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { 23 | inode, uid, err := resolveSocketByNetlink(network, source, destination) 24 | if err != nil { 25 | return nil, err 26 | } 27 | processPath, err := resolveProcessNameByProcSearch(inode, uid) 28 | if err != nil { 29 | s.logger.DebugContext(ctx, "find process path: ", err) 30 | } 31 | return &Info{ 32 | UserId: int32(uid), 33 | ProcessPath: processPath, 34 | }, nil 35 | } 36 | -------------------------------------------------------------------------------- /docs/configuration/inbound/vless.zh.md: -------------------------------------------------------------------------------- 1 | ### 结构 2 | 3 | ```json 4 | { 5 | "type": "vless", 6 | "tag": "vless-in", 7 | 8 | ... // 监听字段 9 | 10 | "users": [ 11 | { 12 | "name": "sekai", 13 | "uuid": "bf000d23-0752-40b4-affe-68f7707a9661", 14 | "flow": "" 15 | } 16 | ], 17 | "tls": {}, 18 | "multiplex": {}, 19 | "transport": {} 20 | } 21 | ``` 22 | 23 | ### 监听字段 24 | 25 | 参阅 [监听字段](/zh/configuration/shared/listen/)。 26 | 27 | ### 字段 28 | 29 | #### users 30 | 31 | ==必填== 32 | 33 | VLESS 用户。 34 | 35 | #### users.uuid 36 | 37 | ==必填== 38 | 39 | VLESS 用户 ID。 40 | 41 | #### users.flow 42 | 43 | VLESS 子协议。 44 | 45 | 可用值: 46 | 47 | * `xtls-rprx-vision` 48 | 49 | #### tls 50 | 51 | TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 52 | 53 | #### multiplex 54 | 55 | 参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。 56 | 57 | #### transport 58 | 59 | V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport/)。 60 | -------------------------------------------------------------------------------- /route/rule_item_protocol.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | F "github.com/sagernet/sing/common/format" 8 | ) 9 | 10 | var _ RuleItem = (*ProtocolItem)(nil) 11 | 12 | type ProtocolItem struct { 13 | protocols []string 14 | protocolMap map[string]bool 15 | } 16 | 17 | func NewProtocolItem(protocols []string) *ProtocolItem { 18 | protocolMap := make(map[string]bool) 19 | for _, protocol := range protocols { 20 | protocolMap[protocol] = true 21 | } 22 | return &ProtocolItem{ 23 | protocols: protocols, 24 | protocolMap: protocolMap, 25 | } 26 | } 27 | 28 | func (r *ProtocolItem) Match(metadata *adapter.InboundContext) bool { 29 | return r.protocolMap[metadata.Protocol] 30 | } 31 | 32 | func (r *ProtocolItem) String() string { 33 | if len(r.protocols) == 1 { 34 | return F.ToString("protocol=", r.protocols[0]) 35 | } 36 | return F.ToString("protocol=[", strings.Join(r.protocols, " "), "]") 37 | } 38 | -------------------------------------------------------------------------------- /common/badtls/read_wait_utls.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 && !without_badtls && with_utls 2 | 3 | package badtls 4 | 5 | import ( 6 | "net" 7 | _ "unsafe" 8 | 9 | "github.com/sagernet/sing/common" 10 | "github.com/sagernet/utls" 11 | ) 12 | 13 | func init() { 14 | tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) { 15 | tlsConn, loaded := common.Cast[*tls.UConn](conn) 16 | if !loaded { 17 | return 18 | } 19 | return true, func() error { 20 | return utlsReadRecord(tlsConn.Conn) 21 | }, func() error { 22 | return utlsHandlePostHandshakeMessage(tlsConn.Conn) 23 | } 24 | }) 25 | } 26 | 27 | //go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord 28 | func utlsReadRecord(c *tls.Conn) error 29 | 30 | //go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage 31 | func utlsHandlePostHandshakeMessage(c *tls.Conn) error 32 | -------------------------------------------------------------------------------- /experimental/clashapi/rules.go: -------------------------------------------------------------------------------- 1 | package clashapi 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | 8 | "github.com/go-chi/chi/v5" 9 | "github.com/go-chi/render" 10 | ) 11 | 12 | func ruleRouter(router adapter.Router) http.Handler { 13 | r := chi.NewRouter() 14 | r.Get("/", getRules(router)) 15 | return r 16 | } 17 | 18 | type Rule struct { 19 | Type string `json:"type"` 20 | Payload string `json:"payload"` 21 | Proxy string `json:"proxy"` 22 | } 23 | 24 | func getRules(router adapter.Router) func(w http.ResponseWriter, r *http.Request) { 25 | return func(w http.ResponseWriter, r *http.Request) { 26 | rawRules := router.Rules() 27 | 28 | var rules []Rule 29 | for _, rule := range rawRules { 30 | rules = append(rules, Rule{ 31 | Type: rule.Type(), 32 | Payload: rule.String(), 33 | Proxy: rule.Outbound(), 34 | }) 35 | } 36 | 37 | render.JSON(w, r, render.M{ 38 | "rules": rules, 39 | }) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/tls/config.go: -------------------------------------------------------------------------------- 1 | package tls 2 | 3 | import ( 4 | "crypto/tls" 5 | 6 | E "github.com/sagernet/sing/common/exceptions" 7 | aTLS "github.com/sagernet/sing/common/tls" 8 | ) 9 | 10 | type ( 11 | Config = aTLS.Config 12 | ConfigCompat = aTLS.ConfigCompat 13 | ServerConfig = aTLS.ServerConfig 14 | ServerConfigCompat = aTLS.ServerConfigCompat 15 | WithSessionIDGenerator = aTLS.WithSessionIDGenerator 16 | Conn = aTLS.Conn 17 | 18 | STDConfig = tls.Config 19 | STDConn = tls.Conn 20 | ConnectionState = tls.ConnectionState 21 | ) 22 | 23 | func ParseTLSVersion(version string) (uint16, error) { 24 | switch version { 25 | case "1.0": 26 | return tls.VersionTLS10, nil 27 | case "1.1": 28 | return tls.VersionTLS11, nil 29 | case "1.2": 30 | return tls.VersionTLS12, nil 31 | case "1.3": 32 | return tls.VersionTLS13, nil 33 | default: 34 | return 0, E.New("unknown tls version:", version) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/configuration/inbound/http.md: -------------------------------------------------------------------------------- 1 | ### Structure 2 | 3 | ```json 4 | { 5 | "type": "http", 6 | "tag": "http-in", 7 | 8 | ... // Listen Fields 9 | 10 | "users": [ 11 | { 12 | "username": "admin", 13 | "password": "admin" 14 | } 15 | ], 16 | "tls": {}, 17 | "set_system_proxy": false 18 | } 19 | ``` 20 | 21 | ### Listen Fields 22 | 23 | See [Listen Fields](/configuration/shared/listen/) for details. 24 | 25 | ### Fields 26 | 27 | #### tls 28 | 29 | TLS configuration, see [TLS](/configuration/shared/tls/#inbound). 30 | 31 | #### users 32 | 33 | HTTP users. 34 | 35 | No authentication required if empty. 36 | 37 | #### set_system_proxy 38 | 39 | !!! quote "" 40 | 41 | Only supported on Linux, Android, Windows, and macOS. 42 | 43 | !!! warning "" 44 | 45 | To work on Android and Apple platforms without privileges, use tun.platform.http_proxy instead. 46 | 47 | Automatically set system proxy configuration when start and clean up when stop. 48 | -------------------------------------------------------------------------------- /route/rule_item_user.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | F "github.com/sagernet/sing/common/format" 8 | ) 9 | 10 | var _ RuleItem = (*UserItem)(nil) 11 | 12 | type UserItem struct { 13 | users []string 14 | userMap map[string]bool 15 | } 16 | 17 | func NewUserItem(users []string) *UserItem { 18 | userMap := make(map[string]bool) 19 | for _, protocol := range users { 20 | userMap[protocol] = true 21 | } 22 | return &UserItem{ 23 | users: users, 24 | userMap: userMap, 25 | } 26 | } 27 | 28 | func (r *UserItem) Match(metadata *adapter.InboundContext) bool { 29 | if metadata.ProcessInfo == nil || metadata.ProcessInfo.User == "" { 30 | return false 31 | } 32 | return r.userMap[metadata.ProcessInfo.User] 33 | } 34 | 35 | func (r *UserItem) String() string { 36 | if len(r.users) == 1 { 37 | return F.ToString("user=", r.users[0]) 38 | } 39 | return F.ToString("user=[", strings.Join(r.users, " "), "]") 40 | } 41 | -------------------------------------------------------------------------------- /test/config/vmess-ws-server.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "loglevel": "debug" 4 | }, 5 | "inbounds": [ 6 | { 7 | "listen": "0.0.0.0", 8 | "port": 1234, 9 | "protocol": "vmess", 10 | "settings": { 11 | "clients": [ 12 | { 13 | "id": "b831381d-6324-4d53-ad4f-8cda48b30811" 14 | } 15 | ] 16 | }, 17 | "streamSettings": { 18 | "network": "ws", 19 | "security": "tls", 20 | "tlsSettings": { 21 | "serverName": "example.org", 22 | "certificates": [ 23 | { 24 | "certificateFile": "/path/to/certificate.crt", 25 | "keyFile": "/path/to/private.key" 26 | } 27 | ] 28 | }, 29 | "wsSettings": { 30 | "maxEarlyData": 2048, 31 | "earlyDataHeaderName": "" 32 | } 33 | } 34 | } 35 | ], 36 | "outbounds": [ 37 | { 38 | "protocol": "freedom" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /common/badtls/read_wait_ech.go: -------------------------------------------------------------------------------- 1 | //go:build go1.21 && !without_badtls && with_ech 2 | 3 | package badtls 4 | 5 | import ( 6 | "net" 7 | _ "unsafe" 8 | 9 | "github.com/sagernet/cloudflare-tls" 10 | "github.com/sagernet/sing/common" 11 | ) 12 | 13 | func init() { 14 | tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) { 15 | tlsConn, loaded := common.Cast[*tls.Conn](conn) 16 | if !loaded { 17 | return 18 | } 19 | return true, func() error { 20 | return echReadRecord(tlsConn) 21 | }, func() error { 22 | return echHandlePostHandshakeMessage(tlsConn) 23 | } 24 | }) 25 | } 26 | 27 | //go:linkname echReadRecord github.com/sagernet/cloudflare-tls.(*Conn).readRecord 28 | func echReadRecord(c *tls.Conn) error 29 | 30 | //go:linkname echHandlePostHandshakeMessage github.com/sagernet/cloudflare-tls.(*Conn).handlePostHandshakeMessage 31 | func echHandlePostHandshakeMessage(c *tls.Conn) error 32 | -------------------------------------------------------------------------------- /docs/configuration/outbound/socks.zh.md: -------------------------------------------------------------------------------- 1 | `socks` 出站是 socks4/socks4a/socks5 客户端 2 | 3 | ### 结构 4 | 5 | ```json 6 | { 7 | "type": "socks", 8 | "tag": "socks-out", 9 | 10 | "server": "127.0.0.1", 11 | "server_port": 1080, 12 | "version": "5", 13 | "username": "sekai", 14 | "password": "admin", 15 | "network": "udp", 16 | "udp_over_tcp": false | {}, 17 | 18 | ... // 拨号字段 19 | } 20 | ``` 21 | 22 | ### 字段 23 | 24 | #### server 25 | 26 | ==必填== 27 | 28 | 服务器地址。 29 | 30 | #### server_port 31 | 32 | ==必填== 33 | 34 | 服务器端口。 35 | 36 | #### version 37 | 38 | SOCKS 版本, 可为 `4` `4a` `5`. 39 | 40 | 默认使用 SOCKS5。 41 | 42 | #### username 43 | 44 | SOCKS 用户名。 45 | 46 | #### password 47 | 48 | SOCKS5 密码。 49 | 50 | #### network 51 | 52 | 启用的网络协议 53 | 54 | `tcp` 或 `udp`。 55 | 56 | 默认所有。 57 | 58 | #### udp_over_tcp 59 | 60 | UDP over TCP 配置。 61 | 62 | 参阅 [UDP Over TCP](/zh/configuration/shared/udp-over-tcp/)。 63 | 64 | ### 拨号字段 65 | 66 | 参阅 [拨号字段](/zh/configuration/shared/dial/)。 67 | -------------------------------------------------------------------------------- /docs/configuration/outbound/trojan.zh.md: -------------------------------------------------------------------------------- 1 | ### 结构 2 | 3 | ```json 4 | { 5 | "type": "trojan", 6 | "tag": "trojan-out", 7 | 8 | "server": "127.0.0.1", 9 | "server_port": 1080, 10 | "password": "8JCsPssfgS8tiRwiMlhARg==", 11 | "network": "tcp", 12 | "tls": {}, 13 | "multiplex": {}, 14 | "transport": {}, 15 | 16 | ... // 拨号字段 17 | } 18 | ``` 19 | 20 | ### 字段 21 | 22 | #### server 23 | 24 | ==必填== 25 | 26 | 服务器地址。 27 | 28 | #### server_port 29 | 30 | ==必填== 31 | 32 | 服务器端口。 33 | 34 | #### password 35 | 36 | ==必填== 37 | 38 | Trojan 密码。 39 | 40 | #### network 41 | 42 | 启用的网络协议。 43 | 44 | `tcp` 或 `udp`。 45 | 46 | 默认所有。 47 | 48 | #### tls 49 | 50 | TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#outbound)。 51 | 52 | #### multiplex 53 | 54 | 参阅 [多路复用](/zh/configuration/shared/multiplex#outbound)。 55 | 56 | #### transport 57 | 58 | V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport/)。 59 | 60 | ### 拨号字段 61 | 62 | 参阅 [拨号字段](/zh/configuration/shared/dial/)。 63 | -------------------------------------------------------------------------------- /route/rule_item_wifi_ssid.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | F "github.com/sagernet/sing/common/format" 8 | ) 9 | 10 | var _ RuleItem = (*WIFISSIDItem)(nil) 11 | 12 | type WIFISSIDItem struct { 13 | ssidList []string 14 | ssidMap map[string]bool 15 | router adapter.Router 16 | } 17 | 18 | func NewWIFISSIDItem(router adapter.Router, ssidList []string) *WIFISSIDItem { 19 | ssidMap := make(map[string]bool) 20 | for _, ssid := range ssidList { 21 | ssidMap[ssid] = true 22 | } 23 | return &WIFISSIDItem{ 24 | ssidList, 25 | ssidMap, 26 | router, 27 | } 28 | } 29 | 30 | func (r *WIFISSIDItem) Match(metadata *adapter.InboundContext) bool { 31 | return r.ssidMap[r.router.WIFIState().SSID] 32 | } 33 | 34 | func (r *WIFISSIDItem) String() string { 35 | if len(r.ssidList) == 1 { 36 | return F.ToString("wifi_ssid=", r.ssidList[0]) 37 | } 38 | return F.ToString("wifi_ssid=[", strings.Join(r.ssidList, " "), "]") 39 | } 40 | -------------------------------------------------------------------------------- /debug_go118.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.19 2 | 3 | package box 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 | applyDebugListenOption(options) 14 | if options.GCPercent != nil { 15 | debug.SetGCPercent(*options.GCPercent) 16 | } 17 | if options.MaxStack != nil { 18 | debug.SetMaxStack(*options.MaxStack) 19 | } 20 | if options.MaxThreads != nil { 21 | debug.SetMaxThreads(*options.MaxThreads) 22 | } 23 | if options.PanicOnFault != nil { 24 | debug.SetPanicOnFault(*options.PanicOnFault) 25 | } 26 | if options.TraceBack != "" { 27 | debug.SetTraceback(options.TraceBack) 28 | } 29 | if options.MemoryLimit != 0 { 30 | // debug.SetMemoryLimit(int64(options.MemoryLimit)) 31 | conntrack.MemoryLimit = uint64(options.MemoryLimit) 32 | } 33 | if options.OOMKiller != nil { 34 | conntrack.KillerEnabled = *options.OOMKiller 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/configuration/inbound/vmess.zh.md: -------------------------------------------------------------------------------- 1 | ### 结构 2 | 3 | ```json 4 | { 5 | "type": "vmess", 6 | "tag": "vmess-in", 7 | 8 | ... // 监听字段 9 | 10 | "users": [ 11 | { 12 | "name": "sekai", 13 | "uuid": "bf000d23-0752-40b4-affe-68f7707a9661", 14 | "alterId": 0 15 | } 16 | ], 17 | "tls": {}, 18 | "multiplex": {}, 19 | "transport": {} 20 | } 21 | ``` 22 | 23 | ### 监听字段 24 | 25 | 参阅 [监听字段](/zh/configuration/shared/listen/)。 26 | 27 | ### 字段 28 | 29 | #### users 30 | 31 | ==必填== 32 | 33 | VMess 用户。 34 | 35 | | Alter ID | 描述 | 36 | |----------|-------| 37 | | 0 | 禁用旧协议 | 38 | | > 0 | 启用旧协议 | 39 | 40 | !!! warning "" 41 | 42 | 提供旧协议支持(VMess MD5 身份验证)仅出于兼容性目的,不建议使用 alterId > 1。 43 | 44 | #### tls 45 | 46 | TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。 47 | 48 | #### multiplex 49 | 50 | 参阅 [多路复用](/zh/configuration/shared/multiplex#inbound)。 51 | 52 | #### transport 53 | 54 | V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport/)。 55 | -------------------------------------------------------------------------------- /docs/configuration/outbound/http.md: -------------------------------------------------------------------------------- 1 | `http` outbound is a HTTP CONNECT proxy client. 2 | 3 | ### Structure 4 | 5 | ```json 6 | { 7 | "type": "http", 8 | "tag": "http-out", 9 | 10 | "server": "127.0.0.1", 11 | "server_port": 1080, 12 | "username": "sekai", 13 | "password": "admin", 14 | "path": "", 15 | "headers": {}, 16 | "tls": {}, 17 | 18 | ... // Dial Fields 19 | } 20 | ``` 21 | 22 | ### Fields 23 | 24 | #### server 25 | 26 | ==Required== 27 | 28 | The server address. 29 | 30 | #### server_port 31 | 32 | ==Required== 33 | 34 | The server port. 35 | 36 | #### username 37 | 38 | Basic authorization username. 39 | 40 | #### password 41 | 42 | Basic authorization password. 43 | 44 | #### path 45 | 46 | Path of HTTP request. 47 | 48 | #### headers 49 | 50 | Extra headers of HTTP request. 51 | 52 | #### tls 53 | 54 | TLS configuration, see [TLS](/configuration/shared/tls/#outbound). 55 | 56 | ### Dial Fields 57 | 58 | See [Dial Fields](/configuration/shared/dial/) for details. 59 | -------------------------------------------------------------------------------- /debug_go119.go: -------------------------------------------------------------------------------- 1 | //go:build go1.19 2 | 3 | package box 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 | applyDebugListenOption(options) 14 | if options.GCPercent != nil { 15 | debug.SetGCPercent(*options.GCPercent) 16 | } 17 | if options.MaxStack != nil { 18 | debug.SetMaxStack(*options.MaxStack) 19 | } 20 | if options.MaxThreads != nil { 21 | debug.SetMaxThreads(*options.MaxThreads) 22 | } 23 | if options.PanicOnFault != nil { 24 | debug.SetPanicOnFault(*options.PanicOnFault) 25 | } 26 | if options.TraceBack != "" { 27 | debug.SetTraceback(options.TraceBack) 28 | } 29 | if options.MemoryLimit != 0 { 30 | debug.SetMemoryLimit(int64(float64(options.MemoryLimit) / 1.5)) 31 | conntrack.MemoryLimit = uint64(options.MemoryLimit) 32 | } 33 | if options.OOMKiller != nil { 34 | conntrack.KillerEnabled = *options.OOMKiller 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /route/rule_item_wifi_bssid.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | F "github.com/sagernet/sing/common/format" 8 | ) 9 | 10 | var _ RuleItem = (*WIFIBSSIDItem)(nil) 11 | 12 | type WIFIBSSIDItem struct { 13 | bssidList []string 14 | bssidMap map[string]bool 15 | router adapter.Router 16 | } 17 | 18 | func NewWIFIBSSIDItem(router adapter.Router, bssidList []string) *WIFIBSSIDItem { 19 | bssidMap := make(map[string]bool) 20 | for _, bssid := range bssidList { 21 | bssidMap[bssid] = true 22 | } 23 | return &WIFIBSSIDItem{ 24 | bssidList, 25 | bssidMap, 26 | router, 27 | } 28 | } 29 | 30 | func (r *WIFIBSSIDItem) Match(metadata *adapter.InboundContext) bool { 31 | return r.bssidMap[r.router.WIFIState().BSSID] 32 | } 33 | 34 | func (r *WIFIBSSIDItem) String() string { 35 | if len(r.bssidList) == 1 { 36 | return F.ToString("wifi_bssid=", r.bssidList[0]) 37 | } 38 | return F.ToString("wifi_bssid=[", strings.Join(r.bssidList, " "), "]") 39 | } 40 | -------------------------------------------------------------------------------- /experimental/libbox/platform/interface.go: -------------------------------------------------------------------------------- 1 | package platform 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | "github.com/sagernet/sing-box/common/process" 8 | "github.com/sagernet/sing-box/option" 9 | "github.com/sagernet/sing-tun" 10 | "github.com/sagernet/sing/common/control" 11 | "github.com/sagernet/sing/common/logger" 12 | ) 13 | 14 | type Interface interface { 15 | Initialize(ctx context.Context, router adapter.Router) error 16 | UsePlatformAutoDetectInterfaceControl() bool 17 | AutoDetectInterfaceControl() control.Func 18 | OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) 19 | UsePlatformDefaultInterfaceMonitor() bool 20 | CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor 21 | UsePlatformInterfaceGetter() bool 22 | Interfaces() ([]control.Interface, error) 23 | UnderNetworkExtension() bool 24 | IncludeAllNetworks() bool 25 | ClearDNSCache() 26 | ReadWIFIState() adapter.WIFIState 27 | process.Searcher 28 | } 29 | -------------------------------------------------------------------------------- /experimental/libbox/remote_profile.go: -------------------------------------------------------------------------------- 1 | package libbox 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | func GenerateRemoteProfileImportLink(name string, remoteURL string) string { 8 | importLink := &url.URL{ 9 | Scheme: "sing-box", 10 | Host: "import-remote-profile", 11 | RawQuery: url.Values{"url": []string{remoteURL}}.Encode(), 12 | Fragment: name, 13 | } 14 | return importLink.String() 15 | } 16 | 17 | type ImportRemoteProfile struct { 18 | Name string 19 | URL string 20 | Host string 21 | } 22 | 23 | func ParseRemoteProfileImportLink(importLink string) (*ImportRemoteProfile, error) { 24 | importURL, err := url.Parse(importLink) 25 | if err != nil { 26 | return nil, err 27 | } 28 | remoteURL, err := url.Parse(importURL.Query().Get("url")) 29 | if err != nil { 30 | return nil, err 31 | } 32 | name := importURL.Fragment 33 | if name == "" { 34 | name = remoteURL.Host 35 | } 36 | return &ImportRemoteProfile{ 37 | Name: name, 38 | URL: remoteURL.String(), 39 | Host: remoteURL.Host, 40 | }, nil 41 | } 42 | -------------------------------------------------------------------------------- /docs/index.zh.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 欢迎来到该 sing-box 项目的文档页。 3 | --- 4 | 5 | # :material-home: 开始 6 | 7 | 欢迎来到该 sing-box 项目的文档页。 8 | 9 | 通用代理平台。 10 | 11 | ## 授权 12 | 13 | ``` 14 | Copyright (C) 2022 by nekohasekai 15 | 16 | This program is free software: you can redistribute it and/or modify 17 | it under the terms of the GNU General Public License as published by 18 | the Free Software Foundation, either version 3 of the License, or 19 | (at your option) any later version. 20 | 21 | This program is distributed in the hope that it will be useful, 22 | but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | GNU General Public License for more details. 25 | 26 | You should have received a copy of the GNU General Public License 27 | along with this program. If not, see . 28 | 29 | In addition, no derivative work may use the name or imply association 30 | with this application without prior consent. 31 | ``` 32 | -------------------------------------------------------------------------------- /route/rule_item_network.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | F "github.com/sagernet/sing/common/format" 8 | ) 9 | 10 | var _ RuleItem = (*NetworkItem)(nil) 11 | 12 | type NetworkItem struct { 13 | networks []string 14 | networkMap map[string]bool 15 | } 16 | 17 | func NewNetworkItem(networks []string) *NetworkItem { 18 | networkMap := make(map[string]bool) 19 | for _, network := range networks { 20 | networkMap[network] = true 21 | } 22 | return &NetworkItem{ 23 | networks: networks, 24 | networkMap: networkMap, 25 | } 26 | } 27 | 28 | func (r *NetworkItem) Match(metadata *adapter.InboundContext) bool { 29 | return r.networkMap[metadata.Network] 30 | } 31 | 32 | func (r *NetworkItem) String() string { 33 | description := "network=" 34 | 35 | pLen := len(r.networks) 36 | if pLen == 1 { 37 | description += F.ToString(r.networks[0]) 38 | } else { 39 | description += "[" + strings.Join(F.MapToString(r.networks), " ") + "]" 40 | } 41 | return description 42 | } 43 | -------------------------------------------------------------------------------- /test/wrapper_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | 6 | C "github.com/sagernet/sing-box/constant" 7 | "github.com/sagernet/sing-box/option" 8 | 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestOptionsWrapper(t *testing.T) { 13 | inbound := option.Inbound{ 14 | Type: C.TypeHTTP, 15 | HTTPOptions: option.HTTPMixedInboundOptions{ 16 | InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ 17 | TLS: &option.InboundTLSOptions{ 18 | Enabled: true, 19 | }, 20 | }, 21 | }, 22 | } 23 | rawOptions, err := inbound.RawOptions() 24 | require.NoError(t, err) 25 | tlsOptionsWrapper, loaded := rawOptions.(option.InboundTLSOptionsWrapper) 26 | require.True(t, loaded, "find inbound tls options") 27 | tlsOptions := tlsOptionsWrapper.TakeInboundTLSOptions() 28 | require.NotNil(t, tlsOptions, "find inbound tls options") 29 | tlsOptions.Enabled = false 30 | tlsOptionsWrapper.ReplaceInboundTLSOptions(tlsOptions) 31 | require.False(t, inbound.HTTPOptions.TLS.Enabled, "replace tls enabled") 32 | } 33 | -------------------------------------------------------------------------------- /cmd/sing-box/cmd_generate_vapid.go: -------------------------------------------------------------------------------- 1 | //go:build go1.20 2 | 3 | package main 4 | 5 | import ( 6 | "crypto/ecdh" 7 | "crypto/rand" 8 | "encoding/base64" 9 | "os" 10 | 11 | "github.com/sagernet/sing-box/log" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var commandGenerateVAPIDKeyPair = &cobra.Command{ 17 | Use: "vapid-keypair", 18 | Short: "Generate VAPID key pair", 19 | Run: func(cmd *cobra.Command, args []string) { 20 | err := generateVAPIDKeyPair() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | }, 25 | } 26 | 27 | func init() { 28 | commandGenerate.AddCommand(commandGenerateVAPIDKeyPair) 29 | } 30 | 31 | func generateVAPIDKeyPair() error { 32 | privateKey, err := ecdh.P256().GenerateKey(rand.Reader) 33 | if err != nil { 34 | return err 35 | } 36 | publicKey := privateKey.PublicKey() 37 | os.Stdout.WriteString("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey.Bytes()) + "\n") 38 | os.Stdout.WriteString("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey.Bytes()) + "\n") 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /common/settings/proxy_windows.go: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | import ( 4 | "context" 5 | 6 | M "github.com/sagernet/sing/common/metadata" 7 | "github.com/sagernet/sing/common/wininet" 8 | ) 9 | 10 | type WindowsSystemProxy struct { 11 | serverAddr M.Socksaddr 12 | supportSOCKS bool 13 | isEnabled bool 14 | } 15 | 16 | func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*WindowsSystemProxy, error) { 17 | return &WindowsSystemProxy{ 18 | serverAddr: serverAddr, 19 | supportSOCKS: supportSOCKS, 20 | }, nil 21 | } 22 | 23 | func (p *WindowsSystemProxy) IsEnabled() bool { 24 | return p.isEnabled 25 | } 26 | 27 | func (p *WindowsSystemProxy) Enable() error { 28 | err := wininet.SetSystemProxy("http://"+p.serverAddr.String(), "") 29 | if err != nil { 30 | return err 31 | } 32 | p.isEnabled = true 33 | return nil 34 | } 35 | 36 | func (p *WindowsSystemProxy) Disable() error { 37 | err := wininet.ClearSystemProxy() 38 | if err != nil { 39 | return err 40 | } 41 | p.isEnabled = false 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /common/conntrack/track.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | 7 | "github.com/sagernet/sing/common" 8 | "github.com/sagernet/sing/common/x/list" 9 | ) 10 | 11 | var ( 12 | connAccess sync.RWMutex 13 | openConnection list.List[io.Closer] 14 | ) 15 | 16 | func Count() int { 17 | if !Enabled { 18 | return 0 19 | } 20 | return openConnection.Len() 21 | } 22 | 23 | func List() []io.Closer { 24 | if !Enabled { 25 | return nil 26 | } 27 | connAccess.RLock() 28 | defer connAccess.RUnlock() 29 | connList := make([]io.Closer, 0, openConnection.Len()) 30 | for element := openConnection.Front(); element != nil; element = element.Next() { 31 | connList = append(connList, element.Value) 32 | } 33 | return connList 34 | } 35 | 36 | func Close() { 37 | if !Enabled { 38 | return 39 | } 40 | connAccess.Lock() 41 | defer connAccess.Unlock() 42 | for element := openConnection.Front(); element != nil; element = element.Next() { 43 | common.Close(element.Value) 44 | element.Value = nil 45 | } 46 | openConnection.Init() 47 | } 48 | -------------------------------------------------------------------------------- /common/dialer/router.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "context" 5 | "net" 6 | 7 | "github.com/sagernet/sing-box/adapter" 8 | M "github.com/sagernet/sing/common/metadata" 9 | N "github.com/sagernet/sing/common/network" 10 | ) 11 | 12 | type RouterDialer struct { 13 | router adapter.Router 14 | } 15 | 16 | func NewRouter(router adapter.Router) N.Dialer { 17 | return &RouterDialer{router: router} 18 | } 19 | 20 | func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { 21 | dialer, err := d.router.DefaultOutbound(network) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return dialer.DialContext(ctx, network, destination) 26 | } 27 | 28 | func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { 29 | dialer, err := d.router.DefaultOutbound(N.NetworkUDP) 30 | if err != nil { 31 | return nil, err 32 | } 33 | return dialer.ListenPacket(ctx, destination) 34 | } 35 | 36 | func (d *RouterDialer) Upstream() any { 37 | return d.router 38 | } 39 | -------------------------------------------------------------------------------- /constant/path.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/sagernet/sing/common/rw" 8 | ) 9 | 10 | const dirName = "sing-box" 11 | 12 | var resourcePaths []string 13 | 14 | func FindPath(name string) (string, bool) { 15 | name = os.ExpandEnv(name) 16 | if rw.IsFile(name) { 17 | return name, true 18 | } 19 | for _, dir := range resourcePaths { 20 | if path := filepath.Join(dir, dirName, name); rw.IsFile(path) { 21 | return path, true 22 | } 23 | if path := filepath.Join(dir, name); rw.IsFile(path) { 24 | return path, true 25 | } 26 | } 27 | return name, false 28 | } 29 | 30 | func init() { 31 | resourcePaths = append(resourcePaths, ".") 32 | if home := os.Getenv("HOME"); home != "" { 33 | resourcePaths = append(resourcePaths, home) 34 | } 35 | if userConfigDir, err := os.UserConfigDir(); err == nil { 36 | resourcePaths = append(resourcePaths, userConfigDir) 37 | } 38 | if userCacheDir, err := os.UserCacheDir(); err == nil { 39 | resourcePaths = append(resourcePaths, userCacheDir) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docs/configuration/outbound/tor.md: -------------------------------------------------------------------------------- 1 | ### Structure 2 | 3 | ```json 4 | { 5 | "type": "tor", 6 | "tag": "tor-out", 7 | 8 | "executable_path": "/usr/bin/tor", 9 | "extra_args": [], 10 | "data_directory": "$HOME/.cache/tor", 11 | "torrc": { 12 | "ClientOnly": 1 13 | }, 14 | 15 | ... // Dial Fields 16 | } 17 | ``` 18 | 19 | !!! info "" 20 | 21 | Embedded Tor is not included by default, see [Installation](/installation/build-from-source/#build-tags). 22 | 23 | ### Fields 24 | 25 | #### executable_path 26 | 27 | The path to the Tor executable. 28 | 29 | Embedded Tor will be ignored if set. 30 | 31 | #### extra_args 32 | 33 | List of extra arguments passed to the Tor instance when started. 34 | 35 | #### data_directory 36 | 37 | ==Recommended== 38 | 39 | The data directory of Tor. 40 | 41 | Each start will be very slow if not specified. 42 | 43 | #### torrc 44 | 45 | Map of torrc options. 46 | 47 | See [tor(1)](https://linux.die.net/man/1/tor) for details. 48 | 49 | ### Dial Fields 50 | 51 | See [Dial Fields](/configuration/shared/dial/) for details. 52 | -------------------------------------------------------------------------------- /docs/configuration/outbound/urltest.md: -------------------------------------------------------------------------------- 1 | ### Structure 2 | 3 | ```json 4 | { 5 | "type": "urltest", 6 | "tag": "auto", 7 | 8 | "outbounds": [ 9 | "proxy-a", 10 | "proxy-b", 11 | "proxy-c" 12 | ], 13 | "url": "", 14 | "interval": "", 15 | "tolerance": 0, 16 | "idle_timeout": "", 17 | "interrupt_exist_connections": false 18 | } 19 | ``` 20 | 21 | ### Fields 22 | 23 | #### outbounds 24 | 25 | ==Required== 26 | 27 | List of outbound tags to test. 28 | 29 | #### url 30 | 31 | The URL to test. `https://www.gstatic.com/generate_204` will be used if empty. 32 | 33 | #### interval 34 | 35 | The test interval. `3m` will be used if empty. 36 | 37 | #### tolerance 38 | 39 | The test tolerance in milliseconds. `50` will be used if empty. 40 | 41 | #### idle_timeout 42 | 43 | The idle timeout. `30m` will be used if empty. 44 | 45 | #### interrupt_exist_connections 46 | 47 | Interrupt existing connections when the selected outbound has changed. 48 | 49 | Only inbound connections are affected by this setting, internal connections will always be interrupted. 50 | -------------------------------------------------------------------------------- /common/pipelistener/listener.go: -------------------------------------------------------------------------------- 1 | package pipelistener 2 | 3 | import ( 4 | "io" 5 | "net" 6 | ) 7 | 8 | var _ net.Listener = (*Listener)(nil) 9 | 10 | type Listener struct { 11 | pipe chan net.Conn 12 | done chan struct{} 13 | } 14 | 15 | func New(channelSize int) *Listener { 16 | return &Listener{ 17 | pipe: make(chan net.Conn, channelSize), 18 | done: make(chan struct{}), 19 | } 20 | } 21 | 22 | func (l *Listener) Serve(conn net.Conn) { 23 | l.pipe <- conn 24 | } 25 | 26 | func (l *Listener) Accept() (net.Conn, error) { 27 | select { 28 | case conn := <-l.pipe: 29 | return conn, nil 30 | case <-l.done: 31 | return nil, io.ErrClosedPipe 32 | } 33 | } 34 | 35 | func (l *Listener) Close() error { 36 | select { 37 | case <-l.done: 38 | return io.ErrClosedPipe 39 | default: 40 | } 41 | close(l.done) 42 | return nil 43 | } 44 | 45 | func (l *Listener) Addr() net.Addr { 46 | return addr{} 47 | } 48 | 49 | type addr struct{} 50 | 51 | func (a addr) Network() string { 52 | return "pipe" 53 | } 54 | 55 | func (a addr) String() string { 56 | return "pipe" 57 | } 58 | -------------------------------------------------------------------------------- /cmd/sing-box/cmd_geosite.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/sagernet/sing-box/common/geosite" 5 | "github.com/sagernet/sing-box/log" 6 | E "github.com/sagernet/sing/common/exceptions" 7 | 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var ( 12 | commandGeoSiteFlagFile string 13 | geositeReader *geosite.Reader 14 | geositeCodeList []string 15 | ) 16 | 17 | var commandGeoSite = &cobra.Command{ 18 | Use: "geosite", 19 | Short: "Geosite tools", 20 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 21 | err := geositePreRun() 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | }, 26 | } 27 | 28 | func init() { 29 | commandGeoSite.PersistentFlags().StringVarP(&commandGeoSiteFlagFile, "file", "f", "geosite.db", "geosite file") 30 | mainCommand.AddCommand(commandGeoSite) 31 | } 32 | 33 | func geositePreRun() error { 34 | reader, codeList, err := geosite.Open(commandGeoSiteFlagFile) 35 | if err != nil { 36 | return E.Cause(err, "open geosite file") 37 | } 38 | geositeReader = reader 39 | geositeCodeList = codeList 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /common/conntrack/conn.go: -------------------------------------------------------------------------------- 1 | package conntrack 2 | 3 | import ( 4 | "io" 5 | "net" 6 | 7 | "github.com/sagernet/sing/common/x/list" 8 | ) 9 | 10 | type Conn struct { 11 | net.Conn 12 | element *list.Element[io.Closer] 13 | } 14 | 15 | func NewConn(conn net.Conn) (net.Conn, error) { 16 | connAccess.Lock() 17 | element := openConnection.PushBack(conn) 18 | connAccess.Unlock() 19 | if KillerEnabled { 20 | err := KillerCheck() 21 | if err != nil { 22 | conn.Close() 23 | return nil, err 24 | } 25 | } 26 | return &Conn{ 27 | Conn: conn, 28 | element: element, 29 | }, nil 30 | } 31 | 32 | func (c *Conn) Close() error { 33 | if c.element.Value != nil { 34 | connAccess.Lock() 35 | if c.element.Value != nil { 36 | openConnection.Remove(c.element) 37 | c.element.Value = nil 38 | } 39 | connAccess.Unlock() 40 | } 41 | return c.Conn.Close() 42 | } 43 | 44 | func (c *Conn) Upstream() any { 45 | return c.Conn 46 | } 47 | 48 | func (c *Conn) ReaderReplaceable() bool { 49 | return true 50 | } 51 | 52 | func (c *Conn) WriterReplaceable() bool { 53 | return true 54 | } 55 | -------------------------------------------------------------------------------- /docs/configuration/inbound/vless.md: -------------------------------------------------------------------------------- 1 | ### Structure 2 | 3 | ```json 4 | { 5 | "type": "vless", 6 | "tag": "vless-in", 7 | 8 | ... // Listen Fields 9 | 10 | "users": [ 11 | { 12 | "name": "sekai", 13 | "uuid": "bf000d23-0752-40b4-affe-68f7707a9661", 14 | "flow": "" 15 | } 16 | ], 17 | "tls": {}, 18 | "multiplex": {}, 19 | "transport": {} 20 | } 21 | ``` 22 | 23 | ### Listen Fields 24 | 25 | See [Listen Fields](/configuration/shared/listen/) for details. 26 | 27 | ### Fields 28 | 29 | #### users 30 | 31 | ==Required== 32 | 33 | VLESS users. 34 | 35 | #### users.uuid 36 | 37 | ==Required== 38 | 39 | VLESS user id. 40 | 41 | #### users.flow 42 | 43 | VLESS Sub-protocol. 44 | 45 | Available values: 46 | 47 | * `xtls-rprx-vision` 48 | 49 | #### tls 50 | 51 | TLS configuration, see [TLS](/configuration/shared/tls/#inbound). 52 | 53 | #### multiplex 54 | 55 | See [Multiplex](/configuration/shared/multiplex#inbound) for details. 56 | 57 | #### transport 58 | 59 | V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport/). 60 | -------------------------------------------------------------------------------- /cmd/sing-box/cmd_generate_ech.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sagernet/sing-box/common/tls" 7 | "github.com/sagernet/sing-box/log" 8 | 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var pqSignatureSchemesEnabled bool 13 | 14 | var commandGenerateECHKeyPair = &cobra.Command{ 15 | Use: "ech-keypair ", 16 | Short: "Generate TLS ECH key pair", 17 | Args: cobra.ExactArgs(1), 18 | Run: func(cmd *cobra.Command, args []string) { 19 | err := generateECHKeyPair(args[0]) 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | }, 24 | } 25 | 26 | func init() { 27 | commandGenerateECHKeyPair.Flags().BoolVar(&pqSignatureSchemesEnabled, "pq-signature-schemes-enabled", false, "Enable PQ signature schemes") 28 | commandGenerate.AddCommand(commandGenerateECHKeyPair) 29 | } 30 | 31 | func generateECHKeyPair(serverName string) error { 32 | configPem, keyPem, err := tls.ECHKeygenDefault(serverName, pqSignatureSchemesEnabled) 33 | if err != nil { 34 | return err 35 | } 36 | os.Stdout.WriteString(configPem) 37 | os.Stdout.WriteString(keyPem) 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /docs/configuration/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | sing-box uses JSON for configuration files. 4 | 5 | ### Structure 6 | 7 | ```json 8 | { 9 | "log": {}, 10 | "dns": {}, 11 | "ntp": {}, 12 | "inbounds": [], 13 | "outbounds": [], 14 | "route": {}, 15 | "experimental": {} 16 | } 17 | ``` 18 | 19 | ### Fields 20 | 21 | | Key | Format | 22 | |----------------|---------------------------------| 23 | | `log` | [Log](./log/) | 24 | | `dns` | [DNS](./dns/) | 25 | | `ntp` | [NTP](./ntp/) | 26 | | `inbounds` | [Inbound](./inbound/) | 27 | | `outbounds` | [Outbound](./outbound/) | 28 | | `route` | [Route](./route/) | 29 | | `experimental` | [Experimental](./experimental/) | 30 | 31 | ### Check 32 | 33 | ```bash 34 | sing-box check 35 | ``` 36 | 37 | ### Format 38 | 39 | ```bash 40 | sing-box format -w -c config.json -D config_directory 41 | ``` 42 | 43 | ### Merge 44 | 45 | ```bash 46 | sing-box merge output.json -c config.json -D config_directory 47 | ``` -------------------------------------------------------------------------------- /inbound/naive_quic.go: -------------------------------------------------------------------------------- 1 | //go:build with_quic 2 | 3 | package inbound 4 | 5 | import ( 6 | "github.com/sagernet/quic-go" 7 | "github.com/sagernet/quic-go/http3" 8 | "github.com/sagernet/sing-quic" 9 | E "github.com/sagernet/sing/common/exceptions" 10 | ) 11 | 12 | func (n *Naive) configureHTTP3Listener() error { 13 | err := qtls.ConfigureHTTP3(n.tlsConfig) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | udpConn, err := n.ListenUDP() 19 | if err != nil { 20 | return err 21 | } 22 | 23 | quicListener, err := qtls.ListenEarly(udpConn, n.tlsConfig, &quic.Config{ 24 | MaxIncomingStreams: 1 << 60, 25 | Allow0RTT: true, 26 | }) 27 | if err != nil { 28 | udpConn.Close() 29 | return err 30 | } 31 | 32 | h3Server := &http3.Server{ 33 | Port: int(n.listenOptions.ListenPort), 34 | Handler: n, 35 | } 36 | 37 | go func() { 38 | sErr := h3Server.ServeListener(quicListener) 39 | udpConn.Close() 40 | if sErr != nil && !E.IsClosedOrCanceled(sErr) { 41 | n.logger.Error("http3 server serve error: ", sErr) 42 | } 43 | }() 44 | 45 | n.h3Server = h3Server 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Welcome to the wiki page for the sing-box project. 3 | --- 4 | 5 | # :material-home: Home 6 | 7 | Welcome to the wiki page for the sing-box project. 8 | 9 | The universal proxy platform. 10 | 11 | ## License 12 | 13 | ``` 14 | Copyright (C) 2022 by nekohasekai 15 | 16 | This program is free software: you can redistribute it and/or modify 17 | it under the terms of the GNU General Public License as published by 18 | the Free Software Foundation, either version 3 of the License, or 19 | (at your option) any later version. 20 | 21 | This program is distributed in the hope that it will be useful, 22 | but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | GNU General Public License for more details. 25 | 26 | You should have received a copy of the GNU General Public License 27 | along with this program. If not, see . 28 | 29 | In addition, no derivative work may use the name or imply association 30 | with this application without prior consent. 31 | ``` 32 | -------------------------------------------------------------------------------- /common/dialer/dialer.go: -------------------------------------------------------------------------------- 1 | package dialer 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | "github.com/sagernet/sing-box/option" 8 | "github.com/sagernet/sing-dns" 9 | N "github.com/sagernet/sing/common/network" 10 | ) 11 | 12 | func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) { 13 | if options.IsWireGuardListener { 14 | return NewDefault(router, options) 15 | } 16 | if router == nil { 17 | return NewDefault(nil, options) 18 | } 19 | var ( 20 | dialer N.Dialer 21 | err error 22 | ) 23 | if options.Detour == "" { 24 | dialer, err = NewDefault(router, options) 25 | if err != nil { 26 | return nil, err 27 | } 28 | } else { 29 | dialer = NewDetour(router, options.Detour) 30 | } 31 | domainStrategy := dns.DomainStrategy(options.DomainStrategy) 32 | if domainStrategy != dns.DomainStrategyAsIS || options.Detour == "" { 33 | dialer = NewResolveDialer( 34 | router, 35 | dialer, 36 | options.Detour == "" && !options.TCPFastOpen, 37 | domainStrategy, 38 | time.Duration(options.FallbackDelay)) 39 | } 40 | return dialer, nil 41 | } 42 | -------------------------------------------------------------------------------- /common/process/searcher_android.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "context" 5 | "net/netip" 6 | 7 | "github.com/sagernet/sing-tun" 8 | ) 9 | 10 | var _ Searcher = (*androidSearcher)(nil) 11 | 12 | type androidSearcher struct { 13 | packageManager tun.PackageManager 14 | } 15 | 16 | func NewSearcher(config Config) (Searcher, error) { 17 | return &androidSearcher{config.PackageManager}, nil 18 | } 19 | 20 | func (s *androidSearcher) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*Info, error) { 21 | _, uid, err := resolveSocketByNetlink(network, source, destination) 22 | if err != nil { 23 | return nil, err 24 | } 25 | if sharedPackage, loaded := s.packageManager.SharedPackageByID(uid % 100000); loaded { 26 | return &Info{ 27 | UserId: int32(uid), 28 | PackageName: sharedPackage, 29 | }, nil 30 | } 31 | if packageName, loaded := s.packageManager.PackageByID(uid % 100000); loaded { 32 | return &Info{ 33 | UserId: int32(uid), 34 | PackageName: packageName, 35 | }, nil 36 | } 37 | return &Info{UserId: int32(uid)}, nil 38 | } 39 | -------------------------------------------------------------------------------- /docs/manual/misc/tunnelvision.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: material/book-lock-open 3 | --- 4 | 5 | # TunnelVision 6 | 7 | TunnelVision is an attack that uses DHCP option 121 to set higher priority routes 8 | so that traffic does not go through the VPN. 9 | 10 | Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-3661 11 | 12 | ## Status 13 | 14 | ### Android 15 | 16 | Android does not handle DHCP option 121 and is not affected. 17 | 18 | ### Apple platforms 19 | 20 | Update [sing-box graphical client](/clients/apple/#download) to `1.9.0-rc.16` or newer, 21 | then enable `includeAllNetworks` in `Settings` — `Packet Tunnel` and you will be unaffected. 22 | 23 | Note: when `includeAllNetworks` is enabled, the default TUN stack is changed to `gvisor`, 24 | and the `system` and `mixed` stacks are not available. 25 | 26 | ### Linux 27 | 28 | Update sing-box to `1.9.0-rc.16` or newer, rules generated by `auto-route` are unaffected. 29 | 30 | ### Windows 31 | 32 | No solution yet. 33 | 34 | ## Workarounds 35 | 36 | * Don't connect to untrusted networks 37 | * Relay untrusted network through another device 38 | * Just ignore it 39 | -------------------------------------------------------------------------------- /option/shadowtls.go: -------------------------------------------------------------------------------- 1 | package option 2 | 3 | type ShadowTLSInboundOptions struct { 4 | ListenOptions 5 | Version int `json:"version,omitempty"` 6 | Password string `json:"password,omitempty"` 7 | Users []ShadowTLSUser `json:"users,omitempty"` 8 | Handshake ShadowTLSHandshakeOptions `json:"handshake,omitempty"` 9 | HandshakeForServerName map[string]ShadowTLSHandshakeOptions `json:"handshake_for_server_name,omitempty"` 10 | StrictMode bool `json:"strict_mode,omitempty"` 11 | } 12 | 13 | type ShadowTLSUser struct { 14 | Name string `json:"name,omitempty"` 15 | Password string `json:"password,omitempty"` 16 | } 17 | 18 | type ShadowTLSHandshakeOptions struct { 19 | ServerOptions 20 | DialerOptions 21 | } 22 | 23 | type ShadowTLSOutboundOptions struct { 24 | DialerOptions 25 | ServerOptions 26 | Version int `json:"version,omitempty"` 27 | Password string `json:"password,omitempty"` 28 | OutboundTLSOptionsContainer 29 | } 30 | -------------------------------------------------------------------------------- /docs/configuration/outbound/ssh.zh.md: -------------------------------------------------------------------------------- 1 | ### 结构 2 | 3 | ```json 4 | { 5 | "type": "ssh", 6 | "tag": "ssh-out", 7 | 8 | "server": "127.0.0.1", 9 | "server_port": 22, 10 | "user": "root", 11 | "password": "admin", 12 | "private_key": "", 13 | "private_key_path": "$HOME/.ssh/id_rsa", 14 | "private_key_passphrase": "", 15 | "host_key": [ 16 | "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdH..." 17 | ], 18 | "host_key_algorithms": [], 19 | "client_version": "SSH-2.0-OpenSSH_7.4p1", 20 | 21 | ... // 拨号字段 22 | } 23 | ``` 24 | 25 | ### 字段 26 | 27 | #### server 28 | 29 | ==必填== 30 | 31 | 服务器地址。 32 | 33 | #### server_port 34 | 35 | 服务器端口,默认使用 22。 36 | 37 | #### user 38 | 39 | SSH 用户, 默认使用 root。 40 | 41 | #### password 42 | 43 | 密码。 44 | 45 | #### private_key 46 | 47 | 密钥。 48 | 49 | #### private_key_path 50 | 51 | 密钥路径。 52 | 53 | #### private_key_passphrase 54 | 55 | 密钥密码。 56 | 57 | #### host_key 58 | 59 | 主机密钥,留空接受所有。 60 | 61 | #### host_key_algorithms 62 | 63 | 主机密钥算法。 64 | 65 | #### client_version 66 | 67 | 客户端版本,默认使用随机值。 68 | 69 | ### 拨号字段 70 | 71 | 参阅 [拨号字段](/zh/configuration/shared/dial/)。 72 | -------------------------------------------------------------------------------- /route/rule_item_ip_is_private.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "net/netip" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | N "github.com/sagernet/sing/common/network" 8 | ) 9 | 10 | var _ RuleItem = (*IPIsPrivateItem)(nil) 11 | 12 | type IPIsPrivateItem struct { 13 | isSource bool 14 | } 15 | 16 | func NewIPIsPrivateItem(isSource bool) *IPIsPrivateItem { 17 | return &IPIsPrivateItem{isSource} 18 | } 19 | 20 | func (r *IPIsPrivateItem) Match(metadata *adapter.InboundContext) bool { 21 | var destination netip.Addr 22 | if r.isSource { 23 | destination = metadata.Source.Addr 24 | } else { 25 | destination = metadata.Destination.Addr 26 | } 27 | if destination.IsValid() { 28 | return !N.IsPublicAddr(destination) 29 | } 30 | if !r.isSource { 31 | for _, destinationAddress := range metadata.DestinationAddresses { 32 | if !N.IsPublicAddr(destinationAddress) { 33 | return true 34 | } 35 | } 36 | } 37 | return false 38 | } 39 | 40 | func (r *IPIsPrivateItem) String() string { 41 | if r.isSource { 42 | return "source_ip_is_private=true" 43 | } else { 44 | return "ip_is_private=true" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cmd/sing-box/cmd_geoip.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/sagernet/sing-box/log" 5 | E "github.com/sagernet/sing/common/exceptions" 6 | 7 | "github.com/oschwald/maxminddb-golang" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var ( 12 | geoipReader *maxminddb.Reader 13 | commandGeoIPFlagFile string 14 | ) 15 | 16 | var commandGeoip = &cobra.Command{ 17 | Use: "geoip", 18 | Short: "GeoIP tools", 19 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 20 | err := geoipPreRun() 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | }, 25 | } 26 | 27 | func init() { 28 | commandGeoip.PersistentFlags().StringVarP(&commandGeoIPFlagFile, "file", "f", "geoip.db", "geoip file") 29 | mainCommand.AddCommand(commandGeoip) 30 | } 31 | 32 | func geoipPreRun() error { 33 | reader, err := maxminddb.Open(commandGeoIPFlagFile) 34 | if err != nil { 35 | return err 36 | } 37 | if reader.Metadata.DatabaseType != "sing-geoip" { 38 | reader.Close() 39 | return E.New("incorrect database type, expected sing-geoip, got ", reader.Metadata.DatabaseType) 40 | } 41 | geoipReader = reader 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /cmd/sing-box/cmd_tools_fetch_http3.go: -------------------------------------------------------------------------------- 1 | //go:build with_quic 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "crypto/tls" 8 | "net/http" 9 | 10 | "github.com/sagernet/quic-go" 11 | "github.com/sagernet/quic-go/http3" 12 | box "github.com/sagernet/sing-box" 13 | "github.com/sagernet/sing/common/bufio" 14 | M "github.com/sagernet/sing/common/metadata" 15 | N "github.com/sagernet/sing/common/network" 16 | ) 17 | 18 | func initializeHTTP3Client(instance *box.Box) error { 19 | dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound) 20 | if err != nil { 21 | return err 22 | } 23 | http3Client = &http.Client{ 24 | Transport: &http3.RoundTripper{ 25 | Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { 26 | destination := M.ParseSocksaddr(addr) 27 | udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination) 28 | if dErr != nil { 29 | return nil, dErr 30 | } 31 | return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg) 32 | }, 33 | }, 34 | } 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /route/rule_item_outbound.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | F "github.com/sagernet/sing/common/format" 8 | ) 9 | 10 | var _ RuleItem = (*OutboundItem)(nil) 11 | 12 | type OutboundItem struct { 13 | outbounds []string 14 | outboundMap map[string]bool 15 | matchAny bool 16 | } 17 | 18 | func NewOutboundRule(outbounds []string) *OutboundItem { 19 | rule := &OutboundItem{outbounds: outbounds, outboundMap: make(map[string]bool)} 20 | for _, outbound := range outbounds { 21 | if outbound == "any" { 22 | rule.matchAny = true 23 | } else { 24 | rule.outboundMap[outbound] = true 25 | } 26 | } 27 | return rule 28 | } 29 | 30 | func (r *OutboundItem) Match(metadata *adapter.InboundContext) bool { 31 | if r.matchAny && metadata.Outbound != "" { 32 | return true 33 | } 34 | return r.outboundMap[metadata.Outbound] 35 | } 36 | 37 | func (r *OutboundItem) String() string { 38 | if len(r.outbounds) == 1 { 39 | return F.ToString("outbound=", r.outbounds[0]) 40 | } else { 41 | return F.ToString("outbound=[", strings.Join(r.outbounds, " "), "]") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /include/quic_stub.go: -------------------------------------------------------------------------------- 1 | //go:build !with_quic 2 | 3 | package include 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/sagernet/sing-box/adapter" 9 | "github.com/sagernet/sing-box/common/tls" 10 | C "github.com/sagernet/sing-box/constant" 11 | "github.com/sagernet/sing-box/option" 12 | "github.com/sagernet/sing-box/transport/v2ray" 13 | "github.com/sagernet/sing-dns" 14 | M "github.com/sagernet/sing/common/metadata" 15 | N "github.com/sagernet/sing/common/network" 16 | ) 17 | 18 | func init() { 19 | dns.RegisterTransport([]string{"quic", "h3"}, func(options dns.TransportOptions) (dns.Transport, error) { 20 | return nil, C.ErrQUICNotIncluded 21 | }) 22 | v2ray.RegisterQUICConstructor( 23 | func(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) { 24 | return nil, C.ErrQUICNotIncluded 25 | }, 26 | func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) { 27 | return nil, C.ErrQUICNotIncluded 28 | }, 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /route/rule_item_user_id.go: -------------------------------------------------------------------------------- 1 | package route 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/sagernet/sing-box/adapter" 7 | F "github.com/sagernet/sing/common/format" 8 | ) 9 | 10 | var _ RuleItem = (*UserIdItem)(nil) 11 | 12 | type UserIdItem struct { 13 | userIds []int32 14 | userIdMap map[int32]bool 15 | } 16 | 17 | func NewUserIDItem(userIdList []int32) *UserIdItem { 18 | rule := &UserIdItem{ 19 | userIds: userIdList, 20 | userIdMap: make(map[int32]bool), 21 | } 22 | for _, userId := range userIdList { 23 | rule.userIdMap[userId] = true 24 | } 25 | return rule 26 | } 27 | 28 | func (r *UserIdItem) Match(metadata *adapter.InboundContext) bool { 29 | if metadata.ProcessInfo == nil || metadata.ProcessInfo.UserId == -1 { 30 | return false 31 | } 32 | return r.userIdMap[metadata.ProcessInfo.UserId] 33 | } 34 | 35 | func (r *UserIdItem) String() string { 36 | var description string 37 | pLen := len(r.userIds) 38 | if pLen == 1 { 39 | description = "user_id=" + F.ToString(r.userIds[0]) 40 | } else { 41 | description = "user_id=[" + strings.Join(F.MapToString(r.userIds), " ") + "]" 42 | } 43 | return description 44 | } 45 | --------------------------------------------------------------------------------