Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions args.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ func parsePluginOptions(s string) (opts Args, err error) {
i++
continue
}
if key == "disableAccessLog" {
err = fmt.Errorf("option %q does not accept a value", key)
return
}
// Skip the equals sign.
i++
// Read the value.
Expand Down
13 changes: 11 additions & 2 deletions docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Shadowsocks server to which it forwards traffic.
| `-mux` | `1` | Number of concurrent multiplexed WebSocket connections on the client. Set to `0` to disable WebSocket multiplexing. Ignored by QUIC and Hysteria2. |
| `-fwmark` | `0` | Set the outbound socket `SO_MARK` value. `0` disables it; nonzero values must fit in an unsigned 32-bit integer. Platform and kernel support are required. |
| `-loglevel` | `warning` | V2Ray log level: `debug`, `info`, `warning`, `error`, or `none`. An empty or unrecognized value uses `warning`. |
| `-disableAccessLog` | `false` | Disable V2Ray access logs. Access logging remains enabled by default. `-loglevel=none` still disables all V2Ray logs. |
| `-version` | `false` | Print the plugin version and Go runtime version, then exit. |

The table lists every application flag. The Go flag package also provides
Expand Down Expand Up @@ -119,6 +120,7 @@ same names use the direct CLI meanings described earlier.
| `fastOpen` | presence | Enable TCP Fast Open. The option name is camelCase. |
| `fwmark` | unsigned integer | Set the outbound socket mark; `0` disables it. |
| `loglevel` | `debug`, `info`, `warning`, `error`, `none` | Set the V2Ray log level. |
| `disableAccessLog` | presence only | Disable V2Ray access logs. Do not add `=value`; values are rejected. |
| `__android_vpn` | presence | Internal Android VPN socket-protection option. Do not use in normal desktop configurations. |

There is no SIP003 option for `-version`. The Android `__android_vpn` option
Expand All @@ -144,12 +146,19 @@ The parser removes the escape backslashes, so the values above become
`/ws;v2` and `secret=part`. For a literal backslash, use `\\` in the option
string. A bare boolean key enables the option. Boolean values are based on
presence, so `tls=false` still enables TLS; omit `tls` to leave it disabled.
`disableAccessLog` is a presence-only opt-out: use the bare key, and do not
provide a value.

For duplicate keys, the first value is used. Unknown keys are ignored. Direct
command-line flags are parsed first; complete SIP003 endpoint variables then
override matching endpoints, and recognized `SS_PLUGIN_OPTIONS` entries
override matching values. If a plugin option is absent, its command-line value
or default remains in effect.
override matching values. `disableAccessLog` is an opt-out: if the CLI flag is
true or the SIP003 key is present, access logs are disabled. Either source can
request disabling; neither can re-enable logs disabled by the other. If the
key is absent and the CLI flag is false, access logs remain enabled.

With all SIP003 endpoint variables present, malformed `SS_PLUGIN_OPTIONS`
causes startup to fail with a configuration error.

## Transport modes

Expand Down
205 changes: 107 additions & 98 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,26 @@ const maxUint32 uint64 = 1<<32 - 1
var (
VERSION = "custom"

vpn = flag.Bool("V", false, "Run in VPN mode.")
fastOpen = flag.Bool("fast-open", false, "Enable TCP fast open.")
localAddr = flag.String("localAddr", "127.0.0.1", "local address to listen on.")
localPort = flag.String("localPort", "1984", "local port to listen on.")
remoteAddr = flag.String("remoteAddr", "127.0.0.1", "remote address to forward.")
remotePort = flag.String("remotePort", "1080", "remote port to forward.")
path = flag.String("path", "/", "URL path for websocket.")
host = flag.String("host", "cloudfront.com", "Hostname for server.")
tlsEnabled = flag.Bool("tls", false, "Enable TLS.")
cert = flag.String("cert", "", "Path to TLS certificate file. Overrides certRaw. Server default: ~/.acme.sh/{host}/fullchain.cer; tries {host}_ecc when cert, certRaw, and key are unset and the default pair is missing.")
certRaw = flag.String("certRaw", "", "Raw TLS certificate content. Intended only for Android.")
key = flag.String("key", "", "(server) Path to TLS key file. Default: ~/.acme.sh/{host}/{host}.key; uses the same directory as the automatically selected certificate.")
mode = flag.String("mode", "websocket", "Transport mode: websocket, quic, hysteria2 (QUIC and Hysteria2 enforce TLS).")
password = flag.String("password", "", "Optional Hysteria2 transport password, separate from the Shadowsocks password.")
mux = flag.Uint("mux", 1, "Concurrent multiplexed connections (websocket client mode only).")
server = flag.Bool("server", false, "Run in server mode")
logLevel = flag.String("loglevel", "", "loglevel for v2ray: debug, info, warning (default), error, none.")
version = flag.Bool("version", false, "Show current version of v2ray-plugin")
fwmark = flag.Uint("fwmark", 0, "Set SO_MARK option for outbound sockets.")
vpn = flag.Bool("V", false, "Run in VPN mode.")
fastOpen = flag.Bool("fast-open", false, "Enable TCP fast open.")
localAddr = flag.String("localAddr", "127.0.0.1", "local address to listen on.")
localPort = flag.String("localPort", "1984", "local port to listen on.")
remoteAddr = flag.String("remoteAddr", "127.0.0.1", "remote address to forward.")
remotePort = flag.String("remotePort", "1080", "remote port to forward.")
path = flag.String("path", "/", "URL path for websocket.")
host = flag.String("host", "cloudfront.com", "Hostname for server.")
tlsEnabled = flag.Bool("tls", false, "Enable TLS.")
cert = flag.String("cert", "", "Path to TLS certificate file. Overrides certRaw. Server default: ~/.acme.sh/{host}/fullchain.cer; tries {host}_ecc when cert, certRaw, and key are unset and the default pair is missing.")
certRaw = flag.String("certRaw", "", "Raw TLS certificate content. Intended only for Android.")
key = flag.String("key", "", "(server) Path to TLS key file. Default: ~/.acme.sh/{host}/{host}.key; uses the same directory as the automatically selected certificate.")
mode = flag.String("mode", "websocket", "Transport mode: websocket, quic, hysteria2 (QUIC and Hysteria2 enforce TLS).")
password = flag.String("password", "", "Optional Hysteria2 transport password, separate from the Shadowsocks password.")
mux = flag.Uint("mux", 1, "Concurrent multiplexed connections (websocket client mode only).")
server = flag.Bool("server", false, "Run in server mode")
logLevel = flag.String("loglevel", "", "loglevel for v2ray: debug, info, warning (default), error, none.")
disableAccessLog = flag.Bool("disableAccessLog", false, "Disable V2Ray access logs.")
version = flag.Bool("version", false, "Show current version of v2ray-plugin")
fwmark = flag.Uint("fwmark", 0, "Set SO_MARK option for outbound sockets.")
)

func homeDir() string {
Expand Down Expand Up @@ -112,10 +113,14 @@ func readACMECertificate(home, host string) (tls.Certificate, error) {
return tls.Certificate{}, fmt.Errorf("no acme.sh certificate pair found for %s", host)
}

func logConfig(logLevel string) *vlog.Config {
func logConfig(logLevel string, disableAccessLog bool) *vlog.Config {
accessLogType := vlog.LogType_Console
if disableAccessLog {
accessLogType = vlog.LogType_None
}
config := &vlog.Config{
Error: &vlog.LogSpecification{Type: vlog.LogType_Console, Level: clog.Severity_Warning},
Access: &vlog.LogSpecification{Type: vlog.LogType_Console},
Access: &vlog.LogSpecification{Type: accessLogType},
}
level := strings.ToLower(logLevel)
switch level {
Expand Down Expand Up @@ -255,7 +260,7 @@ func generateConfig() (*core.Config, error) {
serial.ToTypedMessage(&dispatcher.Config{}),
serial.ToTypedMessage(&proxyman.InboundConfig{}),
serial.ToTypedMessage(&proxyman.OutboundConfig{}),
serial.ToTypedMessage(logConfig(*logLevel)),
serial.ToTypedMessage(logConfig(*logLevel, *disableAccessLog)),
}

if *server {
Expand Down Expand Up @@ -317,93 +322,97 @@ func generateConfig() (*core.Config, error) {
func startV2Ray() (core.Server, error) {

opts, err := parseEnv()
if err != nil {
return nil, newError("failed to parse plugin options").Base(err)
}

if err == nil {
if c, b := opts.Get("mode"); b {
*mode = c
}
if c, b := opts.Get("password"); b {
*password = c
}
if c, b := opts.Get("mux"); b {
if i, err := strconv.ParseUint(c, 10, strconv.IntSize); err == nil {
*mux = uint(i)
} else {
logWarn("failed to parse mux, use default value")
}
}
if _, b := opts.Get("tls"); b {
*tlsEnabled = true
}
if c, b := opts.Get("host"); b {
*host = c
}
if c, b := opts.Get("path"); b {
*path = c
}
if c, b := opts.Get("cert"); b {
*cert = c
}
if c, b := opts.Get("certRaw"); b {
*certRaw = c
}
if c, b := opts.Get("key"); b {
*key = c
}
if c, b := opts.Get("loglevel"); b {
*logLevel = c
}
if _, b := opts.Get("server"); b {
*server = true
if c, b := opts.Get("mode"); b {
*mode = c
}
if c, b := opts.Get("password"); b {
*password = c
}
if c, b := opts.Get("mux"); b {
if i, err := strconv.ParseUint(c, 10, strconv.IntSize); err == nil {
*mux = uint(i)
} else {
logWarn("failed to parse mux, use default value")
}
if c, b := opts.Get("localAddr"); b {
if *server {
*remoteAddr = c
} else {
*localAddr = c
}
}
if _, b := opts.Get("tls"); b {
*tlsEnabled = true
}
if c, b := opts.Get("host"); b {
*host = c
}
if c, b := opts.Get("path"); b {
*path = c
}
if c, b := opts.Get("cert"); b {
*cert = c
}
if c, b := opts.Get("certRaw"); b {
*certRaw = c
}
if c, b := opts.Get("key"); b {
*key = c
}
if c, b := opts.Get("loglevel"); b {
*logLevel = c
}
if _, b := opts.Get("disableAccessLog"); b {
*disableAccessLog = true
}
if _, b := opts.Get("server"); b {
*server = true
}
if c, b := opts.Get("localAddr"); b {
if *server {
*remoteAddr = c
} else {
*localAddr = c
}
if c, b := opts.Get("localPort"); b {
if *server {
*remotePort = c
} else {
*localPort = c
}
}
if c, b := opts.Get("localPort"); b {
if *server {
*remotePort = c
} else {
*localPort = c
}
if c, b := opts.Get("remoteAddr"); b {
if *server {
*localAddr = c
} else {
*remoteAddr = c
}
}
if c, b := opts.Get("remoteAddr"); b {
if *server {
*localAddr = c
} else {
*remoteAddr = c
}
if c, b := opts.Get("remotePort"); b {
if *server {
*localPort = c
} else {
*remotePort = c
}
}
if c, b := opts.Get("remotePort"); b {
if *server {
*localPort = c
} else {
*remotePort = c
}
}

if _, b := opts.Get("fastOpen"); b {
*fastOpen = true
}
if _, b := opts.Get("fastOpen"); b {
*fastOpen = true
}

if _, b := opts.Get("__android_vpn"); b {
*vpn = true
}
if _, b := opts.Get("__android_vpn"); b {
*vpn = true
}

if c, b := opts.Get("fwmark"); b {
if i, err := strconv.ParseUint(c, 10, strconv.IntSize); err == nil {
*fwmark = uint(i)
} else {
logWarn("failed to parse fwmark, use default value")
}
if c, b := opts.Get("fwmark"); b {
if i, err := strconv.ParseUint(c, 10, strconv.IntSize); err == nil {
*fwmark = uint(i)
} else {
logWarn("failed to parse fwmark, use default value")
}
}

if *vpn {
registerControlFunc()
}
if *vpn {
registerControlFunc()
}

config, err := generateConfig()
Expand Down
Loading