开源 Init

This commit is contained in:
古大羊 2022-07-14 07:39:54 +08:00
commit c7b65e278c
68 changed files with 16451 additions and 0 deletions

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
relayports
goports
# Test binary, built with `go test -c`
*.test
.goreleaser.yaml
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
*.log
*.upx
# Dependency directories (remove the comment below to include it)
# vendor/
dist/
config.json

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 gdy , 272288813@qq.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

127
README.md Normal file
View File

@ -0,0 +1,127 @@
# goports
一个主要功能和socat类似,主要实现公网ipv6 tcp/udp 转发至 内网ipv4 tcp/udp 的小工具.
<!-- TOC -->
- [goports](#goports)
- [特性](#特性)
- [使用](#使用)
- [转发规则格式](#转发规则格式)
- [其它启动参数](#其它启动参数)
- [后台界面](#后台界面)
- [开发编译](#开发编译)
- [使用注意&常见问题](#使用注意&常见问题)
<!-- /TOC -->
## 特性
- 后端golang,前端vue3
- 支持Windows、Linux系统支持x86、ARM、MIPS、MIPSLE等架构
- 支持界面化(web后台)管理转发规则,单条转发规则支持设置多个转发端口,一键开关指定转发规则
- 单条规则支持黑白名单安全模式切换,白名单模式可以让没有安全验证的内网服务端口稍微安全一丢丢暴露到公网
- Web后台支持查看最新100条日志
- 另有精简版不带后台,支持命令行快捷设置转发规则,有利于空间有限的嵌入式设备运行.
## 使用
- [百度网盘下载地址](https://pan.baidu.com/s/1NfumD9XjYU3OTeVmbu6vOQ?pwd=6666)
百度网盘版本可能会更新比较频繁,github release更新随缘.
- 默认后台管理地址 http://<运行设备IP>:16601
默认登录账号: 666
默认登录密码: 666
- 常规使用请用 -c <配置文件路径> 指定配置文件的路由方式运行 , -p <后台端口> 可以指定后台管理端口
```bash
#仅指定配置文件路径(如果配置文件不存在会自动创建),建议使用绝对路径
goports -c 666.conf
#同时指定后台端口 8899
goports -c 666.conf -p 8899
```
- 命令行直接运行转发规则,注意后台无法编辑修改命令行启动的转发规则,主要用在不带后台的精简版
```bash
#指定后台端口8899
goports -p 8899 <转发规则1> <转发规则2> <转发规则3>...<<转发规则N>
```
## 转发规则格式
例子1
tcp6@:22222to192.168.31.1:22
监听 tcp6 类型的22222端口转发至192.168.31.1的22端口
例子2
udp@:1194to192.168.31.36
监听 udp(同时包含udp4和udp6)类型的1194端口转发至192.168.31.36的相同端口(1194)
例子3
tcp6,udp6@:53to192.168.31.1:53
监听 tcp6和udp6类型的53端口转发至192.168.31.1的53端口
如果你还是没法理解格式,那么可以通过web管理后台添加转发规则后直接在规则列表中一键复制自动生成的命令行配置
需要注意的是这种方式导入的规则不包含规则中的其它参数部分,命令行模式的规则只支持通过设置启动参数共用相同的额外参数(一般使用影响不大,不需要理会)
## 其它启动参数
使用后台管理的用户不需要理会这部分内容
-pcl <num>
全局代理数量限制(默认128),每个端口转发对应一个代理,这个参数主要是为了防止用户误写规则,生成过多代理造成程序奔溃或占用资源过多,一般不需要动.
-gpmc <num>
全局最大并发连接数(默认10240),设计这个参数是为防止由于未知原因被人恶意高并发访问搞挂运行设备或程序,请根据需求调整.
-smc <num>
单个代理(端口)的最大并发数
-ups <num>
UDP包最大长度,默认1500,一般使用情景不需要理会,有特殊使用情景再自行调整,比如内网小包性能测试.
-upm <bool>
UDP代理性能模式开关,打开后,多核CPU环境下有利于改善UDP小包转发性能,默认已打开.
-udpshort <bool>
UDP short模式,如果需要用到dns转发打开这个开关有助于节省资源.
#后台界面
![规则设置](./previews/relayruleset.png)
![规则列表](./previews/relayrules.png)
![](./previews/whitelistset.png)
![](./previews/whitelist.png)
#开发编译
带后台版本编译
```bash
go build -v -tags "adminweb nomsgpack" -ldflags="-s -w"
```
不带后台版本
```bash
go build -v -tags "nomsgpack" -ldflags="-s -w"
```
# 使用注意&常见问题
- 如果在mips架构CPU下运行有问题请使用未压缩(UPS压缩版本),
- 已知upx3.96版压缩后的程序在mipsle下可能无法运行,虽然已经更换了upx版本,暂时未再发现异常.
- 不同于防火墙端口转发规则,不要设置没有用上的端口,会增加内存的使用.
- 小米路由 ipv4 类型的80和443端口被占用,但只设置监听tcp6(ipv6)的80/443端口转发规则完全没问题.
- 如果需要使用白名单模式,请根据自身需求打开外网访问后台管理页面开关.
- 转发规则启用异常,端口转发没有生效时请登录后台查看日志.

48
base/baseproxyconf.go Normal file
View File

@ -0,0 +1,48 @@
//Copyright 2022 gdy, 272288813@qq.com
package base
import (
"sync/atomic"
)
type BaseProxyConf struct {
TrafficIn int64
TrafficOut int64
key string
ProxyType string // tcp tcp4 tcp6 udp udp4 udp6
//TrafficMonitor bool //流量监控
fromRule string
}
func (p *BaseProxyConf) GetProxyType() string {
return p.ProxyType
}
func (p *BaseProxyConf) GetStatus() string {
return p.ProxyType
}
func (p *BaseProxyConf) SetFromRule(rule string) {
p.fromRule = rule
}
func (p *BaseProxyConf) FromRule() string {
return p.fromRule
}
func (p *BaseProxyConf) ReceiveDataCallback(nw int64) {
atomic.AddInt64(&p.TrafficIn, nw)
}
func (p *BaseProxyConf) SendDataCallback(nw int64) {
atomic.AddInt64(&p.TrafficOut, nw)
}
func (p *BaseProxyConf) GetTrafficIn() int64 {
return atomic.LoadInt64(&p.TrafficIn)
}
func (p *BaseProxyConf) GetTrafficOut() int64 {
return atomic.LoadInt64(&p.TrafficOut)
}

179
base/proxy.go Normal file
View File

@ -0,0 +1,179 @@
//Copyright 2022 gdy, 272288813@qq.com
package base
import (
"errors"
"fmt"
"io"
"strings"
"sync"
"github.com/ljymc/goports/thirdlib/gdylib/pool"
)
type Proxy interface {
StartProxy()
StopProxy()
ReceiveDataCallback(int64)
SendDataCallback(int64)
GetProxyType() string
GetStatus() string
GetListenIP() string
GetListenPort() int
GetKey() string
GetCurrentConnections() int64
SetFromRule(string)
FromRule() string
String() string
GetTrafficIn() int64
GetTrafficOut() int64
SafeCheck(ip string) bool
}
type RelayRuleOptions struct {
UDPPackageSize int `json:"UDPPackageSize,omitempty"`
SingleProxyMaxConnections int64 `json:"SingleProxyMaxConnections,omitempty"`
UDPProxyPerformanceMode bool `json:"UDPProxyPerformanceMode,omitempty"`
UDPShortMode bool `json:"UDPShortMode,omitempty"`
SafeMode string `json:"SafeMode,omitempty"`
}
// Join two io.ReadWriteCloser and do some operations.
func (p *BaseProxyConf) relayData(targetServer io.ReadWriteCloser, client io.ReadWriteCloser) {
var wait sync.WaitGroup
pipe := func(to io.ReadWriteCloser, from io.ReadWriteCloser, writedataCallback func(int64)) {
defer to.Close()
defer from.Close()
defer wait.Done()
nw, _ := p.copyBuffer(to, from, nil, nil)
if writedataCallback != nil {
writedataCallback(nw)
}
// if p.TrafficMonitor {
// buf := pool.GetBuf(8 * 1024 * 1024)
// p.CopyBuffer(to, from, buf, writedataCallback)
// pool.PutBuf(buf)
// } else {
// nw, _ := p.copyBuffer(to, from, nil, nil)
// if writedataCallback != nil {
// writedataCallback(nw)
// }
// }
}
wait.Add(2)
go pipe(targetServer, client, p.ReceiveDataCallback)
go pipe(client, targetServer, p.SendDataCallback)
wait.Wait()
}
func (p *BaseProxyConf) CopyBuffer(dst io.Writer, src io.Reader, buf []byte, writedataCallback func(int64)) (written int64, err error) {
if buf != nil && len(buf) == 0 {
panic("empty buffer in CopyBuffer")
}
return p.copyBuffer(dst, src, buf, writedataCallback)
}
// copyBuffer is the actual implementation of Copy and CopyBuffer.
// if buf is nil, one is allocated.
func (p *BaseProxyConf) copyBuffer(dst io.Writer, src io.Reader, buf []byte, writedataCallback func(int64)) (written int64, err error) {
if buf == nil {
if wt, ok := src.(io.WriterTo); ok {
return wt.WriteTo(dst)
}
if rt, ok := dst.(io.ReaderFrom); ok {
return rt.ReadFrom(src)
}
size := 32 * 1024
if l, ok := src.(*io.LimitedReader); ok && int64(size) > l.N {
if l.N < 1 {
size = 1
} else {
size = int(l.N)
}
}
buf = pool.GetBuf(8 * size)
defer pool.PutBuf(buf)
}
for {
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if nw < 0 || nr < nw {
nw = 0
if ew == nil {
ew = errors.New("invalid write result")
}
}
written += int64(nw)
if writedataCallback != nil {
writedataCallback(int64(nw))
}
if ew != nil {
err = ew
break
}
if nr != nw {
err = io.ErrShortWrite
break
}
}
if er != nil {
if er != io.EOF {
err = er
}
break
}
}
return written, err
}
func formatFileSize(fileSize int64) (size string) {
switch {
case fileSize < 1024:
return fmt.Sprintf("%.2fB", float64(fileSize)/float64(1))
case fileSize < (1024 * 1024):
return fmt.Sprintf("%.2fKB", float64(fileSize)/float64(1024))
case fileSize < (1024 * 1024 * 1024):
return fmt.Sprintf("%.2fMB", float64(fileSize)/float64(1024*1024))
case fileSize < (1024 * 1024 * 1024 * 1024):
return fmt.Sprintf("%.2fGB", float64(fileSize)/float64(1024*1024*1024))
case fileSize < (1024 * 1024 * 1024 * 1024 * 1024):
return fmt.Sprintf("%.2fTB", float64(fileSize)/float64(1024*1024*1024*1024))
default:
return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024))
}
}
func CreateProxy(proxyType, listenIP, targetHost string, balanceTargetAddressList *[]string, listenPort, targetPort int, options *RelayRuleOptions) (p Proxy, err error) {
//key := GetProxyKey(proxyType, listenIP, listenPort)
switch {
case strings.HasPrefix(proxyType, "tcp"):
{
return CreateTCPProxy(proxyType, listenIP, targetHost, balanceTargetAddressList, listenPort, targetPort, options), nil
}
case strings.HasPrefix(proxyType, "udp"):
{
return CreateUDPProxy(proxyType, listenIP, targetHost, balanceTargetAddressList, listenPort, targetPort, options), nil
}
default:
return nil, fmt.Errorf("未支持的类型:%s", proxyType)
}
}
func GetProxyKey(proxyType, listenIP string, listenPort int) string {
return fmt.Sprintf("%s@%s:%d", proxyType, listenIP, listenPort)
}

186
base/tcpproxy.go Normal file
View File

@ -0,0 +1,186 @@
//Copyright 2022 gdy, 272288813@qq.com
package base
import (
"fmt"
"log"
"net"
"strings"
"sync"
)
type TCPProxy struct {
TCPUDPProxyCommonConf
//TcpSingleProxyMaxConns int64
// tcpCurrentConns int64
listenConn net.Listener
listenConnMutex sync.Mutex
connMap map[string]net.Conn
connMapMutex sync.Mutex
}
func CreateTCPProxy(proxyType, listenIP, targetIP string, balanceTargetAddressList *[]string, listenPort, targetPort int, options *RelayRuleOptions) *TCPProxy {
p := &TCPProxy{}
p.ProxyType = proxyType
p.listenIP = listenIP
p.listenPort = listenPort
p.targetIP = targetIP
p.targetPort = targetPort
if balanceTargetAddressList != nil {
p.balanceTargetAddressList = *balanceTargetAddressList
}
p.safeMode = options.SafeMode
p.SetMaxConnections(options.SingleProxyMaxConnections)
return p
}
func (p *TCPProxy) GetStatus() string {
return fmt.Sprintf("%s\nactivity connections:[%d]", p.String(), p.GetCurrentConnections())
}
// func (p *TCPProxy) CheckConns() bool {
// if GetGlobalTCPConns() >= tcpGlobalMaxConnections || p.GetCurrentConnections() >= p.TcpSingleProxyMaxConns {
// // if p.GetGlobalTCPConns() >= tcpGlobalMaxConnections {
// // log.Println("")
// // }
// // if p.GetCurrentTCPConns() >= p.TcpSingleProxyMaxConns {
// // log.Printf("超出单代理TCP限制")
// // }
// return false
// }
// return true
// }
func (p *TCPProxy) StartProxy() {
p.listenConnMutex.Lock()
defer p.listenConnMutex.Unlock()
if p.listenConn != nil {
log.Printf("proxy %s is started", p.String())
return
}
if p.connMap == nil {
p.connMap = make(map[string]net.Conn)
}
ln, err := net.Listen(p.ProxyType, p.GetListentAddress())
if err != nil {
if strings.Contains(err.Error(), "Only one usage of each socket address") {
log.Printf("监听IP端口[%s]已被占用,proxy[%s]启动失败", p.GetListentAddress(), p.String())
} else {
log.Printf("Cannot start proxy[%s]:%s", p.String(), err)
}
return
}
p.listenConn = ln
log.Printf("[proxy][start][%s]", p.String())
go func() {
for {
newConn, err := ln.Accept()
if err != nil {
if strings.Contains(err.Error(), "use of closed network connection") {
break
}
log.Printf(" Cannot accept connection due to error %s", err.Error())
continue
}
newConnAddr := newConn.RemoteAddr().String()
if !p.SafeCheck(newConnAddr) {
log.Printf("[%s]新连接 [%s]安全检查未通过", p.GetKey(), newConnAddr)
newConn.Close()
continue
}
//fmt.Printf("连接IP:[%s]\n", newConn.RemoteAddr().String())
//log.Printf("new tdp connection %s@%s [%s]===>%s", p.ProxyType, p.ListentAddress, newConn.RemoteAddr().String(), p.TargetAddress)
if !p.CheckConnections() {
//log.Printf("超出最大连接数限制\n")
p.PrintConnectionsInfo()
log.Printf("[%s]超出最大连接数限制,不再接受新连接", p.GetKey())
newConn.Close()
continue
}
p.connMapMutex.Lock()
p.connMap[newConn.RemoteAddr().String()] = newConn
p.connMapMutex.Unlock()
//atomic.AddInt64(&tcpconnections, 1)
p.AddCurrentConnections(1)
//fmt.Printf("当前全局TCP连接数:%d\n", p.GetGlobalTCPConns())
go p.handle(newConn)
}
}()
//
//p.test()
//go p.test()
//go p.tcptest()
}
func (p *TCPProxy) StopProxy() {
p.listenConnMutex.Lock()
defer p.listenConnMutex.Unlock()
defer func() {
log.Printf("[proxy][stop][%s]", p.String())
}()
if p.listenConn == nil {
return
}
p.listenConn.Close()
p.listenConn = nil
p.connMapMutex.Lock()
for _, conn := range p.connMap {
conn.Close()
}
p.connMap = make(map[string]net.Conn)
p.connMapMutex.Unlock()
}
func (p *TCPProxy) handle(conn net.Conn) {
//dialer := net.Dialer{Timeout: 10 * time.Second}
//targetConn, err := dialer.Dial("tcp", p.TargetAddress)
targetConn, err := net.Dial("tcp", p.GetTargetAddress())
defer func() {
if targetConn != nil {
targetConn.Close()
}
defer conn.Close()
p.AddCurrentConnections(-1)
p.connMapMutex.Lock()
delete(p.connMap, conn.RemoteAddr().String())
p.connMapMutex.Unlock()
}()
if err != nil {
log.Printf("%s error:%s", p.String(), err.Error())
return
}
//targetConn.SetDeadline(time.Now().Add(time.Second * 3))
// targetTcpConn, ok := targetConn.(*net.TCPConn)
// if ok {
// targetTcpConn.SetReadBuffer(p.BufferSize * 1024 * 256 * 1024)
// targetTcpConn.SetWriteBuffer(p.BufferSize * 1024 * 256 * 1024)
// }
p.relayData(targetConn, conn)
}

172
base/tcpudpcommon.go Normal file
View File

@ -0,0 +1,172 @@
//Copyright 2022 gdy, 272288813@qq.com
package base
import (
"fmt"
"log"
"net"
"strings"
"sync"
"sync/atomic"
)
const TCP_DEFAULT_STREAM_BUFFERSIZE = 128
const DEFAULT_GLOBAL_MAX_CONNECTIONS = int64(10240)
const TCPUDP_DEFAULT_SINGLE_PROXY_MAX_CONNECTIONS = int64(256)
const DEFAULT_MAX_PROXY_COUNT = int64(128)
var globalMaxConnections = DEFAULT_GLOBAL_MAX_CONNECTIONS
var globalCurrentConnections int64 = 0
var gloMaxProxyCount int64 = DEFAULT_MAX_PROXY_COUNT
var safeCheckFunc func(mode, ip string) bool
func SetSafeCheck(f func(mode, ip string) bool) {
safeCheckFunc = f
}
func SetGlobalMaxProxyCount(max int64) {
atomic.StoreInt64(&gloMaxProxyCount, max)
}
func GetGlobalMaxProxyCount() int64 {
return atomic.LoadInt64(&gloMaxProxyCount)
}
func SetGlobalMaxConnections(max int64) {
atomic.StoreInt64(&globalMaxConnections, max)
}
func GetGlobalMaxConnections() int64 {
return atomic.LoadInt64(&globalMaxConnections)
}
func GetSingleProxyMaxConnections(m *int64) int64 {
if *m <= 0 {
return TCPUDP_DEFAULT_SINGLE_PROXY_MAX_CONNECTIONS
}
return *m
}
func GetGlobalConnections() int64 {
return atomic.LoadInt64(&globalCurrentConnections)
}
func GloBalCOnnectionsAdd(add int64) int64 {
return atomic.AddInt64(&globalCurrentConnections, add)
}
type TCPUDPProxyCommonConf struct {
CurrentConnectionsCount int64
SingleProxyMaxConnections int64
targetBalanceIndex int64
BaseProxyConf
listentAddress string
listenIP string
listenPort int
targetIP string
targetPort int
targetAddress string
balanceTargetAddressList []string //均衡负载转发
targetBalanceIndexMutex sync.Mutex
safeMode string
}
func (p *TCPUDPProxyCommonConf) CheckConnections() bool {
if p.GetCurrentConnections() >= GetGlobalMaxConnections() || p.GetCurrentConnections() >= p.SingleProxyMaxConnections {
return false
}
return true
}
func (p *TCPUDPProxyCommonConf) PrintConnectionsInfo() {
log.Printf("[%s]当前连接数:[%d],单代理最大连接数限制[%d],全局最大连接数限制[%d]\n", p.GetKey(), p.GetCurrentConnections(), p.SingleProxyMaxConnections, GetGlobalMaxConnections())
}
func (p *TCPUDPProxyCommonConf) SetMaxConnections(max int64) {
if max <= 0 {
p.SingleProxyMaxConnections = TCPUDP_DEFAULT_SINGLE_PROXY_MAX_CONNECTIONS
} else {
p.SingleProxyMaxConnections = max
}
}
func (p *TCPUDPProxyCommonConf) AddCurrentConnections(a int64) {
atomic.AddInt64(&p.CurrentConnectionsCount, a)
GloBalCOnnectionsAdd(a)
}
func (p *TCPUDPProxyCommonConf) GetCurrentConnections() int64 {
return atomic.LoadInt64(&p.CurrentConnectionsCount)
}
func (p *TCPProxy) GetCurrentCon() int64 {
return atomic.LoadInt64(&p.CurrentConnectionsCount)
}
func (p *TCPUDPProxyCommonConf) GetListentAddress() string {
if p.listentAddress == "" {
if strings.Contains(p.listenIP, ":") {
p.listentAddress = fmt.Sprintf("[%s]:%d", p.listenIP, p.listenPort)
} else {
p.listentAddress = fmt.Sprintf("%s:%d", p.listenIP, p.listenPort)
}
}
return p.listentAddress
}
func (p *TCPUDPProxyCommonConf) GetKey() string {
if p.key == "" {
p.key = GetProxyKey(p.ProxyType, p.listenIP, p.listenPort)
}
return p.key
}
func (p *TCPUDPProxyCommonConf) GetListenIP() string {
return p.listenIP
}
func (p *TCPUDPProxyCommonConf) GetListenPort() int {
return p.listenPort
}
func (p *TCPUDPProxyCommonConf) GetTargetAddress() string {
if len(p.balanceTargetAddressList) == 0 {
if p.targetAddress == "" {
if strings.Contains(p.targetIP, ":") {
p.targetAddress = fmt.Sprintf("[%s]:%d", p.targetIP, p.targetPort)
} else {
p.targetAddress = fmt.Sprintf("%s:%d", p.targetIP, p.targetPort)
}
}
return p.targetAddress
}
var address string
addressListLength := int64(len(p.balanceTargetAddressList))
p.targetBalanceIndexMutex.Lock()
address = p.balanceTargetAddressList[p.targetBalanceIndex%addressListLength]
p.targetBalanceIndex++
p.targetBalanceIndexMutex.Unlock()
return address
}
func (p *TCPUDPProxyCommonConf) String() string {
if len(p.balanceTargetAddressList) == 0 {
return fmt.Sprintf("%s@%s ===> %s", p.ProxyType, p.GetListentAddress(), p.GetTargetAddress())
}
return fmt.Sprintf("%s@%s ===> %v", p.ProxyType, p.GetListentAddress(), p.balanceTargetAddressList)
}
func (p *TCPUDPProxyCommonConf) SafeCheck(remodeAddr string) bool {
host, _, _ := net.SplitHostPort(remodeAddr)
return safeCheckFunc(p.safeMode, host)
}

433
base/udpproxy.go Normal file
View File

@ -0,0 +1,433 @@
//Copyright 2022 gdy, 272288813@qq.com
package base
import (
"fmt"
"log"
"net"
"runtime"
"strings"
"sync"
"time"
"github.com/fatedier/golib/errors"
"github.com/ljymc/goports/thirdlib/gdylib/pool"
)
const UDP_DEFAULT_PACKAGE_SIZE = 1500
//测试
type UDPProxy struct {
//BaseProxyConf
TCPUDPProxyCommonConf
// targetAddr *net.UDPAddr
listenConn *net.UDPConn
listenConnMutex sync.Mutex
relayChs []chan *udpPackge
replyCh chan *udpPackge
udpPackageSize int
targetudpConnItemMap map[string]*udpMapItem
targetudpConnItemMapMutex sync.RWMutex
Upm bool //性能模式
ShortMode bool
}
type udpPackge struct {
dataSize int
data *[]byte
remoteAddr *net.UDPAddr
}
func CreateUDPProxy(proxyType, listenIP, targetIP string, balanceTargetAddressList *[]string, listenPort, targetPort int, options *RelayRuleOptions) *UDPProxy {
p := &UDPProxy{}
//p.Key = key
p.ProxyType = proxyType
p.listenIP = listenIP
p.listenPort = listenPort
p.targetIP = targetIP
p.targetPort = targetPort
if balanceTargetAddressList != nil {
p.balanceTargetAddressList = *balanceTargetAddressList
}
p.Upm = options.UDPProxyPerformanceMode
p.ShortMode = options.UDPShortMode
p.safeMode = options.SafeMode
p.SetUDPPacketSize(options.UDPPackageSize)
return p
}
func (p *UDPProxy) getHandlegoroutineNum() int {
if p.Upm {
return runtime.NumCPU()
}
return 1
}
func (p *UDPProxy) SetUDPPacketSize(size int) {
if size <= 0 {
p.udpPackageSize = UDP_DEFAULT_PACKAGE_SIZE
return
}
if size > 65507 {
p.udpPackageSize = 65507
return
}
p.udpPackageSize = size
}
func (p *UDPProxy) GetUDPPacketSize() int {
return p.udpPackageSize
}
func (p *UDPProxy) StartProxy() {
//p.init()
p.listenConnMutex.Lock()
defer p.listenConnMutex.Unlock()
if p.listenConn != nil {
return
}
bindAddr, err := net.ResolveUDPAddr(p.ProxyType, p.GetListentAddress())
if err != nil {
log.Printf("Cannot start proxy[%s]:%s", p.GetKey(), err)
return
}
ln, err := net.ListenUDP(p.ProxyType, bindAddr)
if err != nil {
if strings.Contains(err.Error(), " bind: Only one usage of each socket address") {
log.Printf("监听IP端口[%s]已被占用,proxy[%s]启动失败", p.GetListentAddress(), p.String())
} else {
log.Printf("Cannot start proxy[%s]:%s", p.String(), err)
}
return
}
ln.SetReadBuffer(p.getHandlegoroutineNum() * 4 * 1024 * 1024)
ln.SetWriteBuffer(p.getHandlegoroutineNum() * 4 * 1024 * 1024)
p.listenConn = ln
log.Printf("[proxy][start][%s]", p.String())
// p.targetAddr, err = net.ResolveUDPAddr(p.ProxyType, p.TargetAddress)
// if err != nil {
// log.Printf("net.ResolveUDPAddr[%s] error:%s", p.TargetAddress, err.Error())
// return
// }
//go p.test()
//p.relayCh = make(chan *udpPackge, 1024)
p.relayChs = make([]chan *udpPackge, p.getHandlegoroutineNum())
for i := range p.relayChs {
p.relayChs[i] = make(chan *udpPackge, 1024)
}
p.replyCh = make(chan *udpPackge, 1024)
if p.targetudpConnItemMap == nil {
p.targetudpConnItemMap = make(map[string]*udpMapItem)
}
for i := range p.relayChs {
go p.Forwarder(i, p.relayChs[i])
}
go p.replyDataToRemotAddress()
go p.CheckTargetUDPConn()
for i := 0; i < p.getHandlegoroutineNum(); i++ {
go p.ListenFunc(ln)
}
}
func (p *UDPProxy) StopProxy() {
p.listenConnMutex.Lock()
defer p.listenConnMutex.Unlock()
defer func() {
p.targetudpConnItemMapMutex.Lock()
for _, v := range p.targetudpConnItemMap {
v.conn.Close()
}
p.targetudpConnItemMap = nil
p.targetudpConnItemMap = make(map[string]*udpMapItem)
p.targetudpConnItemMapMutex.Unlock()
log.Printf("[proxy][stop][%s]", p.String())
}()
if p.listenConn == nil {
return
}
p.listenConn.Close()
p.listenConn = nil
}
//ReadFromTargetOnce one clientAddr only read once,short mode eg: udp dns
func (p *UDPProxy) ReadFromTargetOnce() bool {
if p.targetPort == 53 || p.ShortMode {
return true
}
return false
}
func (p *UDPProxy) GetStatus() string {
return fmt.Sprintf("%s max packet size[%d]", p.String(), p.GetUDPPacketSize())
}
func (p *UDPProxy) ListenFunc(ln *net.UDPConn) {
inDatabuf := pool.GetBuf(p.GetUDPPacketSize())
defer pool.PutBuf(inDatabuf)
i := uint64(0)
for {
if p.listenConn == nil {
break
}
inDatabufSize, clientAddr, err := ln.ReadFromUDP(inDatabuf)
if err != nil {
if strings.Contains(err.Error(), `smaller than the datagram`) {
log.Printf("%s ReadFromUDP error,the udp packet size is smaller than the datagram,please use flag '-ups xxx'set udp packet size \n", p.String())
} else {
if !strings.Contains(err.Error(), "use of closed network connection") {
log.Printf(" %s ReadFromUDP error:\n%s \n", p.String(), err.Error())
}
}
continue
}
//fmt.Printf("inDatabufSize:%d\n", inDatabufSize)
newConnAddr := clientAddr.String()
if !p.SafeCheck(newConnAddr) {
log.Printf("[%s]新连接 [%s]安全检查未通过", p.GetKey(), newConnAddr)
continue
}
// var newConOk bool
// p.targetudpConnItemMapMutex.RLock()
// _, newConOk = p.targetudpConnItemMap[clientAddr.String()]
// p.targetudpConnItemMapMutex.RUnlock()
// if !newConOk {
// log.Printf("new udp connection %s@%s [%s]===>%s", p.ProxyType, p.ListentAddress, clientAddr.String(), p.TargetAddress)
// }
//log.Printf("new udp connection %s@%s [%s]===>%s", p.ProxyType, p.ListentAddress, clientAddr.String(), p.TargetAddress)
data := pool.GetBuf(inDatabufSize)
copy(data, inDatabuf[:inDatabufSize])
inUdpPack := udpPackge{dataSize: inDatabufSize, data: &data, remoteAddr: clientAddr}
//p.relayCh <- &inUdpPack
p.relayChs[i%uint64(p.getHandlegoroutineNum())] <- &inUdpPack
i++
}
}
type udpMapItem struct {
conn *net.UDPConn
lastTime time.Time
}
func (p *UDPProxy) Forwarder(kk int, replych chan *udpPackge) {
// read from targetAddr and write clientAddr
readFromtargetAddrFunc := func(raddr *net.UDPAddr, udpItemKey string, tgConn *net.UDPConn) {
readBuffer := pool.GetBuf(p.GetUDPPacketSize())
defer func() {
pool.PutBuf(readBuffer)
if p.ReadFromTargetOnce() {
tgConn.Close()
}
p.AddCurrentConnections(-1)
}()
var targetConn *net.UDPConn
var udpItem *udpMapItem
var ok bool
p.AddCurrentConnections(1)
for {
targetConn = nil
udpItem = nil
timeout := 1200 * time.Millisecond
if p.ReadFromTargetOnce() {
timeout = 30 * time.Millisecond
}
if p.ReadFromTargetOnce() {
targetConn = tgConn
} else {
p.targetudpConnItemMapMutex.RLock()
udpItem, ok = p.targetudpConnItemMap[udpItemKey]
if !ok {
p.targetudpConnItemMapMutex.RUnlock()
return
}
p.targetudpConnItemMapMutex.RUnlock()
targetConn = udpItem.conn
}
targetConn.SetReadDeadline(time.Now().Add(timeout))
n, _, err := targetConn.ReadFromUDP(readBuffer)
if err != nil {
errStr := err.Error()
if strings.Contains(errStr, `i/o timeout`) && !p.ReadFromTargetOnce() {
continue
}
if !strings.Contains(errStr, `use of closed network connection`) {
log.Printf("targetConn ReadFromUDP error:%s", err.Error())
}
return
}
data := pool.GetBuf(n)
copy(data, readBuffer[:n])
udpMsg := udpPackge{dataSize: n, data: &data, remoteAddr: raddr}
if err = errors.PanicToError(func() {
select {
case p.replyCh <- &udpMsg:
default:
}
}); err != nil {
return
}
if !p.ReadFromTargetOnce() {
p.targetudpConnItemMapMutex.Lock()
udpItem, ok := p.targetudpConnItemMap[udpItemKey]
if ok {
udpItem.lastTime = time.Now()
}
p.targetudpConnItemMapMutex.Unlock()
if !ok {
return
}
}
if p.ReadFromTargetOnce() {
return
}
}
}
var err error
var targetConn *net.UDPConn
// read from readCh
for udpMsg := range replych {
err = nil
targetConn = nil
//if p.ReadFromTargetOnce()
p.targetudpConnItemMapMutex.Lock()
udpConnItem, ok := p.targetudpConnItemMap[udpMsg.remoteAddr.String()]
if !ok || p.ReadFromTargetOnce() { //??
tgAddr, err := net.ResolveUDPAddr(p.ProxyType, p.GetTargetAddress())
if err != nil {
log.Printf("net.ResolveUDPAddr[%s] error:%s", p.GetTargetAddress(), err.Error())
p.targetudpConnItemMapMutex.Unlock()
pool.PutBuf(*udpMsg.data)
continue
}
targetConn, err = net.DialUDP("udp", nil, tgAddr)
if err != nil {
p.targetudpConnItemMapMutex.Unlock()
pool.PutBuf(*udpMsg.data)
continue
}
targetConn.SetWriteBuffer(4 * 1024 * 1024)
targetConn.SetReadBuffer(4 * 1024 * 1024)
if !ok && !p.ReadFromTargetOnce() {
p.AddCurrentConnections(1)
newItem := udpMapItem{conn: targetConn, lastTime: time.Now()}
p.targetudpConnItemMap[udpMsg.remoteAddr.String()] = &newItem
udpConnItem = &newItem
}
} else {
udpConnItem.lastTime = time.Now()
targetConn = udpConnItem.conn
}
p.targetudpConnItemMapMutex.Unlock()
p.ReceiveDataCallback(int64(udpMsg.dataSize))
_, err = targetConn.Write(*udpMsg.data)
if err != nil {
targetConn.Close()
}
pool.PutBuf(*udpMsg.data)
if !ok || p.ReadFromTargetOnce() {
go readFromtargetAddrFunc(udpMsg.remoteAddr, udpMsg.remoteAddr.String(), targetConn)
}
}
}
func (p *UDPProxy) replyDataToRemotAddress() {
for msg := range p.replyCh {
_, err := p.listenConn.WriteToUDP(*(msg.data), msg.remoteAddr)
pool.PutBuf(*msg.data)
if err != nil {
log.Printf("udpConn.WriteToUDP error:%s", err.Error())
continue
}
p.SendDataCallback(int64(msg.dataSize))
}
}
func (p *UDPProxy) CheckTargetUDPConn() {
for {
<-time.After(time.Second * 1)
// connCout := atomic.LoadInt64(&p.targetudpConnCount)
// if connCout <= 0 {
// continue
// }
if p.GetCurrentConnections() <= 0 {
continue
}
p.targetudpConnItemMapMutex.Lock()
var deleteList []string
for k, v := range p.targetudpConnItemMap {
if time.Since(v.lastTime) >= 30*time.Second {
v.conn.Close()
deleteList = append(deleteList, k)
}
}
//fmt.Printf("map:%v\t deleteList:%v\n", p.targetudpConnItemMap, deleteList)
for i := range deleteList {
delete(p.targetudpConnItemMap, deleteList[i])
//log.Printf("删除targetudpConnItemMap [%s]\n", deleteList[i])
//atomic.AddInt64(&p.targetudpConnCount, -1)
p.AddCurrentConnections(-1)
}
p.targetudpConnItemMapMutex.Unlock()
}
}

2
berforebuild.bat Normal file
View File

@ -0,0 +1,2 @@
cd ./web/goports-adminviews
npm run build

45
config.json1 Normal file
View File

@ -0,0 +1,45 @@
{
"AdminWebListen": ":16601",
"ProxyCountLimit": 128,
"GlobalMaxConnections": 10240,
"RelayRuleList": [
{
"Name": "FTP",
"Configurestr": "tcp@:20000-20010,20021to192.168.31.180",
"Enable": true,
"Options": {
"UDPPackageSize": 1500,
"SingleProxyMaxConnections": 256
}
},
{
"Name": "Socket5 均衡负载",
"Configurestr": "tcp@:6666to192.168.31.180:7890,192.168.31.148:7890",
"Enable": true,
"Options": {
"UDPPackageSize": 1500,
"SingleProxyMaxConnections": 256
}
},
{
"Name": "FileBrowser",
"Configurestr": "tcp@:28888to192.168.31.180",
"Enable": true,
"Options": {
"UDPPackageSize": 1500,
"SingleProxyMaxConnections": 256
}
},
{
"Name": "DNS转发",
"Configurestr": "tcp,udp@:53to192.168.31.1",
"Enable": true,
"Options": {
"UDPPackageSize": 1496,
"SingleProxyMaxConnections": 256,
"UDPProxyPerformanceMode": true,
"UDPShortMode": true
}
}
]
}

121
config/blacklist.go Normal file
View File

@ -0,0 +1,121 @@
//Copyright 2022 gdy, 272288813@qq.com
package config
import "time"
type BlackListItem WhiteListItem
type BlackListConfigure struct {
BlackList []BlackListItem `json:"BlackList"` //黑名单列表
}
func GetBlackList() []BlackListItem {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
BlackListFlush(false)
var resList []BlackListItem
if programConfigure == nil {
return resList
}
for i := range programConfigure.BlackListConfigure.BlackList {
resList = append(resList, programConfigure.BlackListConfigure.BlackList[i])
}
return resList
}
func BlackListAdd(ip string, activelifeDuration int32) (string, error) {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
if activelifeDuration <= 0 {
activelifeDuration = 666666
}
EffectiveTimeStr := time.Now().Add(time.Hour * time.Duration(activelifeDuration)).Format("2006-01-02 15:04:05")
for i, ipr := range programConfigure.BlackListConfigure.BlackList {
if ipr.IP == ip {
programConfigure.BlackListConfigure.BlackList[i].EffectiveTime = EffectiveTimeStr
return EffectiveTimeStr, Save()
}
}
item := BlackListItem{IP: ip, EffectiveTime: EffectiveTimeStr}
programConfigure.BlackListConfigure.BlackList = append(programConfigure.BlackListConfigure.BlackList, item)
return EffectiveTimeStr, Save()
}
func BlackListDelete(ip string) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
removeCount := 0
CONTINUECHECK:
removeIndex := -1
for i, ipr := range programConfigure.BlackListConfigure.BlackList {
if ipr.IP == ip {
removeIndex = i
break
}
}
if removeIndex >= 0 {
removeCount++
programConfigure.BlackListConfigure.BlackList = DeleteBlackListlice(programConfigure.BlackListConfigure.BlackList, removeIndex)
goto CONTINUECHECK
}
if removeCount == 0 {
return nil
}
return Save()
}
func BlackListFlush(lock bool) error {
if lock {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
}
removeCount := 0
CONTINUECHECK:
removeIndex := -1
for i, ipr := range programConfigure.BlackListConfigure.BlackList {
ipat, err := time.ParseInLocation("2006-01-02 15:04:05", ipr.EffectiveTime, time.Local)
if err != nil { //有效时间格式有误,当失效处理
removeIndex = i
break
}
if time.Since(ipat) > 0 {
removeIndex = i
break
}
}
if removeIndex >= 0 {
removeCount++
programConfigure.BlackListConfigure.BlackList = DeleteBlackListlice(programConfigure.BlackListConfigure.BlackList, removeIndex)
goto CONTINUECHECK
}
if removeCount == 0 {
return nil
}
return Save()
}
func DeleteBlackListlice(a []BlackListItem, deleteIndex int) []BlackListItem {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}

247
config/config.go Normal file
View File

@ -0,0 +1,247 @@
//Copyright 2022 gdy, 272288813@qq.com
package config
import (
"encoding/json"
"fmt"
"log"
"net"
"strings"
"sync"
"github.com/ljymc/goports/base"
"github.com/ljymc/goports/thirdlib/gdylib/fileutils"
"github.com/ljymc/goports/thirdlib/gdylib/stringsp"
)
const defaultAdminAccount = "666"
const defaultAdminPassword = "666"
const defaultAdminListenPort = 16601
var runMode = "prod"
var version = "0.0.0"
func SetVersion(v string) {
version = v
}
func GetVersion() string {
return version
}
var loginRandomkey = ""
func GetLoginRandomKey() string {
return loginRandomkey
}
func FlushLoginRandomKey() {
loginRandomkey = stringsp.GetRandomString(16)
}
type ConfigureRelayRule struct {
Name string `json:"Name"`
Configurestr string `json:"Configurestr"`
Enable bool `json:"Enable"`
Options base.RelayRuleOptions `json:"Options"`
}
type BaseConfigure struct {
AdminWebListenPort int `json:"AdminWebListenPort"` //管理后台端口
ProxyCountLimit int64 `json:"ProxyCountLimit"` //全局代理数量限制
AdminAccount string `json:"AdminAccount"` //登录账号
AdminPassword string `json:"AdminPassword"` //登录密码
AllowInternetaccess bool `json:"AllowInternetaccess"` //允许外网访问
GlobalMaxConnections int64 `json:"GlobalMaxConnections"` //全局最大并发连接数
}
type ProgramConfigure struct {
BaseConfigure BaseConfigure `json:"BaseConfigure"`
RelayRuleList []ConfigureRelayRule `json:"RelayRuleList"`
WhiteListConfigure WhiteListConfigure `json:"WhiteListConfigure"`
BlackListConfigure BlackListConfigure `json:"BlackListConfigure"`
}
var programConfigureMutex sync.RWMutex
var programConfigure *ProgramConfigure
var configurePath string
//var readConfigureFileOnce sync.Once
var checkConfigureFileOnce sync.Once
var configureFileSign int8 = -1
func GetAuthAccount() map[string]string {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
accountInfo := make(map[string]string)
accountInfo[programConfigure.BaseConfigure.AdminAccount] = programConfigure.BaseConfigure.AdminPassword
return accountInfo
}
func SetConfigRuleList(ruleList *[]ConfigureRelayRule) {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
programConfigure.RelayRuleList = *ruleList
}
func GetRunMode() string {
return runMode
}
func SetRunMode(mode string) {
runMode = mode
}
func GetConfig() *ProgramConfigure {
return programConfigure
}
func GetBaseConfigure() BaseConfigure {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
baseConf := programConfigure.BaseConfigure
return baseConf
}
//保存基础配置
func SetBaseConfigure(conf *BaseConfigure) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
programConfigure.BaseConfigure = *conf
base.SetGlobalMaxConnections(conf.GlobalMaxConnections)
base.SetGlobalMaxProxyCount(conf.ProxyCountLimit)
return Save()
}
func Read(filePath string) (err error) {
pc, err := readProgramConfigure(filePath)
if err != nil {
return err
}
if pc.BaseConfigure.GlobalMaxConnections <= 0 {
pc.BaseConfigure.GlobalMaxConnections = base.DEFAULT_GLOBAL_MAX_CONNECTIONS
}
if pc.BaseConfigure.ProxyCountLimit <= 0 {
pc.BaseConfigure.ProxyCountLimit = base.DEFAULT_MAX_PROXY_COUNT
}
if pc.BaseConfigure.AdminWebListenPort <= 0 {
pc.BaseConfigure.AdminWebListenPort = 16601
}
programConfigure = pc
return nil
}
func LoadDefault(proxyCountLimit int64,
adminWebListenPort int,
globalMaxConnections int64) {
programConfigure = loadDefaultConfigure(proxyCountLimit, adminWebListenPort, globalMaxConnections)
}
func Save() (err error) {
//log.Printf("Save配置\n")
if configureFileSign == 0 {
return fmt.Errorf("配置文件不存在,不作保存")
}
defer func() {
checkConfigureFileOnce.Do(func() {
if err == nil {
configureFileSign = 1
} else {
configureFileSign = 0
}
})
}()
err = saveProgramConfig(programConfigure, configurePath)
return
}
//------------------------------------------------------------------------------------
func readProgramConfigure(filePath string) (conf *ProgramConfigure, err error) {
if filePath == "" {
return nil, fmt.Errorf("未指定配置文件路径")
}
if !strings.HasPrefix(filePath, "/") {
filePath = fmt.Sprintf("%s/%s", fileutils.GetCurrentDirectory(), filePath)
}
configurePath = filePath
//fmt.Printf("filePath:%s\n", configurePath)
fileContent, err := fileutils.ReadTextFromFile(configurePath)
if err != nil {
return nil, fmt.Errorf("读取配置文件出错:%s", err.Error())
}
var pc ProgramConfigure
err = json.Unmarshal([]byte(fileContent), &pc)
if err != nil {
log.Fatalf("解析配置文件出错:%s", err.Error())
return nil, fmt.Errorf("解析配置文件出错:%s", err.Error())
}
return &pc, nil
}
func loadDefaultConfigure(
proxyCountLimit int64,
adminWebListenPort int,
globalMaxConnections int64) *ProgramConfigure {
baseConfigure := BaseConfigure{AdminWebListenPort: adminWebListenPort,
GlobalMaxConnections: globalMaxConnections,
AdminAccount: defaultAdminAccount,
AdminPassword: defaultAdminPassword,
ProxyCountLimit: proxyCountLimit,
AllowInternetaccess: false}
whiteListConfigure := WhiteListConfigure{BaseConfigure: WhiteListBaseConfigure{ActivelifeDuration: 36, BasicAccount: defaultAdminAccount, BasicPassword: defaultAdminPassword}}
var pc ProgramConfigure
pc.BaseConfigure = baseConfigure
pc.WhiteListConfigure = whiteListConfigure
if pc.BaseConfigure.GlobalMaxConnections <= 0 {
pc.BaseConfigure.GlobalMaxConnections = base.DEFAULT_GLOBAL_MAX_CONNECTIONS
}
if pc.BaseConfigure.ProxyCountLimit <= 0 {
pc.BaseConfigure.ProxyCountLimit = base.DEFAULT_MAX_PROXY_COUNT
}
if pc.BaseConfigure.AdminWebListenPort <= 0 {
pc.BaseConfigure.AdminWebListenPort = defaultAdminListenPort
}
return &pc
}
func saveProgramConfig(programConfigure *ProgramConfigure, filePath string) error {
resBytes, err := json.MarshalIndent(*programConfigure, "", "\t")
if err != nil {
return fmt.Errorf("json.Marshal:%s", err.Error())
}
return fileutils.SaveTextToFile(string(resBytes), filePath)
}
func CheckTCPPortAvalid(port int) bool {
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return false
}
ln.Close()
return true
}

65
config/safecheck.go Normal file
View File

@ -0,0 +1,65 @@
//Copyright 2022 gdy, 272288813@qq.com
package config
import "time"
func SafeCheck(mode, ip string) bool {
switch mode {
case "whitelist":
return whiteListCheck(ip)
case "blacklist":
return blackListCheck(ip)
default:
return false
}
}
func whiteListCheck(ip string) bool {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
if programConfigure == nil {
return false
}
for _, item := range programConfigure.WhiteListConfigure.WhiteList {
if item.IP != ip {
continue
}
itemEffectiveTime, err := time.ParseInLocation("2006-01-02 15:04:05", item.EffectiveTime, time.Local)
if err != nil {
return false
}
if time.Since(itemEffectiveTime) < 0 {
return true
}
return false
}
return true
}
func blackListCheck(ip string) bool {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
if programConfigure == nil {
return true
}
for _, item := range programConfigure.BlackListConfigure.BlackList {
if item.IP != ip {
continue
}
itemEffectiveTime, err := time.ParseInLocation("2006-01-02 15:04:05", item.EffectiveTime, time.Local)
if err != nil {
return true
}
if time.Since(itemEffectiveTime) < 0 {
return false
}
return true
}
return true
}

148
config/whitelist.go Normal file
View File

@ -0,0 +1,148 @@
//Copyright 2022 gdy, 272288813@qq.com
package config
import "time"
type WhiteListConfigure struct {
BaseConfigure WhiteListBaseConfigure `json:"BaseConfigure`
WhiteList []WhiteListItem `json:"WhiteList"` //白名单列表
}
type WhiteListItem struct {
IP string `json:"IP"`
EffectiveTime string `json:"Effectivetime"` //有效时间
}
type WhiteListBaseConfigure struct {
URL string `json:"URL"`
ActivelifeDuration int32 `json:"ActivelifeDuration"` //有效期限,小时
BasicAccount string `json:"BasicAccount"`
BasicPassword string `json:"BasicPassword"`
}
func GetWhiteListBaseConfigure() WhiteListBaseConfigure {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
return programConfigure.WhiteListConfigure.BaseConfigure
}
func SetWhiteListBaseConfigure(activelifeDuration int32, url, account, password string) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
programConfigure.WhiteListConfigure.BaseConfigure.URL = url
programConfigure.WhiteListConfigure.BaseConfigure.ActivelifeDuration = activelifeDuration
programConfigure.WhiteListConfigure.BaseConfigure.BasicAccount = account
programConfigure.WhiteListConfigure.BaseConfigure.BasicPassword = password
return Save()
}
func GetWhiteList() []WhiteListItem {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
WhiteListFlush(false)
var resList []WhiteListItem
if programConfigure == nil {
return resList
}
for i := range programConfigure.WhiteListConfigure.WhiteList {
resList = append(resList, programConfigure.WhiteListConfigure.WhiteList[i])
}
return resList
}
func WhiteListAdd(ip string, activelifeDuration int32) (string, error) {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
if activelifeDuration <= 0 {
activelifeDuration = programConfigure.WhiteListConfigure.BaseConfigure.ActivelifeDuration
}
EffectiveTimeStr := time.Now().Add(time.Hour * time.Duration(activelifeDuration)).Format("2006-01-02 15:04:05")
for i, ipr := range programConfigure.WhiteListConfigure.WhiteList {
if ipr.IP == ip {
programConfigure.WhiteListConfigure.WhiteList[i].EffectiveTime = EffectiveTimeStr
return EffectiveTimeStr, Save()
}
}
item := WhiteListItem{IP: ip, EffectiveTime: EffectiveTimeStr}
programConfigure.WhiteListConfigure.WhiteList = append(programConfigure.WhiteListConfigure.WhiteList, item)
return EffectiveTimeStr, Save()
}
func WhiteListDelete(ip string) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
removeCount := 0
CONTINUECHECK:
removeIndex := -1
for i, ipr := range programConfigure.WhiteListConfigure.WhiteList {
if ipr.IP == ip {
removeIndex = i
break
}
}
if removeIndex >= 0 {
removeCount++
programConfigure.WhiteListConfigure.WhiteList = DeleteWhiteListlice(programConfigure.WhiteListConfigure.WhiteList, removeIndex)
goto CONTINUECHECK
}
if removeCount == 0 {
return nil
}
return Save()
}
func WhiteListFlush(lock bool) error {
if lock {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
}
removeCount := 0
CONTINUECHECK:
removeIndex := -1
for i, ipr := range programConfigure.WhiteListConfigure.WhiteList {
ipat, err := time.ParseInLocation("2006-01-02 15:04:05", ipr.EffectiveTime, time.Local)
if err != nil { //有效时间格式有误,当失效处理
removeIndex = i
break
}
if time.Since(ipat) > 0 {
removeIndex = i
break
}
}
if removeIndex >= 0 {
removeCount++
programConfigure.WhiteListConfigure.WhiteList = DeleteWhiteListlice(programConfigure.WhiteListConfigure.WhiteList, removeIndex)
goto CONTINUECHECK
}
if removeCount == 0 {
return nil
}
return Save()
}
func DeleteWhiteListlice(a []WhiteListItem, deleteIndex int) []WhiteListItem {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}

21
debug.go Normal file
View File

@ -0,0 +1,21 @@
//go:build debug
// +build debug
package main
import (
"fmt"
"github.com/ljymc/goports/thirdlib/gdylib/recoverutil"
)
func init() {
defer func() {
recoverErr := recover()
if recoverErr == nil {
return
}
panicFile := fmt.Sprintf("闪退.log")
recoverutil.RecoverHandler(recoverErr, true, true, panicFile)
}()
}

44
go.mod Normal file
View File

@ -0,0 +1,44 @@
module github.com/ljymc/goports
go 1.18
require (
github.com/fatedier/golib v0.2.0
golang.org/x/net v0.0.0-20220524220425-1d687d428aca // indirect
)
require (
github.com/gin-contrib/gzip v0.0.5
github.com/gin-gonic/gin v1.8.1
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/shirou/gopsutil/v3 v3.22.5
)
require (
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.10.0 // indirect
github.com/goccy/go-json v0.9.7 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect
)
replace github.com/ljymc/toolbox/fileutils => ../toolbox/fileutils/

139
go.sum Normal file
View File

@ -0,0 +1,139 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatedier/golib v0.2.0 h1:8BxiUcjleBlXBYlTNUllD8KZZHaFU/NP/vP0Yu1Fkpg=
github.com/fatedier/golib v0.2.0/go.mod h1:e2NPpBGUFsHDjXrfP1B5aK3S0+yUeVxgqfc3go3KNj0=
github.com/gin-contrib/gzip v0.0.5 h1:mhnVU32YnnBh2LPH2iqRqsA/eR7SAqRaD388jL2s/j0=
github.com/gin-contrib/gzip v0.0.5/go.mod h1:OPIK6HR0Um2vNmBUTlayD7qle4yVVRZT0PyhdUigrKk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJQoJA=
github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o=
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20190228165749-92fc7df08ae7/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220524220425-1d687d428aca h1:xTaFYiPROfpPhqrfTIDXj0ri1SpfueYT951s4bAuDO8=
golang.org/x/net v0.0.0-20220524220425-1d687d428aca/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

144
main.go Normal file
View File

@ -0,0 +1,144 @@
//Copyright 2022 gdy, 272288813@qq.com
package main
import (
"flag"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/ljymc/goports/base"
"github.com/ljymc/goports/config"
"github.com/ljymc/goports/rule"
)
var (
listenPort = flag.Int("p", 16601, "http Admin Web listen port ")
pcl = flag.Int64("pcl", -1, "global proxy count limit")
gpmc = flag.Int64("gpmc", -1, "global proxy max connections,default(1024)")
udpPackageSize = flag.Int("ups", base.UDP_DEFAULT_PACKAGE_SIZE, "udp package max size")
smc = flag.Int64("smc", base.TCPUDP_DEFAULT_SINGLE_PROXY_MAX_CONNECTIONS, "signle proxy max connections,default(128)")
upm = flag.Bool("upm", true, "udp proxy Performance Mode open")
udpshort = flag.Bool("udpshort", false, "udp short mode,eg dns")
configureFileURL = flag.String("c", "", "configure file url")
)
var (
runMode = "prod"
version = "unknown"
commit = "none"
date = "unknown"
)
var runTime time.Time
// go build && ./goports 127.0.0.199:7000,443to192.168.31.1:80,443 20100-20110to192.168.31.1:20100-20110
func main() {
flag.Parse()
err := config.Read(*configureFileURL)
if err != nil {
log.Printf("%s", err.Error())
log.Printf("载入默认配置以及命令行设定的参数")
config.LoadDefault(*pcl, *listenPort, *gpmc)
if len(*configureFileURL) > 0 {
err = config.Save()
if err != nil {
log.Printf("保存配置到%s出错:%s", *configureFileURL, err.Error())
}
}
}
gcf := config.GetConfig()
//fmt.Printf("*gcf:%v\n", *gcf)
base.SetSafeCheck(config.SafeCheck)
base.SetGlobalMaxConnections(gcf.BaseConfigure.GlobalMaxConnections)
base.SetGlobalMaxProxyCount(gcf.BaseConfigure.ProxyCountLimit)
config.SetRunMode(runMode)
config.SetVersion(version)
log.Printf("RunMode:%s\n", runMode)
log.Printf("version:%s\tcommit %s, built at %s\n", version, commit, date)
RunAdminWeb(gcf.BaseConfigure.AdminWebListenPort)
runTime = time.Now()
// if *upm {
// log.Printf("udp proxy Performance Mode open ")
// }
//log.Printf("Gobal proxy max connections:[%d] single proxy max connections:[%d]\n", base.GetGlobalMaxConnections(), base.GetSingleProxyMaxConnections(smc))
if len(flag.Args()) > 0 {
LoadRuleListFromCMD(flag.Args())
}
LoadRuleFromConfigFile(gcf)
rule.EnableAllRelayRule() //开启规则
//initProxyList()
//*****************
// time.Sleep(time.Microsecond * 50)
// cruuentPath, _ := fileutils.GetCurrentDirectory()
// panicFile := fmt.Sprintf("%s/relayport_panic.log", cruuentPath)
// fileutils.PanicRedirect(panicFile)
//*****************
//main goroutine wait
sigs := make(chan os.Signal, 1)
exit := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
exit <- true
}()
<-exit
}
func LoadRuleListFromCMD(args []string) {
options := base.RelayRuleOptions{UDPPackageSize: *udpPackageSize,
SingleProxyMaxConnections: *smc,
UDPProxyPerformanceMode: *upm,
UDPShortMode: *udpshort}
relayRules, err := rule.GetRelayRulesFromCMD(flag.Args(), &options)
if err != nil {
log.Print("config.GetRelayRulesFromCMD err:", err.Error())
return
}
_, e := rule.AddRuleToGlobalRuleList(false, (*relayRules)...)
if e != nil {
log.Printf("%s\n", e)
}
}
func LoadRuleFromConfigFile(pc *config.ProgramConfigure) {
if pc == nil {
return
}
for i := range pc.RelayRuleList {
relayRule, err := rule.CreateRuleByConfigureAndOptions(
pc.RelayRuleList[i].Name,
pc.RelayRuleList[i].Configurestr,
pc.RelayRuleList[i].Options)
if err != nil {
continue
}
relayRule.From = "configureFile" //规则来源
relayRule.IsEnable = pc.RelayRuleList[i].Enable
_, e := rule.AddRuleToGlobalRuleList(false, *relayRule)
if e != nil {
log.Printf("%s\n", e)
}
}
}

BIN
previews/relayrules.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
previews/relayruleset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
previews/whitelist.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
previews/whitelistset.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

264
rule/global.go Normal file
View File

@ -0,0 +1,264 @@
//Copyright 2022 gdy, 272288813@qq.com
package rule
import (
"fmt"
"sync"
"github.com/ljymc/goports/base"
"github.com/ljymc/goports/config"
)
var globalRelayRules *[]RelayRule
var globalRelayRulesMutex sync.RWMutex
func SetGlobalRelayRules(rl *[]RelayRule) {
globalRelayRulesMutex.Lock()
defer globalRelayRulesMutex.Unlock()
globalRelayRules = rl
syncRuleListToConfigure()
}
func AddRuleToGlobalRuleList(sync bool, rl ...RelayRule) (string, error) {
globalRelayRulesMutex.Lock()
defer globalRelayRulesMutex.Unlock()
var err error
if globalRelayRules == nil {
var rrl []RelayRule
globalRelayRules = &rrl
}
for i := range rl {
isExits := false
for j := range *globalRelayRules {
if (*globalRelayRules)[j].MainConfigure == rl[i].MainConfigure {
isExits = true
if err == nil {
err = fmt.Errorf("\n\t规则[%s]已存在,不再重复加入规则列表", rl[i].MainConfigure)
} else {
err = fmt.Errorf("%s\n\t规则[%s]已存在,不再重复加入规则列表", err.Error(), rl[i].MainConfigure)
}
break
}
}
if !isExits {
*globalRelayRules = append(*globalRelayRules, rl[i])
if rl[i].From != "cmd" && sync {
syncRes := ""
if syncErr := syncRuleListToConfigure(); syncErr != nil {
syncRes = syncErr.Error()
}
return syncRes, nil
}
}
}
return "", err
}
func AlterRuleInGlobalRuleListByKey(key string, rule *RelayRule) (bool, error) {
globalRelayRulesMutex.Lock()
defer globalRelayRulesMutex.Unlock()
keyIndex := -1
for i := range *globalRelayRules {
if (*globalRelayRules)[i].MainConfigure != key {
continue
}
keyIndex = i
break
}
if keyIndex < 0 {
return true, fmt.Errorf("修改转发规则失败,规则[%s]不存在", key)
}
(*globalRelayRules)[keyIndex].Disable()
(*globalRelayRules)[keyIndex] = *rule
syncErr := syncRuleListToConfigure()
syncSuccess := true
if syncErr != nil {
syncSuccess = false
}
return syncSuccess, nil
}
func DeleteGlobalRuleByKey(key string) (bool, error) {
globalRelayRulesMutex.Lock()
defer globalRelayRulesMutex.Unlock()
if globalRelayRules == nil {
return true, nil
}
deleteIndex := -1
for i := range *globalRelayRules {
if (*globalRelayRules)[i].MainConfigure == key {
deleteIndex = i
break
}
}
if deleteIndex < 0 {
return true, fmt.Errorf("relayRule[%s]no found,delete failed", key)
}
*globalRelayRules = DeleteRuleSlice(*globalRelayRules, deleteIndex)
syncError := syncRuleListToConfigure()
syncSuccess := true
if syncError != nil {
syncSuccess = false
}
return syncSuccess, nil
}
func GetRuleByMainConfigure(configStr string) *RelayRule {
globalRelayRulesMutex.RLock()
defer globalRelayRulesMutex.RUnlock()
if globalRelayRules == nil {
return nil
}
for i := range *globalRelayRules {
//fmt.Printf("MainConfigure %s:::%s\n", (*globalRelayRules)[i].MainConfigure, configStr)
if (*globalRelayRules)[i].MainConfigure == configStr {
r := (*globalRelayRules)[i]
return &r
}
}
return nil
}
func GetGlobalEnableProxyCount() int64 {
globalRelayRulesMutex.RLock()
defer globalRelayRulesMutex.RUnlock()
if globalRelayRules == nil {
return 0
}
count := int64(0)
for _, r := range *globalRelayRules {
if !r.IsEnable {
continue
}
if r.proxyList == nil {
continue
}
count += int64(len(*r.proxyList))
}
return count
}
// func StartAllProxy() {
// allProxyListMutex.Lock()
// defer allProxyListMutex.Unlock()
// if allProxyList == nil {
// return
// }
// for _, p := range *allProxyList {
// go p.StartProxy()
// }
// }
func EnableAllRelayRule() error {
globalRelayRulesMutex.RLock()
defer globalRelayRulesMutex.RUnlock()
var err error
if globalRelayRules == nil {
return nil
}
for i := range *globalRelayRules {
if GetGlobalEnableProxyCount()+(*globalRelayRules)[i].GetProxyCount() <= base.GetGlobalMaxProxyCount() {
if (*globalRelayRules)[i].From == "cmd" || ((*globalRelayRules)[i].From == "configureFile" && (*globalRelayRules)[i].IsEnable) {
(*globalRelayRules)[i].Enable()
}
continue
}
if GetGlobalEnableProxyCount()+(*globalRelayRules)[i].GetProxyCount() > base.DEFAULT_MAX_PROXY_COUNT {
if err == nil {
err = fmt.Errorf("\n\t超出代理数最大限制,规则[%s]未启用", (*globalRelayRules)[i].MainConfigure)
} else {
err = fmt.Errorf("%s\n\t超出代理数最大限制,规则[%s]未启用", err.Error(), (*globalRelayRules)[i].MainConfigure)
}
}
}
return err
}
func EnableRelayRuleByKey(key string) (*RelayRule, bool, error) {
globalRelayRulesMutex.RLock()
defer globalRelayRulesMutex.RUnlock()
for i := range *globalRelayRules {
if (*globalRelayRules)[i].MainConfigure == key {
(*globalRelayRules)[i].Enable()
syncErr := syncRuleListToConfigure()
synsSuccess := true
if syncErr != nil {
synsSuccess = false
}
return &(*globalRelayRules)[i], synsSuccess, nil
}
}
return nil, true, fmt.Errorf("规则[%s]不存在,开启失败", key)
}
func DisableRelayRuleByKey(key string) (*RelayRule, bool, error) {
globalRelayRulesMutex.RLock()
defer globalRelayRulesMutex.RUnlock()
for i := range *globalRelayRules {
if (*globalRelayRules)[i].MainConfigure == key {
(*globalRelayRules)[i].Disable()
syncErr := syncRuleListToConfigure()
synsSuccess := true
if syncErr != nil {
synsSuccess = false
}
return &(*globalRelayRules)[i], synsSuccess, nil
}
}
return nil, true, fmt.Errorf("规则[%s]不存在,停用失败", key)
}
func DeleteRuleSlice(a []RelayRule, deleteIndex int) []RelayRule {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}
//syncRuleListToConfigure 同步规则列表到配置
func syncRuleListToConfigure() error {
var ruleList []config.ConfigureRelayRule
for i := range *globalRelayRules {
if (*globalRelayRules)[i].From == "cmd" {
continue
}
rule := config.ConfigureRelayRule{
Name: (*globalRelayRules)[i].Name,
Configurestr: (*globalRelayRules)[i].MainConfigure,
Enable: (*globalRelayRules)[i].IsEnable,
Options: (*globalRelayRules)[i].Options}
ruleList = append(ruleList, rule)
}
config.SetConfigRuleList(&ruleList)
return config.Save()
}

68
rule/query.go Normal file
View File

@ -0,0 +1,68 @@
//Copyright 2022 gdy, 272288813@qq.com
package rule
import (
"github.com/ljymc/goports/base"
)
type RelayRuleProxyInfo struct {
Proxy string `json:"Proxy"`
CurrentConnections int64 `json:"CurrentConnections"`
TrafficIn int64 `json:"TrafficIn"`
TrafficOut int64 `json:"TrafficOut"`
}
func GetRelayRuleList() (*[]RelayRule, map[string][]RelayRuleProxyInfo) {
globalRelayRulesMutex.RLock()
defer globalRelayRulesMutex.RUnlock()
var rl []RelayRule
proxyInfoMap := make(map[string][]RelayRuleProxyInfo)
if globalRelayRules == nil {
return &rl, proxyInfoMap
}
for i := range *globalRelayRules {
rl = append(rl, (*globalRelayRules)[i])
var rpl []RelayRuleProxyInfo
if (*globalRelayRules)[i].proxyList == nil {
proxyInfoMap[(*globalRelayRules)[i].MainConfigure] = rpl
continue
}
for pindex := range *(*globalRelayRules)[i].proxyList {
pi := RelayRuleProxyInfo{
Proxy: (*(*globalRelayRules)[i].proxyList)[pindex].GetKey(),
CurrentConnections: (*(*globalRelayRules)[i].proxyList)[pindex].GetCurrentConnections(),
TrafficIn: (*(*globalRelayRules)[i].proxyList)[pindex].GetTrafficIn(),
TrafficOut: (*(*globalRelayRules)[i].proxyList)[pindex].GetTrafficOut()}
rpl = append(rpl, pi)
}
proxyInfoMap[(*globalRelayRules)[i].MainConfigure] = rpl
}
return &rl, proxyInfoMap
}
//GetAllProxyInfo
// func GetAllProxyInfo() map[string]interface{} {
// allProxyListMutex.Lock()
// defer allProxyListMutex.Unlock()
// info := make(map[string]interface{})
// if allProxyList == nil {
// return info
// }
// for _, p := range *allProxyList {
// pi := GetProxyInfo(p)
// info[p.GetKey()] = pi
// }
// return info
// }
func GetProxyInfo(p base.Proxy) map[string]string {
pi := make(map[string]string)
pi["proxyType"] = p.GetProxyType()
pi["key"] = p.GetKey()
pi["status"] = p.GetStatus()
pi["fromRule"] = p.FromRule()
return pi
}

545
rule/relayrule.go Normal file
View File

@ -0,0 +1,545 @@
//Copyright 2022 gdy, 272288813@qq.com
package rule
import (
"fmt"
"log"
"net"
"strconv"
"strings"
"github.com/ljymc/goports/base"
)
type RelayRule struct {
Name string `json:"Name"`
MainConfigure string `json:"Mainconfigure"`
RelayType string `json:"RelayType"`
ListenIP string `json:"ListenIP"`
ListenPorts string `json:"ListenPorts"`
TargetIP string `json:"TargetIP"`
TargetPorts string `json:"TargetPorts"`
BalanceTargetAddressList []string `json:"BalanceTargetAddressList"`
Options base.RelayRuleOptions `json:"Options"`
SubRuleList []SubRelayRule `json:"SubRuleList"`
From string `json:"From"`
IsEnable bool `json:"Enable"`
proxyList *[]base.Proxy `json:"-"`
}
type SubRelayRule struct {
ProxyType string `json:"ProxyType"`
BindIP string `json:"BindIP"`
ListenPorts []int `json:"ListenPorts"`
TargetHost string `json:"TargetHost"`
TargetPorts []int `json:"TargetPorts"`
BalanceTargetAddressAddress []string `json:"BalanceTargetAddressAddress"`
}
func (r *RelayRule) Enable() {
r.IsEnable = true
if r.proxyList == nil {
return
}
for _, p := range *r.proxyList {
p.StartProxy()
}
}
func (r *RelayRule) GetProxyCount() int64 {
if r.proxyList == nil {
return 0
}
return int64(len(*r.proxyList))
}
func (r *RelayRule) Disable() {
r.IsEnable = false
if r.proxyList == nil {
return
}
for _, p := range *r.proxyList {
p.StopProxy()
}
}
func GetRelayRulesFromCMD(configureList []string, options *base.RelayRuleOptions) (relayRules *[]RelayRule, err error) {
//proxyMap := make(map[string]base.Proxy)
var relayRuleList []RelayRule
for _, configure := range configureList {
relayRule, err := CreateRuleByConfigureAndOptions("", configure, *options)
if err != nil {
return nil, err
}
relayRule.From = "cmd" //规则来源
relayRuleList = append(relayRuleList, *relayRule)
}
return &relayRuleList, nil
}
func (r *RelayRule) CreateMainConfigure() (configure string) {
if len(r.BalanceTargetAddressList) > 0 {
configure = fmt.Sprintf("%s@%s:%sto%s", r.RelayType, r.ListenIP, r.ListenPorts, strings.Join(r.BalanceTargetAddressList, ","))
} else {
if strings.Compare(r.ListenPorts, r.TargetPorts) == 0 {
configure = fmt.Sprintf("%s@%s:%sto%s", r.RelayType, r.ListenIP, r.ListenPorts, r.TargetIP)
} else {
configure = fmt.Sprintf("%s@%s:%sto%s:%s", r.RelayType, r.ListenIP, r.ListenPorts, r.TargetIP, r.TargetPorts)
}
}
return configure
}
func CreateRuleByConfigureAndOptions(name, configureStr string, options base.RelayRuleOptions) (rule *RelayRule, err error) {
var r RelayRule
r.Options = options
r.SubRuleList, r.RelayType, r.ListenIP, r.ListenPorts, r.TargetIP, r.TargetPorts, r.BalanceTargetAddressList, err = createSubRuleListFromConfigure(configureStr)
if err != nil {
return nil, err
}
r.MainConfigure = r.CreateMainConfigure()
// if len(r.BalanceTargetAddressList) > 0 {
// r.MainConfigure = fmt.Sprintf("%s@%s:%sto%s", r.RelayType, r.ListenIP, r.ListenPorts, strings.Join(r.BalanceTargetAddressList, ","))
// } else {
// if strings.Compare(r.ListenPorts, r.TargetPorts) == 0 {
// r.MainConfigure = fmt.Sprintf("%s@%s:%sto%s", r.RelayType, r.ListenIP, r.ListenPorts, r.TargetIP)
// } else {
// r.MainConfigure = fmt.Sprintf("%s@%s:%sto%s:%s", r.RelayType, r.ListenIP, r.ListenPorts, r.TargetIP, r.TargetPorts)
// }
// }
var pl []base.Proxy
for i := range r.SubRuleList {
if len(r.BalanceTargetAddressList) == 0 {
for j := range r.SubRuleList[i].ListenPorts {
p, e := base.CreateProxy(r.SubRuleList[i].ProxyType,
r.SubRuleList[i].BindIP,
r.SubRuleList[i].TargetHost,
nil,
r.SubRuleList[i].ListenPorts[j],
r.SubRuleList[i].TargetPorts[j],
&options)
if e != nil {
log.Printf("CreateProxy error:%s", e.Error())
continue
}
p.SetFromRule(r.MainConfigure)
pl = append(pl, p)
}
continue
}
p, e := base.CreateProxy(r.SubRuleList[i].ProxyType,
r.SubRuleList[i].BindIP,
r.SubRuleList[i].TargetHost,
&r.BalanceTargetAddressList,
r.SubRuleList[i].ListenPorts[0],
0,
&options)
if e != nil {
log.Printf("CreateProxy error:%s", e.Error())
continue
}
p.SetFromRule(r.MainConfigure)
pl = append(pl, p)
}
r.proxyList = &pl
r.Name = name
return &r, nil
}
func createSubRuleListFromConfigure(str string) (subRelyList []SubRelayRule, proxytypeListStr, listenIP, listenPortsStr, targetIP, targetePortsStr string, targetAddressList []string, err error) {
splitRes := strings.Split(str, "@")
if len(splitRes) > 2 {
err = fmt.Errorf("relay参数:%s格式有误!000", str)
return
}
proxytypeListStr = "tcp,udp"
relayConfig := splitRes[0]
if len(splitRes) == 2 {
proxytypeListStr = splitRes[0]
relayConfig = splitRes[1]
}
proxyTypeList := getProxyTypeList(proxytypeListStr)
err = checkProxyType(proxyTypeList)
if err != nil {
return
}
proxytypeListStr = convertProxyTypeByList(proxyTypeList)
relayConfigArray := strings.Split(relayConfig, "to")
if len(relayConfigArray) > 2 {
err = fmt.Errorf("relay参数:%s格式有误!001", str)
return
}
var listenPorts []int
var targetPorts []int
switch len(relayConfigArray) {
case 1: //监听端口没有指定 比如 192.168.31.22:80,443,20000-20010
tip, tPortsStr, e := getIpAndPortFromAddress(relayConfigArray[0], true, true)
if e != nil {
err = fmt.Errorf("参数中目标地址部分参数[%s]格式有误", relayConfigArray[0])
return
}
targetPorts, e = portsStrToIList(tPortsStr)
if e != nil {
err = fmt.Errorf("参数[%s]中的目标端口部分出错:%s", str, e.Error())
return
}
targetePortsStr = tPortsStr
listenPortsStr = tPortsStr
listenPorts = targetPorts
targetIP = tip
case 2: //监听端口有指定 比如 80,443,20000-20010to192.168.31.222 ,但目标地址的端口不一定指定
bindAddress := relayConfigArray[0]
bip, bPortsStr, e := getIpAndPortFromAddress(bindAddress, false, false)
if e != nil {
err = fmt.Errorf("参数[%s]中的监听端口部分出错:%s", str, e.Error())
return
}
listenIP = bip
listenPortsStr = bPortsStr
//fmt.Printf("bip:%s bindPortsStr:%s\n", bip, bindPortsStr)
listenPorts, e = portsStrToIList(bPortsStr)
if e != nil {
err = fmt.Errorf("参数中绑定端口部分参数[%s]格式有误", bPortsStr)
return
}
if strings.Contains(relayConfigArray[1], ",") { //均衡负载模式
if len(listenPorts) != 1 {
err = fmt.Errorf("均衡负载模式一条配置指定监听一个端口")
return
}
//targetAddressList :
targetAddressList = strings.Split(relayConfigArray[1], ",")
} else {
targetAddress := relayConfigArray[1]
tip, tPortsStr, e := getIpAndPortFromAddress(targetAddress, true, false)
if e != nil {
err = fmt.Errorf("参数中目标地址部分参数[%s]格式有误", targetAddress)
return
}
targetePortsStr = tPortsStr
targetPorts, e = portsStrToIList(tPortsStr)
if e != nil {
err = fmt.Errorf("参数[%s]中的目标端口部分出错:%s", str, e.Error())
return
}
if len(listenPorts) > 0 && len(targetPorts) == 0 {
targetPorts = listenPorts
targetePortsStr = listenPortsStr
} else if len(listenPorts) == 0 && len(targetPorts) > 0 {
listenPorts = targetPorts
listenPortsStr = targetePortsStr
}
if len(listenPorts) != len(targetPorts) {
err = fmt.Errorf("参数[%s]中监听端口数量和目标端口数量不一致", str)
// fmt.Printf("listenPorts:%v\n", listenPorts)
// fmt.Printf("targetPorts:%v\n", targetPorts)
return
}
targetIP = tip
}
default:
}
var SubBaseRule SubRelayRule
SubBaseRule.BindIP = listenIP
SubBaseRule.ListenPorts = append(SubBaseRule.ListenPorts, listenPorts...)
if len(targetAddressList) == 0 {
SubBaseRule.TargetHost = targetIP
SubBaseRule.TargetPorts = append(SubBaseRule.TargetPorts, targetPorts...)
} else {
SubBaseRule.BalanceTargetAddressAddress = targetAddressList
}
for i := range proxyTypeList {
dt := SubBaseRule
dt.ProxyType = proxyTypeList[i]
subRelyList = append(subRelyList, dt)
}
return
}
func convertProxyTypeByList(proxyTypeList []string) (proxyType string) {
for i := range proxyTypeList {
if i == 0 {
proxyType = proxyTypeList[i]
continue
}
proxyType += "," + proxyTypeList[i]
}
return
}
func getProxyTypeList(proxyTypeListStr string) (proxyTypeList []string) {
//tmpList = strings.Split(proxyTypeListStr, ",")
//var
tmpMap := make(map[string]int)
if strings.Contains(proxyTypeListStr, "tcp4") && strings.Contains(proxyTypeListStr, "tcp6") {
proxyTypeList = append(proxyTypeList, "tcp")
tmpMap["tcp"] = 1
proxyTypeListStr = strings.Replace(proxyTypeListStr, "tcp4", ",", -1)
proxyTypeListStr = strings.Replace(proxyTypeListStr, "tcp6", ",", -1)
}
if strings.Contains(proxyTypeListStr, "udp4") && strings.Contains(proxyTypeListStr, "udp6") {
proxyTypeList = append(proxyTypeList, "udp")
tmpMap["udp"] = 1
proxyTypeListStr = strings.Replace(proxyTypeListStr, "udp4", ",", -1)
proxyTypeListStr = strings.Replace(proxyTypeListStr, "udp6", ",", -1)
}
tmpList := strings.Split(proxyTypeListStr, ",")
for i := range tmpList {
if len(tmpList[i]) <= 2 {
continue
}
if _, ok := tmpMap[tmpList[i]]; ok {
continue
}
_, tcpOK := tmpMap["tcp"]
_, udpOK := tmpMap["udp"]
if (tmpList[i] == "tcp4" || tmpList[i] == "tcp6") && tcpOK {
continue
}
if (tmpList[i] == "udp4" || tmpList[i] == "udp6") && udpOK {
continue
}
proxyTypeList = append(proxyTypeList, tmpList[i])
tmpMap[tmpList[i]] = 1
}
return
}
func checkProxyType(proxyTypeList []string) error {
for _, proxyType := range proxyTypeList {
switch proxyType {
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6":
{
return nil
}
default:
{
return fmt.Errorf("unsupport Proxy Type:%s", proxyType)
}
}
}
return nil
}
//CheckProxyConflict 冲突检查
func CheckProxyConflict(proxyList *[]base.Proxy, proxyType, listenIP string, listenPort int) error {
proxyMap := make(map[string]base.Proxy)
for i, p := range *proxyList {
proxyMap[p.GetKey()] = (*proxyList)[i]
}
key := base.GetProxyKey(proxyType, listenIP, listenPort)
if _, ok := proxyMap[key]; ok {
return fmt.Errorf("绑定的地址和端口存在冲突![%s]", key)
}
anyBindKey := fmt.Sprintf("%s@:%d", proxyType, listenPort)
if strings.Compare(key, anyBindKey) == 0 {
for exitsKey := range proxyMap {
if strings.HasSuffix(exitsKey, anyBindKey) {
return fmt.Errorf("绑定的地址和端口存在冲突![%s][%s]", key, exitsKey)
}
}
} else {
if _, ok := proxyMap[anyBindKey]; ok {
return fmt.Errorf("绑定的地址和端口存在冲突![%s]", key)
}
}
return nil
}
//getIpAndPortsFromAddress
func getIpAndPortFromAddress(address string, needip bool, needports bool) (ip string, ports string, err error) {
ipAndPortIndex := strings.LastIndex(address, ":")
// defer func() {
// fmt.Printf("\nFuck: [%s]--->[%s]", ip, ports)
// }()
if ipAndPortIndex < 0 || (!needip && !needports) {
switch {
case (!needip && needports): //地址中仅有端口
ports = address
case (needip && !needports): //地址中仅有ip
{
ip = address
}
case (!needip && !needports): //地址中 端口和ip都不是必须
{
if ipAndPortIndex > 0 {
ip = address[:ipAndPortIndex]
ports = address[ipAndPortIndex+1:]
break
}
//但address非空,判断
if address == "" {
break
}
if net.ParseIP(address) != nil {
ip = address
} else {
ports = address
if strings.HasPrefix(ports, ":") {
ports = ports[1:]
}
}
}
default:
}
return
}
ports = address[ipAndPortIndex+1:]
if ipAndPortIndex <= 1 {
//fmt.Printf("Fuck:%s\n", ports)
return
}
addressHost := address[:ipAndPortIndex]
addressHost = strings.Replace(addressHost, "[", "", -1)
addressHost = strings.Replace(addressHost, "]", "", -1)
if net.ParseIP(addressHost) == nil {
err = fmt.Errorf("ip[%s]格式有误", address[:ipAndPortIndex])
return
}
ip = addressHost
return
}
//portsStrToIList
func portsStrToIList(portsStr string) (ports []int, err error) {
if portsStr == "" {
return
}
if strings.Contains(portsStr, ",") {
tmpStrList := strings.Split(portsStr, ",")
for i := range tmpStrList {
tps, e := portsStrToIList(tmpStrList[i])
if e != nil {
err = fmt.Errorf("端口参数处理出错:%s", e.Error())
return
}
ports = append(ports, tps...)
}
return
}
portsStrList := strings.Split(portsStr, "-")
if len(portsStrList) > 2 {
err = fmt.Errorf("端口%s格式有误", portsStr)
return
}
if len(portsStrList) == 1 { //single listen port
listenPort, e := portStrToi(portsStrList[0])
if e != nil {
err = fmt.Errorf("端口格式有误!%s", e.Error())
return
}
ports = append(ports, listenPort)
}
if len(portsStrList) == 2 {
minListenPort, e := portStrToi(portsStrList[0])
if e != nil {
err = fmt.Errorf("端口格式有误!%s", portsStrList[0])
return
}
maxListenPort, e := portStrToi(portsStrList[1])
if e != nil {
err = fmt.Errorf("端口格式有误!%s", portsStrList[1])
return
}
if maxListenPort <= minListenPort {
err = fmt.Errorf("前一个端口[%d]要小于后一个端口[%d]", minListenPort, maxListenPort)
return
}
i := minListenPort
for {
if i > maxListenPort {
break
}
ports = append(ports, i)
i++
}
}
return
}
func portStrToi(portStr string) (int, error) {
port, err := strconv.Atoi(portStr)
if err != nil {
return 0, fmt.Errorf("端口格式有误:%s", err.Error())
}
if port < 1 || port > 65535 {
return 0, fmt.Errorf("端口[%d]超出范围", port)
}
return port, nil
}

251
rule/relayrule_test.go Normal file
View File

@ -0,0 +1,251 @@
package rule
import (
"testing"
)
func Test_createSubRuleListFromConfigure_1(t *testing.T) {
argsA := "53to192.168.31.1"
goportsList, _, _, _, _, _, _, e := createSubRuleListFromConfigure(argsA)
if e != nil || len(goportsList) != 2 {
t.Errorf("createSubRuleListFromConfigure [%s] testA error %v", argsA, goportsList)
return
}
if goportsList[0].ProxyType != "tcp" || len(goportsList[0].ListenPorts) != 1 || goportsList[0].TargetHost != "192.168.31.1" || len(goportsList[0].TargetPorts) != 1 || len(goportsList[0].ListenPorts) != len(goportsList[0].TargetPorts) {
t.Errorf("createSubRuleListFromConfigure [%s] testB error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[0] != 53 || goportsList[0].TargetPorts[0] != 53 {
t.Errorf("createSubRuleListFromConfigure [%s] testC error %v", argsA, goportsList)
return
}
if goportsList[1].ProxyType != "udp" || len(goportsList[0].ListenPorts) != 1 || goportsList[0].TargetHost != "192.168.31.1" || len(goportsList[0].TargetPorts) != 1 || len(goportsList[0].ListenPorts) != len(goportsList[0].TargetPorts) {
t.Errorf("createSubRuleListFromConfigure [%s] testD error %v", argsA, goportsList)
return
}
if goportsList[1].ListenPorts[0] != 53 || goportsList[1].TargetPorts[0] != 53 {
t.Errorf("createSubRuleListFromConfigure [%s] testE error %v", argsA, goportsList)
return
}
}
func Test_createSubRuleListFromConfigure_2(t *testing.T) {
argsA := "tcp@53to192.168.31.1"
goportsList, _, _, _, _, _, _, e := createSubRuleListFromConfigure(argsA)
if e != nil || len(goportsList) != 1 {
t.Errorf("createSubRuleListFromConfigure [%s] testA error %v", argsA, goportsList)
return
}
if goportsList[0].ProxyType != "tcp" || len(goportsList[0].ListenPorts) != 1 || goportsList[0].TargetHost != "192.168.31.1" || len(goportsList[0].TargetPorts) != 1 || len(goportsList[0].ListenPorts) != len(goportsList[0].TargetPorts) {
t.Errorf("createSubRuleListFromConfigure [%s] testB error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[0] != 53 || goportsList[0].TargetPorts[0] != 53 {
t.Errorf("createSubRuleListFromConfigure [%s] testC error %v", argsA, goportsList)
return
}
}
func Test_createSubRuleListFromConfigure_3(t *testing.T) {
argsA := "udp4@53to192.168.31.1"
goportsList, _, _, _, _, _, _, e := createSubRuleListFromConfigure(argsA)
if e != nil || len(goportsList) != 1 {
t.Errorf("createSubRuleListFromConfigure [%s] testA error %v", argsA, goportsList)
return
}
if goportsList[0].ProxyType != "udp4" || len(goportsList[0].ListenPorts) != 1 || goportsList[0].TargetHost != "192.168.31.1" || len(goportsList[0].TargetPorts) != 1 || len(goportsList[0].ListenPorts) != len(goportsList[0].TargetPorts) {
t.Errorf("createSubRuleListFromConfigure [%s] testB error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[0] != 53 || goportsList[0].TargetPorts[0] != 53 {
t.Errorf("createSubRuleListFromConfigure [%s] testC error %v", argsA, goportsList)
return
}
}
func Test_createSubRuleListFromConfigure_4_0(t *testing.T) {
argsList := []string{"tcp@20000-20021to192.168.31.1", "tcp@192.168.31.1:20000-20021", "tcp@20000-20021to192.168.31.1:20000-20021"}
for _, argsA := range argsList {
goportsList, _, _, _, _, _, _, e := createSubRuleListFromConfigure(argsA)
if e != nil || len(goportsList) != 1 {
t.Errorf("createSubRuleListFromConfigure [%s] testA error %v", argsA, goportsList)
return
}
if goportsList[0].ProxyType != "tcp" || len(goportsList[0].ListenPorts) != 22 || goportsList[0].TargetHost != "192.168.31.1" || len(goportsList[0].TargetPorts) != 22 || len(goportsList[0].ListenPorts) != len(goportsList[0].TargetPorts) {
t.Errorf("createSubRuleListFromConfigure [%s] testB error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[0] != 20000 || goportsList[0].TargetPorts[0] != 20000 {
t.Errorf("createSubRuleListFromConfigure [%s] testC error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[21] != 20021 || goportsList[0].TargetPorts[21] != 20021 {
t.Errorf("createSubRuleListFromConfigure [%s] testD error %v", argsA, goportsList)
return
}
}
}
func Test_createSubRuleListFromConfigure_4_1(t *testing.T) {
argsA := "tcp@30000-30021to192.168.31.1:20000-20021"
goportsList, _, _, _, _, _, _, e := createSubRuleListFromConfigure(argsA)
if e != nil || len(goportsList) != 1 {
t.Errorf("createSubRuleListFromConfigure [%s] testA error %v", argsA, goportsList)
return
}
if goportsList[0].ProxyType != "tcp" || len(goportsList[0].ListenPorts) != 22 || goportsList[0].TargetHost != "192.168.31.1" || len(goportsList[0].TargetPorts) != 22 || len(goportsList[0].ListenPorts) != len(goportsList[0].TargetPorts) {
t.Errorf("createSubRuleListFromConfigure [%s] testB error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[0] != 30000 || goportsList[0].TargetPorts[0] != 20000 {
t.Errorf("createSubRuleListFromConfigure [%s] testC error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[21] != 30021 || goportsList[0].TargetPorts[21] != 20021 {
t.Errorf("createSubRuleListFromConfigure [%s] testD error %v", argsA, goportsList)
return
}
}
func Test_createSubRuleListFromConfigure_5_0(t *testing.T) {
//argsA :=
args := []string{"tcp@80,443,20000-20021to192.168.31.1:80,443,20000-20021",
"tcp@192.168.31.1:80,443,20000-20021",
"tcp@80,443,20000-20021to192.168.31.1"}
for _, argsA := range args {
goportsList, _, _, _, _, _, _, e := createSubRuleListFromConfigure(argsA)
if e != nil || len(goportsList) != 1 {
t.Errorf("createSubRuleListFromConfigure [%s] testA error %v", argsA, goportsList)
return
}
if goportsList[0].ProxyType != "tcp" || len(goportsList[0].ListenPorts) != 24 || goportsList[0].TargetHost != "192.168.31.1" || len(goportsList[0].TargetPorts) != 24 || len(goportsList[0].ListenPorts) != len(goportsList[0].TargetPorts) {
t.Errorf("createSubRuleListFromConfigure [%s] testB error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[0] != 80 || goportsList[0].TargetPorts[0] != 80 {
t.Errorf("createSubRuleListFromConfigure [%s] testC error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[23] != 20021 || goportsList[0].TargetPorts[23] != 20021 {
t.Errorf("createSubRuleListFromConfigure [%s] testD error %v", argsA, goportsList)
return
}
}
}
func Test_createSubRuleListFromConfigure_5_1(t *testing.T) {
//argsA :=
args := []string{"tcp@80,443,30000-30021to192.168.31.1:81,443,20000-20021"}
for _, argsA := range args {
goportsList, _, _, _, _, _, _, e := createSubRuleListFromConfigure(argsA)
if e != nil || len(goportsList) != 1 {
t.Errorf("createSubRuleListFromConfigure [%s] testA error %v", argsA, goportsList)
return
}
if goportsList[0].ProxyType != "tcp" || len(goportsList[0].ListenPorts) != 24 || goportsList[0].TargetHost != "192.168.31.1" || len(goportsList[0].TargetPorts) != 24 || len(goportsList[0].ListenPorts) != len(goportsList[0].TargetPorts) {
t.Errorf("createSubRuleListFromConfigure [%s] testB error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[0] != 80 || goportsList[0].TargetPorts[0] != 81 {
t.Errorf("createSubRuleListFromConfigure [%s] testC error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[23] != 30021 || goportsList[0].TargetPorts[23] != 20021 {
t.Errorf("createSubRuleListFromConfigure [%s] testD error %v", argsA, goportsList)
return
}
}
}
func Test_createSubRuleListFromConfigure_6_0(t *testing.T) {
//argsA :=
args := []string{"tcp6@80,443to192.168.31.1:80,443",
"tcp6@80,443to192.168.31.1",
"tcp6@192.168.31.1:80,443"}
for _, argsA := range args {
goportsList, _, _, _, _, _, _, e := createSubRuleListFromConfigure(argsA)
if e != nil || len(goportsList) != 1 {
t.Errorf("createSubRuleListFromConfigure [%s] testA error %v", argsA, goportsList)
return
}
if goportsList[0].ProxyType != "tcp6" || len(goportsList[0].ListenPorts) != 2 || goportsList[0].TargetHost != "192.168.31.1" || len(goportsList[0].TargetPorts) != 2 || len(goportsList[0].ListenPorts) != len(goportsList[0].TargetPorts) {
t.Errorf("createSubRuleListFromConfigure [%s] testB error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[0] != 80 || goportsList[0].TargetPorts[0] != 80 {
t.Errorf("createSubRuleListFromConfigure [%s] testC error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[1] != 443 || goportsList[0].TargetPorts[1] != 443 {
t.Errorf("createSubRuleListFromConfigure [%s] testD error %v", argsA, goportsList)
return
}
}
}
func Test_createSubRuleListFromConfigure_6_1(t *testing.T) {
//argsA :=
args := []string{"tcp@80,443to192.168.31.1:81,443"}
for _, argsA := range args {
goportsList, _, _, _, _, _, _, e := createSubRuleListFromConfigure(argsA)
if e != nil || len(goportsList) != 1 {
t.Errorf("createSubRuleListFromConfigure [%s] testA error %v", argsA, goportsList)
return
}
if goportsList[0].ProxyType != "tcp" || len(goportsList[0].ListenPorts) != 2 || goportsList[0].TargetHost != "192.168.31.1" || len(goportsList[0].TargetPorts) != 2 || len(goportsList[0].ListenPorts) != len(goportsList[0].TargetPorts) {
t.Errorf("createSubRuleListFromConfigure [%s] testB error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[0] != 80 || goportsList[0].TargetPorts[0] != 81 {
t.Errorf("createSubRuleListFromConfigure [%s] testC error %v", argsA, goportsList)
return
}
if goportsList[0].ListenPorts[1] != 443 || goportsList[0].TargetPorts[1] != 443 {
t.Errorf("createSubRuleListFromConfigure [%s] testD error %v", argsA, goportsList)
return
}
}
}

View File

@ -0,0 +1,61 @@
package fileutils
import (
"encoding/base64"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
)
//获取当前路径
func GetCurrentDirectory() string {
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Fatal(err)
}
return dir
}
//保存base64为文件一般用于保存图片
func SaveBase64AsFile(base64Str *string, fileURL string) (err error) {
decodeStr, _ := base64.StdEncoding.DecodeString(*base64Str) //把base64写入缓存
err = ioutil.WriteFile(fileURL, decodeStr, 0666) //buffer输出到jpg文件中不做处理直接写到文件
return
}
//判断文件或文件夹是否存在
func FileExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
//保存Text到文本
func SaveTextToFile(text, fileURL string) error {
dstFile, err := os.Create(fileURL)
if err != nil {
fmt.Println(err.Error())
return err
}
defer dstFile.Close()
dstFile.WriteString(text + "\n")
return nil
}
//ReadTextFromFile 从文本读取内容
func ReadTextFromFile(path string) (string, error) {
fi, err := os.Open(path)
if err != nil {
return "", err
}
defer fi.Close()
fd, err := ioutil.ReadAll(fi)
return string(fd), nil
}

View File

@ -0,0 +1,26 @@
package fileutils
import (
"os/exec"
)
//OpenProgramOrFile 启动程序
func OpenProgramOrFile(argv []string) error {
var startArgvs []string
for i := range argv {
if i == 0 {
continue
}
startArgvs = append(startArgvs, argv[i])
}
//startArgvs = append(startArgvs, "-c")
//startArgvs = append(startArgvs, argv...)
//fmt.Printf("fuck...%v \n", startArgvs)
//cmd := exec.Command("/bin/bash", startArgvs...)
cmd := exec.Command(argv[0], startArgvs...)
return cmd.Start()
}

View File

@ -0,0 +1,17 @@
package fileutils
import "os/exec"
//OpenProgramOrFile 启动程序
func OpenProgramOrFile(argv []string) error {
var startArgvs []string
startArgvs = append(startArgvs, "/C")
startArgvs = append(startArgvs, "start")
startArgvs = append(startArgvs, argv...)
cmd := exec.Command("cmd.exe", startArgvs...)
return cmd.Start()
}

View File

@ -0,0 +1,101 @@
package ginutils
import (
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
)
//Cors 处理跨域请求,支持options访问
func Cors(params ...interface{}) gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,access_token")
c.Header("Access-Control-Allow-Methods", "POST, GET, PUT,OPTIONS")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
//放行所有OPTIONS方法
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
// 处理请求
c.Next()
}
}
func GetJWTToken(tokenString, tokenKey string) (t *jwt.Token, e error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(tokenKey), nil
})
if err != nil {
//beego.Error("Parse token:", err)
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
// That's not even a token
return nil, errors.New("errInputData")
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
// Token is either expired or not active yet
return nil, errors.New("errExpired")
} else {
// Couldn't handle this token
return nil, errors.New("errInputData")
}
} else {
// Couldn't handle this token
return nil, errors.New("errInputData")
}
}
if !token.Valid {
//beego.Error("Token invalid:", tokenString)
return nil, errors.New("errInputData")
}
return token, nil
}
// info 存储的信息
// key 加密的key
// exp 有效期
//GetJWTTokenString 获取Token字符串
func GetJWTTokenString(info map[string]interface{}, key string, exp time.Duration) (string, error) {
claims := make(jwt.MapClaims)
claims["exp"] = time.Now().Add(exp).Unix() //token 24小时有效期
for k := range info {
claims[k] = info[k]
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte(key))
if err != nil {
return "", fmt.Errorf("生成TokenString出错:%s", err.Error())
}
return tokenString, nil
}
//GetChildDomain 获取子域名部分
func GetChildDomain(host string) string {
hostSplitList := strings.Split(host, ".")
listLen := len(hostSplitList)
var resBuilder strings.Builder
for i := range hostSplitList {
if i >= listLen-2 {
break
}
if resBuilder.Len() > 0 {
resBuilder.WriteString(".")
}
resBuilder.WriteString(hostSplitList[i])
}
return resBuilder.String()
}

View File

@ -0,0 +1,68 @@
package pool
import (
"sync"
)
var (
bufPool sync.Pool
bufPool1k sync.Pool
bufPool2k sync.Pool
bufPool4k sync.Pool
bufPool8k sync.Pool
bufPool16k sync.Pool
)
const (
k16 = 16 * 1024
k8 = 8 * 1024
k4 = 4 * 1024
k2 = 2 * 1024
k1 = 1024
)
func GetBuf(size int) []byte {
var x interface{}
switch {
case size >= k16:
x = bufPool16k.Get()
case size >= k8:
x = bufPool8k.Get()
case size >= k4:
x = bufPool4k.Get()
case size >= k2:
x = bufPool2k.Get()
case size >= k1:
x = bufPool1k.Get()
default:
x = bufPool.Get()
}
if x == nil {
return make([]byte, size)
}
buf := x.([]byte)
if cap(buf) < size {
return make([]byte, size)
}
return buf[:size]
}
func PutBuf(buf interface{}) {
size := cap(buf.([]byte))
switch {
case size >= k16:
bufPool16k.Put(buf)
case size >= k8:
bufPool8k.Put(buf)
case size >= k4:
bufPool4k.Put(buf)
case size >= k2:
bufPool2k.Put(buf)
case size >= k1:
bufPool1k.Put(buf)
default:
bufPool.Put(buf)
}
}

View File

@ -0,0 +1,136 @@
package recoverutil
import (
"fmt"
"os"
"runtime/debug"
"strings"
"time"
"github.com/ljymc/goports/thirdlib/gdylib/fileutils"
"github.com/ljymc/goports/thirdlib/gdylib/stderrredirect"
)
//RecoverHandler 恢复处理
func RecoverHandler(recoverErr interface{}, exit, reboot bool, panicFileURL string) {
if recoverErr == nil {
return
}
outputPanicV2(panicFileURL, recoverErr)
if reboot {
var argvsBuilder strings.Builder
for i := range os.Args {
if i == 0 {
continue
}
if argvsBuilder.Len() == 0 {
argvsBuilder.WriteString(os.Args[i])
} else {
argvsBuilder.WriteString(" ")
argvsBuilder.WriteString(os.Args[i])
}
}
fileutils.OpenProgramOrFile(os.Args) //重启程序
//fileutil.OpenProgramOrFile(restartURIBuilder.String())
}
if exit {
os.Exit(1)
}
}
func outputPanic(panicFileURL string, recoverErr interface{}) {
exeName := os.Args[0] //获取程序名称
now := time.Now() //获取当前时间
pid := os.Getpid() //获取进程ID
if !strings.Contains(panicFileURL, ":") && !strings.HasPrefix(panicFileURL, "/") { //相对路径
panicFileURL = fmt.Sprintf("%s%s%s", fileutils.GetCurrentDirectory(), string(os.PathSeparator), panicFileURL)
}
fileDir := ""
lastIndex := strings.LastIndex(panicFileURL, string(os.PathSeparator))
if lastIndex > 0 {
fileDir = panicFileURL[:lastIndex]
}
if err := os.MkdirAll(fileDir, 0755); err != nil {
panic(fmt.Sprintf("创建错误重定向文件夹路径出错:%s", err.Error()))
}
file, err := os.OpenFile(panicFileURL, os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
panic(fmt.Sprintf("panic重定向出错:%s", err.Error()))
}
defer file.Close()
timeStr := now.Format("2006-01-02 15:04:05") //设定时间格式
file.WriteString("\n\n\n↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓\r\n")
file.WriteString(fmt.Sprintf("%s-%d-%s dump LOG\r\n", exeName, pid, timeStr))
file.WriteString(fmt.Sprintf("%v\r\n", err)) //输出panic信息
file.WriteString(string(debug.Stack())) //输出堆栈信息
file.WriteString("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑\r\n")
// timeStr := now.Format("20060102150405") //设定时间格式
// fname := fmt.Sprintf("%s-%d-%s-dump.log", exeName, pid, timeStr) //保存错误信息文件名:程序名-进程ID-当前时间(年月日时分秒)
// fmt.Println("dump to file ", fname)
// f, err := os.Create(fname)
// if err != nil {
// return
// }
// defer f.Close()
// if recoverErr != nil {
// f.WriteString(fmt.Sprintf("%v\r\n", err)) //输出panic信息
// f.WriteString("========\r\n")
// }
// f.WriteString(string(debug.Stack())) //输出堆栈信息
}
func outputPanicV2(panicFileURL string, recoverErr interface{}) {
exeName := os.Args[0] //获取程序名称
now := time.Now() //获取当前时间
pid := os.Getpid() //获取进程ID
setPanicRedirect := true
if panicFileURL == "" { //空路径不设置
setPanicRedirect = false
}
if !strings.Contains(panicFileURL, ":") && !strings.HasPrefix(panicFileURL, "/") { //相对路径
panicFileURL = fmt.Sprintf("%s%s%s", fileutils.GetCurrentDirectory(), string(os.PathSeparator), panicFileURL)
}
// fileDir := ""
// lastIndex := strings.LastIndex(panicFileURL, string(os.PathSeparator))
// if lastIndex > 0 {
// fileDir = panicFileURL[:lastIndex]
// }
// if err := os.MkdirAll(fileDir, 0755); err != nil {
// panic(fmt.Sprintf("创建错误重定向文件夹路径出错:%s", err.Error()))
// }
if setPanicRedirect {
stderrredirect.PanicRedirect(panicFileURL)
}
timeStr := now.Format("2006-01-02 15:04:05") //设定时间格式
stderrredirect.PanicFile.WriteString("\n\n\n↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓\r\n")
stderrredirect.PanicFile.WriteString(fmt.Sprintf("%s-%d-%s dump LOG\r\n", exeName, pid, timeStr))
// stderrredirect.PanicFile.WriteString(fmt.Sprintf("%v\r\n", err)) //输出panic信息
stderrredirect.PanicFile.WriteString(string(debug.Stack())) //输出堆栈信息
stderrredirect.PanicFile.WriteString("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑\r\n")
// file, err := os.OpenFile(panicFileURL, os.O_CREATE|os.O_APPEND, 0666)
// if err != nil {
// panic(fmt.Sprintf("panic重定向出错:%s", err.Error()))
// }
// defer file.Close()
}

View File

@ -0,0 +1,48 @@
package stderrredirect
import (
"fmt"
"os"
"strings"
"sync"
"syscall"
"github.com/ljymc/goports/thirdlib/gdylib/fileutils"
)
var PanicFile *os.File
var doOnce sync.Once
//PanicRedirect panic重定向
func PanicRedirect(fileURL string) {
doOnce.Do(func() {
if !strings.HasPrefix(fileURL, "/") { //相对路径
fileURL = fmt.Sprintf("%s%s%s", fileutils.GetCurrentDirectory(), string(os.PathSeparator), fileURL)
}
lastIndex := strings.LastIndex(fileURL, string(os.PathSeparator))
fileDir := ""
if lastIndex > 0 {
fileDir = fileURL[:lastIndex]
}
if err := os.MkdirAll(fileDir, 0755); err != nil {
panic(fmt.Sprintf("创建错误重定向文件夹路径出错:%s", err.Error()))
}
file, err := os.OpenFile(fileURL, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
PanicFile = file
if err != nil {
return
}
if err = syscall.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil {
return
}
return
})
}

View File

@ -0,0 +1,58 @@
package stderrredirect
import (
"fmt"
"os"
"strings"
"sync"
"syscall"
"github.com/ljymc/goports/thirdlib/gdylib/fileutils"
)
//错误输出重定向,用于捕获闪退信息
//PanicFile 记录闪退的文件
var PanicFile *os.File
var doOnce sync.Once
//PanicRedirect panic重定向
func PanicRedirect(fileURL string) {
doOnce.Do(func() {
if !strings.Contains(fileURL, ":") { //相对路径
fileURL = fmt.Sprintf("%s%s%s", fileutils.GetCurrentDirectory(), string(os.PathSeparator), fileURL)
}
//fmt.Printf("FileURL:%s\n", fileURL)
lastIndex := strings.LastIndex(fileURL, string(os.PathSeparator))
fileDir := ""
if lastIndex > 0 {
fileDir = fileURL[:lastIndex]
}
if err := os.MkdirAll(fileDir, 0755); err != nil {
panic(fmt.Sprintf("创建错误重定向文件夹路径出错:%s", err.Error()))
}
//fileDir := strings.LastIndex(fileURL, string(os.PathSeparator))
file, err := os.OpenFile(fileURL, os.O_CREATE|os.O_APPEND, 0666)
PanicFile = file
if err != nil {
panic(fmt.Sprintf("panic重定向出错:%s", err.Error()))
}
kernel32 := syscall.NewLazyDLL("kernel32.dll")
setStdHandle := kernel32.NewProc("SetStdHandle")
sh := syscall.STD_ERROR_HANDLE
v, _, err := setStdHandle.Call(uintptr(sh), uintptr(file.Fd()))
if v == 0 {
return
}
})
}

View File

@ -0,0 +1,61 @@
package stringsp
import (
"math/rand"
"strings"
"sync"
"time"
)
var strModel = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
var stringsBytes = []byte(strModel)
var strModelLength = len(strModel)
var numStrModel = "0123456789"
var numStrBytes = []byte(numStrModel)
var numStrLength = len(numStrModel)
var randVar = rand.New(rand.NewSource(time.Now().UnixNano()))
//GetRandomString 生成随机字符串
func GetRandomString(len int) string {
var resBuf strings.Builder
for i := 0; i < len; i++ {
resBuf.WriteByte(stringsBytes[randVar.Intn(strModelLength)])
}
return resBuf.String()
}
//GetRandomStringNum 生成随机数字字符串
func GetRandomStringNum(len int) string {
var resBuf strings.Builder
for i := 0; i < len; i++ {
resBuf.WriteByte(numStrBytes[randVar.Intn(numStrLength)])
}
return resBuf.String()
}
var timeStampIDMutex sync.Mutex
var pretimeStampID int64 = 0
//GetTimeStampID 获取时间戳ID
func GetTimeStampID() int64 {
timeStampIDMutex.Lock()
defer timeStampIDMutex.Unlock()
id := time.Now().UnixNano()
CHECK:
if id == pretimeStampID || id < pretimeStampID {
if id < pretimeStampID {
id = pretimeStampID + 1
} else {
id++
}
goto CHECK
}
pretimeStampID = id
return id
}

16
web.go Normal file
View File

@ -0,0 +1,16 @@
//go:build adminweb
// +build adminweb
package main
import (
"fmt"
"github.com/ljymc/goports/web"
"log"
)
func RunAdminWeb(listenPort int) {
listen := fmt.Sprintf(":%d", listenPort)
go web.RunAdminWeb(listen)
log.Printf("AdminWeb listen on %s", listen)
}

35
web/ginutils.go Normal file
View File

@ -0,0 +1,35 @@
package web
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func HandlerStaticFiles() gin.HandlerFunc {
fileServer := http.FileServer(http.FS(stafs))
return func(c *gin.Context) {
staticFile := isStaticFile(http.FS(stafs), c.Request.URL.Path, true)
if staticFile {
fileServer.ServeHTTP(c.Writer, c.Request)
c.Abort()
}
c.Next()
}
}
//
func isStaticFile(fs http.FileSystem, name string, redirect bool) (isFile bool) {
const indexPage = "/index.html"
if strings.HasSuffix(name, indexPage) {
return true
}
f, err := fs.Open(name)
if err != nil {
return false
}
defer f.Close()
_, err = f.Stat()
return err == nil
}

28
web/goports-adminviews/.gitignore vendored Normal file
View File

@ -0,0 +1,28 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
}

View File

@ -0,0 +1,29 @@
# goports-adminviews
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0" />
<title>goports-admin</title>
</head>
<body style="margin:0">
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8452
web/goports-adminviews/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
{
"name": "goports-adminviews",
"version": "0.1.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview --port 4173"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.4",
"axios": "^0.27.2",
"element-plus": "^2.2.2",
"vue": "^3.2.36",
"vue-clipboard3": "^2.0.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.3.3",
"@vue/cli-plugin-typescript": "~5.0.0",
"babel-plugin-import": "^1.13.5",
"sass": "^1.52.1",
"unplugin-auto-import": "^0.8.6",
"unplugin-vue-components": "^0.19.6",
"vite": "^2.9.9"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,141 @@
<template>
<div class="common-layout">
<el-container>
<el-header class="header" id="header" >
<!-- <p class="title">大吉</p>
<p class="version">version:{{version}}</p> -->
<Pmenu class="menu"></Pmenu>
</el-header>
<el-container>
<!-- <el-aside width="initial" id="aside" direction="vertical" >
<Pmenu v-if="global.currentPage.value=='#login'?false:true"></Pmenu>
</el-aside> -->
<el-container class="body">
<el-main id="pageContent">
<Log v-if="global.currentPage.value=='#log'?true:false"></Log>
<Status v-if="global.currentPage.value=='#status'?true:false"></Status>
<Relayset v-if="global.currentPage.value=='#relayset'?true:false"></Relayset>
<Pset v-if="global.currentPage.value=='#set'?true:false"></Pset>
<Login v-if="global.currentPage.value=='#login'?true:false"></Login>
<WhiteListSet v-if="global.currentPage.value=='#whitelistset'?true:false"></WhiteListSet>
<WhiteLists v-if="global.currentPage.value=='#whitelists'?true:false"></WhiteLists>
<BlackLists v-if="global.currentPage.value=='#blacklists'?true:false"></BlackLists>
</el-main>
</el-container>
</el-container>
</el-container>
</div>
</template>
<script setup lang="ts">
import { onMounted,ref,inject ,computed } from 'vue'
import Status from './components/status.vue'
import Log from './components/log.vue';
import Pmenu from './components/pmenu.vue';
import Relayset from './components/relayset.vue';
import Pset from './components/pset.vue';
import Login from './components/login.vue';
import WhiteListSet from './components/WhiteListSet.vue';
import WhiteLists from './components/WhiteLists.vue';
import BlackLists from './components/BlackLists.vue';
import {apiGetVersion} from "./apis/utils.js"
//console.log("111")
const global:any = inject("global")
const version = ref("0.0.0")
const queryVersion = ()=>{
apiGetVersion().then((res) => {
if (res.ret == 0) {
version.value = res.version
return
}
}).catch((error) => {
})
}
onMounted(() => {
//console.log("222")
//queryVersion()
})
</script>
<style scoped>
#pageContent{
margin:0;
height: 95vh;
overflow: hidden;
padding-left: 1px;
padding-right: 0px;
width: 100%;
}
body{
margin: 0;
width:100%;
}
#header {
background-color: #0d8bbb;
height: fit-content;
width:100%;
padding:0
}
.title {
float:left;
text-align: left;
margin-left: 10%;
font-size:25px;
}
.menu {
float:left;
height: 30px;
width:100vw;
}
.version{
float:right;
}
.title,.version{
width:100%;
color: aliceblue;
font-family: "Helvetica Neue",Helvetica,"PingFang SC","Hiragino Sans GB","Microsoft YaHei","微软雅黑",Arial,sans-serif;
}
</style>

View File

@ -0,0 +1,39 @@
/**
* 使用方式
* * * main.js挂载全局
* import storage from './utils/storage';
const app = createApp(App);
app.config.globalProperties.$storage = storage;
* * * vue3.0 全局使用 vue实例上的数据
* import { defineComponent, reactive, getCurrentInstance } from "vue";
* let { appContext } = getCurrentInstance();
appContext.config.globalProperties.$storage.setItem("username","aaa");
appContext.config.globalProperties.$storage.setItem("age",20);
appContext.config.globalProperties.$storage.clearItem("age");
appContext.config.globalProperties.$storage.clearAll();
*/
export default {
getStorage () { // 先获取该项目的 命名存储空间 下的storage数据 maneger
return JSON.parse(window.localStorage.getItem("goports") || "{}");
},
setItem (key, val) {
let storage = this.getStorage()
// console.log("setItem", storage);
storage[key] = val; // 为当前对象添加 需要存储的值
window.localStorage.setItem("goports", JSON.stringify(storage)) // 保存到本地
},
getItem (key) {
return this.getStorage()[key]
},
// 清空 当前的项目下命名存储的空间 该key项的 Storage 数据
clearItem (key) {
let storage = this.getStorage()
delete storage[key]
window.localStorage.setItem(config.namespace, JSON.stringify(storage)) // 保存到本地
},
// 清空所有的 当前的项目下命名存储的空间 Storage 数据
clearAll () {
window.localStorage.clear();
}
}

View File

@ -0,0 +1,215 @@
// 导入axios实例
import httpRequest from '@/request/index'
import storage from './storage.js'
// 获取锚点
export function GetHash() {
return location.hash
}
// 设置锚点
export function SetHash(hash) {
location.hash = hash
}
export function GetToken(){
//console.log("getTokenkkk: "+storage.getItem("token"))
return storage.getItem("token")==undefined?"":storage.getItem("token");
}
// 获取状态信息
export function apiGetStatus() {
return httpRequest({
url: '/api/status',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf()},
})
}
export function apiGetLogs(pretimestamp) {
return httpRequest({
url: "/api/logs",
method: 'get',
headers:{'Authorization':GetToken()},
params:{pre:pretimestamp,_:new Date().valueOf()}
})
}
export function apiGetRuleList() {
return httpRequest({
url: '/api/rulelist',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf()}
})
}
export function apiAddRule(data) {
return httpRequest({
url: '/api/rule',
method: 'post',
headers:{'Authorization':GetToken()},
data:data
})
}
export function apiDeleteRule(configure) {
return httpRequest({
url: '/api/rule',
method: 'delete',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf(),rule:configure}
})
}
export function apiAlterRule(data) {
return httpRequest({
url: '/api/rule',
method: 'put',
headers:{'Authorization':GetToken()},
data:data
})
}
export function apiRuleEnable(key,enable) {
return httpRequest({
url: '/api/rule/enable',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf(),enable:enable,key:key}
})
}
export function apiQueryBaseConfigure() {
return httpRequest({
url: '/api/baseconfigure',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf()}
})
}
export function apiAlterBaseConfigure(data) {
return httpRequest({
url: '/api/baseconfigure',
method: 'put',
headers:{'Authorization':GetToken()},
data:data
})
}
export function apiLogin(data) {
return httpRequest({
url: '/api/login',
method: 'post',
data:data
})
}
export function apiRebootProgram() {
return httpRequest({
url: '/api/reboot_program',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf()}
})
}
export function apiAlterWhiteListConfigure(data) {
return httpRequest({
url: '/api/whitelist/configure',
method: 'put',
headers:{'Authorization':GetToken()},
data:data
})
}
export function apiGetWhiteListConfigure(data) {
return httpRequest({
url: '/api/whitelist/configure',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf()}
})
}
export function apiGetWhiteList(data) {
return httpRequest({
url: '/api/whitelist',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf()},
})
}
export function apiFlushWhiteList(ip,life) {
return httpRequest({
url: '/api/whitelist/flush',
method: 'put',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf(),ip:ip,life:life}
})
}
export function apiDeleteWhiteList(ip,life) {
return httpRequest({
url: '/api/whitelist',
method: 'delete',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf(),ip:ip},
})
}
export function apiGetBlackList(data) {
return httpRequest({
url: '/api/blacklist',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf()}
})
}
export function apiFlushBlackList(ip,life) {
return httpRequest({
url: '/api/blacklist/flush',
method: 'put',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf(),ip:ip,life:life}
})
}
export function apiDeleteBlackList(ip,life) {
return httpRequest({
url: '/api/blacklist',
method: 'delete',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf(),ip:ip}
})
}
export function apiGetVersion() {
return httpRequest({
url: '/version',
method: 'get',
params:{_:new Date().valueOf()}
})
}
export function apiLogout() {
return httpRequest({
url: '/api/logout',
method: 'put',
headers:{'Authorization':GetToken()},
})
}

View File

@ -0,0 +1,10 @@
.PageRadius {
height: 90vh;
width: 100%;
max-width: 1300px;
border: 1px solid var(--el-border-color);
border-radius: 0;
margin: 20px
}

View File

@ -0,0 +1,74 @@
/* color palette from <https://github.com/vuejs/theme> */
:root {
--vt-c-white: #ffffff;
--vt-c-white-soft: #f8f8f8;
--vt-c-white-mute: #f2f2f2;
--vt-c-black: #181818;
--vt-c-black-soft: #222222;
--vt-c-black-mute: #282828;
--vt-c-indigo: #2c3e50;
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
--vt-c-text-light-1: var(--vt-c-indigo);
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
--vt-c-text-dark-1: var(--vt-c-white);
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
}
/* semantic color variables for this project */
:root {
--color-background: var(--vt-c-white);
--color-background-soft: var(--vt-c-white-soft);
--color-background-mute: var(--vt-c-white-mute);
--color-border: var(--vt-c-divider-light-2);
--color-border-hover: var(--vt-c-divider-light-1);
--color-heading: var(--vt-c-text-light-1);
--color-text: var(--vt-c-text-light-1);
--section-gap: 160px;
}
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);
--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);
--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
position: relative;
font-weight: normal;
}
body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
transition: color 0.5s, background-color 0.5s;
line-height: 1.6;
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
font-size: 15px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View File

@ -0,0 +1,32 @@
.common-layout {
.el-header,
.el-footer,
.el-main,
.el-aside {
display: flex;
justify-content: center;
align-items: center;
}
.el-header,
.el-footer {
background-color: var(--el-color-primary-light-7);
color: var(--el-text-color-primary);
text-align: center;
}
.el-aside {
background-color: var(--el-color-primary-light-8);
color: var(--el-text-color-primary);
text-align: center;
}
.el-main {
background-color: var(--el-color-primary-light-9);
color: var(--el-text-color-primary);
text-align: center;
height: 150px;
}
}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69" xmlns:v="https://vecta.io/nano"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 308 B

View File

@ -0,0 +1,221 @@
<template>
<div class="PageRadius" :style="{
borderRadius: 'base',
}">
<el-affix position="top" :offset="0" class="affix-container">
<el-button type="primary" @click="showAddBlackListDialog">黑名单添加 <el-icon>
<Plus />
</el-icon>
</el-button>
</el-affix>
<el-scrollbar height="100%">
<div class="formradius" :style="{
borderRadius: 'base',
}">
<el-table :data="Blacklist" style="width: 700px" height="85vh">
<el-table-column prop="IP" label="IP" width="200" />
<el-table-column prop="Effectivetime" label="有效时间" width="200" />
<el-table-column fixed="right" label="操作" width="300" >
<template #default="list">
<el-button link type="primary" size="small"
@click="flushBlackListEffectivetime(list.$index, Blacklist[list.$index], 0, '确认要刷新IP[' + Blacklist[list.$index].IP + ']的有效时间?')">
刷新有效时间</el-button>
<el-button link type="primary" size="small"
@click="flushBlackListEffectivetime(list.$index, Blacklist[list.$index], 666666, '确认要设置IP[' + Blacklist[list.$index].IP + ']为长期有效?')">
设置永久有效</el-button>
<el-button link type="primary" size="small"
@click="deleteBlackList(list.$index, Blacklist[list.$index])">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-scrollbar>
<el-dialog v-model="addBlackListDialogVisible" title="添加黑名单IP" draggable :show-close="false" width="400px">
<el-form :model="addBlackListForm">
<el-form-item label="IP" label-width="auto">
<el-input v-model="addBlackListForm.IP" autocomplete="off" />
</el-form-item>
<el-form-item label="有效时间(小时)" label-width="auto">
<el-input-number v-model="addBlackListForm.Life" :min="1" :max="999999" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="addBlackListDialogVisible = false">取消</el-button>
<el-button type="primary" @click="addBlackList">添加</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue'
import { ElMessageBox } from 'element-plus'
import {MessageShow} from '../utils/ui'
import {isIP} from '../utils/utils'
import { apiGetBlackList, apiFlushBlackList, apiDeleteBlackList } from '../apis/utils'
var Blacklist = ref([{ IP: "", Effectivetime: "" }])
Blacklist.value.splice(0, 1)
const addBlackListDialogVisible = ref(false)
const addBlackListForm = ref({ IP: "", Life: 0 })
const showAddBlackListDialog = () => {
addBlackListDialogVisible.value = true
addBlackListForm.value.IP = ""
addBlackListForm.value.Life = 666666
}
const flushBlackListEffectivetime = (index, item, life, text) => {
ElMessageBox.confirm(
text,
'Warning',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
flushBlackListlife(index, item.IP, life)
})
.catch(() => {
})
}
const flushBlackListlife = (index, ip, life) => {
apiFlushBlackList(ip, life).then((res) => {
if (res.ret == 0) {
Blacklist.value[index].Effectivetime = res.data
return
}
MessageShow("error", res.msg)
}).catch((error) => {
MessageShow("error", "刷新IP[" + ip + "]有效时间出错")
})
}
const addBlackList = () => {
if (!isIP(addBlackListForm.value.IP)) {
MessageShow("error", "IP格式有误,请检查修正后再添加")
return
}
apiFlushBlackList(addBlackListForm.value.IP, addBlackListForm.value.Life).then((res) => {
if (res.ret == 0) {
let item = { IP: addBlackListForm.value.IP, Effectivetime: res.data }
Blacklist.value.push(item)
addBlackListDialogVisible.value = false
// MessageShow("success", "")
return
}
MessageShow("error", res.msg)
}).catch((error) => {
MessageShow("error", "刷新IP[" + addBlackListForm.value.IP + "]有效时间出错")
})
}
const deleteBlackList = (index, item) => {
ElMessageBox.confirm(
'确认要删除IP [' + item.IP + "]的黑名单记录?",
'Warning',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
apiDeleteBlackList(item.IP).then((res) => {
if (res.ret == 0) {
Blacklist.value.splice(index, 1)
return
}
MessageShow("error", res.msg)
}).catch((error) => {
MessageShow("error", "删除[" + item.IP + "]的黑名单记录出错")
})
})
.catch(() => {
})
}
const queryBlackList = () => {
apiGetBlackList().then((res) => {
if (res.ret == 0) {
Blacklist.value = res.data
return
}
MessageShow("error", res.msg)
//console.log("getAdminURL "+getAdminURL())
}).catch((error) => {
MessageShow("error", "查询黑名单列表出错")
})
}
const keydown = (e) => {
if (e.keyCode != 13) {
return
}
if (!addBlackListDialogVisible.value) {
return
}
addBlackList()
}
onMounted(() => {
queryBlackList();
window.addEventListener('keydown', keydown)
})
</script>
<style scoped>
.formradius {
border: 0px solid var(--el-border-color);
border-radius: 0;
margin: 0 auto;
width: fit-content;
padding: 15px;
}
</style>

View File

@ -0,0 +1,112 @@
<template>
<!-- <div class="logterm">
{{weblogsContent}}
</div> -->
<el-scrollbar max-height="95vh" class="logtermv2" v-loading="logLoading" element-loading-background="transparent">
{{weblogsContent}}
<br>
<br>
<br>
<br>
<br>
<br>
</el-scrollbar>
</template>
<script setup lang="ts">
import {apiGetLogs } from '../apis/utils'
import { onMounted,onUnmounted,ref,inject } from 'vue'
import {GetHash,SetHash} from'../apis/utils.js'
const global:any = inject("global")
var preLogTimestamp = ""
var preStartTime = ""
const weblogsContent = ref("")
var logLoading = ref(true)
function queryLastlogs() {
// if(GetHash()!="#log"){
// return ;
// }
apiGetLogs(preLogTimestamp).then((res) => {
logLoading.value =false
if (preStartTime!=res.starttime){
weblogsContent.value = ""
preStartTime = res.starttime
}
if (res.logs!=null && res.logs.length > 0) {
if (res.logs[0].timestamp<=preLogTimestamp){
return
}
preLogTimestamp = res.logs[res.logs.length - 1].timestamp
for(var i=0;i<res.logs.length;i++){
weblogsContent.value += res.logs[i].log +"\n"
}
}
})
}
var timerID:any
onMounted(() => {
queryLastlogs();
timerID = setInterval(() => {
queryLastlogs();
}, 1000);
})
onUnmounted(()=>{
clearInterval(timerID)
})
</script>
<style>
.logtermv2 {
background-color: black;
height: 95vh;
width: 100%;
color: white;
text-align: left;
padding-left: 3px;
border: 10px;
overflow-y: auto;
overflow-x: auto;
white-space: pre;
}
.logterm::-webkit-scrollbar {
width: 5px;
}
.logterm::-webkit-scrollbar-thumb {
background-color: #c0c0c0;
border-radius: 10%;
}
</style>

View File

@ -0,0 +1,135 @@
<template>
<div class="PageRadius" :style="{
borderRadius: 'base',
}">
<div class="formradius" :style="{
borderRadius: 'base',
}">
<el-form :model="form" class="SetForm" label-width="auto">
<el-form-item label="管理账号" id="account">
<el-input v-model="form.Account" placeholder="管理账号" autocomplete="off" style="witdh:390px;" />
</el-form-item>
<el-form-item label="管理密码" id="account">
<el-input show-password v-model="form.Password" placeholder="管理密码" autocomplete="off"
style="witdh:390px;" />
</el-form-item>
<el-form-item>
<el-checkbox v-model="rememberPasswordChecked" label="记住密码" size="large" @change="rememberPasswordCheckedChange" />
</el-form-item>
</el-form>
<el-button type="primary" round @click="Login">登录</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, inject } from 'vue'
import { apiLogin } from '../apis/utils'
import {MessageShow} from '../utils/ui'
const rememberPasswordChecked = ref(true)
const global: any = inject("global")
const form = ref({
Account: "",
Password: ""
})
const rememberPasswordCheckedChange= (checked )=>{
//console.log(":"+checked)
SaveLoginInfo(checked)
}
const SaveLoginInfo = (checked) =>{
global.storage.setItem("rememberPassword",checked)
if (checked){
global.storage.setItem("loginAccount",form.value.Account)
global.storage.setItem("loginPassword",form.value.Password)
return
}
global.storage.setItem("loginAccount","")
global.storage.setItem("loginPassword","")
}
const ReadLoginInfo = ()=>{
let rememberPassword = global.storage.getItem("rememberPassword")
rememberPasswordChecked.value = rememberPassword == undefined || rememberPassword == false ? false : true;
if(!rememberPassword){
return
}
form.value.Account= global.storage.getItem("loginAccount")==undefined?"":global.storage.getItem("loginAccount")
form.value.Password = global.storage.getItem("loginPassword")==undefined?"":global.storage.getItem("loginPassword")
}
const Login = () => {
if (form.value.Account == "" || form.value.Password == "") {
MessageShow("error", "账号或密码不能为空")
return
}
SaveLoginInfo(rememberPasswordChecked.value)
apiLogin(form.value).then((res) => {
if (res.ret == 0) {
MessageShow("success", "登录成功")
global.storage.setItem("token",res.token)
global.currentPage.value = "#set"
location.hash="#set"
//console.log("cookies:"+res.cookies)
return
}
MessageShow("error", res.msg)
}).catch((error) => {
console.log("登录失败,网络请求出错:" + error)
MessageShow("error", "登录失败,网络请求出错")
})
}
const keydown = (e) => {
if (e.keyCode == 13) {
Login()
}
}
onMounted(() => {
window.addEventListener('keydown', keydown)
ReadLoginInfo()
})
</script>
<style scoped>
.formradius {
border: 0px solid var(--el-border-color);
border-radius: 0;
margin: 0 auto;
width: fit-content;
padding: 15px;
}
</style>

View File

@ -0,0 +1,233 @@
<template>
<div class="PageRadius" :style="{
borderRadius: 'base',
}">
<el-scrollbar height="100%">
<div class="formradius" :style="{
borderRadius: 'base',
}" >
<el-form :model="form" class="SetForm" label-width="auto">
<el-form-item label="后台管理端口" id="adminListen">
<el-input-number v-model="form.AdminWebListenPort" autocomplete="off" />
</el-form-item>
<el-form-item label="外网访问" id="adminListen">
<el-switch v-model="form.AllowInternetaccess" class="mb-1"
inline-prompt
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
width="50px"
active-text="允许"
inactive-text="禁止" />
</el-form-item>
<el-form-item label="管理登录账号" id="adminAccount">
<el-input v-model="form.AdminAccount" placeholder="管理登录账号" autocomplete="off" style="witdh:390px;" />
</el-form-item>
<el-form-item label="管理登录密码" id="adminPassword">
<el-input v-model="form.AdminPassword" placeholder="管理登录密码" autocomplete="off" />
</el-form-item>
<el-form-item label="全局最大端口代理数量" id="proxyCountLimit">
<el-input-number v-model="form.ProxyCountLimit" autocomplete="off" :min="1" :max="1024" />
</el-form-item>
<el-form-item label="全局最大并发连接数" id="globalMaxConnections">
<el-input-number v-model="form.GlobalMaxConnections" autocomplete="off" :min="1" :max="65535" />
</el-form-item>
</el-form>
<el-button type="primary" round @click="RequestAlterConfigure">保存修改</el-button>
<el-button type="info" round @click="resetFormData">撤销改动</el-button>
<el-button type="danger" round @click="rebootProgram" :disabled="disableRebootButton">重启程序</el-button>
</div>
</el-scrollbar>
</div>
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, ref, computed, reactive } from 'vue'
import { apiQueryBaseConfigure,apiAlterBaseConfigure,apiRebootProgram } from '../apis/utils'
import { ElMessageBox } from 'element-plus'
import {MessageShow} from '../utils/ui'
const formLabelWidth = '10vw'
console.log("window.location.href "+window.location.href)
console.log("window.location.port "+window.location.port)
console.log("window.location.host "+window.location.host)
console.log("window.location "+JSON.stringify(window.location))
const disableRebootButton = ref(false)
const getAdminURL = ()=>{
return window.location.protocol +"//"+window.location.hostname+":"+preFormData.value.AdminWebListenPort
}
const rawData = {
AdminWebListenPort: 1,
AdminAccount: "",
AdminPassword: "",
ProxyCountLimit: 1,
GlobalMaxConnections: 1,
AllowInternetaccess: false,
}
const form = ref(rawData)
const preFormData = ref(rawData)
const resetFormData = ()=>{
form.value.AdminWebListenPort = preFormData.value.AdminWebListenPort
form.value.AdminAccount=preFormData.value.AdminAccount
form.value.AdminPassword=preFormData.value.AdminPassword
form.value.ProxyCountLimit = preFormData.value.ProxyCountLimit
form.value.GlobalMaxConnections = preFormData.value.GlobalMaxConnections
form.value.AllowInternetaccess = preFormData.value.AllowInternetaccess
}
const syncToPreFormData = (data:any)=>{
preFormData.value.AdminWebListenPort = data.value.AdminWebListenPort
preFormData.value.AdminAccount=data.value.AdminAccount
preFormData.value.AdminPassword=data.value.AdminPassword
preFormData.value.ProxyCountLimit = data.value.ProxyCountLimit
preFormData.value.GlobalMaxConnections = data.value.GlobalMaxConnections
preFormData.value.AllowInternetaccess = data.value.AllowInternetaccess
}
const rebootProgram = () => {
disableRebootButton.value = true;
ElMessageBox.confirm(
'确定要重启goports?',
'Warning',
{
confirmButtonText: '确认',
cancelButtonText: '点错了',
type: 'warning',
}
)
.then(() => {
apiRebootProgram().then((res) => {
MessageShow("success", "重启成功,3秒后自动跳转到新登录连接")
setTimeout(()=>{
window.location.href = getAdminURL()
},3000)
//console.log("getAdminURL "+getAdminURL())
}).catch((error) => {
disableRebootButton.value = false;
console.log("重启操作出错:" + error)
MessageShow("error", "重启操作出错")
})
})
.catch(() => {
disableRebootButton.value = false;
})
}
const queryConfigure = ()=>{
apiQueryBaseConfigure().then((res) => {
if (res.ret==0){
form.value = res.baseconfigure
syncToPreFormData(form)
return
}
MessageShow("error", "获取基本配置出错")
}).catch((error) => {
console.log("获取转发规则列表出错:" + error)
MessageShow("error", "获取基本配置出错")
})
}
const RequestAlterConfigure =()=>{
apiAlterBaseConfigure(form.value).then((res) => {
if (res.ret == 0) {
MessageShow("success", "配置修改成功")
syncToPreFormData(form)
return
}
MessageShow("error", res.msg)
}).catch((error) => {
console.log("配置修改失败,网络请求出错:" + error)
MessageShow("error", "配置修改失败,网络请求出错")
})
}
onMounted(() => {
queryConfigure()
})
</script>
<style scoped>
.SetForm {
margin-top: 15px;
margin-left: 20px;
}
.formradius{
border: 0px solid var(--el-border-color);
border-radius: 0;
margin:0 auto;
width:fit-content;
padding:15px;
}
#adminListen {
width: 360px;
}
#adminAccount {
width: 30vw;
max-width: 360px;
min-width: 300px;
}
#adminPassword {
width: 30vw;
max-width: 360px;
min-width: 300px;
}
#proxyCountLimit {
width: 360px;
}
#globalMaxConnections {
width: 360px;
}
</style>

View File

@ -0,0 +1,184 @@
<template>
<el-menu :default-active="activeIndex" class="el-menu-demo menu" mode="horizontal" :ellipsis="false"
@select="handleSelect">
<el-sub-menu index="#menu" v-if="global.currentPage.value != '#login' ? true : false">
<template #title>
<el-icon>
<Menu />
</el-icon>
<span>菜单</span>
</template>
<el-menu-item index="#status">
<el-icon>
<DataAnalysis />
</el-icon>
<template #title>总览</template>
</el-menu-item>
<el-menu-item index="#log">
<el-icon>
<document />
</el-icon>
<template #title>程序日志</template>
</el-menu-item>
<el-sub-menu index="#relay">
<template #title>
<el-icon>
<Position />
</el-icon>
<span>端口转发</span>
</template>
<el-menu-item index="#relayset">
<el-icon>
<List />
</el-icon>
<template #title>转发规则</template>
</el-menu-item>
<el-menu-item index="#whitelistset">
<el-icon>
<Setting />
</el-icon>
<template #title>白名单设置</template>
</el-menu-item>
<el-menu-item index="#whitelists">
<el-icon>
<List />
</el-icon>
<template #title>白名单列表</template>
</el-menu-item>
<el-menu-item index="#blacklists">
<el-icon>
<List />
</el-icon>
<template #title>黑名单列表</template>
</el-menu-item>
</el-sub-menu>
<el-menu-item index="#set">
<el-icon>
<setting />
</el-icon>
<template #title>设置</template>
</el-menu-item>
<el-menu-item index="#logout">
<el-icon>
<Close />
</el-icon>
<template #title>退出登录</template>
</el-menu-item>
</el-sub-menu>
<div class="flex-grow" />
<el-menu-item index="#logo">goports {{ version }}</el-menu-item>
</el-menu>
</template>
<script setup lang="ts">
import { inject, ref, onMounted } from 'vue';
import { SetHash, apiGetVersion } from '../apis/utils.js'
import { ElMessageBox } from 'element-plus'
const global: any = inject("global")
const activeIndex = ref('#set')
const version = ref("")
console.log("currentPage[menu]:" + global.currentPage.value)
const queryVersion = () => {
apiGetVersion().then((res) => {
if (res.ret == 0) {
version.value = res.version
return
}
}).catch((error) => {
})
}
function handleOpen(key, keyPath) {
//console.log(key, keyPath);
}
function handleClose(key, keyPath) {
//console.log(key, keyPath);
}
function handleSelect(key, keyPath, item, routeResult) {
//console.log("")
//console.log(key, keyPath, item, routeResult);
// switchView(key);
switch (key) {
case "#logout":
ElMessageBox.confirm(
'确定要注销登录?',
'Warning',
{
confirmButtonText: '确认',
cancelButtonText: '点错了',
type: 'warning',
}
)
.then(() => {
SetHash(key)
})
.catch(() => {
})
break;
case "#logo":
window.open("https://github.com/ljymc/goports", "_blank");
break;
default:
SetHash(key)
break;
}
}
onMounted(() => {
queryVersion()
})
</script>
<style>
.menu {
background-color: #d9ecff;
}
.flex-grow {
flex-grow: 1;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,99 @@
<template>
<div id="status" v-loading="logLoading" element-loading-background="transparent">
<p class="status">总内存:{{ status.totleMem }} 已用:{{ status.usedMem }} 未用:{{ status.unusedMem }}</p>
<p class="status">CPU全局使用率:{{ status.usedCPU }}</p>
<p class="status">当前进程CPU使用率:{{ status.currentProcessUsedCPU }}</p>
<p class="status">进程协程数:{{ status.goroutine }} 占用内存:{{ status.processUsedMem }}</p>
<p class="status">goports全局连接数:{{ status.currentConnections }} </p>
<p class="status">goports全局限制连接数:{{ status.maxConnections }}</p>
</div>
</template>
<style>
#status {
height: 95vh;
}
.status {
font-size: 15px;
}
#proxys_status {
font-size: 10px;
list-style: none;
padding: 0;
margin: 0;
text-align: left;
margin-left: 0px;
}
</style>
<script setup lang="ts">
import { apiGetStatus } from '../apis/utils'
import { onMounted, onUnmounted, ref,inject } from 'vue'
import { ElNotification } from 'element-plus'
const global:any = inject("global")
var logLoading = ref(true)
const clickTest = ()=>{
ElNotification({
title: 'Prompt',
message: 'This is a message that does not automatically close',
duration: 0,
})
}
var status=ref({totleMem: '0m',
usedMem:'0m',
unusedMem:'0m',
usedCPU:"0%",
currentProcessUsedCPU:"0%",
goroutine:"0",
processUsedMem:"0m",
currentConnections:0,
maxConnections:0,
proxysStatus:""})
var timerID:any
function flushStatus() {
if(global.currentPage.value!="#status"){
return ;
}
apiGetStatus().then((res) => {
logLoading.value =false
status.value = res.data
})
}
onMounted(() => {
flushStatus();
timerID = setInterval(() => {
flushStatus();
}, 3000);
});
onUnmounted(() => {
//console.log("onUnmounted status page ")
clearInterval(timerID)
});
</script>

View File

@ -0,0 +1,145 @@
<template>
<div class="PageRadius" :style="{
borderRadius: 'base',
}">
<el-scrollbar height="100%">
<div class="formradius" :style="{
borderRadius: 'base',
}">
<div class="whitelistConfigure">
<el-form :model="whiteListBaseConfigureForm" class="SetForm" label-width="auto">
<el-form-item label="自定义URL" id="whitelisturl">
<el-input v-model="whiteListBaseConfigureForm.URL" placeholder="自定义URL"
autocomplete="off" style="witdh:250px;margin-bottom:4px;" />
<el-tooltip class="box-item oneLine" effect="dark" placement="bottom"
:content="getWhiteListURL">
<el-button type="info" round @click="copyRelayConfigure(getWhiteListURL)"
style="margin-right: 10px;">复制</el-button>
</el-tooltip>
<a>{{ getNewWhiteListURL }}</a>
</el-form-item>
<el-form-item label="有效时长(小时)" id="whitelistActivelifeDuration">
<el-input-number v-model="whiteListBaseConfigureForm.ActivelifeDuration"
autocomplete="off" :min="1" :max="99999" />
</el-form-item>
<el-form-item label="认证账号" id="basicAccount">
<el-input v-model="whiteListBaseConfigureForm.BasicAccount" placeholder="认证账号"
autocomplete="off" style="witdh:250px;" />
</el-form-item>
<el-form-item label="认证密码" id="basicPassword">
<el-input v-model="whiteListBaseConfigureForm.BasicPassword" placeholder="认证密码"
autocomplete="off" />
</el-form-item>
</el-form>
<el-button type="primary" round @click="SaveWhiteListConfigure">保存配置</el-button>
</div>
</div>
</el-scrollbar>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue'
import {MessageShow} from '../utils/ui'
import {CopyTotoClipboard} from '../utils/utils'
import { apiAlterWhiteListConfigure, apiGetWhiteListConfigure} from '../apis/utils'
const whiteListBaseConfigureForm = ref({
URL: "",
ActivelifeDuration: 36,
BasicAccount: "",
BasicPassword: "",
})
const preWhiteListBaseConfigureForm = ref({
URL: "",
ActivelifeDuration: 36,
BasicAccount: "",
BasicPassword: "",
})
const getWhiteListURL = computed(() => {
if (preWhiteListBaseConfigureForm.value.URL == undefined || preWhiteListBaseConfigureForm.value.URL == "") {
return window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/wl"
}
return window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/wl/" + preWhiteListBaseConfigureForm.value.URL
})
const getNewWhiteListURL = computed(() => {
if (whiteListBaseConfigureForm.value.URL == undefined || whiteListBaseConfigureForm.value.URL == "") {
return window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/wl"
}
return window.location.protocol + "//" + window.location.hostname + ":" + window.location.port + "/wl/" + whiteListBaseConfigureForm.value.URL
})
const copyRelayConfigure = (url: string) => {
CopyTotoClipboard(url)
MessageShow('success', '白名单认证地址 ' + url + ' 已复制到剪切板')
}
const SaveWhiteListConfigure = () => {
apiAlterWhiteListConfigure(whiteListBaseConfigureForm.value).then((res) => {
if (res.ret == 0) {
MessageShow("success", "保存成功")
preWhiteListBaseConfigureForm.value = whiteListBaseConfigureForm.value
return
}
MessageShow("error", res.msg)
//console.log("getAdminURL "+getAdminURL())
}).catch((error) => {
MessageShow("error", "查询白名单设置出错")
})
}
const queryWhiteListConfigure = () => {
apiGetWhiteListConfigure().then((res) => {
if (res.ret == 0) {
whiteListBaseConfigureForm.value = ref(res.data).value
preWhiteListBaseConfigureForm.value.URL = whiteListBaseConfigureForm.value.URL
preWhiteListBaseConfigureForm.value.ActivelifeDuration = whiteListBaseConfigureForm.value.ActivelifeDuration
preWhiteListBaseConfigureForm.value.BasicAccount = whiteListBaseConfigureForm.value.BasicAccount
preWhiteListBaseConfigureForm.value.BasicPassword = whiteListBaseConfigureForm.value.BasicPassword
return
}
MessageShow("error", res.msg)
//console.log("getAdminURL "+getAdminURL())
}).catch((error) => {
MessageShow("error", "查询白名单设置出错")
})
}
onMounted(() => {
queryWhiteListConfigure()
})
</script>
<style scoped>
.formradius{
border: 0px solid var(--el-border-color);
border-radius: 0;
margin:0 auto;
width:fit-content;
padding:15px;
}
</style>

View File

@ -0,0 +1,219 @@
<template>
<div class="PageRadius" :style="{
borderRadius: 'base',
}">
<el-affix position="bottom" :offset="0" class="affix-container">
<el-button type="primary" @click="showAddWhiteListDialog">白名单添加 <el-icon>
<Plus />
</el-icon>
</el-button>
</el-affix>
<el-scrollbar height="100%">
<div class="formradius" :style="{
borderRadius: 'base',
}" >
<el-table :data="whitelist" style="width: 700px" height="85vh">
<el-table-column prop="IP" label="IP" width="200" />
<el-table-column prop="Effectivetime" label="有效时间" width="200" />
<el-table-column fixed="right" label="操作" width="300">
<template #default="list">
<el-button link type="primary" size="small"
@click="flushWhiteListEffectivetime(list.$index, whitelist[list.$index], 0, '确认要刷新IP[' + whitelist[list.$index].IP + ']的有效时间?')">
刷新有效时间</el-button>
<el-button link type="primary" size="small"
@click="flushWhiteListEffectivetime(list.$index, whitelist[list.$index], 666666, '确认要设置IP[' + whitelist[list.$index].IP + ']为长期有效?')">
设置永久有效</el-button>
<el-button link type="primary" size="small"
@click="deleteWhiteList(list.$index, whitelist[list.$index])">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-scrollbar>
<el-dialog v-model="addWhiteListDialogVisible" title="添加白名单IP" draggable :show-close="false" width="400px">
<el-form :model="addWhiteListForm">
<el-form-item label="IP" label-width="auto">
<el-input v-model="addWhiteListForm.IP" autocomplete="off" />
</el-form-item>
<el-form-item label="有效时间(小时)" label-width="auto">
<el-input-number v-model="addWhiteListForm.Life" :min="1" :max="999999" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="addWhiteListDialogVisible = false">取消</el-button>
<el-button type="primary" @click="addWhiteList">添加</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue'
import { ElMessageBox } from 'element-plus'
import {MessageShow} from '../utils/ui'
import {isIP} from '../utils/utils'
import { apiGetWhiteList, apiFlushWhiteList, apiDeleteWhiteList, apiGetBlackList, apiFlushBlackList, apiDeleteBlackList } from '../apis/utils'
var whitelist = ref([{ IP: "", Effectivetime: "" }])
whitelist.value.splice(0, 1)
const addWhiteListDialogVisible = ref(false)
const addWhiteListForm = ref({ IP: "", Life: 0 })
const showAddWhiteListDialog = () => {
addWhiteListDialogVisible.value = true
addWhiteListForm.value.IP = ""
addWhiteListForm.value.Life = 24
}
const flushWhiteListEffectivetime = (index, item, life, text) => {
ElMessageBox.confirm(
text,
'Warning',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
flushWhiteListlife(index, item.IP, life)
})
.catch(() => {
})
}
const flushWhiteListlife = (index, ip, life) => {
apiFlushWhiteList(ip, life).then((res) => {
if (res.ret == 0) {
whitelist.value[index].Effectivetime = res.data
return
}
MessageShow("error", res.msg)
}).catch((error) => {
MessageShow("error", "刷新IP[" + ip + "]有效时间出错")
})
}
const addWhiteList = () => {
if (!isIP(addWhiteListForm.value.IP)) {
MessageShow("error", "IP格式有误,请检查修正后再添加")
return
}
apiFlushWhiteList(addWhiteListForm.value.IP, addWhiteListForm.value.Life).then((res) => {
if (res.ret == 0) {
let item = { IP: addWhiteListForm.value.IP, Effectivetime: res.data }
whitelist.value.push(item)
addWhiteListDialogVisible.value = false
//MessageShow("success", "")
return
}
MessageShow("error", res.msg)
}).catch((error) => {
MessageShow("error", "刷新IP[" + addWhiteListForm.value.IP + "]有效时间出错")
})
}
const deleteWhiteList = (index, item) => {
ElMessageBox.confirm(
'确认要删除IP [' + item.IP + "]的白名单记录?",
'Warning',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
apiDeleteWhiteList(item.IP).then((res) => {
if (res.ret == 0) {
whitelist.value.splice(index, 1)
return
}
MessageShow("error", res.msg)
}).catch((error) => {
MessageShow("error", "删除[" + item.IP + "]的白名单记录出错")
})
})
.catch(() => {
})
}
const queryWhiteList = () => {
apiGetWhiteList().then((res) => {
if (res.ret == 0) {
whitelist.value = res.data
return
}
MessageShow("error", res.msg)
//console.log("getAdminURL "+getAdminURL())
}).catch((error) => {
MessageShow("error", "查询白名单列表出错")
})
}
const keydown = (e)=>{
if (e.keyCode != 13) {
return
}
if(!addWhiteListDialogVisible.value ){
return
}
addWhiteList()
}
onMounted(() => {
queryWhiteList();
window.addEventListener('keydown', keydown)
})
</script>
<style scoped>
.formradius {
border: 0px solid var(--el-border-color);
border-radius: 0;
margin: 0 auto;
width: fit-content;
padding: 15px;
}
</style>

View File

@ -0,0 +1,71 @@
import { createApp,ref } from 'vue'
import App from './App.vue'
import './assets/common-layout.scss'
import './assets/appbase.css'
import * as ElIcon from '@element-plus/icons-vue'
import 'element-plus/theme-chalk/el-notification.css'
import 'element-plus/theme-chalk/el-menu.css'
import 'element-plus/theme-chalk/el-loading.css'
import 'element-plus/theme-chalk/el-message.css'
import 'element-plus/theme-chalk/el-message-box.css'
import 'element-plus/theme-chalk/el-button.css'
import storage from './apis/storage.js'
import {apiLogout} from './apis/utils.js'
import {PageExist,CurrentPage} from './utils/utils'
const app = createApp(App)
for (let iconName in ElIcon){
app.component(iconName, ElIcon[iconName])
}
app.config.globalProperties.$storage = storage;
if(!PageExist(location.hash)){
location.hash="#status"
}
//配置全局变量
//默认页面
var currentPage = ref(location.hash)
// if (process.env.NODE_ENV=="development"){
// currentPage.value="#relayset"
// location.hash="#relayset"
// }
app.provide('global',{
currentPage,
storage,
})
window.onpopstate = function (event){
currentPage.value=location.hash
CurrentPage.value = location.hash
if(location.hash == "#logout"){//注销登录
apiLogout().then((res) => {
}).catch((error) => {
})
storage.setItem("token","")
location.hash ="#login"
return
}
if(!PageExist(location.hash)){
location.hash ="#login"
return
}
}
app.mount('#app')

View File

@ -0,0 +1,73 @@
import axios from 'axios'
console.log("vue run mode "+process.env.NODE_ENV)
var baseURL = "/" //
if (process.env.NODE_ENV=="development"){
//开发环境下这个改为自己的接口地址
baseURL = 'http://192.168.31.70:16601'
}
//var fuck = storage.getItem("cookies")
//console.log("fuck:"+fuck)
//console.log("baseURL: "+ baseURL)
// 创建一个 axios 实例
const service = axios.create({
baseURL: baseURL, // 所有的请求地址前缀部分
timeout: 5000, // 请求超时时间毫秒
withCredentials: false, // 异步请求携带cookie
headers: {
// 设置后端需要的传参类型
'Content-Type': 'application/json',
//'X-Requested-With': 'XMLHttpRequest',
},
})
// 添加请求拦截器
service.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
return config
},
function (error) {
// 对请求错误做些什么
console.log(error)
return Promise.reject(error)
}
)
// 添加响应拦截器
service.interceptors.response.use(
function (response) {
//console.log(response)
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
// dataAxios 是 axios 返回数据中的 data
const dataAxios = response.data
// 这个状态码是和后端约定的
const code = dataAxios.reset
//console.log("dataAxios data: "+JSON.stringify(dataAxios))
//console.log("ret: "+dataAxios.ret)
if (dataAxios.ret!=undefined&& dataAxios.ret==-1){
//global.currentPage.value="set"
console.log("登录失效")
//window.location.href="/"
location.hash ="#login"
//var currentPage = ref("#login")
}
return dataAxios
},
function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
console.log(error)
return Promise.reject(error)
}
)
export default service

View File

@ -0,0 +1,26 @@
import { ElMessage, ElMessageBox } from 'element-plus'
// ElMessageBox.alert(message, {
// confirmButtonText: '好的',
// callback: () => {
// },
// })
export function ShowMessageBox(message: string) {
ElMessageBox.alert(message, {
confirmButtonText: '好的',
callback: () => {
},
})
}
export function MessageShow(type:any,message: string) {
ElMessage({
message: message,
type: type,
})
}

View File

@ -0,0 +1,26 @@
import useClipboard from 'vue-clipboard3'
import { ref } from 'vue'
export function CopyTotoClipboard(data: string) {
useClipboard().toClipboard(data)
}
const ipReg = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){6}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^::([\da-fA-F]{1,4}:){0,4}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:):([\da-fA-F]{1,4}:){0,3}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){2}:([\da-fA-F]{1,4}:){0,2}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){3}:([\da-fA-F]{1,4}:){0,1}((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){4}:((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$|^([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}$|^:((:[\da-fA-F]{1,4}){1,6}|:)$|^[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,5}|:)$|^([\da-fA-F]{1,4}:){2}((:[\da-fA-F]{1,4}){1,4}|:)$|^([\da-fA-F]{1,4}:){3}((:[\da-fA-F]{1,4}){1,3}|:)$|^([\da-fA-F]{1,4}:){4}((:[\da-fA-F]{1,4}){1,2}|:)$|^([\da-fA-F]{1,4}:){5}:([\da-fA-F]{1,4})?$|^([\da-fA-F]{1,4}:){6}:$/
export function isIP(ip :string){
return ipReg.test(ip)
}
const MenuIndexList = ["#status","#log","#relayset","#whitelistset","#whitelists","#blacklists","#set","#login"]
export function PageExist(page:string) {
for(let i in MenuIndexList){
if (MenuIndexList[i]==page){
return true
}
}
return false
}
export const CurrentPage = ref("")

View File

@ -0,0 +1,24 @@
import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
})],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})

98
web/log.go Normal file
View File

@ -0,0 +1,98 @@
package web
import (
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
)
var maxWebLogsNum = 100
var webLogsStore []WebLog
var webLogs WebLogs
var startTimeStamp string
type WebLogs struct {
preTimeStamp int64
timeStampIndex int64
}
// WebLogs
type WebLog struct {
Timestamp int64
Log string
}
func (wlog *WebLogs) Write(p []byte) (n int, err error) {
nowTime := time.Now()
tripContent := strings.TrimSpace(string(p)[20:])
content := fmt.Sprintf("%s %s", nowTime.Format("2006-01-02 15:04:05"), tripContent)
if webLogs.preTimeStamp == nowTime.UnixNano() {
webLogs.timeStampIndex++
} else {
webLogs.timeStampIndex = 0
}
l := WebLog{Timestamp: nowTime.UnixNano() + webLogs.timeStampIndex, Log: content}
webLogsStore = append(webLogsStore, l)
webLogs.preTimeStamp = nowTime.UnixNano()
if len(webLogsStore) > maxWebLogsNum {
webLogsStore = webLogsStore[len(webLogsStore)-maxWebLogsNum:]
}
return len(p), nil
}
// 初始化日志
func init() {
log.SetOutput(io.MultiWriter(&webLogs, os.Stdout))
// log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
var cstZone = time.FixedZone("CST", 8*3600) // 东八
time.Local = cstZone
startTimeStamp = fmt.Sprintf("%d", time.Now().UnixNano())
}
// Logs web
func Logs(c *gin.Context) {
preTimeStampStr := c.Query("pre")
preTimeStamp, err := strconv.ParseInt(preTimeStampStr, 10, 64)
if err != nil {
preTimeStamp = 0
}
var logs []interface{}
for _, l := range webLogsStore {
if l.Timestamp <= preTimeStamp {
continue
}
m := make(map[string]interface{})
m["timestamp"] = fmt.Sprintf("%d", l.Timestamp)
m["log"] = l.Log
logs = append(logs, m)
}
c.JSON(http.StatusOK, gin.H{
"ret": 0,
"starttime": startTimeStamp,
"logs": logs,
})
}
// ClearLog
func ClearLog(writer http.ResponseWriter, request *http.Request) {
webLogsStore = webLogsStore[:0]
}

763
web/web.go Normal file
View File

@ -0,0 +1,763 @@
package web
import (
"crypto/subtle"
"embed"
"encoding/base64"
"fmt"
"io/fs"
"log"
"net"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"unsafe"
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"github.com/ljymc/goports/base"
"github.com/ljymc/goports/config"
"github.com/ljymc/goports/rule"
"github.com/ljymc/goports/thirdlib/gdylib/fileutils"
"github.com/ljymc/goports/thirdlib/gdylib/ginutils"
"github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/mem"
"github.com/shirou/gopsutil/v3/process"
)
//go:embed goports-adminviews/dist
var staticFs embed.FS
var stafs fs.FS
var loginErrorCount = int32(0)
var rebootOnce sync.Once
//store := cookie.NewStore([]byte("secret11111"))
//var fileServer http.Handler
//var cookieStore cookie.Store
func init() {
stafs, _ = fs.Sub(staticFs, "goports-adminviews/dist")
//cookieStore = cookie.NewStore([]byte("goports2022"))
}
func RunAdminWeb(listen string) {
//gin.Default()
gin.SetMode(gin.ReleaseMode)
r := gin.New()
if gin.Mode() != gin.ReleaseMode {
r.Use(gin.Logger(), gin.Recovery())
} else {
r.Use(gin.Recovery())
}
r.Use(checkLocalIP)
//r.Use(sessions.Sessions("goportssession", cookieStore))
r.Use(gzip.Gzip(gzip.DefaultCompression))
// if config.GetRunMode() == "dev" {
// r.Use(CrosHandler())
// }
r.Use(ginutils.Cors())
r.Use(HandlerStaticFiles())
//r.Use(sessionCheck())
//r.StaticFS("/", http.FS(stafs))
authorized := r.Group("/")
authorized.Use(tokenCheck())
{
authorized.GET("/api/logs", Logs)
authorized.GET("/api/status", status)
authorized.GET("/api/test", test)
authorized.GET("/api/rulelist", rulelist)
authorized.POST("/api/rule", addrule)
authorized.DELETE("/api/rule", deleterule)
authorized.PUT("/api/rule", alterrule)
authorized.GET("/api/rule/enable", enablerule)
authorized.GET("/api/baseconfigure", baseconfigure)
authorized.PUT("/api/baseconfigure", alterBaseConfigure)
authorized.GET("/api/reboot_program", rebootProgram)
authorized.GET("/api/whitelist/configure", whitelistConfigure)
authorized.PUT("/api/whitelist/configure", alterWhitelistConfigure)
authorized.GET("/api/whitelist", querywhitelist)
authorized.PUT("/api/whitelist/flush", flushwhitelist)
authorized.DELETE("/api/whitelist", deletewhitelist)
authorized.GET("/api/blacklist", queryblacklist)
authorized.PUT("/api/blacklist/flush", flushblacklist)
authorized.DELETE("/api/blacklist", deleteblacklist)
r.PUT("/api/logout", logout)
}
r.POST("/api/login", login)
r.GET("/wl", whitelistBasicAuth, whilelistAdd)
r.GET("/wl/:url", whitelistBasicAuth, whilelistAdd)
r.GET("/version", queryVersion)
//r.Use(func() *gin.Context {})
err := r.Run(listen)
if err != nil {
log.Printf("http.ListenAndServe error:%s", err.Error())
time.Sleep(time.Minute)
os.Exit(1)
}
}
func logout(c *gin.Context) {
config.FlushLoginRandomKey()
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": "已注销登录"})
}
func queryVersion(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ret": 0, "version": config.GetVersion()})
}
func deleteblacklist(c *gin.Context) {
ip := c.Query("ip")
err := config.BlackListDelete(ip)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "删除黑名单指定IP出错"})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0})
}
func deletewhitelist(c *gin.Context) {
ip := c.Query("ip")
err := config.WhiteListDelete(ip)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "删除白名单指定IP出错"})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0})
}
func flushblacklist(c *gin.Context) {
ip := c.Query("ip")
activelifeDurationStr := c.Query("life")
life, _ := strconv.Atoi(activelifeDurationStr)
newTime, err := config.BlackListAdd(ip, int32(life))
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "刷新IP有效期出错"})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": newTime})
}
func flushwhitelist(c *gin.Context) {
ip := c.Query("ip")
activelifeDurationStr := c.Query("life")
life, _ := strconv.Atoi(activelifeDurationStr)
newTime, err := config.WhiteListAdd(ip, int32(life))
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "刷新IP有效期出错"})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": newTime})
}
func queryblacklist(c *gin.Context) {
resList := config.GetBlackList()
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": resList})
}
func querywhitelist(c *gin.Context) {
resList := config.GetWhiteList()
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": resList})
}
func whitelistBasicAuth(c *gin.Context) {
bc := config.GetWhiteListBaseConfigure()
whilelistURL := c.Param("url")
if (c.Request.RequestURI == "/wl" && bc.URL != "") || whilelistURL != bc.URL {
c.AbortWithStatus(http.StatusNotFound)
return
}
realm := "Basic realm=" + strconv.Quote("Authorization Required")
pairs := processAccounts(gin.Accounts{bc.BasicAccount: bc.BasicPassword})
user, found := pairs.searchCredential(c.GetHeader("Authorization"))
if !found {
// Credentials doesn't match, we return 401 and abort handlers chain.
c.Header("WWW-Authenticate", realm)
c.AbortWithStatus(http.StatusUnauthorized)
return
}
c.Set("user", user)
}
func whilelistAdd(c *gin.Context) {
lifeTime, err := config.WhiteListAdd(c.ClientIP(), 0)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "记录白名单IP出错"})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": fmt.Sprintf("IP已记录进白名单"), "ip": c.ClientIP(), " effective_time": lifeTime})
}
func whitelistConfigure(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": config.GetWhiteListBaseConfigure()})
}
func alterWhitelistConfigure(c *gin.Context) {
var requestObj config.WhiteListBaseConfigure
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "修改请求解析出错"})
return
}
requestObj.BasicAccount = strings.TrimSpace(requestObj.BasicAccount)
if len(requestObj.BasicAccount) == 0 || len(requestObj.BasicPassword) == 0 {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "账号或密码不能为空"})
return
}
err = config.SetWhiteListBaseConfigure(requestObj.ActivelifeDuration, requestObj.URL, requestObj.BasicAccount, requestObj.BasicPassword)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "保存白名单配置出错"})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0})
return
}
func checkLocalIP(c *gin.Context) {
clientIP := c.ClientIP()
//fmt.Printf("clientIP:%s\n", clientIP)
bc := config.GetBaseConfigure()
if !isLocalIP(clientIP) && !bc.AllowInternetaccess {
c.JSON(http.StatusForbidden, gin.H{"ret": 1, "msg": "Forbidden Internetaccess "})
c.Abort()
return
}
}
func tokenCheck() gin.HandlerFunc {
return func(c *gin.Context) {
// if config.GetRunMode() == "dev" {
// c.Next()
// return
// }
tokenString, _ := c.GetQuery("Authorization")
if tokenString == "" {
tokenString = c.GetHeader("Authorization")
}
token, err := ginutils.GetJWTToken(tokenString, "strings")
if err != nil {
c.AbortWithStatusJSON(http.StatusOK, gin.H{"ret": -1, "msg": "登录失效"})
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
c.AbortWithStatusJSON(http.StatusOK, gin.H{"ret": -1, "msg": "登录失效"})
return
}
account := claims["account"].(string)
password := claims["password"].(string)
loginKey := claims["loginkey"].(string)
if account == "" || password == "" {
c.AbortWithStatusJSON(http.StatusOK, gin.H{"ret": -1, "msg": "登录失效"})
return
}
bc := config.GetBaseConfigure()
// //fmt.Printf("session中的account:%s password:%s\n", account, password)
if bc.AdminAccount != account || bc.AdminPassword != password || loginKey != config.GetLoginRandomKey() {
c.AbortWithStatusJSON(http.StatusOK, gin.H{"ret": -1, "msg": "登录失效"})
return
}
c.Next()
}
}
func rebootProgram(c *gin.Context) {
rebootOnce.Do(func() {
go func() {
fileutils.OpenProgramOrFile(os.Args)
os.Exit(0)
}()
})
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
func login(c *gin.Context) {
var requestObj struct {
Account string `json:"Account"`
Password string `json:"Password"`
}
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "登录失败,登录请求解析出错"})
return
}
if atomic.LoadInt32(&loginErrorCount) >= 99 {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "登录错误次数太多,后台登录功能已禁用,请重启程序."})
return
}
bc := config.GetBaseConfigure()
if bc.AdminAccount != requestObj.Account || bc.AdminPassword != requestObj.Password {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "登录失败,账号或密码有误"})
atomic.AddInt32(&loginErrorCount, 1)
return
}
config.FlushLoginRandomKey()
tokenInfo := make(map[string]interface{})
tokenInfo["account"] = requestObj.Account //用户名
tokenInfo["password"] = requestObj.Password
tokenInfo["loginkey"] = config.GetLoginRandomKey()
tokenString, err := ginutils.GetJWTTokenString(tokenInfo, "strings", time.Hour*24)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "登录失败,token生成出错"})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": "登录成功", "token": tokenString})
}
func alterBaseConfigure(c *gin.Context) {
var requestObj config.BaseConfigure
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
requestObj.AdminAccount = strings.TrimSpace(requestObj.AdminAccount)
if len(requestObj.AdminAccount) == 0 || len(requestObj.AdminPassword) == 0 {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "账号或密码不能为空"})
return
}
preBaseConfigure := config.GetBaseConfigure()
if preBaseConfigure.AdminWebListenPort != requestObj.AdminWebListenPort && !config.CheckTCPPortAvalid(requestObj.AdminWebListenPort) { //检测新端口
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("新的后端管理监听端口[%d]已被占用,修改设置失败", requestObj.AdminWebListenPort)})
return
}
err = config.SetBaseConfigure(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": "保存配置过程发生错误,请检测相关启动配置"})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0})
}
func baseconfigure(c *gin.Context) {
conf := config.GetBaseConfigure()
c.JSON(http.StatusOK, gin.H{"ret": 0, "baseconfigure": conf})
}
func enablerule(c *gin.Context) {
enable := c.Query("enable")
key := c.Query("key")
var err error
var r *rule.RelayRule
var syncSuccess bool
if enable == "true" {
r, syncSuccess, err = rule.EnableRelayRuleByKey(key)
} else {
r, syncSuccess, err = rule.DisableRelayRuleByKey(key)
}
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("开关规则出错:%s", err.Error())})
return
}
log.Printf("[%s] relayRule[%s][%s]", enable, r.Name, r.MainConfigure)
syncRes := ""
if !syncSuccess {
syncRes = "同步规则状态到配置文件出错"
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": "", "syncres": syncRes})
}
func alterrule(c *gin.Context) {
var requestRule rule.RelayRule
err := c.BindJSON(&requestRule)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("修改请求解析出错:%s", err.Error())})
return
}
//fmt.Printf("balance:%v\n", requestRule.BalanceTargetAddressList)
preConfigureStr := requestRule.MainConfigure
configureStr := requestRule.CreateMainConfigure()
// configureStr := fmt.Sprintf("%s@%s:%sto%s:%s",
// requestRule.RelayType,
// requestRule.ListenIP, requestRule.ListenPorts,
// requestRule.TargetIP, requestRule.TargetPorts)
r, err := rule.CreateRuleByConfigureAndOptions(requestRule.Name, configureStr, requestRule.Options)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("修改转发规则[%s]时出错:%s", preConfigureStr, err.Error())})
return
}
syncSuccess, err := rule.AlterRuleInGlobalRuleListByKey(preConfigureStr, r)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("修改转发规则[%s]时出错:%s", preConfigureStr, err.Error())})
return
}
r, _, err = rule.EnableRelayRuleByKey(r.MainConfigure)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": fmt.Sprintf("修改转发规则成功,但启用规则时出错:%s", err.Error())})
return
}
log.Printf("修改转发规则[%s][%s]成功", r.Name, r.MainConfigure)
synsRes := ""
if !syncSuccess {
synsRes = "同步修改规则数据到配置文件出错"
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": "修改转发规则成功", "syncres": synsRes})
}
func deleterule(c *gin.Context) {
ruleKey := c.Query("rule")
rule.DisableRelayRuleByKey(ruleKey)
syncSuccess, err := rule.DeleteGlobalRuleByKey(ruleKey)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("删除转发规则出错:%s", err.Error())})
return
}
syncRes := ""
if !syncSuccess {
syncRes = "同步规则信息到配置文件出错"
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": "删除成功", "syncres": syncRes})
}
func addrule(c *gin.Context) {
var requestRule rule.RelayRule
err := c.BindJSON(&requestRule)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("请求解析出错:%s", err.Error())})
return
}
// configureStr := fmt.Sprintf("%s@%s:%sto%s:%s",
// requestRule.RelayType,
// requestRule.ListenIP, requestRule.ListenPorts,
// requestRule.TargetIP, requestRule.TargetPorts)
configureStr := requestRule.CreateMainConfigure()
r, err := rule.CreateRuleByConfigureAndOptions(requestRule.Name, configureStr, requestRule.Options)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("创建转发规则出错:%s", err.Error())})
return
}
synsRes, err := rule.AddRuleToGlobalRuleList(true, *r)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("添加转发规则出错:%s", err.Error())})
return
}
r, _, err = rule.EnableRelayRuleByKey(r.MainConfigure)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": fmt.Sprintf("启用规则出错:%s", err.Error())})
return
}
log.Printf("添加转发规则[%s][%s]成功", r.Name, r.MainConfigure)
if synsRes != "" {
synsRes = "保存配置文件出错,请检查配置文件设置"
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": "添加规则并启用成功", "syncres": synsRes})
}
func rulelist(c *gin.Context) {
ruleList, proxyListInfoMap := rule.GetRelayRuleList()
type ruleItem struct {
Name string `json:"Name"`
MainConfigure string `json:"Mainconfigure"`
RelayType string `json:"RelayType"`
ListenIP string `json:"ListenIP"`
ListenPorts string `json:"ListenPorts"`
TargetIP string `json:"TargetIP"`
TargetPorts string `json:"TargetPorts"`
BalanceTargetAddressList []string `json:"BalanceTargetAddressList"`
Options base.RelayRuleOptions `json:"Options"`
SubRuleList []rule.SubRelayRule `json:"SubRuleList"`
From string `json:"From"`
IsEnable bool `json:"Enable"`
ProxyList []rule.RelayRuleProxyInfo `json:"ProxyList"`
}
//proxyListInfoMap[(*ruleList)[i].MainConfigure]
var data []ruleItem
for i := range *ruleList {
item := ruleItem{
Name: (*ruleList)[i].Name,
MainConfigure: (*ruleList)[i].MainConfigure,
RelayType: (*ruleList)[i].RelayType,
ListenIP: (*ruleList)[i].ListenIP,
ListenPorts: (*ruleList)[i].ListenPorts,
TargetIP: (*ruleList)[i].TargetIP,
TargetPorts: (*ruleList)[i].TargetPorts,
Options: (*ruleList)[i].Options,
SubRuleList: (*ruleList)[i].SubRuleList,
From: (*ruleList)[i].From,
IsEnable: (*ruleList)[i].IsEnable,
ProxyList: proxyListInfoMap[(*ruleList)[i].MainConfigure],
BalanceTargetAddressList: (*ruleList)[i].BalanceTargetAddressList,
}
data = append(data, item)
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": data})
}
func test(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"ret": 0})
}
func status(c *gin.Context) {
v, _ := mem.VirtualMemory()
currentProcessMem := GetCurrentProcessMem()
//fmt.Fprintf(w, "当前进程 CPU使用率:%.2f%% 协程数:%d 进程内存使用:%s 系统内存总量:%s 已用:%s 可用:%s \n", GetCurrentProcessCPUPrecent(), runtime.NumGoroutine(), formatFileSize(currentProcessMem), formatFileSize(v.Total), formatFileSize(v.Used), formatFileSize(v.Free))
//fmt.Fprintf(w, "当前全局TCP 连接数:%d 全局TCP连接数最大限制:%d\n", core.GetGlobalTCPConns(), core.GetGlobalMaxConnections())
//var proxyStatusList []string
// for _, p := range *config.GlobalProxy {
// //fmt.Fprintf(w, "%s\n", p.GetStatus())
// proxyStatusList = append(proxyStatusList, p.GetStatus())
// }
respMap := make(map[string]interface{})
respMap["totleMem"] = formatFileSize(v.Total)
respMap["usedMem"] = formatFileSize(v.Used)
respMap["unusedMem"] = formatFileSize(v.Free)
respMap["currentProcessUsedCPU"] = fmt.Sprintf("%.2f%%", GetCurrentProcessCPUPrecent())
respMap["goroutine"] = fmt.Sprintf("%d", runtime.NumGoroutine())
respMap["processUsedMem"] = formatFileSize(currentProcessMem)
respMap["currentConnections"] = fmt.Sprintf("%d", base.GetGlobalConnections())
respMap["maxConnections"] = fmt.Sprintf("%d", base.GetGlobalMaxConnections())
respMap["usedCPU"] = fmt.Sprintf("%.2f%%", GetCpuPercent())
//respMap["proxysStatus"] = proxyStatusList
c.JSON(http.StatusOK, gin.H{
"ret": 0,
"data": respMap,
})
}
func GetCurrentProcessMem() uint64 {
plist, e := process.Processes()
if e == nil {
for _, p := range plist {
if int(p.Pid) == os.Getpid() {
mem, err := p.MemoryInfo()
if err != nil {
return 0
}
return mem.RSS
}
}
}
return 0
}
func GetCurrentProcessCPUPrecent() float64 {
plist, e := process.Processes()
if e == nil {
for _, p := range plist {
if int(p.Pid) == os.Getpid() {
cpuprecent, err := p.CPUPercent()
if err != nil {
return 0
}
return cpuprecent
}
}
}
return 0
}
func GetCpuPercent() float64 {
percent, _ := cpu.Percent(time.Second, false)
return percent[0]
}
//跨域访问cross origin resource share
func CrosHandler() gin.HandlerFunc {
return func(context *gin.Context) {
method := context.Request.Method
//context.Writer.Header().Set("Access-Control-Allow-Origin", "*")
origin := context.Request.Header.Get("Origin")
context.Header("Access-Control-Allow-Origin", origin) // 设置允许访问所有域
context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE")
context.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma,token,openid,opentoken")
//context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")
//context.Header("Access-Control-Allow-Methods", "*")
//context.Header("Access-Control-Allow-Headers", "*")
context.Header("Access-Control-Expose-Headers", "*")
context.Header("Access-Control-Allow-Credentials", "true")
context.Header("Access-Control-Max-Age", "172800")
//context.Header("Access-Control-Allow-Credentials", "false")
//context.Set("content-type", "application/json")
if method == "OPTIONS" {
context.JSON(http.StatusOK, gin.H{
"ret": 0,
})
}
//处理请求
context.Next()
}
}
//------------------------------------------------------------------------------------------------------------------
func BasicAuth() gin.HandlerFunc {
return gin.BasicAuthForRealm(config.GetAuthAccount(), "")
}
func formatFileSize(fileSize uint64) (size string) {
switch {
case fileSize < 1024:
return fmt.Sprintf("%.2fB", float64(fileSize)/float64(1))
case fileSize < (1024 * 1024):
return fmt.Sprintf("%.2fKB", float64(fileSize)/float64(1024))
case fileSize < (1024 * 1024 * 1024):
return fmt.Sprintf("%.2fMB", float64(fileSize)/float64(1024*1024))
case fileSize < (1024 * 1024 * 1024 * 1024):
return fmt.Sprintf("%.2fGB", float64(fileSize)/float64(1024*1024*1024))
case fileSize < (1024 * 1024 * 1024 * 1024 * 1024):
return fmt.Sprintf("%.2fTB", float64(fileSize)/float64(1024*1024*1024*1024))
default:
return fmt.Sprintf("%.2fEB", float64(fileSize)/float64(1024*1024*1024*1024*1024))
}
}
func isLocalIP(ipstr string) bool {
ip := net.ParseIP(ipstr)
if ip.IsLoopback() {
return true
}
ip4 := ip.To4()
if ip4 == nil {
return false
}
return ip4[0] == 10 || // 10.0.0.0/8
(ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31) || // 172.16.0.0/12
(ip4[0] == 169 && ip4[1] == 254) || // 169.254.0.0/16
(ip4[0] == 192 && ip4[1] == 168) // 192.168.0.0/16
}
//***********************
//basicAuth
type authPair struct {
value string
user string
}
type authPairs []authPair
func processAccounts(accounts gin.Accounts) authPairs {
length := len(accounts)
assert1(length > 0, "Empty list of authorized credentials")
pairs := make(authPairs, 0, length)
for user, password := range accounts {
assert1(user != "", "User can not be empty")
value := authorizationHeader(user, password)
pairs = append(pairs, authPair{
value: value,
user: user,
})
}
return pairs
}
func authorizationHeader(user, password string) string {
base := user + ":" + password
return "Basic " + base64.StdEncoding.EncodeToString(StringToBytes(base))
}
func assert1(guard bool, text string) {
if !guard {
panic(text)
}
}
func StringToBytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(
&struct {
string
Cap int
}{s, len(s)},
))
}
func (a authPairs) searchCredential(authValue string) (string, bool) {
if authValue == "" {
return "", false
}
for _, pair := range a {
if subtle.ConstantTimeCompare(StringToBytes(pair.value), StringToBytes(authValue)) == 1 {
return pair.user, true
}
}
return "", false
}

7
webdisable.go Normal file
View File

@ -0,0 +1,7 @@
//go:build !adminweb
// +build !adminweb
package main
func RunAdminWeb(listenPort int) {
}