Skip to content

Commit

Permalink
xray-plugin
Browse files Browse the repository at this point in the history
require xray-core v1.3.0

Signed-off-by: Teddysun <[email protected]>
  • Loading branch information
teddysun committed Feb 19, 2021
1 parent e40b469 commit f7c1e2a
Show file tree
Hide file tree
Showing 13 changed files with 1,322 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
xray-plugin*
/bin/
/.idea/
4 changes: 3 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
MIT License

Copyright (c) 2021 Teddysun
Copyright (c) 2019 by Max Lv <[email protected]>
Copyright (C) 2019 by Mygod Studio <[email protected]>
Copyright (c) 2021 by Teddysun <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
70 changes: 68 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,68 @@
# xray-plugin
A SIP003 plugin based on xray
# Yet another SIP003 plugin for shadowsocks, based on xray

## Build

* `go build`

## Usage

See command line args for advanced usages.

### Shadowsocks over websocket (HTTP)

On your server

```sh
ss-server -c config.json -p 80 --plugin xray-plugin --plugin-opts "server"
```

On your client

```sh
ss-local -c config.json -p 80 --plugin xray-plugin
```

### Shadowsocks over websocket (HTTPS)

On your server

```sh
ss-server -c config.json -p 443 --plugin xray-plugin --plugin-opts "server;tls;host=mydomain.me"
```

On your client

```sh
ss-local -c config.json -p 443 --plugin xray-plugin --plugin-opts "tls;host=mydomain.me"
```

### Shadowsocks over quic

On your server

```sh
ss-server -c config.json -p 443 --plugin xray-plugin --plugin-opts "server;mode=quic;host=mydomain.me"
```

On your client

```sh
ss-local -c config.json -p 443 --plugin xray-plugin --plugin-opts "mode=quic;host=mydomain.me"
```

### Issue a cert for TLS and QUIC

xray-plugin will look for TLS certificates signed by [acme.sh](https://github.com/Neilpang/acme.sh) by default.
Here's some sample commands for issuing a certificate using CloudFlare.
You can find commands for issuing certificates for other DNS providers at acme.sh.

```sh
curl https://get.acme.sh | sh
~/.acme.sh/acme.sh --issue --dns dns_cf -d mydomain.me
```

Alternatively, you can specify path to your certificates using option `cert` and `key`.

### Use `certRaw` to pass certificate

Instead of using `cert` to pass the certificate file, `certRaw` could be used to pass in PEM format certificate, that is the content between `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----` without the line breaks.
196 changes: 196 additions & 0 deletions args.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package main

import (
"bytes"
"fmt"
"net"
"os"
"sort"
"strings"
)

func isIPv6(str string) bool {
ip := net.ParseIP(str)
return ip != nil && strings.Contains(str, ":")
}

// Key–value mappings for the representation of client and server options.

// Args maps a string key to a list of values. It is similar to url.Values.
type Args map[string][]string

// Get the first value associated with the given key. If there are any values
// associated with the key, the value return has the value and ok is set to
// true. If there are no values for the given key, value is "" and ok is false.
// If you need access to multiple values, use the map directly.
func (args Args) Get(key string) (value string, ok bool) {
if args == nil {
return "", false
}
vals, ok := args[key]
if !ok || len(vals) == 0 {
return "", false
}
return vals[0], true
}

// Append value to the list of values for key.
func (args Args) Add(key, value string) {
args[key] = append(args[key], value)
}

// Return the index of the next unescaped byte in s that is in the term set, or
// else the length of the string if no terminators appear. Additionally return
// the unescaped string up to the returned index.
func indexUnescaped(s string, term []byte) (int, string, error) {
var i int
unesc := make([]byte, 0)
for i = 0; i < len(s); i++ {
b := s[i]
// A terminator byte?
if bytes.IndexByte(term, b) != -1 {
break
}
if b == '\\' {
i++
if i >= len(s) {
return 0, "", fmt.Errorf("nothing following final escape in %q", s)
}
b = s[i]
}
unesc = append(unesc, b)
}
return i, string(unesc), nil
}

// Parse SS_PLUGIN options from environment variables
func parseEnv() (opts Args, err error) {
opts = make(Args)
ss_remote_host := os.Getenv("SS_REMOTE_HOST")
ss_remote_port := os.Getenv("SS_REMOTE_PORT")
ss_local_host := os.Getenv("SS_LOCAL_HOST")
ss_local_port := os.Getenv("SS_LOCAL_PORT")
if len(ss_remote_host) == 0 {
return
}
if len(ss_remote_port) == 0 {
return
}
if len(ss_local_host) == 0 {
return
}
if len(ss_local_port) == 0 {
return
}

opts.Add("remoteAddr", ss_remote_host)
opts.Add("remotePort", ss_remote_port)
opts.Add("localAddr", ss_local_host)
opts.Add("localPort", ss_local_port)

ss_plugin_options := os.Getenv("SS_PLUGIN_OPTIONS")
if len(ss_plugin_options) > 0 {
other_opts, err := parsePluginOptions(ss_plugin_options)
if err != nil {
return nil, err
}
for k, v := range other_opts {
opts[k] = v
}
}
return opts, nil
}

// Parse a name–value mapping as from SS_PLUGIN_OPTIONS.
//
// "<value> is a k=v string value with options that are to be passed to the
// transport. semicolons, equal signs and backslashes must be escaped
// with a backslash."
// Example: secret=nou;cache=/tmp/cache;secret=yes
func parsePluginOptions(s string) (opts Args, err error) {
opts = make(Args)
if len(s) == 0 {
return
}
i := 0
for {
var key, value string
var offset, begin int

if i >= len(s) {
break
}
begin = i
// Read the key.
offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'})
if err != nil {
return
}
if len(key) == 0 {
err = fmt.Errorf("empty key in %q", s[begin:i])
return
}
i += offset
// End of string or no equals sign?
if i >= len(s) || s[i] != '=' {
opts.Add(key, "1")
// Skip the semicolon.
i++
continue
}
// Skip the equals sign.
i++
// Read the value.
offset, value, err = indexUnescaped(s[i:], []byte{';'})
if err != nil {
return
}
i += offset
opts.Add(key, value)
// Skip the semicolon.
i++
}
return opts, nil
}

// Escape backslashes and all the bytes that are in set.
func backslashEscape(s string, set []byte) string {
var buf bytes.Buffer
for _, b := range []byte(s) {
if b == '\\' || bytes.IndexByte(set, b) != -1 {
buf.WriteByte('\\')
}
buf.WriteByte(b)
}
return buf.String()
}

// Encode a name–value mapping so that it is suitable to go in the ARGS option
// of an SMETHOD line. The output is sorted by key. The "ARGS:" prefix is not
// added.
//
// "Equal signs and commas [and backslashes] must be escaped with a backslash."
func encodeSmethodArgs(args Args) string {
if args == nil {
return ""
}

keys := make([]string, 0, len(args))
for key := range args {
keys = append(keys, key)
}
sort.Strings(keys)

escape := func(s string) string {
return backslashEscape(s, []byte{'=', ','})
}

var pairs []string
for _, key := range keys {
for _, value := range args[key] {
pairs = append(pairs, escape(key)+"="+escape(value))
}
}

return strings.Join(pairs, ",")
}
88 changes: 88 additions & 0 deletions build-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/bin/bash
sum="sha1sum"

if ! hash sha1sum 2>/dev/null; then
if ! hash shasum 2>/dev/null; then
echo "I can't see 'sha1sum' or 'shasum'"
echo "Please install one of them!"
exit
fi
sum="shasum"
fi

[[ -z $upx ]] && upx="echo pending"
if [[ $upx == "echo pending" ]] && hash upx 2>/dev/null; then
upx="upx -9"
fi

VERSION=$(git describe --tags)
LDFLAGS="-X main.VERSION=$VERSION -s -w"
GCFLAGS=""

OSES=(linux darwin windows freebsd)
ARCHS=(amd64 386)

mkdir bin

for os in ${OSES[@]}; do
for arch in ${ARCHS[@]}; do
# Go 1.15 drops support for 32-bit binaries on macOS, iOS, iPadOS, watchOS, and tvOS (the darwin/386 and darwin/arm ports)
# Reference URL: https://tip.golang.org/doc/go1.15#darwin
if [ "$os" == "darwin" ] && [ "$arch" == "386" ]; then
continue
fi
suffix=""
if [ "$os" == "windows" ]; then
suffix=".exe"
fi
env CGO_ENABLED=0 GOOS=$os GOARCH=$arch go build -v -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o xray-plugin_${os}_${arch}${suffix}
$upx xray-plugin_${os}_${arch}${suffix} >/dev/null
tar -zcf bin/xray-plugin-${os}-${arch}-$VERSION.tar.gz xray-plugin_${os}_${arch}${suffix}
$sum bin/xray-plugin-${os}-${arch}-$VERSION.tar.gz
done
done

# ARM
ARMS=(5 6 7)
for v in ${ARMS[@]}; do
env CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=$v go build -v -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o xray-plugin_linux_arm$v
done
$upx xray-plugin_linux_arm* >/dev/null
tar -zcf bin/xray-plugin-linux-arm-$VERSION.tar.gz xray-plugin_linux_arm*
$sum bin/xray-plugin-linux-arm-$VERSION.tar.gz

# ARM64 (ARMv8 or aarch64)
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -v -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o xray-plugin_linux_arm64
$upx xray-plugin_linux_arm64 >/dev/null
tar -zcf bin/xray-plugin-linux-arm64-$VERSION.tar.gz xray-plugin_linux_arm64
$sum bin/xray-plugin-linux-arm64-$VERSION.tar.gz

# MIPS
MIPSS=(mips mipsle)
for v in ${MIPSS[@]}; do
env CGO_ENABLED=0 GOOS=linux GOARCH=$v go build -v -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o xray-plugin_linux_$v
env CGO_ENABLED=0 GOOS=linux GOARCH=$v GOMIPS=softfloat go build -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o xray-plugin_linux_${v}_sf
done
$upx xray-plugin_linux_mips* >/dev/null
tar -zcf bin/xray-plugin-linux-mips-$VERSION.tar.gz xray-plugin_linux_mips*
$sum bin/xray-plugin-linux-mips-$VERSION.tar.gz

# MIPS64
MIPS64S=(mips64 mips64le)
for v in ${MIPS64S[@]}; do
env CGO_ENABLED=0 GOOS=linux GOARCH=$v go build -v -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o xray-plugin_linux_$v
done
tar -zcf bin/xray-plugin-linux-mips64-$VERSION.tar.gz xray-plugin_linux_mips64*
$sum bin/xray-plugin-linux-mips64-$VERSION.tar.gz

# ppc64le
env CGO_ENABLED=0 GOOS=linux GOARCH=ppc64le go build -v -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o xray-plugin_linux_ppc64le
$upx xray-plugin_linux_ppc64le >/dev/null
tar -zcf bin/xray-plugin-linux-ppc64le-$VERSION.tar.gz xray-plugin_linux_ppc64le
$sum bin/xray-plugin-linux-ppc64le-$VERSION.tar.gz

# s390x
env CGO_ENABLED=0 GOOS=linux GOARCH=s390x go build -v -ldflags "$LDFLAGS" -gcflags "$GCFLAGS" -o xray-plugin_linux_s390x
$upx xray-plugin_linux_s390x >/dev/null
tar -zcf bin/xray-plugin-linux-s390x-$VERSION.tar.gz xray-plugin_linux_s390x
$sum bin/xray-plugin-linux-s390x-$VERSION.tar.gz
9 changes: 9 additions & 0 deletions errors.generated.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import "github.com/xtls/xray-core/common/errors"

type errPathObjHolder struct{}

func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/teddysun/xray-plugin

require (
github.com/golang/protobuf v1.4.3
github.com/xtls/xray-core v1.3.0
)

go 1.15
Loading

0 comments on commit f7c1e2a

Please sign in to comment.