├── .gitattributes ├── .gitignore ├── .travis.yml ├── Dockerfile.npc ├── Dockerfile.nps ├── LICENSE ├── Makefile ├── README.md ├── README_NPS.md ├── bridge └── bridge.go ├── build.android.sh ├── build.assets.sh ├── build.sh ├── client ├── client.go ├── control.go ├── health.go ├── local.go └── register.go ├── cmd ├── npc │ ├── npc.go │ └── sdk.go └── nps │ └── nps.go ├── conf ├── clients.json ├── global.json ├── hosts.json ├── multi_account.conf ├── npc.conf ├── nps.conf ├── server.key ├── server.pem └── tasks.json ├── docs ├── .nojekyll ├── README.md ├── _coverpage.md ├── _navbar.md ├── _sidebar.md ├── api.md ├── bt.md ├── contribute.md ├── description.md ├── discuss.md ├── donate.md ├── example.md ├── faq.md ├── feature.md ├── index.html ├── install.md ├── introduction.md ├── logo.png ├── logo.svg ├── npc_extend.md ├── npc_sdk.md ├── nps_extend.md ├── nps_use.md ├── run.md ├── server_config.md ├── thanks.md ├── use.md ├── webapi.md └── windows_client_service_configuration.png ├── go.mod ├── go.sum ├── gui └── npc │ ├── AndroidManifest.xml │ └── npc.go ├── image ├── bt │ ├── bt1.jpg │ ├── bt2.png │ ├── bt3.png │ └── bt_1.png ├── cpu1.png ├── cpu2.png ├── donation_wx.png ├── donation_zfb.png ├── http.png ├── httpProxy.png ├── new │ ├── cmd.png │ ├── https.png │ ├── payCode.png │ └── tcp_limit.png ├── qps.png ├── sock5.png ├── speed.png ├── tcp.png ├── udp.png ├── web.png ├── web2.png └── work_flow.svg ├── install.sh ├── lib ├── cache │ └── lru.go ├── common │ ├── const.go │ ├── logs.go │ ├── netpackager.go │ ├── pool.go │ ├── pprof.go │ ├── run.go │ └── util.go ├── config │ ├── config.go │ └── config_test.go ├── conn │ ├── conn.go │ ├── link.go │ ├── listener.go │ └── snappy.go ├── crypt │ ├── clientHello.go │ ├── crypt.go │ └── tls.go ├── daemon │ ├── daemon.go │ └── reload.go ├── file │ ├── db.go │ ├── file.go │ ├── obj.go │ └── sort.go ├── goroutine │ └── pool.go ├── install │ └── install.go ├── nps_mux │ ├── conn.go │ ├── map.go │ ├── mux.go │ ├── mux_test.go │ ├── netpackager.go │ ├── pool.go │ ├── queue.go │ ├── rate.go │ ├── sysGetsock_nowindows.go │ ├── sysGetsock_windows.go │ └── tc.go ├── pmux │ ├── pconn.go │ ├── plistener.go │ ├── pmux.go │ └── pmux_test.go ├── rate │ ├── conn.go │ └── rate.go ├── sheap │ └── heap.go └── version │ └── version.go ├── port.png ├── server.png ├── server ├── connection │ └── connection.go ├── proxy │ ├── base.go │ ├── http.go │ ├── https.go │ ├── p2p.go │ ├── socks5.go │ ├── tcp.go │ ├── transport.go │ ├── transport_windows.go │ ├── udp.go │ └── websocket.go ├── server.go ├── test │ └── test.go └── tool │ └── utils.go └── web ├── controllers ├── auth.go ├── base.go ├── client.go ├── global.go ├── index.go └── login.go ├── routers └── router.go ├── static ├── css │ ├── bootstrap-table.min.css │ ├── bootstrap.min.css │ ├── datatables.css │ ├── fontawesome.min.css │ ├── regular.min.css │ ├── solid.min.css │ ├── style.css │ └── toastr.min.css ├── img │ └── flag │ │ ├── en-US.png │ │ └── zh-CN.png ├── js │ ├── bootstrap-table-locale-all.min.js │ ├── bootstrap-table.min.js │ ├── bootstrap.min.js │ ├── clipboard.min.js │ ├── echarts.min.js │ ├── fontawesome.min.js │ ├── inspinia.js │ ├── jquery-3.4.1.min.js │ ├── language.js │ ├── layui │ │ └── laydate │ │ │ ├── laydate.js │ │ │ └── theme │ │ │ └── default │ │ │ ├── font │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.svg │ │ │ ├── iconfont.ttf │ │ │ └── iconfont.woff │ │ │ └── laydate.css │ ├── popper.min.js │ └── toastr.min.js ├── page │ ├── error.html │ └── languages.xml └── webfonts │ ├── fa-solid-900.eot │ ├── fa-solid-900.svg │ ├── fa-solid-900.ttf │ ├── fa-solid-900.woff │ └── fa-solid-900.woff2 └── views ├── client ├── add.html ├── edit.html └── list.html ├── global └── index.html ├── index ├── add.html ├── edit.html ├── hadd.html ├── hedit.html ├── help.html ├── hlist.html ├── index.html └── list.html ├── login ├── index.html └── register.html └── public ├── error.html └── layout.html /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js linguist-language=golang 2 | *.css linguist-language=golang 3 | *.html linguist-language=golang 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | nps 3 | npc 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.14.x 5 | services: 6 | - docker 7 | script: 8 | - GOPROXY=direct go test -v ./cmd/nps/ 9 | os: 10 | - linux 11 | before_deploy: 12 | - chmod +x ./build.sh && chmod +x ./build.android.sh && ./build.sh 13 | 14 | deploy: 15 | provider: releases 16 | edge: true 17 | token: ${GH_TOKEN} 18 | cleanup: false 19 | file: 20 | - freebsd_386_client.tar.gz 21 | - freebsd_386_server.tar.gz 22 | - freebsd_amd64_client.tar.gz 23 | - freebsd_amd64_server.tar.gz 24 | - freebsd_arm_client.tar.gz 25 | - freebsd_arm_server.tar.gz 26 | - linux_386_client.tar.gz 27 | - linux_386_server.tar.gz 28 | - linux_amd64_client.tar.gz 29 | - linux_amd64_server.tar.gz 30 | - linux_arm64_client.tar.gz 31 | - linux_arm64_server.tar.gz 32 | - linux_arm_v5_client.tar.gz 33 | - linux_arm_v6_client.tar.gz 34 | - linux_arm_v7_client.tar.gz 35 | - linux_arm_v5_server.tar.gz 36 | - linux_arm_v6_server.tar.gz 37 | - linux_arm_v7_server.tar.gz 38 | - linux_mips64le_client.tar.gz 39 | - linux_mips64le_server.tar.gz 40 | - linux_mips64_client.tar.gz 41 | - linux_mips64_server.tar.gz 42 | - linux_mipsle_client.tar.gz 43 | - linux_mipsle_server.tar.gz 44 | - linux_mips_client.tar.gz 45 | - linux_mips_server.tar.gz 46 | - darwin_amd64_client.tar.gz 47 | - darwin_amd64_server.tar.gz 48 | - windows_386_client.tar.gz 49 | - windows_386_server.tar.gz 50 | - windows_amd64_client.tar.gz 51 | - windows_amd64_server.tar.gz 52 | - npc_syno.spk 53 | - npc_sdk.tar.gz 54 | - android_client.apk 55 | on: 56 | tags: true 57 | all_branches: true 58 | -------------------------------------------------------------------------------- /Dockerfile.npc: -------------------------------------------------------------------------------- 1 | FROM golang:1.15 as builder 2 | ARG GOPROXY=direct 3 | WORKDIR /go/src/ehang.io/nps 4 | COPY . . 5 | RUN go get -d -v ./... 6 | RUN CGO_ENABLED=0 go build -ldflags="-w -s -extldflags -static" ./cmd/npc/npc.go 7 | 8 | FROM scratch 9 | COPY --from=builder /go/src/ehang.io/nps/npc / 10 | VOLUME /conf 11 | ENTRYPOINT ["/npc"] 12 | -------------------------------------------------------------------------------- /Dockerfile.nps: -------------------------------------------------------------------------------- 1 | FROM golang:1.15 as builder 2 | ARG GOPROXY=direct 3 | WORKDIR /go/src/ehang.io/nps 4 | COPY . . 5 | RUN go get -d -v ./... 6 | RUN CGO_ENABLED=0 go build -ldflags="-w -s -extldflags -static" ./cmd/nps/nps.go 7 | 8 | FROM scratch 9 | COPY --from=builder /go/src/ehang.io/nps/nps / 10 | COPY --from=builder /go/src/ehang.io/nps/web /web 11 | VOLUME /conf 12 | CMD ["/nps"] 13 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCE_FILES?=./... 2 | TEST_PATTERN?=. 3 | TEST_OPTIONS?= 4 | 5 | export PATH := ./bin:$(PATH) 6 | export GO111MODULE := on 7 | export GOPROXY := https://gocenter.io 8 | 9 | # Build a beta version of goreleaser 10 | build: 11 | go build cmd/nps/nps.go 12 | go build cmd/npc/npc.go 13 | .PHONY: build 14 | 15 | # Install all the build and lint dependencies 16 | setup: 17 | curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh 18 | curl -L https://git.io/misspell | sh 19 | go mod download 20 | .PHONY: setup 21 | 22 | # Run all the tests 23 | test: 24 | go test $(TEST_OPTIONS) -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m 25 | .PHONY: test 26 | 27 | # Run all the tests and opens the coverage report 28 | cover: test 29 | go tool cover -html=coverage.txt 30 | .PHONY: cover 31 | 32 | # gofmt and goimports all go files 33 | fmt: 34 | find . -name '*.go' -not -wholename './vendor/*' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done 35 | .PHONY: fmt 36 | 37 | # Run all the linters 38 | lint: 39 | # TODO: fix tests and lll issues 40 | ./bin/golangci-lint run --tests=false --enable-all --disable=lll ./... 41 | ./bin/misspell -error **/* 42 | .PHONY: lint 43 | 44 | # Clean go.mod 45 | go-mod-tidy: 46 | @go mod tidy -v 47 | @git diff HEAD 48 | @git diff-index --quiet HEAD 49 | .PHONY: go-mod-tidy 50 | 51 | # Run all the tests and code checks 52 | ci: build test lint go-mod-tidy 53 | .PHONY: ci 54 | 55 | # Generate the static documentation 56 | static: 57 | @hugo --enableGitInfo --source www 58 | .PHONY: static 59 | 60 | # Show to-do items per file. 61 | todo: 62 | @grep \ 63 | --exclude-dir=vendor \ 64 | --exclude-dir=node_modules \ 65 | --exclude=Makefile \ 66 | --text \ 67 | --color \ 68 | -nRo -E ' TODO:.*|SkipNow' . 69 | .PHONY: todo 70 | 71 | clean: 72 | rm npc nps 73 | .PHONY: clean 74 | 75 | .DEFAULT_GOAL := build 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nps-socks5服务一键搭建脚本 2 | - [x] 稳定版V3.1 3 | 4 | ## 介绍 ## 5 | 基于[nps](https://github.com/ehang-io/nps)的Shell脚本,集成socks5搭建,管理,启动,添加账号等基本操作。方便用户操作,并且支持快速构建socks5服务环境。 6 | - 默认管理页面ip:18080
7 | - 默认管理员账号密码:admin admin
8 | - 默认socks5账号信息: 端口5555、账号验证无(建议开启) 9 | - 支持多端口、多账号管理
10 | - 加密传输、数据压缩
11 | - 服务端、客户端分离安装、可实现国内中专代理,降低被和谐概率
12 | - 脚本只提供学习交流!
13 | ![image](https://raw.githubusercontent.com/wyx176/nps-socks5/refs/heads/master/server.png) 14 | ![image](https://raw.githubusercontent.com/wyx176/nps-socks5/refs/heads/master/port.png) 15 | ## 系统支持 ## 16 | * contest、ubuntu、debian、windows
17 | ## 功能 ## 18 | - 全自动无人值守安装,服务端部署只需一条命令 19 | - 全新的web端管理,支持多端口、多账号、多服务器、以及中转代理 20 | - 添加账户、删除用户、开启账户验证、关闭账户验证、一键修改端口 21 | 22 | ## 方法一:一键安装或更新到最新 ## 23 |
curl --progress-bar -O https://raw.githubusercontent.com/wyx176/nps-socks5/master/install.sh && chmod 777 install.sh && ./install.sh
24 | 或者 25 |
wget --show-progress -q -N --no-check-certificate https://raw.githubusercontent.com/wyx176/nps-socks5/master/install.sh && chmod 777 install.sh && ./install.sh
26 | ## 方法二:linux、windows均支持,需要安装go语言环境进行编译 27 | [参考NPS文档](https://ehang-io.github.io/nps/#/install)
28 | 1、安装源码 29 |
go get -u github.com/wyx176/nps-socks5
30 | 2、编译服务端:进入到nps-socks5文件夹中执行命令 31 |
go build cmd/nps/nps.go
32 | 3、编译客户端:进入到nps-socks5文件夹中执行命令 33 |
go build cmd/npc/npc.go
34 | ## 相关文件路径、命令 ## 35 | - 1、后台管理的配置文件
36 | /etc/nps/conf
37 | 登录账号web_username=admin
38 | 登录密码web_password=admin
39 | web管理端口web_port = 18080
40 | 修改后需要重启服务端 41 | - 2、基本命令
42 | 启动服务端: nps start
43 | 停止服务端: nps stop
44 | ## 更新日志 ## 45 | -2025.01.11 v3.1
46 | 1、取消账号限制
47 | 2、增加账号到期时间
48 | 3、增加流量限制
49 | 4、修复bug
50 | -2022.10.06 v3.0
51 | 1、端口增加创建时间、到期时间
52 | 2、开启用户注册
53 | 安装后修改/etc/nps/conf/nps.conf中
54 | allow_user_login=true
55 | allow_user_register=true
56 | 57 | -2022.10.03 v2.0
58 | 1、增加多端口、多账号设置
59 | -2022.09.03 v1.0
60 | 61 | ## 写在最后 ## 62 | Telegram交流群:https://t.me/Socks55555 63 | -------------------------------------------------------------------------------- /README_NPS.md: -------------------------------------------------------------------------------- 1 | 2 | # nps 3 | ![](https://img.shields.io/github/stars/ehang-io/nps.svg) ![](https://img.shields.io/github/forks/ehang-io/nps.svg) 4 | [![Gitter](https://badges.gitter.im/cnlh-nps/community.svg)](https://gitter.im/cnlh-nps/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 5 | ![Release](https://github.com/ehang-io/nps/workflows/Release/badge.svg) 6 | ![GitHub All Releases](https://img.shields.io/github/downloads/ehang-io/nps/total) 7 | 8 | [README](https://github.com/ehang-io/nps/blob/master/README.md)|[中文文档](https://github.com/ehang-io/nps/blob/master/README_zh.md) 9 | 10 | nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**,可支持任何**tcp、udp**上层协议(访问内网网站、本地支付接口调试、ssh访问、远程桌面,内网dns解析等等……),此外还**支持内网http代理、内网socks5代理**、**p2p等**,并带有功能强大的web管理端。 11 | 12 | 13 | ## 背景 14 | ![image](https://github.com/ehang-io/nps/blob/master/image/web.png?raw=true) 15 | 16 | 1. 做微信公众号开发、小程序开发等----> 域名代理模式 17 | 18 | 2. 想在外网通过ssh连接内网的机器,做云服务器到内网服务器端口的映射,----> tcp代理模式 19 | 20 | 3. 在非内网环境下使用内网dns,或者需要通过udp访问内网机器等----> udp代理模式 21 | 22 | 4. 在外网使用HTTP代理访问内网站点----> http代理模式 23 | 24 | 5. 搭建一个内网穿透ss,在外网如同使用内网vpn一样访问内网资源或者设备----> socks5代理模式 25 | ## 特点 26 | - 协议支持全面,兼容几乎所有常用协议,例如tcp、udp、http(s)、socks5、p2p、http代理... 27 | - 全平台兼容(linux、windows、macos、群辉等),支持一键安装为系统服务 28 | - 控制全面,同时支持服务端和客户端控制 29 | - https集成,支持将后端代理和web服务转成https,同时支持多证书 30 | - 操作简单,只需简单的配置即可在web ui上完成其余操作 31 | - 展示信息全面,流量、系统信息、即时带宽、客户端版本等 32 | - 扩展功能强大,该有的都有了(缓存、压缩、加密、流量限制、带宽限制、端口复用等等) 33 | - 域名解析具备自定义header、404页面配置、host修改、站点保护、URL路由、泛解析等功能 34 | - 服务端支持多用户和用户注册功能 35 | 36 | **没找到你想要的功能?不要紧,点击[进入文档](https://ehang-io.github.io/nps)查找吧** 37 | ## 快速开始 38 | 39 | ### 安装 40 | > [releases](https://github.com/ehang-io/nps/releases) 41 | 42 | 下载对应的系统版本即可,服务端和客户端是单独的 43 | 44 | ### 服务端启动 45 | 下载完服务器压缩包后,解压,然后进入解压后的文件夹 46 | 47 | - 执行安装命令 48 | 49 | 对于linux|darwin ```sudo ./nps install``` 50 | 51 | 对于windows,管理员身份运行cmd,进入安装目录 ```nps.exe install``` 52 | 53 | - 默认端口 54 | 55 | nps默认配置文件使用了80,443,8080,8024端口 56 | 57 | 80与443端口为域名解析模式默认端口 58 | 59 | 8080为web管理访问端口 60 | 61 | 8024为网桥端口,用于客户端与服务器通信 62 | 63 | - 启动 64 | 65 | 对于linux|darwin ```sudo nps start``` 66 | 67 | 对于windows,管理员身份运行cmd,进入程序目录 ```nps.exe start``` 68 | 69 | ```安装后windows配置文件位于 C:\Program Files\nps,linux和darwin位于/etc/nps``` 70 | 71 | **如果发现没有启动成功,可以查看日志(Windows日志文件位于当前运行目录下,linux和darwin位于/var/log/nps.log)** 72 | - 访问服务端ip:web服务端口(默认为8080) 73 | - 使用用户名和密码登陆(默认admin/123,正式使用一定要更改) 74 | - 创建客户端 75 | 76 | ### 客户端连接 77 | - 点击web管理中客户端前的+号,复制启动命令 78 | - 执行启动命令,linux直接执行即可,windows将./npc换成npc.exe用cmd执行 79 | 80 | 如果需要注册到系统服务可查看[注册到系统服务](https://ehang-io.github.io/nps/#/use?id=注册到系统服务) 81 | 82 | ### 配置 83 | - 客户端连接后,在web中配置对应穿透服务即可 84 | - 更多高级用法见[完整文档](https://ehang-io.github.io/nps/) 85 | 86 | ## 贡献 87 | - 如果遇到bug可以直接提交至dev分支 88 | - 使用遇到问题可以通过issues反馈 89 | - 项目处于开发阶段,还有很多待完善的地方,如果可以贡献代码,请提交 PR 至 dev 分支 90 | - 如果有新的功能特性反馈,可以通过issues或者qq群反馈 91 | -------------------------------------------------------------------------------- /build.android.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | cd /go 4 | apt-get install libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev -y 5 | go get -u fyne.io/fyne/v2/cmd/fyne fyne.io/fyne/v2 6 | #mkdir -p /go/src/fyne.io 7 | #cd src/fyne.io 8 | #git clone https://github.com/fyne-io/fyne.git 9 | #cd fyne 10 | #git checkout v1.2.0 11 | #go install -v ./cmd/fyne 12 | #fyne package -os android fyne.io/fyne/cmd/hello 13 | echo "fyne install success" 14 | mkdir -p /go/src/ehang.io/nps 15 | cp -R /app/* /go/src/ehang.io/nps 16 | cd /go/src/ehang.io/nps 17 | #go get -u fyne.io/fyne fyne.io/fyne/cmd/fyne 18 | rm cmd/npc/sdk.go 19 | #go get -u ./... 20 | #go mod tidy 21 | #rm -rf /go/src/golang.org/x/mobile 22 | echo "tidy success" 23 | cd /go/src/ehang.io/nps 24 | go mod vendor 25 | cd vendor 26 | cp -R * /go/src 27 | cd .. 28 | rm -rf vendor 29 | #rm -rf ~/.cache/* 30 | echo "vendor success" 31 | cd gui/npc 32 | fyne package -appID org.nps.client -os android -icon ../../docs/logo.png 33 | mv npc.apk /app/android_client.apk 34 | echo "android build success" 35 | -------------------------------------------------------------------------------- /client/health.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "container/heap" 5 | "net" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "ehang.io/nps/lib/conn" 11 | "ehang.io/nps/lib/file" 12 | "ehang.io/nps/lib/sheap" 13 | "github.com/astaxie/beego/logs" 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | var isStart bool 18 | var serverConn *conn.Conn 19 | 20 | func heathCheck(healths []*file.Health, c *conn.Conn) bool { 21 | serverConn = c 22 | if isStart { 23 | for _, v := range healths { 24 | v.HealthMap = make(map[string]int) 25 | } 26 | return true 27 | } 28 | isStart = true 29 | h := &sheap.IntHeap{} 30 | for _, v := range healths { 31 | if v.HealthMaxFail > 0 && v.HealthCheckTimeout > 0 && v.HealthCheckInterval > 0 { 32 | v.HealthNextTime = time.Now().Add(time.Duration(v.HealthCheckInterval) * time.Second) 33 | heap.Push(h, v.HealthNextTime.Unix()) 34 | v.HealthMap = make(map[string]int) 35 | } 36 | } 37 | go session(healths, h) 38 | return true 39 | } 40 | 41 | func session(healths []*file.Health, h *sheap.IntHeap) { 42 | for { 43 | if h.Len() == 0 { 44 | logs.Error("health check error") 45 | break 46 | } 47 | rs := heap.Pop(h).(int64) - time.Now().Unix() 48 | if rs <= 0 { 49 | continue 50 | } 51 | timer := time.NewTimer(time.Duration(rs) * time.Second) 52 | select { 53 | case <-timer.C: 54 | for _, v := range healths { 55 | if v.HealthNextTime.Before(time.Now()) { 56 | v.HealthNextTime = time.Now().Add(time.Duration(v.HealthCheckInterval) * time.Second) 57 | //check 58 | go check(v) 59 | //reset time 60 | heap.Push(h, v.HealthNextTime.Unix()) 61 | } 62 | } 63 | } 64 | } 65 | } 66 | 67 | // work when just one port and many target 68 | func check(t *file.Health) { 69 | arr := strings.Split(t.HealthCheckTarget, ",") 70 | var err error 71 | var rs *http.Response 72 | for _, v := range arr { 73 | if t.HealthCheckType == "tcp" { 74 | var c net.Conn 75 | c, err = net.DialTimeout("tcp", v, time.Duration(t.HealthCheckTimeout)*time.Second) 76 | if err == nil { 77 | c.Close() 78 | } 79 | } else { 80 | client := &http.Client{} 81 | client.Timeout = time.Duration(t.HealthCheckTimeout) * time.Second 82 | rs, err = client.Get("http://" + v + t.HttpHealthUrl) 83 | if err == nil && rs.StatusCode != 200 { 84 | err = errors.New("status code is not match") 85 | } 86 | } 87 | t.Lock() 88 | if err != nil { 89 | t.HealthMap[v] += 1 90 | } else if t.HealthMap[v] >= t.HealthMaxFail { 91 | //send recovery add 92 | serverConn.SendHealthInfo(v, "1") 93 | t.HealthMap[v] = 0 94 | } 95 | 96 | if t.HealthMap[v] > 0 && t.HealthMap[v]%t.HealthMaxFail == 0 { 97 | //send fail remove 98 | serverConn.SendHealthInfo(v, "0") 99 | } 100 | t.Unlock() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /client/register.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/binary" 5 | "log" 6 | "os" 7 | 8 | "ehang.io/nps/lib/common" 9 | ) 10 | 11 | func RegisterLocalIp(server string, vKey string, tp string, proxyUrl string, hour int) { 12 | c, err := NewConn(tp, vKey, server, common.WORK_REGISTER, proxyUrl) 13 | if err != nil { 14 | log.Fatalln(err) 15 | } 16 | if err := binary.Write(c, binary.LittleEndian, int32(hour)); err != nil { 17 | log.Fatalln(err) 18 | } 19 | log.Printf("Successful ip registration for local public network, the validity period is %d hours.", hour) 20 | os.Exit(0) 21 | } 22 | -------------------------------------------------------------------------------- /cmd/npc/sdk.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "C" 5 | "ehang.io/nps/client" 6 | "ehang.io/nps/lib/common" 7 | "ehang.io/nps/lib/version" 8 | "github.com/astaxie/beego/logs" 9 | ) 10 | 11 | var cl *client.TRPClient 12 | 13 | //export StartClientByVerifyKey 14 | func StartClientByVerifyKey(serverAddr, verifyKey, connType, proxyUrl *C.char) int { 15 | _ = logs.SetLogger("store") 16 | if cl != nil { 17 | cl.Close() 18 | } 19 | cl = client.NewRPClient(C.GoString(serverAddr), C.GoString(verifyKey), C.GoString(connType), C.GoString(proxyUrl), nil, 60) 20 | cl.Start() 21 | return 1 22 | } 23 | 24 | //export GetClientStatus 25 | func GetClientStatus() int { 26 | return client.NowStatus 27 | } 28 | 29 | //export CloseClient 30 | func CloseClient() { 31 | if cl != nil { 32 | cl.Close() 33 | } 34 | } 35 | 36 | //export Version 37 | func Version() *C.char { 38 | return C.CString(version.VERSION) 39 | } 40 | 41 | //export Logs 42 | func Logs() *C.char { 43 | return C.CString(common.GetLogMsg()) 44 | } 45 | 46 | func main() { 47 | // Need a main function to make CGO compile package as C shared library 48 | } 49 | -------------------------------------------------------------------------------- /conf/clients.json: -------------------------------------------------------------------------------- 1 | {"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":1,"VerifyKey":"a1efa114df","Addr":"","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":false,"MaxTunnelNum":0,"Version":"0.26.21","BlackIpList":[""],"CreateTime":"2025-01-03 17:07:37","LastOnlineTime":""} 2 | *#* -------------------------------------------------------------------------------- /conf/global.json: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /conf/hosts.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/conf/hosts.json -------------------------------------------------------------------------------- /conf/multi_account.conf: -------------------------------------------------------------------------------- 1 | # key -> user | value -> pwd 2 | npc=npc.pwd -------------------------------------------------------------------------------- /conf/npc.conf: -------------------------------------------------------------------------------- 1 | [common] 2 | server_addr=127.0.0.1:8024 3 | conn_type=tcp 4 | vkey=123 5 | auto_reconnection=true 6 | max_conn=1000 7 | flow_limit=1000 8 | rate_limit=1000 9 | basic_username=11 10 | basic_password=3 11 | web_username=user 12 | web_password=1234 13 | crypt=true 14 | compress=true 15 | #pprof_addr=0.0.0.0:9999 16 | disconnect_timeout=60 17 | tls_enable=true 18 | 19 | [health_check_test1] 20 | health_check_timeout=1 21 | health_check_max_failed=3 22 | health_check_interval=1 23 | health_http_url=/ 24 | health_check_type=http 25 | health_check_target=127.0.0.1:8083,127.0.0.1:8082 26 | 27 | [health_check_test2] 28 | health_check_timeout=1 29 | health_check_max_failed=3 30 | health_check_interval=1 31 | health_check_type=tcp 32 | health_check_target=127.0.0.1:8083,127.0.0.1:8082 33 | 34 | [web] 35 | host=c.o.com 36 | target_addr=127.0.0.1:8083,127.0.0.1:8082 37 | 38 | [tcp] 39 | mode=tcp 40 | target_addr=127.0.0.1:8080 41 | server_port=10000 42 | 43 | [socks5] 44 | mode=socks5 45 | server_port=19009 46 | multi_account=multi_account.conf 47 | 48 | [file] 49 | mode=file 50 | server_port=19008 51 | local_path=/Users/liuhe/Downloads 52 | strip_pre=/web/ 53 | 54 | [http] 55 | mode=httpProxy 56 | server_port=19004 57 | 58 | [udp] 59 | mode=udp 60 | server_port=12253 61 | target_addr=114.114.114.114:53 62 | 63 | [ssh_secret] 64 | mode=secret 65 | password=ssh2 66 | target_addr=123.206.77.88:22 67 | 68 | [ssh_p2p] 69 | mode=p2p 70 | password=ssh3 71 | 72 | [secret_ssh] 73 | local_port=2001 74 | password=ssh2 75 | 76 | [p2p_ssh] 77 | local_port=2002 78 | password=ssh3 79 | target_addr=123.206.77.88:22 -------------------------------------------------------------------------------- /conf/nps.conf: -------------------------------------------------------------------------------- 1 | appname = nps 2 | #Boot mode(dev|pro) 3 | runmode = dev 4 | 5 | #HTTP(S) proxy port, no startup if empty 6 | http_proxy_ip=0.0.0.0 7 | http_proxy_port= 8 | https_proxy_port= 9 | https_just_proxy=true 10 | #default https certificate setting 11 | https_default_cert_file=conf/server.pem 12 | https_default_key_file=conf/server.key 13 | 14 | ##bridge 15 | bridge_type=tcp 16 | bridge_port=8024 17 | bridge_ip=0.0.0.0 18 | 19 | # Public password, which clients can use to connect to the server 20 | # After the connection, the server will be able to open relevant ports and parse related domain names according to its own configuration file. 21 | public_vkey=123321 22 | 23 | #Traffic data persistence interval(minute) 24 | #Ignorance means no persistence 25 | flow_store_interval=1 26 | 27 | # log level LevelEmergency->0 LevelAlert->1 LevelCritical->2 LevelError->3 LevelWarning->4 LevelNotice->5 LevelInformational->6 LevelDebug->7 28 | log_level=6 29 | log_path=nps.log 30 | 31 | #Whether to restrict IP access, true or false or ignore 32 | #ip_limit=true 33 | 34 | #p2p 35 | #p2p_ip=127.0.0.1 36 | #p2p_port=6000 37 | 38 | #web 39 | web_host=a.o.com 40 | web_username=admin 41 | web_password=admin 42 | web_port = 18080 43 | web_ip=0.0.0.0 44 | web_base_url= 45 | web_open_ssl=false 46 | web_cert_file=conf/server.pem 47 | web_key_file=conf/server.key 48 | # if web under proxy use sub path. like http://host/nps need this. 49 | #web_base_url=/nps 50 | 51 | #Web API unauthenticated IP address(the len of auth_crypt_key must be 16) 52 | #Remove comments if needed 53 | #auth_key=test 54 | auth_key=123 55 | #获取服务端authKey时的aes加密密钥,16位 56 | auth_crypt_key =213 57 | 58 | #allow_ports=9001-9009,10001,11000-12000 59 | 60 | #Web management multi-user login 61 | allow_user_login=false 62 | allow_user_register=false 63 | allow_user_change_username=false 64 | 65 | #extension 66 | #流量限制 67 | allow_flow_limit=true 68 | #带宽限制 69 | allow_rate_limit=true 70 | #客户端最大隧道数限制 71 | allow_tunnel_num_limit=true 72 | allow_local_proxy=false 73 | #客户端最大连接数 74 | allow_connection_num_limit=true 75 | #每个隧道监听不同的服务端端口 76 | allow_multi_ip=true 77 | system_info_display=true 78 | 79 | #获取用户真实ip 80 | http_add_origin_header=true 81 | 82 | #cache 83 | http_cache=false 84 | http_cache_length=100 85 | 86 | #get origin ip 87 | #http_add_origin_header=false 88 | 89 | #pprof debug options 90 | #pprof_ip=0.0.0.0 91 | #pprof_port=9999 92 | 93 | #client disconnect timeout 94 | disconnect_timeout=60 95 | 96 | #管理面板开启验证码校验 97 | open_captcha=false 98 | 99 | 100 | # 是否开启tls 101 | tls_enable=false 102 | tls_bridge_port=8025 -------------------------------------------------------------------------------- /conf/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA2MVLOHvgU8FCp6LgQrPfaWcGygrsRk7TL9hbT8MxbCRUSLV7 3 | Lbt3q5Knz8eTN4NWmwE6L5glOcH2x3Hnn+hPjbvgq35XBBIccAm0cYYKqoKkikeK 4 | FZM0Gp/WhSrhJ4laTyQqyleIFKpwD9kHDiC/sxjGDhSFmHKhhAnsQIRm2tppFXX0 5 | aAMqJEm88jzk1BN2QtKjEAn1u8v1+QW1KP3WuzdXH4L7hhMll66/KIm6Hfs2FRHQ 6 | pRUWqZeJY4q79NW5p5f+siGwOsGpxb/p11pM+0xnCH3UIFbm3zCTzP4sLvkfFGAe 7 | yAHsAwmaP8dJxh40ej3NN8uNiNvt8nw2Vb/1LwIDAQABAoIBAD40x/RKoEKIyE8B 8 | D6g0pB1EQo+CePFoN3SYewO1uR4WgtVmtxWVoa7r5BpdZGLe3uCWhpMX7z7W6bGs 9 | f1LFQOckjkHIfMIfTGfecRjO5Yqu+Pbxtq+gUah+S/plJr3IzdC+SUVNvzBnBMeX 10 | eU3Vmg2UQ2nQ+9GWu8D/c/vDwxx0X8oQ2G8QaxX0tUurlSMNA3M7xySwEvhx54fO 11 | UrDF3Q4yF48eA4butxVLFWf3cnlY+nR8uYd2vKfmp689/8C6kkfoM9igB78e93sm 12 | uDM2eRLm4kU5WLl301T42n6AF7w8J0MhLLVOIeLs4l5gZPa3uKvYFmuHQao7e/5R 13 | U/jHKrECgYEA8alPXuxFSVOvdhIsSN//Frj9CdExVdYmaLkt/2LO4FMnOaWh1xh7 14 | 5iCY1bJT8D9dhfbqRg3qW2oguZD8gu04R8fTRegQ89qmAIwsEYqVf9salR41lZU4 15 | Rc+5yc7O11WIe9Lzu+ONFBFkAh3UFMR4zVZ/JhKIG/P5Srm7SUdKW2cCgYEA5aHo 16 | x2LR+yKhjkrBzHG3Qrfy1PtlYHjOpYYAKHQcBFuiG08W3CK/vkYl+mhv0uyhT7mn 17 | q6NDqrpZPRnDlOoEqgRS1X/QWKN6Pgd4HNLIawvp0vK9jYXDPcAXFzVthXCIwFcn 18 | 3a3m4cHiuLdRNOHkydiHQyTOF6eEneN07TDvwvkCgYEApzOd1u9igPmFzQuF2GYi 19 | +HXFnaU/nUQuDwcQ7EJRIKRn31raPxiRoQesty5LJU6yRp4wOYgnPliPi9Tk4TGA 20 | XynC4/tMv2vorzhMxVY9Wdke602bhYNZC/RNd3O/aP2lEQdD3Bv04I2nxE8fDb9i 21 | VbAjCRSJV83WDf2zt1+78sECgYEAzezjRiKdcZu9y0/I+WEk2cUCE/MaF2he0FsZ 22 | uy1cjp/qAJltQ5452xUnK6cKWNlxU4CHF0mC/hC8xCldliZCZoEYE3PaUBLSJdwm 23 | 35o6tpxpZI3gZJCG5NJlIp/8BkVDrVC7ZHV17hAkFEf4n/bPaB8wNYtE8jt8luaK 24 | TcarzGkCgYBn2alN0RLN2PHDurraFZB6GuCvh/arEjSCY3SDFQPF10CVjTDV7sx3 25 | eqJkwJ81syTmfJwZIceWbOFGgsuSx37UrQAVlHZSvzeqEg9dA5HqSoOACyidJI7j 26 | RG2+HB+KpsIZjGgLrEM4i7VOpYUDRdaouIXngFq/t9HNT+MDck5/Lw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /conf/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDtTCCAp2gAwIBAgIJAPXRSiP0Fs7sMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQwHhcNMTcxMTA3MDg1MzQ2WhcNMjcxMTA1MDg1MzQ2WjBF 5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 7 | CgKCAQEA2MVLOHvgU8FCp6LgQrPfaWcGygrsRk7TL9hbT8MxbCRUSLV7Lbt3q5Kn 8 | z8eTN4NWmwE6L5glOcH2x3Hnn+hPjbvgq35XBBIccAm0cYYKqoKkikeKFZM0Gp/W 9 | hSrhJ4laTyQqyleIFKpwD9kHDiC/sxjGDhSFmHKhhAnsQIRm2tppFXX0aAMqJEm8 10 | 8jzk1BN2QtKjEAn1u8v1+QW1KP3WuzdXH4L7hhMll66/KIm6Hfs2FRHQpRUWqZeJ 11 | Y4q79NW5p5f+siGwOsGpxb/p11pM+0xnCH3UIFbm3zCTzP4sLvkfFGAeyAHsAwma 12 | P8dJxh40ej3NN8uNiNvt8nw2Vb/1LwIDAQABo4GnMIGkMB0GA1UdDgQWBBQdPc0R 13 | a8alY6Ab7voidkTGaH4PxzB1BgNVHSMEbjBsgBQdPc0Ra8alY6Ab7voidkTGaH4P 14 | x6FJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV 15 | BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAPXRSiP0Fs7sMAwGA1UdEwQF 16 | MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAH1IZNkjuvt2nZPzXsuiVNyCE1vm346z 17 | naE0Uzt3aseAN9m/iiB8mLz+ryvWc2aFMX5lTdsHdm2rqmqBCBXeRwTLf4OeHIju 18 | ZQW6makWt6PxANEo6gbdPbQXbS420ssUhnR2irIH1SdI31iikVFPdiS0baRRE/gS 19 | +440M1jOOOnKm0Qin92ejsshmji/0qaD2+6D5TNw4HmIZaFTBw+kfjxCL6trfeBn 20 | 4fT0RJ121V3G3+AtG5sWQ93B3pCg+jtD+fGKkNSLhphq84bD1Zv7l73QGOoylkEn 21 | Sc0ajTLOXFBb83yRdlgV3Da95jH9rDZ4jSod48m+KemoZTDQw0vSwAU= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /conf/tasks.json: -------------------------------------------------------------------------------- 1 | {"Id":1,"Port":5555,"S5User":"","PortConfig":{"FlowLimit":0,"RateLimit":0,"MaxConn":0,"NowConn":0,"ExpireTime":""},"CreateTime":"2025-01-11 07:21:09","ServerIp":"","Mode":"socks5","Status":true,"RunStatus":true,"Client":{"Cnf":{"U":"","P":"","Compress":false,"Crypt":false},"Id":1,"VerifyKey":"a1efa114df","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":false,"MaxTunnelNum":0,"Version":"0.26.21","BlackIpList":[""],"CreateTime":"2025-01-03 17:07:37","LastOnlineTime":""},"Ports":"","Flow":{"ExportFlow":0,"InletFlow":0,"FlowLimit":0},"Password":"","Remark":"默认账号","TargetAddr":"","NoStore":false,"IsHttp":false,"LocalPath":"","StripPre":"","Target":{"TargetStr":"","TargetArr":null,"LocalProxy":false},"MultiAccount":{"AccountMap":{}},"HealthCheckTimeout":0,"HealthMaxFail":0,"HealthCheckInterval":0,"HealthNextTime":"0001-01-01T00:00:00Z","HealthMap":null,"HttpHealthUrl":"","HealthRemoveArr":null,"HealthCheckType":"","HealthCheckTarget":""} 2 | *#* -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/docs/.nojekyll -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # nps 2 | ![](https://img.shields.io/github/stars/cnlh/nps.svg) ![](https://img.shields.io/github/forks/cnlh/nps.svg) 3 | [![Gitter](https://badges.gitter.im/cnlh-nps/community.svg)](https://gitter.im/cnlh-nps/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 4 | [![Build Status](https://travis-ci.org/ehang-io/nps.svg?branch=master)](https://travis-ci.org/cnlh/nps) 5 | 6 | nps是一款轻量级、高性能、功能强大的**内网穿透**代理服务器。目前支持**tcp、udp流量转发**,可支持任何**tcp、udp**上层协议(访问内网网站、本地支付接口调试、ssh访问、远程桌面,内网dns解析等等……),此外还**支持内网http代理、内网socks5代理**、**p2p等**,并带有功能强大的web管理端。 7 | 8 | 9 | ## 背景 10 | ![image](https://github.com/ehang-io/nps/blob/master/image/web.png?raw=true) 11 | 12 | 1. 做微信公众号开发、小程序开发等----> 域名代理模式 13 | 14 | 15 | 2. 想在外网通过ssh连接内网的机器,做云服务器到内网服务器端口的映射,----> tcp代理模式 16 | 17 | 3. 在非内网环境下使用内网dns,或者需要通过udp访问内网机器等----> udp代理模式 18 | 19 | 4. 在外网使用HTTP代理访问内网站点----> http代理模式 20 | 21 | 5. 搭建一个内网穿透ss,在外网如同使用内网vpn一样访问内网资源或者设备----> socks5代理模式 22 | -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](logo.svg) 2 | 3 | # NPS 0.26.10 4 | 5 | > 一款轻量级、高性能、功能强大的内网穿透代理服务器 6 | 7 | - 几乎支持所有协议 8 | - 支持内网http代理、内网socks5代理、p2p等 9 | - 简洁但功能强大的WEB管理界面 10 | - 支持服务端、客户端同时控制 11 | - 扩展功能强大 12 | - 全平台兼容,一键注册为服务 13 | 14 | 15 | [GitHub](https://github.com/ehang-io/nps/) 16 | [开始使用](#nps) 17 | -------------------------------------------------------------------------------- /docs/_navbar.md: -------------------------------------------------------------------------------- 1 | * [![GitHub stars](https://img.shields.io/github/stars/ehang-io/nps?style=social)](https://github.com/ehang-io/nps/stargazers) 2 | 3 | * [![GitHub forks](https://img.shields.io/github/forks/ehang-io/nps?style=social)](https://github.com/ehang-io/nps/network) -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * 入门 2 | * [安装](install.md) 3 | * [启动](run.md) 4 | * [使用示例](example.md) 5 | * 服务端 6 | * [介绍](introduction.md) 7 | * [使用](nps_use.md) 8 | * [配置文件](server_config.md) 9 | * [增强功能](nps_extend.md) 10 | 11 | * 客户端 12 | 13 | * [基本使用](use.md) 14 | * [增强功能](npc_extend.md) 15 | 16 | * 扩展 17 | 18 | * [功能](feature.md) 19 | * [说明](description.md) 20 | * [web api](api.md) 21 | * [sdk](npc_sdk.md) 22 | 23 | * 其他 24 | 25 | * [FAQ](faq.md) 26 | * [贡献](contribute.md) 27 | * [捐助](donate.md) 28 | * [致谢](thanks.md) 29 | * [交流](discuss.md) 30 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | # web api 2 | 3 | 需要开启请先去掉`nps.conf`中`auth_key`的注释并配置一个合适的密钥 4 | ## webAPI验证说明 5 | - 采用auth_key的验证方式 6 | - 在提交的每个请求后面附带两个参数,`auth_key` 和`timestamp` 7 | 8 | ``` 9 | auth_key的生成方式为:md5(配置文件中的auth_key+当前时间戳) 10 | ``` 11 | 12 | ``` 13 | timestamp为当前时间戳 14 | ``` 15 | ``` 16 | curl --request POST \ 17 | --url http://127.0.0.1:8080/client/list \ 18 | --data 'auth_key=2a0000d9229e7dbcf79dd0f5e04bb084×tamp=1553045344&start=0&limit=10' 19 | ``` 20 | **注意:** 为保证安全,时间戳的有效范围为20秒内,所以每次提交请求必须重新生成。 21 | 22 | ## 获取服务端时间 23 | 由于服务端与api请求的客户端时间差异不能太大,所以提供了一个可以获取服务端时间的接口 24 | 25 | ``` 26 | POST /auth/gettime 27 | ``` 28 | 29 | ## 获取服务端authKey 30 | 31 | 如果想获取authKey,服务端提供获取authKey的接口 32 | 33 | ``` 34 | POST /auth/getauthkey 35 | ``` 36 | 将返回加密后的authKey,采用aes cbc加密,请使用与服务端配置文件中cryptKey相同的密钥进行解密 37 | 38 | **注意:** nps配置文件中`auth_crypt_key`需为16位 39 | - 解密密钥长度128 40 | - 偏移量与密钥相同 41 | - 补码方式pkcs5padding 42 | - 解密串编码方式 十六进制 43 | 44 | ## 详细文档 45 | - **[详见](webapi.md)** (感谢@avengexyz) 46 | -------------------------------------------------------------------------------- /docs/bt.md: -------------------------------------------------------------------------------- 1 | # 🐳 宝塔面板 (一键部署) 2 | 3 | > 安装宝塔面板,前往宝塔面板官网,选择对应的脚本下载安装,推荐宝塔版本 9.2.0+ 4 | 5 | ![logo](../image/bt/bt1.jpg) 6 | 7 | # 安装宝塔面板 8 | 1. 安装宝塔面板,前往 [宝塔面板官网](https://www.bt.cn/new/download.html) 进行安装,选择正式版脚本安装。 9 | 10 | 2. 登录面板,点击左侧 **Docker** 进入 `Docker 管理`。 11 | 12 | 3. 如提示未安装 Docker / Docker Compose, 可根据上方引导安装。 13 | 14 | 15 | # 安装NPS服务端 16 | 1. 在宝塔面板左侧` Docker 菜单`,进入 **应用商城**,搜索 `NPS`,找到 `NPS 服务端` 并点击 **安装**。 17 | 18 | 2. 配置NPS,进入安装目录,修改 `conf/nps.conf` 即可(修改后记得重启容器),服务端参数配置详见 [配置文件说明](https://ehang-io.github.io/nps/#/server_config) 19 | ![logo](../image/bt/bt2.png) 20 | 21 | 22 | 23 | **注意**:NPS 默认的`WEB端口`为`80,443`, 如果端口被占用,请修改 `nps.conf` 中的 `http_proxy_port`,`https_proxy_port` 为其他端口。 24 | web管理端口默认为`8081`,服务端启动后,输入:`ip:8081` 即可访问。 25 | 26 | 27 | 28 | # 安装NPS客户端 29 | 1. 在宝塔面板左侧` Docker 菜单`,进入 **应用商城**,搜索 `NPS`,找到 `NPS 客户端` 并点击 **安装**。 30 | 31 | 2. 客户端配置有两种方式,1. [无配置文件模式](https://ehang-io.github.io/nps/#/use?id=%e6%97%a0%e9%85%8d%e7%bd%ae%e6%96%87%e4%bb%b6%e6%a8%a1%e5%bc%8f)(推荐),2. [配置文件模式](https://ehang-io.github.io/nps/#/use?id=%e9%85%8d%e7%bd%ae%e6%96%87%e4%bb%b6%e6%a8%a1%e5%bc%8f)。 32 | 33 | - **无配置文件模式**:点击安装后,输入`服务地址`、`连接密钥`,即可启动客户端。`连接方式`和`开启TLS`参数,为了避免意外错误,不建议修改。如果需要配置多个客户端,直接安装多个即可(注意名字不要重复)。 34 | 35 | - **配置文件模式**: `服务地址`、`连接密钥`不需要填写,直接点击 **安装** ,然后在`安装目录`下找到 `conf/npc.conf`,修改配置文件,再重启容器即可。客户端参数配置详见 [配置文件说明](https://ehang-io.github.io/nps/#/use?id=%e9%85%8d%e7%bd%ae%e6%96%87%e4%bb%b6%e6%a8%a1%e5%bc%8f)。 36 | 37 | **注意**:强烈推荐使用无配置文件模式,所有数据应该在服务端保存和配置,而客户端只做连接转发。客户端配置文件对小白极不友好,配置繁琐,容易出错。 38 | 39 | ![logo](../image/bt/bt3.png) 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /docs/contribute.md: -------------------------------------------------------------------------------- 1 | # 贡献 2 | 3 | - 如果遇到bug可以直接提交至dev分支 4 | - 使用遇到问题可以通过issues反馈 5 | - 项目处于开发阶段,还有很多待完善的地方,如果可以贡献代码,请提交 PR 至 dev 分支 6 | - 如果有新的功能特性反馈,可以通过issues或者qq群反馈 7 | -------------------------------------------------------------------------------- /docs/description.md: -------------------------------------------------------------------------------- 1 | # 说明 2 | ## 获取用户真实ip 3 | 如需使用需要在`nps.conf`中设置`http_add_origin_header=true` 4 | 5 | 在域名代理模式中,可以通过request请求 header 中的 X-Forwarded-For 和 X-Real-IP 来获取用户真实 IP。 6 | 7 | **本代理前会在每一个http(s)请求中添加了这两个 header。** 8 | 9 | ## 热更新支持 10 | 对于绝大多数配置,在web管理中的修改将实时使用,无需重启客户端或者服务端 11 | 12 | ## 客户端地址显示 13 | 在web管理中将显示客户端的连接地址 14 | 15 | ## 流量统计 16 | 可统计显示每个代理使用的流量,由于压缩和加密等原因,会和实际环境中的略有差异 17 | 18 | ## 当前客户端带宽 19 | 可统计每个客户端当前的带宽,可能和实际有一定差异,仅供参考。 20 | 21 | ## 客户端与服务端版本对比 22 | 为了程序正常运行,客户端与服务端的核心版本必须一致,否则将导致客户端无法成功连接致服务端。 23 | 24 | ## Linux系统限制 25 | 默认情况下linux对连接数量有限制,对于性能好的机器完全可以调整内核参数以处理更多的连接。 26 | `tcp_max_syn_backlog` `somaxconn` 27 | 酌情调整参数,增强网络性能 28 | 29 | ## web管理保护 30 | 当一个ip连续登陆失败次数超过10次,将在一分钟内禁止该ip再次尝试。 31 | -------------------------------------------------------------------------------- /docs/discuss.md: -------------------------------------------------------------------------------- 1 | # 交流群 2 | 3 | ![二维码.jpeg](https://i.loli.net/2019/02/15/5c66c32a42074.jpeg) 4 | -------------------------------------------------------------------------------- /docs/donate.md: -------------------------------------------------------------------------------- 1 | # 捐助 2 | 如果您觉得nps对你有帮助,欢迎给予我们一定捐助,也是帮助nps更好的发展。 3 | 4 | ## 支付宝 5 | ![image](https://github.com/ehang-io/nps/blob/master/image/donation_zfb.png?raw=true) 6 | ## 微信 7 | ![image](https://github.com/ehang-io/nps/blob/master/image/donation_wx.png?raw=true) 8 | -------------------------------------------------------------------------------- /docs/example.md: -------------------------------------------------------------------------------- 1 | # 使用示例 2 | ## 统一准备工作(必做) 3 | - 开启服务端,假设公网服务器ip为1.1.1.1,配置文件中`bridge_port`为8024,配置文件中`web_port`为8080 4 | - 访问1.1.1.1:8080 5 | - 在客户端管理中创建一个客户端,记录下验证密钥 6 | - 内网客户端运行(windows使用cmd运行加.exe) 7 | 8 | ```shell 9 | ./npc -server=1.1.1.1:8024 -vkey=客户端的密钥 10 | ``` 11 | **注意:运行服务端后,请确保能从客户端设备上正常访问配置文件中所配置的`bridge_port`端口,telnet,netcat这类的来检查** 12 | 13 | ## 域名解析 14 | 15 | **适用范围:** 小程序开发、微信公众号开发、产品演示 16 | 17 | **注意:域名解析模式为http反向代理,不是dns服务器,在web上能够轻松灵活配置** 18 | 19 | **假设场景:** 20 | - 有一个域名proxy.com,有一台公网机器ip为1.1.1.1 21 | - 两个内网开发站点127.0.0.1:81,127.0.0.1:82 22 | - 想通过(http|https://)a.proxy.com访问127.0.0.1:81,通过(http|https://)b.proxy.com访问127.0.0.1:82 23 | 24 | **使用步骤** 25 | - 将*.proxy.com解析到公网服务器1.1.1.1 26 | - 点击刚才创建的客户端的域名管理,添加两条规则规则:1、域名:`a.proxy.com`,内网目标:`127.0.0.1:81`,2、域名:`b.proxy.com`,内网目标:`127.0.0.1:82` 27 | 28 | 现在访问(http|https://)`a.proxy.com`,`b.proxy.com`即可成功 29 | 30 | **https:** 如需使用https请进行相关配置,详见 [使用https](/nps_extend?id=使用https) 31 | 32 | ## tcp隧道 33 | 34 | 35 | **适用范围:** ssh、远程桌面等tcp连接场景 36 | 37 | **假设场景:** 38 | 想通过访问公网服务器1.1.1.1的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接 39 | 40 | **使用步骤** 41 | - 在刚才创建的客户端隧道管理中添加一条tcp隧道,填写监听的端口(8001)、内网目标ip和目标端口(10.1.50.101:22),保存。 42 | - 访问公网服务器ip(1.1.1.1),填写的监听端口(8001),相当于访问内网ip(10.1.50.101):目标端口(22),例如:`ssh -p 8001 root@1.1.1.1` 43 | 44 | ## udp隧道 45 | 46 | **适用范围:** 内网dns解析等udp连接场景 47 | 48 | **假设场景:** 49 | 内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为1.1.1.1 50 | 51 | **使用步骤** 52 | - 在刚才创建的客户端的隧道管理中添加一条udp隧道,填写监听的端口(53)、内网目标ip和目标端口(10.1.50.102:53),保存。 53 | - 修改需要使用的dns地址为1.1.1.1,则相当于使用10.1.50.102作为dns服务器 54 | 55 | ## socks5代理 56 | 57 | 58 | **适用范围:** 在外网环境下如同使用vpn一样访问内网设备或者资源 59 | 60 | **假设场景:** 61 | 想将公网服务器1.1.1.1的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果 62 | 63 | **使用步骤** 64 | - 在刚才创建的客户端隧道管理中添加一条socks5代理,填写监听的端口(8003),保存。 65 | - 在外网环境的本机配置socks5代理(例如使用proxifier进行全局代理),ip为公网服务器ip(1.1.1.1),端口为填写的监听端口(8003),即可畅享内网了 66 | 67 | **注意** 68 | 经过socks5代理,当收到socks5数据包时socket已经是accept状态。表现是扫描端口全open,建立连接后短时间关闭。若想同内网表现一致,建议远程连接一台设备。 69 | 70 | ## http正向代理 71 | 72 | **适用范围:** 在外网环境下使用http正向代理访问内网站点 73 | 74 | **假设场景:** 75 | 想将公网服务器1.1.1.1的8004端口作为http代理,访问内网网站 76 | 77 | **使用步骤** 78 | 79 | - 在刚才创建的客户端隧道管理中添加一条http代理,填写监听的端口(8004),保存。 80 | - 在外网环境的本机配置http代理,ip为公网服务器ip(1.1.1.1),端口为填写的监听端口(8004),即可访问了 81 | 82 | **注意:对于私密代理与p2p,除了统一配置的客户端和服务端,还需要一个客户端作为访问端提供一个端口来访问** 83 | 84 | ## 私密代理 85 | 86 | **适用范围:** 无需占用多余的端口、安全性要求较高可以防止其他人连接的tcp服务,例如ssh。 87 | 88 | **假设场景:** 89 | 无需新增多的端口实现访问内网服务器10.1.50.2的22端口 90 | 91 | **使用步骤** 92 | - 在刚才创建的客户端中添加一条私密代理,并设置唯一密钥secrettest和内网目标10.1.50.2:22 93 | - 在需要连接ssh的机器上以执行命令 94 | 95 | ``` 96 | ./npc -server=1.1.1.1:8024 -vkey=vkey -type=tcp -password=secrettest -local_type=secret 97 | ``` 98 | 如需指定本地端口可加参数`-local_port=xx`,默认为2000 99 | 100 | **注意:** password为web管理上添加的唯一密钥,具体命令可查看web管理上的命令提示 101 | 102 | 假设10.1.50.2用户名为root,现在执行`ssh -p 2000 root@127.0.0.1`即可访问ssh 103 | 104 | 105 | ## p2p服务 106 | 107 | **适用范围:** 大流量传输场景,流量不经过公网服务器,但是由于p2p穿透和nat类型关系较大,不保证100%成功,支持大部分nat类型。[nat类型检测](/npc_extend?id=nat类型检测) 108 | 109 | **假设场景:** 110 | 111 | 想通过访问使用端机器(访问端,也就是本机)的2000端口---->访问到内网机器 10.2.50.2的22端口 112 | 113 | **使用步骤** 114 | - 在`nps.conf`中设置`p2p_ip`(nps服务器ip)和`p2p_port`(nps服务器udp端口) 115 | > 注:若 `p2p_port` 设置为6000,请在防火墙开放6000~6002(额外添加2个端口)udp端口 116 | - 在刚才刚才创建的客户端中添加一条p2p代理,并设置唯一密钥p2pssh 117 | - 在使用端机器(本机)执行命令 118 | 119 | ``` 120 | ./npc -server=1.1.1.1:8024 -vkey=123 -password=p2pssh -target=10.2.50.2:22 121 | ``` 122 | 如需指定本地端口可加参数`-local_port=xx`,默认为2000 123 | 124 | **注意:** password为web管理上添加的唯一密钥,具体命令可查看web管理上的命令提示 125 | 126 | 假设内网机器为10.2.50.2的ssh用户名为root,现在在本机上执行`ssh -p 2000 root@127.0.0.1`即可访问机器2的ssh,如果是网站在浏览器访问127.0.0.1:2000端口即可。 127 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | - 服务端无法启动 4 | ``` 5 | 服务端默认配置启用了8024,8080,80,443端口,端口冲突无法启动,请修改配置 6 | ``` 7 | - 客户端无法连接服务端 8 | ``` 9 | 请检查配置文件中的所有端口是否在安全组,防火墙放行 10 | 请检查vkey是否对应 11 | 请检查版本是否对应 12 | ``` 13 | - 服务端配置文件修改无效 14 | ``` 15 | install 之后,Linux 配置文件在 /etc/nps 16 | ``` 17 | - p2p穿透失败 [p2p服务](https://ehang-io.github.io/nps/#/example?id=p2p%e6%9c%8d%e5%8a%a1) 18 | ``` 19 | 双方nat类型都是Symmetric Nat一定不成功,建议先查看nat类型。请按照文档操作(标题上有超链接) 20 | ``` 21 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /docs/install.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | ## 安装包安装 3 | [releases](https://github.com/ehang-io/nps/releases) 4 | 5 | 下载对应的系统版本即可,服务端和客户端是单独的 6 | 7 | ## 源码安装 8 | - 安装源码 9 | ```go get -u ehang.io/nps``` 10 | - 编译 11 | 12 | 服务端```go build cmd/nps/nps.go``` 13 | 14 | 客户端```go build cmd/npc/npc.go``` 15 | 16 | ## docker安装 17 | > [server](https://hub.docker.com/r/ffdfgdfg/nps) 18 | > [client](https://hub.docker.com/r/ffdfgdfg/npc) 19 | -------------------------------------------------------------------------------- /docs/introduction.md: -------------------------------------------------------------------------------- 1 | ![image](https://github.com/ehang-io/nps/blob/master/image/web2.png?raw=true) 2 | # 介绍 3 | 4 | 可在网页上配置和管理各个tcp、udp隧道、内网站点代理,http、https解析等,功能强大,操作方便。 5 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/docs/logo.png -------------------------------------------------------------------------------- /docs/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/npc_extend.md: -------------------------------------------------------------------------------- 1 | # 增强功能 2 | ## nat类型检测 3 | ``` 4 | ./npc nat -stun_addr=stun.stunprotocol.org:3478 5 | ``` 6 | 如果p2p双方都是Symmetric Nat,肯定不能成功,其他组合都有较大成功率。`stun_addr`可以指定stun服务器地址。 7 | ## 状态检查 8 | ``` 9 | ./npc status -config=npc配置文件路径 10 | ``` 11 | ## 重载配置文件 12 | ``` 13 | ./npc restart -config=npc配置文件路径 14 | ``` 15 | 16 | ## 通过代理连接nps 17 | 有时候运行npc的内网机器无法直接访问外网,此时可以可以通过socks5代理连接nps 18 | 19 | 对于配置文件方式启动,设置 20 | ```ini 21 | [common] 22 | proxy_url=socks5://111:222@127.0.0.1:8024 23 | ``` 24 | 对于无配置文件模式,加上参数 25 | 26 | ``` 27 | -proxy=socks5://111:222@127.0.0.1:8024 28 | ``` 29 | 支持socks5和http两种模式 30 | 31 | 即socks5://username:password@ip:port 32 | 33 | 或http://username:password@ip:port 34 | 35 | ## 群晖支持 36 | 可在releases中下载spk群晖套件,例如`npc_x64-6.1_0.19.0-1.spk` 37 | -------------------------------------------------------------------------------- /docs/npc_sdk.md: -------------------------------------------------------------------------------- 1 | # npc sdk文档 2 | 3 | ``` 4 | 命令行模式启动客户端 5 | 从v0.26.10开始,此函数会阻塞,直到客户端退出返回,请自行管理是否重连 6 | p0->连接地址 7 | p1->vkey 8 | p2->连接类型(tcp or udp) 9 | p3->连接代理 10 | 11 | extern GoInt StartClientByVerifyKey(char* p0, char* p1, char* p2, char* p3); 12 | 13 | 查看当前启动的客户端状态,在线为1,离线为0 14 | extern GoInt GetClientStatus(); 15 | 16 | 关闭客户端 17 | extern void CloseClient(); 18 | 19 | 获取当前客户端版本 20 | extern char* Version(); 21 | 22 | 获取日志,实时更新 23 | extern char* Logs(); 24 | ``` 25 | -------------------------------------------------------------------------------- /docs/nps_extend.md: -------------------------------------------------------------------------------- 1 | # 增强功能 2 | ## 使用https 3 | 4 | **方式一:** 类似于nginx实现https的处理 5 | 6 | 在配置文件中将https_proxy_port设置为443或者其他你想配置的端口,将`https_just_proxy`设置为false,nps 重启后,在web管理界面,域名新增或修改界面中修改域名证书和密钥。 7 | 8 | **此外:** 可以在`nps.conf`中设置一个默认的https配置,当遇到未在web中设置https证书的域名解析时,将自动使用默认证书,另还有一种情况就是对于某些请求的clienthello不携带sni扩展信息,nps也将自动使用默认证书 9 | 10 | 11 | **方式二:** 在内网对应服务器上设置https 12 | 13 | 在`nps.conf`中将`https_just_proxy`设置为true,并且打开`https_proxy_port`端口,然后nps将直接转发https请求到内网服务器上,由内网服务器进行https处理 14 | 15 | ## 与nginx配合 16 | 17 | 有时候我们还需要在云服务器上运行nginx来保证静态文件缓存等,本代理可和nginx配合使用,在配置文件中将httpProxyPort设置为非80端口,并在nginx中配置代理,例如httpProxyPort为8010时 18 | ``` 19 | server { 20 | listen 80; 21 | server_name *.proxy.com; 22 | location / { 23 | proxy_set_header Host $http_host; 24 | proxy_pass http://127.0.0.1:8010; 25 | } 26 | } 27 | ``` 28 | 如需使用https也可在nginx监听443端口并配置ssl,并将本代理的httpsProxyPort设置为空关闭https即可,例如httpProxyPort为8020时 29 | 30 | ``` 31 | server { 32 | listen 443; 33 | server_name *.proxy.com; 34 | ssl on; 35 | ssl_certificate certificate.crt; 36 | ssl_certificate_key private.key; 37 | ssl_session_timeout 5m; 38 | ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; 39 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 40 | ssl_prefer_server_ciphers on; 41 | location / { 42 | proxy_set_header Host $http_host; 43 | proxy_pass http://127.0.0.1:8020; 44 | } 45 | } 46 | ``` 47 | ## web管理使用https 48 | 如果web管理需要使用https,可以在配置文件`nps.conf`中设置`web_open_ssl=true`,并配置`web_cert_file`和`web_key_file` 49 | ## web使用Caddy代理 50 | 51 | 如果将web配置到Caddy代理,实现子路径访问nps,可以这样配置. 52 | 53 | 假设我们想通过 `http://caddy_ip:caddy_port/nps` 来访问后台, Caddyfile 这样配置: 54 | 55 | ```Caddyfile 56 | caddy_ip:caddy_port/nps { 57 | ##server_ip 为 nps 服务器IP 58 | ##web_port 为 nps 后台端口 59 | proxy / http://server_ip:web_port/nps { 60 | transparent 61 | } 62 | } 63 | ``` 64 | 65 | nps.conf 修改 `web_base_url` 为 `/nps` 即可 66 | ``` 67 | web_base_url=/nps 68 | ``` 69 | 70 | 71 | ## 关闭代理 72 | 73 | 如需关闭http代理可在配置文件中将http_proxy_port设置为空,如需关闭https代理可在配置文件中将https_proxy_port设置为空。 74 | 75 | ## 流量数据持久化 76 | 服务端支持将流量数据持久化,默认情况下是关闭的,如果有需求可以设置`nps.conf`中的`flow_store_interval`参数,单位为分钟 77 | 78 | **注意:** nps不会持久化通过公钥连接的客户端 79 | ## 系统信息显示 80 | nps服务端支持在web上显示和统计服务器的相关信息,但默认一些统计图表是关闭的,如需开启请在`nps.conf`中设置`system_info_display=true` 81 | 82 | ## 自定义客户端连接密钥 83 | web上可以自定义客户端连接的密钥,但是必须具有唯一性 84 | ## 关闭公钥访问 85 | 可以将`nps.conf`中的`public_vkey`设置为空或者删除 86 | 87 | ## 关闭web管理 88 | 可以将`nps.conf`中的`web_port`设置为空或者删除 89 | 90 | ## 服务端多用户登陆 91 | 如果将`nps.conf`中的`allow_user_login`设置为true,服务端web将支持多用户登陆,登陆用户名为user,默认密码为每个客户端的验证密钥,登陆后可以进入客户端编辑修改web登陆的用户名和密码,默认该功能是关闭的。 92 | 93 | ## 用户注册功能 94 | nps服务端支持用户注册功能,可将`nps.conf`中的`allow_user_register`设置为true,开启后登陆页将会有有注册功能, 95 | 96 | ## 监听指定ip 97 | 98 | nps支持每个隧道监听不同的服务端端口,在`nps.conf`中设置`allow_multi_ip=true`后,可在web中控制,或者npc配置文件中(可忽略,默认为0.0.0.0) 99 | ```ini 100 | server_ip=xxx 101 | ``` 102 | ## 代理到服务端本地 103 | 在使用nps监听80或者443端口时,默认是将所有的请求都会转发到内网上,但有时候我们的nps服务器的上一些服务也需要使用这两个端口,nps提供类似于`nginx` `proxy_pass` 的功能,支持将代理到服务器本地,该功能支持域名解析,tcp、udp隧道,默认关闭。 104 | 105 | **即:** 假设在nps的vps服务器上有一个服务使用5000端口,这时候nps占用了80端口和443,我们想能使用一个域名通过http(s)访问到5000的服务。 106 | 107 | **使用方式:** 在`nps.conf`中设置`allow_local_proxy=true`,然后在web上设置想转发的隧道或者域名然后选择转发到本地选项即可成功。 108 | -------------------------------------------------------------------------------- /docs/nps_use.md: -------------------------------------------------------------------------------- 1 | # 使用 2 | **提示:使用web模式时,服务端执行文件必须在项目根目录,否则无法正确加载配置文件** 3 | 4 | ## web管理 5 | 6 | 进入web界面,公网ip:web界面端口(默认8080),密码默认为123 7 | 8 | 进入web管理界面,有详细的说明 9 | 10 | ## 服务端配置文件重载 11 | 对于linux、darwin 12 | ```shell 13 | sudo nps reload 14 | ``` 15 | 对于windows 16 | ```shell 17 | nps.exe reload 18 | ``` 19 | **说明:** 仅支持部分配置重载,例如`allow_user_login` `auth_crypt_key` `auth_key` `web_username` `web_password` 等,未来将支持更多 20 | 21 | 22 | ## 服务端停止或重启 23 | 对于linux、darwin 24 | ```shell 25 | sudo nps stop|restart 26 | ``` 27 | 对于windows 28 | ```shell 29 | nps.exe stop|restart 30 | ``` 31 | ## 服务端更新 32 | 请首先执行 `sudo nps stop` 或者 `nps.exe stop` 停止运行,然后 33 | 34 | 对于linux 35 | ```shell 36 | sudo nps-update update 37 | ``` 38 | 对于windows 39 | ```shell 40 | nps-update.exe update 41 | ``` 42 | 43 | 更新完成后,执行执行 `sudo nps start` 或者 `nps.exe start` 重新运行即可完成升级 44 | 45 | 如果无法更新成功,可以直接自行下载releases压缩包然后覆盖原有的nps二进制文件和web目录 46 | 47 | 注意:`nps install` 之后的 nps 不在原位置,请使用 `whereis nps` 查找具体目录覆盖 nps 二进制文件 48 | -------------------------------------------------------------------------------- /docs/run.md: -------------------------------------------------------------------------------- 1 | # 启动 2 | ## 服务端 3 | 下载完服务器压缩包后,解压,然后进入解压后的文件夹 4 | 5 | - 执行安装命令 6 | 7 | 对于linux|darwin ```sudo ./nps install``` 8 | 9 | 对于windows,管理员身份运行cmd,进入安装目录 ```nps.exe install``` 10 | 11 | - 启动 12 | 13 | 对于linux|darwin ```sudo nps start``` 14 | 15 | 对于windows,管理员身份运行cmd,进入程序目录 ```nps.exe start``` 16 | 17 | ```安装后windows配置文件位于 C:\Program Files\nps,linux和darwin位于/etc/nps``` 18 | 19 | 停止和重启可用,stop和restart 20 | 21 | **如果发现没有启动成功,可以使用`nps(.exe) stop`,然后运行`nps.(exe)`运行调试,或查看日志**(Windows日志文件位于当前运行目录下,linux和darwin位于/var/log/nps.log) 22 | - 访问服务端ip:web服务端口(默认为8080) 23 | - 使用用户名和密码登陆(默认admin/123,正式使用一定要更改) 24 | - 创建客户端 25 | 26 | ## 客户端 27 | - 下载客户端安装包并解压,进入到解压目录 28 | - 点击web管理中客户端前的+号,复制启动命令 29 | - 执行启动命令,linux直接执行即可,windows将./npc换成npc.exe用**cmd执行** 30 | 31 | 如果使用`powershell`运行,**请将ip括起来!** 32 | 33 | 如果需要注册到系统服务可查看[注册到系统服务](/use?id=注册到系统服务) 34 | 35 | ## 版本检查 36 | - 对客户端以及服务的均可以使用参数`-version`打印版本 37 | - `nps -version`或`./nps -version` 38 | - `npc -version`或`./npc -version` 39 | 40 | ## 配置 41 | - 客户端连接后,在web中配置对应穿透服务即可 42 | - 可以查看[使用示例](/example) 43 | -------------------------------------------------------------------------------- /docs/server_config.md: -------------------------------------------------------------------------------- 1 | # 服务端配置文件 2 | - /etc/nps/conf/nps.conf 3 | 4 | 名称 | 含义 5 | ---|--- 6 | web_port | web管理端口 7 | web_password | web界面管理密码 8 | web_username | web界面管理账号 9 | web_base_url | web管理主路径,用于将web管理置于代理子路径后面 10 | bridge_port | 服务端客户端通信端口 11 | https_proxy_port | 域名代理https代理监听端口 12 | http_proxy_port | 域名代理http代理监听端口 13 | auth_key|web api密钥 14 | bridge_type|客户端与服务端连接方式kcp或tcp 15 | public_vkey|客户端以配置文件模式启动时的密钥,设置为空表示关闭客户端配置文件连接模式 16 | ip_limit|是否限制ip访问,true或false或忽略 17 | flow_store_interval|服务端流量数据持久化间隔,单位分钟,忽略表示不持久化 18 | log_level|日志输出级别 19 | auth_crypt_key | 获取服务端authKey时的aes加密密钥,16位 20 | p2p_ip| 服务端Ip,使用p2p模式必填 21 | p2p_port|p2p模式开启的udp端口 22 | pprof_ip|debug pprof 服务端ip 23 | pprof_port|debug pprof 端口 24 | disconnect_timeout|客户端连接超时,单位 5s,默认值 60,即 300s = 5mins 25 | -------------------------------------------------------------------------------- /docs/thanks.md: -------------------------------------------------------------------------------- 1 | Thanks [jetbrains](https://www.jetbrains.com/?from=nps) for providing development tools for nps 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/use.md: -------------------------------------------------------------------------------- 1 | # 基本使用 2 | ## 无配置文件模式 3 | 此模式的各种配置在服务端web管理中完成,客户端除运行一条命令外无需任何其他设置 4 | ``` 5 | ./npc -server=ip:port -vkey=web界面中显示的密钥 6 | ``` 7 | ## 注册到系统服务(开机启动、守护进程) 8 | 对于linux、darwin 9 | - 注册:`sudo ./npc install 其他参数(例如-server=xx -vkey=xx或者-config=xxx)` 10 | - 启动:`sudo npc start` 11 | - 停止:`sudo npc stop` 12 | - 如果需要更换命令内容需要先卸载`./npc uninstall`,再重新注册 13 | 14 | 对于windows,使用管理员身份运行cmd 15 | 16 | - 注册:`npc.exe install 其他参数(例如-server=xx -vkey=xx或者-config=xxx)` 17 | - 启动:`npc.exe start` 18 | - 停止:`npc.exe stop` 19 | - 如果需要更换命令内容需要先卸载`npc.exe uninstall`,再重新注册 20 | - 如果需要当客户端退出时自动重启客户端,请按照如图所示配置 21 | ![image](https://github.com/ehang-io/nps/blob/master/docs/windows_client_service_configuration.png?raw=true) 22 | 23 | 注册到服务后,日志文件windows位于当前目录下,linux和darwin位于/var/log/npc.log 24 | 25 | ## 客户端更新 26 | 首先进入到对于的客户端二进制文件目录 27 | 28 | 请首先执行`sudo npc stop`或者`npc.exe stop`停止运行,然后 29 | 30 | 对于linux 31 | ```shell 32 | sudo npc-update update 33 | ``` 34 | 对于windows 35 | ```shell 36 | npc-update.exe update 37 | ``` 38 | 39 | 更新完成后,执行执行`sudo npc start`或者`npc.exe start`重新运行即可完成升级 40 | 41 | 如果无法更新成功,可以直接自行下载releases压缩包然后覆盖原有的npc二进制文件 42 | 43 | ## 配置文件模式 44 | 此模式使用nps的公钥或者客户端私钥验证,各种配置在客户端完成,同时服务端web也可以进行管理 45 | ``` 46 | ./npc -config=npc配置文件路径 47 | ``` 48 | ## 配置文件说明 49 | [示例配置文件](https://github.com/ehang-io/nps/tree/master/conf/npc.conf) 50 | #### 全局配置 51 | ```ini 52 | [common] 53 | server_addr=1.1.1.1:8024 54 | conn_type=tcp 55 | vkey=123 56 | username=111 57 | password=222 58 | compress=true 59 | crypt=true 60 | rate_limit=10000 61 | flow_limit=100 62 | remark=test 63 | max_conn=10 64 | #pprof_addr=0.0.0.0:9999 65 | ``` 66 | 项 | 含义 67 | ---|--- 68 | server_addr | 服务端ip/域名:port 69 | conn_type | 与服务端通信模式(tcp或kcp) 70 | vkey|服务端配置文件中的密钥(非web) 71 | username|socks5或http(s)密码保护用户名(可忽略) 72 | password|socks5或http(s)密码保护密码(可忽略) 73 | compress|是否压缩传输(true或false或忽略) 74 | crypt|是否加密传输(true或false或忽略) 75 | rate_limit|速度限制,可忽略 76 | flow_limit|流量限制,可忽略 77 | remark|客户端备注,可忽略 78 | max_conn|最大连接数,可忽略 79 | pprof_addr|debug pprof ip:port 80 | #### 域名代理 81 | 82 | ```ini 83 | [common] 84 | server_addr=1.1.1.1:8024 85 | vkey=123 86 | [web1] 87 | host=a.proxy.com 88 | target_addr=127.0.0.1:8080,127.0.0.1:8082 89 | host_change=www.proxy.com 90 | header_set_proxy=nps 91 | ``` 92 | 项 | 含义 93 | ---|--- 94 | web1 | 备注 95 | host | 域名(http|https都可解析) 96 | target_addr|内网目标,负载均衡时多个目标,逗号隔开 97 | host_change|请求host修改 98 | header_xxx|请求header修改或添加,header_proxy表示添加header proxy:nps 99 | 100 | #### tcp隧道模式 101 | 102 | ```ini 103 | [common] 104 | server_addr=1.1.1.1:8024 105 | vkey=123 106 | [tcp] 107 | mode=tcp 108 | target_addr=127.0.0.1:8080 109 | server_port=9001 110 | ``` 111 | 项 | 含义 112 | ---|--- 113 | mode | tcp 114 | server_port | 在服务端的代理端口 115 | tartget_addr|内网目标 116 | 117 | #### udp隧道模式 118 | 119 | ```ini 120 | [common] 121 | server_addr=1.1.1.1:8024 122 | vkey=123 123 | [udp] 124 | mode=udp 125 | target_addr=127.0.0.1:8080 126 | server_port=9002 127 | ``` 128 | 项 | 含义 129 | ---|--- 130 | mode | udp 131 | server_port | 在服务端的代理端口 132 | target_addr|内网目标 133 | #### http代理模式 134 | 135 | ```ini 136 | [common] 137 | server_addr=1.1.1.1:8024 138 | vkey=123 139 | [http] 140 | mode=httpProxy 141 | server_port=9003 142 | ``` 143 | 项 | 含义 144 | ---|--- 145 | mode | httpProxy 146 | server_port | 在服务端的代理端口 147 | #### socks5代理模式 148 | 149 | ```ini 150 | [common] 151 | server_addr=1.1.1.1:8024 152 | vkey=123 153 | [socks5] 154 | mode=socks5 155 | server_port=9004 156 | multi_account=multi_account.conf 157 | ``` 158 | 项 | 含义 159 | ---|--- 160 | mode | socks5 161 | server_port | 在服务端的代理端口 162 | multi_account | socks5多账号配置文件(可选),配置后使用basic_username和basic_password无法通过认证 163 | #### 私密代理模式 164 | 165 | ```ini 166 | [common] 167 | server_addr=1.1.1.1:8024 168 | vkey=123 169 | [secret_ssh] 170 | mode=secret 171 | password=ssh2 172 | target_addr=10.1.50.2:22 173 | ``` 174 | 项 | 含义 175 | ---|--- 176 | mode | secret 177 | password | 唯一密钥 178 | target_addr|内网目标 179 | 180 | #### p2p代理模式 181 | 182 | ```ini 183 | [common] 184 | server_addr=1.1.1.1:8024 185 | vkey=123 186 | [p2p_ssh] 187 | mode=p2p 188 | password=ssh2 189 | target_addr=10.1.50.2:22 190 | ``` 191 | 项 | 含义 192 | ---|--- 193 | mode | p2p 194 | password | 唯一密钥 195 | target_addr|内网目标 196 | 197 | 198 | #### 文件访问模式 199 | 利用nps提供一个公网可访问的本地文件服务,此模式仅客户端使用配置文件模式方可启动 200 | 201 | ```ini 202 | [common] 203 | server_addr=1.1.1.1:8024 204 | vkey=123 205 | [file] 206 | mode=file 207 | server_port=9100 208 | local_path=/tmp/ 209 | strip_pre=/web/ 210 | ```` 211 | 212 | 项 | 含义 213 | ---|--- 214 | mode | file 215 | server_port | 服务端开启的端口 216 | local_path|本地文件目录 217 | strip_pre|前缀 218 | 219 | 对于`strip_pre`,访问公网`ip:9100/web/`相当于访问`/tmp/`目录 220 | 221 | #### 断线重连 222 | ```ini 223 | [common] 224 | auto_reconnection=true 225 | ``` 226 | -------------------------------------------------------------------------------- /docs/webapi.md: -------------------------------------------------------------------------------- 1 | 获取客户端列表 2 | 3 | ``` 4 | POST /client/list/ 5 | ``` 6 | 7 | 8 | | 参数 | 含义 | 9 | | --- | --- | 10 | | search | 搜索 | 11 | | order | 排序asc 正序 desc倒序 | 12 | | offset | 分页(第几页) | 13 | | limit | 条数(分页显示的条数) | 14 | 15 | *** 16 | 获取单个客户端 17 | 18 | ``` 19 | POST /client/getclient/ 20 | ``` 21 | 22 | 23 | | 参数 | 含义 | 24 | | --- | --- | 25 | | id | 客户端id | 26 | 27 | *** 28 | 添加客户端 29 | 30 | ``` 31 | POST /client/add/ 32 | ``` 33 | 34 | | 参数 | 含义 | 35 | | --- | --- | 36 | | remark | 备注 | 37 | | u | basic权限认证用户名 | 38 | | p | basic权限认证密码 | 39 | | limit | 条数(分页显示的条数) | 40 | | vkey | 客户端验证密钥 | 41 | | config\_conn\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 | 42 | | compress | 压缩1允许 0不允许 | 43 | | crypt | 是否加密(1或者0)1允许 0不允许 | 44 | | rate\_limit | 带宽限制 单位KB/S 空则为不限制 | 45 | | flow\_limit | 流量限制 单位M 空则为不限制 | 46 | | max\_conn | 客户端最大连接数量 空则为不限制 | 47 | | max\_tunnel | 客户端最大隧道数量 空则为不限制 | 48 | 49 | *** 50 | 修改客户端 51 | 52 | ``` 53 | POST /client/edit/ 54 | ``` 55 | 56 | | 参数 | 含义 | 57 | | --- | --- | 58 | | remark | 备注 | 59 | | u | basic权限认证用户名 | 60 | | p | basic权限认证密码 | 61 | | limit | 条数(分页显示的条数) | 62 | | vkey | 客户端验证密钥 | 63 | | config\_conn\_allow | 是否允许客户端以配置文件模式连接 1允许 0不允许 | 64 | | compress | 压缩1允许 0不允许 | 65 | | crypt | 是否加密(1或者0)1允许 0不允许 | 66 | | rate\_limit | 带宽限制 单位KB/S 空则为不限制 | 67 | | flow\_limit | 流量限制 单位M 空则为不限制 | 68 | | max\_conn | 客户端最大连接数量 空则为不限制 | 69 | | max\_tunnel | 客户端最大隧道数量 空则为不限制 | 70 | | id | 要修改的客户端id | 71 | 72 | *** 73 | 删除客户端 74 | 75 | ``` 76 | POST /client/del/ 77 | ``` 78 | 79 | | 参数 | 含义 | 80 | | --- | --- | 81 | | id | 要删除的客户端id | 82 | 83 | *** 84 | 获取域名解析列表 85 | 86 | ``` 87 | POST /index/hostlist/ 88 | ``` 89 | 90 | | 参数 | 含义 | 91 | | --- | --- | 92 | | search | 搜索(可以搜域名/备注什么的) | 93 | | offset | 分页(第几页) | 94 | | limit | 条数(分页显示的条数) | 95 | 96 | *** 97 | 添加域名解析 98 | 99 | ``` 100 | POST /index/addhost/ 101 | ``` 102 | 103 | 104 | | 参数 | 含义 | 105 | | --- | --- | 106 | | remark | 备注 | 107 | | host | 域名 | 108 | | scheme | 协议类型(三种 all http https) | 109 | | location | url路由 空则为不限制 | 110 | | client\_id | 客户端id | 111 | | target | 内网目标(ip:端口) | 112 | | header | request header 请求头 | 113 | | hostchange | request host 请求主机 | 114 | 115 | *** 116 | 修改域名解析 117 | 118 | ``` 119 | POST /index/edithost/ 120 | ``` 121 | 122 | | 参数 | 含义 | 123 | | --- | --- | 124 | | remark | 备注 | 125 | | host | 域名 | 126 | | scheme | 协议类型(三种 all http https) | 127 | | location | url路由 空则为不限制 | 128 | | client\_id | 客户端id | 129 | | target | 内网目标(ip:端口) | 130 | | header | request header 请求头 | 131 | | hostchange | request host 请求主机 | 132 | | id | 需要修改的域名解析id | 133 | 134 | *** 135 | 删除域名解析 136 | 137 | ``` 138 | POST /index/delhost/ 139 | ``` 140 | 141 | | 参数 | 含义 | 142 | | --- | --- | 143 | | id | 需要删除的域名解析id | 144 | 145 | *** 146 | 获取单条隧道信息 147 | 148 | ``` 149 | POST /index/getonetunnel/ 150 | ``` 151 | 152 | | 参数 | 含义 | 153 | | --- | --- | 154 | | id | 隧道的id | 155 | 156 | *** 157 | 获取隧道列表 158 | 159 | ``` 160 | POST /index/gettunnel/ 161 | ``` 162 | 163 | | 参数 | 含义 | 164 | | --- | --- | 165 | | client\_id | 穿透隧道的客户端id | 166 | | type | 类型tcp udp httpProx socks5 secret p2p | 167 | | search | 搜索 | 168 | | offset | 分页(第几页) | 169 | | limit | 条数(分页显示的条数) | 170 | 171 | *** 172 | 添加隧道 173 | 174 | ``` 175 | POST /index/add/ 176 | ``` 177 | 178 | | 参数 | 含义 | 179 | | --- | --- | 180 | | type | 类型tcp udp httpProx socks5 secret p2p | 181 | | remark | 备注 | 182 | | port | 服务端端口 | 183 | | target | 目标(ip:端口) | 184 | | client\_id | 客户端id | 185 | 186 | *** 187 | 修改隧道 188 | 189 | ``` 190 | POST /index/edit/ 191 | ``` 192 | 193 | | 参数 | 含义 | 194 | | --- | --- | 195 | | type | 类型tcp udp httpProx socks5 secret p2p | 196 | | remark | 备注 | 197 | | port | 服务端端口 | 198 | | target | 目标(ip:端口) | 199 | | client\_id | 客户端id | 200 | | id | 隧道id | 201 | 202 | *** 203 | 删除隧道 204 | 205 | ``` 206 | POST /index/del/ 207 | ``` 208 | 209 | | 参数 | 含义 | 210 | | --- | --- | 211 | | id | 隧道id | 212 | 213 | *** 214 | 隧道停止工作 215 | 216 | ``` 217 | POST /index/stop/ 218 | ``` 219 | 220 | | 参数 | 含义 | 221 | | --- | --- | 222 | | id | 隧道id | 223 | 224 | *** 225 | 隧道开始工作 226 | 227 | ``` 228 | POST /index/start/ 229 | ``` 230 | 231 | | 参数 | 含义 | 232 | | --- | --- | 233 | | id | 隧道id | 234 | -------------------------------------------------------------------------------- /docs/windows_client_service_configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/docs/windows_client_service_configuration.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module ehang.io/nps 2 | 3 | go 1.22 4 | 5 | require ( 6 | fyne.io/fyne/v2 v2.0.2 7 | github.com/astaxie/beego v1.12.0 8 | github.com/c4milo/unpackit v0.0.0-20170704181138-4ed373e9ef1c 9 | github.com/ccding/go-stun v0.0.0-20180726100737-be486d185f3d 10 | github.com/fatih/color v1.18.0 11 | github.com/golang/snappy v0.0.3 12 | github.com/google/uuid v1.6.0 13 | github.com/kardianos/service v1.2.0 14 | github.com/panjf2000/ants/v2 v2.4.2 15 | github.com/pkg/errors v0.9.1 16 | github.com/shirou/gopsutil/v3 v3.23.10 17 | github.com/xtaci/kcp-go v5.4.20+incompatible 18 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 19 | ) 20 | 21 | require ( 22 | github.com/bradfitz/iter v0.0.0-20191230175014-e8f45d346db8 // indirect 23 | github.com/davecgh/go-spew v1.1.1 // indirect 24 | github.com/dsnet/compress v0.0.1 // indirect 25 | github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 // indirect 26 | github.com/fsnotify/fsnotify v1.4.9 // indirect 27 | github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f // indirect 28 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect 29 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 // indirect 30 | github.com/go-ole/go-ole v1.2.6 // indirect 31 | github.com/godbus/dbus/v5 v5.0.3 // indirect 32 | github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect 33 | github.com/hooklift/assert v0.0.0-20170704181755-9d1defd6d214 // indirect 34 | github.com/klauspost/compress v1.4.1 // indirect 35 | github.com/klauspost/cpuid v1.3.1 // indirect 36 | github.com/klauspost/cpuid/v2 v2.0.6 // indirect 37 | github.com/klauspost/pgzip v1.2.1 // indirect 38 | github.com/klauspost/reedsolomon v1.9.12 // indirect 39 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 40 | github.com/mattn/go-colorable v0.1.13 // indirect 41 | github.com/mattn/go-isatty v0.0.20 // indirect 42 | github.com/pmezard/go-difflib v1.0.0 // indirect 43 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 44 | github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect 45 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 46 | github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 // indirect 47 | github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 // indirect 48 | github.com/stretchr/testify v1.8.4 // indirect 49 | github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect 50 | github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect 51 | github.com/tjfoc/gmsm v1.4.0 // indirect 52 | github.com/tklauser/go-sysconf v0.3.12 // indirect 53 | github.com/tklauser/numcpus v0.6.1 // indirect 54 | github.com/ulikunitz/xz v0.5.6 // indirect 55 | github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae // indirect 56 | github.com/yusufpapurcu/wmi v1.2.3 // indirect 57 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect 58 | golang.org/x/image v0.0.0-20200430140353-33d19683fad8 // indirect 59 | golang.org/x/sys v0.25.0 // indirect 60 | golang.org/x/text v0.3.3 // indirect 61 | gopkg.in/yaml.v2 v2.2.8 // indirect 62 | gopkg.in/yaml.v3 v3.0.1 // indirect 63 | ) 64 | 65 | replace github.com/astaxie/beego => github.com/exfly/beego v1.12.0-export-init 66 | -------------------------------------------------------------------------------- /gui/npc/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /gui/npc/npc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "ehang.io/nps/client" 5 | "ehang.io/nps/lib/common" 6 | "ehang.io/nps/lib/daemon" 7 | "ehang.io/nps/lib/version" 8 | "fmt" 9 | "fyne.io/fyne/v2" 10 | "fyne.io/fyne/v2/app" 11 | "fyne.io/fyne/v2/container" 12 | "fyne.io/fyne/v2/layout" 13 | "fyne.io/fyne/v2/widget" 14 | "github.com/astaxie/beego/logs" 15 | "io/ioutil" 16 | "os" 17 | "path" 18 | "runtime" 19 | "strings" 20 | "time" 21 | ) 22 | 23 | func main() { 24 | daemon.InitDaemon("npc", common.GetRunPath(), common.GetTmpPath()) 25 | logs.SetLogger("store") 26 | application := app.New() 27 | window := application.NewWindow("Npc " + version.VERSION) 28 | window.SetContent(WidgetScreen()) 29 | window.Resize(fyne.NewSize(910, 350)) 30 | 31 | window.ShowAndRun() 32 | 33 | } 34 | 35 | var ( 36 | start bool 37 | closing bool 38 | status = "Start!" 39 | connType = "tcp" 40 | cl = new(client.TRPClient) 41 | refreshCh = make(chan struct{}) 42 | ) 43 | 44 | func WidgetScreen() fyne.CanvasObject { 45 | return fyne.NewContainerWithLayout(layout.NewBorderLayout(nil, nil, nil, nil), 46 | makeMainTab(), 47 | ) 48 | } 49 | 50 | func makeMainTab() *fyne.Container { 51 | serverPort := widget.NewEntry() 52 | serverPort.SetPlaceHolder("Server:Port") 53 | 54 | vKey := widget.NewEntry() 55 | vKey.SetPlaceHolder("Vkey") 56 | radio := widget.NewRadioGroup([]string{"tcp", "kcp"}, func(s string) { connType = s }) 57 | radio.Horizontal = true 58 | 59 | button := widget.NewButton(status, func() { 60 | onclick(serverPort.Text, vKey.Text, connType) 61 | }) 62 | go func() { 63 | for { 64 | <-refreshCh 65 | button.SetText(status) 66 | } 67 | }() 68 | 69 | lo := widget.NewMultiLineEntry() 70 | lo.Disable() 71 | lo.Resize(fyne.NewSize(910, 250)) 72 | slo := container.NewScroll(lo) 73 | slo.Resize(fyne.NewSize(910, 250)) 74 | go func() { 75 | for { 76 | time.Sleep(time.Second) 77 | lo.SetText(common.GetLogMsg()) 78 | slo.Resize(fyne.NewSize(910, 250)) 79 | } 80 | }() 81 | 82 | sp, vk, ct := loadConfig() 83 | if sp != "" && vk != "" && ct != "" { 84 | serverPort.SetText(sp) 85 | vKey.SetText(vk) 86 | connType = ct 87 | radio.SetSelected(ct) 88 | onclick(sp, vk, ct) 89 | } 90 | 91 | return container.NewVBox( 92 | widget.NewLabel("Npc "+version.VERSION), 93 | serverPort, 94 | vKey, 95 | radio, 96 | button, 97 | slo, 98 | ) 99 | } 100 | 101 | func onclick(s, v, c string) { 102 | start = !start 103 | if start { 104 | closing = false 105 | status = "Stop!" 106 | // init the npc 107 | fmt.Println("submit", s, v, c) 108 | sp, vk, ct := loadConfig() 109 | if sp != s || vk != v || ct != c { 110 | saveConfig(s, v, c) 111 | } 112 | go func() { 113 | for { 114 | cl = client.NewRPClient(s, v, c, "", nil, 60) 115 | status = "Stop!" 116 | refreshCh <- struct{}{} 117 | cl.Start() 118 | logs.Warn("client closed, reconnecting in 5 seconds...") 119 | if closing { 120 | return 121 | } 122 | status = "Reconnecting..." 123 | refreshCh <- struct{}{} 124 | time.Sleep(time.Second * 5) 125 | } 126 | }() 127 | } else { 128 | // close the npc 129 | status = "Start!" 130 | closing = true 131 | if cl != nil { 132 | go cl.Close() 133 | cl = nil 134 | } 135 | } 136 | refreshCh <- struct{}{} 137 | } 138 | 139 | func getDir() (dir string, err error) { 140 | if runtime.GOOS != "android" { 141 | dir, err = os.UserConfigDir() 142 | if err != nil { 143 | return 144 | } 145 | } else { 146 | dir = "/data/data/org.nps.client/files" 147 | } 148 | return 149 | } 150 | 151 | func saveConfig(host, vkey, connType string) { 152 | data := strings.Join([]string{host, vkey, connType}, "\n") 153 | ph, err := getDir() 154 | if err != nil { 155 | logs.Warn("not found config dir") 156 | return 157 | } 158 | _ = os.Remove(path.Join(ph, "npc.conf")) 159 | f, err := os.OpenFile(path.Join(ph, "npc.conf"), os.O_CREATE|os.O_WRONLY, 0644) 160 | defer f.Close() 161 | if err != nil { 162 | logs.Error(err) 163 | return 164 | } 165 | if _, err := f.Write([]byte(data)); err != nil { 166 | _ = f.Close() // ignore error; Write error takes precedence 167 | logs.Error(err) 168 | return 169 | } 170 | } 171 | 172 | func loadConfig() (host, vkey, connType string) { 173 | ph, err := getDir() 174 | if err != nil { 175 | logs.Warn("not found config dir") 176 | return 177 | } 178 | f, err := os.OpenFile(path.Join(ph, "npc.conf"), os.O_RDONLY, 0644) 179 | defer f.Close() 180 | if err != nil { 181 | logs.Error(err) 182 | return 183 | } 184 | data, err := ioutil.ReadAll(f) 185 | if err != nil { 186 | logs.Error(err) 187 | return 188 | } 189 | li := strings.Split(string(data), "\n") 190 | host = li[0] 191 | vkey = li[1] 192 | connType = li[2] 193 | return 194 | } 195 | -------------------------------------------------------------------------------- /image/bt/bt1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/bt/bt1.jpg -------------------------------------------------------------------------------- /image/bt/bt2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/bt/bt2.png -------------------------------------------------------------------------------- /image/bt/bt3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/bt/bt3.png -------------------------------------------------------------------------------- /image/bt/bt_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/bt/bt_1.png -------------------------------------------------------------------------------- /image/cpu1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/cpu1.png -------------------------------------------------------------------------------- /image/cpu2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/cpu2.png -------------------------------------------------------------------------------- /image/donation_wx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/donation_wx.png -------------------------------------------------------------------------------- /image/donation_zfb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/donation_zfb.png -------------------------------------------------------------------------------- /image/http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/http.png -------------------------------------------------------------------------------- /image/httpProxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/httpProxy.png -------------------------------------------------------------------------------- /image/new/cmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/new/cmd.png -------------------------------------------------------------------------------- /image/new/https.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/new/https.png -------------------------------------------------------------------------------- /image/new/payCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/new/payCode.png -------------------------------------------------------------------------------- /image/new/tcp_limit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/new/tcp_limit.png -------------------------------------------------------------------------------- /image/qps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/qps.png -------------------------------------------------------------------------------- /image/sock5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/sock5.png -------------------------------------------------------------------------------- /image/speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/speed.png -------------------------------------------------------------------------------- /image/tcp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/tcp.png -------------------------------------------------------------------------------- /image/udp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/udp.png -------------------------------------------------------------------------------- /image/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/web.png -------------------------------------------------------------------------------- /image/web2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/image/web2.png -------------------------------------------------------------------------------- /lib/cache/lru.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "container/list" 5 | "sync" 6 | ) 7 | 8 | // Cache is an LRU cache. It is safe for concurrent access. 9 | type Cache struct { 10 | // MaxEntries is the maximum number of cache entries before 11 | // an item is evicted. Zero means no limit. 12 | MaxEntries int 13 | 14 | //Execute this callback function when an element is culled 15 | OnEvicted func(key Key, value interface{}) 16 | 17 | ll *list.List //list 18 | cache sync.Map 19 | } 20 | 21 | // A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators 22 | type Key interface{} 23 | 24 | type entry struct { 25 | key Key 26 | value interface{} 27 | } 28 | 29 | // New creates a new Cache. 30 | // If maxEntries is 0, the cache has no length limit. 31 | // that eviction is done by the caller. 32 | func New(maxEntries int) *Cache { 33 | return &Cache{ 34 | MaxEntries: maxEntries, 35 | ll: list.New(), 36 | //cache: make(map[interface{}]*list.Element), 37 | } 38 | } 39 | 40 | // If the key value already exists, move the key to the front 41 | func (c *Cache) Add(key Key, value interface{}) { 42 | if ee, ok := c.cache.Load(key); ok { 43 | c.ll.MoveToFront(ee.(*list.Element)) // move to the front 44 | ee.(*list.Element).Value.(*entry).value = value 45 | return 46 | } 47 | ele := c.ll.PushFront(&entry{key, value}) 48 | c.cache.Store(key, ele) 49 | if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries { // Remove the oldest element if the limit is exceeded 50 | c.RemoveOldest() 51 | } 52 | } 53 | 54 | // Get looks up a key's value from the cache. 55 | func (c *Cache) Get(key Key) (value interface{}, ok bool) { 56 | if ele, hit := c.cache.Load(key); hit { 57 | c.ll.MoveToFront(ele.(*list.Element)) 58 | return ele.(*list.Element).Value.(*entry).value, true 59 | } 60 | return 61 | } 62 | 63 | // Remove removes the provided key from the cache. 64 | func (c *Cache) Remove(key Key) { 65 | if ele, hit := c.cache.Load(key); hit { 66 | c.removeElement(ele.(*list.Element)) 67 | } 68 | } 69 | 70 | // RemoveOldest removes the oldest item from the cache. 71 | func (c *Cache) RemoveOldest() { 72 | ele := c.ll.Back() 73 | if ele != nil { 74 | c.removeElement(ele) 75 | } 76 | } 77 | 78 | func (c *Cache) removeElement(e *list.Element) { 79 | c.ll.Remove(e) 80 | kv := e.Value.(*entry) 81 | c.cache.Delete(kv.key) 82 | if c.OnEvicted != nil { 83 | c.OnEvicted(kv.key, kv.value) 84 | } 85 | } 86 | 87 | // Len returns the number of items in the cache. 88 | func (c *Cache) Len() int { 89 | return c.ll.Len() 90 | } 91 | 92 | // Clear purges all stored items from the cache. 93 | func (c *Cache) Clear() { 94 | if c.OnEvicted != nil { 95 | c.cache.Range(func(key, value interface{}) bool { 96 | kv := value.(*list.Element).Value.(*entry) 97 | c.OnEvicted(kv.key, kv.value) 98 | return true 99 | }) 100 | } 101 | c.ll = nil 102 | } 103 | -------------------------------------------------------------------------------- /lib/common/const.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const ( 4 | CONN_DATA_SEQ = "*#*" //Separator 5 | VERIFY_EER = "vkey" 6 | VERIFY_SUCCESS = "sucs" 7 | WORK_MAIN = "main" 8 | WORK_CHAN = "chan" 9 | WORK_CONFIG = "conf" 10 | WORK_REGISTER = "rgst" 11 | WORK_SECRET = "sert" 12 | WORK_FILE = "file" 13 | WORK_P2P = "p2pm" 14 | WORK_P2P_VISITOR = "p2pv" 15 | WORK_P2P_PROVIDER = "p2pp" 16 | WORK_P2P_CONNECT = "p2pc" 17 | WORK_P2P_SUCCESS = "p2ps" 18 | WORK_P2P_END = "p2pe" 19 | WORK_P2P_LAST = "p2pl" 20 | WORK_STATUS = "stus" 21 | RES_MSG = "msg0" 22 | RES_CLOSE = "clse" 23 | NEW_UDP_CONN = "udpc" //p2p udp conn 24 | NEW_TASK = "task" 25 | NEW_CONF = "conf" 26 | NEW_HOST = "host" 27 | CONN_TCP = "tcp" 28 | CONN_UDP = "udp" 29 | CONN_TEST = "TST" 30 | DEFAULT_TIME = "2006-01-02 15:04:05" 31 | UnauthorizedBytes = `HTTP/1.1 401 Unauthorized 32 | Content-Type: text/plain; charset=utf-8 33 | WWW-Authenticate: Basic realm="easyProxy" 34 | 35 | 401 Unauthorized` 36 | ConnectionFailBytes = `HTTP/1.1 404 Not Found 37 | 38 | ` 39 | ) 40 | -------------------------------------------------------------------------------- /lib/common/logs.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/astaxie/beego/logs" 5 | "time" 6 | ) 7 | 8 | const MaxMsgLen = 5000 9 | 10 | var logMsgs string 11 | 12 | func init() { 13 | logs.Register("store", func() logs.Logger { 14 | return new(StoreMsg) 15 | }) 16 | } 17 | 18 | func GetLogMsg() string { 19 | return logMsgs 20 | } 21 | 22 | type StoreMsg struct { 23 | } 24 | 25 | func (lg *StoreMsg) Init(config string) error { 26 | return nil 27 | } 28 | 29 | func (lg *StoreMsg) WriteMsg(when time.Time, msg string, level int) error { 30 | m := when.Format("2006-01-02 15:04:05") + " " + msg + "\r\n" 31 | if len(logMsgs) > MaxMsgLen { 32 | start := MaxMsgLen - len(m) 33 | if start <= 0 { 34 | start = MaxMsgLen 35 | } 36 | logMsgs = logMsgs[start:] 37 | } 38 | logMsgs += m 39 | return nil 40 | } 41 | 42 | func (lg *StoreMsg) Destroy() { 43 | return 44 | } 45 | 46 | func (lg *StoreMsg) Flush() { 47 | return 48 | } 49 | -------------------------------------------------------------------------------- /lib/common/netpackager.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "io" 8 | "io/ioutil" 9 | "net" 10 | "strconv" 11 | ) 12 | 13 | type NetPackager interface { 14 | Pack(writer io.Writer) (err error) 15 | UnPack(reader io.Reader) (err error) 16 | } 17 | 18 | const ( 19 | ipV4 = 1 20 | domainName = 3 21 | ipV6 = 4 22 | ) 23 | 24 | type UDPHeader struct { 25 | Rsv uint16 26 | Frag uint8 27 | Addr *Addr 28 | } 29 | 30 | func NewUDPHeader(rsv uint16, frag uint8, addr *Addr) *UDPHeader { 31 | return &UDPHeader{ 32 | Rsv: rsv, 33 | Frag: frag, 34 | Addr: addr, 35 | } 36 | } 37 | 38 | type Addr struct { 39 | Type uint8 40 | Host string 41 | Port uint16 42 | } 43 | 44 | func (addr *Addr) String() string { 45 | return net.JoinHostPort(addr.Host, strconv.Itoa(int(addr.Port))) 46 | } 47 | 48 | func (addr *Addr) Decode(b []byte) error { 49 | addr.Type = b[0] 50 | pos := 1 51 | switch addr.Type { 52 | case ipV4: 53 | addr.Host = net.IP(b[pos : pos+net.IPv4len]).String() 54 | pos += net.IPv4len 55 | case ipV6: 56 | addr.Host = net.IP(b[pos : pos+net.IPv6len]).String() 57 | pos += net.IPv6len 58 | case domainName: 59 | addrlen := int(b[pos]) 60 | pos++ 61 | addr.Host = string(b[pos : pos+addrlen]) 62 | pos += addrlen 63 | default: 64 | return errors.New("decode error") 65 | } 66 | 67 | addr.Port = binary.BigEndian.Uint16(b[pos:]) 68 | 69 | return nil 70 | } 71 | 72 | func (addr *Addr) Encode(b []byte) (int, error) { 73 | b[0] = addr.Type 74 | pos := 1 75 | switch addr.Type { 76 | case ipV4: 77 | ip4 := net.ParseIP(addr.Host).To4() 78 | if ip4 == nil { 79 | ip4 = net.IPv4zero.To4() 80 | } 81 | pos += copy(b[pos:], ip4) 82 | case domainName: 83 | b[pos] = byte(len(addr.Host)) 84 | pos++ 85 | pos += copy(b[pos:], []byte(addr.Host)) 86 | case ipV6: 87 | ip16 := net.ParseIP(addr.Host).To16() 88 | if ip16 == nil { 89 | ip16 = net.IPv6zero.To16() 90 | } 91 | pos += copy(b[pos:], ip16) 92 | default: 93 | b[0] = ipV4 94 | copy(b[pos:pos+4], net.IPv4zero.To4()) 95 | pos += 4 96 | } 97 | binary.BigEndian.PutUint16(b[pos:], addr.Port) 98 | pos += 2 99 | 100 | return pos, nil 101 | } 102 | 103 | func (h *UDPHeader) Write(w io.Writer) error { 104 | b := BufPoolUdp.Get().([]byte) 105 | defer BufPoolUdp.Put(b) 106 | 107 | binary.BigEndian.PutUint16(b[:2], h.Rsv) 108 | b[2] = h.Frag 109 | 110 | addr := h.Addr 111 | if addr == nil { 112 | addr = &Addr{} 113 | } 114 | length, _ := addr.Encode(b[3:]) 115 | 116 | _, err := w.Write(b[:3+length]) 117 | return err 118 | } 119 | 120 | type UDPDatagram struct { 121 | Header *UDPHeader 122 | Data []byte 123 | } 124 | 125 | func ReadUDPDatagram(r io.Reader) (*UDPDatagram, error) { 126 | b := BufPoolUdp.Get().([]byte) 127 | defer BufPoolUdp.Put(b) 128 | 129 | // when r is a streaming (such as TCP connection), we may read more than the required data, 130 | // but we don't know how to handle it. So we use io.ReadFull to instead of io.ReadAtLeast 131 | // to make sure that no redundant data will be discarded. 132 | n, err := io.ReadFull(r, b[:5]) 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | header := &UDPHeader{ 138 | Rsv: binary.BigEndian.Uint16(b[:2]), 139 | Frag: b[2], 140 | } 141 | 142 | atype := b[3] 143 | hlen := 0 144 | switch atype { 145 | case ipV4: 146 | hlen = 10 147 | case ipV6: 148 | hlen = 22 149 | case domainName: 150 | hlen = 7 + int(b[4]) 151 | default: 152 | return nil, errors.New("addr not support") 153 | } 154 | dlen := int(header.Rsv) 155 | if dlen == 0 { // standard SOCKS5 UDP datagram 156 | extra, err := ioutil.ReadAll(r) // we assume no redundant data 157 | if err != nil { 158 | return nil, err 159 | } 160 | copy(b[n:], extra) 161 | n += len(extra) // total length 162 | dlen = n - hlen // data length 163 | } else { // extended feature, for UDP over TCP, using reserved field as data length 164 | if _, err := io.ReadFull(r, b[n:hlen+dlen]); err != nil { 165 | return nil, err 166 | } 167 | n = hlen + dlen 168 | } 169 | header.Addr = new(Addr) 170 | if err := header.Addr.Decode(b[3:hlen]); err != nil { 171 | return nil, err 172 | } 173 | data := make([]byte, dlen) 174 | copy(data, b[hlen:n]) 175 | d := &UDPDatagram{ 176 | Header: header, 177 | Data: data, 178 | } 179 | return d, nil 180 | } 181 | 182 | func NewUDPDatagram(header *UDPHeader, data []byte) *UDPDatagram { 183 | return &UDPDatagram{ 184 | Header: header, 185 | Data: data, 186 | } 187 | } 188 | 189 | func (d *UDPDatagram) Write(w io.Writer) error { 190 | h := d.Header 191 | if h == nil { 192 | h = &UDPHeader{} 193 | } 194 | buf := bytes.Buffer{} 195 | if err := h.Write(&buf); err != nil { 196 | return err 197 | } 198 | if _, err := buf.Write(d.Data); err != nil { 199 | return err 200 | } 201 | 202 | _, err := buf.WriteTo(w) 203 | return err 204 | } 205 | 206 | func ToSocksAddr(addr net.Addr) *Addr { 207 | host := "0.0.0.0" 208 | port := 0 209 | if addr != nil { 210 | h, p, _ := net.SplitHostPort(addr.String()) 211 | host = h 212 | port, _ = strconv.Atoi(p) 213 | } 214 | return &Addr{ 215 | Type: ipV4, 216 | Host: host, 217 | Port: uint16(port), 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /lib/common/pool.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | const PoolSize = 64 * 1024 8 | const PoolSizeSmall = 100 9 | const PoolSizeUdp = 1472 + 200 10 | const PoolSizeCopy = 32 << 10 11 | 12 | var BufPool = sync.Pool{ 13 | New: func() interface{} { 14 | return make([]byte, PoolSize) 15 | }, 16 | } 17 | 18 | var BufPoolUdp = sync.Pool{ 19 | New: func() interface{} { 20 | return make([]byte, PoolSizeUdp) 21 | }, 22 | } 23 | var BufPoolMax = sync.Pool{ 24 | New: func() interface{} { 25 | return make([]byte, PoolSize) 26 | }, 27 | } 28 | var BufPoolSmall = sync.Pool{ 29 | New: func() interface{} { 30 | return make([]byte, PoolSizeSmall) 31 | }, 32 | } 33 | var BufPoolCopy = sync.Pool{ 34 | New: func() interface{} { 35 | return make([]byte, PoolSizeCopy) 36 | }, 37 | } 38 | 39 | func PutBufPoolUdp(buf []byte) { 40 | if cap(buf) == PoolSizeUdp { 41 | BufPoolUdp.Put(buf[:PoolSizeUdp]) 42 | } 43 | } 44 | 45 | func PutBufPoolCopy(buf []byte) { 46 | if cap(buf) == PoolSizeCopy { 47 | BufPoolCopy.Put(buf[:PoolSizeCopy]) 48 | } 49 | } 50 | 51 | func GetBufPoolCopy() []byte { 52 | return (BufPoolCopy.Get().([]byte))[:PoolSizeCopy] 53 | } 54 | 55 | func PutBufPoolMax(buf []byte) { 56 | if cap(buf) == PoolSize { 57 | BufPoolMax.Put(buf[:PoolSize]) 58 | } 59 | } 60 | 61 | type copyBufferPool struct { 62 | pool sync.Pool 63 | } 64 | 65 | func (Self *copyBufferPool) New() { 66 | Self.pool = sync.Pool{ 67 | New: func() interface{} { 68 | return make([]byte, PoolSizeCopy, PoolSizeCopy) 69 | }, 70 | } 71 | } 72 | 73 | func (Self *copyBufferPool) Get() []byte { 74 | buf := Self.pool.Get().([]byte) 75 | return buf[:PoolSizeCopy] // just like make a new slice, but data may not be 0 76 | } 77 | 78 | func (Self *copyBufferPool) Put(x []byte) { 79 | if len(x) == PoolSizeCopy { 80 | Self.pool.Put(x) 81 | } else { 82 | x = nil // buf is not full, not allowed, New method returns a full buf 83 | } 84 | } 85 | 86 | var once = sync.Once{} 87 | var CopyBuff = copyBufferPool{} 88 | 89 | func newPool() { 90 | CopyBuff.New() 91 | } 92 | 93 | func init() { 94 | once.Do(newPool) 95 | } 96 | -------------------------------------------------------------------------------- /lib/common/pprof.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "github.com/astaxie/beego" 5 | "github.com/astaxie/beego/logs" 6 | "net/http" 7 | _ "net/http/pprof" 8 | ) 9 | 10 | func InitPProfFromFile() { 11 | ip := beego.AppConfig.String("pprof_ip") 12 | p := beego.AppConfig.String("pprof_port") 13 | if len(ip) > 0 && len(p) > 0 && IsPort(p) { 14 | runPProf(ip + ":" + p) 15 | } 16 | } 17 | 18 | func InitPProfFromArg(arg string) { 19 | if len(arg) > 0 { 20 | runPProf(arg) 21 | } 22 | } 23 | 24 | func runPProf(ipPort string) { 25 | go func() { 26 | _ = http.ListenAndServe(ipPort, nil) 27 | }() 28 | logs.Info("PProf debug listen on", ipPort) 29 | } 30 | -------------------------------------------------------------------------------- /lib/common/run.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "runtime" 7 | ) 8 | 9 | var ConfPath string 10 | 11 | // Get the currently selected configuration file directory 12 | // For non-Windows systems, select the /etc/nps as config directory if exist, or select ./ 13 | // windows system, select the C:\Program Files\nps as config directory if exist, or select ./ 14 | func GetRunPath() string { 15 | var path string 16 | if len(os.Args) == 1 { 17 | if !IsWindows() { 18 | dir, _ := filepath.Abs(filepath.Dir(os.Args[0])) //返回 19 | return dir + "/" 20 | } else { 21 | return "./" 22 | } 23 | } else { 24 | if path = GetInstallPath(); !FileExists(path) { 25 | return GetAppPath() 26 | } 27 | } 28 | return path 29 | } 30 | 31 | // Different systems get different installation paths 32 | func GetInstallPath() string { 33 | var path string 34 | 35 | if ConfPath != "" { 36 | return ConfPath 37 | } 38 | 39 | if IsWindows() { 40 | path = `C:\Program Files\nps` 41 | } else { 42 | path = "/etc/nps" 43 | } 44 | 45 | return path 46 | } 47 | 48 | // Get the absolute path to the running directory 49 | func GetAppPath() string { 50 | if path, err := filepath.Abs(filepath.Dir(os.Args[0])); err == nil { 51 | return path 52 | } 53 | return os.Args[0] 54 | } 55 | 56 | // Determine whether the current system is a Windows system? 57 | func IsWindows() bool { 58 | if runtime.GOOS == "windows" { 59 | return true 60 | } 61 | return false 62 | } 63 | 64 | // interface log file path 65 | func GetLogPath() string { 66 | var path string 67 | if IsWindows() { 68 | path = filepath.Join(GetAppPath(), "nps.log") 69 | } else { 70 | path = "/var/log/nps.log" 71 | } 72 | return path 73 | } 74 | 75 | // interface npc log file path 76 | func GetNpcLogPath() string { 77 | var path string 78 | if IsWindows() { 79 | path = filepath.Join(GetAppPath(), "npc.log") 80 | } else { 81 | path = "/var/log/npc.log" 82 | } 83 | return path 84 | } 85 | 86 | // interface pid file path 87 | func GetTmpPath() string { 88 | var path string 89 | if IsWindows() { 90 | path = GetAppPath() 91 | } else { 92 | path = "/tmp" 93 | } 94 | return path 95 | } 96 | 97 | // config file path 98 | func GetConfigPath() string { 99 | var path string 100 | if IsWindows() { 101 | path = filepath.Join(GetAppPath(), "conf/npc.conf") 102 | } else { 103 | path = "conf/npc.conf" 104 | } 105 | return path 106 | } 107 | -------------------------------------------------------------------------------- /lib/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "log" 5 | "regexp" 6 | "testing" 7 | ) 8 | 9 | func TestReg(t *testing.T) { 10 | content := ` 11 | [common] 12 | server=127.0.0.1:8284 13 | tp=tcp 14 | vkey=123 15 | [web2] 16 | host=www.baidu.com 17 | host_change=www.sina.com 18 | target=127.0.0.1:8080,127.0.0.1:8082 19 | header_cookkile=122123 20 | header_user-Agent=122123 21 | [web2] 22 | host=www.baidu.com 23 | host_change=www.sina.com 24 | target=127.0.0.1:8080,127.0.0.1:8082 25 | header_cookkile="122123" 26 | header_user-Agent=122123 27 | [tunnel1] 28 | type=udp 29 | target=127.0.0.1:8080 30 | port=9001 31 | compress=snappy 32 | crypt=true 33 | u=1 34 | p=2 35 | [tunnel2] 36 | type=tcp 37 | target=127.0.0.1:8080 38 | port=9001 39 | compress=snappy 40 | crypt=true 41 | u=1 42 | p=2 43 | ` 44 | re, err := regexp.Compile(`\[.+?\]`) 45 | if err != nil { 46 | t.Fail() 47 | } 48 | log.Println(re.FindAllString(content, -1)) 49 | } 50 | 51 | func TestDealCommon(t *testing.T) { 52 | s := `server=127.0.0.1:8284 53 | tp=tcp 54 | vkey=123` 55 | f := new(CommonConfig) 56 | f.Server = "127.0.0.1:8284" 57 | f.Tp = "tcp" 58 | f.VKey = "123" 59 | if c := dealCommon(s); *c != *f { 60 | t.Fail() 61 | } 62 | } 63 | 64 | func TestGetTitleContent(t *testing.T) { 65 | s := "[common]" 66 | if getTitleContent(s) != "common" { 67 | t.Fail() 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/conn/link.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import "time" 4 | 5 | type Secret struct { 6 | Password string 7 | Conn *Conn 8 | } 9 | 10 | func NewSecret(p string, conn *Conn) *Secret { 11 | return &Secret{ 12 | Password: p, 13 | Conn: conn, 14 | } 15 | } 16 | 17 | type Link struct { 18 | ConnType string //连接类型 19 | Host string //目标 20 | Crypt bool //加密 21 | Compress bool 22 | LocalProxy bool 23 | RemoteAddr string 24 | Option Options 25 | } 26 | 27 | type Option func(*Options) 28 | 29 | type Options struct { 30 | Timeout time.Duration 31 | } 32 | 33 | var defaultTimeOut = time.Second * 5 34 | 35 | func NewLink(connType string, host string, crypt bool, compress bool, remoteAddr string, localProxy bool, opts ...Option) *Link { 36 | options := newOptions(opts...) 37 | 38 | return &Link{ 39 | RemoteAddr: remoteAddr, 40 | ConnType: connType, 41 | Host: host, 42 | Crypt: crypt, 43 | Compress: compress, 44 | LocalProxy: localProxy, 45 | Option: options, 46 | } 47 | } 48 | 49 | func newOptions(opts ...Option) Options { 50 | opt := Options{ 51 | Timeout: defaultTimeOut, 52 | } 53 | for _, o := range opts { 54 | o(&opt) 55 | } 56 | return opt 57 | } 58 | 59 | func LinkTimeout(t time.Duration) Option { 60 | return func(opt *Options) { 61 | opt.Timeout = t 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/conn/listener.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | "github.com/astaxie/beego/logs" 8 | "github.com/xtaci/kcp-go" 9 | ) 10 | 11 | func NewTcpListenerAndProcess(addr string, f func(c net.Conn), listener *net.Listener) error { 12 | var err error 13 | *listener, err = net.Listen("tcp", addr) 14 | if err != nil { 15 | return err 16 | } 17 | Accept(*listener, f) 18 | return nil 19 | } 20 | 21 | func NewKcpListenerAndProcess(addr string, f func(c net.Conn)) error { 22 | kcpListener, err := kcp.ListenWithOptions(addr, nil, 150, 3) 23 | if err != nil { 24 | logs.Error(err) 25 | return err 26 | } 27 | for { 28 | c, err := kcpListener.AcceptKCP() 29 | SetUdpSession(c) 30 | if err != nil { 31 | logs.Warn(err) 32 | continue 33 | } 34 | go f(c) 35 | } 36 | return nil 37 | } 38 | 39 | func Accept(l net.Listener, f func(c net.Conn)) { 40 | for { 41 | c, err := l.Accept() 42 | if err != nil { 43 | if strings.Contains(err.Error(), "use of closed network connection") { 44 | break 45 | } 46 | if strings.Contains(err.Error(), "the mux has closed") { 47 | break 48 | } 49 | logs.Warn(err) 50 | continue 51 | } 52 | if c == nil { 53 | logs.Warn("nil connection") 54 | break 55 | } 56 | go f(c) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/conn/snappy.go: -------------------------------------------------------------------------------- 1 | package conn 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | 7 | "github.com/golang/snappy" 8 | ) 9 | 10 | type SnappyConn struct { 11 | w *snappy.Writer 12 | r *snappy.Reader 13 | c io.Closer 14 | } 15 | 16 | func NewSnappyConn(conn io.ReadWriteCloser) *SnappyConn { 17 | c := new(SnappyConn) 18 | c.w = snappy.NewBufferedWriter(conn) 19 | c.r = snappy.NewReader(conn) 20 | c.c = conn.(io.Closer) 21 | return c 22 | } 23 | 24 | // snappy压缩写 25 | func (s *SnappyConn) Write(b []byte) (n int, err error) { 26 | if n, err = s.w.Write(b); err != nil { 27 | return 28 | } 29 | if err = s.w.Flush(); err != nil { 30 | return 31 | } 32 | return 33 | } 34 | 35 | // snappy压缩读 36 | func (s *SnappyConn) Read(b []byte) (n int, err error) { 37 | return s.r.Read(b) 38 | } 39 | 40 | func (s *SnappyConn) Close() error { 41 | err := s.w.Close() 42 | err2 := s.c.Close() 43 | if err != nil && err2 == nil { 44 | return err 45 | } 46 | if err == nil && err2 != nil { 47 | return err2 48 | } 49 | if err != nil && err2 != nil { 50 | return errors.New(err.Error() + err2.Error()) 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /lib/crypt/crypt.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/md5" 8 | "encoding/base64" 9 | "encoding/hex" 10 | "errors" 11 | "github.com/google/uuid" 12 | "math/rand" 13 | "strings" 14 | "time" 15 | ) 16 | 17 | // en 18 | func AesEncrypt(origData, key []byte) ([]byte, error) { 19 | block, err := aes.NewCipher(key) 20 | if err != nil { 21 | return nil, err 22 | } 23 | blockSize := block.BlockSize() 24 | origData = PKCS5Padding(origData, blockSize) 25 | blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) 26 | crypted := make([]byte, len(origData)) 27 | blockMode.CryptBlocks(crypted, origData) 28 | return crypted, nil 29 | } 30 | 31 | // de 32 | func AesDecrypt(crypted, key []byte) ([]byte, error) { 33 | block, err := aes.NewCipher(key) 34 | if err != nil { 35 | return nil, err 36 | } 37 | blockSize := block.BlockSize() 38 | blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) 39 | origData := make([]byte, len(crypted)) 40 | blockMode.CryptBlocks(origData, crypted) 41 | err, origData = PKCS5UnPadding(origData) 42 | return origData, err 43 | } 44 | 45 | // Completion when the length is insufficient 46 | func PKCS5Padding(ciphertext []byte, blockSize int) []byte { 47 | padding := blockSize - len(ciphertext)%blockSize 48 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 49 | return append(ciphertext, padtext...) 50 | } 51 | 52 | // Remove excess 53 | func PKCS5UnPadding(origData []byte) (error, []byte) { 54 | length := len(origData) 55 | unpadding := int(origData[length-1]) 56 | if (length - unpadding) < 0 { 57 | return errors.New("len error"), nil 58 | } 59 | return nil, origData[:(length - unpadding)] 60 | } 61 | 62 | // Generate 32-bit MD5 strings 63 | func Md5(s string) string { 64 | h := md5.New() 65 | h.Write([]byte(s)) 66 | return hex.EncodeToString(h.Sum(nil)) 67 | } 68 | 69 | // Generating Random Verification Key 70 | func GetRandomString(l int) string { 71 | str := "0123456789abcdefghijklmnopqrstuvwxyz" 72 | bytes := []byte(str) 73 | result := []byte{} 74 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 75 | for i := 0; i < l; i++ { 76 | result = append(result, bytes[r.Intn(len(bytes))]) 77 | } 78 | return string(result) 79 | } 80 | 81 | func GetVkey() string { 82 | // 生成UUID 83 | u, _ := uuid.NewRandom() 84 | // 将UUID转换为字符串 85 | uuidStr := u.String() 86 | uuidStr = strings.ReplaceAll(uuidStr, "-", "") 87 | // 截取前10位 88 | return uuidStr[:10] 89 | } 90 | 91 | func Base64Decoding(encodedString string) (string, error) { 92 | decodedBytes, err := base64.StdEncoding.DecodeString(encodedString) 93 | if err != nil { 94 | // 处理解码错误 95 | return "", err 96 | } 97 | 98 | // 将解码后的字节数组转换为字符串 99 | decodedString := string(decodedBytes) 100 | 101 | // 如果startCmd开头不包含"nps ",则报错 102 | if len(decodedString) < 4 || !(decodedString[0:4] == "nps ") { 103 | return "", errors.New("快捷启动命令错误,请检查") 104 | } 105 | 106 | return decodedString[4:], nil 107 | 108 | } 109 | -------------------------------------------------------------------------------- /lib/crypt/tls.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/tls" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/pem" 10 | "log" 11 | "math/big" 12 | "net" 13 | "os" 14 | "time" 15 | 16 | "github.com/astaxie/beego/logs" 17 | ) 18 | 19 | var ( 20 | cert tls.Certificate 21 | ) 22 | 23 | func InitTls() { 24 | c, k, err := generateKeyPair("NPS Org") 25 | if err == nil { 26 | cert, err = tls.X509KeyPair(c, k) 27 | } 28 | if err != nil { 29 | log.Fatalln("Error initializing crypto certs", err) 30 | } 31 | } 32 | 33 | func GetCert() tls.Certificate { 34 | return cert 35 | } 36 | 37 | func NewTlsServerConn(conn net.Conn) net.Conn { 38 | var err error 39 | if err != nil { 40 | logs.Error(err) 41 | os.Exit(0) 42 | return nil 43 | } 44 | config := &tls.Config{Certificates: []tls.Certificate{cert}} 45 | return tls.Server(conn, config) 46 | } 47 | 48 | func NewTlsClientConn(conn net.Conn) net.Conn { 49 | conf := &tls.Config{ 50 | InsecureSkipVerify: true, 51 | } 52 | return tls.Client(conn, conf) 53 | } 54 | 55 | func generateKeyPair(CommonName string) (rawCert, rawKey []byte, err error) { 56 | // Create private key and self-signed certificate 57 | // Adapted from https://golang.org/src/crypto/tls/generate_cert.go 58 | 59 | priv, err := rsa.GenerateKey(rand.Reader, 2048) 60 | if err != nil { 61 | return 62 | } 63 | validFor := time.Hour * 24 * 365 * 10 // ten years 64 | notBefore := time.Now() 65 | notAfter := notBefore.Add(validFor) 66 | serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) 67 | serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) 68 | template := x509.Certificate{ 69 | SerialNumber: serialNumber, 70 | Subject: pkix.Name{ 71 | Organization: []string{"My Company Name LTD."}, 72 | CommonName: CommonName, 73 | Country: []string{"US"}, 74 | }, 75 | NotBefore: notBefore, 76 | NotAfter: notAfter, 77 | 78 | KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 79 | ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 80 | BasicConstraintsValid: true, 81 | } 82 | derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) 83 | if err != nil { 84 | return 85 | } 86 | 87 | rawCert = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 88 | rawKey = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) 89 | 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /lib/daemon/daemon.go: -------------------------------------------------------------------------------- 1 | package daemon 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "strconv" 10 | "strings" 11 | 12 | "ehang.io/nps/lib/common" 13 | ) 14 | 15 | func InitDaemon(f string, runPath string, pidPath string) { 16 | if len(os.Args) < 2 { 17 | return 18 | } 19 | var args []string 20 | args = append(args, os.Args[0]) 21 | if len(os.Args) >= 2 { 22 | args = append(args, os.Args[2:]...) 23 | } 24 | args = append(args, "-log=file") 25 | switch os.Args[1] { 26 | case "start": 27 | start(args, f, pidPath, runPath) 28 | os.Exit(0) 29 | case "stop": 30 | stop(f, args[0], pidPath) 31 | os.Exit(0) 32 | case "restart": 33 | stop(f, args[0], pidPath) 34 | start(args, f, pidPath, runPath) 35 | os.Exit(0) 36 | case "status": 37 | if status(f, pidPath) { 38 | log.Printf("%s is running", f) 39 | } else { 40 | log.Printf("%s is not running", f) 41 | } 42 | os.Exit(0) 43 | case "reload": 44 | reload(f, pidPath) 45 | os.Exit(0) 46 | } 47 | } 48 | 49 | func reload(f string, pidPath string) { 50 | if f == "nps" && !common.IsWindows() && !status(f, pidPath) { 51 | log.Println("reload fail") 52 | return 53 | } 54 | var c *exec.Cmd 55 | var err error 56 | b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid")) 57 | if err == nil { 58 | c = exec.Command("/bin/bash", "-c", `kill -30 `+string(b)) 59 | } else { 60 | log.Fatalln("reload error,pid file does not exist") 61 | } 62 | if c.Run() == nil { 63 | log.Println("reload success") 64 | } else { 65 | log.Println("reload fail") 66 | } 67 | } 68 | 69 | func status(f string, pidPath string) bool { 70 | var cmd *exec.Cmd 71 | b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid")) 72 | if err == nil { 73 | if !common.IsWindows() { 74 | cmd = exec.Command("/bin/sh", "-c", "ps -ax | awk '{ print $1 }' | grep "+string(b)) 75 | } else { 76 | cmd = exec.Command("tasklist") 77 | } 78 | out, _ := cmd.Output() 79 | if strings.Index(string(out), string(b)) > -1 { 80 | return true 81 | } 82 | } 83 | return false 84 | } 85 | 86 | func start(osArgs []string, f string, pidPath, runPath string) { 87 | if status(f, pidPath) { 88 | log.Printf(" %s is running", f) 89 | return 90 | } 91 | cmd := exec.Command(osArgs[0], osArgs[1:]...) 92 | cmd.Start() 93 | if cmd.Process.Pid > 0 { 94 | log.Println("start ok , pid:", cmd.Process.Pid, "config path:", runPath) 95 | d1 := []byte(strconv.Itoa(cmd.Process.Pid)) 96 | ioutil.WriteFile(filepath.Join(pidPath, f+".pid"), d1, 0600) 97 | } else { 98 | log.Println("start error") 99 | } 100 | } 101 | 102 | func stop(f string, p string, pidPath string) { 103 | if !status(f, pidPath) { 104 | log.Printf(" %s is not running", f) 105 | return 106 | } 107 | var c *exec.Cmd 108 | var err error 109 | if common.IsWindows() { 110 | p := strings.Split(p, `\`) 111 | c = exec.Command("taskkill", "/F", "/IM", p[len(p)-1]) 112 | } else { 113 | b, err := ioutil.ReadFile(filepath.Join(pidPath, f+".pid")) 114 | if err == nil { 115 | c = exec.Command("/bin/bash", "-c", `kill -9 `+string(b)) 116 | } else { 117 | log.Fatalln("stop error,pid file does not exist") 118 | } 119 | } 120 | err = c.Run() 121 | if err != nil { 122 | log.Println("stop error,", err) 123 | } else { 124 | log.Println("stop ok") 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/daemon/reload.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package daemon 5 | 6 | import ( 7 | "os" 8 | "os/signal" 9 | "path/filepath" 10 | "syscall" 11 | 12 | "ehang.io/nps/lib/common" 13 | "github.com/astaxie/beego" 14 | ) 15 | 16 | func init() { 17 | s := make(chan os.Signal, 1) 18 | signal.Notify(s, syscall.SIGUSR1) 19 | go func() { 20 | for { 21 | <-s 22 | beego.LoadAppConfig("ini", filepath.Join(common.GetRunPath(), "conf", "nps.conf")) 23 | } 24 | }() 25 | } 26 | -------------------------------------------------------------------------------- /lib/file/sort.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | "sync" 7 | ) 8 | 9 | // A data structure to hold a key/value pair. 10 | type Pair struct { 11 | key string //sort key 12 | cId int 13 | order string 14 | clientFlow *Flow 15 | } 16 | 17 | // A slice of Pairs that implements sort.Interface to sort by Value. 18 | type PairList []*Pair 19 | 20 | func (p PairList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 21 | func (p PairList) Len() int { return len(p) } 22 | func (p PairList) Less(i, j int) bool { 23 | if p[i].order == "desc" { 24 | return reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() < reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int() 25 | } 26 | return reflect.ValueOf(*p[i].clientFlow).FieldByName(p[i].key).Int() > reflect.ValueOf(*p[j].clientFlow).FieldByName(p[j].key).Int() 27 | } 28 | 29 | // A function to turn a map into a PairList, then sort and return it. 30 | func sortClientByKey(m sync.Map, sortKey, order string) (res []int) { 31 | p := make(PairList, 0) 32 | m.Range(func(key, value interface{}) bool { 33 | p = append(p, &Pair{sortKey, value.(*Client).Id, order, value.(*Client).Flow}) 34 | return true 35 | }) 36 | sort.Sort(p) 37 | for _, v := range p { 38 | res = append(res, v.cId) 39 | } 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /lib/goroutine/pool.go: -------------------------------------------------------------------------------- 1 | package goroutine 2 | 3 | import ( 4 | "ehang.io/nps/lib/common" 5 | "ehang.io/nps/lib/file" 6 | "github.com/astaxie/beego/logs" 7 | "github.com/panjf2000/ants/v2" 8 | "io" 9 | "net" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | type connGroup struct { 15 | src io.ReadWriteCloser 16 | dst io.ReadWriteCloser 17 | wg *sync.WaitGroup 18 | n *int64 19 | flow *file.Flow 20 | task *file.Tunnel 21 | remote string 22 | } 23 | 24 | //func newConnGroup(dst, src io.ReadWriteCloser, wg *sync.WaitGroup, n *int64) connGroup { 25 | // return connGroup{ 26 | // src: src, 27 | // dst: dst, 28 | // wg: wg, 29 | // n: n, 30 | // } 31 | //} 32 | 33 | func newConnGroup(dst, src io.ReadWriteCloser, wg *sync.WaitGroup, n *int64, flow *file.Flow, task *file.Tunnel, remote string) connGroup { 34 | return connGroup{ 35 | src: src, 36 | dst: dst, 37 | wg: wg, 38 | n: n, 39 | flow: flow, 40 | task: task, 41 | remote: remote, 42 | } 43 | } 44 | 45 | func CopyBuffer(dst io.Writer, src io.Reader, flow *file.Flow, task *file.Tunnel, remote string) (err error) { 46 | buf := common.CopyBuff.Get() 47 | defer common.CopyBuff.Put(buf) 48 | for { 49 | if len(buf) <= 0 { 50 | break 51 | } 52 | nr, er := src.Read(buf) 53 | 54 | //if len(pr)>0 && pr[0] && nr > 50 { 55 | // logs.Warn(string(buf[:50])) 56 | //} 57 | 58 | if task != nil { 59 | task.IsHttp = false 60 | firstLine := string(buf[0:nr]) 61 | if len(firstLine) > 3 { 62 | method := firstLine[0:3] 63 | if method != "" && (method == "HTT" || method == "GET" || method == "POS" || method == "HEA" || method == "PUT" || method == "DEL") { 64 | if method != "HTT" { 65 | heads := strings.Split(firstLine, "\r\n") 66 | if len(heads) >= 2 { 67 | logs.Info("HTTP Request method %s, %s, remote address %s, target %s", heads[0], heads[1], remote, task.Target.TargetStr) 68 | } 69 | } 70 | 71 | task.IsHttp = true 72 | } 73 | } 74 | } 75 | 76 | if nr > 0 { 77 | nw, ew := dst.Write(buf[0:nr]) 78 | if nw > 0 { 79 | //written += int64(nw) 80 | if flow != nil { 81 | flow.Add(int64(nw), int64(nw)) 82 | // <<20 = 1024 * 1024 83 | if flow.FlowLimit > 0 && (flow.FlowLimit<<20) < (flow.ExportFlow+flow.InletFlow) { 84 | logs.Info("流量已经超出.........") 85 | break 86 | } 87 | } 88 | 89 | } 90 | if ew != nil { 91 | err = ew 92 | break 93 | } 94 | if nr != nw { 95 | err = io.ErrShortWrite 96 | break 97 | } 98 | } 99 | if er != nil { 100 | err = er 101 | break 102 | } 103 | } 104 | //return written, err 105 | return err 106 | } 107 | 108 | func copyConnGroup(group interface{}) { 109 | //logs.Info("copyConnGroup.........") 110 | cg, ok := group.(connGroup) 111 | if !ok { 112 | return 113 | } 114 | var err error 115 | err = CopyBuffer(cg.dst, cg.src, cg.flow, cg.task, cg.remote) 116 | if err != nil { 117 | cg.src.Close() 118 | cg.dst.Close() 119 | //logs.Warn("close npc by copy from nps", err, c.connId) 120 | } 121 | 122 | //if conns.flow != nil { 123 | // conns.flow.Add(in, out) 124 | //} 125 | cg.wg.Done() 126 | } 127 | 128 | type Conns struct { 129 | conn1 io.ReadWriteCloser // mux connection 130 | conn2 net.Conn // outside connection 131 | flow *file.Flow 132 | wg *sync.WaitGroup 133 | task *file.Tunnel 134 | } 135 | 136 | func NewConns(c1 io.ReadWriteCloser, c2 net.Conn, flow *file.Flow, wg *sync.WaitGroup, task *file.Tunnel) Conns { 137 | return Conns{ 138 | conn1: c1, 139 | conn2: c2, 140 | flow: flow, 141 | wg: wg, 142 | task: task, 143 | } 144 | } 145 | 146 | func copyConns(group interface{}) { 147 | //logs.Info("copyConns.........") 148 | conns := group.(Conns) 149 | wg := new(sync.WaitGroup) 150 | wg.Add(2) 151 | var in, out int64 152 | remoteAddr := conns.conn2.RemoteAddr().String() 153 | _ = connCopyPool.Invoke(newConnGroup(conns.conn1, conns.conn2, wg, &in, conns.flow, conns.task, remoteAddr)) 154 | // outside to mux : incoming 155 | _ = connCopyPool.Invoke(newConnGroup(conns.conn2, conns.conn1, wg, &out, conns.flow, conns.task, remoteAddr)) 156 | // mux to outside : outgoing 157 | wg.Wait() 158 | //if conns.flow != nil { 159 | // conns.flow.Add(in, out) 160 | //} 161 | conns.wg.Done() 162 | } 163 | 164 | var connCopyPool, _ = ants.NewPoolWithFunc(200000, copyConnGroup, ants.WithNonblocking(false)) 165 | var CopyConnsPool, _ = ants.NewPoolWithFunc(100000, copyConns, ants.WithNonblocking(false)) 166 | -------------------------------------------------------------------------------- /lib/nps_mux/map.go: -------------------------------------------------------------------------------- 1 | package nps_mux 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type connMap struct { 8 | cMap map[int32]*conn 9 | //closeCh chan struct{} 10 | sync.RWMutex 11 | } 12 | 13 | func NewConnMap() *connMap { 14 | cMap := &connMap{ 15 | cMap: make(map[int32]*conn), 16 | } 17 | return cMap 18 | } 19 | 20 | func (s *connMap) Size() (n int) { 21 | s.RLock() 22 | n = len(s.cMap) 23 | s.RUnlock() 24 | return 25 | } 26 | 27 | func (s *connMap) Get(id int32) (*conn, bool) { 28 | s.RLock() 29 | v, ok := s.cMap[id] 30 | s.RUnlock() 31 | if ok && v != nil { 32 | return v, true 33 | } 34 | return nil, false 35 | } 36 | 37 | func (s *connMap) Set(id int32, v *conn) { 38 | s.Lock() 39 | s.cMap[id] = v 40 | s.Unlock() 41 | } 42 | 43 | func (s *connMap) Close() { 44 | for _, v := range s.cMap { 45 | _ = v.Close() // close all the connections in the mux 46 | } 47 | } 48 | 49 | func (s *connMap) Delete(id int32) { 50 | s.Lock() 51 | delete(s.cMap, id) 52 | s.Unlock() 53 | } 54 | -------------------------------------------------------------------------------- /lib/nps_mux/netpackager.go: -------------------------------------------------------------------------------- 1 | package nps_mux 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "github.com/astaxie/beego/logs" 7 | "io" 8 | ) 9 | 10 | type basePackager struct { 11 | buf []byte 12 | // buf contain the mux protocol struct binary data, we copy data to buf firstly. 13 | // replace binary.Read/Write method, it may use reflect shows slowly. 14 | // also reduce conn.Read/Write calls which use syscall. 15 | // due to our test, conn.Write method reduce by two-thirds CPU times, 16 | // conn.Write method has 20% reduction of the CPU times, 17 | // totally provides more than twice of the CPU performance improvement. 18 | length uint16 19 | content []byte 20 | } 21 | 22 | func (Self *basePackager) Set(content []byte) (err error) { 23 | Self.reset() 24 | if content != nil { 25 | n := len(content) 26 | //fmt.Println(content) 27 | if n == 0 { 28 | // 长度为0的包,不应该向上抛,不然客户端会EOF,这里暂时没解决空包的问题 TODO 29 | //logs.Error("mux:packer: newpack content is zero length") 30 | //err = errors.New("mux:packer: newpack content is zero length") 31 | } 32 | if n > maximumSegmentSize { 33 | logs.Error("mux:packer: newpack content segment too large") 34 | //err = errors.New("mux:packer: newpack content segment too large") 35 | return 36 | } 37 | Self.content = Self.content[:n] 38 | copy(Self.content, content) 39 | } else { 40 | logs.Error("mux:packer: newpack content is nil") 41 | return 42 | //panic("mux:packer: newpack content is nil") 43 | //err = errors.New("mux:packer: newpack content is nil") 44 | } 45 | Self.setLength() 46 | return 47 | } 48 | 49 | func (Self *basePackager) Pack(writer io.Writer) (err error) { 50 | binary.LittleEndian.PutUint16(Self.buf[5:7], Self.length) 51 | _, err = writer.Write(Self.buf[:7]) 52 | if err != nil { 53 | return 54 | } 55 | _, err = writer.Write(Self.content[:Self.length]) 56 | return 57 | } 58 | 59 | func (Self *basePackager) UnPack(reader io.Reader) (n uint16, err error) { 60 | Self.reset() 61 | l, err := io.ReadFull(reader, Self.buf[5:7]) 62 | if err != nil { 63 | return 64 | } 65 | n += uint16(l) 66 | Self.length = binary.LittleEndian.Uint16(Self.buf[5:7]) 67 | if int(Self.length) > cap(Self.content) { 68 | err = errors.New("mux:packer: unpack err, content length too large") 69 | return 70 | } 71 | if Self.length > maximumSegmentSize { 72 | err = errors.New("mux:packer: unpack content segment too large") 73 | return 74 | } 75 | Self.content = Self.content[:int(Self.length)] 76 | l, err = io.ReadFull(reader, Self.content) 77 | n += uint16(l) 78 | return 79 | } 80 | 81 | func (Self *basePackager) setLength() { 82 | Self.length = uint16(len(Self.content)) 83 | return 84 | } 85 | 86 | func (Self *basePackager) reset() { 87 | Self.length = 0 88 | Self.content = Self.content[:0] // reset length 89 | } 90 | 91 | type muxPackager struct { 92 | flag uint8 93 | id int32 94 | window uint64 95 | basePackager 96 | } 97 | 98 | func (Self *muxPackager) Set(flag uint8, id int32, content interface{}) (err error) { 99 | Self.buf = windowBuff.Get() 100 | Self.flag = flag 101 | Self.id = id 102 | switch flag { 103 | case muxPingFlag, muxPingReturn, muxNewMsg, muxNewMsgPart: 104 | Self.content = windowBuff.Get() 105 | if content != nil { 106 | err = Self.basePackager.Set(content.([]byte)) 107 | } 108 | case muxMsgSendOk: 109 | // MUX_MSG_SEND_OK contains one data 110 | Self.window = content.(uint64) 111 | } 112 | return 113 | } 114 | 115 | func (Self *muxPackager) Pack(writer io.Writer) (err error) { 116 | Self.buf = Self.buf[0:13] 117 | Self.buf[0] = byte(Self.flag) 118 | binary.LittleEndian.PutUint32(Self.buf[1:5], uint32(Self.id)) 119 | switch Self.flag { 120 | case muxNewMsg, muxNewMsgPart, muxPingFlag, muxPingReturn: 121 | err = Self.basePackager.Pack(writer) 122 | windowBuff.Put(Self.content) 123 | case muxMsgSendOk: 124 | binary.LittleEndian.PutUint64(Self.buf[5:13], Self.window) 125 | _, err = writer.Write(Self.buf[:13]) 126 | default: 127 | _, err = writer.Write(Self.buf[:5]) 128 | } 129 | windowBuff.Put(Self.buf) 130 | return 131 | } 132 | 133 | func (Self *muxPackager) UnPack(reader io.Reader) (n uint16, err error) { 134 | Self.buf = windowBuff.Get() 135 | Self.buf = Self.buf[0:13] 136 | l, err := io.ReadFull(reader, Self.buf[:5]) 137 | if err != nil { 138 | return 139 | } 140 | n += uint16(l) 141 | Self.flag = uint8(Self.buf[0]) 142 | Self.id = int32(binary.LittleEndian.Uint32(Self.buf[1:5])) 143 | switch Self.flag { 144 | case muxNewMsg, muxNewMsgPart, muxPingFlag, muxPingReturn: 145 | var m uint16 146 | Self.content = windowBuff.Get() // need Get a window buf from pool 147 | m, err = Self.basePackager.UnPack(reader) 148 | n += m 149 | case muxMsgSendOk: 150 | l, err = io.ReadFull(reader, Self.buf[5:13]) 151 | Self.window = binary.LittleEndian.Uint64(Self.buf[5:13]) 152 | n += uint16(l) // uint64 153 | } 154 | windowBuff.Put(Self.buf) 155 | return 156 | } 157 | 158 | func (Self *muxPackager) reset() { 159 | Self.id = 0 160 | Self.flag = 0 161 | Self.length = 0 162 | Self.content = nil 163 | Self.window = 0 164 | Self.buf = nil 165 | } 166 | -------------------------------------------------------------------------------- /lib/nps_mux/pool.go: -------------------------------------------------------------------------------- 1 | package nps_mux 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | const ( 8 | poolSizeBuffer = 4096 9 | //poolSizeBuffer = 4096 * 10 // a mux packager total length 10 | poolSizeWindow = poolSizeBuffer - 2 - 4 - 4 - 1 // content length 11 | ) 12 | 13 | type windowBufferPool struct { 14 | pool sync.Pool 15 | } 16 | 17 | func newWindowBufferPool() *windowBufferPool { 18 | return &windowBufferPool{ 19 | pool: sync.Pool{ 20 | New: func() interface{} { 21 | return make([]byte, poolSizeWindow, poolSizeWindow) 22 | }, 23 | }, 24 | } 25 | } 26 | 27 | //func trace(buf []byte, ty string) { 28 | // pc := make([]uintptr, 10) // at least 1 entry needed 29 | // n := runtime.Callers(0, pc) 30 | // for i := 0; i < n; i++ { 31 | // f := runtime.FuncForPC(pc[i]) 32 | // file, line := f.FileLine(pc[i]) 33 | // log.Printf("%v %p %s:%d %s\n", ty, buf, file, line, f.Name()) 34 | // } 35 | //} 36 | 37 | func (Self *windowBufferPool) Get() (buf []byte) { 38 | buf = Self.pool.Get().([]byte) 39 | //trace(buf, "get") 40 | return buf[:poolSizeWindow] 41 | } 42 | 43 | func (Self *windowBufferPool) Put(x []byte) { 44 | //trace(x, "put") 45 | Self.pool.Put(x[:poolSizeWindow]) // make buf to full 46 | } 47 | 48 | type muxPackagerPool struct { 49 | pool sync.Pool 50 | } 51 | 52 | func newMuxPackagerPool() *muxPackagerPool { 53 | return &muxPackagerPool{ 54 | pool: sync.Pool{ 55 | New: func() interface{} { 56 | pack := muxPackager{} 57 | return &pack 58 | }, 59 | }, 60 | } 61 | } 62 | 63 | func (Self *muxPackagerPool) Get() *muxPackager { 64 | return Self.pool.Get().(*muxPackager) 65 | } 66 | 67 | func (Self *muxPackagerPool) Put(pack *muxPackager) { 68 | pack.reset() 69 | Self.pool.Put(pack) 70 | } 71 | 72 | type listElementPool struct { 73 | pool sync.Pool 74 | } 75 | 76 | func newListElementPool() *listElementPool { 77 | return &listElementPool{ 78 | pool: sync.Pool{ 79 | New: func() interface{} { 80 | element := listElement{} 81 | return &element 82 | }, 83 | }, 84 | } 85 | } 86 | 87 | func (Self *listElementPool) Get() *listElement { 88 | return Self.pool.Get().(*listElement) 89 | } 90 | 91 | func (Self *listElementPool) Put(element *listElement) { 92 | element.Reset() 93 | Self.pool.Put(element) 94 | } 95 | 96 | var ( 97 | muxPack = newMuxPackagerPool() 98 | windowBuff = newWindowBufferPool() 99 | listEle = newListElementPool() 100 | ) 101 | -------------------------------------------------------------------------------- /lib/nps_mux/rate.go: -------------------------------------------------------------------------------- 1 | package nps_mux 2 | 3 | import ( 4 | "net" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | type Rate struct { 10 | bucketSize int64 11 | bucketSurplusSize int64 12 | bucketAddSize int64 13 | stopChan chan bool 14 | NowRate int64 15 | } 16 | 17 | func NewRate(addSize int64) *Rate { 18 | return &Rate{ 19 | bucketSize: addSize * 2, 20 | bucketSurplusSize: 0, 21 | bucketAddSize: addSize, 22 | stopChan: make(chan bool), 23 | } 24 | } 25 | 26 | func (s *Rate) Start() { 27 | go s.session() 28 | } 29 | 30 | func (s *Rate) add(size int64) { 31 | if res := s.bucketSize - s.bucketSurplusSize; res < s.bucketAddSize { 32 | atomic.AddInt64(&s.bucketSurplusSize, res) 33 | return 34 | } 35 | atomic.AddInt64(&s.bucketSurplusSize, size) 36 | } 37 | 38 | // 回桶 39 | func (s *Rate) ReturnBucket(size int64) { 40 | s.add(size) 41 | } 42 | 43 | // 停止 44 | func (s *Rate) Stop() { 45 | s.stopChan <- true 46 | } 47 | 48 | func (s *Rate) Get(size int64) { 49 | if s.bucketSurplusSize >= size { 50 | atomic.AddInt64(&s.bucketSurplusSize, -size) 51 | return 52 | } 53 | ticker := time.NewTicker(time.Millisecond * 100) 54 | for { 55 | select { 56 | case <-ticker.C: 57 | if s.bucketSurplusSize >= size { 58 | atomic.AddInt64(&s.bucketSurplusSize, -size) 59 | ticker.Stop() 60 | return 61 | } 62 | } 63 | } 64 | } 65 | 66 | func (s *Rate) session() { 67 | ticker := time.NewTicker(time.Second * 1) 68 | for { 69 | select { 70 | case <-ticker.C: 71 | if rs := s.bucketAddSize - s.bucketSurplusSize; rs > 0 { 72 | s.NowRate = rs 73 | } else { 74 | s.NowRate = s.bucketSize - s.bucketSurplusSize 75 | } 76 | s.add(s.bucketAddSize) 77 | case <-s.stopChan: 78 | ticker.Stop() 79 | return 80 | } 81 | } 82 | } 83 | 84 | type Conn struct { 85 | conn net.Conn 86 | rate *Rate 87 | } 88 | 89 | func NewRateConn(rate *Rate, conn net.Conn) *Conn { 90 | return &Conn{ 91 | conn: conn, 92 | rate: rate, 93 | } 94 | } 95 | 96 | func (conn *Conn) Read(b []byte) (n int, err error) { 97 | defer func() { 98 | conn.rate.Get(int64(n)) 99 | }() 100 | return conn.conn.Read(b) 101 | } 102 | 103 | func (conn *Conn) Write(b []byte) (n int, err error) { 104 | defer func() { 105 | conn.rate.Get(int64(n)) 106 | }() 107 | return conn.conn.Write(b) 108 | } 109 | 110 | func (conn *Conn) LocalAddr() net.Addr { 111 | return conn.conn.LocalAddr() 112 | } 113 | 114 | func (conn *Conn) RemoteAddr() net.Addr { 115 | return conn.conn.RemoteAddr() 116 | } 117 | 118 | func (conn *Conn) SetDeadline(t time.Time) error { 119 | return conn.conn.SetDeadline(t) 120 | } 121 | 122 | func (conn *Conn) SetWriteDeadline(t time.Time) error { 123 | return conn.conn.SetWriteDeadline(t) 124 | } 125 | 126 | func (conn *Conn) SetReadDeadline(t time.Time) error { 127 | return conn.conn.SetReadDeadline(t) 128 | } 129 | 130 | func (conn *Conn) Close() error { 131 | return conn.conn.Close() 132 | } 133 | -------------------------------------------------------------------------------- /lib/nps_mux/sysGetsock_nowindows.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package nps_mux 5 | 6 | import ( 7 | "net" 8 | "os" 9 | "syscall" 10 | ) 11 | 12 | func sysGetSock(fd *os.File) (bufferSize int, err error) { 13 | if fd != nil { 14 | return syscall.GetsockoptInt(int(fd.Fd()), syscall.SOL_SOCKET, syscall.SO_RCVBUF) 15 | } else { 16 | return 5 * 1024 * 1024, nil 17 | } 18 | } 19 | 20 | func getConnFd(c net.Conn) (fd *os.File, err error) { 21 | switch c.(type) { 22 | case *net.TCPConn: 23 | fd, err = c.(*net.TCPConn).File() 24 | if err != nil { 25 | return 26 | } 27 | return 28 | case *net.UDPConn: 29 | fd, err = c.(*net.UDPConn).File() 30 | if err != nil { 31 | return 32 | } 33 | return 34 | default: 35 | return 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/nps_mux/sysGetsock_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package nps_mux 5 | 6 | import ( 7 | "net" 8 | "os" 9 | ) 10 | 11 | func sysGetSock(fd *os.File) (bufferSize int, err error) { 12 | // https://github.com/golang/sys/blob/master/windows/syscall_windows.go#L1184 13 | // not support, WTF??? 14 | // Todo 15 | // return syscall.GetsockoptInt((syscall.Handle)(unsafe.Pointer(fd.Fd())), syscall.SOL_SOCKET, syscall.SO_RCVBUF) 16 | bufferSize = 15 * 1024 * 1024 17 | return 18 | } 19 | 20 | func getConnFd(c net.Conn) (fd *os.File, err error) { 21 | switch c.(type) { 22 | case *net.TCPConn: 23 | //fd, err = c.(*net.TCPConn).File() 24 | //if err != nil { 25 | // return 26 | //} 27 | return 28 | case *net.UDPConn: 29 | //fd, err = c.(*net.UDPConn).File() 30 | //if err != nil { 31 | // return 32 | //} 33 | return 34 | default: 35 | return 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/pmux/pconn.go: -------------------------------------------------------------------------------- 1 | package pmux 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | type PortConn struct { 9 | Conn net.Conn 10 | rs []byte 11 | readMore bool 12 | start int 13 | } 14 | 15 | func newPortConn(conn net.Conn, rs []byte, readMore bool) *PortConn { 16 | return &PortConn{ 17 | Conn: conn, 18 | rs: rs, 19 | readMore: readMore, 20 | } 21 | } 22 | 23 | func (pConn *PortConn) Read(b []byte) (n int, err error) { 24 | if len(b) < len(pConn.rs)-pConn.start { 25 | defer func() { 26 | pConn.start = pConn.start + len(b) 27 | }() 28 | return copy(b, pConn.rs), nil 29 | } 30 | if pConn.start < len(pConn.rs) { 31 | defer func() { 32 | pConn.start = len(pConn.rs) 33 | }() 34 | n = copy(b, pConn.rs[pConn.start:]) 35 | if !pConn.readMore { 36 | return 37 | } 38 | } 39 | var n2 = 0 40 | n2, err = pConn.Conn.Read(b[n:]) 41 | n = n + n2 42 | return 43 | } 44 | 45 | func (pConn *PortConn) Write(b []byte) (n int, err error) { 46 | return pConn.Conn.Write(b) 47 | } 48 | 49 | func (pConn *PortConn) Close() error { 50 | return pConn.Conn.Close() 51 | } 52 | 53 | func (pConn *PortConn) LocalAddr() net.Addr { 54 | return pConn.Conn.LocalAddr() 55 | } 56 | 57 | func (pConn *PortConn) RemoteAddr() net.Addr { 58 | return pConn.Conn.RemoteAddr() 59 | } 60 | 61 | func (pConn *PortConn) SetDeadline(t time.Time) error { 62 | return pConn.Conn.SetDeadline(t) 63 | } 64 | 65 | func (pConn *PortConn) SetReadDeadline(t time.Time) error { 66 | return pConn.Conn.SetReadDeadline(t) 67 | } 68 | 69 | func (pConn *PortConn) SetWriteDeadline(t time.Time) error { 70 | return pConn.Conn.SetWriteDeadline(t) 71 | } 72 | -------------------------------------------------------------------------------- /lib/pmux/plistener.go: -------------------------------------------------------------------------------- 1 | package pmux 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | ) 7 | 8 | type PortListener struct { 9 | net.Listener 10 | connCh chan *PortConn 11 | addr net.Addr 12 | isClose bool 13 | } 14 | 15 | func NewPortListener(connCh chan *PortConn, addr net.Addr) *PortListener { 16 | return &PortListener{ 17 | connCh: connCh, 18 | addr: addr, 19 | } 20 | } 21 | 22 | func (pListener *PortListener) Accept() (net.Conn, error) { 23 | if pListener.isClose { 24 | return nil, errors.New("the listener has closed") 25 | } 26 | conn := <-pListener.connCh 27 | if conn != nil { 28 | return conn, nil 29 | } 30 | return nil, errors.New("the listener has closed") 31 | } 32 | 33 | func (pListener *PortListener) Close() error { 34 | //close 35 | if pListener.isClose { 36 | return errors.New("the listener has closed") 37 | } 38 | pListener.isClose = true 39 | return nil 40 | } 41 | 42 | func (pListener *PortListener) Addr() net.Addr { 43 | return pListener.addr 44 | } 45 | -------------------------------------------------------------------------------- /lib/pmux/pmux.go: -------------------------------------------------------------------------------- 1 | // This module is used for port reuse 2 | // Distinguish client, web manager , HTTP and HTTPS according to the difference of protocol 3 | package pmux 4 | 5 | import ( 6 | "bufio" 7 | "bytes" 8 | "io" 9 | "net" 10 | "os" 11 | "strconv" 12 | "strings" 13 | "time" 14 | 15 | "ehang.io/nps/lib/common" 16 | "github.com/astaxie/beego/logs" 17 | "github.com/pkg/errors" 18 | ) 19 | 20 | const ( 21 | HTTP_GET = 716984 22 | HTTP_POST = 807983 23 | HTTP_HEAD = 726965 24 | HTTP_PUT = 808585 25 | HTTP_DELETE = 686976 26 | HTTP_CONNECT = 677978 27 | HTTP_OPTIONS = 798084 28 | HTTP_TRACE = 848265 29 | CLIENT = 848384 30 | ACCEPT_TIME_OUT = 10 31 | ) 32 | 33 | type PortMux struct { 34 | net.Listener 35 | port int 36 | isClose bool 37 | managerHost string 38 | clientConn chan *PortConn 39 | httpConn chan *PortConn 40 | httpsConn chan *PortConn 41 | managerConn chan *PortConn 42 | } 43 | 44 | func NewPortMux(port int, managerHost string) *PortMux { 45 | pMux := &PortMux{ 46 | managerHost: managerHost, 47 | port: port, 48 | clientConn: make(chan *PortConn), 49 | httpConn: make(chan *PortConn), 50 | httpsConn: make(chan *PortConn), 51 | managerConn: make(chan *PortConn), 52 | } 53 | pMux.Start() 54 | return pMux 55 | } 56 | 57 | func (pMux *PortMux) Start() error { 58 | // Port multiplexing is based on TCP only 59 | tcpAddr, err := net.ResolveTCPAddr("tcp", "0.0.0.0:"+strconv.Itoa(pMux.port)) 60 | if err != nil { 61 | return err 62 | } 63 | pMux.Listener, err = net.ListenTCP("tcp", tcpAddr) 64 | if err != nil { 65 | logs.Error(err) 66 | os.Exit(0) 67 | } 68 | go func() { 69 | for { 70 | conn, err := pMux.Listener.Accept() 71 | if err != nil { 72 | logs.Warn(err) 73 | //close 74 | pMux.Close() 75 | } 76 | go pMux.process(conn) 77 | } 78 | }() 79 | return nil 80 | } 81 | 82 | func (pMux *PortMux) process(conn net.Conn) { 83 | // Recognition according to different signs 84 | // read 3 byte 85 | buf := make([]byte, 3) 86 | if n, err := io.ReadFull(conn, buf); err != nil || n != 3 { 87 | return 88 | } 89 | var ch chan *PortConn 90 | var rs []byte 91 | var buffer bytes.Buffer 92 | var readMore = false 93 | switch common.BytesToNum(buf) { 94 | case HTTP_CONNECT, HTTP_DELETE, HTTP_GET, HTTP_HEAD, HTTP_OPTIONS, HTTP_POST, HTTP_PUT, HTTP_TRACE: //http and manager 95 | buffer.Reset() 96 | r := bufio.NewReader(conn) 97 | buffer.Write(buf) 98 | for { 99 | b, _, err := r.ReadLine() 100 | if err != nil { 101 | logs.Warn("read line error", err.Error()) 102 | conn.Close() 103 | break 104 | } 105 | buffer.Write(b) 106 | buffer.Write([]byte("\r\n")) 107 | if strings.Index(string(b), "Host:") == 0 || strings.Index(string(b), "host:") == 0 { 108 | // Remove host and space effects 109 | str := strings.Replace(string(b), "Host:", "", -1) 110 | str = strings.Replace(str, "host:", "", -1) 111 | str = strings.TrimSpace(str) 112 | // Determine whether it is the same as the manager domain name 113 | if common.GetIpByAddr(str) == pMux.managerHost { 114 | ch = pMux.managerConn 115 | } else { 116 | ch = pMux.httpConn 117 | } 118 | b, _ := r.Peek(r.Buffered()) 119 | buffer.Write(b) 120 | rs = buffer.Bytes() 121 | break 122 | } 123 | } 124 | case CLIENT: // client connection 125 | ch = pMux.clientConn 126 | default: // https 127 | readMore = true 128 | ch = pMux.httpsConn 129 | } 130 | if len(rs) == 0 { 131 | rs = buf 132 | } 133 | timer := time.NewTimer(ACCEPT_TIME_OUT) 134 | select { 135 | case <-timer.C: 136 | case ch <- newPortConn(conn, rs, readMore): 137 | } 138 | } 139 | 140 | func (pMux *PortMux) Close() error { 141 | if pMux.isClose { 142 | return errors.New("the port pmux has closed") 143 | } 144 | pMux.isClose = true 145 | close(pMux.clientConn) 146 | close(pMux.httpsConn) 147 | close(pMux.httpConn) 148 | close(pMux.managerConn) 149 | return pMux.Listener.Close() 150 | } 151 | 152 | func (pMux *PortMux) GetClientListener() net.Listener { 153 | return NewPortListener(pMux.clientConn, pMux.Listener.Addr()) 154 | } 155 | 156 | func (pMux *PortMux) GetHttpListener() net.Listener { 157 | return NewPortListener(pMux.httpConn, pMux.Listener.Addr()) 158 | } 159 | 160 | func (pMux *PortMux) GetHttpsListener() net.Listener { 161 | return NewPortListener(pMux.httpsConn, pMux.Listener.Addr()) 162 | } 163 | 164 | func (pMux *PortMux) GetManagerListener() net.Listener { 165 | return NewPortListener(pMux.managerConn, pMux.Listener.Addr()) 166 | } 167 | -------------------------------------------------------------------------------- /lib/pmux/pmux_test.go: -------------------------------------------------------------------------------- 1 | package pmux 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/astaxie/beego/logs" 8 | ) 9 | 10 | func TestPortMux_Close(t *testing.T) { 11 | logs.Reset() 12 | logs.EnableFuncCallDepth(true) 13 | logs.SetLogFuncCallDepth(3) 14 | 15 | pMux := NewPortMux(8888, "Ds") 16 | go func() { 17 | if pMux.Start() != nil { 18 | logs.Warn("Error") 19 | } 20 | }() 21 | time.Sleep(time.Second * 3) 22 | go func() { 23 | l := pMux.GetHttpListener() 24 | conn, err := l.Accept() 25 | logs.Warn(conn, err) 26 | }() 27 | go func() { 28 | l := pMux.GetHttpListener() 29 | conn, err := l.Accept() 30 | logs.Warn(conn, err) 31 | }() 32 | go func() { 33 | l := pMux.GetHttpListener() 34 | conn, err := l.Accept() 35 | logs.Warn(conn, err) 36 | }() 37 | l := pMux.GetHttpListener() 38 | conn, err := l.Accept() 39 | logs.Warn(conn, err) 40 | } 41 | -------------------------------------------------------------------------------- /lib/rate/conn.go: -------------------------------------------------------------------------------- 1 | package rate 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type rateConn struct { 8 | conn io.ReadWriteCloser 9 | rate *Rate 10 | } 11 | 12 | func NewRateConn(conn io.ReadWriteCloser, rate *Rate) io.ReadWriteCloser { 13 | return &rateConn{ 14 | conn: conn, 15 | rate: rate, 16 | } 17 | } 18 | 19 | func (s *rateConn) Read(b []byte) (n int, err error) { 20 | n, err = s.conn.Read(b) 21 | if s.rate != nil { 22 | s.rate.Get(int64(n)) 23 | } 24 | return 25 | } 26 | 27 | func (s *rateConn) Write(b []byte) (n int, err error) { 28 | n, err = s.conn.Write(b) 29 | if s.rate != nil { 30 | s.rate.Get(int64(n)) 31 | } 32 | return 33 | } 34 | 35 | func (s *rateConn) Close() error { 36 | return s.conn.Close() 37 | } 38 | -------------------------------------------------------------------------------- /lib/rate/rate.go: -------------------------------------------------------------------------------- 1 | package rate 2 | 3 | import ( 4 | "sync/atomic" 5 | "time" 6 | ) 7 | 8 | type Rate struct { 9 | bucketSize int64 10 | bucketSurplusSize int64 11 | bucketAddSize int64 12 | stopChan chan bool 13 | NowRate int64 14 | } 15 | 16 | func NewRate(addSize int64) *Rate { 17 | return &Rate{ 18 | bucketSize: addSize * 2, 19 | bucketSurplusSize: 0, 20 | bucketAddSize: addSize, 21 | stopChan: make(chan bool), 22 | } 23 | } 24 | 25 | func (s *Rate) Start() { 26 | go s.session() 27 | } 28 | 29 | func (s *Rate) add(size int64) { 30 | if res := s.bucketSize - s.bucketSurplusSize; res < s.bucketAddSize { 31 | atomic.AddInt64(&s.bucketSurplusSize, res) 32 | return 33 | } 34 | atomic.AddInt64(&s.bucketSurplusSize, size) 35 | } 36 | 37 | // 回桶 38 | func (s *Rate) ReturnBucket(size int64) { 39 | s.add(size) 40 | } 41 | 42 | // 停止 43 | func (s *Rate) Stop() { 44 | s.stopChan <- true 45 | } 46 | 47 | func (s *Rate) Get(size int64) { 48 | if s.bucketSurplusSize >= size { 49 | atomic.AddInt64(&s.bucketSurplusSize, -size) 50 | return 51 | } 52 | ticker := time.NewTicker(time.Millisecond * 100) 53 | for { 54 | select { 55 | case <-ticker.C: 56 | if s.bucketSurplusSize >= size { 57 | atomic.AddInt64(&s.bucketSurplusSize, -size) 58 | ticker.Stop() 59 | return 60 | } 61 | } 62 | } 63 | } 64 | 65 | func (s *Rate) session() { 66 | ticker := time.NewTicker(time.Second * 1) 67 | for { 68 | select { 69 | case <-ticker.C: 70 | if rs := s.bucketAddSize - s.bucketSurplusSize; rs > 0 { 71 | s.NowRate = rs 72 | } else { 73 | s.NowRate = s.bucketSize - s.bucketSurplusSize 74 | } 75 | s.add(s.bucketAddSize) 76 | case <-s.stopChan: 77 | ticker.Stop() 78 | return 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/sheap/heap.go: -------------------------------------------------------------------------------- 1 | package sheap 2 | 3 | type IntHeap []int64 4 | 5 | func (h IntHeap) Len() int { return len(h) } 6 | func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } 7 | func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 8 | 9 | func (h *IntHeap) Push(x interface{}) { 10 | // Push and Pop use pointer receivers because they modify the slice's length, 11 | // not just its contents. 12 | *h = append(*h, x.(int64)) 13 | } 14 | 15 | func (h *IntHeap) Pop() interface{} { 16 | old := *h 17 | n := len(old) 18 | x := old[n-1] 19 | *h = old[0 : n-1] 20 | return x 21 | } 22 | -------------------------------------------------------------------------------- /lib/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | const VERSION = "0.26.21" 4 | 5 | // Compulsory minimum version, Minimum downward compatibility to this version 6 | func GetVersion() string { 7 | return "0.26.0" 8 | } 9 | -------------------------------------------------------------------------------- /port.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/port.png -------------------------------------------------------------------------------- /server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/server.png -------------------------------------------------------------------------------- /server/connection/connection.go: -------------------------------------------------------------------------------- 1 | package connection 2 | 3 | import ( 4 | "net" 5 | "os" 6 | "strconv" 7 | 8 | "ehang.io/nps/lib/pmux" 9 | "github.com/astaxie/beego" 10 | "github.com/astaxie/beego/logs" 11 | ) 12 | 13 | var pMux *pmux.PortMux 14 | var bridgePort string 15 | var httpsPort string 16 | var httpPort string 17 | var webPort string 18 | 19 | func InitConnectionService() { 20 | bridgePort = beego.AppConfig.String("bridge_port") 21 | httpsPort = beego.AppConfig.String("https_proxy_port") 22 | httpPort = beego.AppConfig.String("http_proxy_port") 23 | webPort = beego.AppConfig.String("web_port") 24 | 25 | if httpPort == bridgePort || httpsPort == bridgePort || webPort == bridgePort { 26 | port, err := strconv.Atoi(bridgePort) 27 | if err != nil { 28 | logs.Error(err) 29 | os.Exit(0) 30 | } 31 | pMux = pmux.NewPortMux(port, beego.AppConfig.String("web_host")) 32 | } 33 | } 34 | 35 | func GetBridgeListener(tp string) (net.Listener, error) { 36 | logs.Info("server start, the bridge type is %s, the bridge port is %s", tp, bridgePort) 37 | var p int 38 | var err error 39 | if p, err = strconv.Atoi(bridgePort); err != nil { 40 | return nil, err 41 | } 42 | if pMux != nil { 43 | return pMux.GetClientListener(), nil 44 | } 45 | return net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP(beego.AppConfig.String("bridge_ip")), p, ""}) 46 | } 47 | 48 | func GetHttpListener() (net.Listener, error) { 49 | if pMux != nil && httpPort == bridgePort { 50 | logs.Info("start http listener, port is", bridgePort) 51 | return pMux.GetHttpListener(), nil 52 | } 53 | logs.Info("start http listener, port is", httpPort) 54 | return getTcpListener(beego.AppConfig.String("http_proxy_ip"), httpPort) 55 | } 56 | 57 | func GetHttpsListener() (net.Listener, error) { 58 | if pMux != nil && httpsPort == bridgePort { 59 | logs.Info("start https listener, port is", bridgePort) 60 | return pMux.GetHttpsListener(), nil 61 | } 62 | logs.Info("start https listener, port is", httpsPort) 63 | return getTcpListener(beego.AppConfig.String("http_proxy_ip"), httpsPort) 64 | } 65 | 66 | func GetWebManagerListener() (net.Listener, error) { 67 | if pMux != nil && webPort == bridgePort { 68 | logs.Info("Web management start, access port is", bridgePort) 69 | return pMux.GetManagerListener(), nil 70 | } 71 | logs.Info("web management start, access port is", webPort) 72 | return getTcpListener(beego.AppConfig.String("web_ip"), webPort) 73 | } 74 | 75 | func getTcpListener(ip, p string) (net.Listener, error) { 76 | port, err := strconv.Atoi(p) 77 | if err != nil { 78 | logs.Error(err) 79 | os.Exit(0) 80 | } 81 | if ip == "" { 82 | ip = "0.0.0.0" 83 | } 84 | return net.ListenTCP("tcp", &net.TCPAddr{net.ParseIP(ip), port, ""}) 85 | } 86 | -------------------------------------------------------------------------------- /server/proxy/base.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "net/http" 7 | "sort" 8 | "sync" 9 | 10 | "ehang.io/nps/bridge" 11 | "ehang.io/nps/lib/common" 12 | "ehang.io/nps/lib/conn" 13 | "ehang.io/nps/lib/file" 14 | "github.com/astaxie/beego/logs" 15 | ) 16 | 17 | type Service interface { 18 | Start() error 19 | Close() error 20 | } 21 | 22 | type NetBridge interface { 23 | SendLinkInfo(clientId int, link *conn.Link, t *file.Tunnel) (target net.Conn, err error) 24 | } 25 | 26 | // BaseServer struct 27 | type BaseServer struct { 28 | id int 29 | bridge NetBridge 30 | task *file.Tunnel 31 | errorContent []byte 32 | sync.Mutex 33 | } 34 | 35 | func NewBaseServer(bridge *bridge.Bridge, task *file.Tunnel) *BaseServer { 36 | return &BaseServer{ 37 | bridge: bridge, 38 | task: task, 39 | errorContent: nil, 40 | Mutex: sync.Mutex{}, 41 | } 42 | } 43 | 44 | // add the flow 45 | func (s *BaseServer) FlowAdd(in, out int64) { 46 | s.Lock() 47 | defer s.Unlock() 48 | s.task.Flow.ExportFlow += out 49 | s.task.Flow.InletFlow += in 50 | } 51 | 52 | // change the flow 53 | func (s *BaseServer) FlowAddHost(host *file.Host, in, out int64) { 54 | s.Lock() 55 | defer s.Unlock() 56 | host.Flow.ExportFlow += out 57 | host.Flow.InletFlow += in 58 | } 59 | 60 | // write fail bytes to the connection 61 | func (s *BaseServer) writeConnFail(c net.Conn) { 62 | c.Write([]byte(common.ConnectionFailBytes)) 63 | c.Write(s.errorContent) 64 | } 65 | 66 | // auth check 67 | func (s *BaseServer) auth(r *http.Request, c *conn.Conn, u, p string) error { 68 | if u != "" && p != "" && !common.CheckAuth(r, u, p) { 69 | c.Write([]byte(common.UnauthorizedBytes)) 70 | c.Close() 71 | return errors.New("401 Unauthorized") 72 | } 73 | return nil 74 | } 75 | 76 | // check flow limit of the client ,and decrease the allow num of client 77 | func (s *BaseServer) CheckFlowAndConnNum(client *file.Client) error { 78 | if client.Flow.FlowLimit > 0 && (client.Flow.FlowLimit<<20) < (client.Flow.ExportFlow+client.Flow.InletFlow) { 79 | return errors.New("Traffic exceeded") 80 | } 81 | if !client.GetConn() { 82 | return errors.New("Connections exceed the current client limit") 83 | } 84 | return nil 85 | } 86 | 87 | func (s *BaseServer) CheckFlowAndConnNumByPort(portConfig *file.PortConfig, client *file.Client) error { 88 | //判断是否到期 89 | if portConfig.ExpireTime != "" && common.IsExpired(portConfig.ExpireTime) { 90 | return errors.New("Port expired") 91 | } 92 | if portConfig.FlowLimit > 0 && (portConfig.FlowLimit<<20) < (client.Flow.ExportFlow+client.Flow.InletFlow) { 93 | return errors.New("Traffic exceeded") 94 | } 95 | if !portConfig.GetConn() { 96 | return errors.New("Connections exceed the current client limit") 97 | } 98 | return nil 99 | } 100 | 101 | func in(target string, str_array []string) bool { 102 | sort.Strings(str_array) 103 | index := sort.SearchStrings(str_array, target) 104 | if index < len(str_array) && str_array[index] == target { 105 | return true 106 | } 107 | return false 108 | } 109 | 110 | // create a new connection and start bytes copying 111 | func (s *BaseServer) DealClient(c *conn.Conn, client *file.Client, addr string, 112 | rb []byte, tp string, f func(), flow *file.Flow, localProxy bool, task *file.Tunnel) error { 113 | 114 | // 判断访问地址是否在全局黑名单内 115 | if IsGlobalBlackIp(c.RemoteAddr().String()) { 116 | c.Close() 117 | return nil 118 | } 119 | 120 | // 判断访问地址是否在黑名单内 121 | if common.IsBlackIp(c.RemoteAddr().String(), client.VerifyKey, client.BlackIpList) { 122 | c.Close() 123 | return nil 124 | } 125 | 126 | link := conn.NewLink(tp, addr, client.Cnf.Crypt, client.Cnf.Compress, c.Conn.RemoteAddr().String(), localProxy) 127 | if target, err := s.bridge.SendLinkInfo(client.Id, link, s.task); err != nil { 128 | logs.Warn("get connection from client id %d error %s", client.Id, err.Error()) 129 | c.Close() 130 | return err 131 | } else { 132 | if f != nil { 133 | f() 134 | } 135 | conn.CopyWaitGroup(target, c.Conn, link.Crypt, link.Compress, client.Rate, flow, true, rb, task) 136 | } 137 | return nil 138 | } 139 | 140 | // 判断访问地址是否在全局黑名单内 141 | func IsGlobalBlackIp(ipPort string) bool { 142 | // 判断访问地址是否在全局黑名单内 143 | global := file.GetDb().GetGlobal() 144 | if global != nil { 145 | ip := common.GetIpByAddr(ipPort) 146 | if in(ip, global.BlackIpList) { 147 | logs.Error("IP地址[" + ip + "]在全局黑名单列表内") 148 | return true 149 | } 150 | } 151 | 152 | return false 153 | } 154 | -------------------------------------------------------------------------------- /server/proxy/p2p.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | "time" 7 | 8 | "ehang.io/nps/lib/common" 9 | "github.com/astaxie/beego/logs" 10 | ) 11 | 12 | type P2PServer struct { 13 | BaseServer 14 | p2pPort int 15 | p2p map[string]*p2p 16 | listener *net.UDPConn 17 | } 18 | 19 | type p2p struct { 20 | visitorAddr *net.UDPAddr 21 | providerAddr *net.UDPAddr 22 | } 23 | 24 | func NewP2PServer(p2pPort int) *P2PServer { 25 | return &P2PServer{ 26 | p2pPort: p2pPort, 27 | p2p: make(map[string]*p2p), 28 | } 29 | } 30 | 31 | func (s *P2PServer) Start() error { 32 | logs.Info("start p2p server port", s.p2pPort) 33 | var err error 34 | s.listener, err = net.ListenUDP("udp", &net.UDPAddr{net.ParseIP("0.0.0.0"), s.p2pPort, ""}) 35 | if err != nil { 36 | return err 37 | } 38 | for { 39 | buf := common.BufPoolUdp.Get().([]byte) 40 | n, addr, err := s.listener.ReadFromUDP(buf) 41 | if err != nil { 42 | if strings.Contains(err.Error(), "use of closed network connection") { 43 | break 44 | } 45 | continue 46 | } 47 | go s.handleP2P(addr, string(buf[:n])) 48 | } 49 | return nil 50 | } 51 | 52 | func (s *P2PServer) handleP2P(addr *net.UDPAddr, str string) { 53 | var ( 54 | v *p2p 55 | ok bool 56 | ) 57 | arr := strings.Split(str, common.CONN_DATA_SEQ) 58 | if len(arr) < 2 { 59 | return 60 | } 61 | if v, ok = s.p2p[arr[0]]; !ok { 62 | v = new(p2p) 63 | s.p2p[arr[0]] = v 64 | } 65 | logs.Trace("new p2p connection ,role %s , password %s ,local address %s", arr[1], arr[0], addr.String()) 66 | if arr[1] == common.WORK_P2P_VISITOR { 67 | v.visitorAddr = addr 68 | for i := 20; i > 0; i-- { 69 | if v.providerAddr != nil { 70 | s.listener.WriteTo([]byte(v.providerAddr.String()), v.visitorAddr) 71 | s.listener.WriteTo([]byte(v.visitorAddr.String()), v.providerAddr) 72 | break 73 | } 74 | time.Sleep(time.Second) 75 | } 76 | delete(s.p2p, arr[0]) 77 | } else { 78 | v.providerAddr = addr 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /server/proxy/tcp.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "net/http" 7 | "path/filepath" 8 | "strconv" 9 | 10 | "ehang.io/nps/bridge" 11 | "ehang.io/nps/lib/common" 12 | "ehang.io/nps/lib/conn" 13 | "ehang.io/nps/lib/file" 14 | "ehang.io/nps/server/connection" 15 | "github.com/astaxie/beego" 16 | "github.com/astaxie/beego/logs" 17 | ) 18 | 19 | type TunnelModeServer struct { 20 | BaseServer 21 | process process 22 | listener net.Listener 23 | } 24 | 25 | // tcp|http|host 26 | func NewTunnelModeServer(process process, bridge NetBridge, task *file.Tunnel) *TunnelModeServer { 27 | s := new(TunnelModeServer) 28 | s.bridge = bridge 29 | s.process = process 30 | s.task = task 31 | return s 32 | } 33 | 34 | // 开始 35 | func (s *TunnelModeServer) Start() error { 36 | return conn.NewTcpListenerAndProcess(s.task.ServerIp+":"+strconv.Itoa(s.task.Port), func(c net.Conn) { 37 | if err := s.CheckFlowAndConnNum(s.task.Client); err != nil { 38 | logs.Warn("client id %d, task id %d,error %s, when tcp connection", s.task.Client.Id, s.task.Id, err.Error()) 39 | c.Close() 40 | return 41 | } 42 | logs.Trace("new tcp connection,local port %d,client %d,remote address %s", s.task.Port, s.task.Client.Id, c.RemoteAddr()) 43 | s.process(conn.NewConn(c), s) 44 | s.task.Client.AddConn() 45 | }, &s.listener) 46 | } 47 | 48 | // close 49 | func (s *TunnelModeServer) Close() error { 50 | return s.listener.Close() 51 | } 52 | 53 | // web管理方式 54 | type WebServer struct { 55 | BaseServer 56 | } 57 | 58 | // 开始 59 | func (s *WebServer) Start() error { 60 | p, _ := beego.AppConfig.Int("web_port") 61 | if p == 0 { 62 | stop := make(chan struct{}) 63 | <-stop 64 | } 65 | beego.BConfig.WebConfig.Session.SessionOn = true 66 | beego.SetStaticPath(beego.AppConfig.String("web_base_url")+"/static", filepath.Join(common.GetRunPath(), "web", "static")) 67 | beego.SetViewsPath(filepath.Join(common.GetRunPath(), "web", "views")) 68 | err := errors.New("Web management startup failure ") 69 | var l net.Listener 70 | if l, err = connection.GetWebManagerListener(); err == nil { 71 | beego.InitBeforeHTTPRun() 72 | if beego.AppConfig.String("web_open_ssl") == "true" { 73 | keyPath := beego.AppConfig.String("web_key_file") 74 | certPath := beego.AppConfig.String("web_cert_file") 75 | err = http.ServeTLS(l, beego.BeeApp.Handlers, certPath, keyPath) 76 | } else { 77 | err = http.Serve(l, beego.BeeApp.Handlers) 78 | } 79 | } else { 80 | logs.Error(err) 81 | } 82 | return err 83 | } 84 | 85 | func (s *WebServer) Close() error { 86 | return nil 87 | } 88 | 89 | // new 90 | func NewWebServer(bridge *bridge.Bridge) *WebServer { 91 | s := new(WebServer) 92 | s.bridge = bridge 93 | return s 94 | } 95 | 96 | type process func(c *conn.Conn, s *TunnelModeServer) error 97 | 98 | // tcp proxy 99 | func ProcessTunnel(c *conn.Conn, s *TunnelModeServer) error { 100 | targetAddr, err := s.task.Target.GetRandomTarget() 101 | if err != nil { 102 | c.Close() 103 | logs.Warn("tcp port %d ,client id %d,task id %d connect error %s", s.task.Port, s.task.Client.Id, s.task.Id, err.Error()) 104 | return err 105 | } 106 | 107 | return s.DealClient(c, s.task.Client, targetAddr, nil, common.CONN_TCP, nil, s.task.Client.Flow, s.task.Target.LocalProxy, s.task) 108 | } 109 | 110 | // http proxy 111 | func ProcessHttp(c *conn.Conn, s *TunnelModeServer) error { 112 | 113 | _, addr, rb, err, r := c.GetHost() 114 | if err != nil { 115 | c.Close() 116 | logs.Info(err) 117 | return err 118 | } 119 | if r.Method == "CONNECT" { 120 | c.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n")) 121 | rb = nil 122 | } 123 | if err := s.auth(r, c, s.task.Client.Cnf.U, s.task.Client.Cnf.P); err != nil { 124 | return err 125 | } 126 | return s.DealClient(c, s.task.Client, addr, rb, common.CONN_TCP, nil, s.task.Client.Flow, s.task.Target.LocalProxy, nil) 127 | 128 | } 129 | -------------------------------------------------------------------------------- /server/proxy/transport.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package proxy 5 | 6 | import ( 7 | "net" 8 | "strconv" 9 | "syscall" 10 | 11 | "ehang.io/nps/lib/common" 12 | "ehang.io/nps/lib/conn" 13 | ) 14 | 15 | func HandleTrans(c *conn.Conn, s *TunnelModeServer) error { 16 | if addr, err := getAddress(c.Conn); err != nil { 17 | return err 18 | } else { 19 | return s.DealClient(c, s.task.Client, addr, nil, common.CONN_TCP, nil, s.task.Flow, s.task.Target.LocalProxy, nil) 20 | } 21 | } 22 | 23 | const SO_ORIGINAL_DST = 80 24 | 25 | func getAddress(conn net.Conn) (string, error) { 26 | sysrawconn, f := conn.(syscall.Conn) 27 | if !f { 28 | return "", nil 29 | } 30 | rawConn, err := sysrawconn.SyscallConn() 31 | if err != nil { 32 | return "", nil 33 | } 34 | var ip string 35 | var port uint16 36 | err = rawConn.Control(func(fd uintptr) { 37 | addr, err := syscall.GetsockoptIPv6Mreq(int(fd), syscall.IPPROTO_IP, SO_ORIGINAL_DST) 38 | if err != nil { 39 | return 40 | } 41 | ip = net.IP(addr.Multiaddr[4:8]).String() 42 | port = uint16(addr.Multiaddr[2])<<8 + uint16(addr.Multiaddr[3]) 43 | }) 44 | return ip + ":" + strconv.Itoa(int(port)), nil 45 | } 46 | -------------------------------------------------------------------------------- /server/proxy/transport_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package proxy 5 | 6 | import ( 7 | "ehang.io/nps/lib/conn" 8 | ) 9 | 10 | func HandleTrans(c *conn.Conn, s *TunnelModeServer) error { 11 | return nil 12 | } 13 | -------------------------------------------------------------------------------- /server/proxy/udp.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | "ehang.io/nps/bridge" 11 | "ehang.io/nps/lib/common" 12 | "ehang.io/nps/lib/conn" 13 | "ehang.io/nps/lib/file" 14 | "github.com/astaxie/beego/logs" 15 | ) 16 | 17 | type UdpModeServer struct { 18 | BaseServer 19 | addrMap sync.Map 20 | listener *net.UDPConn 21 | } 22 | 23 | func NewUdpModeServer(bridge *bridge.Bridge, task *file.Tunnel) *UdpModeServer { 24 | s := new(UdpModeServer) 25 | s.bridge = bridge 26 | s.task = task 27 | return s 28 | } 29 | 30 | // 开始 31 | func (s *UdpModeServer) Start() error { 32 | var err error 33 | if s.task.ServerIp == "" { 34 | s.task.ServerIp = "0.0.0.0" 35 | } 36 | s.listener, err = net.ListenUDP("udp", &net.UDPAddr{net.ParseIP(s.task.ServerIp), s.task.Port, ""}) 37 | if err != nil { 38 | return err 39 | } 40 | for { 41 | buf := common.BufPoolUdp.Get().([]byte) 42 | n, addr, err := s.listener.ReadFromUDP(buf) 43 | if err != nil { 44 | if strings.Contains(err.Error(), "use of closed network connection") { 45 | break 46 | } 47 | continue 48 | } 49 | 50 | // 判断访问地址是否在全局黑名单内 51 | if IsGlobalBlackIp(addr.String()) { 52 | break 53 | } 54 | 55 | // 判断访问地址是否在黑名单内 56 | if common.IsBlackIp(addr.String(), s.task.Client.VerifyKey, s.task.Client.BlackIpList) { 57 | break 58 | } 59 | 60 | logs.Trace("New udp connection,client %d,remote address %s", s.task.Client.Id, addr) 61 | go s.process(addr, buf[:n]) 62 | } 63 | return nil 64 | } 65 | 66 | func (s *UdpModeServer) process(addr *net.UDPAddr, data []byte) { 67 | if v, ok := s.addrMap.Load(addr.String()); ok { 68 | clientConn, ok := v.(io.ReadWriteCloser) 69 | if ok { 70 | _, err := clientConn.Write(data) 71 | if err != nil { 72 | logs.Warn(err) 73 | return 74 | } 75 | s.task.Client.Flow.Add(int64(len(data)), int64(len(data))) 76 | } 77 | } else { 78 | if err := s.CheckFlowAndConnNum(s.task.Client); err != nil { 79 | logs.Warn("client id %d, task id %d,error %s, when udp connection", s.task.Client.Id, s.task.Id, err.Error()) 80 | return 81 | } 82 | defer s.task.Client.AddConn() 83 | link := conn.NewLink(common.CONN_UDP, s.task.Target.TargetStr, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, addr.String(), s.task.Target.LocalProxy) 84 | if clientConn, err := s.bridge.SendLinkInfo(s.task.Client.Id, link, s.task); err != nil { 85 | return 86 | } else { 87 | target := conn.GetConn(clientConn, s.task.Client.Cnf.Crypt, s.task.Client.Cnf.Compress, nil, true) 88 | s.addrMap.Store(addr.String(), target) 89 | defer target.Close() 90 | 91 | _, err := target.Write(data) 92 | if err != nil { 93 | logs.Warn(err) 94 | return 95 | } 96 | 97 | buf := common.BufPoolUdp.Get().([]byte) 98 | defer common.BufPoolUdp.Put(buf) 99 | 100 | s.task.Client.Flow.Add(int64(len(data)), int64(len(data))) 101 | for { 102 | clientConn.SetReadDeadline(time.Now().Add(time.Duration(60) * time.Second)) 103 | if n, err := target.Read(buf); err != nil { 104 | s.addrMap.Delete(addr.String()) 105 | logs.Warn(err) 106 | return 107 | } else { 108 | _, err := s.listener.WriteTo(buf[:n], addr) 109 | if err != nil { 110 | logs.Warn(err) 111 | return 112 | } 113 | s.task.Client.Flow.Add(int64(n), int64(n)) 114 | } 115 | //if err := s.CheckFlowAndConnNum(s.task.Client); err != nil { 116 | // logs.Warn("client id %d, task id %d,error %s, when udp connection", s.task.Client.Id, s.task.Id, err.Error()) 117 | // return 118 | //} 119 | } 120 | } 121 | } 122 | } 123 | 124 | func (s *UdpModeServer) Close() error { 125 | return s.listener.Close() 126 | } 127 | -------------------------------------------------------------------------------- /server/test/test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "log" 5 | "path/filepath" 6 | "strconv" 7 | 8 | "ehang.io/nps/lib/common" 9 | "ehang.io/nps/lib/file" 10 | "github.com/astaxie/beego" 11 | ) 12 | 13 | func TestServerConfig() { 14 | var postTcpArr []int 15 | var postUdpArr []int 16 | file.GetDb().JsonDb.Tasks.Range(func(key, value interface{}) bool { 17 | v := value.(*file.Tunnel) 18 | if v.Mode == "udp" { 19 | isInArr(&postUdpArr, v.Port, v.Remark, "udp") 20 | } else if v.Port != 0 { 21 | 22 | isInArr(&postTcpArr, v.Port, v.Remark, "tcp") 23 | } 24 | return true 25 | }) 26 | p, err := beego.AppConfig.Int("web_port") 27 | if err != nil { 28 | log.Fatalln("Getting web management port error :", err) 29 | } else { 30 | isInArr(&postTcpArr, p, "Web Management port", "tcp") 31 | } 32 | 33 | if p := beego.AppConfig.String("bridge_port"); p != "" { 34 | if port, err := strconv.Atoi(p); err != nil { 35 | log.Fatalln("get Server and client communication portserror:", err) 36 | } else if beego.AppConfig.String("bridge_type") == "kcp" { 37 | isInArr(&postUdpArr, port, "Server and client communication ports", "udp") 38 | } else { 39 | isInArr(&postTcpArr, port, "Server and client communication ports", "tcp") 40 | } 41 | } 42 | 43 | if p := beego.AppConfig.String("httpProxyPort"); p != "" { 44 | if port, err := strconv.Atoi(p); err != nil { 45 | log.Fatalln("get http port error:", err) 46 | } else { 47 | isInArr(&postTcpArr, port, "https port", "tcp") 48 | } 49 | } 50 | if p := beego.AppConfig.String("https_proxy_port"); p != "" { 51 | if b, err := beego.AppConfig.Bool("https_just_proxy"); !(err == nil && b) { 52 | if port, err := strconv.Atoi(p); err != nil { 53 | log.Fatalln("get https port error", err) 54 | } else { 55 | if beego.AppConfig.String("pemPath") != "" && !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("pemPath"))) { 56 | log.Fatalf("ssl certFile %s is not exist", beego.AppConfig.String("pemPath")) 57 | } 58 | if beego.AppConfig.String("keyPath") != "" && !common.FileExists(filepath.Join(common.GetRunPath(), beego.AppConfig.String("keyPath"))) { 59 | log.Fatalf("ssl keyFile %s is not exist", beego.AppConfig.String("pemPath")) 60 | } 61 | isInArr(&postTcpArr, port, "http port", "tcp") 62 | } 63 | } 64 | } 65 | } 66 | 67 | func isInArr(arr *[]int, val int, remark string, tp string) { 68 | for _, v := range *arr { 69 | if v == val { 70 | log.Fatalf("the port %d is reused,remark: %s", val, remark) 71 | } 72 | } 73 | if tp == "tcp" { 74 | if !common.TestTcpPort(val) { 75 | log.Fatalf("open the %d port error ,remark: %s", val, remark) 76 | } 77 | } else { 78 | if !common.TestUdpPort(val) { 79 | log.Fatalf("open the %d port error ,remark: %s", val, remark) 80 | } 81 | } 82 | *arr = append(*arr, val) 83 | return 84 | } 85 | -------------------------------------------------------------------------------- /server/tool/utils.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "strconv" 7 | "time" 8 | 9 | "ehang.io/nps/lib/common" 10 | "github.com/astaxie/beego" 11 | "github.com/shirou/gopsutil/v3/cpu" 12 | "github.com/shirou/gopsutil/v3/load" 13 | "github.com/shirou/gopsutil/v3/mem" 14 | "github.com/shirou/gopsutil/v3/net" 15 | ) 16 | 17 | var ( 18 | ports []int 19 | ServerStatus []map[string]interface{} 20 | ) 21 | 22 | func StartSystemInfo() { 23 | if b, err := beego.AppConfig.Bool("system_info_display"); err == nil && b { 24 | ServerStatus = make([]map[string]interface{}, 0, 1500) 25 | go getSeverStatus() 26 | } 27 | } 28 | 29 | func InitAllowPort() { 30 | p := beego.AppConfig.String("allow_ports") 31 | ports = common.GetPorts(p) 32 | } 33 | 34 | func TestServerPort(p int, m string) (b bool) { 35 | if m == "p2p" || m == "secret" { 36 | return true 37 | } 38 | if p > 65535 || p < 0 { 39 | return false 40 | } 41 | if len(ports) != 0 { 42 | if !common.InIntArr(ports, p) { 43 | return false 44 | } 45 | } 46 | if m == "udp" { 47 | b = common.TestUdpPort(p) 48 | } else { 49 | b = common.TestTcpPort(p) 50 | } 51 | return 52 | } 53 | 54 | func GenerateServerPort(m string) int { 55 | for { 56 | //生成随机数 1024 - 65535 57 | serverPort := rand.Intn(65535) 58 | if serverPort < 1024 { 59 | serverPort = 1024 60 | } 61 | 62 | if TestServerPort(serverPort, m) { 63 | return serverPort 64 | } 65 | } 66 | } 67 | 68 | func getSeverStatus() { 69 | for { 70 | if len(ServerStatus) < 10 { 71 | time.Sleep(time.Second) 72 | } else { 73 | time.Sleep(time.Minute) 74 | } 75 | cpuPercet, _ := cpu.Percent(0, true) 76 | var cpuAll float64 77 | for _, v := range cpuPercet { 78 | cpuAll += v 79 | } 80 | m := make(map[string]interface{}) 81 | loads, _ := load.Avg() 82 | m["load1"] = loads.Load1 83 | m["load5"] = loads.Load5 84 | m["load15"] = loads.Load15 85 | m["cpu"] = math.Round(cpuAll / float64(len(cpuPercet))) 86 | swap, _ := mem.SwapMemory() 87 | m["swap_mem"] = math.Round(swap.UsedPercent) 88 | vir, _ := mem.VirtualMemory() 89 | m["virtual_mem"] = math.Round(vir.UsedPercent) 90 | conn, _ := net.ProtoCounters(nil) 91 | io1, _ := net.IOCounters(false) 92 | time.Sleep(time.Millisecond * 500) 93 | io2, _ := net.IOCounters(false) 94 | if len(io2) > 0 && len(io1) > 0 { 95 | m["io_send"] = (io2[0].BytesSent - io1[0].BytesSent) * 2 96 | m["io_recv"] = (io2[0].BytesRecv - io1[0].BytesRecv) * 2 97 | } 98 | t := time.Now() 99 | m["time"] = strconv.Itoa(t.Hour()) + ":" + strconv.Itoa(t.Minute()) + ":" + strconv.Itoa(t.Second()) 100 | 101 | for _, v := range conn { 102 | m[v.Protocol] = v.Stats["CurrEstab"] 103 | } 104 | if len(ServerStatus) >= 1440 { 105 | ServerStatus = ServerStatus[1:] 106 | } 107 | ServerStatus = append(ServerStatus, m) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /web/controllers/auth.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "encoding/hex" 5 | "time" 6 | 7 | "ehang.io/nps/lib/crypt" 8 | "github.com/astaxie/beego" 9 | ) 10 | 11 | type AuthController struct { 12 | beego.Controller 13 | } 14 | 15 | func (s *AuthController) GetAuthKey() { 16 | m := make(map[string]interface{}) 17 | defer func() { 18 | s.Data["json"] = m 19 | s.ServeJSON() 20 | }() 21 | if cryptKey := beego.AppConfig.String("auth_crypt_key"); len(cryptKey) != 16 { 22 | m["status"] = 0 23 | return 24 | } else { 25 | b, err := crypt.AesEncrypt([]byte(beego.AppConfig.String("auth_key")), []byte(cryptKey)) 26 | if err != nil { 27 | m["status"] = 0 28 | return 29 | } 30 | m["status"] = 1 31 | m["crypt_auth_key"] = hex.EncodeToString(b) 32 | m["crypt_type"] = "aes cbc" 33 | return 34 | } 35 | } 36 | 37 | func (s *AuthController) GetTime() { 38 | m := make(map[string]interface{}) 39 | m["time"] = time.Now().Unix() 40 | s.Data["json"] = m 41 | s.ServeJSON() 42 | } 43 | -------------------------------------------------------------------------------- /web/controllers/client.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "ehang.io/nps/lib/common" 5 | "ehang.io/nps/lib/file" 6 | "ehang.io/nps/lib/rate" 7 | "ehang.io/nps/server" 8 | "github.com/astaxie/beego" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type ClientController struct { 14 | BaseController 15 | } 16 | 17 | func (s *ClientController) List() { 18 | if s.Ctx.Request.Method == "GET" { 19 | s.Data["menu"] = "client" 20 | s.SetInfo("client") 21 | s.display("client/list") 22 | return 23 | } 24 | start, length := s.GetAjaxParams() 25 | clientIdSession := s.GetSession("clientId") 26 | var clientId int 27 | if clientIdSession == nil { 28 | clientId = 0 29 | } else { 30 | clientId = clientIdSession.(int) 31 | } 32 | list, cnt := server.GetClientList(start, length, s.getEscapeString("search"), s.getEscapeString("sort"), s.getEscapeString("order"), clientId) 33 | cmd := make(map[string]interface{}) 34 | ip := s.Ctx.Request.Host 35 | cmd["ip"] = common.GetIpByAddr(ip) 36 | cmd["bridgeType"] = beego.AppConfig.String("bridge_type") 37 | cmd["bridgePort"] = server.Bridge.TunnelPort 38 | s.AjaxTable(list, cnt, cnt, cmd) 39 | } 40 | 41 | // 添加客户端 42 | func (s *ClientController) Add() { 43 | if s.Ctx.Request.Method == "GET" { 44 | s.Data["menu"] = "client" 45 | s.SetInfo("add client") 46 | s.display() 47 | } else { 48 | id := int(file.GetDb().JsonDb.GetClientId()) 49 | t := &file.Client{ 50 | VerifyKey: s.getEscapeString("vkey"), 51 | Id: id, 52 | Status: true, 53 | Remark: s.getEscapeString("remark"), 54 | Cnf: &file.Config{ 55 | U: s.getEscapeString("u"), 56 | P: s.getEscapeString("p"), 57 | Compress: common.GetBoolByStr(s.getEscapeString("compress")), 58 | Crypt: s.GetBoolNoErr("crypt"), 59 | }, 60 | ConfigConnAllow: s.GetBoolNoErr("config_conn_allow"), 61 | RateLimit: s.GetIntNoErr("rate_limit"), 62 | MaxConn: s.GetIntNoErr("max_conn"), 63 | WebUserName: s.getEscapeString("web_username"), 64 | WebPassword: s.getEscapeString("web_password"), 65 | MaxTunnelNum: s.GetIntNoErr("max_tunnel"), 66 | Flow: &file.Flow{ 67 | ExportFlow: 0, 68 | InletFlow: 0, 69 | FlowLimit: int64(s.GetIntNoErr("flow_limit")), 70 | }, 71 | BlackIpList: RemoveRepeatedElement(strings.Split(s.getEscapeString("blackiplist"), "\r\n")), 72 | CreateTime: time.Now().Format("2006-01-02 15:04:05"), 73 | } 74 | if err := file.GetDb().NewClient(t); err != nil { 75 | s.AjaxErr(err.Error()) 76 | } 77 | s.AjaxOkWithId("add success", id) 78 | } 79 | } 80 | func (s *ClientController) GetClient() { 81 | if s.Ctx.Request.Method == "POST" { 82 | id := s.GetIntNoErr("id") 83 | data := make(map[string]interface{}) 84 | if c, err := file.GetDb().GetClient(id); err != nil { 85 | data["code"] = 0 86 | } else { 87 | data["code"] = 1 88 | data["data"] = c 89 | } 90 | s.Data["json"] = data 91 | s.ServeJSON() 92 | } 93 | } 94 | 95 | // 修改客户端 96 | func (s *ClientController) Edit() { 97 | id := s.GetIntNoErr("id") 98 | if s.Ctx.Request.Method == "GET" { 99 | s.Data["menu"] = "client" 100 | if c, err := file.GetDb().GetClient(id); err != nil { 101 | s.error() 102 | } else { 103 | s.Data["c"] = c 104 | s.Data["BlackIpList"] = strings.Join(c.BlackIpList, "\r\n") 105 | } 106 | s.SetInfo("edit client") 107 | s.display() 108 | } else { 109 | if c, err := file.GetDb().GetClient(id); err != nil { 110 | s.error() 111 | s.AjaxErr("client ID not found") 112 | return 113 | } else { 114 | if s.getEscapeString("web_username") != "" { 115 | if s.getEscapeString("web_username") == beego.AppConfig.String("web_username") || !file.GetDb().VerifyUserName(s.getEscapeString("web_username"), c.Id) { 116 | s.AjaxErr("web login username duplicate, please reset") 117 | return 118 | } 119 | } 120 | if s.GetSession("isAdmin").(bool) { 121 | if !file.GetDb().VerifyVkey(s.getEscapeString("vkey"), c.Id) { 122 | s.AjaxErr("Vkey duplicate, please reset") 123 | return 124 | } 125 | c.VerifyKey = s.getEscapeString("vkey") 126 | c.Flow.FlowLimit = int64(s.GetIntNoErr("flow_limit")) 127 | c.RateLimit = s.GetIntNoErr("rate_limit") 128 | c.MaxConn = s.GetIntNoErr("max_conn") 129 | c.MaxTunnelNum = s.GetIntNoErr("max_tunnel") 130 | } 131 | c.Remark = s.getEscapeString("remark") 132 | c.Cnf.U = s.getEscapeString("u") 133 | c.Cnf.P = s.getEscapeString("p") 134 | c.Cnf.Compress = common.GetBoolByStr(s.getEscapeString("compress")) 135 | c.Cnf.Crypt = s.GetBoolNoErr("crypt") 136 | b, err := beego.AppConfig.Bool("allow_user_change_username") 137 | if s.GetSession("isAdmin").(bool) || (err == nil && b) { 138 | c.WebUserName = s.getEscapeString("web_username") 139 | } 140 | c.WebPassword = s.getEscapeString("web_password") 141 | c.ConfigConnAllow = s.GetBoolNoErr("config_conn_allow") 142 | if c.Rate != nil { 143 | c.Rate.Stop() 144 | } 145 | if c.RateLimit > 0 { 146 | c.Rate = rate.NewRate(int64(c.RateLimit * 1024)) 147 | c.Rate.Start() 148 | } else { 149 | c.Rate = rate.NewRate(int64(2 << 23)) 150 | c.Rate.Start() 151 | } 152 | 153 | c.BlackIpList = RemoveRepeatedElement(strings.Split(s.getEscapeString("blackiplist"), "\r\n")) 154 | file.GetDb().JsonDb.StoreClientsToJsonFile() 155 | } 156 | s.AjaxOk("save success") 157 | } 158 | } 159 | 160 | func RemoveRepeatedElement(arr []string) (newArr []string) { 161 | newArr = make([]string, 0) 162 | for i := 0; i < len(arr); i++ { 163 | repeat := false 164 | for j := i + 1; j < len(arr); j++ { 165 | if arr[i] == arr[j] { 166 | repeat = true 167 | break 168 | } 169 | } 170 | if !repeat { 171 | newArr = append(newArr, arr[i]) 172 | } 173 | } 174 | return 175 | } 176 | 177 | // 更改状态 178 | func (s *ClientController) ChangeStatus() { 179 | id := s.GetIntNoErr("id") 180 | if client, err := file.GetDb().GetClient(id); err == nil { 181 | client.Status = s.GetBoolNoErr("status") 182 | if client.Status == false { 183 | server.DelClientConnect(client.Id) 184 | } 185 | s.AjaxOk("modified success") 186 | } 187 | s.AjaxErr("modified fail") 188 | } 189 | 190 | // 删除客户端 191 | func (s *ClientController) Del() { 192 | id := s.GetIntNoErr("id") 193 | if err := file.GetDb().DelClient(id); err != nil { 194 | s.AjaxErr("delete error") 195 | } 196 | server.DelTunnelAndHostByClientId(id, false) 197 | server.DelClientConnect(id) 198 | s.AjaxOk("delete success") 199 | } 200 | -------------------------------------------------------------------------------- /web/controllers/global.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "ehang.io/nps/lib/file" 5 | "strings" 6 | ) 7 | 8 | type GlobalController struct { 9 | BaseController 10 | } 11 | 12 | func (s *GlobalController) Index() { 13 | //if s.Ctx.Request.Method == "GET" { 14 | // 15 | // return 16 | //} 17 | s.Data["menu"] = "global" 18 | s.SetInfo("global") 19 | s.display("global/index") 20 | 21 | global := file.GetDb().GetGlobal() 22 | if global == nil { 23 | return 24 | } 25 | s.Data["globalBlackIpList"] = strings.Join(global.BlackIpList, "\r\n") 26 | } 27 | 28 | // 添加全局黑名单IP 29 | func (s *GlobalController) Save() { 30 | //global, err := file.GetDb().GetGlobal() 31 | //if err != nil { 32 | // return 33 | //} 34 | if s.Ctx.Request.Method == "GET" { 35 | s.Data["menu"] = "global" 36 | s.SetInfo("save global") 37 | s.display() 38 | } else { 39 | 40 | t := &file.Glob{BlackIpList: RemoveRepeatedElement(strings.Split(s.getEscapeString("globalBlackIpList"), "\r\n"))} 41 | 42 | if err := file.GetDb().SaveGlobal(t); err != nil { 43 | s.AjaxErr(err.Error()) 44 | } 45 | s.AjaxOk("save success") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web/controllers/login.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/astaxie/beego/cache" 5 | "github.com/astaxie/beego/utils/captcha" 6 | "math/rand" 7 | "net" 8 | "sync" 9 | "time" 10 | 11 | "ehang.io/nps/lib/common" 12 | "ehang.io/nps/lib/file" 13 | "ehang.io/nps/server" 14 | "github.com/astaxie/beego" 15 | ) 16 | 17 | type LoginController struct { 18 | beego.Controller 19 | } 20 | 21 | var ipRecord sync.Map 22 | var cpt *captcha.Captcha 23 | 24 | type record struct { 25 | hasLoginFailTimes int 26 | lastLoginTime time.Time 27 | } 28 | 29 | func init() { 30 | // use beego cache system store the captcha data 31 | store := cache.NewMemoryCache() 32 | cpt = captcha.NewWithFilter("/captcha/", store) 33 | } 34 | 35 | func (self *LoginController) Index() { 36 | // Try login implicitly, will succeed if it's configured as no-auth(empty username&password). 37 | webBaseUrl := beego.AppConfig.String("web_base_url") 38 | if self.doLogin("", "", false) { 39 | self.Redirect(webBaseUrl+"/index/index", 302) 40 | } 41 | self.Data["web_base_url"] = webBaseUrl 42 | self.Data["register_allow"], _ = beego.AppConfig.Bool("allow_user_register") 43 | self.Data["captcha_open"], _ = beego.AppConfig.Bool("open_captcha") 44 | self.TplName = "login/index.html" 45 | } 46 | 47 | func (self *LoginController) Verify() { 48 | username := self.GetString("username") 49 | password := self.GetString("password") 50 | captchaOpen, _ := beego.AppConfig.Bool("open_captcha") 51 | if captchaOpen { 52 | if !cpt.VerifyReq(self.Ctx.Request) { 53 | self.Data["json"] = map[string]interface{}{"status": 0, "msg": "the verification code is wrong, please get it again and try again"} 54 | self.ServeJSON() 55 | } 56 | } 57 | if self.doLogin(username, password, true) { 58 | self.Data["json"] = map[string]interface{}{"status": 1, "msg": "login success"} 59 | } else { 60 | self.Data["json"] = map[string]interface{}{"status": 0, "msg": "username or password incorrect"} 61 | } 62 | self.ServeJSON() 63 | } 64 | 65 | func (self *LoginController) doLogin(username, password string, explicit bool) bool { 66 | clearIprecord() 67 | ip, _, _ := net.SplitHostPort(self.Ctx.Request.RemoteAddr) 68 | if v, ok := ipRecord.Load(ip); ok { 69 | vv := v.(*record) 70 | if (time.Now().Unix() - vv.lastLoginTime.Unix()) >= 60 { 71 | vv.hasLoginFailTimes = 0 72 | } 73 | if vv.hasLoginFailTimes >= 10 { 74 | return false 75 | } 76 | } 77 | var auth bool 78 | if password == beego.AppConfig.String("web_password") && username == beego.AppConfig.String("web_username") { 79 | self.SetSession("isAdmin", true) 80 | self.DelSession("clientId") 81 | self.DelSession("username") 82 | auth = true 83 | server.Bridge.Register.Store(common.GetIpByAddr(self.Ctx.Input.IP()), time.Now().Add(time.Hour*time.Duration(2))) 84 | } 85 | b, err := beego.AppConfig.Bool("allow_user_login") 86 | if err == nil && b && !auth { 87 | file.GetDb().JsonDb.Clients.Range(func(key, value interface{}) bool { 88 | v := value.(*file.Client) 89 | if !v.Status || v.NoDisplay { 90 | return true 91 | } 92 | if v.WebUserName == "" && v.WebPassword == "" { 93 | if username != "user" || v.VerifyKey != password { 94 | return true 95 | } else { 96 | auth = true 97 | } 98 | } 99 | if !auth && v.WebPassword == password && v.WebUserName == username { 100 | auth = true 101 | } 102 | if auth { 103 | self.SetSession("isAdmin", false) 104 | self.SetSession("clientId", v.Id) 105 | self.SetSession("username", v.WebUserName) 106 | return false 107 | } 108 | return true 109 | }) 110 | } 111 | if auth { 112 | self.SetSession("auth", true) 113 | ipRecord.Delete(ip) 114 | return true 115 | 116 | } 117 | if v, load := ipRecord.LoadOrStore(ip, &record{hasLoginFailTimes: 1, lastLoginTime: time.Now()}); load && explicit { 118 | vv := v.(*record) 119 | vv.lastLoginTime = time.Now() 120 | vv.hasLoginFailTimes += 1 121 | ipRecord.Store(ip, vv) 122 | } 123 | return false 124 | } 125 | func (self *LoginController) Register() { 126 | if self.Ctx.Request.Method == "GET" { 127 | self.Data["web_base_url"] = beego.AppConfig.String("web_base_url") 128 | self.TplName = "login/register.html" 129 | } else { 130 | if b, err := beego.AppConfig.Bool("allow_user_register"); err != nil || !b { 131 | self.Data["json"] = map[string]interface{}{"status": 0, "msg": "register is not allow"} 132 | self.ServeJSON() 133 | return 134 | } 135 | if self.GetString("username") == "" || self.GetString("password") == "" || self.GetString("username") == beego.AppConfig.String("web_username") { 136 | self.Data["json"] = map[string]interface{}{"status": 0, "msg": "please check your input"} 137 | self.ServeJSON() 138 | return 139 | } 140 | t := &file.Client{ 141 | Id: int(file.GetDb().JsonDb.GetClientId()), 142 | Status: true, 143 | Cnf: &file.Config{}, 144 | WebUserName: self.GetString("username"), 145 | WebPassword: self.GetString("password"), 146 | Flow: &file.Flow{}, 147 | } 148 | if err := file.GetDb().NewClient(t); err != nil { 149 | self.Data["json"] = map[string]interface{}{"status": 0, "msg": err.Error()} 150 | } else { 151 | self.Data["json"] = map[string]interface{}{"status": 1, "msg": "register success"} 152 | } 153 | self.ServeJSON() 154 | } 155 | } 156 | 157 | func (self *LoginController) Out() { 158 | self.SetSession("auth", false) 159 | self.Redirect(beego.AppConfig.String("web_base_url")+"/login/index", 302) 160 | } 161 | 162 | func clearIprecord() { 163 | rand.Seed(time.Now().UnixNano()) 164 | x := rand.Intn(100) 165 | if x == 1 { 166 | ipRecord.Range(func(key, value interface{}) bool { 167 | v := value.(*record) 168 | if time.Now().Unix()-v.lastLoginTime.Unix() >= 60 { 169 | ipRecord.Delete(key) 170 | } 171 | return true 172 | }) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /web/routers/router.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "ehang.io/nps/web/controllers" 5 | "github.com/astaxie/beego" 6 | ) 7 | 8 | func Init() { 9 | web_base_url := beego.AppConfig.String("web_base_url") 10 | if len(web_base_url) > 0 { 11 | ns := beego.NewNamespace(web_base_url, 12 | beego.NSRouter("/", &controllers.IndexController{}, "*:Index"), 13 | beego.NSAutoRouter(&controllers.IndexController{}), 14 | beego.NSAutoRouter(&controllers.LoginController{}), 15 | beego.NSAutoRouter(&controllers.ClientController{}), 16 | beego.NSAutoRouter(&controllers.AuthController{}), 17 | beego.NSAutoRouter(&controllers.GlobalController{}), 18 | ) 19 | beego.AddNamespace(ns) 20 | } else { 21 | beego.Router("/", &controllers.IndexController{}, "*:Index") 22 | beego.AutoRouter(&controllers.IndexController{}) 23 | beego.AutoRouter(&controllers.LoginController{}) 24 | beego.AutoRouter(&controllers.ClientController{}) 25 | beego.AutoRouter(&controllers.AuthController{}) 26 | beego.AutoRouter(&controllers.GlobalController{}) 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /web/static/css/regular.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.11.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:auto;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:"Font Awesome 5 Free";font-weight:400} -------------------------------------------------------------------------------- /web/static/css/solid.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.11.2 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:auto;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.fas{font-family:"Font Awesome 5 Free";font-weight:900} -------------------------------------------------------------------------------- /web/static/css/toastr.min.css: -------------------------------------------------------------------------------- 1 | .toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#FFF}.toast-message a:hover{color:#CCC;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#FFF;-webkit-text-shadow:0 1px 0 #fff;text-shadow:0 1px 0 #fff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);line-height:1}.toast-close-button:focus,.toast-close-button:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}.rtl .toast-close-button{left:-.3em;float:left;right:.3em}button.toast-close-button{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999;pointer-events:none}#toast-container *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}#toast-container>div{position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;background-position:15px center;background-repeat:no-repeat;-moz-box-shadow:0 0 12px #999;-webkit-box-shadow:0 0 12px #999;box-shadow:0 0 12px #999;color:#FFF;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80)}#toast-container>div.rtl{direction:rtl;padding:15px 50px 15px 15px;background-position:right 15px center}#toast-container>div:hover{-moz-box-shadow:0 0 12px #000;-webkit-box-shadow:0 0 12px #000;box-shadow:0 0 12px #000;opacity:1;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);filter:alpha(opacity=100);cursor:pointer}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-bottom-center>div,#toast-container.toast-top-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-bottom-full-width>div,#toast-container.toast-top-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{background-color:#51A351}.toast-error{background-color:#BD362F}.toast-info{background-color:#2F96B4}.toast-warning{background-color:#F89406}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width:240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:241px) and (max-width:480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width:481px) and (max-width:768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}} -------------------------------------------------------------------------------- /web/static/img/flag/en-US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/web/static/img/flag/en-US.png -------------------------------------------------------------------------------- /web/static/img/flag/zh-CN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/web/static/img/flag/zh-CN.png -------------------------------------------------------------------------------- /web/static/js/layui/laydate/theme/default/font/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/web/static/js/layui/laydate/theme/default/font/iconfont.eot -------------------------------------------------------------------------------- /web/static/js/layui/laydate/theme/default/font/iconfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by iconfont 9 | 10 | 11 | 12 | 13 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /web/static/js/layui/laydate/theme/default/font/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/web/static/js/layui/laydate/theme/default/font/iconfont.ttf -------------------------------------------------------------------------------- /web/static/js/layui/laydate/theme/default/font/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/web/static/js/layui/laydate/theme/default/font/iconfont.woff -------------------------------------------------------------------------------- /web/static/js/toastr.min.js: -------------------------------------------------------------------------------- 1 | !function(e){e(["jquery"],function(e){return function(){function t(e,t,n){return g({type:O.error,iconClass:m().iconClasses.error,message:e,optionsOverride:n,title:t})}function n(t,n){return t||(t=m()),v=e("#"+t.containerId),v.length?v:(n&&(v=d(t)),v)}function o(e,t,n){return g({type:O.info,iconClass:m().iconClasses.info,message:e,optionsOverride:n,title:t})}function s(e){C=e}function i(e,t,n){return g({type:O.success,iconClass:m().iconClasses.success,message:e,optionsOverride:n,title:t})}function a(e,t,n){return g({type:O.warning,iconClass:m().iconClasses.warning,message:e,optionsOverride:n,title:t})}function r(e,t){var o=m();v||n(o),u(e,o,t)||l(o)}function c(t){var o=m();return v||n(o),t&&0===e(":focus",t).length?void h(t):void(v.children().length&&v.remove())}function l(t){for(var n=v.children(),o=n.length-1;o>=0;o--)u(e(n[o]),t)}function u(t,n,o){var s=!(!o||!o.force)&&o.force;return!(!t||!s&&0!==e(":focus",t).length)&&(t[n.hideMethod]({duration:n.hideDuration,easing:n.hideEasing,complete:function(){h(t)}}),!0)}function d(t){return v=e("
").attr("id",t.containerId).addClass(t.positionClass),v.appendTo(e(t.target)),v}function p(){return{tapToDismiss:!0,toastClass:"toast",containerId:"toast-container",debug:!1,showMethod:"fadeIn",showDuration:300,showEasing:"swing",onShown:void 0,hideMethod:"fadeOut",hideDuration:1e3,hideEasing:"swing",onHidden:void 0,closeMethod:!1,closeDuration:!1,closeEasing:!1,closeOnHover:!0,extendedTimeOut:1e3,iconClasses:{error:"toast-error",info:"toast-info",success:"toast-success",warning:"toast-warning"},iconClass:"toast-info",positionClass:"toast-top-right",timeOut:5e3,titleClass:"toast-title",messageClass:"toast-message",escapeHtml:!1,target:"body",closeHtml:'',closeClass:"toast-close-button",newestOnTop:!0,preventDuplicates:!1,progressBar:!1,progressClass:"toast-progress",rtl:!1}}function f(e){C&&C(e)}function g(t){function o(e){return null==e&&(e=""),e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'").replace(//g,">")}function s(){c(),u(),d(),p(),g(),C(),l(),i()}function i(){var e="";switch(t.iconClass){case"toast-success":case"toast-info":e="polite";break;default:e="assertive"}I.attr("aria-live",e)}function a(){E.closeOnHover&&I.hover(H,D),!E.onclick&&E.tapToDismiss&&I.click(b),E.closeButton&&j&&j.click(function(e){e.stopPropagation?e.stopPropagation():void 0!==e.cancelBubble&&e.cancelBubble!==!0&&(e.cancelBubble=!0),E.onCloseClick&&E.onCloseClick(e),b(!0)}),E.onclick&&I.click(function(e){E.onclick(e),b()})}function r(){I.hide(),I[E.showMethod]({duration:E.showDuration,easing:E.showEasing,complete:E.onShown}),E.timeOut>0&&(k=setTimeout(b,E.timeOut),F.maxHideTime=parseFloat(E.timeOut),F.hideEta=(new Date).getTime()+F.maxHideTime,E.progressBar&&(F.intervalId=setInterval(x,10)))}function c(){t.iconClass&&I.addClass(E.toastClass).addClass(y)}function l(){E.newestOnTop?v.prepend(I):v.append(I)}function u(){if(t.title){var e=t.title;E.escapeHtml&&(e=o(t.title)),M.append(e).addClass(E.titleClass),I.append(M)}}function d(){if(t.message){var e=t.message;E.escapeHtml&&(e=o(t.message)),B.append(e).addClass(E.messageClass),I.append(B)}}function p(){E.closeButton&&(j.addClass(E.closeClass).attr("role","button"),I.prepend(j))}function g(){E.progressBar&&(q.addClass(E.progressClass),I.prepend(q))}function C(){E.rtl&&I.addClass("rtl")}function O(e,t){if(e.preventDuplicates){if(t.message===w)return!0;w=t.message}return!1}function b(t){var n=t&&E.closeMethod!==!1?E.closeMethod:E.hideMethod,o=t&&E.closeDuration!==!1?E.closeDuration:E.hideDuration,s=t&&E.closeEasing!==!1?E.closeEasing:E.hideEasing;if(!e(":focus",I).length||t)return clearTimeout(F.intervalId),I[n]({duration:o,easing:s,complete:function(){h(I),clearTimeout(k),E.onHidden&&"hidden"!==P.state&&E.onHidden(),P.state="hidden",P.endTime=new Date,f(P)}})}function D(){(E.timeOut>0||E.extendedTimeOut>0)&&(k=setTimeout(b,E.extendedTimeOut),F.maxHideTime=parseFloat(E.extendedTimeOut),F.hideEta=(new Date).getTime()+F.maxHideTime)}function H(){clearTimeout(k),F.hideEta=0,I.stop(!0,!0)[E.showMethod]({duration:E.showDuration,easing:E.showEasing})}function x(){var e=(F.hideEta-(new Date).getTime())/F.maxHideTime*100;q.width(e+"%")}var E=m(),y=t.iconClass||E.iconClass;if("undefined"!=typeof t.optionsOverride&&(E=e.extend(E,t.optionsOverride),y=t.optionsOverride.iconClass||y),!O(E,t)){T++,v=n(E,!0);var k=null,I=e("
"),M=e("
"),B=e("
"),q=e("
"),j=e(E.closeHtml),F={intervalId:null,hideEta:null,maxHideTime:null},P={toastId:T,state:"visible",startTime:new Date,options:E,map:t};return s(),r(),a(),f(P),E.debug&&console&&console.log(P),I}}function m(){return e.extend({},p(),b.options)}function h(e){v||(v=n()),e.is(":visible")||(e.remove(),e=null,0===v.children().length&&(v.remove(),w=void 0))}var v,C,w,T=0,O={error:"error",info:"info",success:"success",warning:"warning"},b={clear:r,remove:c,error:t,getContainer:n,info:o,options:{},subscribe:s,success:i,version:"2.1.3",warning:a};return b}()})}("function"==typeof define&&define.amd?define:function(e,t){"undefined"!=typeof module&&module.exports?module.exports=t(require("jquery")):window.toastr=t(window.jQuery)}); 2 | //# sourceMappingURL=toastr.js.map 3 | -------------------------------------------------------------------------------- /web/static/page/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nps error 6 | 7 | 8 | 404 not found,power by nps 9 | 10 | -------------------------------------------------------------------------------- /web/static/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/web/static/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /web/static/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/web/static/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /web/static/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/web/static/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /web/static/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wyx176/nps-socks5/d3ff75cb5cfddb4b2893c0e3d2d505c20efba9dc/web/static/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /web/views/client/add.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 |
6 |
7 |
8 | 9 |
10 | 11 |
12 |
13 | 14 | 21 | 28 |
29 | 30 |
31 | 32 | 33 |
34 |
35 | {{if eq true .allow_user_login}} 36 |
37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 | 46 |
47 |
48 | {{end}} 49 | 58 | 67 | 76 | 77 |
78 | 79 |
80 | 81 | 82 |
83 |
84 | 85 |
86 |
87 |
88 | 91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | -------------------------------------------------------------------------------- /web/views/client/edit.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 |
6 |
7 | 8 |
9 | 10 |
11 | 12 |
13 |
14 | 15 | {{if eq true .isAdmin}} 16 |
17 | 18 |
19 | 20 | 21 |
22 |
23 | {{end}} 24 | {{if eq true .allow_user_login}} 25 | {{if or (eq true .allow_user_change_username) (eq true .isAdmin)}} 26 |
27 | 28 |
29 | 30 |
31 |
32 | {{end}} 33 |
34 | 35 |
36 | 37 |
38 |
39 | {{end}} 40 | 41 |
42 | 43 |
44 | 45 | 46 |
47 |
48 | 49 |
50 |
51 |
52 | 55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | -------------------------------------------------------------------------------- /web/views/global/index.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 |
13 | 14 |
15 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 35 | 36 |
37 | 38 | 45 | -------------------------------------------------------------------------------- /web/views/index/help.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 6 | 8 | 10 |
11 |
12 |
13 |
14 | 15 |
16 |
17 |

域名代理模式

18 |

19 | 适用范围: 小程序开发、微信公众号开发、产品演示 20 |

21 |

22 | 假设场景: 23 |

  • 有一个域名proxy.com,有一台公网机器ip为{{.ip}}
  • 24 |
  • 两个内网开发站点127.0.0.1:81,127.0.0.1:82
  • 25 |
  • 想通过a.proxy.com访问127.0.0.1:81,通过b.proxy.com访问127.0.0.1:82
  • 26 |

    27 |

    使用步骤:

    28 |
      29 |
    • 将*.proxy.com解析到公网服务器{{.ip}}
    • 30 |
    • 在客户端管理中创建一个客户端,记录下验证密钥
    • 31 |
    • 点击该客户端的域名管理,添加两条规则规则:1、域名:a.proxy.com,内网目标:127.0.0.1:81,2、域名:b.proxy.com,内网目标:127.0.0.1:82
    • 32 |
    • 内网客户端运行 33 |
      ./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
      34 |
    • 35 |
    • 现在访问a.proxy.com,b.proxy.com即可成功
    • 36 |
    37 |

    注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,如需使用https请在配置文件中将https端口设置为443,和将对应的证书文件路径添加到配置文件中 38 |

    39 |
    40 |
    41 |
    42 |
    43 |
    44 |
    45 |

    tcp隧道模式

    46 |

    47 | 适用范围: ssh、远程桌面等tcp连接场景 48 |

    49 |

    50 | 假设场景: 想通过访问公网服务器{{.ip}}的8001端口,连接内网机器10.1.50.101的22端口,实现ssh连接 51 |

    52 |

    使用步骤:

    53 |
      54 |
    • 在客户端管理中创建一个客户端,记录下验证密钥
    • 55 |
    • 内网客户端运行 56 |
      ./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
      57 |
      58 |
    • 59 |
    • 在该客户端隧道管理中添加一条tcp隧道,填写监听的端口(8001)、内网目标ip和目标端口(10.1.50.101:22),选择压缩方式,保存。
    • 60 |
    • 访问公网服务器ip({{.ip}}),填写的监听端口(8001),相当于访问内网ip(10.1.50.101):目标端口(22),例如:ssh -p 8001 root@{{.ip}}
    • 61 |
    62 |

    注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动

    63 |
    64 |
    65 |
    66 |
    67 |

    udp隧道模式

    68 |

    69 | 适用范围: 内网dns解析等udp连接场景 70 |

    71 |

    72 | 假设场景: 内网有一台dns(10.1.50.102:53),在非内网环境下想使用该dns,公网服务器为{{.ip}} 73 |

    74 |

    使用步骤:

    75 |
      76 |
    • 在客户端管理中创建一个客户端,记录下验证密钥
    • 77 |
    • 内网客户端运行 78 |
      ./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
      79 |
      80 |
    • 81 |
    • 在该客户端的隧道管理中添加一条udp隧道,填写监听的端口(53)、内网目标ip和目标端口(10.1.50.102:53),选择压缩方式,保存。
    • 82 |
    • 修改本机dns为{{.ip}},则相当于使用10.1.50.202作为dns服务器
    • 83 |
    84 |

    注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动

    85 |
    86 |
    87 |
    88 |
    89 |
    90 |
    91 |

    socks5代理模式

    92 |

    93 | 适用范围: 在外网环境下如同使用vpn一样访问内网设备或者资源 94 |

    95 |

    96 | 假设场景: 想将公网服务器{{.ip}}的8003端口作为socks5代理,达到访问内网任意设备或者资源的效果 97 |

    98 |

    使用步骤:

    99 |
      100 |
    • 在客户端管理中创建一个客户端,记录下验证密钥
    • 101 |
    • 内网客户端运行 102 |
      ./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
      103 |
      104 |
    • 105 |
    • 在该客户端隧道管理中添加一条socks5代理,填写监听的端口(8003),验证用户名和密码自行选择(建议先不填,部分客户端不支持,proxifer支持),选择压缩方式,保存。
    • 106 |
    • 在外网环境的本机配置socks5代理,ip为公网服务器ip({{.ip}}),端口为填写的监听端口(8003),即可畅享内网了
    • 107 |
    108 |

    注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动

    109 |
    110 |
    111 |
    112 |
    113 |

    http代理模式

    114 |

    115 | 适用范围: 在外网环境下访问内网站点 116 |

    117 |

    118 | 假设场景: 想将公网服务器{{.ip}}的8004端口作为http代理,访问内网网站 119 |

    120 |

    使用步骤:

    121 |
      122 |
    • 在客户端管理中创建一个客户端,记录下验证密钥
    • 123 |
    • 内网客户端运行 124 |
      ./npc -server={{.ip}}:{{.p}} -vkey=客户端的密钥
      125 |
      126 |
    • 127 |
    • 在该客户端隧道管理中添加一条http代理,填写监听的端口(8004),选择压缩方式,保存。
    • 128 |
    • 在外网环境的本机配置http代理,ip为公网服务器ip({{.ip}}),端口为填写的监听端口(8004),即可访问了
    • 129 |
    130 |

    注:上文中提到公网ip({{.ip}})为系统自动识别,如果是在测试环境中请自行对应,默认内网客户端已经启动

    131 |
    132 |
    133 |
    134 |
    135 |

    单个客户端可以添加多条隧道或者域名解析

    136 |
    137 |
    138 |
    139 | -------------------------------------------------------------------------------- /web/views/login/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
    28 | 38 |
    39 |
    40 |
    41 | 42 |
    43 |

    44 |
      45 |
    • 46 |
    47 |
    48 | 49 |
    50 |
    51 |
    52 |
    53 | 55 |
    56 |
    57 | 59 |
    60 | {{if eq true .captcha_open}} 61 |
    62 | {{create_captcha}} 63 | 65 |
    66 | {{end}} 67 | 69 | {{if eq true .register_allow}} 70 |

    71 | 73 | {{end}} 74 |
    75 |
    76 |
    77 |
    78 |
    79 | 85 |
    86 | 87 | 88 | 89 | 90 | 91 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /web/views/login/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
    28 | 35 |
    36 |
    37 |
    38 |

    39 |
    40 |

    41 |

    42 |
    43 |
    44 | 45 |
    46 |
    47 | 48 |
    49 | 50 |

    51 | 52 |
    53 |
    54 |
    55 | 61 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /web/views/public/error.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Error 404: Page not found

    3 |

    The page you have requested is not found.

    4 |

    Go Back

    5 |
    --------------------------------------------------------------------------------