From a6e6d848d6f719710a43ca015027bd93befb721d Mon Sep 17 00:00:00 2001 From: Mikhail Osipov Date: Mon, 8 Mar 2021 02:25:01 +0300 Subject: add cmd, hook, socket help --- PLAN | 2 + cmd/tunneld/main.go | 2 +- pkg/server/cmds.go | 225 ++++++++++++++++++++++++++++++++++++++----- pkg/server/echo.go | 7 +- pkg/server/env.go | 44 +++------ pkg/server/exit.go | 2 +- pkg/server/help.go | 77 +++++++++++++++ pkg/server/hook/aes.go | 2 +- pkg/server/hook/alpha.go | 4 +- pkg/server/hook/auth.go | 2 +- pkg/server/hook/b64.go | 2 +- pkg/server/hook/b85.go | 2 +- pkg/server/hook/buf.go | 2 +- pkg/server/hook/dump.go | 2 +- pkg/server/hook/hex.go | 2 +- pkg/server/hook/hook.go | 48 ++++++--- pkg/server/hook/info-http.go | 2 +- pkg/server/hook/proxy.go | 2 +- pkg/server/hook/split.go | 2 +- pkg/server/hook/zip.go | 2 +- pkg/server/opts/opts.go | 172 +++++++++++++++++++++------------ pkg/server/server.go | 50 +--------- pkg/server/sleep.go | 20 ++-- pkg/server/socket/defer.go | 4 +- pkg/server/socket/dial.go | 2 +- pkg/server/socket/listen.go | 2 +- pkg/server/socket/loop.go | 2 +- pkg/server/socket/proxy.go | 2 +- pkg/server/socket/socket.go | 31 +++++- pkg/server/socket/tun.go | 2 +- pkg/server/status.go | 4 +- pkg/server/tunnel.go | 42 ++------ 32 files changed, 511 insertions(+), 255 deletions(-) create mode 100644 PLAN create mode 100644 pkg/server/help.go diff --git a/PLAN b/PLAN new file mode 100644 index 0000000..be0a0ef --- /dev/null +++ b/PLAN @@ -0,0 +1,2 @@ +- hook params help +- cmd help diff --git a/cmd/tunneld/main.go b/cmd/tunneld/main.go index 8ae1e4e..c808aab 100644 --- a/cmd/tunneld/main.go +++ b/cmd/tunneld/main.go @@ -163,7 +163,7 @@ func (p *parser) read(fp *os.File) error { } if len(args) > 0 { - fmt.Errorf("%s:%d: uncomplete command", fp.Name(), line) + return fmt.Errorf("%s:%d: unexpected end of file", fp.Name(), line) } return nil 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) + } + + return opts.configure(v) +} - for _, s := range strings.Split(field.Tag.Get("opts"), ",") { +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) - - 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 - } - - delete(m, fs.name) + f := v.Type().Field(n) + x := append(append([]int{}, index...), f.Index...) + + if f.Anonymous { + visit(x, v.Field(n)) + continue } + + if !v.Field(n).CanSet() { + continue + } + + 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, "") } -- cgit v1.2.3-70-g09d2