Update On Sat Sep 7 20:31:58 CEST 2024

This commit is contained in:
github-action[bot]
2024-09-07 20:31:59 +02:00
parent 1a9fdafeaa
commit 6235744370
121 changed files with 2380 additions and 1297 deletions

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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
}

View File

@@ -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
}