Skip to content

用hysteria2做ip池

  • 起因爬虫做了ip的并发限制,大量爬取之后ip被封,所以想用hysteria2做ip池.
  • 为什么用hysteria2也是因为目前来看hysteria2用的比ss多
  • hysteria2/clash/ss 这些本身编译好的程序都是不支持 访问同一个网站的时候使用不同ip的.唯一方案是开启多个客户端,但是很麻烦.
  • ip池本身使用的是clash的配置文件
  • 这种pr官方肯定不会接受... 放一下魔改了的源码

结果

bash
2024-11-04T00:30:38+08:00       INFO    HTTP proxy server listening     {"addr": "127.0.0.1:8080"}
2024-11-04T00:30:38+08:00       INFO    HTTP proxy server listening     {"addr": "127.0.0.1:8088"}
2024-11-04T00:30:38+08:00       INFO    HTTP proxy server listening     {"addr": "127.0.0.1:8085"}
2024-11-04T00:30:38+08:00       INFO    HTTP proxy server listening     {"addr": "127.0.0.1:8086"}
2024-11-04T00:30:38+08:00       INFO    HTTP proxy server listening     {"addr": "127.0.0.1:8089"}
2024-11-04T00:30:38+08:00       INFO    HTTP proxy server listening     {"addr": "127.0.0.1:8081"}
2024-11-04T00:30:38+08:00       INFO    HTTP proxy server listening     {"addr": "127.0.0.1:8084"}
2024-11-04T00:30:38+08:00       INFO    HTTP proxy server listening     {"addr": "127.0.0.1:8082"}
2024-11-04T00:30:38+08:00       INFO    HTTP proxy server listening     {"addr": "127.0.0.1:8087"}
2024-11-04T00:30:38+08:00       INFO    HTTP proxy server listening     {"addr": "127.0.0.1:8083"}

负载均衡什么的自己做就好了,用nginx反代也是可以的

修改 添加文件 app/cmd/myclient.go

用test 运行,或者在app/main.go中添加

go
package main

import "github.com/apernet/hysteria/app/v2/cmd"

func main() {
	cmd.MyClientRun()
}

app/cmd/myclient.go

go
package cmd

import (
	"fmt"
	"github.com/apernet/hysteria/core/v2/client"
	"go.uber.org/zap"
	"gopkg.in/yaml.v3"
	"net"
	"os"
	"os/signal"
	"sort"
	"strconv"
	"syscall"
	"time"
)

// CONFIGFILEDIR 是配置文件目录的常量
const CONFIGFILEDIR = "/Users/parapeng/Library/Application Support/io.github.clash-verge-rev.clash-verge-rev/profiles/"

// configFiles 是配置文件列表,包含多个配置文件路径
var configFiles = []string{
	CONFIGFILEDIR + "ROO5OxI3HLEr.yaml", //新狮云
	CONFIGFILEDIR + "RkXrhCTvpx4T.yaml", //宝可梦
}

// Proxy 代表clash配置部分
type Proxy struct {
	Name           string `yaml:"name"`
	Server         string `yaml:"server"`
	Port           int    `yaml:"port"`
	Ports          string `yaml:"ports"`
	MPort          string `yaml:"mport"`
	UDP            bool   `yaml:"udp"`
	SkipCertVerify bool   `yaml:"skip-cert-verify"`
	SNI            string `yaml:"sni"`
	Type           string `yaml:"type"`
	Password       string `yaml:"password"`
	Obfs           string `yaml:"obfs"`
	ObfsPassword   string `yaml:"obfs-password"`
	PingLatency    time.Duration
}

// clashConfig 是Clash配置结构体,包含多个代理配置
type clashConfig struct {
	Proxies []Proxy `yaml:"proxies"`
}

// h2clientConfig 将Clash配置转换为Hysteria2客户端配置
func (c *clashConfig) h2clientConfig() []*clientConfig {
	var ret []*clientConfig
	for _, proxy := range c.Proxies {
		cc := clientConfig{
			Server: fmt.Sprintf("%s:%d", proxy.Server, proxy.Port),
			Auth:   proxy.Password,
		}
		if proxy.Obfs != "" && proxy.ObfsPassword != "" {
			cc.Obfs = clientConfigObfs{
				proxy.Obfs,
				clientConfigObfsSalamander{
					Password: proxy.ObfsPassword,
				},
			}
		}
		ret = append(ret, &cc)
	}
	return ret
}

// parseConfig 解析配置文件,返回解析后的Clash配置
func parseConfig(files []string, max int) (*clashConfig, error) {
	serverMap := make(map[string]Proxy)
	for _, file := range files {
		data, err := os.ReadFile(file)
		if err != nil {
			return nil, fmt.Errorf("failed to read config file %s: %w", file, err)
		}
		var config clashConfig
		if err = yaml.Unmarshal(data, &config); err != nil {
			return nil, fmt.Errorf("failed to unmarshal YAML in %s: %w", file, err)
		}
		for _, proxy := range config.Proxies {
			if proxy.Type != "hysteria2" {
				continue
			}
			if _, ok := serverMap[proxy.Server]; ok {
				continue
			}
			serverMap[proxy.Server] = proxy
		}
	}

	// 按照延迟排序,取前 max 个
	proxies := make([]Proxy, 0, len(serverMap))
	for _, proxy := range serverMap {
		// 获取 proxy.Server 的“延迟”值
		latency, err := getPingLatency(proxy.Server, proxy.Port)
		if err != nil {
			logger.Warn("failed to measure latency for server", zap.String("server", proxy.Server), zap.Error(err))
			continue
		}
		proxy.PingLatency = latency
		proxies = append(proxies, proxy)
	}
	sort.Slice(proxies, func(i, j int) bool {
		return proxies[i].PingLatency < proxies[j].PingLatency
	})
	if len(proxies) > max {
		proxies = proxies[:max]
	}

	return &clashConfig{proxies}, nil
}

// getPingLatency 测量服务器的延迟
func getPingLatency(server string, port int) (time.Duration, error) {
	address := fmt.Sprintf("%s:%d", server, port)
	start := time.Now()
	conn, err := net.DialTimeout("udp", address, time.Second*5)
	if err != nil {
		return 0, fmt.Errorf("failed to connect to server %s: %w", address, err)
	}
	defer conn.Close()
	return time.Since(start), nil // 返回连接所花费的时间
}

// MyClientRun 是客户端运行的主函数
func MyClientRun() {
	initLogger()
	clashConfig, err := parseConfig(configFiles, 10)
	if err != nil {
		logger.Fatal("failed to parse config", zap.Error(err))
	}
	configs := clashConfig.h2clientConfig()
	clients := map[string]client.Client{}
	var runner clientModeRunner

	// 初始化所有客户端
	err = initClients(configs, &runner, clients)
	if err != nil {
		logger.Fatal("failed to initialize clients", zap.Error(err))
	}
	// 运行客户端模式
	runClientMode(&runner, clients)
}

// initClients 初始化客户端并将其添加到 runner 中
func initClients(configs []*clientConfig, runner *clientModeRunner, clients map[string]client.Client) error {
	for i, config := range configs {
		hc := httpConfig{
			Listen: fmt.Sprintf("127.0.0.1:808%d", i),
		}
		logger.Info("config:", zap.String("addr", hc.Listen), zap.String("Server", config.Server))
		cli, err := client.NewReconnectableClient(
			config.Config,
			func(c client.Client, info *client.HandshakeInfo, count int) {
				logger.Info("connected to server",
					zap.Bool("udpEnabled", info.UDPEnabled),
					zap.Uint64("tx", info.Tx),
					zap.Int("count", count))
			},
			true,
		)
		if err != nil {
			return fmt.Errorf("failed to initialize client for server %s: %w", config.Server, err)
		}

		clients[config.Server] = cli
		runner.Add("HTTP proxy server"+strconv.Itoa(i), func() error {
			return clientHTTP(hc, cli)
		})
	}
	return nil
}

// runClientMode 运行客户端模式并监听结果
func runClientMode(runner *clientModeRunner, clients map[string]client.Client) {
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
	runnerChan := make(chan clientModeRunnerResult, 1)
	go func() {
		runnerChan <- runner.Run()
	}()

	select {
	case r := <-runnerChan:
		if r.OK {
			logger.Info(r.Msg)
		} else {
			for cli := range clients {
				clients[cli].Close()
			}
			if r.Err != nil {
				logger.Fatal(r.Msg, zap.Error(r.Err))
			} else {
				logger.Fatal(r.Msg)
			}
		}
	case <-signalChan:
		logger.Info("received signal, shutting down gracefully")
		for key, cli := range clients {
			cli.Close()
			logger.Info(key + ":Closed")
		}
		signal.Stop(signalChan)
		logger.Info("signal:Closed")
	}

}

成果是一个壁纸网站: