init
This commit is contained in:
commit
98f555391f
|
@ -0,0 +1,2 @@
|
|||
bin
|
||||
.swp
|
|
@ -0,0 +1,2 @@
|
|||
bin
|
||||
.swap
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
|
@ -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=
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package encoding
|
||||
|
||||
type Decoder interface {
|
||||
Decode(v interface{}) (err error)
|
||||
}
|
||||
|
||||
type Encoder interface {
|
||||
Encode(v interface{}) (err error)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package proto
|
||||
|
||||
type Request struct {
|
||||
Target string
|
||||
Token string
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Code int
|
||||
Message string
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
Loading…
Reference in New Issue