update
parent
1b27f3b2ab
commit
8239477e53
|
@ -3,4 +3,5 @@ bin
|
|||
go.sum
|
||||
config.toml
|
||||
temp
|
||||
logs
|
||||
logs
|
||||
*.db
|
|
@ -0,0 +1,42 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
db *gorm.DB
|
||||
)
|
||||
|
||||
func InitDB() {
|
||||
gdb, err := gorm.Open(sqlite.Open("data.db"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
db = gdb.Debug()
|
||||
|
||||
err = db.Exec(createTable).Error
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func DB() *gorm.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
var (
|
||||
createTable = `
|
||||
create table if not exists forward (
|
||||
id integer primary key not null,
|
||||
name varchar(60) not null,
|
||||
local_ip text not null,
|
||||
local_port integer not null,
|
||||
target_addr text not null,
|
||||
protocol integer default 0 not null,
|
||||
status integer default 0 not null,
|
||||
create_time datetime DEFAULT CURRENT_TIMESTAMP not null,
|
||||
update_time datetime
|
||||
)`
|
||||
)
|
|
@ -0,0 +1,201 @@
|
|||
package forward
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.makemake.in/kzkzzzz/mycommon/mylog"
|
||||
"proxyport/app/db"
|
||||
"proxyport/app/model"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Protocol int
|
||||
|
||||
func (p Protocol) String() string {
|
||||
switch p {
|
||||
case 0:
|
||||
return "TCP"
|
||||
case 1:
|
||||
return "UDP"
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
const (
|
||||
ProtocolTCP Protocol = 0
|
||||
ProtocolUDP Protocol = 1
|
||||
)
|
||||
|
||||
type IForward interface {
|
||||
Forward() error
|
||||
Stop()
|
||||
}
|
||||
|
||||
var ListenerManager = &Manager{
|
||||
lock: &sync.Mutex{},
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
Id int
|
||||
Name string
|
||||
TargetAddr []string
|
||||
LocalAddr string
|
||||
LocalIp string
|
||||
LocalPort int
|
||||
Protocol Protocol
|
||||
Status int
|
||||
}
|
||||
|
||||
type activeForward struct {
|
||||
forward IForward
|
||||
info *Info
|
||||
}
|
||||
|
||||
type forwardKey struct {
|
||||
localAddr string
|
||||
protocol Protocol
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
activeForwardMap map[forwardKey]*activeForward
|
||||
lock *sync.Mutex
|
||||
}
|
||||
|
||||
func (m *Manager) initForwardList() []*Info {
|
||||
data := make([]*model.Forward, 0)
|
||||
err := db.DB().Table("forward").Select("*").Find(&data).Error
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
list := make([]*Info, 0)
|
||||
|
||||
for _, v := range data {
|
||||
if v.Status == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
list = append(list, m.convertModel(v))
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (m *Manager) convertModel(v *model.Forward) *Info {
|
||||
info := &Info{
|
||||
Id: v.Id,
|
||||
Name: v.Name,
|
||||
TargetAddr: v.TargetAddr,
|
||||
LocalIp: v.LocalIp,
|
||||
LocalPort: v.LocalPort,
|
||||
Protocol: Protocol(v.Protocol),
|
||||
Status: v.Status,
|
||||
}
|
||||
|
||||
if v.LocalIp == "" {
|
||||
info.LocalAddr = fmt.Sprintf("0.0.0.0:%d", v.LocalPort)
|
||||
} else {
|
||||
info.LocalAddr = fmt.Sprintf("%s:%d", v.LocalIp, v.LocalPort)
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func (m *Manager) Add(v *model.Forward) error {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
forwardInfo := m.convertModel(v)
|
||||
|
||||
key := forwardKey{
|
||||
localAddr: forwardInfo.LocalAddr,
|
||||
protocol: forwardInfo.Protocol,
|
||||
}
|
||||
|
||||
_, ok := m.activeForwardMap[key]
|
||||
if ok {
|
||||
return fmt.Errorf("[%s] [%s] is exist", forwardInfo.Protocol, forwardInfo.LocalAddr)
|
||||
}
|
||||
|
||||
tmp := &activeForward{
|
||||
info: forwardInfo,
|
||||
}
|
||||
if Protocol(v.Protocol) == ProtocolUDP {
|
||||
tmp.forward = NewUDP(forwardInfo)
|
||||
} else {
|
||||
tmp.forward = NewTCP(forwardInfo)
|
||||
}
|
||||
m.activeForwardMap[key] = tmp
|
||||
|
||||
err := tmp.forward.Forward()
|
||||
if err != nil {
|
||||
delete(m.activeForwardMap, key)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Remove(v *model.Forward) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
forwardInfo := m.convertModel(v)
|
||||
|
||||
key := forwardKey{
|
||||
localAddr: forwardInfo.LocalAddr,
|
||||
protocol: forwardInfo.Protocol,
|
||||
}
|
||||
fr, ok := m.activeForwardMap[key]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
fr.forward.Stop()
|
||||
delete(m.activeForwardMap, key)
|
||||
}
|
||||
|
||||
func (m *Manager) Start(ctx context.Context) {
|
||||
m.activeForwardMap = make(map[forwardKey]*activeForward)
|
||||
|
||||
list := m.initForwardList()
|
||||
for _, v := range list {
|
||||
key := forwardKey{
|
||||
localAddr: v.LocalAddr,
|
||||
protocol: v.Protocol,
|
||||
}
|
||||
|
||||
switch v.Protocol {
|
||||
case ProtocolTCP:
|
||||
m.activeForwardMap[key] = &activeForward{
|
||||
forward: NewTCP(v),
|
||||
info: v,
|
||||
}
|
||||
|
||||
case ProtocolUDP:
|
||||
m.activeForwardMap[key] = &activeForward{
|
||||
forward: NewUDP(v),
|
||||
info: v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range m.activeForwardMap {
|
||||
err := v.forward.Forward()
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
m.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) Stop() {
|
||||
for _, v := range m.activeForwardMap {
|
||||
v.forward.Stop()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package forward
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.makemake.in/kzkzzzz/mycommon/mylog"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ IForward = (*TCP)(nil)
|
||||
|
||||
type TCP struct {
|
||||
forwardInfo *Info
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
func NewTCP(forwardInfo *Info) *TCP {
|
||||
return &TCP{
|
||||
forwardInfo: forwardInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCP) Forward() error {
|
||||
listenTimeout := time.Second
|
||||
ctx0, cancel0 := context.WithTimeout(context.Background(), listenTimeout)
|
||||
defer cancel0()
|
||||
|
||||
var (
|
||||
errChan = make(chan error, 1)
|
||||
listenerRes = make(chan net.Listener, 1)
|
||||
)
|
||||
|
||||
go func() {
|
||||
listener, err := net.Listen("tcp", t.forwardInfo.LocalAddr)
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
listenerRes <- listener
|
||||
}()
|
||||
|
||||
select {
|
||||
case listener := <-listenerRes:
|
||||
t.listener = listener
|
||||
|
||||
case err := <-errChan:
|
||||
return err
|
||||
|
||||
case <-ctx0.Done():
|
||||
return fmt.Errorf("listen timeout %s %s", t.forwardInfo.LocalAddr, listenTimeout)
|
||||
}
|
||||
|
||||
//
|
||||
//mylog.Infof("start listen: %s", t.forwardInfo.LocalAddr)
|
||||
//lc := net.ListenConfig{}
|
||||
|
||||
//listener, err := lc.Listen(ctx0, "tcp", t.forwardInfo.LocalAddr)
|
||||
// 启动 TCP 监听
|
||||
|
||||
mylog.Infof("[TCP] %s %s -> %s", t.forwardInfo.Name, t.forwardInfo.LocalAddr, t.forwardInfo.TargetAddr)
|
||||
|
||||
go func() {
|
||||
ctx, cancelCtx := context.WithCancel(context.Background())
|
||||
defer cancelCtx()
|
||||
|
||||
for {
|
||||
// 接受连接
|
||||
conn, err := t.listener.Accept()
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
break
|
||||
}
|
||||
// 处理连接
|
||||
go t.handleConn(ctx, conn, t.forwardInfo.TargetAddr)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TCP) handleConn(mainCtx context.Context, localConn net.Conn, targetAddrList []string) {
|
||||
defer localConn.Close()
|
||||
|
||||
targetAddr := targetAddrList[rand.Intn(len(targetAddrList))]
|
||||
|
||||
// 连接到目标地址
|
||||
targetConn, err := net.Dial("tcp", targetAddr)
|
||||
if err != nil {
|
||||
mylog.Error("Error connecting to target:", err)
|
||||
return
|
||||
}
|
||||
defer targetConn.Close()
|
||||
|
||||
ctx, cancelCtx := context.WithCancel(context.Background())
|
||||
defer cancelCtx()
|
||||
|
||||
defer func() {
|
||||
mylog.Warnf("tcp forward stop %s -> %+v", localConn.RemoteAddr(), targetAddrList)
|
||||
}()
|
||||
mylog.Debugf("tcp forward %s -> %s", localConn.RemoteAddr(), targetAddr)
|
||||
|
||||
go func() {
|
||||
defer cancelCtx()
|
||||
_, err := io.Copy(targetConn, localConn) // 从客户端转发到目标
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer cancelCtx()
|
||||
_, err := io.Copy(localConn, targetConn) // 从目标转发到客户端
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-mainCtx.Done():
|
||||
cancelCtx()
|
||||
case <-ctx.Done():
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *TCP) Stop() {
|
||||
if t.listener != nil {
|
||||
t.listener.Close()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
package forward
|
||||
|
||||
import (
|
||||
"git.makemake.in/kzkzzzz/mycommon/mylog"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ IForward = (*UDP)(nil)
|
||||
|
||||
type UDP struct {
|
||||
forwardInfo *Info
|
||||
conn *net.UDPConn
|
||||
packetBufferSize int
|
||||
messageTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewUDP(forwardInfo *Info) *UDP {
|
||||
return &UDP{
|
||||
forwardInfo: forwardInfo,
|
||||
packetBufferSize: 2048,
|
||||
messageTimeout: time.Second * 2,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *UDP) Forward() error {
|
||||
udpLocalAddr, err := net.ResolveUDPAddr("udp", u.forwardInfo.LocalAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, err := net.ListenUDP("udp", udpLocalAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.conn = conn
|
||||
|
||||
mylog.Infof("[UDP] %s %s -> %s", u.forwardInfo.Name, u.forwardInfo.LocalAddr, u.forwardInfo.TargetAddr)
|
||||
|
||||
go func() {
|
||||
err := u.handleConn(conn)
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UDP) handleConn(conn *net.UDPConn) error {
|
||||
defer func() {
|
||||
mylog.Warnf("udp forward stop %s -> %+v", u.forwardInfo.LocalAddr, u.forwardInfo.TargetAddr)
|
||||
}()
|
||||
|
||||
buffer := make([]byte, u.packetBufferSize) // 创建一个缓冲区
|
||||
|
||||
for {
|
||||
// 接收数据
|
||||
n, clientAddr, err := conn.ReadFromUDP(buffer)
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
targetAddr := u.forwardInfo.TargetAddr[rand.Intn(len(u.forwardInfo.TargetAddr))]
|
||||
|
||||
mylog.Debugf("udp forward %s -> %s", clientAddr.String(), targetAddr)
|
||||
|
||||
// 解析目标地址
|
||||
//remoteAddr, err := net.ResolveUDPAddr("udp", targetAddr)
|
||||
//if err != nil {
|
||||
// mylog.Error(err)
|
||||
// return err
|
||||
//}
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Timeout: u.messageTimeout,
|
||||
}
|
||||
remoteRawConn, err := dialer.Dial("udp", targetAddr)
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
remoteRawConn.SetWriteDeadline(time.Now().Add(u.messageTimeout))
|
||||
remoteRawConn.SetReadDeadline(time.Now().Add(u.messageTimeout))
|
||||
|
||||
remoteConn := remoteRawConn.(*net.UDPConn)
|
||||
|
||||
//remoteConn, err := net.DialUDP("udp", nil, remoteAddr)
|
||||
//dial, err := reuseport.Dial("udp", "", targetAddr)
|
||||
|
||||
// 将数据转发到目标地址
|
||||
_, err = remoteConn.Write(buffer[:n])
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
res := make([]byte, u.packetBufferSize)
|
||||
resLen, _, err := remoteConn.ReadFromUDP(res)
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 将目标地址的响应返回给客户端
|
||||
_, err = conn.WriteToUDP(res[:resLen], clientAddr)
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (u *UDP) Stop() {
|
||||
if u.conn != nil {
|
||||
u.conn.Close()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Forward struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
LocalIp string `json:"local_ip"`
|
||||
LocalPort int `json:"local_port"`
|
||||
TargetAddr StringList `json:"target_addr"`
|
||||
Protocol int `json:"protocol"`
|
||||
Status int `json:"status"`
|
||||
CreateTime string `json:"create_time"`
|
||||
UpdateTime string `json:"update_time"`
|
||||
}
|
||||
|
||||
type StringList []string
|
||||
|
||||
func (s *StringList) Scan(value interface{}) error {
|
||||
v, ok := value.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("value is not string")
|
||||
}
|
||||
|
||||
*s = strings.Split(v, ",")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s StringList) Value() (driver.Value, error) {
|
||||
if len(s) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
return strings.Join(s, ","), nil
|
||||
}
|
126
app/proxy.go
126
app/proxy.go
|
@ -1,126 +0,0 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.makemake.in/kzkzzzz/mycommon/myconf"
|
||||
"git.makemake.in/kzkzzzz/mycommon/mylog"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ProxyItem struct {
|
||||
RemoteAddr []string
|
||||
LocalAddr string
|
||||
}
|
||||
|
||||
var (
|
||||
mainWg = &sync.WaitGroup{}
|
||||
mainCtx, mainCancel = context.WithCancel(context.Background())
|
||||
|
||||
proxyInfo map[string]*ProxyItem
|
||||
)
|
||||
|
||||
func Run() {
|
||||
err := myconf.Conf().UnmarshalKey("proxy", &proxyInfo)
|
||||
if err != nil {
|
||||
mylog.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(proxyInfo))
|
||||
for name := range proxyInfo {
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
go listenTcp(name, proxyInfo[name])
|
||||
}
|
||||
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
|
||||
v := <-ch
|
||||
mylog.Infof("捕获退出信号: %s", v.String())
|
||||
mainCancel()
|
||||
|
||||
mainWg.Wait()
|
||||
mylog.Info("stop")
|
||||
|
||||
}
|
||||
|
||||
func listenTcp(name string, item *ProxyItem) {
|
||||
listen, err := net.Listen("tcp4", item.LocalAddr)
|
||||
if err != nil {
|
||||
mylog.Errorf("[%s] listen err: %s", name, err)
|
||||
return
|
||||
}
|
||||
|
||||
mylog.Infof("[%s] forward %s -> %+v", name, item.LocalAddr, item.RemoteAddr)
|
||||
|
||||
for {
|
||||
localConn, err := listen.Accept()
|
||||
if err != nil {
|
||||
mylog.Errorf("conn accept err: %s", item.LocalAddr, err)
|
||||
return
|
||||
}
|
||||
|
||||
mainWg.Add(1)
|
||||
go proxyTcp(localConn, item)
|
||||
}
|
||||
}
|
||||
|
||||
func proxyTcp(localConn net.Conn, item *ProxyItem) {
|
||||
defer mainWg.Done()
|
||||
|
||||
randRemoteAddr := item.RemoteAddr[rand.Intn(len(item.RemoteAddr))]
|
||||
|
||||
remoteConn, err := net.DialTimeout("tcp", randRemoteAddr, time.Second*3)
|
||||
if err != nil {
|
||||
localConn.Close()
|
||||
mylog.Errorf("connect remote err: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
mylog.Debugf("start proxy %s -> %s", localConn.RemoteAddr(), randRemoteAddr)
|
||||
|
||||
waitCh := make(chan struct{})
|
||||
go func() {
|
||||
wg0 := &sync.WaitGroup{}
|
||||
wg0.Add(2)
|
||||
go copyConn(wg0, localConn, remoteConn)
|
||||
go copyConn(wg0, remoteConn, localConn)
|
||||
wg0.Wait()
|
||||
close(waitCh)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-waitCh:
|
||||
|
||||
case <-mainCtx.Done():
|
||||
localConn.Close()
|
||||
remoteConn.Close()
|
||||
}
|
||||
|
||||
mylog.Debugf("stop proxy %s -> %s", localConn.RemoteAddr(), randRemoteAddr)
|
||||
|
||||
}
|
||||
|
||||
func copyConn(wg0 *sync.WaitGroup, localConn, remoteConn net.Conn) {
|
||||
defer func() {
|
||||
wg0.Done()
|
||||
localConn.Close()
|
||||
remoteConn.Close()
|
||||
}()
|
||||
|
||||
_, err := io.Copy(remoteConn, localConn)
|
||||
if err != nil {
|
||||
mylog.Debug(err)
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,42 @@
|
|||
package static
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"github.com/gin-gonic/gin"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed assets
|
||||
assetsFs embed.FS
|
||||
|
||||
//go:embed view
|
||||
viewFs embed.FS
|
||||
)
|
||||
|
||||
func LoadStatic(engine *gin.Engine) {
|
||||
sub, err := fs.Sub(assetsFs, "assets")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
cacheSuffix := []string{".css", ".js", ".js.map", ".css.map"}
|
||||
|
||||
engine.Use(func(ctx *gin.Context) {
|
||||
if ctx.Request.Method == http.MethodGet {
|
||||
for _, suffix := range cacheSuffix {
|
||||
if strings.HasSuffix(ctx.Request.URL.Path, suffix) {
|
||||
ctx.Writer.Header().Set("Cache-Control", "max-age=7200")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.Next()
|
||||
})
|
||||
|
||||
engine.StaticFS("/assets", http.FS(sub))
|
||||
engine.SetHTMLTemplate(template.Must(template.New("").ParseFS(viewFs, "view/*")))
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>端口转发配置</title>
|
||||
<script type="module" crossorigin src="./assets/index-lqpwyav5.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-BA8XwqWY.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,464 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.makemake.in/kzkzzzz/mycommon/mylog"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-playground/locales/en"
|
||||
ut "github.com/go-playground/universal-translator"
|
||||
"github.com/go-playground/validator/v10"
|
||||
enTr "github.com/go-playground/validator/v10/translations/en"
|
||||
"github.com/spf13/cast"
|
||||
"gorm.io/gorm"
|
||||
"net/http"
|
||||
"proxyport/app/db"
|
||||
"proxyport/app/forward"
|
||||
"proxyport/app/model"
|
||||
"proxyport/app/static"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Config = &Cfg{}
|
||||
|
||||
type Cfg struct {
|
||||
ListenAddr string
|
||||
User string
|
||||
Password string
|
||||
}
|
||||
|
||||
func Start(ctx context.Context) {
|
||||
InitGinTrans()
|
||||
|
||||
engine := gin.Default()
|
||||
|
||||
if Config.User != "" && Config.Password != "" {
|
||||
engine.Use(gin.BasicAuth(gin.Accounts{
|
||||
Config.User: Config.Password,
|
||||
}))
|
||||
}
|
||||
|
||||
static.LoadStatic(engine)
|
||||
engine.Use(cors())
|
||||
|
||||
engine.GET("/", func(ctx *gin.Context) {
|
||||
ctx.HTML(http.StatusOK, "index.html", nil)
|
||||
})
|
||||
engine.GET("/List", List)
|
||||
engine.POST("/Create", Create)
|
||||
engine.POST("/Update", Update)
|
||||
engine.POST("/Delete", Delete)
|
||||
engine.POST("/SwitchStatus", SwitchStatus)
|
||||
|
||||
hs := &http.Server{
|
||||
Addr: Config.ListenAddr,
|
||||
Handler: engine,
|
||||
}
|
||||
|
||||
go func() {
|
||||
mylog.Infof("http listen on %s", hs.Addr)
|
||||
mylog.Warn(hs.ListenAndServe())
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
hs.Close()
|
||||
|
||||
}
|
||||
|
||||
func cors() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
ctx.Header("Access-Control-Allow-Origin", "*")
|
||||
ctx.Header("Access-Control-Allow-Methods", "GET, POST")
|
||||
|
||||
ctx.Header("Access-Control-Allow-Headers", "Content-Type")
|
||||
|
||||
if ctx.Request.Method == http.MethodOptions {
|
||||
ctx.AbortWithStatus(http.StatusOK)
|
||||
} else {
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func List(ctx *gin.Context) {
|
||||
data := make([]*model.Forward, 0)
|
||||
|
||||
err := db.DB().Table("forward").Select("*").Order("update_time desc").Find(&data).Error
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
success(ctx, data)
|
||||
}
|
||||
|
||||
func Create(ctx *gin.Context) {
|
||||
type Req struct {
|
||||
TargetAddr string `json:"target_addr" binding:"min=1"`
|
||||
LocalPort int `json:"local_port" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
ForwardType int `json:"protocol" binding:"oneof=0 1"`
|
||||
Status int `json:"status" binding:"oneof=0 1"`
|
||||
}
|
||||
|
||||
req := &Req{}
|
||||
err := ctx.ShouldBindJSON(req)
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = checkAddr(fmt.Sprintf("0.0.0.0:%d", req.LocalPort))
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
targetAddr, err := parseAddrList(req.TargetAddr)
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = checkUnique(0, req.LocalPort, req.ForwardType)
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
err = db.DB().Transaction(func(tx *gorm.DB) error {
|
||||
mForward := &model.Forward{
|
||||
LocalPort: req.LocalPort,
|
||||
TargetAddr: targetAddr,
|
||||
Name: req.Name,
|
||||
Protocol: req.ForwardType,
|
||||
Status: 1,
|
||||
CreateTime: now.Format(time.DateTime),
|
||||
UpdateTime: now.Format(time.DateTime),
|
||||
}
|
||||
|
||||
err := tx.Table("forward").Create(mForward).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = forward.ListenerManager.Add(mForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
success(ctx, "ok")
|
||||
}
|
||||
|
||||
func Update(ctx *gin.Context) {
|
||||
type Req struct {
|
||||
Id int `json:"id" binding:"required"`
|
||||
TargetAddr string `json:"target_addr" binding:"min=1"`
|
||||
LocalPort int `json:"local_port" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Protocol int `json:"protocol" binding:"oneof=0 1"`
|
||||
Status int `json:"status" binding:"oneof=0 1"`
|
||||
}
|
||||
|
||||
req := &Req{}
|
||||
err := ctx.ShouldBindJSON(req)
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = checkAddr(fmt.Sprintf("0.0.0.0:%d", req.LocalPort))
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
targetAddr, err := parseAddrList(req.TargetAddr)
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = checkUnique(req.Id, req.LocalPort, req.Protocol)
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
current, err := getForwardById(req.Id)
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = db.DB().Transaction(func(tx *gorm.DB) error {
|
||||
mForward := &model.Forward{
|
||||
LocalPort: req.LocalPort,
|
||||
TargetAddr: targetAddr,
|
||||
Name: req.Name,
|
||||
Protocol: req.Protocol,
|
||||
UpdateTime: time.Now().Format(time.DateTime),
|
||||
}
|
||||
|
||||
err = tx.Table("forward").Select("*").Omit("create_time", "status").Where("id = ?", req.Id).
|
||||
Limit(1).
|
||||
Updates(mForward).Error
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
forward.ListenerManager.Remove(current)
|
||||
|
||||
if current.Status == 1 {
|
||||
err = forward.ListenerManager.Add(mForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
success(ctx, "ok")
|
||||
}
|
||||
|
||||
func Delete(ctx *gin.Context) {
|
||||
type Req struct {
|
||||
Id int `json:"id" binding:"required"`
|
||||
}
|
||||
|
||||
req := &Req{}
|
||||
err := ctx.ShouldBindJSON(req)
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
mForward, err := getForwardById(req.Id)
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = db.DB().Table("forward").Select("*").Where("id = ?", req.Id).Limit(1).Delete(nil).Error
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
forward.ListenerManager.Remove(mForward)
|
||||
|
||||
success(ctx, "ok")
|
||||
}
|
||||
|
||||
var addrReg = regexp.MustCompile(`^[^:]+:([0-9]{1,5})$`)
|
||||
|
||||
func checkAddr(addr string) error {
|
||||
res := addrReg.FindStringSubmatch(addr)
|
||||
if len(res) != 2 {
|
||||
return fmt.Errorf("invalid addr: %s", addr)
|
||||
}
|
||||
|
||||
intPort := cast.ToInt(res[1])
|
||||
if intPort < 10 || intPort > 65535 {
|
||||
return fmt.Errorf("port: %d out of range: 10 - 65535", intPort)
|
||||
}
|
||||
|
||||
//_, err := netip.ParseAddrPort(addr)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkUnique(id, localPort, forwardType int) error {
|
||||
query := db.DB().Table("forward").
|
||||
Where("local_port = ?", localPort).
|
||||
Where("protocol = ?", forwardType)
|
||||
|
||||
if id > 0 {
|
||||
query.Where("id != ?", id)
|
||||
}
|
||||
|
||||
var res []int
|
||||
err := query.Select("id").Limit(1).Find(&res).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return fmt.Errorf("%s port: %d already use", forward.Protocol(forwardType).String(), localPort)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getForwardById(id int) (*model.Forward, error) {
|
||||
if id <= 0 {
|
||||
return nil, fmt.Errorf("id err: %d", id)
|
||||
}
|
||||
mForward := &model.Forward{}
|
||||
err := db.DB().Table("forward").Select("*").Where("id = ?", id).First(mForward).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mForward, nil
|
||||
}
|
||||
|
||||
func SwitchStatus(ctx *gin.Context) {
|
||||
type Req struct {
|
||||
Id int `json:"id" binding:"required"`
|
||||
Status int `json:"status" binding:"oneof=0 1"`
|
||||
}
|
||||
|
||||
req := &Req{}
|
||||
err := ctx.ShouldBindJSON(req)
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
mForward, err := getForwardById(req.Id)
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = db.DB().Transaction(func(tx *gorm.DB) error {
|
||||
err = tx.Table("forward").Where("id = ?", req.Id).Updates(map[string]any{
|
||||
"status": req.Status,
|
||||
}).Limit(1).Error
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch req.Status {
|
||||
case 1:
|
||||
err = forward.ListenerManager.Add(mForward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
forward.ListenerManager.Remove(mForward)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
fail(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
success(ctx, "ok")
|
||||
}
|
||||
|
||||
type apiRes struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
func success(ctx *gin.Context, data any) {
|
||||
ctx.JSON(http.StatusOK, &apiRes{
|
||||
Code: 0,
|
||||
Message: "ok",
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
var ginTrans ut.Translator
|
||||
|
||||
func InitGinTrans() {
|
||||
v := binding.Validator.Engine().(*validator.Validate)
|
||||
v.RegisterTagNameFunc(func(fld reflect.StructField) string {
|
||||
nameSp := strings.SplitN(fld.Tag.Get("json"), ",", 2)
|
||||
if len(nameSp) == 0 {
|
||||
return fld.Name
|
||||
}
|
||||
|
||||
name := nameSp[0]
|
||||
if name == "-" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if name != "" {
|
||||
return name
|
||||
}
|
||||
|
||||
return fld.Name
|
||||
})
|
||||
enT := en.New()
|
||||
|
||||
uni := ut.New(enT, enT)
|
||||
tr, _ := uni.GetTranslator("en")
|
||||
_ = enTr.RegisterDefaultTranslations(v, tr)
|
||||
|
||||
ginTrans = tr
|
||||
}
|
||||
|
||||
func fail(ctx *gin.Context, err error) {
|
||||
res := &apiRes{Code: 1}
|
||||
|
||||
switch ve := err.(type) {
|
||||
case validator.ValidationErrors:
|
||||
if len(ve) > 0 {
|
||||
res.Message = ve[0].Translate(ginTrans)
|
||||
} else {
|
||||
res.Message = ve.Error()
|
||||
}
|
||||
|
||||
default:
|
||||
res.Message = err.Error()
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, res)
|
||||
}
|
||||
|
||||
var emptySplitReg = regexp.MustCompile(`\s*\n\s*`)
|
||||
|
||||
func parseAddrList(addrStr string) ([]string, error) {
|
||||
sp := emptySplitReg.Split(addrStr, -1)
|
||||
if len(sp) == 0 {
|
||||
return nil, fmt.Errorf("addr list is empty")
|
||||
}
|
||||
|
||||
res := make([]string, 0)
|
||||
for i, _ := range sp {
|
||||
sp[i] = strings.TrimSpace(sp[i])
|
||||
|
||||
if sp[i] == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
err := checkAddr(sp[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, sp[i])
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
VITE_API_BASE_URL="/"
|
||||
VITE_WEB_USER=""
|
||||
VITE_WEB_PASSWORD=""
|
|
@ -0,0 +1,3 @@
|
|||
VITE_API_BASE_URL="http://localhost:28083/"
|
||||
VITE_WEB_USER=""
|
||||
VITE_WEB_PASSWORD=""
|
|
@ -0,0 +1,30 @@
|
|||
# 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?
|
||||
|
||||
*.tsbuildinfo
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
# frontend
|
||||
|
||||
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).
|
||||
|
||||
## 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
|
||||
```
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>端口转发配置</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="./src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "frontend",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host --mode dev --port 15173",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.7",
|
||||
"element-plus": "^2.8.4",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.29",
|
||||
"vue-router": "^4.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.0.5",
|
||||
"sass": "^1.79.4",
|
||||
"sass-loader": "^16.0.2",
|
||||
"unplugin-auto-import": "^0.18.3",
|
||||
"unplugin-vue-components": "^0.27.4",
|
||||
"vite": "^5.3.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
|
||||
<div class="app-container">
|
||||
<div class="content">
|
||||
<RouterView/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {RouterView} from 'vue-router'
|
||||
</script>
|
||||
|
||||
|
||||
<style>
|
||||
.text-danger {
|
||||
color: #F56C6C!important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.menu {
|
||||
height: 100%;
|
||||
width: 12rem;
|
||||
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
flex: auto;
|
||||
box-sizing: border-box;
|
||||
padding: 0 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,77 @@
|
|||
import axios from "axios";
|
||||
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {Loading} from "@/helper/loading.js";
|
||||
|
||||
const http = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL,
|
||||
timeout: 60 * 1000,
|
||||
})
|
||||
|
||||
http.defaults.headers.post['Content-Type'] = "application/json"
|
||||
|
||||
|
||||
|
||||
const loadingObj = new Loading()
|
||||
|
||||
|
||||
http.interceptors.request.use(
|
||||
(config) => {
|
||||
loadingObj.addLoading()
|
||||
// console.log(config)
|
||||
|
||||
let user = import.meta.env.VITE_WEB_USER
|
||||
let password = import.meta.env.VITE_WEB_PASSWORD
|
||||
if (user && password) {
|
||||
config.auth = {
|
||||
username: user,
|
||||
password: password
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
// return new Promise((resolve, reject) => {
|
||||
// resolve(config)
|
||||
// })
|
||||
},
|
||||
err => {
|
||||
return Promise.reject(err)
|
||||
},
|
||||
)
|
||||
|
||||
http.interceptors.response.use(
|
||||
res => {
|
||||
loadingObj.closeLoading()
|
||||
if (res.data.code !== 0) {
|
||||
ElMessage({
|
||||
message: res.data.message,
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
})
|
||||
return Promise.reject(res)
|
||||
}
|
||||
//
|
||||
// ElMessage({
|
||||
// message: 'Congrats, this is a success message.',
|
||||
// type: 'success',
|
||||
// })
|
||||
|
||||
return res
|
||||
},
|
||||
|
||||
err => {
|
||||
loadingObj.closeLoading()
|
||||
|
||||
ElMessage({
|
||||
message: err,
|
||||
type: 'error',
|
||||
showClose: true,
|
||||
})
|
||||
|
||||
return Promise.reject(err)
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
export default http
|
|
@ -0,0 +1,36 @@
|
|||
import {ElLoading} from 'element-plus'
|
||||
|
||||
export class Loading {
|
||||
loadingCount = 0
|
||||
loading = null
|
||||
|
||||
constructor() {
|
||||
this.loadingCount = 0
|
||||
}
|
||||
|
||||
initLoading = () => {
|
||||
if (this.loading) {
|
||||
this.loading.close()
|
||||
}
|
||||
this.loading = ElLoading.service({
|
||||
fullscreen: true
|
||||
})
|
||||
}
|
||||
|
||||
addLoading = () => {
|
||||
if (this.loadingCount === 0) {
|
||||
this.initLoading()
|
||||
}
|
||||
this.loadingCount++
|
||||
}
|
||||
|
||||
|
||||
closeLoading = () => {
|
||||
if (this.loadingCount > 0) {
|
||||
if (this.loadingCount === 1) {
|
||||
this.loading.close()
|
||||
}
|
||||
this.loadingCount--
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import 'element-plus/dist/index.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
|
@ -0,0 +1,14 @@
|
|||
import {createRouter, createWebHashHistory, createWebHistory} from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('../views/Home.vue')
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
export default router
|
|
@ -0,0 +1,12 @@
|
|||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
|
@ -0,0 +1,229 @@
|
|||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<h2>端口转发</h2>
|
||||
</div>
|
||||
|
||||
<div style="width: 100%;">
|
||||
<el-button type="success" @click="createForward">添加</el-button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<el-table :data="data.list" style="width: 100%">
|
||||
<el-table-column align="center" label="协议">
|
||||
<template #default="scope">
|
||||
<div v-if="scope.row.protocol === 1">
|
||||
<el-tag type="success">UDP</el-tag>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<el-tag type="primary">TCP</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<el-table-column align="center" label="状态">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row.status_bool" @change="switchStatus(scope.row)"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
|
||||
<el-table-column align="center" prop="name" label="名称"/>
|
||||
<el-table-column align="center" prop="local_port" label="本地端口"/>
|
||||
|
||||
<el-table-column align="center" label="远程地址">
|
||||
<template #default="scope">
|
||||
<div style="white-space: pre">{{ formatList(scope.row.target_addr) }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="center" label="操作">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="primary" @click="updateForward(scope.row)">修改</el-button>
|
||||
|
||||
<el-popconfirm title="Are you sure to delete this?" @confirm="deleteForward(scope.row)" :hide-after="0">
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
|
||||
<el-dialog
|
||||
v-model="data.dialogVisible"
|
||||
:title="data.form.id > 0 ? '修改': '添加'"
|
||||
width="600px"
|
||||
align-center
|
||||
>
|
||||
|
||||
<div>
|
||||
|
||||
<el-form :model="data.form" label-width="auto">
|
||||
|
||||
<el-form-item label="协议">
|
||||
<el-radio-group v-model="data.form.protocol">
|
||||
<el-radio :value="0">TCP</el-radio>
|
||||
<el-radio :value="1">UDP</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<el-form-item label="名称">
|
||||
<el-input v-model="data.form.name" placeholder="名称"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="本地端口">
|
||||
<el-input v-model="data.form.local_port" placeholder="本地监听端口"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="远程地址">
|
||||
<el-input type="textarea" :rows="5" v-model="data.form.target_addr" placeholder="一行一个, 格式 ip:端口, 例如 127.0.0.1:8080"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="data.dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmConfig">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, reactive} from "vue";
|
||||
import http from "@/helper/http.js";
|
||||
|
||||
const data = reactive({
|
||||
list: [],
|
||||
dialogVisible: false,
|
||||
form: {
|
||||
protocol: 0,
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getList()
|
||||
})
|
||||
|
||||
const createForward = () => {
|
||||
data.dialogVisible = true
|
||||
data.form = {
|
||||
protocol: 0,
|
||||
}
|
||||
}
|
||||
|
||||
const updateForward = (item) => {
|
||||
data.dialogVisible = true
|
||||
data.form = JSON.parse(JSON.stringify(item))
|
||||
data.form.target_addr = item.target_addr.join("\n")
|
||||
}
|
||||
|
||||
const deleteForward = async (item) => {
|
||||
await http.post("/Delete", {
|
||||
id: item.id,
|
||||
})
|
||||
await getList()
|
||||
}
|
||||
|
||||
|
||||
const switchStatus = async (item) => {
|
||||
console.log(item)
|
||||
let req = {
|
||||
id: item.id,
|
||||
}
|
||||
if (item.status_bool) {
|
||||
req.status = 1
|
||||
} else {
|
||||
req.status = 0
|
||||
}
|
||||
|
||||
try {
|
||||
await http.post("/SwitchStatus", req)
|
||||
await getList()
|
||||
|
||||
|
||||
} catch (err) {
|
||||
item.status_bool = !item.status_bool
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getList = async () => {
|
||||
let res = await http.get("/List")
|
||||
data.list = res.data.data
|
||||
|
||||
for (const v of data.list) {
|
||||
if (v.status === 1) {
|
||||
v.status_bool = true
|
||||
} else {
|
||||
v.status_bool = false
|
||||
}
|
||||
}
|
||||
|
||||
console.log(res)
|
||||
}
|
||||
|
||||
const confirmConfig = async () => {
|
||||
console.log(data.form)
|
||||
|
||||
let res;
|
||||
|
||||
if (data.form.id > 0) {
|
||||
res = await http.post("/Update", {
|
||||
id: data.form.id,
|
||||
name: data.form.name,
|
||||
local_port: Number(data.form.local_port),
|
||||
target_addr: data.form.target_addr,
|
||||
protocol: data.form.protocol,
|
||||
})
|
||||
|
||||
|
||||
} else {
|
||||
res = await http.post("/Create", {
|
||||
name: data.form.name,
|
||||
local_port: Number(data.form.local_port),
|
||||
target_addr: data.form.target_addr,
|
||||
protocol: data.form.protocol,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
data.dialogVisible = false
|
||||
getList()
|
||||
|
||||
console.log(res)
|
||||
|
||||
}
|
||||
|
||||
const setStatus = (item) => {
|
||||
if (item.status_bool) {
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const formatList = (list) => {
|
||||
if (list.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return list.join("\n")
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,26 @@
|
|||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: './',
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
}
|
||||
})
|
42
go.mod
42
go.mod
|
@ -3,16 +3,42 @@ module proxyport
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
git.makemake.in/kzkzzzz/mycommon v0.0.0-20240719075030-85d75101130c
|
||||
git.makemake.in/kzkzzzz/mycommon v0.0.0-20240930075521-197476e80569
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
gorm.io/gorm v1.25.7
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gin-gonic/gin v1.10.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/libp2p/go-reuseport v0.4.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
|
@ -20,12 +46,22 @@ require (
|
|||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/viper v1.19.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/net v0.25.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/text v0.15.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/sqlite v1.23.1 // indirect
|
||||
)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
case $1 in
|
||||
"backend")
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o temp/proxyport.linux main.go
|
||||
scp -P 5566 temp/proxyport.linux kzkzzzz@193.32.149.42:temp/proxyport/proxyport.linux
|
||||
;;
|
||||
"frontend")
|
||||
cd frontend
|
||||
npm run build
|
||||
static_dir=/c/userdata/dev/devgo/my/proxyport/app/static
|
||||
rm -rf $static_dir/assets
|
||||
cp -rf dist/index.html $static_dir/view/
|
||||
cp -rf dist/assets $static_dir/assets/
|
||||
;;
|
||||
*)
|
||||
echo 'other'
|
||||
;;
|
||||
|
||||
esac
|
60
main.go
60
main.go
|
@ -1,23 +1,59 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"git.makemake.in/kzkzzzz/mycommon/myconf"
|
||||
"context"
|
||||
"flag"
|
||||
"git.makemake.in/kzkzzzz/mycommon/mylog"
|
||||
"github.com/spf13/pflag"
|
||||
"proxyport/app"
|
||||
"os"
|
||||
"os/signal"
|
||||
"proxyport/app/db"
|
||||
"proxyport/app/forward"
|
||||
"proxyport/app/web"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
logLevel string
|
||||
)
|
||||
|
||||
func main() {
|
||||
pflag.String("conf", "config.toml", "config file path")
|
||||
pflag.String("log.level", "info", "log level")
|
||||
myconf.LoadFlag()
|
||||
flag.StringVar(&logLevel, "log_level", "debug", "log level")
|
||||
flag.StringVar(&web.Config.ListenAddr, "listen_addr", ":28083", "web port")
|
||||
flag.StringVar(&web.Config.User, "user", "", "web user")
|
||||
flag.StringVar(&web.Config.Password, "password", "", "web password")
|
||||
flag.Parse()
|
||||
|
||||
myconf.LoadFile(myconf.Conf().GetString("conf"))
|
||||
mylog.SetLogLevel(logLevel)
|
||||
mylog.Init()
|
||||
|
||||
config := mylog.DefaultConfig
|
||||
config.Level = myconf.Conf().GetString("log.level")
|
||||
mylog.Init("", config)
|
||||
defer mylog.Flush()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
app.Run()
|
||||
db.InitDB()
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(3)
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
ch := make(chan os.Signal, 1)
|
||||
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
mylog.Warnf("catch signal: %v", <-ch)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
forward.ListenerManager.Start(ctx)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
web.Start(ctx)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue