This commit is contained in:
lovezsh 2024-08-25 18:57:31 +08:00
commit 98f555391f
17 changed files with 447 additions and 0 deletions

2
.dockerignore Normal file
View File

@ -0,0 +1,2 @@
bin
.swp

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
bin
.swap

27
Makefile Normal file
View File

@ -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

3
README.md Normal file
View File

@ -0,0 +1,3 @@
## 一、简介
这是一个用于个人科学上网的工具, 用来解决各种公共工具因协议特征被识别的问题。

71
cmd/main.go Normal file
View File

@ -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)
}
}

11
go.mod Normal file
View File

@ -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
)

8
go.sum Normal file
View File

@ -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=

21
internal/agent/agent.go Normal file
View File

@ -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()
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -0,0 +1,9 @@
package encoding
type Decoder interface {
Decode(v interface{}) (err error)
}
type Encoder interface {
Encode(v interface{}) (err error)
}

View File

@ -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)
}
}

11
internal/proto/proto.go Normal file
View File

@ -0,0 +1,11 @@
package proto
type Request struct {
Target string
Token string
}
type Response struct {
Code int
Message string
}

88
internal/server/server.go Normal file
View File

@ -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)
}
}

15
scripts/nip-agent.service Normal file
View File

@ -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

View File

@ -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