From e11511637b33240967a51bb9fdd081e9bea363b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B2=B3?= Date: Sun, 13 Oct 2019 13:25:22 +0800 Subject: [PATCH] pugin init --- conf/clients.json | 2 + conf/hosts.json | 2 + conf/nps.conf | 2 +- conf/tasks.json | 3 + core/struct.go | 34 ++++++ module.md | 36 +++++++ server/proxy/socks5.go | 1 + server/socks5/socks5_access_handle.go | 128 +++++++++++++++++++++++ server/socks5/socks5_handshake_handle.go | 58 ++++++++++ 9 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 core/struct.go create mode 100644 module.md create mode 100644 server/socks5/socks5_access_handle.go create mode 100644 server/socks5/socks5_handshake_handle.go diff --git a/conf/clients.json b/conf/clients.json index e69de29..4d941cc 100644 --- a/conf/clients.json +++ b/conf/clients.json @@ -0,0 +1,2 @@ +{"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":2,"VerifyKey":"m3ue28maxy3pgqp8","Addr":"","Remark":"","Status":true,"IsConnect":false,"RateLimit":0,"Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Rate":{"NowRate":0},"NoStore":false,"NoDisplay":false,"MaxConn":0,"NowConn":0,"WebUserName":"","WebPassword":"","ConfigConnAllow":true,"MaxTunnelNum":0} +*#* \ No newline at end of file diff --git a/conf/hosts.json b/conf/hosts.json index e69de29..a3ab6de 100644 --- a/conf/hosts.json +++ b/conf/hosts.json @@ -0,0 +1,2 @@ +{"Id":1,"Host":"a.o.com","HeaderChange":"","HostChange":"","Location":"/","Remark":"","Scheme":"all","CertFilePath":"","KeyFilePath":"","NoStore":false,"IsClose":false,"Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Client":{"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":2,"VerifyKey":"m3ue28maxy3pgqp8","Addr":"127.0.0.1","Remark":"","Status":true,"IsConnect":true,"RateLimit":0,"Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Rate":{"NowRate":0},"NoStore":false,"NoDisplay":false,"MaxConn":0,"NowConn":0,"WebUserName":"","WebPassword":"","ConfigConnAllow":true,"MaxTunnelNum":0},"Target":{"TargetStr":"127.0.0.1:8080","TargetArr":null,"LocalProxy":false}} +*#* \ No newline at end of file diff --git a/conf/nps.conf b/conf/nps.conf index c75a430..8c98672 100755 --- a/conf/nps.conf +++ b/conf/nps.conf @@ -4,7 +4,7 @@ runmode = dev #HTTP(S) proxy port, no startup if empty http_proxy_ip=0.0.0.0 -http_proxy_port=80 +http_proxy_port=809 https_proxy_port=443 https_just_proxy=true #default https certificate setting diff --git a/conf/tasks.json b/conf/tasks.json index e69de29..315a403 100644 --- a/conf/tasks.json +++ b/conf/tasks.json @@ -0,0 +1,3 @@ +{"Id":2,"Port":1234,"ServerIp":"","Mode":"tcp","Status":true,"RunStatus":true,"Client":{"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":2,"VerifyKey":"m3ue28maxy3pgqp8","Addr":"","Remark":"","Status":true,"IsConnect":false,"RateLimit":0,"Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Rate":{"NowRate":0},"NoStore":false,"NoDisplay":false,"MaxConn":0,"NowConn":0,"WebUserName":"","WebPassword":"","ConfigConnAllow":true,"MaxTunnelNum":0},"Ports":"","Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Password":"","Remark":"","TargetAddr":"","NoStore":false,"LocalPath":"","StripPre":"","Target":{"TargetStr":"152.136.18.138:22","TargetArr":null,"LocalProxy":false},"HealthCheckTimeout":0,"HealthMaxFail":0,"HealthCheckInterval":0,"HealthNextTime":"0001-01-01T00:00:00Z","HealthMap":null,"HttpHealthUrl":"","HealthRemoveArr":null,"HealthCheckType":"","HealthCheckTarget":""} +*#*{"Id":3,"Port":1111,"ServerIp":"","Mode":"socks5","Status":true,"RunStatus":false,"Client":{"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":2,"VerifyKey":"m3ue28maxy3pgqp8","Addr":"","Remark":"","Status":true,"IsConnect":false,"RateLimit":0,"Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Rate":{"NowRate":0},"NoStore":false,"NoDisplay":false,"MaxConn":0,"NowConn":0,"WebUserName":"","WebPassword":"","ConfigConnAllow":true,"MaxTunnelNum":0},"Ports":"","Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Password":"","Remark":"","TargetAddr":"","NoStore":false,"LocalPath":"","StripPre":"","Target":{"TargetStr":"","TargetArr":null,"LocalProxy":false},"HealthCheckTimeout":0,"HealthMaxFail":0,"HealthCheckInterval":0,"HealthNextTime":"0001-01-01T00:00:00Z","HealthMap":null,"HttpHealthUrl":"","HealthRemoveArr":null,"HealthCheckType":"","HealthCheckTarget":""} +*#* \ No newline at end of file diff --git a/core/struct.go b/core/struct.go new file mode 100644 index 0000000..578d275 --- /dev/null +++ b/core/struct.go @@ -0,0 +1,34 @@ +package core + +import ( + "context" +) + +// This structure is used to describe the plugin configuration item name and description. +type Config struct { + ConfigName string + Description string +} + +type Stage uint8 + +// These constants are meant to describe the stage in which the plugin is running. +const ( + STAGE_START_RUN_END Stage = iota + STAGE_START_RUN + STAGE_START_END + STAGE_RUN_END + STAGE_START + STAGE_END + STAGE_RUN +) + +// Plugin interface, all plugins must implement those functions. +type Plugin interface { + GetConfigName() []*Config + GetBeforePlugin() Plugin + GetStage() Stage + Start(ctx context.Context, config map[string]string) error + Run(ctx context.Context, config map[string]string) error + End(ctx context.Context, config map[string]string) error +} diff --git a/module.md b/module.md new file mode 100644 index 0000000..721239d --- /dev/null +++ b/module.md @@ -0,0 +1,36 @@ +主干程序模块化编写 + +假设socks5来说 +首先需要定一个一个socks5 struct { +包含 +客户端地址 +服务端地址 +读到的用户名 +读到的密码 +连接类型(tcp、udp) +远程地址(ipv4 ipv6) +远程端口 +} + +每个模块需要一个获取配置参数的func + +定义好了之后在每每个模块中对该结=结构体进行操作 + +调用每一个模块的时候,需要如下参数 +ctx上线文 +socks5结构体 +该module的配置 +当前的connection +预分配的connection +每个模块根据自己的业务逻辑进行处理 + + +其他技术: +零拷贝 +事件驱动 +回调 +触发器 + + +插件启动范围 +插件优先级 diff --git a/server/proxy/socks5.go b/server/proxy/socks5.go index 2fe72c1..d555e9d 100755 --- a/server/proxy/socks5.go +++ b/server/proxy/socks5.go @@ -199,6 +199,7 @@ func (s *Sock5ModeServer) handleConn(c net.Conn) { c.Close() return } + if s.task.Client.Cnf.U != "" && s.task.Client.Cnf.P != "" { buf[1] = UserPassAuth c.Write(buf) diff --git a/server/socks5/socks5_access_handle.go b/server/socks5/socks5_access_handle.go new file mode 100644 index 0000000..6313828 --- /dev/null +++ b/server/socks5/socks5_access_handle.go @@ -0,0 +1,128 @@ +package socks5 + +import ( + "context" + "errors" + "github.com/cnlh/nps/core" + "io" + "net" +) + +const ( + UserPassAuth = uint8(2) + userAuthVersion = uint8(1) + authSuccess = uint8(0) + authFailure = uint8(1) + UserNoAuth = uint8(0) +) + +type Access struct { + clientConn net.Conn + username string + password string +} + +func (access *Access) GetConfigName() []*core.Config { + c := make([]*core.Config, 0) + c = append(c, &core.Config{ConfigName: "socks5_check_access", Description: "need check the permission?"}) + c = append(c, &core.Config{ConfigName: "socks5_access_username", Description: "auth username"}) + c = append(c, &core.Config{ConfigName: "socks5_access_password", Description: "auth password"}) + return nil +} + +func (access *Access) GetStage() core.Stage { + return core.STAGE_RUN +} + +func (access *Access) GetBeforePlugin() core.Plugin { + return &Handshake{} +} + +func (access *Access) Start(ctx context.Context, config map[string]string) error { + return nil +} +func (access *Access) End(ctx context.Context, config map[string]string) error { + return nil +} + +func (access *Access) Run(ctx context.Context, config map[string]string) error { + clientCtxConn := ctx.Value("clientConn") + if clientCtxConn == nil { + return errors.New("the client access.clientConnection is not exist") + } + access.clientConn = clientCtxConn.(net.Conn) + if config["socks5_check_access"] != "true" { + return access.sendAccessMsgToClient(UserNoAuth) + } + configUsername := config["socks5_access_username"] + configPassword := config["socks5_access_password"] + if configUsername == "" || configPassword == "" { + return access.sendAccessMsgToClient(UserNoAuth) + } + // need auth + if err := access.sendAccessMsgToClient(UserPassAuth); err != nil { + return err + } + // send auth reply to client ,and get the auth information + var err error + access.username, access.password, err = access.getAuthInfoFromClient() + if err != nil { + return err + } + context.WithValue(ctx, access.username, access.password) + // check + return access.checkAuth(configUsername, configPassword) +} + +func (access *Access) sendAccessMsgToClient(auth uint8) error { + buf := make([]byte, 2) + buf[0] = 5 + buf[1] = auth + n, err := access.clientConn.Write(buf) + if err != nil || n != 2 { + return errors.New("write access message to client error " + err.Error()) + } + return nil +} + +func (access *Access) getAuthInfoFromClient() (username string, password string, err error) { + header := []byte{0, 0} + if _, err = io.ReadAtLeast(access.clientConn, header, 2); err != nil { + return + } + if header[0] != userAuthVersion { + err = errors.New("authentication method is not supported") + return + } + userLen := int(header[1]) + user := make([]byte, userLen) + if _, err = io.ReadAtLeast(access.clientConn, user, userLen); err != nil { + return + } + if _, err := access.clientConn.Read(header[:1]); err != nil { + err = errors.New("get password length error" + err.Error()) + return + } + passLen := int(header[0]) + pass := make([]byte, passLen) + if _, err := io.ReadAtLeast(access.clientConn, pass, passLen); err != nil { + err = errors.New("get password error" + err.Error()) + return + } + username = string(user) + password = string(pass) + return +} + +func (access *Access) checkAuth(configUserName, configPassword string) error { + if access.username == configUserName && access.password == configPassword { + _, err := access.clientConn.Write([]byte{userAuthVersion, authSuccess}) + return err + } else { + _, err := access.clientConn.Write([]byte{userAuthVersion, authFailure}) + if err != nil { + return err + } + return errors.New("auth check error,username or password does not match") + } +} diff --git a/server/socks5/socks5_handshake_handle.go b/server/socks5/socks5_handshake_handle.go new file mode 100644 index 0000000..6c5ac78 --- /dev/null +++ b/server/socks5/socks5_handshake_handle.go @@ -0,0 +1,58 @@ +package socks5 + +import ( + "context" + "errors" + "fmt" + "github.com/cnlh/nps/core" + "io" + "net" +) + +type Handshake struct { +} + +func (handshake *Handshake) GetConfigName() []*core.Config { + return nil +} +func (handshake *Handshake) GetStage() core.Stage { + return core.STAGE_RUN +} +func (handshake *Handshake) GetBeforePlugin() core.Plugin { + return nil +} +func (handshake *Handshake) Start(ctx context.Context, config map[string]string) error { + return nil +} + +func (handshake *Handshake) Run(ctx context.Context, config map[string]string) error { + clientCtxConn := ctx.Value("clientConn") + if clientCtxConn == nil { + return errors.New("the client connection is not exist") + } + clientConn := clientCtxConn.(net.Conn) + + buf := make([]byte, 2) + if _, err := io.ReadFull(clientConn, buf); err != nil { + return errors.New("negotiation err while read 2 bytes from client connection: " + err.Error()) + } + + if version := buf[0]; version != 5 { + return errors.New("only support socks5") + } + nMethods := buf[1] + + methods := make([]byte, nMethods) + + if n, err := clientConn.Read(methods); n != int(nMethods) || err != nil { + return errors.New(fmt.Sprintf("read methods error, need %d , read %d, error %s", nMethods, n, err.Error())) + } else { + context.WithValue(ctx, "methods", methods[:n]) + } + + return nil +} + +func (handshake *Handshake) End(ctx context.Context, config map[string]string) error { + return nil +}