commit 98f555391f6ed3f6f0c7814dad330098bbcc150f Author: lovezsh <1942314542@qq.com> Date: Sun Aug 25 18:57:31 2024 +0800 init diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fb125a2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +bin +.swp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b696de3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +.swap diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..daa61ef --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +tidy: + @GOPROXY=https://goproxy.cn go mod tidy + +build: tidy + @go build -o bin/nip cmd/main.go + +run-server: build + @NIP_SERVER_ADDR=127.0.0.1:8000 \ + NIP_SERVER_TOKEN=xxx \ + ./bin/nip server + +run-agent: build + @NIP_AGENT_ADDR=127.0.0.1:10080 \ + NIP_AGENT_TOKEN=xxx \ + NIP_AGENT_SERVER_ADDR=127.0.0.1:8000 \ + ./bin/nip agent + +install: build + @yes | cp ./bin/nip /usr/bin/nip + @chmod +x /usr/bin/nip + @yes | cp scripts/nip-server.service /usr/lib/systemd/system/ + @yes | cp scripts/nip-agent.service /usr/lib/systemd/system/ + @systemctl daemon-reload + +test: + @curl -p -x http://127.0.0.1:10080 https://myip.ipip.net -vv + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b76cf1a --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +## 一、简介 + +这是一个用于个人科学上网的工具, 用来解决各种公共工具因协议特征被识别的问题。 diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..8fbc74d --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,71 @@ +package main + +import ( + "fmt" + "log" + "os" + + "git.lovezsh.com/lovezsh/nip/internal/agent" + "git.lovezsh.com/lovezsh/nip/internal/server" + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "server", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "addr", + Usage: "listen address", + Value: ":3000", + EnvVars: []string{"NIP_SERVER_ADDR"}, + }, + &cli.StringFlag{ + Name: "token", + Required: true, + EnvVars: []string{"NIP_SERVER_TOKEN"}, + }, + }, + Action: func(ctx *cli.Context) error { + s := server.New(ctx.String("token"), ctx.String("addr")) + log.Printf("server listen at: %s", ctx.String("addr")) + return s.ListenAndServe() + }, + }, + { + Name: "agent", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "addr", + Usage: "listen address", + Value: ":4000", + EnvVars: []string{"NIP_AGENT_ADDR"}, + }, + &cli.StringFlag{ + Name: "token", + Required: true, + EnvVars: []string{"NIP_AGENT_TOKEN"}, + }, + &cli.StringFlag{ + Name: "server", + Value: "server address", + Required: true, + EnvVars: []string{"NIP_AGENT_SERVER_ADDR"}, + }, + }, + Action: func(ctx *cli.Context) error { + a := agent.New(ctx.String("addr"), ctx.String("token"), ctx.String("server")) + log.Printf("agent listen at %s", ctx.String("addr")) + log.Printf("using proxy server %s", ctx.String("server")) + return a.ListenAndServe() + }, + }, + }, + } + if err := app.Run(os.Args); err != nil { + fmt.Fprintf(os.Stderr, "error: %v", err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..59c6823 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module git.lovezsh.com/lovezsh/nip + +go 1.22.4 + +require github.com/urfave/cli/v2 v2.27.4 + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..46bd4d8 --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= +github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= diff --git a/internal/agent/agent.go b/internal/agent/agent.go new file mode 100644 index 0000000..1b96a5e --- /dev/null +++ b/internal/agent/agent.go @@ -0,0 +1,21 @@ +package agent + +import ( + "git.lovezsh.com/lovezsh/nip/internal/forward" + "git.lovezsh.com/lovezsh/nip/internal/pkg/httptunnel" +) + +type Agent struct { + httptunnel *httptunnel.Server +} + +func New(addr string, token string, server string) *Agent { + forwarder := forward.New(server, token) + return &Agent{ + httptunnel: httptunnel.New(addr, forwarder.Forward), + } +} + +func (agent *Agent) ListenAndServe() (err error) { + return agent.httptunnel.ListenAndServe() +} diff --git a/internal/forward/forward.go b/internal/forward/forward.go new file mode 100644 index 0000000..76bbd49 --- /dev/null +++ b/internal/forward/forward.go @@ -0,0 +1,59 @@ +package forward + +import ( + "errors" + "io" + "net" + + "git.lovezsh.com/lovezsh/nip/internal/pkg/encoding" + "git.lovezsh.com/lovezsh/nip/internal/proto" +) + +type Forwarder interface { + Forward(net.Conn, string) (err error) +} + +type forwarder struct { + server string + token string +} + +func New(server string, token string) Forwarder { + return &forwarder{server, token} +} + +func (f *forwarder) Forward(src net.Conn, target string) (err error) { + dst, err := net.Dial("tcp", f.server) + if err != nil { + return err + } + defer dst.Close() + + req := proto.Request{ + Target: target, + Token: f.token, + } + if err = encoding.NewEncoder(dst).Encode(&req); err != nil { + return + } + + var resp proto.Response + if err = encoding.NewDecoder(dst).Decode(&resp); err != nil { + return + } + + if resp.Code != 0 { + return errors.New(resp.Message) + } + + ch := make(chan error) + go func() { + _, err := io.Copy(dst, src) + ch <- err + }() + go func() { + _, err := io.Copy(src, dst) + ch <- err + }() + return <-ch +} diff --git a/internal/pkg/encoding/decoder.go b/internal/pkg/encoding/decoder.go new file mode 100644 index 0000000..2ae8a02 --- /dev/null +++ b/internal/pkg/encoding/decoder.go @@ -0,0 +1,18 @@ +package encoding + +import ( + "encoding/json" + "io" +) + +type decoder struct { + r io.Reader +} + +func NewDecoder(r io.Reader) Decoder { + return &decoder{r} +} + +func (d *decoder) Decode(v interface{}) (err error) { + return json.NewDecoder(d.r).Decode(&v) +} diff --git a/internal/pkg/encoding/encode.go b/internal/pkg/encoding/encode.go new file mode 100644 index 0000000..aec9d3f --- /dev/null +++ b/internal/pkg/encoding/encode.go @@ -0,0 +1,18 @@ +package encoding + +import ( + "encoding/json" + "io" +) + +type encoder struct { + w io.Writer +} + +func NewEncoder(w io.Writer) Encoder { + return &encoder{w} +} + +func (e *encoder) Encode(v interface{}) (err error) { + return json.NewEncoder(e.w).Encode(&v) +} diff --git a/internal/pkg/encoding/encoding.go b/internal/pkg/encoding/encoding.go new file mode 100644 index 0000000..0c72173 --- /dev/null +++ b/internal/pkg/encoding/encoding.go @@ -0,0 +1,9 @@ +package encoding + +type Decoder interface { + Decode(v interface{}) (err error) +} + +type Encoder interface { + Encode(v interface{}) (err error) +} diff --git a/internal/pkg/httptunnel/httptunnel.go b/internal/pkg/httptunnel/httptunnel.go new file mode 100644 index 0000000..d09363a --- /dev/null +++ b/internal/pkg/httptunnel/httptunnel.go @@ -0,0 +1,70 @@ +package httptunnel + +import ( + "bufio" + "errors" + "log" + "net" + "net/http" + "strings" +) + +type Server struct { + addr string + forward func(src net.Conn, dst string) (err error) +} + +func New(addr string, forward func(src net.Conn, dst string) (err error)) *Server { + return &Server{ + addr, + forward, + } +} + +func (s *Server) ListenAndServe() (err error) { + l, err := net.Listen("tcp", s.addr) + if err != nil { + return err + } + defer l.Close() + + for { + conn, err := l.Accept() + if err != nil { + return err + } + go s.Serve(conn) + } +} + +func (s *Server) Parse(conn net.Conn) (target string, err error) { + reader := bufio.NewReader(conn) + line, _, err := reader.ReadLine() + if err != nil { + return "", err + } + + terms := strings.Split(string(line), " ") + if len(terms) != 3 { + return "", errors.New("bad protocol") + } + if terms[0] != http.MethodConnect { + return "", errors.New("method is not connect") + } + _, err = conn.Write([]byte("HTTP/1.1 200 OK\r\n\r\n")) + return terms[1], err +} + +func (s *Server) Serve(conn net.Conn) { + defer conn.Close() + + target, err := s.Parse(conn) + if err != nil { + log.Printf("failed to parse http tunnel protocol error: %v", err) + return + } + + if err := s.forward(conn, target); err != nil { + log.Printf("http tunnel server forward request error: %v", err) + } +} diff --git a/internal/proto/proto.go b/internal/proto/proto.go new file mode 100644 index 0000000..62e1897 --- /dev/null +++ b/internal/proto/proto.go @@ -0,0 +1,11 @@ +package proto + +type Request struct { + Target string + Token string +} + +type Response struct { + Code int + Message string +} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..8fb9647 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,88 @@ +package server + +import ( + "errors" + "io" + "log" + "net" + + "git.lovezsh.com/lovezsh/nip/internal/pkg/encoding" + "git.lovezsh.com/lovezsh/nip/internal/proto" +) + +type Server struct { + token string + addr string +} + +func New(token string, addr string) *Server { + return &Server{ + token: token, + addr: addr, + } +} + +func (s *Server) ListenAndServe() (err error) { + l, err := net.Listen("tcp", s.addr) + if err != nil { + return err + } + defer l.Close() + + for { + conn, err := l.Accept() + if err != nil { + return err + } + go s.Serve(conn) + } +} + +func (s *Server) handshake(conn net.Conn) (target net.Conn, err error) { + var req proto.Request + if err = encoding.NewDecoder(conn).Decode(&req); err != nil { + return + } + + if req.Token != s.token { + encoding.NewEncoder(conn).Encode(&proto.Response{Code: 1, Message: "token invalid"}) + return nil, errors.New("token invalid") + } + + target, err = net.Dial("tcp", req.Target) + if err != nil { + encoding.NewEncoder(conn).Encode(&proto.Response{Code: 2, Message: err.Error()}) + return nil, err + } + + if err = encoding.NewEncoder(conn).Encode(&proto.Response{Code: 0}); err != nil { + target.Close() + return nil, err + } + + return target, nil +} + +func (s *Server) Serve(conn net.Conn) { + defer conn.Close() + + dst, err := s.handshake(conn) + if err != nil { + log.Printf("handshake error: %v", err) + return + } + defer dst.Close() + + ch := make(chan error) + go func() { + _, err := io.Copy(dst, conn) + ch <- err + }() + go func() { + _, err := io.Copy(conn, dst) + ch <- err + }() + if err := <-ch; err != nil { + log.Printf("data stream copy error: %v", err) + } +} diff --git a/scripts/nip-agent.service b/scripts/nip-agent.service new file mode 100644 index 0000000..d6b9b91 --- /dev/null +++ b/scripts/nip-agent.service @@ -0,0 +1,15 @@ +[Unit] +Description="NIP agent" +After=network.target +Wants=network.target + +[Service] +Type=simple +Environment=NIP_AGENT_ADRR=127.0.0.1:10080 +Environment=NIP_AGENT_TOKEN=xxx +Environment=NIP_AGENT_SERVER_ADDR=127.0.0.1:3000 +ExecStart=/usr/bin/nip agent + +[Install] +WantedBy=multi-user.target + diff --git a/scripts/nip-server.service b/scripts/nip-server.service new file mode 100644 index 0000000..672dfd8 --- /dev/null +++ b/scripts/nip-server.service @@ -0,0 +1,14 @@ +[Unit] +Description="NIP server" +After=network.target +Wants=network.target + +[Service] +Type=simple +Environment=NIP_SERVER_ADRR=:3000 +Environment=NIP_SERVER_TOKEN=xxx +ExecStart=/usr/bin/nip server + +[Install] +WantedBy=multi-user.target +