1.3.1 主要新增反向代理功能模块.

This commit is contained in:
古大羊 2022-09-24 17:28:58 +08:00
parent da023ceb77
commit ff20532615
61 changed files with 5567 additions and 2046 deletions

View File

@ -40,6 +40,13 @@
- 其它细节功能自己慢慢发现...
- 没有文档,后台各处的提示信息已经足够多.
- 支持的DNS服务商和DDNS-GO一样,有Alidns(阿里云),百度云,Cloudflare,Dnspod(腾讯云),华为云.自定义(Callback)内置有每步,No-IP,Dynv6,Dynu模版,一键填充,仅需修改相应用户密码或者token即可快速接入.
- 3.Http反向代理
- 支持HttpBasic认证
- 支持IP黑白名单
- 支持UserAgent黑白名单
- 日志记录最近访问情况
- 一键开关子规则
- 前端域名与后端地址 支持一对一,一对多(均衡负载),多对多(下一级反向代理)
- 将要实现的功能
- 有建议可联系作者.
@ -155,6 +162,11 @@
![](./previews/domainsync.png)
#### Http反向代理
![](./previews/reverseproxy.png)
#开发编译

View File

@ -22,7 +22,7 @@ func GetAppInfo() *AppInfo {
}
func InitAppInfo(version, date string) {
appInfo.AppName = "Lucky(大吉)"
appInfo.AppName = "Lucky"
appInfo.Version = version
appInfo.Date = date
appInfo.OS = runtime.GOOS

View File

@ -1,10 +1,30 @@
//Copyright 2022 gdy, 272288813@qq.com
// Copyright 2022 gdy, 272288813@qq.com
package config
import "time"
import (
"fmt"
"net"
"strings"
"time"
)
type BlackListItem WhiteListItem
func (w *BlackListItem) Contains(ip string) bool {
netIP := net.ParseIP(ip)
if netIP == nil {
return false
}
if w.NetIP != nil {
return w.NetIP.Equal(netIP)
}
if w.Cidr != nil {
return w.Cidr.Contains(netIP)
}
return false
}
type BlackListConfigure struct {
BlackList []BlackListItem `json:"BlackList"` //黑名单列表
}
@ -25,10 +45,44 @@ func GetBlackList() []BlackListItem {
return resList
}
func BlackListInit() {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
var netIP net.IP
var cidr *net.IPNet
for i := range programConfigure.BlackListConfigure.BlackList {
netIP = nil
cidr = nil
if strings.Contains(programConfigure.BlackListConfigure.BlackList[i].IP, "/") {
_, cidr, _ = net.ParseCIDR(programConfigure.BlackListConfigure.BlackList[i].IP)
} else {
netIP = net.ParseIP(programConfigure.BlackListConfigure.BlackList[i].IP)
}
programConfigure.BlackListConfigure.BlackList[i].Cidr = cidr
programConfigure.BlackListConfigure.BlackList[i].NetIP = netIP
}
}
func BlackListAdd(ip string, activelifeDuration int32) (string, error) {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
var err error
var netIP net.IP = nil
var cidr *net.IPNet = nil
if strings.Contains(ip, "/") {
_, cidr, err = net.ParseCIDR(ip)
if err != nil {
return "", fmt.Errorf("网段格式有误,转换出错:%s", err.Error())
}
} else {
netIP = net.ParseIP(ip)
if netIP == nil {
return "", fmt.Errorf("IP格式有误")
}
}
if activelifeDuration <= 0 {
activelifeDuration = 666666
}
@ -41,7 +95,7 @@ func BlackListAdd(ip string, activelifeDuration int32) (string, error) {
return EffectiveTimeStr, Save()
}
}
item := BlackListItem{IP: ip, EffectiveTime: EffectiveTimeStr}
item := BlackListItem{IP: ip, EffectiveTime: EffectiveTimeStr, NetIP: netIP, Cidr: cidr}
programConfigure.BlackListConfigure.BlackList = append(programConfigure.BlackListConfigure.BlackList, item)
return EffectiveTimeStr, Save()
}

View File

@ -10,7 +10,7 @@ import (
"strings"
"sync"
"github.com/gdy666/lucky/base"
"github.com/gdy666/lucky/socketproxy"
"github.com/gdy666/lucky/thirdlib/gdylib/fileutils"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
)
@ -18,6 +18,9 @@ import (
const defaultAdminAccount = "666"
const defaultAdminPassword = "666"
const defaultAdminListenPort = 16601
const defaultLogSize = 2048
const minLogSize = 1024
const maxLogSize = 40960
var runMode = "prod"
var version = "0.0.0"
@ -41,10 +44,10 @@ func FlushLoginRandomKey() {
}
type ConfigureRelayRule struct {
Name string `json:"Name"`
Configurestr string `json:"Configurestr"`
Enable bool `json:"Enable"`
Options base.RelayRuleOptions `json:"Options"`
Name string `json:"Name"`
Configurestr string `json:"Configurestr"`
Enable bool `json:"Enable"`
Options socketproxy.RelayRuleOptions `json:"Options"`
}
type BaseConfigure struct {
@ -54,15 +57,17 @@ type BaseConfigure struct {
AdminPassword string `json:"AdminPassword"` //登录密码
AllowInternetaccess bool `json:"AllowInternetaccess"` //允许外网访问
GlobalMaxConnections int64 `json:"GlobalMaxConnections"` //全局最大连接数
LogMaxSize int `json:"LogMaxSize"` //日志记录最大条数
}
type ProgramConfigure struct {
BaseConfigure BaseConfigure `json:"BaseConfigure"`
RelayRuleList []ConfigureRelayRule `json:"RelayRuleList"`
WhiteListConfigure WhiteListConfigure `json:"WhiteListConfigure"`
BlackListConfigure BlackListConfigure `json:"BlackListConfigure"`
DDNSConfigure DDNSConfigure `json:"DDNSConfigure"` //DDNS 参数设置
DDNSTaskList []DDNSTask `json:"DDNSTaskList"`
BaseConfigure BaseConfigure `json:"BaseConfigure"`
RelayRuleList []ConfigureRelayRule `json:"RelayRuleList"`
WhiteListConfigure WhiteListConfigure `json:"WhiteListConfigure"`
BlackListConfigure BlackListConfigure `json:"BlackListConfigure"`
DDNSConfigure DDNSConfigure `json:"DDNSConfigure"` //DDNS 参数设置
DDNSTaskList []DDNSTask `json:"DDNSTaskList"` //DDNS任务列表
ReverseProxyRuleList []ReverseProxyRule `json:"ReverseProxyRuleList"` //反向代理规则列表
}
var programConfigureMutex sync.RWMutex
@ -147,8 +152,14 @@ func SetBaseConfigure(conf *BaseConfigure) error {
defer programConfigureMutex.Unlock()
programConfigure.BaseConfigure = *conf
base.SetGlobalMaxConnections(conf.GlobalMaxConnections)
base.SetGlobalMaxProxyCount(conf.ProxyCountLimit)
socketproxy.SetGlobalMaxConnections(conf.GlobalMaxConnections)
socketproxy.SetGlobalMaxProxyCount(conf.ProxyCountLimit)
if conf.LogMaxSize < minLogSize {
conf.LogMaxSize = minLogSize
} else if conf.LogMaxSize > maxLogSize {
conf.LogMaxSize = maxLogSize
}
return Save()
}
@ -190,17 +201,23 @@ func Read(filePath string) (err error) {
}
if pc.BaseConfigure.GlobalMaxConnections <= 0 {
pc.BaseConfigure.GlobalMaxConnections = base.DEFAULT_GLOBAL_MAX_CONNECTIONS
pc.BaseConfigure.GlobalMaxConnections = socketproxy.DEFAULT_GLOBAL_MAX_CONNECTIONS
}
if pc.BaseConfigure.ProxyCountLimit <= 0 {
pc.BaseConfigure.ProxyCountLimit = base.DEFAULT_MAX_PROXY_COUNT
pc.BaseConfigure.ProxyCountLimit = socketproxy.DEFAULT_MAX_PROXY_COUNT
}
if pc.BaseConfigure.AdminWebListenPort <= 0 {
pc.BaseConfigure.AdminWebListenPort = 16601
}
if pc.BaseConfigure.LogMaxSize < minLogSize {
pc.BaseConfigure.LogMaxSize = minLogSize
} else if pc.BaseConfigure.LogMaxSize > maxLogSize {
pc.BaseConfigure.LogMaxSize = maxLogSize
}
programConfigure = pc
return nil
@ -273,7 +290,8 @@ func loadDefaultConfigure(
AdminAccount: defaultAdminAccount,
AdminPassword: defaultAdminPassword,
ProxyCountLimit: proxyCountLimit,
AllowInternetaccess: false}
AllowInternetaccess: false,
LogMaxSize: defaultLogSize}
whiteListConfigure := WhiteListConfigure{BaseConfigure: WhiteListBaseConfigure{ActivelifeDuration: 36, BasicAccount: defaultAdminAccount, BasicPassword: defaultAdminPassword}}
@ -282,11 +300,11 @@ func loadDefaultConfigure(
pc.WhiteListConfigure = whiteListConfigure
if pc.BaseConfigure.GlobalMaxConnections <= 0 {
pc.BaseConfigure.GlobalMaxConnections = base.DEFAULT_GLOBAL_MAX_CONNECTIONS
pc.BaseConfigure.GlobalMaxConnections = socketproxy.DEFAULT_GLOBAL_MAX_CONNECTIONS
}
if pc.BaseConfigure.ProxyCountLimit <= 0 {
pc.BaseConfigure.ProxyCountLimit = base.DEFAULT_MAX_PROXY_COUNT
pc.BaseConfigure.ProxyCountLimit = socketproxy.DEFAULT_MAX_PROXY_COUNT
}
if pc.BaseConfigure.AdminWebListenPort <= 0 {

728
config/reverseproxy.go Normal file
View File

@ -0,0 +1,728 @@
package config
import (
"fmt"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
"sync"
"time"
"github.com/gdy666/lucky/thirdlib/gdylib/ginutils"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
"github.com/gdy666/lucky/thirdlib/gdylib/logsbuffer"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
var reverseProxyLogsStore map[string]*logsbuffer.LogsBuffer
var reverseProxyLogsStoreMu sync.Mutex
var reverseProxyServerStore sync.Map
var reverseProxyServerStoreMu sync.Mutex
func init() {
reverseProxyLogsStore = make(map[string]*logsbuffer.LogsBuffer)
}
func CreateReverseProxyLogbuffer(key string, buffSize int) *logsbuffer.LogsBuffer {
reverseProxyLogsStoreMu.Lock()
defer reverseProxyLogsStoreMu.Unlock()
var buf *logsbuffer.LogsBuffer
var ok bool
if buf, ok = reverseProxyLogsStore[key]; !ok {
buf = &logsbuffer.LogsBuffer{}
buf.SetBufferSize(buffSize)
reverseProxyLogsStore[key] = buf
} else if buf.GetBufferSize() != buffSize {
buf.SetBufferSize(buffSize)
}
return buf
}
// TidyReverseProxyCache 整理反向代理日志缓存
func TidyReverseProxyCache() {
ruleList := GetReverseProxyRuleList()
var keyListBuffer strings.Builder
for _, rule := range ruleList {
keyListBuffer.WriteString(rule.DefaultProxy.Key)
keyListBuffer.WriteString(",")
for _, sr := range rule.ProxyList {
keyListBuffer.WriteString(sr.Key)
keyListBuffer.WriteString(",")
}
}
keyListStr := keyListBuffer.String()
reverseProxyLogsStoreMu.Lock()
defer reverseProxyLogsStoreMu.Unlock()
var needDeleteKeys []string
for k := range reverseProxyLogsStore {
if !strings.Contains(keyListStr, k) {
needDeleteKeys = append(needDeleteKeys, k)
}
}
for i := range needDeleteKeys {
delete(reverseProxyLogsStore, needDeleteKeys[i])
reverseProxyServerStore.Delete(needDeleteKeys[i])
}
}
type SubReverProxyRule struct {
Key string `json:"Key"`
initOnce sync.Once
Locations []string `json:"Locations"` //长度大于1时均衡负载
locationMutex *sync.Mutex `json:"-"`
locationsCount int `json:"-"`
locationIndex uint64 `json:"-"`
EnableAccessLog bool `json:"EnableAccessLog"` //开启日志
LogLevel int `json:"LogLevel"` //日志输出级别
LogOutputToConsole bool `json:"LogOutputToConsole"` //日志输出到终端
AccessLogMaxNum int `json:"AccessLogMaxNum"` //最大条数
WebListShowLastLogMaxCount int `json:"WebListShowLastLogMaxCount"` //前端列表显示最新日志最大条数
RequestInfoLogFormat string `json:"RequestInfoLogFormat"` //请求信息在日志中的格式
ForwardedByClientIP bool `json:"ForwardedByClientIP"`
TrustedCIDRsStrList []string `json:"TrustedCIDRsStrList"`
RemoteIPHeaders []string `json:"RemoteIPHeaders"` //识别客户端原始IP的Http请求头
TrustedProxyCIDRs []*net.IPNet `json:"-"`
AddRemoteIPToHeader bool `json:"AddRemoteIPToHeader"` //追加客户端连接IP到指定Header
AddRemoteIPHeaderKey string `json:"AddRemoteIPHeaderKey"`
EnableBasicAuth bool `json:"EnableBasicAuth"` //启用BasicAuth认证
BasicAuthUser string `json:"BasicAuthUser"` //如果配置此参数,暴露出去的 HTTP 服务需要采用 Basic Auth 的鉴权才能访问
BasicAuthPasswd string `json:"BasicAuthPasswd"` //结合 BasicAuthUser 使用
SafeIPMode string `json:"SafeIPMode"` //IP过滤模式 黑白名单
SafeUserAgentMode string `json:"SafeUserAgentMode"` //UserAgent 过滤模式 黑白名单
UserAgentfilter []string `json:"UserAgentfilter"` //UserAgent 过滤内容
CustomRobotTxt bool `json:"CustomRobotTxt"`
RobotTxt string `json:"RobotTxt"`
//------------------
logsBuffer *logsbuffer.LogsBuffer
logrus *logrus.Logger
logger *log.Logger
}
type ReverseProxyRule struct {
RuleName string `json:"RuleName"`
RuleKey string `json:"RuleKey"`
Enable bool `json:"Enable"`
ListenIP string `json:"ListenIP"`
ListenPort int `json:"ListenPort"`
EnableTLS bool `json:"EnableTLS"`
Network string `json:"Network"`
DefaultProxy struct {
SubReverProxyRule
} `json:"DefaultProxy"`
ProxyList []ReverseProxy `json:"ProxyList"`
domainsMap *sync.Map
initOnec sync.Once
}
func (r *ReverseProxyRule) Init() {
r.initOnec.Do(func() {
r.initDomainsMap()
})
}
func (r *SubReverProxyRule) Logf(level logrus.Level, c *gin.Context, format string, v ...any) {
clientIP := r.ClientIP(c)
remoteIP := c.RemoteIP()
method := c.Request.Method
host := c.Request.Host
//hostname, hostport := httputils.SplitHostPort(c.Request.Host)
url := c.Request.URL.String()
//path := c.Request.URL.Path
r.GetLogrus().WithFields(logrus.Fields{
"ClientIP": clientIP,
"RemoteIP": remoteIP,
"Method": method,
"Host": host,
// "Hostname": hostname,
// "Hostport": hostport,
"URL": url,
//"path": path,
"UserAgent": c.Request.UserAgent(),
}).Logf(level, format, v...)
}
func (r *SubReverProxyRule) HandlerReverseProxy(remote *url.URL, path string, c *gin.Context) {
proxy := httputil.NewSingleHostReverseProxy(remote)
proxy.Director = func(req *http.Request) {
req.Header = c.Request.Header
req.Host = remote.Host
req.URL.Scheme = remote.Scheme
req.URL.Host = remote.Host
req.URL.Path = path
if r.AddRemoteIPToHeader && r.AddRemoteIPHeaderKey != "" {
cip := r.ClientIP(c)
req.Header.Add(r.AddRemoteIPHeaderKey, cip)
}
}
proxy.ErrorLog = r.GetLogger()
proxy.ServeHTTP(c.Writer, c.Request)
}
func (r *SubReverProxyRule) PrintfToConsole(entry *logrus.Entry) error {
if !r.LogOutputToConsole {
return nil
}
s, _ := entry.String()
log.Print(s)
return nil
}
func (r *SubReverProxyRule) GetLogrus() *logrus.Logger {
if r.logrus == nil {
r.logrus = logrus.New()
r.logrus.SetLevel(logrus.Level(r.LogLevel))
r.GetLogsBuffer().SetFireCallback(r.PrintfToConsole)
r.logrus.SetOutput(r.GetLogsBuffer())
r.logrus.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: "2006-01-02 15:04:05",
DisableTimestamp: true,
DisableHTMLEscape: true,
DataKey: "ExtInfo",
})
r.logrus.AddHook(r.GetLogsBuffer())
}
return r.logrus
}
func (r *SubReverProxyRule) GetLogger() *log.Logger {
if r.logger == nil {
r.logger = log.New(r.GetLogsBuffer(), "", log.LstdFlags)
}
return r.logger
}
func (r *SubReverProxyRule) GetLogsBuffer() *logsbuffer.LogsBuffer {
if r.logsBuffer == nil {
r.logsBuffer = CreateReverseProxyLogbuffer(r.Key, r.AccessLogMaxNum)
}
return r.logsBuffer
}
func (r *SubReverProxyRule) checkupClientIP(ip string) bool {
return SafeCheck(r.SafeIPMode, ip)
}
func (r *SubReverProxyRule) checkupUserAgent(ua string) bool {
isContains := false
for _, c := range r.UserAgentfilter {
if strings.Contains(ua, c) {
isContains = true
break
}
}
switch r.SafeUserAgentMode {
case "whitelist":
return isContains
case "blacklist":
return !isContains
default:
return false
}
}
func (r *ReverseProxyRule) ReverseProxyHandler(c *gin.Context) {
path := c.Param("proxyPath")
hostName, _ := httputils.SplitHostPort(c.Request.Host)
rule, ok := r.GetSubRuleByDomain(hostName)
var subRule *SubReverProxyRule = nil
if ok && rule.Enable {
subRule = &rule.SubReverProxyRule
} else {
subRule = &r.DefaultProxy.SubReverProxyRule
}
if !subRule.checkupClientIP(subRule.ClientIP(c)) { //IP检查
subRule.Logf(logrus.WarnLevel, c, "IP[%s]禁止访问,当前Ip检查模式[%s]", subRule.ClientIP(c), subRule.SafeIPMode)
c.Abort()
return
}
if !subRule.checkupUserAgent(c.Request.UserAgent()) {
subRule.Logf(logrus.WarnLevel, c, "IP[%s]UA[%s]禁止访问,当前UA检查模式[%s]", subRule.ClientIP(c), c.Request.UserAgent(), subRule.SafeUserAgentMode)
c.Abort()
return
}
if !subRule.BasicAuthHandler(c) {
subRule.Logf(logrus.WarnLevel, c, "BasicAuth认证不通过")
c.Abort()
return
}
if subRule.CustomRobotTxt && c.Request.RequestURI == "/robots.txt" {
if c.Request.Method != "GET" && c.Request.Method != "HEAD" {
status := http.StatusOK
if c.Request.Method != "OPTIONS" {
status = http.StatusMethodNotAllowed
}
c.Header("Allow", "GET,HEAD,OPTIONS")
c.AbortWithStatus(status)
return
}
c.Data(http.StatusOK, "text/plain", []byte(subRule.RobotTxt))
subRule.Logf(logrus.InfoLevel, c, "触发自定义robots.txt")
return
}
location := subRule.GetLocation()
if location == "" && subRule.Key == r.RuleKey {
subRule.Logf(logrus.InfoLevel, c, "域名[%s]没有对应后端地址,默认后端地址没有设置", hostName)
c.Abort()
return
}
if subRule.Key == r.RuleKey {
subRule.Logf(logrus.InfoLevel, c, "[%s] 指向默认后端地址[%s%s]", hostName, location, c.Request.URL.String())
} else {
subRule.Logf(logrus.InfoLevel, c, "[%s] 指向后端地址[%s%s]", hostName, location, c.Request.URL.String())
}
remote, err := url.Parse(location)
if err != nil {
subRule.Logf(logrus.ErrorLevel, c, "后端地址转换出错:%s", err.Error())
c.JSON(http.StatusBadGateway, gin.H{"ret": 1, "msg": fmt.Sprintf("后端地址[%s] 转换出错:%s", location, err.Error())})
return
}
subRule.HandlerReverseProxy(remote, path, c)
}
func (r *ReverseProxyRule) GetSubRuleByDomain(domain string) (*ReverseProxy, bool) {
val, ok := r.domainsMap.Load(domain)
if !ok {
return nil, false
}
return val.(*ReverseProxy), true
}
type ReverseProxy struct {
SubReverProxyRule
Enable bool `json:"Enable"`
Remark string `json:"Remark"`
Domains []string `json:"Domains"` //自定义域名
}
func GetSubRuleByKey(ruleKey, proxyKey string) *SubReverProxyRule {
//rule := getSubRuleByKey()
rule := GetReverseProxyRuleByKey(ruleKey)
if rule == nil {
return nil
}
//fmt.Printf("FFF ruleKey:%s proxyKey:%s\n", ruleKey, proxyKey)
if proxyKey == "default" {
return &rule.DefaultProxy.SubReverProxyRule
}
for i := range rule.ProxyList {
if rule.ProxyList[i].Key == proxyKey {
return &rule.ProxyList[i].SubReverProxyRule
}
}
return nil
}
func (r *ReverseProxyRule) GetServer() *http.Server {
s, loaded := reverseProxyServerStore.Load(r.RuleKey)
if !loaded {
return nil
}
return s.(*http.Server)
}
func (r *ReverseProxyRule) SetServer(s *http.Server) {
if s == nil {
reverseProxyServerStore.Delete(r.RuleKey)
return
}
reverseProxyServerStore.Store(r.RuleKey, s)
}
func (r *ReverseProxyRule) ServerStart() error {
// r.smu.Lock()
// defer r.smu.Unlock()
reverseProxyServerStoreMu.Lock()
defer reverseProxyServerStoreMu.Unlock()
server := r.GetServer()
if server != nil {
return fmt.Errorf("RuleServer[%s]已经启动,请勿重复启动", r.Addr())
}
ginR := gin.New()
ginR.Any("/*proxyPath", r.ReverseProxyHandler)
server = &http.Server{
Addr: r.Addr(),
Handler: ginR,
}
ln, err := net.Listen(r.Network, r.Addr())
if err != nil {
return err
}
var serveResult error
go func() {
serveResult = server.Serve(ln)
}()
<-time.After(time.Millisecond * 300)
defer func() {
if serveResult == nil {
//setPreReverseProxyHttpServer(r.RuleKey, r.server)
r.SetServer(server)
}
}()
return serveResult
}
func (r *ReverseProxyRule) ServerStop() {
reverseProxyServerStoreMu.Lock()
defer reverseProxyServerStoreMu.Unlock()
server := r.GetServer()
if server == nil {
return
}
server.Close()
r.SetServer(nil)
}
func (r *ReverseProxyRule) initDomainsMap() error {
r.domainsMap = &sync.Map{}
for i := range r.ProxyList {
for j := range r.ProxyList[i].Domains {
_, loaded := r.domainsMap.LoadOrStore(r.ProxyList[i].Domains[j], &r.ProxyList[i])
if loaded {
return fmt.Errorf("前端域名[%s]冲突", r.ProxyList[i].Domains[j])
}
}
}
return nil
}
func (r *SubReverProxyRule) initOnceExec() {
r.initOnce.Do(func() {
r.locationsCount = len(r.Locations)
r.InitTrustedProxyCIDRs()
r.locationMutex = &sync.Mutex{}
})
}
func (r *SubReverProxyRule) GetLocation() string {
r.initOnceExec()
r.locationMutex.Lock()
defer func() {
r.locationIndex++
r.locationMutex.Unlock()
}()
if r.locationsCount == 0 {
return ""
}
return r.Locations[r.locationIndex%uint64(r.locationsCount)]
}
func (r *SubReverProxyRule) BasicAuthHandler(c *gin.Context) bool {
if !r.EnableBasicAuth || r.BasicAuthUser == "" {
return true
}
realm := "Basic realm=" + strconv.Quote("Authorization Required")
pairs := ginutils.ProcessAccounts(gin.Accounts{r.BasicAuthUser: r.BasicAuthPasswd})
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 false
}
c.Set("user", user)
return true
}
func (r *SubReverProxyRule) InitTrustedProxyCIDRs() error {
var res []*net.IPNet
for i := range r.TrustedCIDRsStrList {
if strings.TrimSpace(r.TrustedCIDRsStrList[i]) == "" {
continue
}
_, cidr, err := net.ParseCIDR(r.TrustedCIDRsStrList[i])
if err != nil {
return fmt.Errorf("[%s]网段格式有误", r.TrustedCIDRsStrList[i])
}
res = append(res, cidr)
}
r.TrustedProxyCIDRs = res
return nil
}
func (r *SubReverProxyRule) ClientIP(c *gin.Context) string {
remoteIP := net.ParseIP(c.RemoteIP())
if remoteIP == nil {
return ""
}
trusted := r.isTrustedProxy(remoteIP)
if trusted && r.ForwardedByClientIP && r.RemoteIPHeaders != nil {
for _, headerName := range r.RemoteIPHeaders {
ip, valid := r.validateHeader(c.Request.Header.Get(headerName))
if valid {
return ip
}
}
}
return remoteIP.String()
}
func (r *SubReverProxyRule) validateHeader(header string) (clientIP string, valid bool) {
if header == "" {
return "", false
}
items := strings.Split(header, ",")
for i := len(items) - 1; i >= 0; i-- {
ipStr := strings.TrimSpace(items[i])
ip := net.ParseIP(ipStr)
if ip == nil {
break
}
if (i == 0) || (!r.isTrustedProxy(ip)) {
return ipStr, true
}
}
return "", false
}
func (r *SubReverProxyRule) isTrustedProxy(ip net.IP) bool {
r.initOnceExec()
if r.TrustedProxyCIDRs == nil {
return false
}
for _, cidr := range r.TrustedProxyCIDRs {
if cidr.Contains(ip) {
return true
}
}
return false
}
func (r *ReverseProxyRule) Addr() string {
return fmt.Sprintf("%s:%d", r.ListenIP, r.ListenPort)
}
type LogItem struct {
ProxyKey string
ClientIP string
LogContent string
LogTime string
}
// 2006-01-02 15:04:05
func ReverseProxyLogConvert(lg *logsbuffer.LogItem) any {
l := LogItem{
LogContent: lg.Content,
LogTime: time.Unix(lg.Timestamp/int64(time.Second), 0).Format("2006-01-02 15:04:05")}
return l
}
func (r *ReverseProxyRule) GetLastLogs() map[string][]any {
res := make(map[string][]any)
res["default"] = r.DefaultProxy.GetLogsBuffer().GetLastLogs(ReverseProxyLogConvert, r.DefaultProxy.WebListShowLastLogMaxCount)
for i := range r.ProxyList {
res[r.ProxyList[i].Key] = r.ProxyList[i].GetLogsBuffer().GetLastLogs(
ReverseProxyLogConvert, r.ProxyList[i].WebListShowLastLogMaxCount)
}
return res
}
//------------------------------------------------------------
func GetReverseProxyRuleList() []*ReverseProxyRule {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
var resList []*ReverseProxyRule
for i := range programConfigure.ReverseProxyRuleList {
programConfigure.ReverseProxyRuleList[i].Init()
rule := programConfigure.ReverseProxyRuleList[i]
resList = append(resList, &rule)
}
return resList
}
func GetReverseProxyRuleByKey(ruleKey string) *ReverseProxyRule {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
ruleIndex := -1
for i := range programConfigure.ReverseProxyRuleList {
if programConfigure.ReverseProxyRuleList[i].RuleKey == ruleKey {
ruleIndex = i
break
}
}
if ruleIndex == -1 {
return nil
}
res := programConfigure.ReverseProxyRuleList[ruleIndex]
return &res
}
func ReverseProxyRuleListAdd(rule *ReverseProxyRule) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
programConfigure.ReverseProxyRuleList = append(programConfigure.ReverseProxyRuleList, *rule)
return Save()
}
func ReverseProxyRuleListDelete(ruleKey string) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
ruleIndex := -1
for i := range programConfigure.ReverseProxyRuleList {
if programConfigure.ReverseProxyRuleList[i].RuleKey == ruleKey {
ruleIndex = i
break
}
}
if ruleIndex == -1 {
return fmt.Errorf("找不到需要删除的DDNS任务")
}
programConfigure.ReverseProxyRuleList = DeleteReverseProxyRuleListlice(programConfigure.ReverseProxyRuleList, ruleIndex)
return Save()
}
func EnableReverseProxyRuleByKey(ruleKey string, enable bool) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
ruleIndex := -1
for i := range programConfigure.DDNSTaskList {
if programConfigure.ReverseProxyRuleList[i].RuleKey == ruleKey {
ruleIndex = i
break
}
}
if ruleIndex == -1 {
return fmt.Errorf("开关反向代理规则失败,ruleKey %s 未找到", ruleKey)
}
programConfigure.ReverseProxyRuleList[ruleIndex].Enable = enable
return Save()
}
func EnableReverseProxySubRule(ruleKey, proxyKey string, enable bool) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
ruleIndex := -1
for i := range programConfigure.DDNSTaskList {
if programConfigure.ReverseProxyRuleList[i].RuleKey == ruleKey {
ruleIndex = i
break
}
}
if ruleIndex == -1 {
return fmt.Errorf("开关反向代理子规则失败,ruleKey %s 未找到", ruleKey)
}
proxyIndex := -1
for i := range programConfigure.ReverseProxyRuleList[ruleIndex].ProxyList {
if programConfigure.ReverseProxyRuleList[ruleIndex].ProxyList[i].Key == proxyKey {
proxyIndex = i
break
}
}
if proxyIndex == -1 {
return fmt.Errorf("开关反向代理子规则失败,proxyKey %s 未找到", proxyKey)
}
programConfigure.ReverseProxyRuleList[ruleIndex].ProxyList[proxyIndex].Enable = enable
return Save()
}
func UpdateReverseProxyRulet(rule ReverseProxyRule) error {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
ruleIndex := -1
for i := range programConfigure.DDNSTaskList {
if programConfigure.ReverseProxyRuleList[i].RuleKey == rule.RuleKey {
ruleIndex = i
break
}
}
if ruleIndex == -1 {
return fmt.Errorf("找不到需要更新的反向代理规则")
}
// rule.RuleKey = programConfigure.ReverseProxyRuleList[ruleIndex].RuleKey
programConfigure.ReverseProxyRuleList[ruleIndex] = rule
return Save()
}
func DeleteReverseProxyRuleListlice(a []ReverseProxyRule, deleteIndex int) []ReverseProxyRule {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}

View File

@ -1,4 +1,4 @@
//Copyright 2022 gdy, 272288813@qq.com
// Copyright 2022 gdy, 272288813@qq.com
package config
import (
@ -22,17 +22,17 @@ func whiteListCheck(ip string) bool {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
if programConfigure == nil {
//log.Printf("AAAA")
return false
}
for _, item := range programConfigure.WhiteListConfigure.WhiteList {
if item.IP != ip {
if !item.Contains(ip) {
continue
}
itemEffectiveTime, err := time.ParseInLocation("2006-01-02 15:04:05", item.EffectiveTime, time.Local)
if err != nil {
//log.Printf("BBBB")
return false
}
@ -55,7 +55,7 @@ func blackListCheck(ip string) bool {
}
for _, item := range programConfigure.BlackListConfigure.BlackList {
if item.IP != ip {
if !item.Contains(ip) {
continue
}
itemEffectiveTime, err := time.ParseInLocation("2006-01-02 15:04:05", item.EffectiveTime, time.Local)

View File

@ -1,7 +1,12 @@
// Copyright 2022 gdy, 272288813@qq.com
package config
import "time"
import (
"fmt"
"net"
"strings"
"time"
)
type WhiteListConfigure struct {
BaseConfigure WhiteListBaseConfigure `json:"BaseConfigure"`
@ -9,8 +14,25 @@ type WhiteListConfigure struct {
}
type WhiteListItem struct {
IP string `json:"IP"`
EffectiveTime string `json:"Effectivetime"` //有效时间
IP string `json:"IP"`
EffectiveTime string `json:"Effectivetime"` //有效时间
NetIP net.IP `json:"-"`
Cidr *net.IPNet `json:"-"`
}
func (w *WhiteListItem) Contains(ip string) bool {
netIP := net.ParseIP(ip)
if netIP == nil {
return false
}
if w.NetIP != nil {
return w.NetIP.Equal(netIP)
}
if w.Cidr != nil {
return w.Cidr.Contains(netIP)
}
return false
}
type WhiteListBaseConfigure struct {
@ -52,10 +74,44 @@ func GetWhiteList() []WhiteListItem {
return resList
}
func WhiteListInit() {
programConfigureMutex.RLock()
defer programConfigureMutex.RUnlock()
var netIP net.IP
var cidr *net.IPNet
for i := range programConfigure.WhiteListConfigure.WhiteList {
netIP = nil
cidr = nil
if strings.Contains(programConfigure.WhiteListConfigure.WhiteList[i].IP, "/") {
_, cidr, _ = net.ParseCIDR(programConfigure.WhiteListConfigure.WhiteList[i].IP)
} else {
netIP = net.ParseIP(programConfigure.WhiteListConfigure.WhiteList[i].IP)
}
programConfigure.WhiteListConfigure.WhiteList[i].Cidr = cidr
programConfigure.WhiteListConfigure.WhiteList[i].NetIP = netIP
}
}
func WhiteListAdd(ip string, activelifeDuration int32) (string, error) {
programConfigureMutex.Lock()
defer programConfigureMutex.Unlock()
var err error
var netIP net.IP = nil
var cidr *net.IPNet = nil
if strings.Contains(ip, "/") {
_, cidr, err = net.ParseCIDR(ip)
if err != nil {
return "", fmt.Errorf("网段格式有误,转换出错:%s", err.Error())
}
} else {
netIP = net.ParseIP(ip)
if netIP == nil {
return "", fmt.Errorf("IP格式有误")
}
}
if activelifeDuration <= 0 {
activelifeDuration = programConfigure.WhiteListConfigure.BaseConfigure.ActivelifeDuration
}
@ -68,7 +124,7 @@ func WhiteListAdd(ip string, activelifeDuration int32) (string, error) {
return EffectiveTimeStr, Save()
}
}
item := WhiteListItem{IP: ip, EffectiveTime: EffectiveTimeStr}
item := WhiteListItem{IP: ip, EffectiveTime: EffectiveTimeStr, NetIP: netIP, Cidr: cidr}
programConfigure.WhiteListConfigure.WhiteList = append(programConfigure.WhiteListConfigure.WhiteList, item)
return EffectiveTimeStr, Save()
}

View File

@ -166,6 +166,10 @@ func (dnspod *Dnspod) getRecordList(domain *ddnscore.Domain, typ string) (result
params.Add("sub_domain", domain.GetSubDomain())
params.Add("format", "json")
if !params.Has("record_line") {
params.Add("record_line", "默认")
}
client, e := dnspod.CreateHTTPClient()
if e != nil {
err = e

100
ddns/godaddy.go Normal file
View File

@ -0,0 +1,100 @@
package ddns
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strconv"
"github.com/gdy666/lucky/ddnscore.go"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
)
type godaddyRecord struct {
Data string `json:"data"`
Name string `json:"name"`
TTL int `json:"ttl"`
Type string `json:"type"`
}
type godaddyRecords []godaddyRecord
type GoDaddy struct {
DNSCommon
TTL int
header http.Header
client *http.Client
}
// Init 初始化
func (gd *GoDaddy) Init(task *ddnscore.DDNSTaskInfo) {
gd.DNSCommon.Init(task)
// if task.TTL == "" {
// // 默认600s
// gd.TTL = 600
// } else {
// gd.TTL = task.TTL
// }
if task.TTL == "" {
// 默认300s
gd.TTL = 600
} else {
ttl, err := strconv.Atoi(task.TTL)
if err != nil {
gd.TTL = 600
} else {
gd.TTL = ttl
}
}
gd.header = map[string][]string{
"Authorization": {fmt.Sprintf("sso-key %s:%s", task.DNS.ID, task.DNS.Secret)},
"Content-Type": {"application/json"},
}
//g.throttle, _ = util.GetThrottle(55)
gd.client, _ = gd.CreateHTTPClient()
gd.SetCreateUpdateDomainFunc(gd.createUpdateDomain)
}
func (gd *GoDaddy) createUpdateDomain(recordType, ipAddr string, domain *ddnscore.Domain) {
_, err := gd.sendReq(http.MethodPut, recordType, domain, &godaddyRecords{godaddyRecord{
Data: ipAddr,
Name: domain.SubDomain,
TTL: gd.TTL,
Type: recordType,
}})
if err != nil {
domain.SetDomainUpdateStatus(ddnscore.UpdatedFailed, err.Error())
}
}
func (gd *GoDaddy) sendReq(method string, rType string, domain *ddnscore.Domain, data any) (*godaddyRecords, error) {
var body *bytes.Buffer
if data != nil {
if buffer, err := json.Marshal(data); err != nil {
return nil, err
} else {
body = bytes.NewBuffer(buffer)
}
}
path := fmt.Sprintf("https://api.godaddy.com/v1/domains/%s/records/%s/%s",
domain.DomainName, rType, domain.SubDomain)
req, err := http.NewRequest(method, path, body)
if err != nil {
return nil, err
}
req.Header = gd.header
resp, err := gd.client.Do(req)
if err != nil {
return nil, err
}
result := &godaddyRecords{}
httputils.GetAndParseJSONResponseFromHttpResponse(resp, result)
return result, nil
}

View File

@ -101,8 +101,6 @@ func DDNSTaskInfoMapUpdateDomainInfo(task *DDNSTaskInfo) {
state.(*DDNSTaskState).Domains = task.TaskState.Domains
}
//func DDNSTaskInfo
func DDNSTaskInfoMapDelete(key string) {
taskInfoMapMutex.Lock()
defer taskInfoMapMutex.Unlock()
@ -126,8 +124,7 @@ func GetDDNSTaskInfoList() []*DDNSTaskInfo {
var res []*DDNSTaskInfo
for i := range ddnsTaskList {
ti := CreateDDNSTaskInfo(ddnsTaskList[i])
res = append(res, &ti)
taskInfoMap.Store(ddnsTaskList[i].TaskKey, &ti.TaskState)
res = append(res, ti)
}
return res
}
@ -140,25 +137,23 @@ func GetDDNSTaskInfoByKey(key string) *DDNSTaskInfo {
return nil
}
info := CreateDDNSTaskInfo(ddnsConf)
return &info
return info
}
func CreateDDNSTaskInfo(task *config.DDNSTask) DDNSTaskInfo {
func CreateDDNSTaskInfo(task *config.DDNSTask) *DDNSTaskInfo {
var res DDNSTaskInfo
res.DDNSTask = *task
info, ok := taskInfoMap.Load(task.TaskKey)
if ok {
res.TaskState = *info.(*DDNSTaskState)
} else {
var ds DDNSTaskState
ds.Init(res.Domains)
res.TaskState.Init(res.Domains)
if task.Enable {
ds.SetDomainUpdateStatus(UpdateWaiting, "")
res.TaskState.SetDomainUpdateStatus(UpdateWaiting, "")
} else {
ds.SetDomainUpdateStatus(UpdateStop, "")
res.TaskState.SetDomainUpdateStatus(UpdateStop, "")
}
res.TaskState = ds
taskInfoMap.Store(task.TaskKey, &res.TaskState)
}
return res
return &res
}

View File

@ -10,6 +10,7 @@ import (
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/thirdlib/gdylib/httputils"
"github.com/gdy666/lucky/thirdlib/gdylib/netinterfaces"
)
// Ipv4Reg IPv4正则
@ -50,7 +51,7 @@ func (d *DDNSTaskInfo) CheckIPChange() (ipAddr string, change bool) {
func (d *DDNSTaskInfo) getIpv4Addr() (result string) {
// 判断从哪里获取IP
if d.GetType == "netInterface" {
result = GetIPFromNetInterface("IPv4", d.NetInterface, d.IPReg)
result = netinterfaces.GetIPFromNetInterface("IPv4", d.NetInterface, d.IPReg)
return
}
@ -111,7 +112,7 @@ func (d *DDNSTaskInfo) getIpv4Addr() (result string) {
func (d *DDNSTaskInfo) getIpv6Addr() (result string) {
// 判断从哪里获取IP
if d.GetType == "netInterface" {
result = GetIPFromNetInterface("IPv6", d.NetInterface, d.IPReg)
result = netinterfaces.GetIPFromNetInterface("IPv6", d.NetInterface, d.IPReg)
return
}

19
go.mod
View File

@ -9,8 +9,9 @@ require (
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/guonaihong/gout v0.3.1
github.com/miekg/dns v1.1.50
github.com/shirou/gopsutil/v3 v3.22.7
golang.org/x/net v0.0.0-20220809012201-f428fae20770
github.com/shirou/gopsutil/v3 v3.22.8
github.com/sirupsen/logrus v1.9.0
golang.org/x/net v0.0.0-20220921203646-d300de134e69
)
require (
@ -18,24 +19,24 @@ require (
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.11.0 // indirect
github.com/goccy/go-json v0.9.10 // indirect
github.com/go-playground/validator/v10 v10.11.1 // indirect
github.com/goccy/go-json v0.9.11 // 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-20220517141722-cf486979b281 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c // indirect
github.com/mattn/go-isatty v0.0.16 // 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.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.5.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-20220722155217-630584e8d5aa // indirect
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 // indirect
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/protobuf v1.28.1 // indirect

43
go.sum
View File

@ -19,19 +19,20 @@ github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa
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.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc=
github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/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.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/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/guonaihong/gout v0.3.1 h1:pj/44Jw0TTmcHF2RjMaCWhKPwCH98YuQejbN15Hts/o=
@ -49,10 +50,11 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281 h1:aczX6NMOtt6L4YT0fQvKkDK6LZEtdOso9sUH89V1+P0=
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281/go.mod h1:lc+czkgO/8F7puNki5jk8QyujbfK1LOT7Wl0ON2hxyk=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY=
github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -61,8 +63,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
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/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -74,15 +76,16 @@ github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:Om
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.7 h1:flKnuCMfUUrO+oAvwAd6GKZgnPzr098VA/UJ14nhJd4=
github.com/shirou/gopsutil/v3 v3.22.7/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI=
github.com/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y=
github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
@ -101,8 +104,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY=
golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -113,8 +116,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220809012201-f428fae20770 h1:dIi4qVdvjZEjiMDv7vhokAZNGnz3kepwuXqFKYDdDMs=
golang.org/x/net v0.0.0-20220809012201-f428fae20770/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220921203646-d300de134e69 h1:hUJpGDpnfwdJW8iNypFjmSY0sCBEL+spFTZ2eO+Sfps=
golang.org/x/net v0.0.0-20220921203646-d300de134e69/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
@ -132,8 +135,10 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs=
golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

27
main.go
View File

@ -10,18 +10,19 @@ import (
"syscall"
"time"
"github.com/gdy666/lucky/base"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/ddns"
"github.com/gdy666/lucky/reverseproxy"
"github.com/gdy666/lucky/rule"
"github.com/gdy666/lucky/socketproxy"
)
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)")
udpPackageSize = flag.Int("ups", socketproxy.UDP_DEFAULT_PACKAGE_SIZE, "udp package max size")
smc = flag.Int64("smc", socketproxy.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")
@ -36,6 +37,11 @@ var (
var runTime time.Time
func init() {
var cstZone = time.FixedZone("CST", 8*3600) // 东八
time.Local = cstZone
}
func main() {
flag.Parse()
config.InitAppInfo(version, date)
@ -55,16 +61,19 @@ func main() {
gcf := config.GetConfig()
config.BlackListInit()
config.WhiteListInit()
//fmt.Printf("*gcf:%v\n", *gcf)
base.SetSafeCheck(config.SafeCheck)
base.SetGlobalMaxConnections(gcf.BaseConfigure.GlobalMaxConnections)
base.SetGlobalMaxProxyCount(gcf.BaseConfigure.ProxyCountLimit)
socketproxy.SetSafeCheck(config.SafeCheck)
socketproxy.SetGlobalMaxConnections(gcf.BaseConfigure.GlobalMaxConnections)
socketproxy.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)
RunAdminWeb(gcf.BaseConfigure.AdminWebListenPort, gcf.BaseConfigure.LogMaxSize)
runTime = time.Now()
@ -89,6 +98,8 @@ func main() {
ddns.Run(time.Duration(ddnsConf.FirstCheckDelay)*time.Second, time.Duration(ddnsConf.Intervals)*time.Second)
}
reverseproxy.InitReverseProxyServer()
//ddns.RunTimer(time.Second, time.Second*30)
//initProxyList()
@ -113,7 +124,7 @@ func main() {
}
func LoadRuleListFromCMD(args []string) {
options := base.RelayRuleOptions{UDPPackageSize: *udpPackageSize,
options := socketproxy.RelayRuleOptions{UDPPackageSize: *udpPackageSize,
SingleProxyMaxConnections: *smc,
UDPProxyPerformanceMode: *upm,
UDPShortMode: *udpshort}

BIN
previews/reverseproxy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

76
reverseproxy/proxy.go Normal file
View File

@ -0,0 +1,76 @@
package reverseproxy
import (
"fmt"
"log"
"github.com/gdy666/lucky/config"
)
func InitReverseProxyServer() {
ruleList := config.GetReverseProxyRuleList()
for ruleIndex := range ruleList {
if ruleList[ruleIndex].Enable {
startRes := ruleList[ruleIndex].ServerStart()
if startRes == nil {
log.Printf("启动反向代理服务[%s]成功", ruleList[ruleIndex].Addr())
} else {
log.Printf("启动反向代理服务[%s]失败:%s", ruleList[ruleIndex].Addr(), startRes.Error())
}
}
}
}
func EnableRuleByKey(key string, enable bool) error {
rule := config.GetReverseProxyRuleByKey(key)
if rule == nil {
return fmt.Errorf("GetReverseProxyRuleByKey not found:%s", key)
}
if enable {
err := rule.ServerStart()
if err != nil {
log.Printf("启用反向代理规则[%s]出错:%s", rule.Addr(), err.Error())
config.EnableReverseProxyRuleByKey(key, false)
return fmt.Errorf("启用反向代理规则[%s]出错:%s", rule.Addr(), err.Error())
} else {
log.Printf("启用反向代理规则[%s]成功", rule.Addr())
}
} else {
rule.ServerStop()
log.Printf("停用反向代理规则[%s]成功", rule.Addr())
}
return config.EnableReverseProxyRuleByKey(key, enable)
}
type RuleInfo struct {
config.ReverseProxyRule
AccessLogs map[string][]any
}
func GetProxyRuleListInfo() *[]RuleInfo {
ruleList := config.GetReverseProxyRuleList()
var res []RuleInfo
for i := range ruleList {
//ti := createProxyRuleInfo(nil, ruleList[i])
var ri RuleInfo
ri.ReverseProxyRule = *ruleList[i]
ri.AccessLogs = ruleList[i].GetLastLogs()
res = append(res, ri)
}
return &res
}
func GetAccessLogs(ruleKey, proxyKey string, pageSize, page int) (int, []any) {
var res []any
total := 0
subRule := config.GetSubRuleByKey(ruleKey, proxyKey)
if subRule == nil {
return 0, res
}
total, res = subRule.GetLogsBuffer().GetLogsByLimit(config.ReverseProxyLogConvert, pageSize, page)
return total, res
}

View File

@ -1,12 +1,12 @@
//Copyright 2022 gdy, 272288813@qq.com
// Copyright 2022 gdy, 272288813@qq.com
package rule
import (
"fmt"
"sync"
"github.com/gdy666/lucky/base"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/socketproxy"
)
var globalRelayRules *[]RelayRule
@ -177,14 +177,14 @@ func EnableAllRelayRule() error {
}
for i := range *globalRelayRules {
if GetGlobalEnableProxyCount()+(*globalRelayRules)[i].GetProxyCount() <= base.GetGlobalMaxProxyCount() {
if GetGlobalEnableProxyCount()+(*globalRelayRules)[i].GetProxyCount() <= socketproxy.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 GetGlobalEnableProxyCount()+(*globalRelayRules)[i].GetProxyCount() > socketproxy.DEFAULT_MAX_PROXY_COUNT {
if err == nil {
err = fmt.Errorf("\n\t超出代理数最大限制,规则[%s]未启用", (*globalRelayRules)[i].MainConfigure)
} else {
@ -245,7 +245,7 @@ func DeleteRuleSlice(a []RelayRule, deleteIndex int) []RelayRule {
return a[:j]
}
//syncRuleListToConfigure 同步规则列表到配置
// syncRuleListToConfigure 同步规则列表到配置
func syncRuleListToConfigure() error {
var ruleList []config.ConfigureRelayRule
for i := range *globalRelayRules {

View File

@ -1,9 +1,7 @@
//Copyright 2022 gdy, 272288813@qq.com
// Copyright 2022 gdy, 272288813@qq.com
package rule
import (
"github.com/gdy666/lucky/base"
)
import "github.com/gdy666/lucky/socketproxy"
type RelayRuleProxyInfo struct {
Proxy string `json:"Proxy"`
@ -58,7 +56,7 @@ func GetRelayRuleList() (*[]RelayRule, map[string][]RelayRuleProxyInfo) {
// return info
// }
func GetProxyInfo(p base.Proxy) map[string]string {
func GetProxyInfo(p socketproxy.Proxy) map[string]string {
pi := make(map[string]string)
pi["proxyType"] = p.GetProxyType()
pi["key"] = p.GetKey()

View File

@ -8,23 +8,23 @@ import (
"strconv"
"strings"
"github.com/gdy666/lucky/base"
"github.com/gdy666/lucky/socketproxy"
)
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:"-"`
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 socketproxy.RelayRuleOptions `json:"Options"`
SubRuleList []SubRelayRule `json:"SubRuleList"`
From string `json:"From"`
IsEnable bool `json:"Enable"`
proxyList *[]socketproxy.Proxy `json:"-"`
}
type SubRelayRule struct {
@ -63,8 +63,8 @@ func (r *RelayRule) Disable() {
}
}
func GetRelayRulesFromCMD(configureList []string, options *base.RelayRuleOptions) (relayRules *[]RelayRule, err error) {
//proxyMap := make(map[string]base.Proxy)
func GetRelayRulesFromCMD(configureList []string, options *socketproxy.RelayRuleOptions) (relayRules *[]RelayRule, err error) {
//proxyMap := make(map[string]socketproxy.Proxy)
var relayRuleList []RelayRule
@ -95,7 +95,7 @@ func (r *RelayRule) CreateMainConfigure() (configure string) {
return configure
}
func CreateRuleByConfigureAndOptions(name, configureStr string, options base.RelayRuleOptions) (rule *RelayRule, err error) {
func CreateRuleByConfigureAndOptions(name, configureStr string, options socketproxy.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)
@ -115,12 +115,12 @@ func CreateRuleByConfigureAndOptions(name, configureStr string, options base.Rel
// }
// }
var pl []base.Proxy
var pl []socketproxy.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,
p, e := socketproxy.CreateProxy(r.SubRuleList[i].ProxyType,
r.SubRuleList[i].BindIP,
r.SubRuleList[i].TargetHost,
nil,
@ -137,7 +137,7 @@ func CreateRuleByConfigureAndOptions(name, configureStr string, options base.Rel
continue
}
p, e := base.CreateProxy(r.SubRuleList[i].ProxyType,
p, e := socketproxy.CreateProxy(r.SubRuleList[i].ProxyType,
r.SubRuleList[i].BindIP,
r.SubRuleList[i].TargetHost,
&r.BalanceTargetAddressList,
@ -375,13 +375,13 @@ func checkProxyType(proxyTypeList []string) error {
}
// CheckProxyConflict 冲突检查
func CheckProxyConflict(proxyList *[]base.Proxy, proxyType, listenIP string, listenPort int) error {
proxyMap := make(map[string]base.Proxy)
func CheckProxyConflict(proxyList *[]socketproxy.Proxy, proxyType, listenIP string, listenPort int) error {
proxyMap := make(map[string]socketproxy.Proxy)
for i, p := range *proxyList {
proxyMap[p.GetKey()] = (*proxyList)[i]
}
key := base.GetProxyKey(proxyType, listenIP, listenPort)
key := socketproxy.GetProxyKey(proxyType, listenIP, listenPort)
if _, ok := proxyMap[key]; ok {
return fmt.Errorf("绑定的地址和端口存在冲突![%s]", key)
}

View File

@ -1,5 +1,5 @@
//Copyright 2022 gdy, 272288813@qq.com
package base
// Copyright 2022 gdy, 272288813@qq.com
package socketproxy
import (
"sync/atomic"

View File

@ -1,5 +1,5 @@
//Copyright 2022 gdy, 272288813@qq.com
package base
// Copyright 2022 gdy, 272288813@qq.com
package socketproxy
import (
"errors"

View File

@ -1,5 +1,5 @@
//Copyright 2022 gdy, 272288813@qq.com
package base
// Copyright 2022 gdy, 272288813@qq.com
package socketproxy
import (
"fmt"

View File

@ -1,5 +1,5 @@
//Copyright 2022 gdy, 272288813@qq.com
package base
// Copyright 2022 gdy, 272288813@qq.com
package socketproxy
import (
"fmt"
@ -116,7 +116,6 @@ func (p *TCPUDPProxyCommonConf) GetListentAddress() string {
} else {
p.listentAddress = fmt.Sprintf("%s:%d", p.listenIP, p.listenPort)
}
}
return p.listentAddress
}

View File

@ -1,5 +1,5 @@
//Copyright 2022 gdy, 272288813@qq.com
package base
// Copyright 2022 gdy, 272288813@qq.com
package socketproxy
import (
"fmt"
@ -173,7 +173,7 @@ func (p *UDPProxy) StopProxy() {
}
//ReadFromTargetOnce one clientAddr only read once,short mode eg: udp dns
// ReadFromTargetOnce one clientAddr only read once,short mode eg: udp dns
func (p *UDPProxy) ReadFromTargetOnce() bool {
if p.targetPort == 53 || p.ShortMode {
return true

View File

@ -0,0 +1,63 @@
package ginutils
import (
"crypto/subtle"
"encoding/base64"
"unsafe"
"github.com/gin-gonic/gin"
)
type BasicAuthPair struct {
value string
user string
}
type BasicAuthPairs []BasicAuthPair
func ProcessAccounts(accounts gin.Accounts) BasicAuthPairs {
length := len(accounts)
assert1(length > 0, "Empty list of authorized credentials")
pairs := make(BasicAuthPairs, 0, length)
for user, password := range accounts {
assert1(user != "", "User can not be empty")
value := authorizationHeader(user, password)
pairs = append(pairs, BasicAuthPair{
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 BasicAuthPairs) 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
}

View File

@ -0,0 +1,59 @@
package ginutils
import (
"errors"
"fmt"
"time"
"github.com/golang-jwt/jwt"
)
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
}

View File

@ -1,25 +1,26 @@
package web
package ginutils
import (
"io/fs"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func HandlerStaticFiles() gin.HandlerFunc {
fileServer := http.FileServer(http.FS(stafs))
func HandlerStaticFiles(files fs.FS) gin.HandlerFunc {
fileServer := http.FileServer(http.FS(files))
return func(c *gin.Context) {
staticFile := isStaticFile(http.FS(stafs), c.Request.URL.Path, true)
staticFile := isStaticFile(http.FS(files), c.Request.URL.Path, true)
if staticFile {
fileServer.ServeHTTP(c.Writer, c.Request)
c.Abort()
return
}
c.Next()
}
}
//
func isStaticFile(fs http.FileSystem, name string, redirect bool) (isFile bool) {
const indexPage = "/index.html"
if strings.HasSuffix(name, indexPage) {

View File

@ -1,17 +1,13 @@
package ginutils
import (
"errors"
"fmt"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
)
//Cors 处理跨域请求,支持options访问
// Cors 处理跨域请求,支持options访问
func Cors(params ...interface{}) gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
@ -31,57 +27,7 @@ func Cors(params ...interface{}) gin.HandlerFunc {
}
}
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 获取子域名部分
// GetChildDomain 获取子域名部分
func GetChildDomain(host string) string {
hostSplitList := strings.Split(host, ".")
listLen := len(hostSplitList)

View File

@ -24,6 +24,36 @@ func init() {
globalTransportMap = make(map[string]*http.Transport)
}
func SplitHostPort(hostPort string) (host, port string) {
host = hostPort
colon := strings.LastIndexByte(host, ':')
if colon != -1 && validOptionalPort(host[colon:]) {
host, port = host[:colon], host[colon+1:]
}
if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
host = host[1 : len(host)-1]
}
return
}
func validOptionalPort(port string) bool {
if port == "" {
return true
}
if port[0] != ':' {
return false
}
for _, b := range port[1:] {
if b < '0' || b > '9' {
return false
}
}
return true
}
func GetAndParseJSONResponseFromHttpResponse(resp *http.Response, result interface{}) error {
bytes, err := GetBytesFromHttpResponse(resp)
if err != nil {

View File

@ -0,0 +1,171 @@
package logsbuffer
import (
"fmt"
"strings"
"sync"
"time"
"github.com/sirupsen/logrus"
)
type LogsBuffer struct {
bufferSize int
preTimeStamp int64
timeStampIndex int64
logsStore []LogItem
mu sync.RWMutex
fireCallback func(entry *logrus.Entry) error
}
type LogItem struct {
Timestamp int64
Content string
Data map[string]any
}
func (l *LogsBuffer) AddLog(t time.Time, msg string, data map[string]any) {
l.mu.Lock()
defer l.mu.Unlock()
if l.preTimeStamp == t.UnixNano() {
l.timeStampIndex++
} else {
l.timeStampIndex = 0
}
li := LogItem{Timestamp: t.UnixNano() + l.timeStampIndex, Content: strings.TrimSpace(msg), Data: data}
l.logsStore = append(l.logsStore, li)
l.preTimeStamp = t.UnixNano()
if len(l.logsStore) > l.bufferSize+16 {
l.logsStore = l.logsStore[len(l.logsStore)-l.bufferSize:]
}
}
func (l *LogsBuffer) Fire(entry *logrus.Entry) error {
entryStr, err := entry.String()
if err != nil {
return fmt.Errorf("entry.String() err:%s", err.Error())
}
l.AddLog(entry.Time, entryStr, entry.Data)
if l.fireCallback != nil {
return l.fireCallback(entry)
}
return nil
}
func (l *LogsBuffer) SetFireCallback(f func(entry *logrus.Entry) error) {
l.fireCallback = f
}
func (l *LogsBuffer) Levels() []logrus.Level {
return logrus.AllLevels
}
func (l *LogsBuffer) Write(p []byte) (n int, err error) {
l.AddLog(time.Now(), string(p), nil)
return len(p), nil
}
func (l *LogsBuffer) GetLogs(logItemConvertFunc func(*LogItem) any, fTimestamp int64) []any {
l.mu.RLock()
defer l.mu.RUnlock()
var logs []any
for i := range l.logsStore {
if l.logsStore[i].Timestamp <= fTimestamp {
continue
}
lg := l.getLogItem(logItemConvertFunc, l.logsStore[i])
logs = append(logs, lg)
}
return logs
}
func (l *LogsBuffer) GetLastLogs(logItemConvertFunc func(*LogItem) any, maxCount int) []any {
l.mu.RLock()
defer l.mu.RUnlock()
logCount := len(l.logsStore)
var resRaw []LogItem
if maxCount >= logCount {
resRaw = l.logsStore[0:]
} else {
resRaw = l.logsStore[logCount-maxCount:]
}
var logs []any
for i := range resRaw {
lg := l.getLogItem(logItemConvertFunc, resRaw[i])
logs = append(logs, lg)
}
return logs
}
func (l *LogsBuffer) GetLogsByLimit(logItemConvertFunc func(*LogItem) any, pageSize, page int) (int, []any) {
l.mu.RLock()
defer l.mu.RUnlock()
logCount := len(l.logsStore)
var resRaw []LogItem
firstIndex := (page - 1) * pageSize
endIndex := firstIndex + pageSize
if firstIndex < logCount {
if endIndex >= logCount {
resRaw = l.logsStore[firstIndex:]
} else {
resRaw = l.logsStore[firstIndex:endIndex]
}
}
var logs []any
for i := range resRaw {
lg := l.getLogItem(logItemConvertFunc, resRaw[i])
logs = append(logs, lg)
}
return logCount, logs
}
func (l *LogsBuffer) getLogItem(logItemConvertFunc func(*LogItem) any, li LogItem) any {
var lg any
if logItemConvertFunc == nil {
lg = li
} else {
lg = logItemConvertFunc(&li)
}
return lg
}
func (l *LogsBuffer) ClearLog() {
l.logsStore = l.logsStore[:0]
}
func Create(size int) *LogsBuffer {
lb := &LogsBuffer{bufferSize: size}
return lb
}
func (l *LogsBuffer) SetBufferSize(size int) {
l.mu.Lock()
defer l.mu.Unlock()
l.bufferSize = size
if len(l.logsStore) > l.bufferSize {
l.logsStore = l.logsStore[len(l.logsStore)-l.bufferSize:]
}
}
func (l *LogsBuffer) GetBufferSize() int {
l.mu.RLock()
defer l.mu.RUnlock()
return l.bufferSize
}
func (l *LogsBuffer) GetLogCount() int {
l.mu.Lock()
defer l.mu.Unlock()
return len(l.logsStore)
}

View File

@ -1,4 +1,4 @@
package ddnscore
package netinterfaces
import (
"log"
@ -119,7 +119,7 @@ func GetIPFromNetInterface(ipType, netinterface, ipreg string) string {
for i := range addressList {
matched, err := regexp.MatchString(ipreg, addressList[i])
if matched && err == nil {
log.Printf("正则匹配上")
//log.Printf("正则匹配上")
return addressList[i]
}
}

View File

@ -0,0 +1,12 @@
package slice
func DeleteAnyListlice(a []any, deleteIndex int) []any {
j := 0
for i := range a {
if i != deleteIndex {
a[j] = a[i]
j++
}
}
return a[:j]
}

View File

@ -0,0 +1,28 @@
package stringsp
import "fmt"
const (
KB = uint(1024)
MB = uint64(1024 * 1024)
GB = uint64(1024 * 1024 * 1024)
TB = uint64(1024 * 1024 * 1024 * 1024)
EB = uint64(1024 * 1024 * 1024 * 1024 * 1024)
)
func BinaryUnitToStr(binaryUnit uint64) (size string) {
switch {
case binaryUnit < 1024:
return fmt.Sprintf("%.2fB", float64(binaryUnit)/float64(1))
case binaryUnit < MB: //1024*1024
return fmt.Sprintf("%.2fKB", float64(binaryUnit)/float64(KB))
case binaryUnit < GB: //1024 * 1024 * 1024
return fmt.Sprintf("%.2fMB", float64(binaryUnit)/float64(MB))
case binaryUnit < TB: //1024 * 1024 * 1024 * 1024
return fmt.Sprintf("%.2fGB", float64(binaryUnit)/float64(GB))
case binaryUnit < EB: //1024 * 1024 * 1024 * 1024 * 1024
return fmt.Sprintf("%.2fTB", float64(binaryUnit)/float64(TB))
default:
return fmt.Sprintf("%.2fEB", float64(binaryUnit)/float64(EB))
}
}

View File

@ -0,0 +1,17 @@
package stringsp
import (
"net/url"
"strings"
)
func GetHostAndPathFromURL(urlstr string) (string, string, string, string, error) {
if !strings.HasPrefix(urlstr, "http") {
urlstr = "http://" + urlstr
}
u, err := url.Parse(urlstr)
if err != nil {
return "", "", "", "", err
}
return u.Scheme, u.Hostname(), u.Port(), u.Path, nil
}

4
web.go
View File

@ -9,8 +9,8 @@ import (
"log"
)
func RunAdminWeb(listenPort int) {
func RunAdminWeb(listenPort, logMaxSize int) {
listen := fmt.Sprintf(":%d", listenPort)
go web.RunAdminWeb(listen)
go web.RunAdminWeb(listen, logMaxSize)
log.Printf("AdminWeb listen on %s", listen)
}

View File

@ -13,6 +13,8 @@ declare module '@vue/runtime-core' {
ElButton: typeof import('element-plus/es')['ElButton']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDescriptions: typeof import('element-plus/es')['ElDescriptions']
ElDescriptionsItem: typeof import('element-plus/es')['ElDescriptionsItem']
@ -29,6 +31,7 @@ declare module '@vue/runtime-core' {
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElOption: typeof import('element-plus/es')['ElOption']
ElPagination: typeof import('element-plus/es')['ElPagination']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
@ -45,6 +48,7 @@ declare module '@vue/runtime-core' {
Pmenu: typeof import('./src/components/Pmenu.vue')['default']
PSet: typeof import('./src/components/PSet.vue')['default']
RelaySet: typeof import('./src/components/RelaySet.vue')['default']
ReverseProxy: typeof import('./src/components/ReverseProxy.vue')['default']
Status: typeof import('./src/components/Status.vue')['default']
WhiteLists: typeof import('./src/components/WhiteLists.vue')['default']
WhiteListSet: typeof import('./src/components/WhiteListSet.vue')['default']

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

View File

@ -7,9 +7,9 @@
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0" />
<title>Lucky(大吉)</title>
<script type="module" crossorigin src="/assets/index.baa1812e.js"></script>
<link rel="stylesheet" href="/assets/index.ddd95d4c.css">
<title>Lucky</title>
<script type="module" crossorigin src="/assets/index.2c06576c.js"></script>
<link rel="stylesheet" href="/assets/index.6859b28a.css">
</head>
<body style="margin:0">
<div id="app"></div>

View File

@ -7,7 +7,7 @@
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0" />
<title>Lucky(大吉)</title>
<title>Lucky</title>
</head>
<body style="margin:0">
<div id="app"></div>

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@
<BlackLists v-if="global.currentPage.value=='#blacklists'?true:false"></BlackLists>
<DDNS v-if="global.currentPage.value=='#ddnstasklist'?true:false"></DDNS>
<DDNSSet v-if="global.currentPage.value=='#ddnsset'?true:false"></DDNSSet>
<ReverseProxy v-if="global.currentPage.value=='#reverseproxylist'?true:false"></ReverseProxy>
<About v-if="global.currentPage.value=='#about'?true:false"></About>
</el-main>
@ -49,6 +50,7 @@ import WhiteListSet from './components/WhiteListSet.vue';
import WhiteLists from './components/WhiteLists.vue';
import BlackLists from './components/BlackLists.vue';
import DDNS from './components/DDNS.vue';
import ReverseProxy from './components/reverseproxy.vue';
import {apiGetVersion} from "./apis/utils.js"
import DDNSSet from './components/DDNSSet.vue';

View File

@ -337,4 +337,70 @@ export function apiGetConfigure() {
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf()}
})
}
}
export function apiAddReverseProxyRule(data) {
return httpRequest({
url: '/api/reverseproxyrule',
method: 'post',
headers:{'Authorization':GetToken()},
data:data
})
}
export function apiGeReverseProxyRuleList() {
return httpRequest({
url: '/api/reverseproxyrules',
method: 'get',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf()}
})
}
export function apiAlterReverseProxyRule(data) {
return httpRequest({
url: '/api/reverseproxyrule',
method: 'put',
headers:{'Authorization':GetToken()},
data:data,
params:{_:new Date().valueOf()}
})
}
export function apiDeleteReverseProxyRule(ruleKey) {
return httpRequest({
url: '/api/reverseproxyrule',
method: 'delete',
headers:{'Authorization':GetToken()},
params:{_:new Date().valueOf(),key:ruleKey}
})
}
export function apiReverseProxyRuleEnable(ruleKey,proxyKey,enable) {
return httpRequest({
url: '/api/reverseproxyrule/enable',
method: 'get',
headers:{'Authorization':GetToken()},
params:{
_:new Date().valueOf(),
enable:enable,
ruleKey:ruleKey,
proxyKey:proxyKey}
})
}
export function apiReverseProxyRuleLogs(ruleKey,proxyKey,pageSize,page) {
return httpRequest({
url: '/api/reverseproxyrule/logs',
method: 'get',
headers:{'Authorization':GetToken()},
params:{
_:new Date().valueOf(),
ruleKey:ruleKey,
proxyKey:proxyKey,
pageSize:pageSize,
page:page}
})
}

View File

@ -4,18 +4,13 @@
borderRadius: 'base',
}">
<!-- <el-affix position="bottom" :offset="0" class="affix-container">
<el-button type="primary" @click="showAddOrAlterWhiteListDialog('add')">DDNS任务添加 <el-icon>
<Plus />
</el-icon>
</el-button>
</el-affix> -->
<el-scrollbar height="100%">
<div class="itemradius" :style="{
borderRadius: 'base',
}" v-for="task,taskIndex in taskList">
}" v-for="task, taskIndex in taskList">
<el-descriptions :column="4" border>
@ -39,7 +34,7 @@
</el-tooltip>
&nbsp;&nbsp;
<el-button type="primary" @click="showAddOrAlterWhiteListDialog('alter', task)">编辑</el-button>
<el-button type="primary" @click="showAddOrAlterDDNSTaskDialog('alter', task)">编辑</el-button>
<el-button type="danger" @click="deleteTask(task)">删除</el-button>
</el-descriptions-item>
@ -69,7 +64,7 @@
<el-descriptions-item label="公网IP">
<el-tooltip :placement="taskIndex==0?'bottom':'top'" effect="dark" :trigger-keys="[]"
<el-tooltip :placement="taskIndex == 0 ? 'bottom' : 'top'" effect="dark" :trigger-keys="[]"
content="">
<template #content>
<span v-html="getIPHistroyListHtml(task.TaskState.IPAddrHistory)"></span>
@ -108,7 +103,7 @@
<el-descriptions-item label="WebHook 触发时间" :span="task.TaskState.WebhookCallTime == '' ? 3 : 1">
<el-tooltip :placement="taskIndex==0?'bottom':'top'" effect="dark" :trigger-keys="[]"
<el-tooltip :placement="taskIndex == 0 ? 'bottom' : 'top'" effect="dark" :trigger-keys="[]"
content="">
<template #content>
<span
@ -116,7 +111,7 @@
</template>
<el-button color="#409eff" size="default">
{{ task.TaskState.WebhookCallTime == "" ? '从未触发' :
task.TaskState.WebhookCallTime
task.TaskState.WebhookCallTime
}}
</el-button>
</el-tooltip>
@ -126,7 +121,7 @@
<el-descriptions-item label="WebHook 触发结果"
v-if="task.TaskState.WebhookCallTime == '' ? false : true"
:span="task.TaskState.WebhookCallErrorMsg == '' ? 2 : 1">
<el-tooltip :placement="taskIndex==0?'bottom':'top'" effect="dark" :trigger-keys="[]"
<el-tooltip :placement="taskIndex == 0 ? 'bottom' : 'top'" effect="dark" :trigger-keys="[]"
content="">
<template #content>
<span
@ -177,28 +172,28 @@
<el-descriptions-item label="域名">
<el-button color="#409eff" size="default"
@click="copyDomain(domain.SubDomain, domain.DomainName)">
{{ domain.SubDomain ==''?domain.DomainName:domain.SubDomain + "." + domain.DomainName }}
{{ domain.SubDomain == '' ? domain.DomainName : domain.SubDomain + "." + domain.DomainName }}
</el-button>
</el-descriptions-item>
<el-descriptions-item label="同步结果">
<el-tooltip :placement="taskIndex==0?'bottom':'top'" effect="dark" :trigger-keys="[]"
<el-tooltip :placement="taskIndex == 0 ? 'bottom' : 'top'" effect="dark" :trigger-keys="[]"
content="">
<template #content>
<span v-html="GetSyncUpdateHistroyListHtml(domain.UpdateHistroy)"></span>
</template>
<el-button :type="domain.UpdateStatus == '失败' ? 'danger' : task.Enable?'success':'info'"
<el-button :type="domain.UpdateStatus == '失败' ? 'danger' : task.Enable ? 'success' : 'info'"
size="small">
{{ task.Enable?domain.UpdateStatus:'停止同步' }}
{{ task.Enable ? domain.UpdateStatus : '停止同步' }}
</el-button>
</el-tooltip>
</el-descriptions-item>
<el-descriptions-item label="最后检测时间" :span="domain.Message == '' ? 2 : 1">
<el-tooltip :placement="taskIndex==0?'bottom':'top'" effect="dark" :trigger-keys="[]"
<el-tooltip :placement="taskIndex == 0 ? 'bottom' : 'top'" effect="dark" :trigger-keys="[]"
content="">
<template #content>
<span v-html="GetSyncUpdateHistroyListHtml(domain.UpdateHistroy)"></span>
@ -226,7 +221,7 @@
</el-scrollbar>
<el-affix position="bottom" :offset="30" class="affix-container">
<el-button type="primary" @click="showAddOrAlterWhiteListDialog('add', null)">添加DDNS任务 <el-icon>
<el-button type="primary" @click="showAddOrAlterDDNSTaskDialog('add', null)">添加DDNS任务 <el-icon>
<Plus />
</el-icon>
</el-button>
@ -259,6 +254,7 @@
</el-form-item>
<div v-show="DDNSForm.Enable">
@ -266,7 +262,6 @@
<div class="fromitemDivRadius">
<p>DNS服务商设置</p>
<div class="fromitemChildDivRadius">
<el-form-item label="DNS服务商" label-width="auto">
@ -404,7 +399,7 @@
<div v-show="DDNSForm.DNS.Callback.Server == 'other' ? false : true">
<div v-if="DDNSForm.DNS.Callback.Server=='meibu'" style="font-size: small;">
<div v-if="DDNSForm.DNS.Callback.Server == 'meibu'" style="font-size: small;">
<el-tooltip content="注意:每步 IPv4和IPv6的接口不相同,免费二级域名不能同时支持IPv4和IPv6" placement="top">
<el-form-item label-width="auto">
<el-link type="primary" style="font-size: small;"
@ -416,7 +411,7 @@
</div>
<div v-if="DDNSForm.DNS.Callback.Server=='noip'" style="font-size: small;">
<div v-if="DDNSForm.DNS.Callback.Server == 'noip'" style="font-size: small;">
<el-form-item label-width="auto">
<el-link type="primary" style="font-size: small;" href="https://www.noip.com"
target="_blank">
@ -425,7 +420,7 @@
</el-form-item>
</div>
<div v-if="DDNSForm.DNS.Callback.Server=='dynv6'" style="font-size: small;">
<div v-if="DDNSForm.DNS.Callback.Server == 'dynv6'" style="font-size: small;">
<el-form-item label-width="auto">
<el-link type="primary" style="font-size: small;" href="https://dynv6.com/"
target="_blank">
@ -439,7 +434,7 @@
</el-form-item>
</div>
<div v-if="DDNSForm.DNS.Callback.Server=='dynu'" style="font-size: small;">
<div v-if="DDNSForm.DNS.Callback.Server == 'dynu'" style="font-size: small;">
<el-form-item label-width="auto">
<el-link type="primary" style="font-size: small;" href="https://www.dynu.com/"
target="_blank">
@ -924,22 +919,22 @@
</el-tooltip>
<div v-show="!DDNSForm.WebhookDisableCallbackSuccessContentCheck">
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>用于判断记录Webhook 接口是否成功调用<br />
多种表示成功的不同字符串请分多行写<br />
支持的变量 <br />
#{ipAddr} : 当前公网IP<br />
#{successDomains} : 更新/添加成功的域名列表,域名之间用,号分隔<br />
#{successDomainsLine} : 更新/添加成功的域名列表,域名之间用'\n'分隔<br />
#{failedDomains} : 更新/添加失败的域名列表,域名之间用,号分隔<br />
#{failedDomainsLine} : 更新/添加失败的域名列表,域名之间用'\n'分隔</template>
<el-form-item label="接口调用成功包含的字符串" label-width="auto">
<el-input v-model="DDNSFormWebhookSuccessContentArea"
:autosize="{ minRows: 3, maxRows: 5 }" type="textarea" autocomplete="off"
placeholder="" />
</el-form-item>
</el-tooltip>
<div v-show="!DDNSForm.WebhookDisableCallbackSuccessContentCheck">
<el-tooltip class="box-item" effect="dark" :trigger-keys="[]" content="">
<template #content>用于判断记录Webhook 接口是否成功调用<br />
多种表示成功的不同字符串请分多行写<br />
支持的变量 <br />
#{ipAddr} : 当前公网IP<br />
#{successDomains} : 更新/添加成功的域名列表,域名之间用,号分隔<br />
#{successDomainsLine} : 更新/添加成功的域名列表,域名之间用'\n'分隔<br />
#{failedDomains} : 更新/添加失败的域名列表,域名之间用,号分隔<br />
#{failedDomainsLine} : 更新/添加失败的域名列表,域名之间用'\n'分隔</template>
<el-form-item label="接口调用成功包含的字符串" label-width="auto">
<el-input v-model="DDNSFormWebhookSuccessContentArea"
:autosize="{ minRows: 3, maxRows: 5 }" type="textarea"
autocomplete="off" placeholder="" />
</el-form-item>
</el-tooltip>
</div>
@ -996,8 +991,8 @@
<span class="dialog-footer">
<el-button @click="addDDNSDialogVisible = false">取消</el-button>
<el-button type="primary" @click="exeAddOrAlterDDNSOption">{{ DDNSFormOptionType == "add" ? '添加' :
'修改'
}}
'修改'
}}
</el-button>
</span>
</template>
@ -1042,12 +1037,12 @@ var taskList = ref([{
NetInterface: "",
IPReg: "",
Domains: [""],
HttpClientTimeout:20,
HttpClientTimeout: 20,
DNS: {
Name: "alidns",
ID: "",
Secret: "",
ForceInterval:600,
ForceInterval: 600,
ResolverDoaminCheck: false,
DNSServerList: [''],
HttpClientProxyType: "",
@ -1060,7 +1055,7 @@ var taskList = ref([{
Headers: [""],
RequestBody: "",
Server: "",
DisableCallbackSuccessContentCheck:false,
DisableCallbackSuccessContentCheck: false,
CallbackSuccessContent: [""]
},
@ -1070,7 +1065,7 @@ var taskList = ref([{
WebhookMethod: "",
WebhookHeaders: [""],
WebhookRequestBody: "",
WebhookDisableCallbackSuccessContentCheck:false,
WebhookDisableCallbackSuccessContentCheck: false,
WebhookSuccessContent: [""],
WebhookProxy: "",
WebhookProxyAddr: "",
@ -1082,23 +1077,24 @@ var taskList = ref([{
WebhookCallTime: "",
WebhookCallResult: false,
WebhookCallErrorMsg: "",
IPAddrHistory:[{IPaddr:"",RecordTime:""}],
WebhookCallHistroy:[{CallTime:"",CallResult:""}],
IPAddrHistory: [{ IPaddr: "", RecordTime: "" }],
WebhookCallHistroy: [{ CallTime: "", CallResult: "" }],
IpAddr: "",
Domains: [{
Domains: [{
DomainName: "",
SubDomain: "",
UpdateStatus: "",
LastUpdateStatusTime: "",
Message: "",
UpdateHistroy:[{UpdateStatus:"",UpdateTime:""}] }]
SubDomain: "",
UpdateStatus: "",
LastUpdateStatusTime: "",
Message: "",
UpdateHistroy: [{ UpdateStatus: "", UpdateTime: "" }]
}]
},
}]);
taskList.value.splice(0, 1)
const rhtml = ref("")
const webhookSelect= ref("")
const webhookSelect = ref("")
const WebhookServerListArea = ref("")
rhtml.value = ` <el-link type="info">info</el-link>`
@ -1266,8 +1262,8 @@ const WebhookServerList = [
label: '企业微信',
},
{
value:'custom',
label:'自定义',
value: 'custom',
label: '自定义',
},
]
@ -1320,91 +1316,91 @@ const CallbackMethodList = [
]
const getIPHistroyListHtml = (ipHistroy)=>{
const getIPHistroyListHtml = (ipHistroy) => {
let res = ""
for (let i in ipHistroy){
let ipText = ipHistroy[i].IPaddr;
if (ipText==""){
for (let i in ipHistroy) {
let ipText = ipHistroy[i].IPaddr;
if (ipText == "") {
ipText = "获取IP失败"
}
res += ipHistroy[i].RecordTime +"&nbsp; &nbsp; &nbsp;"+ ipText + '<br />'
res += ipHistroy[i].RecordTime + "&nbsp; &nbsp; &nbsp;" + ipText + '<br />'
}
return res
}
const GetSyncUpdateHistroyListHtml = (updateHistroy)=>{
let res = ""
const GetSyncUpdateHistroyListHtml = (updateHistroy) => {
let res = ""
for (let i in updateHistroy){
let state = updateHistroy[i].UpdateStatus;
for (let i in updateHistroy) {
let state = updateHistroy[i].UpdateStatus;
res += updateHistroy[i].UpdateTime +"&nbsp; &nbsp; &nbsp;"+ state + '<br />'
res += updateHistroy[i].UpdateTime + "&nbsp; &nbsp; &nbsp;" + state + '<br />'
}
return res
}
const getWebhookCallHistroyListHtml = (histroy)=>{
const getWebhookCallHistroyListHtml = (histroy) => {
let res = "仅记录程序本次启动以来的Webhook调用记录<br />"
for (let i in histroy){
let result = histroy[i].CallResult;
res += histroy[i].CallTime +"&nbsp; &nbsp; &nbsp;"+ result + '<br />'
for (let i in histroy) {
let result = histroy[i].CallResult;
res += histroy[i].CallTime + "&nbsp; &nbsp; &nbsp;" + result + '<br />'
}
return res
}
const WebhookServerSelectChange = (server : string)=>{
switch (server){
const WebhookServerSelectChange = (server: string) => {
switch (server) {
case "dingding":
let dingding_msg = {
msgtype:"markdown",
markdown:{
title:"DDNS域名同步反馈",
text:'#### DDNS域名同步反馈 \n - IP地址#{ipAddr} \n - 域名更新成功列表:#{successDomainsLine}\n - 域名更新失败列表:#{failedDomainsLine}\n - Webhook触发时间: \n #{time}'
msgtype: "markdown",
markdown: {
title: "DDNS域名同步反馈",
text: '#### DDNS域名同步反馈 \n - IP地址#{ipAddr} \n - 域名更新成功列表:#{successDomainsLine}\n - 域名更新失败列表:#{failedDomainsLine}\n - Webhook触发时间: \n #{time}'
},
}
WebhookServerListArea.value = JSON.stringify(dingding_msg,null,2);
}
WebhookServerListArea.value = JSON.stringify(dingding_msg, null, 2);
break;
case 'feishu':
let feishu_msg = {
msg_type:"post",
content:{
post:{
zh_cn:{
title:"DDNS域名同步反馈",
content:[
[{tag:"text",text:"IP地址#{ipAddr}"}],
[{tag:"text",text:"域名更新成功列表:#{successDomainsLine}"}],
[{tag:"text",text:"域名更新失败列表:#{failedDomainsLine}"}],
[{tag:"text",text:"Webhook触发时间: \n#{time}"}],
]
msg_type: "post",
content: {
post: {
zh_cn: {
title: "DDNS域名同步反馈",
content: [
[{ tag: "text", text: "IP地址#{ipAddr}" }],
[{ tag: "text", text: "域名更新成功列表:#{su.ccessDomainsLine}" }],
[{ tag: "text", text: "域名更新失败列表:#{failedDomainsLine}" }],
[{ tag: "text", text: "Webhook触发时间: \n#{time}" }],
]
}
}
}
}
WebhookServerListArea.value= JSON.stringify(feishu_msg,null,2)
WebhookServerListArea.value = JSON.stringify(feishu_msg, null, 2)
break
case 'weixinpro':
let weixin_msg = {
msgtype:"markdown",
markdown:{
content:'#### DDNS域名同步反馈 \n##### IP地址\n#{ipAddr} \n##### 域名更新成功列表:\n#{successDomainsLine}\n##### 域名更新失败列表:\n#{failedDomainsLine}\n##### Webhook触发时间: \n#{time}'
msgtype: "markdown",
markdown: {
content: '#### DDNS域名同步反馈 \n##### IP地址\n#{ipAddr} \n##### 域名更新成功列表:\n#{successDomainsLine}\n##### 域名更新失败列表:\n#{failedDomainsLine}\n##### Webhook触发时间: \n#{time}'
}
}
WebhookServerListArea.value= JSON.stringify(weixin_msg,null,2)
WebhookServerListArea.value = JSON.stringify(weixin_msg, null, 2)
break
default:
}
}
const copyDomain = ( SubDomain: string,domain: string) => {
const copyDomain = (SubDomain: string, domain: string) => {
let content = SubDomain ==''?domain:SubDomain + "." + domain;
let content = SubDomain == '' ? domain : SubDomain + "." + domain;
CopyTotoClipboard(content)
MessageShow('success', '域名 ' + content + ' 已复制到剪切板')
@ -1581,12 +1577,12 @@ const DDNSForm = ref(
NetInterface: "",
IPReg: "",
Domains: [""],
HttpClientTimeout:60,
HttpClientTimeout: 60,
DNS: {
Name: "alidns",
ID: "",
Secret: "",
ForceInterval:3600,
ForceInterval: 3600,
ResolverDoaminCheck: false,
DNSServerList: [""],
HttpClientProxyType: "",
@ -1599,17 +1595,17 @@ const DDNSForm = ref(
Headers: [""],
RequestBody: "",
Server: "",
DisableCallbackSuccessContentCheck:false,
DisableCallbackSuccessContentCheck: false,
CallbackSuccessContent: [""],
},
},
WebhookEnable: false,
WebhookCallOnGetIPfail:false,
WebhookCallOnGetIPfail: false,
WebhookURL: "",
WebhookMethod: "",
WebhookHeaders: [""],
WebhookRequestBody: "",
WebhookDisableCallbackSuccessContentCheck:false,
WebhookDisableCallbackSuccessContentCheck: false,
WebhookSuccessContent: [""],
WebhookProxy: "",
WebhookProxyAddr: "",
@ -1629,12 +1625,12 @@ const preDDNSFrom = ref(
NetInterface: "",
IPReg: "",
Domains: [""],
HttpClientTimeout:20,
HttpClientTimeout: 20,
DNS: {
Name: "alidns",
ID: "",
Secret: "",
ForceInterval:3600,
ForceInterval: 3600,
ResolverDoaminCheck: false,
DNSServerList: [''],
HttpClientProxyType: "",
@ -1647,17 +1643,17 @@ const preDDNSFrom = ref(
Headers: [""],
RequestBody: "",
Server: "",
DisableCallbackSuccessContentCheck:false,
DisableCallbackSuccessContentCheck: false,
CallbackSuccessContent: [""],
},
},
WebhookEnable: false,
WebhookCallOnGetIPfail:false,
WebhookCallOnGetIPfail: false,
WebhookURL: "",
WebhookMethod: "",
WebhookHeaders: [""],
WebhookRequestBody: "",
WebhookDisableCallbackSuccessContentCheck:false,
WebhookDisableCallbackSuccessContentCheck: false,
WebhookSuccessContent: [""],
WebhookProxy: "",
WebhookProxyAddr: "",
@ -1678,10 +1674,10 @@ const DDNSFormOptionType = ref("")
const checkIPv4URLList = ["https://4.ipw.cn", "http://v4.ip.zxinc.org/getip", "https://myip4.ipip.net", "https://www.taobao.com/help/getip.php", "https://ddns.oray.com/checkip", "https://ip.3322.net", "https://v4.myip.la"]
const checkIPv6URLList = ["https://6.ipw.cn", "https://ipv6.ddnspod.com", "http://v6.ip.zxinc.org/getip", "https://speed.neu6.edu.cn/getIP.php", "https://v6.ident.me", "https://v6.myip.la"]
const showAddOrAlterWhiteListDialog = (optionType: string, task: any) => {
const showAddOrAlterDDNSTaskDialog = (optionType: string, task: any) => {
//console.log("optionType fuck:" + optionType)
webhookSelect.value=""
webhookSelect.value = ""
queryNetinterfaces()
DDNSFormOptionType.value = optionType
@ -1697,35 +1693,35 @@ const showAddOrAlterWhiteListDialog = (optionType: string, task: any) => {
DDNSForm.value.IPReg = ""
DDNSForm.value.Domains = [""]
DDNSForm.value.HttpClientTimeout = 20,
DDNSForm.value.DNS = {
Name: "alidns",
ID: "",
Secret: "",
ForceInterval:3600,
ResolverDoaminCheck: true,
DNSServerList: [],
HttpClientProxyType: "",
HttpClientProxyAddr: "",
HttpClientProxyUser: "",
HttpClientProxyPassword: "",
Callback: {
URL: "",
Method: "get",
Headers: [""],
RequestBody: "",
Server: "other",
DisableCallbackSuccessContentCheck:false,
CallbackSuccessContent: [],
DDNSForm.value.DNS = {
Name: "alidns",
ID: "",
Secret: "",
ForceInterval: 3600,
ResolverDoaminCheck: true,
DNSServerList: [],
HttpClientProxyType: "",
HttpClientProxyAddr: "",
HttpClientProxyUser: "",
HttpClientProxyPassword: "",
Callback: {
URL: "",
Method: "get",
Headers: [""],
RequestBody: "",
Server: "other",
DisableCallbackSuccessContentCheck: false,
CallbackSuccessContent: [],
}
}
}
DDNSForm.value.WebhookEnable = false
DDNSForm.value.WebhookCallOnGetIPfail = false
DDNSForm.value.WebhookURL = ""
DDNSForm.value.WebhookMethod = "get"
DDNSForm.value.WebhookHeaders = []
DDNSForm.value.WebhookRequestBody = ""
DDNSForm.value.WebhookDisableCallbackSuccessContentCheck=false,
DDNSForm.value.WebhookSuccessContent = []
DDNSForm.value.WebhookDisableCallbackSuccessContentCheck = false,
DDNSForm.value.WebhookSuccessContent = []
DDNSForm.value.WebhookProxy = ""
DDNSForm.value.WebhookProxyAddr = ""
DDNSForm.value.WebhookProxyUser = ""
@ -1751,35 +1747,35 @@ const showAddOrAlterWhiteListDialog = (optionType: string, task: any) => {
preDDNSFrom.value.IPReg = ""
preDDNSFrom.value.Domains = [""]
preDDNSFrom.value.HttpClientTimeout = 20,
preDDNSFrom.value.DNS = {
Name: "alidns",
ID: "",
Secret: "",
ForceInterval:3600,
ResolverDoaminCheck: true,
DNSServerList: [],
HttpClientProxyType: "",
HttpClientProxyAddr: "",
HttpClientProxyUser: "",
HttpClientProxyPassword: "",
Callback: {
URL: "",
Method: "get",
Headers: [""],
RequestBody: "",
DisableCallbackSuccessContentCheck:false,
CallbackSuccessContent: [],
Server: "other",
preDDNSFrom.value.DNS = {
Name: "alidns",
ID: "",
Secret: "",
ForceInterval: 3600,
ResolverDoaminCheck: true,
DNSServerList: [],
HttpClientProxyType: "",
HttpClientProxyAddr: "",
HttpClientProxyUser: "",
HttpClientProxyPassword: "",
Callback: {
URL: "",
Method: "get",
Headers: [""],
RequestBody: "",
DisableCallbackSuccessContentCheck: false,
CallbackSuccessContent: [],
Server: "other",
}
}
}
preDDNSFrom.value.WebhookEnable = false
preDDNSFrom.value.WebhookCallOnGetIPfail = false
preDDNSFrom.value.WebhookURL = ""
preDDNSFrom.value.WebhookMethod = "get"
preDDNSFrom.value.WebhookHeaders = []
preDDNSFrom.value.WebhookRequestBody = ""
preDDNSFrom.value.WebhookDisableCallbackSuccessContentCheck=false,
preDDNSFrom.value.WebhookSuccessContent = []
preDDNSFrom.value.WebhookDisableCallbackSuccessContentCheck = false,
preDDNSFrom.value.WebhookSuccessContent = []
preDDNSFrom.value.WebhookProxy = ""
preDDNSFrom.value.WebhookProxyAddr = ""
preDDNSFrom.value.WebhookProxyUser = ""
@ -1800,7 +1796,7 @@ const showAddOrAlterWhiteListDialog = (optionType: string, task: any) => {
Name: task.DNS.Name,
ID: task.DNS.ID,
Secret: task.DNS.Secret,
ForceInterval:task.DNS.ForceInterval,
ForceInterval: task.DNS.ForceInterval,
ResolverDoaminCheck: task.DNS.ResolverDoaminCheck,
DNSServerList: task.DNS.DNSServerList,
HttpClientProxyType: task.DNS.HttpClientProxyType,
@ -1813,7 +1809,7 @@ const showAddOrAlterWhiteListDialog = (optionType: string, task: any) => {
Headers: task.DNS.Callback.Headers,
RequestBody: task.DNS.Callback.RequestBody,
Server: task.DNS.Callback.Server,
DisableCallbackSuccessContentCheck:task.DNS.Callback.DisableCallbackSuccessContentCheck,
DisableCallbackSuccessContentCheck: task.DNS.Callback.DisableCallbackSuccessContentCheck,
CallbackSuccessContent: task.DNS.Callback.CallbackSuccessContent
}
}
@ -1852,7 +1848,7 @@ const showAddOrAlterWhiteListDialog = (optionType: string, task: any) => {
Name: task.DNS.Name,
ID: task.DNS.ID,
Secret: task.DNS.Secret,
ForceInterval:task.DNS.ForceInterval,
ForceInterval: task.DNS.ForceInterval,
ResolverDoaminCheck: task.DNS.ResolverDoaminCheck,
DNSServerList: task.DNS.DNSServerList,
HttpClientProxyType: task.DNS.HttpClientProxyType,
@ -1865,7 +1861,7 @@ const showAddOrAlterWhiteListDialog = (optionType: string, task: any) => {
Headers: task.DNS.Callback.Headers,
RequestBody: task.DNS.Callback.RequestBody,
CallbackSuccessContent: task.DNS.Callback.CallbackSuccessContent,
DisableCallbackSuccessContentCheck:task.DNS.Callback.DisableCallbackSuccessContentCheck,
DisableCallbackSuccessContentCheck: task.DNS.Callback.DisableCallbackSuccessContentCheck,
Server: task.DNS.Callback.Server,
}
}
@ -2178,11 +2174,12 @@ const checkDDNSFormAvalid = () => {
} else {
data.DNS.Callback = {
URL: "",
Method: "",
Headers: [],
RequestBody: "", Server: "", CallbackSuccessContent: [],DisableCallbackSuccessContentCheck:false }
data.DNS.Callback = {
URL: "",
Method: "",
Headers: [],
RequestBody: "", Server: "", CallbackSuccessContent: [], DisableCallbackSuccessContentCheck: false
}
}
if (data.WebhookEnable) {
@ -2213,7 +2210,7 @@ const checkDNSData = (dns: any) => {
return "Callback 接口地址不能为空"
}
if (!dns.Callback.DisableCallbackSuccessContentCheck && dns.Callback.CallbackSuccessContent.length==0){
if (!dns.Callback.DisableCallbackSuccessContentCheck && dns.Callback.CallbackSuccessContent.length == 0) {
return "接口调用成功包含的字符串不能为空,如果要指定为空请禁用检测"
}

View File

@ -44,11 +44,15 @@ function queryLastlogs() {
}
if (res.logs!=null && res.logs.length > 0) {
if (res.logs[0].timestamp<=preLogTimestamp){
return
}
// if (res.logs[0].timestamp!=preLogTimestamp){
// console.log("fuckkkk")
// return
// }
preLogTimestamp = res.logs[res.logs.length - 1].timestamp
console.log("fff "+res.logs[res.logs.length - 1].log)
console.log("追加日志 "+preLogTimestamp)
for(var i=0;i<res.logs.length;i++){
weblogsContent.value += res.logs[i].log +"\n"
}
@ -94,7 +98,8 @@ onUnmounted(()=>{
border: 10px;
overflow-y: auto;
overflow-x: auto;
white-space: pre;
white-space: pre-wrap;
word-wrap:break-word;
}

View File

@ -31,6 +31,10 @@
<el-input v-model="form.AdminPassword" placeholder="管理登录密码" autocomplete="off" />
</el-form-item>
<el-form-item label="日志记录最大条数" id="logMaxSize">
<el-input-number v-model="form.LogMaxSize" autocomplete="off" :min="1024" :max="40960" />
</el-form-item>
<el-form-item label="全局最大端口代理数量" id="proxyCountLimit">
<el-input-number v-model="form.ProxyCountLimit" autocomplete="off" :min="1" :max="1024" />
@ -150,6 +154,7 @@ const rawData = {
ProxyCountLimit: 1,
GlobalMaxConnections: 1,
AllowInternetaccess: false,
LogMaxSize:1024,
}
const form = ref(rawData)
@ -286,8 +291,6 @@ onMounted(() => {
margin: 0 auto;
width: fit-content;
padding: 15px;
}
#adminListen {

View File

@ -24,6 +24,8 @@
<template #title>程序日志</template>
</el-menu-item>
<el-divider style="margin-top: 0px;margin-bottom: 0px;" />
<el-sub-menu index="#relay">
<template #title>
<el-icon>
@ -38,7 +40,57 @@
</el-icon>
<template #title>转发规则</template>
</el-menu-item>
</el-sub-menu>
<el-sub-menu index="#reverseproxy">
<template #title>
<el-icon>
<Position />
</el-icon>
<span>反向代理</span>
</template>
<el-menu-item index="#reverseproxylist">
<el-icon>
<List />
</el-icon>
<template #title>反向代理规则列表</template>
</el-menu-item>
</el-sub-menu>
<el-sub-menu index="#ddns">
<template #title>
<el-icon>
<Promotion />
</el-icon>
<span>动态域名</span>
</template>
<el-menu-item index="#ddnstasklist">
<el-icon>
<List />
</el-icon>
<template #title>动态域名任务列表</template>
</el-menu-item>
<el-menu-item index="#ddnsset">
<el-icon>
<Setting />
</el-icon>
<template #title>动态域名设置</template>
</el-menu-item>
</el-sub-menu>
<el-divider style="margin-top: 0px;margin-bottom: 0px;" />
<el-sub-menu index="#safe">
<template #title>
<el-icon>
<Guide />
</el-icon>
<span>IP过滤设置</span>
</template>
<el-menu-item index="#whitelistset">
<el-icon>
@ -62,40 +114,7 @@
</el-menu-item>
</el-sub-menu>
<el-sub-menu index="#ddns">
<template #title>
<el-icon>
<Promotion />
</el-icon>
<span>动态域名</span>
</template>
<el-menu-item index="#ddnstasklist">
<el-icon>
<List />
</el-icon>
<template #title>动态域名任务列表</template>
</el-menu-item>
<el-menu-item index="#ddnsset">
<el-icon>
<Setting />
</el-icon>
<template #title>动态域名设置</template>
</el-menu-item>
</el-sub-menu>
<el-menu-item index="#set">
<el-icon>
@ -105,7 +124,7 @@
</el-menu-item>
<el-divider style="margin-top: 0px;margin-bottom: 0px;" />
<el-menu-item index="#about">
<el-icon>
<Pointer />
@ -194,7 +213,7 @@ function handleSelect(key, keyPath, item, routeResult) {
break;
case "#logo":
//window.open("https://github.com/gdy666/lucky", "_blank");
location.hash ="#about"
location.hash = "#about"
break;
default:
SetHash(key)

File diff suppressed because it is too large Load Diff

View File

@ -127,10 +127,10 @@ const flushWhiteListlife = (index, ip, life) => {
const addWhiteList = () => {
if (!isIP(addWhiteListForm.value.IP)) {
MessageShow("error", "IP格式有误,请检查修正后再添加")
return
}
// if (!isIP(addWhiteListForm.value.IP)) {
// MessageShow("error", "IP,")
// return
// }
apiFlushWhiteList(addWhiteListForm.value.IP, addWhiteListForm.value.Life).then((res) => {
if (res.ret == 0) {

View File

@ -58,6 +58,7 @@ window.onpopstate = function (event){
}
if(!PageExist(location.hash)){
console.log("location.hash["+location.hash +"]no exist")
location.hash ="#login"
return
}

View File

@ -14,7 +14,7 @@ export function isIP(ip :string){
const MenuIndexList = ["#status",
"#log","#relayset","#whitelistset",
"#whitelists","#blacklists","#set",
"#login","#ddns","#ddnstasklist","#ddnsset","#about"]
"#login","#ddns","#ddnstasklist","#ddnsset","#about","#reverseproxylist"]
export function PageExist(page:string) {
for(let i in MenuIndexList){
@ -39,4 +39,47 @@ export function StringToArrayList(str : string){
resList.push(item)
}
return resList
}
}
export function StrArrayListToBrHtml( strList : string[]){
var resHtml = ""
for ( let i in strList){
resHtml += strList[i] + '<br />'
}
return resHtml
}
export function StrArrayListToArea(strList : string[]){
var res = ""
for ( let i in strList){
if(i!="0"){
res +='\n'
}
res += strList[i]
// res += strList[i] + '\n'
}
return res
}
export const LogLevelList = [
{
value: 2,
label: 'Error',
},
{
value: 3,
label: 'Warn',
},
{
value: 4,
label: 'Info',
},
{
value: 5,
label: 'Debug',
},
{
value: 6,
label: 'Trace',
},
]

126
web/blackwhitelist.go Normal file
View File

@ -0,0 +1,126 @@
package web
import (
"fmt"
"net/http"
"strconv"
"strings"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/thirdlib/gdylib/ginutils"
"github.com/gin-gonic/gin"
)
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})
}
func querywhitelist(c *gin.Context) {
resList := config.GetWhiteList()
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": resList})
}
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": fmt.Sprintf("刷新IP有效期出错:%s", err.Error())})
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 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 := ginutils.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": "IP已记录进白名单", "ip": c.ClientIP(), " effective_time": lifeTime})
}

108
web/configure.go Normal file
View File

@ -0,0 +1,108 @@
package web
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"sync"
"time"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/thirdlib/gdylib/fileutils"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
"github.com/gin-gonic/gin"
)
func configure(c *gin.Context) {
//c config.GetConfig()
configureBytes := config.GetConfigureBytes()
//c.Header("Content-Type", "application/json")
//c.Data(http.StatusOK, "application/json", configureBytes)
c.JSON(http.StatusOK,
gin.H{
"ret": 0,
"time": time.Now().Format("060102150405"),
"configure": string(configureBytes)},
)
}
var restoreConfigureVar *config.ProgramConfigure
var restoreConfigureKey string
var restoreConfigureMutex sync.Mutex
func restoreConfigureConfirm(c *gin.Context) {
restoreConfigureMutex.Lock()
defer restoreConfigureMutex.Unlock()
key := c.Query("key")
if key != restoreConfigureKey {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "key不一致"})
return
}
err := config.SetConfig(restoreConfigureVar)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "还原配置出错"})
return
}
rebootOnce.Do(func() {
go func() {
fileutils.OpenProgramOrFile(os.Args)
os.Exit(0)
}()
})
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": "还原配置成功", "port": restoreConfigureVar.BaseConfigure.AdminWebListenPort})
}
func restoreConfigure(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("c.FormFile err:%s", err.Error())})
return
}
src, err := file.Open()
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("file.Open err:%s", err.Error())})
return
}
defer src.Close()
fileBytes, err := io.ReadAll(src)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("ioutil.ReadAll err:%s", err.Error())})
return
}
//log.Printf("file:%s\n", string(fileBytes))
var conf config.ProgramConfigure
err = json.Unmarshal(fileBytes, &conf)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("配置文件[%s]有误", file.Filename)})
return
}
if conf.BaseConfigure.AdminAccount == "" ||
conf.BaseConfigure.AdminPassword == "" ||
conf.BaseConfigure.AdminWebListenPort <= 0 ||
conf.BaseConfigure.AdminWebListenPort >= 65536 ||
conf.BaseConfigure.GlobalMaxConnections <= 0 ||
conf.BaseConfigure.ProxyCountLimit <= 0 {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("配置文件[%s]参数有误", file.Filename)})
return
}
restoreConfigureMutex.Lock()
defer restoreConfigureMutex.Unlock()
restoreConfigureVar = &conf
restoreConfigureKey = stringsp.GetRandomStringNum(16)
c.JSON(http.StatusOK, gin.H{"ret": 0, "file": file.Filename, "restoreConfigureKey": restoreConfigureKey})
}

293
web/ddns.go Normal file
View File

@ -0,0 +1,293 @@
package web
import (
"fmt"
"net/http"
"strings"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/ddnscore.go"
"github.com/gdy666/lucky/thirdlib/gdylib/service"
"github.com/gin-gonic/gin"
)
func addDDNS(c *gin.Context) {
var requestObj config.DDNSTask
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
//fmt.Printf("addDDNS requestObj:%v\n", requestObj)
err = config.CheckDDNSTaskAvalid(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": err.Error()})
return
}
dealRequestDDNSTask(&requestObj)
err = config.DDNSTaskListAdd(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "DDNS任务添加出错"})
return
}
if requestObj.Enable {
service.Message("ddns", "syncDDNSTask", requestObj.TaskKey)
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
func deleteDDNSTask(c *gin.Context) {
taskKey := c.Query("key")
err := config.DDNSTaskListDelete(taskKey)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Errorf("删除DDNS任务出错:%s", err.Error())})
return
}
ddnscore.DDNSTaskInfoMapDelete(taskKey)
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
func enableddns(c *gin.Context) {
enable := c.Query("enable")
key := c.Query("key")
var err error
if enable == "true" {
err = ddnscore.EnableDDNSTaskByKey(key, true)
if err == nil {
service.Message("ddns", "syncDDNSTask", key)
}
} else {
err = ddnscore.EnableDDNSTaskByKey(key, false)
}
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("开关DDNS任务出错:%s", err.Error())})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
func ddnsconfigure(c *gin.Context) {
conf := config.GetDDNSConfigure()
c.JSON(http.StatusOK, gin.H{"ret": 0, "ddnsconfigure": conf})
}
func alterDDNSTask(c *gin.Context) {
taskKey := c.Query("key")
var requestObj config.DDNSTask
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
err = config.CheckDDNSTaskAvalid(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": err.Error()})
return
}
dealRequestDDNSTask(&requestObj)
err = config.UpdateTaskToDDNSTaskList(taskKey, requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("更新DDNS任务出错:%s", err.Error())})
return
}
ddnscore.DDNSTaskInfoMapDelete(taskKey)
if requestObj.Enable {
service.Message("ddns", "syncDDNSTask", taskKey)
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
func ddnsTaskList(c *gin.Context) {
conf := config.GetDDNSConfigure()
if !conf.Enable {
c.JSON(http.StatusOK, gin.H{"ret": 6, "msg": "请先在设置页面启用DDNS动态域名服务"})
return
}
taskList := ddnscore.GetDDNSTaskInfoList()
ddnscore.FLushWebLastAccessDDNSTaskListLastTime()
c.JSON(http.StatusOK, gin.H{"ret": 0, "data": taskList})
}
func dealRequestDDNSTask(t *config.DDNSTask) {
if t.DNS.Name == "callback" {
t.DNS.ID = ""
t.DNS.Secret = ""
t.DNS.Callback.URL = strings.TrimSpace(t.DNS.Callback.URL)
//requestObj.DNS.Callback.CallbackSuccessContent = strings.TrimSpace(requestObj.DNS.Callback.CallbackSuccessContent)
t.DNS.Callback.RequestBody = strings.TrimSpace(t.DNS.Callback.RequestBody)
} else {
t.DNS.Callback = config.DNSCallback{}
}
if !t.DNS.ResolverDoaminCheck && len(t.DNS.DNSServerList) > 0 {
t.DNS.DNSServerList = []string{}
}
if t.DNS.ResolverDoaminCheck && (len(t.DNS.DNSServerList) == 0 || (len(t.DNS.DNSServerList) == 1 && t.DNS.DNSServerList[0] == "")) {
if t.TaskType == "IPv6" {
t.DNS.DNSServerList = config.DefaultIPv6DNSServerList
} else {
t.DNS.DNSServerList = config.DefaultIPv4DNSServerList
}
}
if t.DNS.HttpClientProxyType != "" && t.DNS.HttpClientProxyAddr == "" {
t.DNS.HttpClientProxyType = ""
}
if t.DNS.HttpClientProxyType == "" {
t.DNS.HttpClientProxyAddr = ""
t.DNS.HttpClientProxyUser = ""
t.DNS.HttpClientProxyPassword = ""
}
if t.GetType == "url" {
t.NetInterface = ""
t.IPReg = ""
}
if t.GetType == "netInterface" {
t.URL = []string{}
}
if t.DNS.Name == "callback" {
if t.DNS.Callback.DisableCallbackSuccessContentCheck {
t.DNS.Callback.CallbackSuccessContent = []string{}
}
}
if !t.WebhookEnable {
t.WebhookHeaders = []string{}
t.WebhookMethod = ""
t.WebhookRequestBody = ""
t.WebhookURL = ""
t.WebhookSuccessContent = []string{}
t.WebhookProxy = ""
t.WebhookProxyAddr = ""
t.WebhookProxyUser = ""
t.WebhookProxyPassword = ""
}
if t.WebhookEnable {
if t.WebhookMethod == "get" {
t.WebhookRequestBody = ""
}
if t.WebhookProxy == "" {
t.WebhookProxyAddr = ""
t.WebhookProxyUser = ""
t.WebhookProxyPassword = ""
}
}
if t.DNS.ForceInterval < 60 {
t.DNS.ForceInterval = 60
} else if t.DNS.ForceInterval > 360000 {
t.DNS.ForceInterval = 360000
}
if t.HttpClientTimeout < 3 {
t.HttpClientTimeout = 3
} else if t.HttpClientTimeout > 60 {
t.HttpClientTimeout = 60
}
}
func alterDDNSConfigure(c *gin.Context) {
var requestObj config.DDNSConfigure
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
preConfigure := config.GetDDNSConfigure()
if preConfigure.Enable != requestObj.Enable {
//log.Printf("动态服务服务状态改变:%v", requestObj.Enable)
if requestObj.Enable {
service.Start("ddns")
} else {
service.Stop("ddns")
}
}
err = config.SetDDNSConfigure(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": "保存配置过程发生错误,请检测相关启动配置"})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0})
}
func webhookTest(c *gin.Context) {
key := c.Query("key")
ddnsTask := ddnscore.GetDDNSTaskInfoByKey(key)
if ddnsTask == nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("找不到key对应的DDNS任务:%s", key)})
return
}
var request struct {
WebhookURL string `json:"WebhookURL"`
WebhookMethod string `json:"WebhookMethod"`
WebhookHeaders []string `json:"WebhookHeaders"`
WebhookRequestBody string `json:"WebhookRequestBody"`
WebhookSuccessContent []string `json:"WebhookSuccessContent"` //接口调用成功包含的内容
WebhookProxy string `json:"WebhookProxy"` //使用DNS代理设置 ""表示禁用,"dns"表示使用dns的代理设置
WebhookProxyAddr string `json:"WebhookProxyAddr"` //代理服务器IP
WebhookProxyUser string `json:"WebhookProxyUser"` //代理用户
WebhookProxyPassword string `json:"WebhookProxyPassword"` //代理密码
}
err := c.Bind(&request)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": fmt.Sprintf("请求解析出错:%s", err.Error())})
return
}
responseStr, err := ddnscore.WebhookTest(ddnsTask,
request.WebhookURL,
request.WebhookMethod,
request.WebhookRequestBody,
request.WebhookProxy,
request.WebhookProxyAddr,
request.WebhookProxyUser,
request.WebhookProxyPassword,
request.WebhookHeaders,
request.WebhookSuccessContent)
msg := "Webhook接口调用成功"
if err != nil {
msg = err.Error()
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": msg, "Response": responseStr})
}

View File

@ -1,98 +0,0 @@
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: strings.TrimSpace(string(p))}
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]
}

262
web/reverseproxy.go Normal file
View File

@ -0,0 +1,262 @@
package web
import (
"fmt"
"net"
"net/http"
"strconv"
"strings"
"github.com/gdy666/lucky/config"
"github.com/gdy666/lucky/reverseproxy"
"github.com/gdy666/lucky/thirdlib/gdylib/stringsp"
"github.com/gin-gonic/gin"
)
func reverseProxys(c *gin.Context) {
proxyRuleList := reverseproxy.GetProxyRuleListInfo()
c.JSON(http.StatusOK, gin.H{"ret": 0, "list": proxyRuleList})
}
func addReverseProxyRule(c *gin.Context) {
var requestObj config.ReverseProxyRule
err := c.BindJSON(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
err = checkReverseProxyRuleRequest(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": err.Error()})
return
}
err = config.ReverseProxyRuleListAdd(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": fmt.Sprintf("添加反向代理规则失败:%s", err.Error())})
return
}
if requestObj.Enable {
reverseproxy.EnableRuleByKey(requestObj.RuleKey, true)
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
func alterReverseProxyRule(c *gin.Context) {
var requestObj config.ReverseProxyRule
err := c.BindJSON(&requestObj)
if err != nil {
fmt.Printf("fff:%s\n", err.Error())
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": "请求解析出错"})
return
}
err = checkReverseProxyRuleRequest(&requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": err.Error()})
return
}
err = config.UpdateReverseProxyRulet(requestObj)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": fmt.Sprintf("修改反向代理规则失败:%s", err.Error())})
return
}
// needStop := false
// if preRule != nil &&
// (preRule.Network != requestObj.Network ||
// preRule.ListenPort != requestObj.ListenPort ||
// preRule.Enable != requestObj.Enable ||
// !requestObj.Enable) {
// needStop = true
// }
// if needStop {
// }
reverseproxy.EnableRuleByKey(requestObj.RuleKey, false)
//reverseproxy.FlushCache(requestObj.RuleKey)
if requestObj.Enable {
reverseproxy.EnableRuleByKey(requestObj.RuleKey, true)
}
config.TidyReverseProxyCache()
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
func deleteReverseProxyRule(c *gin.Context) {
ruleKey := c.Query("key")
err := reverseproxy.EnableRuleByKey(ruleKey, false)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 2, "msg": fmt.Sprintf("删除反向代理规则出错:%s", err.Error())})
return
}
err = config.ReverseProxyRuleListDelete(ruleKey)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 3, "msg": fmt.Sprintf("删除反向代理规则出错:%s", err.Error())})
return
}
config.TidyReverseProxyCache()
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
func enableReverseProxyRule(c *gin.Context) {
enableStr := c.Query("enable")
ruleKey := c.Query("ruleKey")
proxyKey := c.Query("proxyKey")
enable := false
if enableStr == "true" {
enable = true
}
if proxyKey == "" { //开关规则
err := reverseproxy.EnableRuleByKey(ruleKey, enable)
if err != nil {
errMsg := err.Error()
if strings.Contains(errMsg, "Only one usage of each socket address") {
errMsg = "端口已被占用"
}
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": errMsg})
return
}
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
return
}
err := config.EnableReverseProxySubRule(ruleKey, proxyKey, enable)
if err != nil {
c.JSON(http.StatusOK, gin.H{"ret": 1, "msg": err.Error()})
return
}
//reverseproxy.FlushCache(ruleKey)
c.JSON(http.StatusOK, gin.H{"ret": 0, "msg": ""})
}
func checkReverseProxyRuleRequest(rule *config.ReverseProxyRule) error {
// if len(rule.ProxyList) <= 0 {
// return fmt.Errorf("至少添加一条反向代理转发规则")
// }
var err error
if rule.RuleKey == "" {
rule.RuleKey = stringsp.GetRandomString(16)
}
rule.DefaultProxy.Key = rule.RuleKey
if len(rule.DefaultProxy.Locations) > 0 {
defaultLocations := []string{}
for i := range rule.DefaultProxy.Locations {
scheme, hostname, port, _, e := stringsp.GetHostAndPathFromURL(rule.DefaultProxy.Locations[i])
if e != nil {
return fmt.Errorf("默认目标地址[%s]格式有误", rule.DefaultProxy.Locations[i])
}
if port != "" {
port = ":" + port
}
defaultLocations = append(defaultLocations, fmt.Sprintf("%s://%s%s", scheme, hostname, port))
}
rule.DefaultProxy.Locations = defaultLocations
}
if rule.DefaultProxy.AddRemoteIPToHeader && rule.DefaultProxy.AddRemoteIPHeaderKey == "" {
return fmt.Errorf("追加客户端连接IP到指定Header 启用时,自定义HeaderKey不能为空")
}
for i := range rule.ProxyList {
domainsLength := len(rule.ProxyList[i].Domains)
if domainsLength <= 0 {
return fmt.Errorf("第 %d 条反向代理转发规则中域名不能为空", i+1)
}
locationsLength := len(rule.ProxyList[i].Locations)
if locationsLength <= 0 {
return fmt.Errorf("第 %d 条反向代理转发规则中后端目标地址不能为空", i+1)
}
for j := range rule.ProxyList[i].Domains {
_, hostname, _, _, e := stringsp.GetHostAndPathFromURL(rule.ProxyList[i].Domains[j])
if e != nil {
return fmt.Errorf("第 %d 条反向代理转发规则中第 %d 条前端地址/域名[%s]格式有误", i+1, j+1, rule.ProxyList[i].Domains[j])
}
rule.ProxyList[i].Domains[j] = hostname
}
for j := range rule.ProxyList[i].Locations {
scheme, hostname, port, _, e := stringsp.GetHostAndPathFromURL(rule.ProxyList[i].Locations[j])
if e != nil {
return fmt.Errorf("第 %d 条反向代理转发规则中第 %d 条后端目标地址[%s]格式有误", i+1, j+1, rule.ProxyList[i].Locations[j])
}
if port != "" {
port = ":" + port
}
rule.ProxyList[i].Locations[j] = fmt.Sprintf("%s://%s%s", scheme, hostname, port)
}
if rule.ProxyList[i].AddRemoteIPToHeader && rule.ProxyList[i].AddRemoteIPHeaderKey == "" {
return fmt.Errorf("第 %d 条子规则中 追加客户端连接IP到指定Header 启用时,自定义HeaderKey不能为空", i+1)
}
if rule.ProxyList[i].Key == "" {
rule.ProxyList[i].Key = stringsp.GetRandomString(16)
}
for j := range rule.ProxyList[i].TrustedCIDRsStrList {
_, _, err = net.ParseCIDR(rule.ProxyList[i].TrustedCIDRsStrList[j])
if err != nil {
return fmt.Errorf("第 %d 条子规则中 TrustedCIDRsStrList[%s]格式有误", i+1, rule.ProxyList[i].TrustedCIDRsStrList[j])
}
}
}
for i := range rule.DefaultProxy.TrustedCIDRsStrList {
_, _, err = net.ParseCIDR(rule.DefaultProxy.TrustedCIDRsStrList[i])
if err != nil {
return fmt.Errorf("默认子规则中的 TrustedCIDRsStrList[%s]格式有误", rule.DefaultProxy.TrustedCIDRsStrList[i])
}
}
rule.Init()
return nil
}
func getReverseProxyLog(c *gin.Context) {
ruleKey := c.Query("ruleKey")
proxyKey := c.Query("proxyKey")
pageSize, _ := strconv.Atoi(c.Query("pageSize"))
if pageSize <= 0 {
pageSize = 10
}
page, _ := strconv.Atoi(c.Query("page"))
if page <= 0 {
page = 1
}
//last := c.Query("last")
total, logList := reverseproxy.GetAccessLogs(ruleKey, proxyKey, pageSize, page)
c.JSON(http.StatusOK, gin.H{"ret": 0, "total": total, "page": page, "pageSize": pageSize, "logs": logList})
}

200
web/socketproxy.go Normal file
View File

@ -0,0 +1,200 @@
package web
import (
"fmt"
"log"
"net/http"
"strings"
"github.com/gdy666/lucky/rule"
"github.com/gdy666/lucky/socketproxy"
"github.com/gin-gonic/gin"
)
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 socketproxy.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 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
}
dealRequestRule(&requestRule)
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 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
}
dealRequestRule(&requestRule)
//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 dealRequestRule(r *rule.RelayRule) {
r.ListenPorts = strings.TrimSpace(r.ListenPorts)
r.TargetPorts = strings.TrimSpace(r.TargetPorts)
r.ListenIP = strings.TrimSpace(r.ListenIP)
r.TargetIP = strings.TrimSpace(r.TargetIP)
r.RelayType = strings.TrimSpace(r.RelayType)
r.Name = strings.TrimSpace(r.Name)
}
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})
}

File diff suppressed because it is too large Load Diff

View File

@ -3,5 +3,5 @@
package main
func RunAdminWeb(listenPort int) {
func RunAdminWeb(listenPort int, logMaxSize int) {
}