mirror of
https://github.com/bolucat/Archive.git
synced 2025-12-24 13:28:37 +08:00
Update On Sat Sep 7 20:31:58 CEST 2024
This commit is contained in:
@@ -152,7 +152,7 @@ func sumFloat64Metric(metricMap map[string]*dto.MetricFamily, metricName string)
|
||||
ret += getMetricValue(m, metric.GetType())
|
||||
}
|
||||
}
|
||||
return 0
|
||||
return ret
|
||||
}
|
||||
|
||||
func getLabel(metric *dto.Metric, name string) string {
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
relay_cfg "github.com/Ehco1996/ehco/internal/relay/conf"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type ClashSub struct {
|
||||
Name string
|
||||
URL string
|
||||
|
||||
cCfg *clashConfig
|
||||
}
|
||||
|
||||
func NewClashSub(rawClashCfgBuf []byte, name string, url string) (*ClashSub, error) {
|
||||
var rawCfg clashConfig
|
||||
err := yaml.Unmarshal(rawClashCfgBuf, &rawCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawCfg.Adjust()
|
||||
return &ClashSub{cCfg: &rawCfg, Name: name, URL: url}, nil
|
||||
}
|
||||
|
||||
func NewClashSubByURL(url string, name string) (*ClashSub, error) {
|
||||
body, err := getHttpBody(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewClashSub(body, name, url)
|
||||
}
|
||||
|
||||
func (c *ClashSub) ToClashConfigYaml() ([]byte, error) {
|
||||
return yaml.Marshal(c.cCfg)
|
||||
}
|
||||
|
||||
func (c *ClashSub) ToGroupedClashConfigYaml() ([]byte, error) {
|
||||
groupProxy := c.cCfg.groupByLongestCommonPrefix()
|
||||
ps := []*Proxies{}
|
||||
groupNameList := []string{}
|
||||
for groupName := range groupProxy {
|
||||
groupNameList = append(groupNameList, groupName)
|
||||
}
|
||||
sort.Strings(groupNameList)
|
||||
for _, groupName := range groupNameList {
|
||||
proxies := groupProxy[groupName]
|
||||
// only use first proxy will be show in proxy provider, other will be merged into load balance in relay
|
||||
p := proxies[0].getOrCreateGroupLeader()
|
||||
ps = append(ps, p)
|
||||
}
|
||||
groupedCfg := &clashConfig{&ps}
|
||||
return yaml.Marshal(groupedCfg)
|
||||
}
|
||||
|
||||
func (c *ClashSub) Refresh() error {
|
||||
// get new clash sub by url
|
||||
newSub, err := NewClashSubByURL(c.URL, c.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
needAdd := []*Proxies{}
|
||||
needDeleteProxyName := map[string]struct{}{}
|
||||
|
||||
// check if need add/delete proxies
|
||||
for _, newProxy := range *newSub.cCfg.Proxies {
|
||||
oldProxy := c.cCfg.GetProxyByRawName(newProxy.rawName)
|
||||
if oldProxy == nil {
|
||||
needAdd = append(needAdd, newProxy)
|
||||
} else if oldProxy.Different(newProxy) {
|
||||
// update so we need to delete and add again
|
||||
needDeleteProxyName[oldProxy.rawName] = struct{}{}
|
||||
needAdd = append(needAdd, newProxy)
|
||||
}
|
||||
}
|
||||
// check if need delete proxies
|
||||
for _, proxy := range *c.cCfg.Proxies {
|
||||
newProxy := newSub.cCfg.GetProxyByRawName(proxy.rawName)
|
||||
if newProxy == nil {
|
||||
needDeleteProxyName[proxy.rawName] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
tmp := []*Proxies{}
|
||||
// delete proxies from changedCfg
|
||||
for _, p := range *c.cCfg.Proxies {
|
||||
if _, ok := needDeleteProxyName[p.rawName]; !ok {
|
||||
tmp = append(tmp, p)
|
||||
}
|
||||
}
|
||||
// add new proxies to changedCfg
|
||||
tmp = append(tmp, needAdd...)
|
||||
|
||||
// update current
|
||||
c.cCfg.Proxies = &tmp
|
||||
|
||||
// init group leader for each group
|
||||
groupProxy := c.cCfg.groupByLongestCommonPrefix()
|
||||
for _, proxies := range groupProxy {
|
||||
// only use first proxy will be show in proxy provider, other will be merged into load balance in relay
|
||||
proxies[0].getOrCreateGroupLeader()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToRelayConfigs convert clash sub to relay configs
|
||||
// Proxy's port will be used as relay listen port
|
||||
// Group's proxies will be merged into load balance in relay
|
||||
// a new free port will be used as relay listen port for each group
|
||||
func (c *ClashSub) ToRelayConfigs(listenHost string) ([]*relay_cfg.Config, error) {
|
||||
relayConfigs := []*relay_cfg.Config{}
|
||||
portSet := map[int]struct{}{}
|
||||
|
||||
const minPort = 10000
|
||||
const maxPort = 65535
|
||||
nextPort := minPort
|
||||
getNextAvailablePort := func() int {
|
||||
for ; nextPort <= maxPort; nextPort++ {
|
||||
if _, occupied := portSet[nextPort]; !occupied {
|
||||
portSet[nextPort] = struct{}{}
|
||||
defer func() { nextPort++ }()
|
||||
return nextPort
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// generate relay config for each proxy
|
||||
for _, proxy := range *c.cCfg.Proxies {
|
||||
proxyPort, err := strconv.Atoi(proxy.Port)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := portSet[proxyPort]; ok {
|
||||
proxyPort = getNextAvailablePort()
|
||||
}
|
||||
var newName string
|
||||
if strings.HasSuffix(proxy.Name, "-") {
|
||||
newName = fmt.Sprintf("%s%s", proxy.Name, c.Name)
|
||||
} else {
|
||||
newName = fmt.Sprintf("%s-%s", proxy.Name, c.Name)
|
||||
}
|
||||
rc, err := proxy.ToRelayConfig(listenHost, strconv.Itoa(proxyPort), newName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
relayConfigs = append(relayConfigs, rc)
|
||||
}
|
||||
|
||||
// generate relay config for each group
|
||||
groupProxy := c.cCfg.groupByLongestCommonPrefix()
|
||||
for groupName, proxies := range groupProxy {
|
||||
groupLeader := proxies[0].getOrCreateGroupLeader()
|
||||
var newName string
|
||||
if strings.HasSuffix(groupName, "-") {
|
||||
newName = fmt.Sprintf("%slb", groupName)
|
||||
} else {
|
||||
newName = fmt.Sprintf("%s-lb", groupName)
|
||||
}
|
||||
port := getNextAvailablePort()
|
||||
if port == -1 {
|
||||
return nil, fmt.Errorf("no available port")
|
||||
}
|
||||
rc, err := groupLeader.ToRelayConfig(listenHost, strconv.Itoa(port), newName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// add other proxies address in group to relay config
|
||||
for _, proxy := range proxies[1:] {
|
||||
remote := net.JoinHostPort(proxy.rawServer, proxy.rawPort)
|
||||
if strInArray(remote, rc.Remotes) {
|
||||
continue
|
||||
}
|
||||
rc.Remotes = append(rc.Remotes, remote)
|
||||
if proxy.UDP {
|
||||
rc.Options = &relay_cfg.Options{
|
||||
EnableUDP: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
relayConfigs = append(relayConfigs, rc)
|
||||
}
|
||||
return relayConfigs, nil
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Ehco1996/ehco/pkg/log"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
l *zap.Logger
|
||||
|
||||
configBuf = []byte(`
|
||||
proxies:
|
||||
- name: ss
|
||||
type: ss
|
||||
server: proxy1.example.com
|
||||
port: 1080
|
||||
password: password
|
||||
cipher: aes-128-gcm
|
||||
udp: true
|
||||
- name: trojan
|
||||
type: trojan
|
||||
server: proxy2.example.com
|
||||
port: 443
|
||||
password: password
|
||||
skip-cert-verify: true
|
||||
`)
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.InitGlobalLogger("debug")
|
||||
l = zap.L().Named("clash_test")
|
||||
}
|
||||
|
||||
func TestNewClashConfig(t *testing.T) {
|
||||
// todo add more proxy types
|
||||
|
||||
cs, err := NewClashSub(configBuf, "test", "")
|
||||
assert.NoError(t, err, "NewConfig should not return an error")
|
||||
assert.NotNil(t, cs, "Config should not be nil")
|
||||
expectedProxyCount := 2
|
||||
assert.Equal(t, expectedProxyCount, len(*cs.cCfg.Proxies), "Proxy count should match")
|
||||
|
||||
yamlBuf, err := cs.ToClashConfigYaml()
|
||||
assert.NoError(t, err, "ToClashConfigYaml should not return an error")
|
||||
assert.NotNil(t, yamlBuf, "yamlBuf should not be nil")
|
||||
l.Info("yamlBuf", zap.String("yamlBuf", string(yamlBuf)))
|
||||
}
|
||||
|
||||
func TestToRelayConfigs(t *testing.T) {
|
||||
cs, err := NewClashSub(configBuf, "test", "")
|
||||
assert.NoError(t, err, "NewConfig should not return an error")
|
||||
assert.NotNil(t, cs, "Config should not be nil")
|
||||
|
||||
relayConfigs, err := cs.ToRelayConfigs("localhost")
|
||||
assert.NoError(t, err, "ToRelayConfigs should not return an error")
|
||||
assert.NotNil(t, relayConfigs, "relayConfigs should not be nil")
|
||||
expectedRelayCount := 4 // 2 proxy + 2 load balance
|
||||
assert.Equal(t, expectedRelayCount, len(relayConfigs), "Relay count should match")
|
||||
l.Info("relayConfigs", zap.Any("relayConfigs", relayConfigs))
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Ehco1996/ehco/internal/constant"
|
||||
relay_cfg "github.com/Ehco1996/ehco/internal/relay/conf"
|
||||
)
|
||||
|
||||
type clashConfig struct {
|
||||
Proxies *[]*Proxies `yaml:"proxies"`
|
||||
}
|
||||
|
||||
func (cc *clashConfig) GetProxyByRawName(name string) *Proxies {
|
||||
for _, proxy := range *cc.Proxies {
|
||||
if proxy.rawName == name {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *clashConfig) GetProxyByName(name string) *Proxies {
|
||||
for _, proxy := range *cc.Proxies {
|
||||
if proxy.Name == name {
|
||||
return proxy
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cc *clashConfig) Adjust() {
|
||||
for _, proxy := range *cc.Proxies {
|
||||
if proxy.rawName == "" {
|
||||
proxy.rawName = proxy.Name
|
||||
proxy.rawPort = proxy.Port
|
||||
proxy.rawServer = proxy.Server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cc *clashConfig) groupByLongestCommonPrefix() map[string][]*Proxies {
|
||||
proxies := cc.Proxies
|
||||
|
||||
proxyNameList := []string{}
|
||||
for _, proxy := range *proxies {
|
||||
proxyNameList = append(proxyNameList, proxy.Name)
|
||||
}
|
||||
groupNameMap := groupByLongestCommonPrefix(proxyNameList)
|
||||
|
||||
proxyGroups := make(map[string][]*Proxies)
|
||||
for groupName, proxyNames := range groupNameMap {
|
||||
for _, proxyName := range proxyNames {
|
||||
proxyGroups[groupName] = append(proxyGroups[groupName], cc.GetProxyByName(proxyName))
|
||||
}
|
||||
}
|
||||
return proxyGroups
|
||||
}
|
||||
|
||||
type Proxies struct {
|
||||
// basic fields
|
||||
Name string `yaml:"name"`
|
||||
Type string `yaml:"type"`
|
||||
Server string `yaml:"server"`
|
||||
Port string `yaml:"port"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
UDP bool `yaml:"udp,omitempty"`
|
||||
|
||||
// for shadowsocks todo(support opts)
|
||||
Cipher string `yaml:"cipher,omitempty"`
|
||||
|
||||
// for trojan todo(support opts)
|
||||
ALPN []string `yaml:"alpn,omitempty"`
|
||||
SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"`
|
||||
SNI string `yaml:"sni,omitempty"`
|
||||
Network string `yaml:"network,omitempty"`
|
||||
|
||||
// for socks5 todo(support opts)
|
||||
UserName string `yaml:"username,omitempty"`
|
||||
TLS bool `yaml:"tls,omitempty"`
|
||||
|
||||
// for vmess todo(support opts)
|
||||
UUID string `yaml:"uuid,omitempty"`
|
||||
AlterID int `yaml:"alterId,omitempty"`
|
||||
ServerName string `yaml:"servername,omitempty"`
|
||||
|
||||
rawName string
|
||||
rawServer string
|
||||
rawPort string
|
||||
relayCfg *relay_cfg.Config
|
||||
|
||||
groupLeader *Proxies
|
||||
}
|
||||
|
||||
func (p *Proxies) Different(new *Proxies) bool {
|
||||
if p.Type != new.Type ||
|
||||
p.Password != new.Password ||
|
||||
p.UDP != new.UDP ||
|
||||
p.Cipher != new.Cipher ||
|
||||
len(p.ALPN) != len(new.ALPN) ||
|
||||
p.SkipCertVerify != new.SkipCertVerify ||
|
||||
p.SNI != new.SNI ||
|
||||
p.Network != new.Network ||
|
||||
p.UserName != new.UserName ||
|
||||
p.TLS != new.TLS ||
|
||||
p.UUID != new.UUID ||
|
||||
p.AlterID != new.AlterID ||
|
||||
p.ServerName != new.ServerName {
|
||||
return true
|
||||
}
|
||||
// ALPN field is a slice, should assert values successively.
|
||||
for i, v := range p.ALPN {
|
||||
if v != new.ALPN[i] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Server Port Name will be changed when ToRelayConfig is called. so we just need to compare the other fields.
|
||||
if p.rawName != new.rawName ||
|
||||
p.rawServer != new.rawServer ||
|
||||
p.rawPort != new.rawPort {
|
||||
return true
|
||||
}
|
||||
|
||||
// All fields are equivalent, so proxies are not different.
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *Proxies) ToRelayConfig(listenHost string, listenPort string, newName string) (*relay_cfg.Config, error) {
|
||||
if p.relayCfg != nil {
|
||||
return p.relayCfg, nil
|
||||
}
|
||||
remoteAddr := net.JoinHostPort(p.Server, p.Port)
|
||||
r := &relay_cfg.Config{
|
||||
Label: newName,
|
||||
ListenType: constant.RelayTypeRaw,
|
||||
TransportType: constant.RelayTypeRaw,
|
||||
Listen: net.JoinHostPort(listenHost, listenPort),
|
||||
Remotes: []string{remoteAddr},
|
||||
}
|
||||
if p.UDP {
|
||||
r.Options = &relay_cfg.Options{
|
||||
EnableUDP: true,
|
||||
}
|
||||
}
|
||||
if err := r.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// overwrite name,port,and server by relay
|
||||
p.Name = newName
|
||||
p.Server = listenHost
|
||||
p.Port = listenPort
|
||||
p.relayCfg = r
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (p *Proxies) Clone() *Proxies {
|
||||
cloned := &Proxies{
|
||||
Name: p.Name,
|
||||
Type: p.Type,
|
||||
Server: p.Server,
|
||||
Port: p.Port,
|
||||
Password: p.Password,
|
||||
UDP: p.UDP,
|
||||
Cipher: p.Cipher,
|
||||
ALPN: p.ALPN,
|
||||
SkipCertVerify: p.SkipCertVerify,
|
||||
SNI: p.SNI,
|
||||
Network: p.Network,
|
||||
UserName: p.UserName,
|
||||
TLS: p.TLS,
|
||||
UUID: p.UUID,
|
||||
AlterID: p.AlterID,
|
||||
ServerName: p.ServerName,
|
||||
|
||||
rawName: p.rawName,
|
||||
rawServer: p.rawServer,
|
||||
rawPort: p.rawPort,
|
||||
}
|
||||
if p.relayCfg != nil {
|
||||
cloned.relayCfg = p.relayCfg.Clone()
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
func (p *Proxies) getOrCreateGroupLeader() *Proxies {
|
||||
if p.groupLeader != nil {
|
||||
return p.groupLeader
|
||||
}
|
||||
p.groupLeader = p.Clone()
|
||||
// reset name,port,and server to raw
|
||||
p.groupLeader.Name = p.rawName
|
||||
p.groupLeader.Port = p.rawPort
|
||||
p.groupLeader.Server = p.rawServer
|
||||
p.groupLeader.relayCfg = nil
|
||||
return p.groupLeader
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package sub
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var client = http.Client{Timeout: time.Second * 10}
|
||||
|
||||
func getHttpBody(url string) ([]byte, error) {
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("http get sub config url=%s meet err=%v", url, err)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
msg := fmt.Sprintf("http get sub config url=%s meet status code=%d", url, resp.StatusCode)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("read body meet err=%v", err)
|
||||
return nil, fmt.Errorf(msg)
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func longestCommonPrefix(s string, t string) string {
|
||||
runeS := []rune(s)
|
||||
runeT := []rune(t)
|
||||
|
||||
i := 0
|
||||
for i = 0; i < len(runeS) && i < len(runeT); i++ {
|
||||
if runeS[i] != runeT[i] {
|
||||
return string(runeS[:i])
|
||||
}
|
||||
}
|
||||
return string(runeS[:i])
|
||||
}
|
||||
|
||||
func groupByLongestCommonPrefix(strList []string) map[string][]string {
|
||||
sort.Strings(strList)
|
||||
|
||||
grouped := make(map[string][]string)
|
||||
|
||||
// 先找出有相同前缀的字符串
|
||||
for i := 0; i < len(strList); i++ {
|
||||
for j := i + 1; j < len(strList); j++ {
|
||||
prefix := longestCommonPrefix(strList[i], strList[j])
|
||||
if prefix == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := grouped[prefix]; !ok {
|
||||
grouped[prefix] = []string{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤掉有相同前缀的前缀中较短的
|
||||
for prefix := range grouped {
|
||||
for otherPrefix := range grouped {
|
||||
if prefix == otherPrefix {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(otherPrefix, prefix) {
|
||||
delete(grouped, prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将字符串分组
|
||||
for _, proxy := range strList {
|
||||
foundPrefix := false
|
||||
for prefix := range grouped {
|
||||
if strings.HasPrefix(proxy, prefix) {
|
||||
grouped[prefix] = append(grouped[prefix], proxy)
|
||||
foundPrefix = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// 处理没有相同前缀的字符串,自己是一个分组
|
||||
if !foundPrefix {
|
||||
grouped[proxy] = []string{proxy}
|
||||
}
|
||||
}
|
||||
return grouped
|
||||
}
|
||||
|
||||
func strInArray(ele string, array []string) bool {
|
||||
for _, v := range array {
|
||||
if v == ele {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user