summaryrefslogtreecommitdiff
path: root/pkg/server
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/server')
-rw-r--r--pkg/server/cmds.go225
-rw-r--r--pkg/server/echo.go7
-rw-r--r--pkg/server/env.go44
-rw-r--r--pkg/server/exit.go2
-rw-r--r--pkg/server/help.go77
-rw-r--r--pkg/server/hook/aes.go2
-rw-r--r--pkg/server/hook/alpha.go4
-rw-r--r--pkg/server/hook/auth.go2
-rw-r--r--pkg/server/hook/b64.go2
-rw-r--r--pkg/server/hook/b85.go2
-rw-r--r--pkg/server/hook/buf.go2
-rw-r--r--pkg/server/hook/dump.go2
-rw-r--r--pkg/server/hook/hex.go2
-rw-r--r--pkg/server/hook/hook.go48
-rw-r--r--pkg/server/hook/info-http.go2
-rw-r--r--pkg/server/hook/proxy.go2
-rw-r--r--pkg/server/hook/split.go2
-rw-r--r--pkg/server/hook/zip.go2
-rw-r--r--pkg/server/opts/opts.go168
-rw-r--r--pkg/server/server.go50
-rw-r--r--pkg/server/sleep.go20
-rw-r--r--pkg/server/socket/defer.go4
-rw-r--r--pkg/server/socket/dial.go2
-rw-r--r--pkg/server/socket/listen.go2
-rw-r--r--pkg/server/socket/loop.go2
-rw-r--r--pkg/server/socket/proxy.go2
-rw-r--r--pkg/server/socket/socket.go31
-rw-r--r--pkg/server/socket/tun.go2
-rw-r--r--pkg/server/status.go4
-rw-r--r--pkg/server/tunnel.go42
30 files changed, 506 insertions, 252 deletions
diff --git a/pkg/server/cmds.go b/pkg/server/cmds.go
index dbd7083..a525b86 100644
--- a/pkg/server/cmds.go
+++ b/pkg/server/cmds.go
@@ -1,14 +1,29 @@
package server
import (
+ "fmt"
"log"
+ "reflect"
"sort"
+ "strconv"
"strings"
+ "unsafe"
)
+const uintBitSize = 8 * int(unsafe.Sizeof(uint(0)))
+
+type parseFunc func(string) (interface{}, error)
+type parseSliceFunc func([]string) (interface{}, error)
+
type cmd struct {
name string
- f func(r *request)
+ args string
+ desc string
+ f interface{}
+ cbs []parseFunc
+ rest parseSliceFunc
+
+ variadic bool
}
type node struct {
@@ -18,15 +33,171 @@ type node struct {
var cmds = newNode()
-func init() {
- newCmd(help, "help")
+func (c *cmd) run(r *request, v []string) {
+ desc := func(n int) string {
+ if n == 1 {
+ return fmt.Sprint(n, " is expected")
+ }
+ return fmt.Sprint(n, " are expected")
+ }
+
+ argc := len(c.cbs)
+
+ switch {
+ case argc > len(v):
+ r.Fatal("not enough args: ", desc(argc))
+ case argc < len(v) && c.rest == nil:
+ if argc == 0 {
+ r.Fatal("args are not expected")
+ }
+
+ r.Fatal("too many args: ", desc(argc))
+ }
+
+ parse := func(f func(i interface{})) error {
+ for n, cb := range c.cbs {
+ i, err := cb(v[n])
+ if err != nil {
+ return err
+ }
+ f(i)
+ }
+ if c.rest != nil {
+ i, err := c.rest(v[argc:])
+ if err != nil {
+ return err
+ }
+ f(i)
+ }
+ return nil
+ }
+
+ args := []reflect.Value{reflect.ValueOf(r)}
+ err := parse(func(i interface{}) {
+ args = append(args, reflect.ValueOf(i))
+ })
+ if err != nil {
+ r.Fatal("args parse failed: ", err)
+ }
+
+ if c.variadic {
+ reflect.ValueOf(c.f).CallSlice(args)
+ } else {
+ reflect.ValueOf(c.f).Call(args)
+ }
}
func newNode() *node {
return &node{m: map[string]*node{}}
}
-func newCmd(f func(r *request), where string) {
+func parseString(s string) (interface{}, error) {
+ return s, nil
+}
+
+func parseInt(s string) (interface{}, error) {
+ n, err := strconv.Atoi(s)
+ if err != nil {
+ return nil, fmt.Errorf("%s: bad integer", s)
+ }
+ return n, nil
+}
+
+func parseUint(s string) (interface{}, error) {
+ n, err := strconv.ParseUint(s, 10, uintBitSize)
+ if err != nil {
+ return nil, fmt.Errorf("%s: bad unsigned integer", s)
+ }
+ return uint(n), nil
+}
+
+func parseSlice(t reflect.Type, f parseFunc, ss []string) (interface{}, error) {
+ v := reflect.New(reflect.SliceOf(t)).Elem()
+
+ for _, s := range ss {
+ i, err := f(s)
+ if err != nil {
+ return nil, err
+ }
+ v = reflect.Append(v, reflect.ValueOf(i))
+ }
+
+ return v.Interface(), nil
+}
+
+func newParseSlice(t reflect.Type, f parseFunc) parseSliceFunc {
+ return func(ss []string) (interface{}, error) {
+ return parseSlice(t, f, ss)
+ }
+}
+
+var parseFuncMap = map[reflect.Kind]parseFunc{
+ reflect.Int: parseInt,
+ reflect.Uint: parseUint,
+ reflect.String: parseString,
+}
+
+func isPtrRequest(t reflect.Type) bool {
+ if t.Kind() != reflect.Ptr {
+ return false
+ }
+
+ if t.Elem().Kind() != reflect.Struct {
+ return false
+ }
+
+ _, ok := reflect.New(t.Elem()).Interface().(*request)
+
+ return ok
+}
+
+func (c *cmd) parseFuncSignature(i interface{}) error {
+ t := reflect.TypeOf(i)
+
+ if t.Kind() != reflect.Func {
+ return fmt.Errorf("%s: not a function", t)
+ }
+
+ argc := t.NumIn()
+
+ if argc < 1 || !isPtrRequest(t.In(0)) {
+ return fmt.Errorf("%s: first arg *request is expected", t)
+ }
+
+ for n := 1; n < argc; n++ {
+ slice := false
+ t := t.In(n)
+
+ if t.Kind() == reflect.Slice {
+ if n < argc-1 {
+ return fmt.Errorf("%s: slice cannot be in the middle", t)
+ }
+ slice = true
+ t = t.Elem()
+ }
+
+ if f, ok := parseFuncMap[t.Kind()]; !ok {
+ return fmt.Errorf("%s: unsupported %d arg type %s", t, t)
+ } else if slice {
+ c.rest = newParseSlice(t, f)
+ } else {
+ c.cbs = append(c.cbs, f)
+ }
+ }
+
+ c.f = i
+ c.variadic = t.IsVariadic()
+
+ return nil
+}
+
+func newCmd(where string, f interface{}, args string) {
+ c := &cmd{name: where, args: args}
+
+ if err := c.parseFuncSignature(f); err != nil {
+ log.Panicf("newCmd: %s", err)
+ }
+
path := strings.Fields(where)
node := cmds
@@ -48,30 +219,36 @@ func newCmd(f func(r *request), where string) {
log.Panicf("handler already registered at '%s'", where)
}
- node.c = &cmd{
- name: where,
- f: f,
- }
+ node.c = c
}
-func getCmd(path []string) (*cmd, []string) {
+func runCmd(r *request, args []string) {
+ var c *cmd
+ var v []string
+
node := cmds
- for n, name := range path {
+ for n, name := range args {
node = node.m[name]
if node == nil {
- return nil, nil
+ break
}
if node.c != nil {
- return node.c, path[n+1:]
+ c = node.c
+ v = args[n+1:]
+ break
}
}
- return nil, nil
+ if c == nil {
+ r.Fatal("command not found")
+ }
+
+ c.run(r, v)
}
-func help(r *request) {
+func help(r *request, args []string) {
var ss []string
var walker func([]string, *node)
@@ -91,27 +268,27 @@ func help(r *request) {
}
}
- root := cmds
+ node := cmds
- for _, s := range r.args {
- root = root.m[s]
+ for _, s := range args {
+ node = node.m[s]
- if root == nil {
+ if node == nil {
break
}
}
- if root == nil {
+ if node == nil {
r.Print("nothing to help")
return
}
- if root.c != nil {
- r.Print("usage: ", root.c.name, " [...]")
+ if node.c != nil {
+ r.Println("usage:", node.c.name, node.c.args)
return
}
- walker(nil, root)
+ walker(nil, node)
sort.Strings(ss)
@@ -119,3 +296,7 @@ func help(r *request) {
r.Println(s)
}
}
+
+func init() {
+ newCmd("help", help, "[path]")
+}
diff --git a/pkg/server/echo.go b/pkg/server/echo.go
index 2ac54b9..ee0537b 100644
--- a/pkg/server/echo.go
+++ b/pkg/server/echo.go
@@ -4,11 +4,10 @@ import (
"strings"
)
-func echo(r *request) {
+func echo(r *request, args ...string) {
expand := false
- args := r.args
- if r.argc > 0 && r.args[0] == "-e" {
+ if len(args) > 0 && args[0] == "-e" {
expand = true
args = args[1:]
}
@@ -21,5 +20,5 @@ func echo(r *request) {
}
func init() {
- newCmd(echo, "echo")
+ newCmd("echo", echo, "[args]")
}
diff --git a/pkg/server/env.go b/pkg/server/env.go
index 3ed820e..aa0ed40 100644
--- a/pkg/server/env.go
+++ b/pkg/server/env.go
@@ -4,44 +4,32 @@ import (
"strings"
)
-func varGet(r *request) {
- r.expect(1)
-
- if v, ok := r.c.s.env.Find(r.args[0]); ok {
+func varGet(r *request, name string) {
+ if v, ok := r.c.s.env.Find(name); ok {
r.Print(v)
} else {
r.Fatal("no such variable")
}
}
-func varSet(r *request) {
- if len(r.args) < 2 {
- r.Fatal("not enough args")
- }
-
- value := strings.Join(r.args[1:], " ")
+func varSet(r *request, name string, args ...string) {
+ value := strings.Join(args, " ")
- if err := r.c.s.env.Set(r.args[0], value); err != nil {
+ if err := r.c.s.env.Set(name, value); err != nil {
r.Fatal(err)
}
}
-func varPush(r *request) {
- if len(r.args) < 2 {
- r.Fatal("not enough args")
- }
-
- value := strings.Join(r.args[1:], " ")
+func varPush(r *request, name string, args ...string) {
+ value := strings.Join(args, " ")
- if err := r.c.s.env.Push(r.args[0], value); err != nil {
+ if err := r.c.s.env.Push(name, value); err != nil {
r.Fatal(err)
}
}
-func varUnset(r *request) {
- r.expect(1)
-
- if !r.c.s.env.Del(r.args[0]) {
+func varUnset(r *request, name string) {
+ if !r.c.s.env.Del(name) {
r.Fatal("no such variable")
}
}
@@ -58,10 +46,10 @@ func varClear(r *request) {
}
func init() {
- newCmd(varGet, "get")
- newCmd(varSet, "set")
- newCmd(varPush, "push")
- newCmd(varUnset, "unset")
- newCmd(varShow, "env")
- newCmd(varClear, "clear")
+ newCmd("get", varGet, "name")
+ newCmd("set", varSet, "name value")
+ newCmd("push", varPush, "name value")
+ newCmd("unset", varUnset, "name")
+ newCmd("env", varShow, "name")
+ newCmd("clear", varClear, "")
}
diff --git a/pkg/server/exit.go b/pkg/server/exit.go
index 785568d..e767403 100644
--- a/pkg/server/exit.go
+++ b/pkg/server/exit.go
@@ -5,5 +5,5 @@ func exit(r *request) {
}
func init() {
- newCmd(exit, "exit")
+ newCmd("exit", exit, "")
}
diff --git a/pkg/server/help.go b/pkg/server/help.go
new file mode 100644
index 0000000..16f2776
--- /dev/null
+++ b/pkg/server/help.go
@@ -0,0 +1,77 @@
+package server
+
+import (
+ "fmt"
+ "io"
+
+ "tunnel/pkg/server/hook"
+ "tunnel/pkg/server/opts"
+ "tunnel/pkg/server/socket"
+)
+
+func showHookList(r *request) {
+ for _, h := range hook.GetList() {
+ r.Println(h)
+ }
+}
+
+func showSocketList(r *request) {
+ for _, s := range socket.GetList() {
+ r.Println(s)
+ }
+}
+
+func showUsage(r *request, name, desc string, param []opts.Param) {
+ r.Printf("usage: %s", name)
+
+ for _, p := range param {
+ formatParamShort(r, p)
+ }
+
+ r.Printf("\n\n%s", desc)
+}
+
+func showHookHelp(r *request, name string) {
+ t := hook.GetType(name)
+ if t == nil {
+ r.Fatal("no such hook")
+ }
+
+ showUsage(r, t.Name, t.Desc, t.Param)
+}
+
+func formatParamShort(w io.Writer, p opts.Param) {
+ if !p.Required {
+ fmt.Fprint(w, "[")
+ }
+ fmt.Fprintf(w, ",%s", p.Name)
+
+ if p.Kind != opts.Bool {
+ fmt.Fprintf(w, "=%s", p.Kind)
+
+ if p.Default != "" {
+ fmt.Fprintf(w, "(%s)", p.Default)
+ }
+ }
+
+ if !p.Required {
+ fmt.Fprint(w, "]")
+ }
+}
+
+func showSocketHelp(r *request, name string) {
+ t := socket.GetType(name)
+ if t == nil {
+ r.Fatal("no such socket")
+ }
+
+ showUsage(r, t.Name, t.Desc, t.Param)
+}
+
+func init() {
+ newCmd("hook list", showHookList, "")
+ newCmd("hook help", showHookHelp, "hook-name")
+
+ newCmd("socket list", showSocketList, "")
+ newCmd("socket help", showSocketHelp, "socket-name")
+}
diff --git a/pkg/server/hook/aes.go b/pkg/server/hook/aes.go
index dc48f49..184d18d 100644
--- a/pkg/server/hook/aes.go
+++ b/pkg/server/hook/aes.go
@@ -75,5 +75,5 @@ func (aesHook) New(env env.Env) (interface{}, error) {
}
func init() {
- register("aes", aesHook{})
+ register("aes", "aes encryption out/in", aesHook{})
}
diff --git a/pkg/server/hook/alpha.go b/pkg/server/hook/alpha.go
index d1fefcc..f25d4de 100644
--- a/pkg/server/hook/alpha.go
+++ b/pkg/server/hook/alpha.go
@@ -27,6 +27,6 @@ func alpha(cb func(rune) rune) Func {
}
func init() {
- registerFunc("lower", alpha(unicode.ToLower))
- registerFunc("upper", alpha(unicode.ToUpper))
+ registerFunc("lower", "lowercase filter out/", alpha(unicode.ToLower))
+ registerFunc("upper", "uppercase filter out/", alpha(unicode.ToUpper))
}
diff --git a/pkg/server/hook/auth.go b/pkg/server/hook/auth.go
index dbfc9bc..21f900e 100644
--- a/pkg/server/hook/auth.go
+++ b/pkg/server/hook/auth.go
@@ -170,5 +170,5 @@ func (h *authHook) New(env env.Env) (interface{}, error) {
}
func init() {
- register("auth", authHook{})
+ register("auth", "chap authentication out/in", authHook{})
}
diff --git a/pkg/server/hook/b64.go b/pkg/server/hook/b64.go
index c6637e5..d12d4c9 100644
--- a/pkg/server/hook/b64.go
+++ b/pkg/server/hook/b64.go
@@ -39,5 +39,5 @@ func (b64Pipe) Recv(rq, wq queue.Q) error {
}
func init() {
- registerPipe("b64", b64Pipe{})
+ registerPipe("b64", "base64 filter out/in", b64Pipe{})
}
diff --git a/pkg/server/hook/b85.go b/pkg/server/hook/b85.go
index d90a1c4..967882d 100644
--- a/pkg/server/hook/b85.go
+++ b/pkg/server/hook/b85.go
@@ -44,5 +44,5 @@ func (b85Pipe) Recv(rq, wq queue.Q) error {
}
func init() {
- registerPipe("b85", b85Pipe{})
+ registerPipe("b85", "base85 filter out/in", b85Pipe{})
}
diff --git a/pkg/server/hook/buf.go b/pkg/server/hook/buf.go
index 0306c73..98c57c9 100644
--- a/pkg/server/hook/buf.go
+++ b/pkg/server/hook/buf.go
@@ -47,5 +47,5 @@ func buffering(rq, wq queue.Q) error {
}
func init() {
- registerFunc("buf", buffering)
+ registerFunc("buf", "stream buffering out/", buffering)
}
diff --git a/pkg/server/hook/dump.go b/pkg/server/hook/dump.go
index d871d63..bcf958f 100644
--- a/pkg/server/hook/dump.go
+++ b/pkg/server/hook/dump.go
@@ -103,5 +103,5 @@ func (h *dumpHook) New(env env.Env) (interface{}, error) {
}
func init() {
- register("dump", dumpHook{})
+ register("dump", "stream dumper out/in", dumpHook{})
}
diff --git a/pkg/server/hook/hex.go b/pkg/server/hook/hex.go
index 362dbd4..4d7f1e7 100644
--- a/pkg/server/hook/hex.go
+++ b/pkg/server/hook/hex.go
@@ -24,5 +24,5 @@ func (hexPipe) Recv(rq, wq queue.Q) error {
}
func init() {
- registerPipe("hex", hexPipe{})
+ registerPipe("hex", "stream hexify out/", hexPipe{})
}
diff --git a/pkg/server/hook/hook.go b/pkg/server/hook/hook.go
index 36b01d4..1464272 100644
--- a/pkg/server/hook/hook.go
+++ b/pkg/server/hook/hook.go
@@ -12,7 +12,17 @@ import (
"tunnel/pkg/server/queue"
)
-var hooks = map[string]interface{}{}
+type Type struct {
+ Name string
+ Desc string
+ Func bool
+
+ Param []opts.Param
+
+ data interface{}
+}
+
+var hooks = map[string]*Type{}
type Pipe struct {
priv interface{}
@@ -112,9 +122,9 @@ func New(desc string) (H, error) {
reverse = true
}
- if i, ok := hooks[name]; !ok {
+ if hookType, ok := hooks[name]; !ok {
return nil, fmt.Errorf("unknown hook '%s'", name)
- } else if h, err := initHook(i, opts); err != nil {
+ } else if h, err := initHook(hookType.data, opts); err != nil {
return nil, fmt.Errorf("%s: %w", name, err)
} else {
w := &wrapper{
@@ -126,29 +136,39 @@ func New(desc string) (H, error) {
}
}
-func register(name string, i interface{}) {
+func register(name string, desc string, i interface{}) {
+ var param []opts.Param
+
switch t := reflect.TypeOf(i); t.Kind() {
case reflect.Struct:
if _, ok := reflect.New(t).Interface().(Hooker); !ok {
- log.Panicf("uncompatible hook type '%s'", t.String())
+ log.Panicf("uncompatible hook type '%s'", t)
}
+ param = opts.Parametrize(t)
case reflect.Func:
if _, ok := i.(Func); !ok {
- log.Panicf("uncompatible func type '%s'", t.String())
+ log.Panicf("uncompatible func type '%s'", t)
}
default:
- log.Panicf("non-struct and non-func type '%s'", t.String())
+ log.Panicf("non-struct and non-func type '%s'", t)
}
if _, ok := hooks[name]; ok {
log.Panicf("duplicate hook name '%s'", name)
}
- hooks[name] = i
+ hooks[name] = &Type{
+ Name: name,
+ Desc: desc,
+
+ Param: param,
+
+ data: i,
+ }
}
-func registerFunc(name string, f Func) {
- register(name, f)
+func registerFunc(name string, desc string, f Func) {
+ register(name, desc, f)
}
type pipeHolder struct {
@@ -159,8 +179,8 @@ func (p pipeHolder) New(env.Env) (interface{}, error) {
return p.i, nil
}
-func registerPipe(name string, i interface{}) {
- register(name, pipeHolder{i})
+func registerPipe(name string, desc string, i interface{}) {
+ register(name, desc, pipeHolder{i})
}
func GetList() []string {
@@ -174,3 +194,7 @@ func GetList() []string {
return list
}
+
+func GetType(name string) *Type {
+ return hooks[name]
+}
diff --git a/pkg/server/hook/info-http.go b/pkg/server/hook/info-http.go
index ec56f87..721c286 100644
--- a/pkg/server/hook/info-http.go
+++ b/pkg/server/hook/info-http.go
@@ -47,5 +47,5 @@ func (infoHttpHook) New(env env.Env) (interface{}, error) {
}
func init() {
- register("info-http", infoHttpHook{})
+ register("info-http", "display http connect host out/", infoHttpHook{})
}
diff --git a/pkg/server/hook/proxy.go b/pkg/server/hook/proxy.go
index 4276d9a..bba17e3 100644
--- a/pkg/server/hook/proxy.go
+++ b/pkg/server/hook/proxy.go
@@ -87,5 +87,5 @@ func (h *proxyHook) New(env env.Env) (interface{}, error) {
}
func init() {
- register("proxy", proxyHook{})
+ register("proxy", "http connect client out/in", proxyHook{})
}
diff --git a/pkg/server/hook/split.go b/pkg/server/hook/split.go
index 59c8055..eee36d4 100644
--- a/pkg/server/hook/split.go
+++ b/pkg/server/hook/split.go
@@ -36,5 +36,5 @@ func (h *splitHook) New(env.Env) (interface{}, error) {
}
func init() {
- register("split", splitHook{})
+ register("split", "stream splitter out/", splitHook{})
}
diff --git a/pkg/server/hook/zip.go b/pkg/server/hook/zip.go
index 615b50d..6283cb5 100644
--- a/pkg/server/hook/zip.go
+++ b/pkg/server/hook/zip.go
@@ -42,5 +42,5 @@ func (zipPipe) Recv(rq, wq queue.Q) error {
}
func init() {
- registerPipe("zip", zipPipe{})
+ registerPipe("zip", "zip compression filter out/in", zipPipe{})
}
diff --git a/pkg/server/opts/opts.go b/pkg/server/opts/opts.go
index c5729a3..2c781e3 100644
--- a/pkg/server/opts/opts.go
+++ b/pkg/server/opts/opts.go
@@ -7,12 +7,24 @@ import (
"strings"
)
-type fieldSpec struct {
- name string
- inline bool
- required bool
- positive bool
- defaultValue string
+type Kind = reflect.Kind
+
+const (
+ Bool = reflect.Bool
+ Int = reflect.Int
+ String = reflect.String
+)
+
+type Param struct {
+ Name string
+ Kind Kind
+
+ Default string
+ Required bool
+
+ Positive bool
+
+ index []int
}
type Opts map[string]string
@@ -38,47 +50,55 @@ func (opts Opts) Bool(key string) bool {
return ok
}
-func (opts Opts) Configure(v interface{}) error {
- rv := reflect.ValueOf(v)
+func extract(i interface{}) (reflect.Value, error) {
+ v := reflect.ValueOf(i)
+
+ var err error
switch {
- case rv.Kind() != reflect.Ptr:
- return fmt.Errorf("opts: configure failed: non-pointer %s", rv.Type().String())
- case rv.IsNil():
- return fmt.Errorf("opts: configure failed: nil %s", rv.Type().String())
- case rv.Elem().Kind() != reflect.Struct:
- return fmt.Errorf("opts: configure failed: non-struct %s", rv.Elem().Type().String())
+ case v.Kind() != reflect.Ptr:
+ err = fmt.Errorf("non-pointer %s", v.Type().String())
+ case v.IsNil():
+ err = fmt.Errorf("nil %s", v.Type().String())
+ case v.Elem().Kind() != reflect.Struct:
+ err = fmt.Errorf("non-struct %s", v.Elem().Type().String())
+ }
+
+ if err != nil {
+ return reflect.Value{}, err
}
- return opts.configure(rv.Elem())
+ return v.Elem(), nil
}
-func getFieldSpec(field reflect.StructField) (fs fieldSpec) {
- fs.name = strings.ToLower(field.Name)
+func (opts Opts) Configure(i interface{}) error {
+ v, err := extract(i)
+ if err != nil {
+ return fmt.Errorf("opts: configure failed: %w", err)
+ }
- for _, s := range strings.Split(field.Tag.Get("opts"), ",") {
+ return opts.configure(v)
+}
+
+func (p *Param) parseTags(tags string) {
+ for _, s := range strings.Split(tags, ",") {
switch {
- case s == "inline":
- fs.inline = true
case s == "required":
- fs.required = true
+ p.Required = true
case s == "positive":
- fs.positive = true
+ p.Positive = true
case strings.HasPrefix(s, "default:"):
- fs.defaultValue = s[8:]
+ p.Default = s[8:]
}
}
-
- return
}
-func (opts Opts) setValue(v reflect.Value, fs fieldSpec) error {
- s, ok := opts[fs.name]
- kind := v.Kind()
+func (opts Opts) setValue(v reflect.Value, p Param) error {
+ s, ok := opts[p.Name]
- if kind == reflect.Bool {
+ if p.Kind == Bool {
if s != "" {
- return fmt.Errorf("%s: boolean option does not need a value", fs.name)
+ return fmt.Errorf("%s: boolean option does not need a value", p.Name)
}
if ok {
@@ -89,70 +109,96 @@ func (opts Opts) setValue(v reflect.Value, fs fieldSpec) error {
}
if s == "" {
- if fs.required {
- return fmt.Errorf("%s: expect value", fs.name)
+ if p.Required {
+ return fmt.Errorf("%s: expect value", p.Name)
}
- s = fs.defaultValue
+ s = p.Default
}
if s == "" {
return nil
}
- switch kind {
- case reflect.String:
+ switch p.Kind {
+ case String:
v.SetString(s)
- case reflect.Int:
+ case Int:
n, err := strconv.Atoi(s)
if err != nil {
- return fmt.Errorf("%s: %w", fs.name, err)
+ return fmt.Errorf("%s: %w", p.Name, err)
}
if n <= 0 {
- return fmt.Errorf("%s: expect positive", fs.name)
+ return fmt.Errorf("%s: expect positive", p.Name)
}
v.SetInt(int64(n))
default:
- return fmt.Errorf("%s: unsupported type '%s'", fs.name, v.Type().String())
+ return fmt.Errorf("%s: unsupported type '%s'", p.Name, v.Type().String())
}
return nil
}
-func (opts Opts) configure(v reflect.Value) error {
- m := map[string]bool{}
-
- for s := range opts {
- m[s] = true
+func Parametrize(t reflect.Type) []Param {
+ if t.Kind() != reflect.Struct {
+ return nil
}
- var visit func(v reflect.Value) error
+ return parametrize(reflect.New(t).Elem())
+}
- visit = func(v reflect.Value) error {
- t := v.Type()
+func parametrize(v reflect.Value) (param []Param) {
+ var visit func(index []int, v reflect.Value)
+ visit = func(index []int, v reflect.Value) {
for n := 0; n < v.NumField(); n++ {
- fs := getFieldSpec(t.Field(n))
- fv := v.Field(n)
+ f := v.Type().Field(n)
+ x := append(append([]int{}, index...), f.Index...)
- if fs.inline {
- if err := visit(fv); err != nil {
- return err
- }
- } else if fv.CanSet() {
- if err := opts.setValue(v.Field(n), fs); err != nil {
- return err
- }
+ if f.Anonymous {
+ visit(x, v.Field(n))
+ continue
+ }
+
+ if !v.Field(n).CanSet() {
+ continue
+ }
- delete(m, fs.name)
+ p := Param{
+ Name: strings.ToLower(f.Name),
+ Kind: f.Type.Kind(),
+
+ index: x,
}
+
+ p.parseTags(f.Tag.Get("opts"))
+
+ param = append(param, p)
}
+ }
- return nil
+ visit(nil, v)
+
+ return
+}
+
+func (opts Opts) configure(v reflect.Value) error {
+ param := parametrize(v)
+
+ m := map[string]bool{}
+
+ for s := range opts {
+ m[s] = true
}
- if err := visit(v); err != nil {
- return err
+ for _, p := range param {
+ f := v.FieldByIndex(p.index)
+
+ if err := opts.setValue(f, p); err != nil {
+ return err
+ }
+
+ delete(m, p.Name)
}
if len(m) > 0 {
diff --git a/pkg/server/server.go b/pkg/server/server.go
index 8c5683a..43cd542 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -47,11 +47,6 @@ type request struct {
c *client
- cmd *cmd
-
- argc int
- args []string
-
fail bool
out bytes.Buffer
@@ -93,37 +88,6 @@ func (r *request) Fatalf(format string, v ...interface{}) {
panic(requestError(fmt.Sprintf(format, v...)))
}
-func (r *request) expect(c ...int) {
- desc := func(n int) string {
- var sep string
-
- if n == 1 {
- sep = " is "
- } else {
- sep = " are "
- }
-
- return fmt.Sprint(n, sep, "expected")
- }
-
- check := func(cond bool, args ...interface{}) {
- if cond {
- r.Fatal(args...)
- }
- }
-
- switch len(c) {
- case 0:
- check(r.argc > 0, "args are not expected")
- case 1:
- check(r.argc < c[0], "not enough args: ", desc(c[0]))
- check(r.argc > c[0], "too many args: ", desc(c[0]))
- case 2:
- check(r.argc < c[0], "not enough args: at least ", desc(c[0]))
- check(r.argc > c[1], "too many args: no more than ", desc(c[1]))
- }
-}
-
func (s *Server) isDone() bool {
select {
case <-s.done:
@@ -281,16 +245,6 @@ func (c *client) decode(b []byte) ([]string, error) {
return t, nil
}
-func (r *request) parse(args []string) {
- if c, args := getCmd(r.c.s.env.EvalStrings(args)); c == nil {
- r.Fatal("command not found")
- } else {
- r.args = args
- r.argc = len(args)
- r.cmd = c
- }
-}
-
func (r *request) run(args []string) {
defer func() {
switch err := recover().(type) {
@@ -305,12 +259,10 @@ func (r *request) run(args []string) {
log.Println(r.c, r, ">", strings.Join(args, " "))
- r.parse(args)
-
r.c.s.mu.Lock()
defer r.c.s.mu.Unlock()
- r.cmd.f(r)
+ runCmd(r, r.c.s.env.EvalStrings(args))
}
func (c *client) close() {
diff --git a/pkg/server/sleep.go b/pkg/server/sleep.go
index 7d21135..33038f1 100644
--- a/pkg/server/sleep.go
+++ b/pkg/server/sleep.go
@@ -1,27 +1,19 @@
package server
import (
- "strconv"
"time"
)
-const maxSleep = 10
+const maxSleepTout = 10
-func sleep(r *request) {
- r.expect(1)
-
- n, err := strconv.Atoi(r.args[0])
- if err != nil || n < 0 {
- r.Fatalf("invalid time interval '%s'", r.args[0])
- }
-
- if n > maxSleep {
- r.Fatalf("no more than %d", maxSleep)
+func sleep(r *request, tout uint) {
+ if tout > maxSleepTout {
+ r.Fatalf("no more than %d", maxSleepTout)
}
- time.Sleep(time.Duration(n) * time.Second)
+ time.Sleep(time.Duration(tout) * time.Second)
}
func init() {
- newCmd(sleep, "sleep")
+ newCmd("sleep", sleep, "tout")
}
diff --git a/pkg/server/socket/defer.go b/pkg/server/socket/defer.go
index 7c1436e..d63d51e 100644
--- a/pkg/server/socket/defer.go
+++ b/pkg/server/socket/defer.go
@@ -6,7 +6,7 @@ import (
)
type deferSocket struct {
- dialSocket `opts:"inline"`
+ dialSocket
}
type deferConn struct {
@@ -76,5 +76,5 @@ func (c *deferConn) Close() (err error) {
}
func init() {
- register("defer", deferSocket{})
+ register("defer", "deferred 'dial' after first sending", deferSocket{})
}
diff --git a/pkg/server/socket/dial.go b/pkg/server/socket/dial.go
index b2df3b7..a31e192 100644
--- a/pkg/server/socket/dial.go
+++ b/pkg/server/socket/dial.go
@@ -40,5 +40,5 @@ func (s *dialSocket) Close() {
}
func init() {
- register("dial", dialSocket{})
+ register("dial", "connect to the network address", dialSocket{})
}
diff --git a/pkg/server/socket/listen.go b/pkg/server/socket/listen.go
index 2c2f184..9d19677 100644
--- a/pkg/server/socket/listen.go
+++ b/pkg/server/socket/listen.go
@@ -96,5 +96,5 @@ func (s *listenSocket) Close() {
}
func init() {
- register("listen", listenSocket{})
+ register("listen", "accept connections on the local network address", listenSocket{})
}
diff --git a/pkg/server/socket/loop.go b/pkg/server/socket/loop.go
index c442140..1c45067 100644
--- a/pkg/server/socket/loop.go
+++ b/pkg/server/socket/loop.go
@@ -42,5 +42,5 @@ func (s *loopSocket) Close() {
}
func init() {
- register("loop", loopSocket{})
+ register("loop", "send data back", loopSocket{})
}
diff --git a/pkg/server/socket/proxy.go b/pkg/server/socket/proxy.go
index 1be4bba..c46d1c1 100644
--- a/pkg/server/socket/proxy.go
+++ b/pkg/server/socket/proxy.go
@@ -137,5 +137,5 @@ func (s *proxyServer) Close() (err error) {
}
func init() {
- register("proxy", proxySocket{})
+ register("proxy", "http connect proxy server", proxySocket{})
}
diff --git a/pkg/server/socket/socket.go b/pkg/server/socket/socket.go
index 03b73d9..ea35be3 100644
--- a/pkg/server/socket/socket.go
+++ b/pkg/server/socket/socket.go
@@ -72,12 +72,12 @@ func (c *conn) Close() error {
func New(desc string, e env.Env) (S, error) {
name, opts := opts.Parse(desc)
- t, ok := sockets[name]
+ sockType, ok := sockets[name]
if !ok {
return nil, fmt.Errorf("%s: unknown type", name)
}
- s := reflect.New(t).Interface()
+ s := reflect.New(sockType.t).Interface()
if err := opts.Configure(s); err != nil {
return nil, fmt.Errorf("%s: %w", name, err)
}
@@ -101,9 +101,18 @@ func parseProtoAddr(proto, addr string) (string, string) {
return proto, addr
}
-var sockets = map[string]reflect.Type{}
+type Type struct {
+ Desc string
+ Name string
-func register(name string, i interface{}) {
+ Param []opts.Param
+
+ t reflect.Type
+}
+
+var sockets = map[string]*Type{}
+
+func register(name string, desc string, i interface{}) {
t := reflect.TypeOf(i)
if t.Kind() != reflect.Struct {
log.Panicf("non-struct type '%s'", t.String())
@@ -114,7 +123,15 @@ func register(name string, i interface{}) {
if _, ok := sockets[name]; ok {
log.Panicf("duplicate socket name '%s'", name)
}
- sockets[name] = t
+
+ sockets[name] = &Type{
+ Desc: desc,
+ Name: name,
+
+ Param: opts.Parametrize(t),
+
+ t: t,
+ }
}
func GetList() []string {
@@ -128,3 +145,7 @@ func GetList() []string {
return list
}
+
+func GetType(name string) *Type {
+ return sockets[name]
+}
diff --git a/pkg/server/socket/tun.go b/pkg/server/socket/tun.go
index 3e673eb..61a30f0 100644
--- a/pkg/server/socket/tun.go
+++ b/pkg/server/socket/tun.go
@@ -122,5 +122,5 @@ func (c *tunConn) Close() error {
}
func init() {
- register("tun", tunSocket{})
+ register("tun", "tun device", tunSocket{})
}
diff --git a/pkg/server/status.go b/pkg/server/status.go
index 4689274..d8f01c6 100644
--- a/pkg/server/status.go
+++ b/pkg/server/status.go
@@ -5,11 +5,9 @@ import (
)
func status(r *request) {
- r.expect()
-
r.Printf("since %s", r.c.s.since.Format(config.TimeFormat))
}
func init() {
- newCmd(status, "status")
+ newCmd("status", status, "")
}
diff --git a/pkg/server/tunnel.go b/pkg/server/tunnel.go
index 8b86ddc..e2fce92 100644
--- a/pkg/server/tunnel.go
+++ b/pkg/server/tunnel.go
@@ -421,10 +421,8 @@ func isOkTunnelName(s string) bool {
return s != ""
}
-func tunnelAdd(r *request) {
+func tunnelAdd(r *request, args ...string) {
limit := maxQueueLimit
-
- args := r.args
name := ""
for len(args) > 1 {
@@ -492,11 +490,7 @@ func tunnelAdd(r *request) {
log.Println(r.c, r, t, "create")
}
-func tunnelDel(r *request) {
- r.expect(1)
-
- id := r.args[0]
-
+func tunnelDel(r *request, id string) {
if t, ok := r.c.s.tunnels[id]; !ok {
r.Fatal("no such entry")
} else {
@@ -505,10 +499,7 @@ func tunnelDel(r *request) {
}
}
-func tunnelRename(r *request) {
- r.expect(2)
-
- old, new := r.args[0], r.args[1]
+func tunnelRename(r *request, old, new string) {
if !isOkTunnelName(new) {
r.Fatal("bad name")
}
@@ -584,29 +575,14 @@ func showRecent(r *request) {
}
}
-func showHooks(r *request) {
- for _, h := range hook.GetList() {
- r.Println(h)
- }
-}
-
-func showSockets(r *request) {
- for _, s := range socket.GetList() {
- r.Println(s)
- }
-}
-
func init() {
- newCmd(tunnelAdd, "add")
- newCmd(tunnelDel, "del")
-
- newCmd(tunnelRename, "rename")
+ newCmd("add", tunnelAdd, "[name id] [limit N] [single] socket [hook ...] socket")
+ newCmd("del", tunnelDel, "id")
- newCmd(showHooks, "hooks")
- newCmd(showSockets, "sockets")
+ newCmd("rename", tunnelRename, "old-id new-id")
- newCmd(showTunnels, "show")
+ newCmd("show", showTunnels, "")
- newCmd(showActive, "active")
- newCmd(showRecent, "recent")
+ newCmd("active", showActive, "")
+ newCmd("recent", showRecent, "")
}