mirror of
https://github.com/bolucat/Archive.git
synced 2025-10-28 10:32:58 +08:00
Update On Sun Apr 7 20:25:36 CEST 2024
This commit is contained in:
1
.github/update.log
vendored
1
.github/update.log
vendored
@@ -609,3 +609,4 @@ Update On Wed Apr 3 20:26:29 CEST 2024
|
||||
Update On Thu Apr 4 20:27:14 CEST 2024
|
||||
Update On Fri Apr 5 20:26:37 CEST 2024
|
||||
Update On Sat Apr 6 20:26:05 CEST 2024
|
||||
Update On Sun Apr 7 20:25:26 CEST 2024
|
||||
|
||||
@@ -25,7 +25,7 @@ type Status struct {
|
||||
}
|
||||
|
||||
func openUrl(ctx context.Context, url string) (io.ReadCloser, error) {
|
||||
response, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"ClashMetaForAndroid/" + app.VersionName()}}, nil, "")
|
||||
response, err := clashHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {"ClashMetaForAndroid/" + app.VersionName()}}, nil)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -88,16 +88,13 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
vehicle = resource.NewFileVehicle(path)
|
||||
case "http":
|
||||
var path string
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
if schema.Path != "" {
|
||||
path = C.Path.Resolve(schema.Path)
|
||||
if !features.CMFA && !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
} else {
|
||||
path = C.Path.GetPathByHash("proxies", schema.URL)
|
||||
}
|
||||
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||
|
||||
@@ -125,7 +125,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
||||
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
|
||||
if userInfoStr == "" {
|
||||
resp2, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
||||
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, "")
|
||||
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func InitGeoSite() error {
|
||||
func downloadGeoSite(path string) (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func downloadGeoSite(path string) (err error) {
|
||||
func downloadGeoIP(path string) (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@ import (
|
||||
"github.com/metacubex/mihomo/listener/inner"
|
||||
)
|
||||
|
||||
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader, specialProxy string) (*http.Response, error) {
|
||||
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
|
||||
return HttpRequestWithProxy(ctx, url, method, header, body, "")
|
||||
}
|
||||
|
||||
func HttpRequestWithProxy(ctx context.Context, url, method string, header map[string][]string, body io.Reader, specialProxy string) (*http.Response, error) {
|
||||
method = strings.ToUpper(method)
|
||||
urlRes, err := URL.Parse(url)
|
||||
if err != nil {
|
||||
|
||||
@@ -82,7 +82,7 @@ func IPInstance() IPReader {
|
||||
func DownloadMMDB(path string) (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -115,7 +115,7 @@ func ASNInstance() ASNReader {
|
||||
func DownloadASN(path string) (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.ASNUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.ASNUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func (h *HTTPVehicle) Path() string {
|
||||
func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, h.url, http.MethodGet, h.header, nil, h.proxy)
|
||||
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, h.url, http.MethodGet, h.header, nil, h.proxy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
func downloadForBytes(url string) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ const MaxPackageFileSize = 32 * 1024 * 1024
|
||||
func downloadPackageFile() (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("http request failed: %w", err)
|
||||
}
|
||||
@@ -415,7 +415,7 @@ func copyFile(src, dst string) error {
|
||||
func getLatestVersion() (version string, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get Latest Version fail: %w", err)
|
||||
}
|
||||
|
||||
@@ -62,15 +62,12 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
vehicle = resource.NewFileVehicle(path)
|
||||
case "http":
|
||||
var path string
|
||||
path := C.Path.GetPathByHash("rules", schema.URL)
|
||||
if schema.Path != "" {
|
||||
path = C.Path.Resolve(schema.Path)
|
||||
if !features.CMFA && !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
} else {
|
||||
path = C.Path.GetPathByHash("rules", schema.URL)
|
||||
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, nil)
|
||||
default:
|
||||
|
||||
12
clash-nyanpasu/.github/workflows/release.yml
vendored
12
clash-nyanpasu/.github/workflows/release.yml
vendored
@@ -171,3 +171,15 @@ jobs:
|
||||
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}
|
||||
TELEGRAM_TO: "@keikolog"
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Post Tweet
|
||||
uses: rg-wood/send-tweet-action@v1
|
||||
with:
|
||||
status: |
|
||||
Clash Nyanpasu ${{ github.event.release.tag_name }} Released!
|
||||
|
||||
Download Link: https://github.com/LibNyanpasu/clash-nyanpasu/releases/tag/v${{ github.event.release.tag_name }}
|
||||
consumer-key: ${{ secrets.TWITTER_CONSUMER_KEY }}
|
||||
consumer-secret: ${{ secrets.TWITTER_CONSUMER_SECRET }}
|
||||
access-token: ${{ secrets.TWITTER_ACCESS_TOKEN }}
|
||||
access-token-secret: ${{ secrets.TWITTER_ACCESS_TOKEN_SECRET }}
|
||||
|
||||
@@ -14,6 +14,12 @@ A <a href="https://github.com/Dreamacro/clash">Clash</a> GUI based on <a href="h
|
||||
<a href="https://github.com/LibNyanpasu/clash-nyanpasu/releases/pre-release"><img src="https://img.shields.io/github/actions/workflow/status/LibNyanpasu/clash-nyanpasu/dev.yaml?style=flat-square" alt="Dev Build Status" /></a>
|
||||
<a href="https://github.com/LibNyanpasu/clash-nyanpasu/stargazers"><img src="https://img.shields.io/github/stars/LibNyanpasu/clash-nyanpasu?style=flat-square" alt="Nyanpasu stars" /></a>
|
||||
<a href="https://github.com/LibNyanpasu/clash-nyanpasu/releases/latest"><img src="https://img.shields.io/github/downloads/LibNyanpasu/clash-nyanpasu/total?style=flat-square" alt="GitHub Downloads (all assets, all releases)" /></a>
|
||||
<a href="https://github.com/LibNyanpasu/clash-nyanpasu/blob/main/LICENSE"><img src="https://img.shields.io/github/license/LibNyanpasu/clash-nyanpasu?style=flat-square" alt="Nyanpasu License" /></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://twitter.com/ClashNyanpasu"><img src="https://img.shields.io/twitter/follow/ClashNyanpasu
|
||||
?style=flat-square" alt="Nyanpasu Twitter" /></a>
|
||||
</p>
|
||||
|
||||
## Features
|
||||
@@ -80,6 +86,7 @@ Issue and PR welcome!
|
||||
Clash Nyanpasu was based on or inspired by these projects and so on:
|
||||
|
||||
- [zzzgydi/clash-verge](https://github.com/zzzgydi/clash-verge): A Clash GUI based on tauri. Supports Windows, macOS and Linux.
|
||||
- [clash-verge-rev/clash-verge-rev](https://github.com/clash-verge-rev/clash-verge-rev): Another fork of Clash Verge. Some patches are included for bug fixes.
|
||||
- [tauri-apps/tauri](https://github.com/tauri-apps/tauri): Build smaller, faster, and more secure desktop applications with a web frontend.
|
||||
- [Dreamacro/clash](https://github.com/Dreamacro/clash): A rule-based tunnel in Go.
|
||||
- [MetaCubeX/Clash.Meta](https://github.com/MetaCubeX/mihomo): A rule-based tunnel in Go.
|
||||
|
||||
@@ -384,31 +384,30 @@ pub fn get_custom_app_dir() -> CmdResult<Option<String>> {
|
||||
pub async fn set_custom_app_dir(app_handle: tauri::AppHandle, path: String) -> CmdResult {
|
||||
use crate::utils::{self, dialog::migrate_dialog, winreg::set_app_dir};
|
||||
use rust_i18n::t;
|
||||
use std::{path::PathBuf, time::Duration};
|
||||
use std::path::PathBuf;
|
||||
|
||||
let path_str = path.clone();
|
||||
let path = PathBuf::from(path);
|
||||
|
||||
// show a dialog to ask whether to migrate the data
|
||||
let res = tauri::async_runtime::spawn_blocking(move || {
|
||||
let res =
|
||||
tauri::async_runtime::spawn_blocking(move || {
|
||||
let msg = t!("dialog.custom_app_dir_migrate", path = path_str).to_string();
|
||||
|
||||
if migrate_dialog(&msg) {
|
||||
let app_exe = tauri::utils::platform::current_exe()?;
|
||||
let app_exe = dunce::canonicalize(app_exe)?.to_string_lossy().to_string();
|
||||
std::thread::spawn(move || {
|
||||
std::process::Command::new("powershell")
|
||||
.arg("-Command")
|
||||
.arg(
|
||||
format!(
|
||||
r#"Start-Process '{}' -ArgumentList 'migrate-home-dir','{}' -Verb runAs"#,
|
||||
r#"Start-Process '{}' -ArgumentList 'migrate-home-dir','"{}"' -Verb runAs"#,
|
||||
app_exe.as_str(),
|
||||
path_str.as_str()
|
||||
)
|
||||
.as_str(),
|
||||
).spawn().unwrap();
|
||||
utils::help::quit_application(&app_handle);
|
||||
});
|
||||
} else {
|
||||
set_app_dir(&path)?;
|
||||
}
|
||||
@@ -419,6 +418,12 @@ pub async fn set_custom_app_dir(app_handle: tauri::AppHandle, path: String) -> C
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn restart_application(app_handle: tauri::AppHandle) -> CmdResult {
|
||||
crate::utils::help::restart_application(&app_handle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[tauri::command]
|
||||
pub async fn set_custom_app_dir(_path: String) -> CmdResult {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Ok;
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use tauri::utils::platform::current_exe;
|
||||
|
||||
use crate::utils;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "clash-nyanpasu", version, about, long_about = None)]
|
||||
@@ -12,16 +17,52 @@ pub struct Cli {
|
||||
enum Commands {
|
||||
#[command(about = "Migrate home directory to another path.")]
|
||||
MigrateHomeDir { target_path: String },
|
||||
#[command(about = "A launch bridge to resolve the delay exit issue.")]
|
||||
Launch {
|
||||
// FIXME: why the raw arg is not working?
|
||||
#[arg(raw = true)]
|
||||
args: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
struct DelayedExitGuard;
|
||||
impl DelayedExitGuard {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
impl Drop for DelayedExitGuard {
|
||||
fn drop(&mut self) {
|
||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse() -> anyhow::Result<()> {
|
||||
let cli = Cli::parse();
|
||||
if let Some(commands) = &cli.command {
|
||||
let guard = DelayedExitGuard::new();
|
||||
match commands {
|
||||
Commands::MigrateHomeDir { target_path } => {
|
||||
self::handler::migrate_home_dir_handler(target_path).unwrap();
|
||||
}
|
||||
Commands::Launch { args } => {
|
||||
let _ = utils::init::check_singleton().unwrap();
|
||||
let appimage: Option<String> = {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
std::env::var_os("APPIMAGE").map(|s| s.to_string_lossy().to_string())
|
||||
}
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
None
|
||||
};
|
||||
let path = match appimage {
|
||||
Some(appimage) => std::path::PathBuf::from_str(&appimage).unwrap(),
|
||||
None => current_exe().unwrap(),
|
||||
};
|
||||
std::process::Command::new(path).args(args).spawn().unwrap();
|
||||
}
|
||||
}
|
||||
drop(guard);
|
||||
std::process::exit(0);
|
||||
}
|
||||
Ok(()) // bypass
|
||||
@@ -36,6 +77,7 @@ mod handler {
|
||||
use std::{path::PathBuf, process::Command, str::FromStr, thread, time::Duration};
|
||||
use sysinfo::System;
|
||||
use tauri::utils::platform::current_exe;
|
||||
println!("target path {}", target_path);
|
||||
|
||||
let token = Token::with_current_process()?;
|
||||
if let PrivilegeLevel::NotPrivileged = token.privilege_level()? {
|
||||
|
||||
@@ -28,3 +28,9 @@ impl Storage {
|
||||
rocksdb::DB::destroy(&rocksdb::Options::default(), &self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Storage {
|
||||
fn drop(&mut self) {
|
||||
self.destroy().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{cmds, config::Config, feat, utils, utils::resolve};
|
||||
use anyhow::Result;
|
||||
use rust_i18n::t;
|
||||
use tauri::{
|
||||
api, AppHandle, CustomMenuItem, Manager, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
|
||||
AppHandle, CustomMenuItem, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
|
||||
SystemTraySubmenu,
|
||||
};
|
||||
use tracing_attributes::instrument;
|
||||
@@ -151,7 +151,7 @@ impl Tray {
|
||||
"open_core_dir" => crate::log_err!(cmds::open_core_dir()),
|
||||
"open_logs_dir" => crate::log_err!(cmds::open_logs_dir()),
|
||||
"restart_clash" => feat::restart_clash_core(),
|
||||
"restart_app" => api::process::restart(&app_handle.env()),
|
||||
"restart_app" => utils::help::restart_application(app_handle),
|
||||
"quit" => {
|
||||
utils::help::quit_application(app_handle);
|
||||
}
|
||||
|
||||
@@ -13,9 +13,8 @@ mod utils;
|
||||
use crate::{
|
||||
config::Config,
|
||||
core::{commands, handle::Handle},
|
||||
utils::{dirs, init, resolve},
|
||||
utils::{init, resolve},
|
||||
};
|
||||
use anyhow::Context;
|
||||
use tauri::{api, Manager, SystemTray};
|
||||
|
||||
rust_i18n::i18n!("../../locales");
|
||||
@@ -54,16 +53,7 @@ fn main() -> std::io::Result<()> {
|
||||
tauri_plugin_deep_link::prepare("moe.elaina.clash.nyanpasu");
|
||||
|
||||
// 单例检测
|
||||
let placeholder = dirs::get_single_instance_placeholder();
|
||||
let single_instance_result: anyhow::Result<()> =
|
||||
single_instance::SingleInstance::new(&placeholder)
|
||||
.context("failed to create single instance")
|
||||
.map(|instance| {
|
||||
if !instance.is_single() {
|
||||
println!("app exists");
|
||||
std::process::exit(0);
|
||||
}
|
||||
});
|
||||
let single_instance_result = utils::init::check_singleton();
|
||||
|
||||
// Use system locale as default
|
||||
let locale = {
|
||||
@@ -86,7 +76,7 @@ fn main() -> std::io::Result<()> {
|
||||
rust_i18n::set_locale(verge.as_str());
|
||||
|
||||
// show a dialog to print the single instance error
|
||||
single_instance_result.unwrap();
|
||||
let _singleton = single_instance_result.unwrap(); // hold the guard until the end of the program
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = tauri::Builder::default()
|
||||
@@ -179,6 +169,7 @@ fn main() -> std::io::Result<()> {
|
||||
cmds::get_proxies,
|
||||
cmds::select_proxy,
|
||||
cmds::update_proxy_provider,
|
||||
cmds::restart_application,
|
||||
]);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -14,7 +14,10 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
use tauri::{
|
||||
api::shell::{open, Program},
|
||||
api::{
|
||||
process::current_binary,
|
||||
shell::{open, Program},
|
||||
},
|
||||
AppHandle, Manager,
|
||||
};
|
||||
use tracing::{debug, warn};
|
||||
@@ -216,14 +219,36 @@ pub fn get_max_scale_factor() -> f64 {
|
||||
}
|
||||
|
||||
#[instrument(skip(app_handle))]
|
||||
pub fn quit_application(app_handle: &AppHandle) {
|
||||
fn cleanup_processes(app_handle: &AppHandle) {
|
||||
let _ = super::resolve::save_window_state(app_handle, true);
|
||||
|
||||
super::resolve::resolve_reset();
|
||||
tauri::api::process::kill_children();
|
||||
}
|
||||
|
||||
#[instrument(skip(app_handle))]
|
||||
pub fn quit_application(app_handle: &AppHandle) {
|
||||
cleanup_processes(app_handle);
|
||||
app_handle.exit(0);
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[instrument(skip(app_handle))]
|
||||
pub fn restart_application(app_handle: &AppHandle) {
|
||||
cleanup_processes(app_handle);
|
||||
let env = app_handle.env();
|
||||
let path = current_binary(&env).unwrap();
|
||||
let arg = std::env::args().collect::<Vec<String>>();
|
||||
let mut args = vec!["launch".to_string(), "--".to_string()];
|
||||
// filter out the first arg
|
||||
if arg.len() > 1 {
|
||||
args.extend(arg.iter().skip(1).cloned());
|
||||
}
|
||||
tracing::info!("restart app: {:#?} with args: {:#?}", path, args);
|
||||
std::process::Command::new(path)
|
||||
.args(args)
|
||||
.spawn()
|
||||
.expect("application failed to start");
|
||||
app_handle.exit(0);
|
||||
// flush all data to disk
|
||||
crate::core::storage::Storage::global().destroy().unwrap();
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
config::*,
|
||||
utils::{dialog::migrate_dialog, dirs, help},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use fs_extra::dir::CopyOptions;
|
||||
use runas::Command as RunasCommand;
|
||||
use rust_i18n::t;
|
||||
@@ -196,6 +196,21 @@ pub fn init_service() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_singleton() -> Result<single_instance::SingleInstance> {
|
||||
let placeholder = super::dirs::get_single_instance_placeholder();
|
||||
for i in 0..5 {
|
||||
let instance = single_instance::SingleInstance::new(&placeholder)
|
||||
.context("failed to create single instance")?;
|
||||
if instance.is_single() {
|
||||
return Ok(instance);
|
||||
}
|
||||
if i != 4 {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
}
|
||||
anyhow::bail!("single instance check failed: app still exists after 4s");
|
||||
}
|
||||
|
||||
pub fn do_config_migration(old_app_dir: &PathBuf, app_dir: &PathBuf) -> anyhow::Result<()> {
|
||||
let copy_option = CopyOptions::new();
|
||||
let copy_option = copy_option.overwrite(true);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 1,
|
||||
"latest": {
|
||||
"mihomo": "v1.18.3",
|
||||
"mihomo_alpha": "alpha-5bae0b6",
|
||||
"mihomo_alpha": "alpha-c44949b",
|
||||
"clash_rs": "v0.1.15",
|
||||
"clash_premium": "2023-09-05-gdcc8d87"
|
||||
},
|
||||
@@ -36,5 +36,5 @@
|
||||
"darwin-x64": "clash-darwin-amd64-n{}.gz"
|
||||
}
|
||||
},
|
||||
"updated_at": "2024-04-05T22:19:27.268Z"
|
||||
"updated_at": "2024-04-06T22:19:13.513Z"
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
"react-markdown": "9.0.1",
|
||||
"react-router-dom": "6.22.3",
|
||||
"react-transition-group": "4.4.5",
|
||||
"react-virtuoso": "4.7.7",
|
||||
"react-virtuoso": "4.7.8",
|
||||
"recoil": "0.7.7",
|
||||
"swr": "2.2.5"
|
||||
},
|
||||
|
||||
8
clash-nyanpasu/pnpm-lock.yaml
generated
8
clash-nyanpasu/pnpm-lock.yaml
generated
@@ -93,8 +93,8 @@ dependencies:
|
||||
specifier: 4.4.5
|
||||
version: 4.4.5(react-dom@18.2.0)(react@18.2.0)
|
||||
react-virtuoso:
|
||||
specifier: 4.7.7
|
||||
version: 4.7.7(react-dom@18.2.0)(react@18.2.0)
|
||||
specifier: 4.7.8
|
||||
version: 4.7.8(react-dom@18.2.0)(react@18.2.0)
|
||||
recoil:
|
||||
specifier: 0.7.7
|
||||
version: 0.7.7(react-dom@18.2.0)(react@18.2.0)
|
||||
@@ -6251,8 +6251,8 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/react-virtuoso@4.7.7(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-n9NdMNaAtxHYH6e3H6zr1Kb08sp+1XPVnfE4cEMgrvmPBLugd9eeJtQo/1uA+SHhGaPX3uqZOuQsfKbX1r8P/A==}
|
||||
/react-virtuoso@4.7.8(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-P0BHOsLrmfnXv1bY9Nja07nvFciRGNgM7lTRHMcVDteTDb9tLtHzajBapKGUZ5zdyUOEVWvuW6ufQxzdGN2AKw==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: '>=16 || >=17 || >= 18'
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
openAppDir,
|
||||
openCoreDir,
|
||||
openLogsDir,
|
||||
restartApplication,
|
||||
setCustomAppDir,
|
||||
} from "@/services/cmds";
|
||||
import { sleep } from "@/utils";
|
||||
@@ -22,7 +23,6 @@ import {
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { open } from "@tauri-apps/api/dialog";
|
||||
import { relaunch } from "@tauri-apps/api/process";
|
||||
import { checkUpdate } from "@tauri-apps/api/updater";
|
||||
import { useAsyncEffect, useLockFn } from "ahooks";
|
||||
import { useRef, useState } from "react";
|
||||
@@ -123,7 +123,7 @@ const SettingVerge = ({ onError }: Props) => {
|
||||
body: t("App directory changed successfully"),
|
||||
});
|
||||
await sleep(1000);
|
||||
await relaunch();
|
||||
await restartApplication();
|
||||
} catch (err: any) {
|
||||
useMessage(err.message || err.toString(), {
|
||||
title: t("Error"),
|
||||
|
||||
@@ -264,3 +264,7 @@ export async function getCustomAppDir() {
|
||||
export async function setCustomAppDir(path: string) {
|
||||
return invoke<void>("set_custom_app_dir", { path });
|
||||
}
|
||||
|
||||
export async function restartApplication() {
|
||||
return invoke<void>("restart_application");
|
||||
}
|
||||
|
||||
9
echo/.github/workflows/cd.yaml
vendored
9
echo/.github/workflows/cd.yaml
vendored
@@ -166,9 +166,12 @@ jobs:
|
||||
echo "Creating manifest list..."
|
||||
AMD64_DIGEST=$(ls amd64/)
|
||||
ARM64_DIGEST=$(ls arm64/)
|
||||
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
|
||||
--append ${{ env.REGISTRY_IMAGE }}@sha256:$AMD64_DIGEST \
|
||||
--append ${{ env.REGISTRY_IMAGE }}@sha256:$ARM64_DIGEST
|
||||
echo "AMD64_DIGEST: $AMD64_DIGEST"
|
||||
echo "ARM64_DIGEST: $ARM64_DIGEST"
|
||||
docker buildx imagetools create \
|
||||
--tag ${{ env.REGISTRY_IMAGE }}:latest \
|
||||
${{ env.REGISTRY_IMAGE }}@sha256:$AMD64_DIGEST \
|
||||
${{ env.REGISTRY_IMAGE }}@sha256:$ARM64_DIGEST
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
|
||||
300
echo/README.md
300
echo/README.md
@@ -8,126 +8,11 @@
|
||||
|
||||
## Ehco Relay - 让流量转发更简单
|
||||
|
||||
我们很高兴地宣布,ehco 现在提供 SaaS(软件即服务)版本!这是一个全托管的解决方案,旨在为那些希望在不搭建和管理自己的服务器的情况下享受 ehco 强大流量转发能力的用户提供服务。
|
||||
ehco 现在提供 SaaS(软件即服务)版本!这是一个全托管的解决方案,旨在为那些希望在不搭建和管理自己的服务器的情况下享受 ehco 强大流量转发能力的用户提供服务。
|
||||
|
||||
- [Ehco Relay 官方网站](https://ehco-relay.cc)
|
||||
- [Ehco Relay 文档](https://docs.ehco-relay.cc/)
|
||||
|
||||
### 为什么选择 Ehco Relay?
|
||||
|
||||
- **即刻启动**:无需复杂的配置或服务器管理,立即获得高性能的流量转发服务。
|
||||
- **全面托管**:我们的团队会处理所有后端事务,包括维护、更新和故障排除,让您可以专注于您的主要业务。
|
||||
- **灵活的计划**:无论您是个人开发者还是大型企业,我们都提供多种计划来满足您的需求。
|
||||
- **安全可靠**:利用最先进的加密技术,确保您的数据传输安全无忧。
|
||||
|
||||
欢迎访问官网来了解更多信息,并开始您的免费试用。只需几个简单的步骤,您就可以轻松地设置并运行 ehco,享受无缝的流量转发服务。
|
||||
|
||||
## 使用场景
|
||||
|
||||
<details> <summary>连接内网服务</summary>
|
||||
|
||||
本地无法链接集群内的服务,可以通过 ehco 将本地流量转发到集群内,方便本地开发和调试
|
||||
|
||||
e.g. 本地开发调试连接内网服务 db, db host: xxx-rds.xxx.us-east-1.rds.amazonaws.com
|
||||
|
||||
1. 在 k8s 内 启动一个 ehco pod. 启动命令如下:
|
||||
`ehco -l 0.0.0.0:3306 -r xxx-rds.xxx.us-east-1.rds.amazonaws.com:3306`
|
||||
|
||||
2. 使用 kube port-forward 将本地的 3306 端口转发到 ehco pod 的 3306 端口
|
||||
`kubectl port-forward pod/ehco-pod 3306:3306`
|
||||
|
||||
3. 本地使用客户端连接
|
||||
`mysql -h 127.0.0.1:3306 -u root -p`
|
||||
</details>
|
||||
|
||||
<details> <summary>中转 proxy 客户端,提供负载均衡功能</summary>
|
||||
|
||||
从 **v1.1.4-dev(nightly)** 开始, ehco 支持了从 clash proxy provider 读取 proxy 配置并复写成 ehco 的 relay 配置
|
||||
从而实现了 ehco 作为代理客户端的前置代理,提供负载均衡,流量监控等功能
|
||||
|
||||
e.g.
|
||||
|
||||
1. 配置 ehco 的 config.json 并启动
|
||||
|
||||
```json
|
||||
{
|
||||
"web_host": "12.0.0.1",
|
||||
"web_port": 9000,
|
||||
"sub_configs": [
|
||||
{
|
||||
"name": "nas",
|
||||
"url": "your url"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`ehco -c config.json`
|
||||
|
||||
2. 访问 ehco 的 web 界面 获取 ehco 的 proxy provider url
|
||||
|
||||
`http://<web_host>:<web_port>`
|
||||
|
||||

|
||||
|
||||
ehco 会将每个 clash proxy provider 转换成两个新 clash provider
|
||||
|
||||
- 会将每个的 proxy 转换成一个 relay
|
||||
- 会将 proxy 按最长前缀**分组**,并将每个分组转换成开启负载均衡的 relay
|
||||
|
||||
举个例子
|
||||
|
||||
```yaml
|
||||
proxies:
|
||||
- name: us-1
|
||||
server: s1
|
||||
password:
|
||||
port: 1
|
||||
- name: us-2
|
||||
server: s2
|
||||
port: 2
|
||||
- name: jb-1
|
||||
server: s3
|
||||
password: pass
|
||||
port: 3
|
||||
```
|
||||
|
||||
上面这个包含 3 个 proxy 的会被转换成 5 个 relay:
|
||||
|
||||
- us-1 relay to s1:1
|
||||
- us-2 relay to s2:2
|
||||
- jb-1 relay to s3:3
|
||||
us-lb relay to s1:1,s2:2
|
||||
- jb-1-lb relay to s3:3
|
||||
|
||||
3. 将 ehco 的 proxy provider url 配置到 clash 的配置文件中
|
||||
|
||||
```yaml
|
||||
proxy-providers:
|
||||
ehco:
|
||||
type: http
|
||||
url: http://<web_host>:<web_port>/clash_proxy_provider/?sub_name=<name>
|
||||
ehco-lb:
|
||||
type: http
|
||||
url: http://<web_host>:<web_port>/clash_proxy_provider/?sub_name=name&grouped=true
|
||||
```
|
||||
|
||||
你就能得到一个支持负载均衡的 clash proxy client 了,并且还能在 dashboard 上看到流量监控哟
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details> <summary>WIP: 隧道连接到 proxy 集群</summary>
|
||||
</details>
|
||||
|
||||
## 安装
|
||||
|
||||
- ehco 提供预编译的的二进制 [release](https://github.com/Ehco1996/ehco/releases) 页面下载
|
||||
|
||||
- ehco 提供 [nightly build](https://github.com/Ehco1996/ehco/releases/tag/v0.0.0-nightly)
|
||||
|
||||
- ehco 提供 docker 镜像 `docker pull ehco1996/ehco`
|
||||
|
||||
## 主要功能
|
||||
|
||||
- tcp/udp relay
|
||||
@@ -135,185 +20,4 @@ proxy-providers:
|
||||
- proxy server (内嵌了完整班版本的 xray)
|
||||
- 监控报警 (prometheus/grafana)
|
||||
- WebAPI (http://web_host:web_port)
|
||||
|
||||
## 中转使用介绍
|
||||
|
||||
使用隧道需要至少两台主机, 并且在两台主机上都安装了 ehco
|
||||
|
||||
- 中转机器 A 假设机器 A 的 IP 是 1.1.1.1
|
||||
- 落地机器 B 假设机器 B 的 IP 是 2.2.2.2 并且落地机器 B 的 5555 端口跑着一个 SS/v2ray/任意 tcp/udp 服务
|
||||
|
||||
<details> <summary>案例一 不用隧道直接通过中转机器中转用户流量</summary>
|
||||
直接在中转机器 A 上输入: `ehco -l 0.0.0.0:1234 -r 2.2.2.2:5555`
|
||||
|
||||
> 该命令表示将所有从中转机器 A 的 1234 端口进入的流量直接转发到落地机器 B 的 5555 端口
|
||||
|
||||
用户即可通过 中转机器 A 的 1234 端口访问到落地机器 B 的 5555 端口的 SS/v2ray 服务了
|
||||
|
||||
</details>
|
||||
|
||||
<details> <summary>案例二 用 mwss 隧道中转用户流量</summary>
|
||||
在落地机器 B 上输入: `ehco -l 0.0.0.0:443 -lt mwss -r 127.0.0.1:5555`
|
||||
|
||||
> 该命令表示将所有从落地机器 B 的 443 端口进入的 wss 流量解密后转发到落地机器 B 的 5555 端口
|
||||
|
||||
在中转机器 A 上输入: `ehco -l 0.0.0.0:1234 -r wss://2.2.2.2:443 -tt mwss`
|
||||
|
||||
> 该命令表示将所有从 A 的 1234 端口进入的流量通过 wss 加密后转发到落地机器 B 的 443 端口
|
||||
|
||||
用户即可通过 中转机器 A 的 1234 端口访问到落地机器 B 的 5555 端口的 SS/v2ray 服务了
|
||||
|
||||
</details>
|
||||
|
||||
## 内嵌 Xray 功能介绍
|
||||
|
||||
<details> <summary>ehco 内的 xray 服务端</summary>
|
||||
从 `v1.1.2` 开始,ehco 内置了完整版本的 [xray](https://github.com/XTLS/Xray-core) 后端,可以通过标准的 xray 配置文件来启动内置的 xray server, 配置的 key 为 `xray_config`:
|
||||
|
||||
- 单端口多用户的 ss [xray_ss.json](examples/xray_ss.json)
|
||||
- 单端口多用户的 trojan [xray_trojan.json](examples/xray_trojan.json)
|
||||
</details>
|
||||
|
||||
<details> <summary>用户流量统计</summary>
|
||||
|
||||
从 `v1.1.2` 开始,ehco 支持通过 api 下方用户配置和上报用户流量,配置的 key 为 `sync_traffic_endpoint`:
|
||||
|
||||
ehco 会每隔 60s 发送一次 GET 请求,从 `sync_traffic_endpoint` 同步一次用户配置,到 xray server 里,期望的 API 返回格式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"users": [
|
||||
{
|
||||
"user_id": 1,
|
||||
"method": "user1",
|
||||
"password": 1024,
|
||||
"level": 1024,
|
||||
"upload_traffic": 1024,
|
||||
"download_traffic": 1024,
|
||||
"protocol": "trojan/ss"
|
||||
},
|
||||
{
|
||||
"user_id": 2,
|
||||
"method": "user1",
|
||||
"password": 1024,
|
||||
"level": 1024,
|
||||
"upload_traffic": 1024,
|
||||
"download_traffic": 1024,
|
||||
"protocol": "trojan/ss"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
ehco 会每隔 60s 发送一次 POST 请求至 `sync_traffic_endpoint` ,上报当前 xray server 所有用户的流量使用情况,发送的请求格式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"user_id": 1,
|
||||
"upload_traffic": 1024,
|
||||
"download_traffic": 1024
|
||||
},
|
||||
{
|
||||
"user_id": 2,
|
||||
"upload_traffic": 1024,
|
||||
"download_traffic": 1024
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
需要注意的是,如果想使用此功能,对 xray 的完整配置文件有如下限制
|
||||
|
||||
- 的配置文件必须包开启 `stats` 和 `api` 功能
|
||||
- ss inbound 的 `tag` 必须为 `ss_proxy`
|
||||
- trojan inbound 的 `tag` 必须为 `trojan_proxy`
|
||||
|
||||
一个完整的例子可以参考 [xray_ss.json](examples/xray_ss.json) 和 [xray_trojan.json](examples/xray_trojan.json)
|
||||
|
||||
</details>
|
||||
|
||||
## 配置文件格式
|
||||
|
||||
> ehco 支持从 `配置文件` / `http接口` 里读取 `json` 格式的配置并启动
|
||||
> (更多例子可以参考项目里的 [config.json](examples/config.json) 文件):
|
||||
|
||||
<details> <summary>热重载配置</summary>
|
||||
|
||||
- 大于 1.1.0 版本的 ehco 支持热重载配置
|
||||
- 通过 `kill -HUP pid` 信号来热重载配置
|
||||
- 通过配置 `reload_interval` 来指定配置文件的路径
|
||||
- 通过访问 POST `http://web_host:web_port/reload/` 接口来热重载配置
|
||||
</details>
|
||||
|
||||
## 监控报警
|
||||
|
||||
- dashboard 和 prometheus 规则可以从`monitor`文件夹下找到,可以自行导入
|
||||
|
||||
- 类似 Smoking Ping 的延迟监控
|
||||
|
||||

|
||||
|
||||
- 流量监控
|
||||
|
||||

|
||||
|
||||
## Benchmark(Apple m1)
|
||||
|
||||
iperf:
|
||||
|
||||
```sh
|
||||
# run iperf server on 5201
|
||||
iperf3 -s
|
||||
|
||||
# 直接转发
|
||||
# run relay server listen 1234 to 9001 (raw)
|
||||
go run cmd/ehco/main.go -l 0.0.0.0:1234 -r 0.0.0.0:5201
|
||||
|
||||
# 直接转发END
|
||||
|
||||
# 通过ws隧道转发
|
||||
# listen 1235 relay over ws to 1236
|
||||
go run cmd/ehco/main.go -l 0.0.0.0:1235 -r ws://0.0.0.0:1236 -tt ws
|
||||
|
||||
# listen 1236 through ws relay to 5201
|
||||
go run cmd/ehco/main.go -l 0.0.0.0:1236 -lt ws -r 0.0.0.0:5201
|
||||
# 通过ws隧道转发END
|
||||
|
||||
# 通过wss隧道转发
|
||||
# listen 1234 relay over wss to 1236
|
||||
go run cmd/ehco/main.go -l 0.0.0.0:1235 -r wss://0.0.0.0:1236 -tt wss
|
||||
|
||||
# listen 1236 through wss relay to 5201
|
||||
go run cmd/ehco/main.go -l 0.0.0.0:1236 -lt wss -r 0.0.0.0:5201
|
||||
# 通过wss隧道转发END
|
||||
|
||||
# 通过mwss隧道转发 和wss相比 速度会慢,但是能减少延迟
|
||||
# listen 1237 relay over mwss to 1238
|
||||
go run cmd/ehco/main.go -l 0.0.0.0:1237 -r wss://0.0.0.0:1238 -tt mwss
|
||||
|
||||
# listen 1238 through mwss relay to 5201
|
||||
go run cmd/ehco/main.go -l 0.0.0.0:1238 -lt mwss -r 0.0.0.0:5201
|
||||
# 通过mwss隧道转发END
|
||||
|
||||
# run through file
|
||||
go run cmd/ehco/main.go -c config.json
|
||||
|
||||
# benchmark tcp
|
||||
iperf3 -c 0.0.0.0 -p 1234
|
||||
|
||||
# benchmark tcp through wss
|
||||
iperf3 -c 0.0.0.0 -p 1235
|
||||
|
||||
# benchmark upd
|
||||
iperf3 -c 0.0.0.0 -p 1234 -u -b 1G --length 1024
|
||||
```
|
||||
|
||||
```
|
||||
| iperf | raw | relay(raw) | relay(ws) | relay(wss) | relay(mwss) | relay(mtcp) |
|
||||
| ----- | -------------- | ------------- | ------------ | ------------ | -------------- | -------------- |
|
||||
| tcp | 123 Gbits/sec | 55 Gbits/sec | 41 Gbits/sec | 10 Gbits/sec | 5.78 Gbits/sec | 22.2 Gbits/sec |
|
||||
| udp | 14.5 Gbits/sec | 3.3 Gbits/sec | 直接转发 | 直接转发 | 直接转发 | 直接转发 |
|
||||
|
||||
```
|
||||
- [更多功能请探索文档](https://docs.ehco-relay.cc/)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
cli "github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var cliLogger = log.MustNewLogger("info").Sugar().Named("cli-app")
|
||||
var cliLogger = log.MustNewLogger("info").Sugar().Named("cli")
|
||||
|
||||
func startAction(ctx *cli.Context) error {
|
||||
cfg, err := InitConfigAndComponents()
|
||||
|
||||
@@ -49,10 +49,10 @@ func loadConfig() (cfg *config.Config, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// init tls
|
||||
// init tls when need
|
||||
for _, cfg := range cfg.RelayConfigs {
|
||||
if cfg.ListenType == constant.Listen_WSS || cfg.ListenType == constant.Listen_MWSS ||
|
||||
cfg.TransportType == constant.Transport_WSS || cfg.TransportType == constant.Transport_MWSS {
|
||||
if cfg.ListenType == constant.RelayTypeWSS || cfg.ListenType == constant.RelayTypeMWSS ||
|
||||
cfg.TransportType == constant.RelayTypeWSS || cfg.TransportType == constant.RelayTypeMWSS {
|
||||
if err := tls.InitTlsCfg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -20,19 +20,16 @@ const (
|
||||
SmuxMaxAliveDuration = 10 * time.Minute
|
||||
SmuxMaxStreamCnt = 5
|
||||
|
||||
Listen_RAW = "raw"
|
||||
Listen_WS = "ws"
|
||||
Listen_WSS = "wss"
|
||||
Listen_MWSS = "mwss"
|
||||
Listen_MTCP = "mtcp"
|
||||
|
||||
Transport_RAW = "raw"
|
||||
Transport_WS = "ws"
|
||||
Transport_WSS = "wss"
|
||||
Transport_MWSS = "mwss"
|
||||
Transport_MTCP = "mtcp"
|
||||
|
||||
// todo add udp buffer size
|
||||
BUFFER_POOL_SIZE = 1024 // support 512 connections
|
||||
BUFFER_SIZE = 20 * 1024 // 20KB the maximum packet size of shadowsocks is about 16 KiB
|
||||
)
|
||||
|
||||
// relay type
|
||||
const (
|
||||
RelayTypeRaw = "raw"
|
||||
RelayTypeWS = "ws"
|
||||
RelayTypeWSS = "wss"
|
||||
RelayTypeMWSS = "mwss"
|
||||
RelayTypeMTCP = "mtcp"
|
||||
)
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Ehco1996/ehco/internal/constant"
|
||||
|
||||
"github.com/Ehco1996/ehco/pkg/lb"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
@@ -22,19 +24,19 @@ func (r *Config) Validate() error {
|
||||
if r.Adjust() != nil {
|
||||
return errors.New("adjust config failed")
|
||||
}
|
||||
if r.ListenType != constant.Listen_RAW &&
|
||||
r.ListenType != constant.Listen_WS &&
|
||||
r.ListenType != constant.Listen_WSS &&
|
||||
r.ListenType != constant.Listen_MTCP &&
|
||||
r.ListenType != constant.Listen_MWSS {
|
||||
if r.ListenType != constant.RelayTypeRaw &&
|
||||
r.ListenType != constant.RelayTypeWS &&
|
||||
r.ListenType != constant.RelayTypeWSS &&
|
||||
r.ListenType != constant.RelayTypeMTCP &&
|
||||
r.ListenType != constant.RelayTypeMWSS {
|
||||
return fmt.Errorf("invalid listen type:%s", r.ListenType)
|
||||
}
|
||||
|
||||
if r.TransportType != constant.Transport_RAW &&
|
||||
r.TransportType != constant.Transport_WS &&
|
||||
r.TransportType != constant.Transport_WSS &&
|
||||
r.TransportType != constant.Transport_MTCP &&
|
||||
r.TransportType != constant.Transport_MWSS {
|
||||
if r.TransportType != constant.RelayTypeRaw &&
|
||||
r.TransportType != constant.RelayTypeWS &&
|
||||
r.TransportType != constant.RelayTypeWSS &&
|
||||
r.TransportType != constant.RelayTypeMTCP &&
|
||||
r.TransportType != constant.RelayTypeMWSS {
|
||||
return fmt.Errorf("invalid transport type:%s", r.ListenType)
|
||||
}
|
||||
|
||||
@@ -106,16 +108,31 @@ func (r *Config) Different(new *Config) bool {
|
||||
}
|
||||
|
||||
// todo make this shorter and more readable
|
||||
func (r *Config) defaultLabel() string {
|
||||
defaultLabel := fmt.Sprintf("<At=%s Over=%s TCP-To=%s UDP-To=%s Through=%s>",
|
||||
r.Listen, r.ListenType, r.TCPRemotes, r.UDPRemotes, r.TransportType)
|
||||
func (r *Config) DefaultLabel() string {
|
||||
defaultLabel := fmt.Sprintf("<At=%s TCP-To=%s TP=%s>",
|
||||
r.Listen, r.TCPRemotes, r.TransportType)
|
||||
return defaultLabel
|
||||
}
|
||||
|
||||
func (r *Config) Adjust() error {
|
||||
if r.Label == "" {
|
||||
r.Label = r.defaultLabel()
|
||||
r.Label = r.DefaultLabel()
|
||||
zap.S().Debugf("label is empty, set default label:%s", r.Label)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Config) ToTCPRemotes() lb.RoundRobin {
|
||||
tcpNodeList := make([]*lb.Node, len(r.TCPRemotes))
|
||||
for idx, addr := range r.TCPRemotes {
|
||||
tcpNodeList[idx] = &lb.Node{
|
||||
Address: addr,
|
||||
Label: fmt.Sprintf("%s-%s", r.Label, addr),
|
||||
}
|
||||
}
|
||||
return lb.NewRoundRobin(tcpNodeList)
|
||||
}
|
||||
|
||||
func (r *Config) GetLoggerName() string {
|
||||
return fmt.Sprintf("%s(%s<->%s)", r.Label, r.ListenType, r.TransportType)
|
||||
}
|
||||
|
||||
@@ -1,138 +1,50 @@
|
||||
package relay
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/Ehco1996/ehco/internal/cmgr"
|
||||
"github.com/Ehco1996/ehco/internal/constant"
|
||||
"github.com/Ehco1996/ehco/internal/relay/conf"
|
||||
"github.com/Ehco1996/ehco/internal/transporter"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
Name string // unique name for all relay
|
||||
TransportType string
|
||||
ListenType string
|
||||
|
||||
TP transporter.RelayTransporter
|
||||
|
||||
LocalTCPAddr *net.TCPAddr
|
||||
closeTcpF func() error
|
||||
|
||||
cfg *conf.Config
|
||||
l *zap.SugaredLogger
|
||||
|
||||
relayServer transporter.RelayServer
|
||||
}
|
||||
|
||||
func (r *Relay) UniqueID() string {
|
||||
return r.cfg.Label
|
||||
}
|
||||
|
||||
func NewRelay(cfg *conf.Config, connMgr cmgr.Cmgr) (*Relay, error) {
|
||||
localTCPAddr, err := net.ResolveTCPAddr("tcp", cfg.Listen)
|
||||
base := transporter.NewBaseTransporter(cfg, connMgr)
|
||||
s, err := transporter.NewRelayServer(cfg.ListenType, base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &Relay{
|
||||
relayServer: s,
|
||||
cfg: cfg,
|
||||
l: zap.S().Named("relay"),
|
||||
|
||||
Name: cfg.Label,
|
||||
LocalTCPAddr: localTCPAddr,
|
||||
ListenType: cfg.ListenType,
|
||||
TransportType: cfg.TransportType,
|
||||
TP: transporter.NewRelayTransporter(cfg, connMgr),
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *Relay) ListenAndServe() error {
|
||||
errCh := make(chan error)
|
||||
|
||||
if len(r.cfg.TCPRemotes) > 0 {
|
||||
switch r.ListenType {
|
||||
case constant.Listen_RAW:
|
||||
go func() {
|
||||
errCh <- r.RunLocalTCPServer()
|
||||
r.l.Infof("Start TCP Relay Server:%s", r.cfg.DefaultLabel())
|
||||
errCh <- r.relayServer.ListenAndServe()
|
||||
}()
|
||||
case constant.Listen_MTCP:
|
||||
go func() {
|
||||
errCh <- r.RunLocalMTCPServer()
|
||||
}()
|
||||
case constant.Listen_WS:
|
||||
go func() {
|
||||
errCh <- r.RunLocalWSServer()
|
||||
}()
|
||||
case constant.Listen_WSS:
|
||||
go func() {
|
||||
errCh <- r.RunLocalWSSServer()
|
||||
}()
|
||||
case constant.Listen_MWSS:
|
||||
go func() {
|
||||
errCh <- r.RunLocalMWSSServer()
|
||||
}()
|
||||
}
|
||||
}
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func (r *Relay) Close() {
|
||||
r.l.Infof("Close relay label: %s", r.Name)
|
||||
if r.closeTcpF != nil {
|
||||
err := r.closeTcpF()
|
||||
if err != nil {
|
||||
r.l.Infof("Close TCP Relay Server:%s", r.cfg.DefaultLabel())
|
||||
if err := r.relayServer.Close(); err != nil {
|
||||
r.l.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Relay) RunLocalTCPServer() error {
|
||||
rawServer, err := transporter.NewRawServer(r.LocalTCPAddr.String(), r.TP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.closeTcpF = func() error {
|
||||
return rawServer.Close()
|
||||
}
|
||||
r.l.Infof("Start TCP relay Server: %s", r.Name)
|
||||
return rawServer.ListenAndServe()
|
||||
}
|
||||
|
||||
func (r *Relay) RunLocalMTCPServer() error {
|
||||
tp := r.TP.(*transporter.RawClient)
|
||||
mTCPServer := transporter.NewMTCPServer(r.LocalTCPAddr.String(), tp, r.l.Named("MTCPServer"))
|
||||
r.closeTcpF = func() error {
|
||||
return mTCPServer.Close()
|
||||
}
|
||||
r.l.Infof("Start MTCP relay Server: %s", r.Name)
|
||||
return mTCPServer.ListenAndServe()
|
||||
}
|
||||
|
||||
func (r *Relay) RunLocalWSServer() error {
|
||||
tp := r.TP.(*transporter.RawClient)
|
||||
wsServer := transporter.NewWSServer(r.LocalTCPAddr.String(), tp, r.l.Named("WSServer"))
|
||||
r.closeTcpF = func() error {
|
||||
return wsServer.Close()
|
||||
}
|
||||
r.l.Infof("Start WS relay Server: %s", r.Name)
|
||||
return wsServer.ListenAndServe()
|
||||
}
|
||||
|
||||
func (r *Relay) RunLocalWSSServer() error {
|
||||
tp := r.TP.(*transporter.RawClient)
|
||||
wssServer := transporter.NewWSSServer(r.LocalTCPAddr.String(), tp, r.l.Named("WSSServer"))
|
||||
r.closeTcpF = func() error {
|
||||
return wssServer.Close()
|
||||
}
|
||||
r.l.Infof("Start WSS relay Server: %s", r.Name)
|
||||
return wssServer.ListenAndServe()
|
||||
}
|
||||
|
||||
func (r *Relay) RunLocalMWSSServer() error {
|
||||
tp := r.TP.(*transporter.RawClient)
|
||||
mwssServer := transporter.NewMWSSServer(r.LocalTCPAddr.String(), tp, r.l.Named("MWSSServer"))
|
||||
r.closeTcpF = func() error {
|
||||
return mwssServer.Close()
|
||||
}
|
||||
r.l.Infof("Start MWSS relay Server: %s", r.Name)
|
||||
return mwssServer.ListenAndServe()
|
||||
}
|
||||
|
||||
@@ -46,18 +46,18 @@ func NewServer(cfg *config.Config) (*Server, error) {
|
||||
}
|
||||
|
||||
func (s *Server) startOneRelay(r *Relay) {
|
||||
s.relayM.Store(r.Name, r)
|
||||
s.relayM.Store(r.UniqueID(), r)
|
||||
// mute closed network error for tcp server and mute http.ErrServerClosed for http server when config reload
|
||||
if err := r.ListenAndServe(); err != nil &&
|
||||
!errors.Is(err, net.ErrClosed) && !errors.Is(err, http.ErrServerClosed) {
|
||||
s.l.Errorf("start relay %s meet error: %s", r.Name, err)
|
||||
s.l.Errorf("start relay %s meet error: %s", r.UniqueID(), err)
|
||||
s.errCH <- err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) stopOneRelay(r *Relay) {
|
||||
r.Close()
|
||||
s.relayM.Delete(r.Name)
|
||||
s.relayM.Delete(r.UniqueID())
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context) error {
|
||||
|
||||
55
echo/internal/transporter/base.go
Normal file
55
echo/internal/transporter/base.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package transporter
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/Ehco1996/ehco/internal/cmgr"
|
||||
"github.com/Ehco1996/ehco/internal/conn"
|
||||
"github.com/Ehco1996/ehco/internal/metrics"
|
||||
"github.com/Ehco1996/ehco/internal/relay/conf"
|
||||
|
||||
"github.com/Ehco1996/ehco/pkg/lb"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type baseTransporter struct {
|
||||
cmgr cmgr.Cmgr
|
||||
cfg *conf.Config
|
||||
tCPRemotes lb.RoundRobin
|
||||
l *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func NewBaseTransporter(cfg *conf.Config, cmgr cmgr.Cmgr) *baseTransporter {
|
||||
return &baseTransporter{
|
||||
cfg: cfg,
|
||||
cmgr: cmgr,
|
||||
tCPRemotes: cfg.ToTCPRemotes(),
|
||||
l: zap.S().Named(cfg.GetLoggerName()),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *baseTransporter) GetTCPListenAddr() (*net.TCPAddr, error) {
|
||||
return net.ResolveTCPAddr("tcp", b.cfg.Listen)
|
||||
}
|
||||
|
||||
func (b *baseTransporter) GetRemote() *lb.Node {
|
||||
return b.tCPRemotes.Next()
|
||||
}
|
||||
|
||||
func (b *baseTransporter) RelayTCPConn(c net.Conn, handshakeF TCPHandShakeF) error {
|
||||
remote := b.GetRemote()
|
||||
metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Inc()
|
||||
defer metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Dec()
|
||||
|
||||
clonedRemote := remote.Clone()
|
||||
rc, err := handshakeF(clonedRemote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.l.Infof("RelayTCPConn from %s to %s", c.LocalAddr(), remote.Address)
|
||||
relayConn := conn.NewRelayConn(
|
||||
b.cfg.Label, c, rc, conn.WithHandshakeDuration(clonedRemote.HandShakeDuration))
|
||||
b.cmgr.AddConnection(relayConn)
|
||||
defer b.cmgr.RemoveConnection(relayConn)
|
||||
return relayConn.Transport(remote.Label)
|
||||
}
|
||||
@@ -1,42 +1,54 @@
|
||||
package transporter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/Ehco1996/ehco/internal/cmgr"
|
||||
"github.com/Ehco1996/ehco/internal/constant"
|
||||
"github.com/Ehco1996/ehco/internal/relay/conf"
|
||||
"github.com/Ehco1996/ehco/pkg/lb"
|
||||
)
|
||||
|
||||
// RelayTransporter
|
||||
type RelayTransporter interface {
|
||||
dialRemote(remote *lb.Node) (net.Conn, error)
|
||||
HandleTCPConn(c net.Conn, remote *lb.Node) error
|
||||
GetRemote() *lb.Node
|
||||
type TCPHandShakeF func(remote *lb.Node) (net.Conn, error)
|
||||
|
||||
type RelayClient interface {
|
||||
TCPHandShake(remote *lb.Node) (net.Conn, error)
|
||||
RelayTCPConn(c net.Conn, handshakeF TCPHandShakeF) error
|
||||
}
|
||||
|
||||
func NewRelayTransporter(cfg *conf.Config, connMgr cmgr.Cmgr) RelayTransporter {
|
||||
tcpNodeList := make([]*lb.Node, len(cfg.TCPRemotes))
|
||||
for idx, addr := range cfg.TCPRemotes {
|
||||
tcpNodeList[idx] = &lb.Node{
|
||||
Address: addr,
|
||||
Label: fmt.Sprintf("%s-%s", cfg.Label, addr),
|
||||
func NewRelayClient(relayType string, base *baseTransporter) (RelayClient, error) {
|
||||
switch relayType {
|
||||
case constant.RelayTypeRaw:
|
||||
return newRawClient(base)
|
||||
case constant.RelayTypeWS:
|
||||
return newWsClient(base)
|
||||
case constant.RelayTypeWSS:
|
||||
return newWssClient(base)
|
||||
case constant.RelayTypeMWSS:
|
||||
return newMwssClient(base)
|
||||
case constant.RelayTypeMTCP:
|
||||
return newMtcpClient(base)
|
||||
default:
|
||||
panic("unsupported transport type")
|
||||
}
|
||||
}
|
||||
raw := newRawClient(cfg.Label, lb.NewRoundRobin(tcpNodeList), connMgr)
|
||||
switch cfg.TransportType {
|
||||
case constant.Transport_RAW:
|
||||
return raw
|
||||
case constant.Transport_WS:
|
||||
return newWsClient(raw)
|
||||
case constant.Transport_WSS:
|
||||
return newWSSClient(raw)
|
||||
case constant.Transport_MWSS:
|
||||
return newMWSSClient(raw)
|
||||
case constant.Transport_MTCP:
|
||||
return newMTCPClient(raw)
|
||||
|
||||
type RelayServer interface {
|
||||
ListenAndServe() error
|
||||
Close() error
|
||||
}
|
||||
|
||||
func NewRelayServer(relayType string, base *baseTransporter) (RelayServer, error) {
|
||||
switch relayType {
|
||||
case constant.RelayTypeRaw:
|
||||
return newRawServer(base)
|
||||
case constant.RelayTypeWS:
|
||||
return newWsServer(base)
|
||||
case constant.RelayTypeWSS:
|
||||
return newWssServer(base)
|
||||
case constant.RelayTypeMWSS:
|
||||
return newMwssServer(base)
|
||||
case constant.RelayTypeMTCP:
|
||||
return newMtcpServer(base)
|
||||
default:
|
||||
panic("unsupported transport type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -136,3 +136,66 @@ func (tr *smuxTransporter) Dial(ctx context.Context, addr string) (conn net.Conn
|
||||
curSM.streamList = append(curSM.streamList, stream)
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
type muxServer interface {
|
||||
ListenAndServe() error
|
||||
Accept() (net.Conn, error)
|
||||
Close() error
|
||||
mux(net.Conn)
|
||||
}
|
||||
|
||||
func newMuxServer(listenAddr string, l *zap.SugaredLogger) *muxServerImpl {
|
||||
return &muxServerImpl{
|
||||
errChan: make(chan error, 1),
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
listenAddr: listenAddr,
|
||||
l: l,
|
||||
}
|
||||
}
|
||||
|
||||
type muxServerImpl struct {
|
||||
errChan chan error
|
||||
connChan chan net.Conn
|
||||
|
||||
listenAddr string
|
||||
l *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func (s *muxServerImpl) Accept() (net.Conn, error) {
|
||||
select {
|
||||
case conn := <-s.connChan:
|
||||
return conn, nil
|
||||
case err := <-s.errChan:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *muxServerImpl) mux(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
cfg := smux.DefaultConfig()
|
||||
cfg.KeepAliveDisabled = true
|
||||
session, err := smux.Server(conn, cfg)
|
||||
if err != nil {
|
||||
s.l.Debugf("server err %s - %s : %s", conn.RemoteAddr(), s.listenAddr, err)
|
||||
return
|
||||
}
|
||||
defer session.Close() // nolint: errcheck
|
||||
|
||||
s.l.Debugf("session init %s %s", conn.RemoteAddr(), s.listenAddr)
|
||||
defer s.l.Debugf("session close %s >-< %s", conn.RemoteAddr(), s.listenAddr)
|
||||
|
||||
for {
|
||||
stream, err := session.AcceptStream()
|
||||
if err != nil {
|
||||
s.l.Errorf("accept stream err: %s", err)
|
||||
break
|
||||
}
|
||||
select {
|
||||
case s.connChan <- stream:
|
||||
default:
|
||||
stream.Close() // nolint: errcheck
|
||||
s.l.Infof("%s - %s: connection queue is full", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,40 +5,33 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/Ehco1996/ehco/internal/cmgr"
|
||||
"github.com/Ehco1996/ehco/internal/conn"
|
||||
"github.com/Ehco1996/ehco/internal/constant"
|
||||
"github.com/Ehco1996/ehco/internal/metrics"
|
||||
"github.com/Ehco1996/ehco/pkg/lb"
|
||||
)
|
||||
|
||||
var (
|
||||
_ RelayClient = &RawClient{}
|
||||
_ RelayServer = &RawServer{}
|
||||
)
|
||||
|
||||
type RawClient struct {
|
||||
relayLabel string
|
||||
cmgr cmgr.Cmgr
|
||||
tCPRemotes lb.RoundRobin
|
||||
l *zap.SugaredLogger
|
||||
*baseTransporter
|
||||
|
||||
dialer *net.Dialer
|
||||
}
|
||||
|
||||
func newRawClient(relayLabel string, tcpRemotes lb.RoundRobin, cmgr cmgr.Cmgr) *RawClient {
|
||||
func newRawClient(base *baseTransporter) (*RawClient, error) {
|
||||
r := &RawClient{
|
||||
cmgr: cmgr,
|
||||
relayLabel: relayLabel,
|
||||
tCPRemotes: tcpRemotes,
|
||||
l: zap.S().Named(relayLabel),
|
||||
baseTransporter: base,
|
||||
dialer: &net.Dialer{Timeout: constant.DialTimeOut},
|
||||
}
|
||||
return r
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (raw *RawClient) GetRemote() *lb.Node {
|
||||
return raw.tCPRemotes.Next()
|
||||
}
|
||||
|
||||
func (raw *RawClient) dialRemote(remote *lb.Node) (net.Conn, error) {
|
||||
func (raw *RawClient) TCPHandShake(remote *lb.Node) (net.Conn, error) {
|
||||
t1 := time.Now()
|
||||
d := net.Dialer{Timeout: constant.DialTimeOut}
|
||||
rc, err := d.Dial("tcp", remote.Address)
|
||||
rc, err := raw.dialer.Dial("tcp", remote.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -48,38 +41,32 @@ func (raw *RawClient) dialRemote(remote *lb.Node) (net.Conn, error) {
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (raw *RawClient) HandleTCPConn(c net.Conn, remote *lb.Node) error {
|
||||
metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Inc()
|
||||
defer metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Dec()
|
||||
|
||||
clonedRemote := remote.Clone()
|
||||
rc, err := raw.dialRemote(clonedRemote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
raw.l.Infof("HandleTCPConn from %s to %s", c.LocalAddr(), remote.Address)
|
||||
relayConn := conn.NewRelayConn(raw.relayLabel, c, rc, conn.WithHandshakeDuration(clonedRemote.HandShakeDuration))
|
||||
raw.cmgr.AddConnection(relayConn)
|
||||
defer raw.cmgr.RemoveConnection(relayConn)
|
||||
return relayConn.Transport(remote.Label)
|
||||
}
|
||||
|
||||
type RawServer struct {
|
||||
rtp RelayTransporter
|
||||
*baseTransporter
|
||||
localTCPAddr *net.TCPAddr
|
||||
lis *net.TCPListener
|
||||
l *zap.SugaredLogger
|
||||
relayer RelayClient
|
||||
}
|
||||
|
||||
func NewRawServer(addr string, rtp RelayTransporter) (*RawServer, error) {
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
func newRawServer(base *baseTransporter) (*RawServer, error) {
|
||||
addr, err := base.GetTCPListenAddr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lis, err := net.ListenTCP("tcp", tcpAddr)
|
||||
lis, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &RawServer{lis: lis, rtp: rtp}, nil
|
||||
relayer, err := NewRelayClient(base.cfg.TransportType, base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &RawServer{
|
||||
lis: lis,
|
||||
baseTransporter: base,
|
||||
localTCPAddr: addr,
|
||||
relayer: relayer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *RawServer) Close() error {
|
||||
@@ -93,13 +80,8 @@ func (s *RawServer) ListenAndServe() error {
|
||||
return err
|
||||
}
|
||||
go func(c net.Conn) {
|
||||
remote := s.rtp.GetRemote()
|
||||
metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Inc()
|
||||
defer metrics.CurConnectionCount.WithLabelValues(remote.Label, metrics.METRIC_CONN_TYPE_TCP).Dec()
|
||||
if err := s.rtp.HandleTCPConn(c, remote); err != nil {
|
||||
s.l.Errorf("HandleTCPConn meet error tp:%s from:%s to:%s err:%s",
|
||||
s.rtp,
|
||||
c.RemoteAddr(), remote.Address, err)
|
||||
if err := s.RelayTCPConn(c, s.relayer.TCPHandShake); err != nil {
|
||||
s.l.Errorf("RelayTCPConn error: %s", err.Error())
|
||||
}
|
||||
}(c)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// nolint: errcheck
|
||||
package transporter
|
||||
|
||||
import (
|
||||
@@ -7,29 +6,32 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/xtaci/smux"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/Ehco1996/ehco/internal/conn"
|
||||
"github.com/Ehco1996/ehco/internal/constant"
|
||||
"github.com/Ehco1996/ehco/internal/metrics"
|
||||
"github.com/Ehco1996/ehco/pkg/lb"
|
||||
)
|
||||
|
||||
type MTCPClient struct {
|
||||
var (
|
||||
_ RelayClient = &MtcpClient{}
|
||||
_ RelayServer = &MtcpServer{}
|
||||
)
|
||||
|
||||
type MtcpClient struct {
|
||||
*RawClient
|
||||
dialer *net.Dialer
|
||||
mtp *smuxTransporter
|
||||
muxTP *smuxTransporter
|
||||
}
|
||||
|
||||
func newMTCPClient(raw *RawClient) *MTCPClient {
|
||||
dialer := &net.Dialer{Timeout: constant.DialTimeOut}
|
||||
c := &MTCPClient{dialer: dialer, RawClient: raw}
|
||||
mtp := NewSmuxTransporter(raw.l.Named("mtcp"), c.initNewSession)
|
||||
c.mtp = mtp
|
||||
return c
|
||||
func newMtcpClient(base *baseTransporter) (*MtcpClient, error) {
|
||||
raw, err := newRawClient(base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &MtcpClient{RawClient: raw}
|
||||
c.muxTP = NewSmuxTransporter(raw.l.Named("mtcp"), c.initNewSession)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *MTCPClient) initNewSession(ctx context.Context, addr string) (*smux.Session, error) {
|
||||
func (c *MtcpClient) initNewSession(ctx context.Context, addr string) (*smux.Session, error) {
|
||||
rc, err := c.dialer.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -45,9 +47,9 @@ func (c *MTCPClient) initNewSession(ctx context.Context, addr string) (*smux.Ses
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (s *MTCPClient) dialRemote(remote *lb.Node) (net.Conn, error) {
|
||||
func (s *MtcpClient) TCPHandShake(remote *lb.Node) (net.Conn, error) {
|
||||
t1 := time.Now()
|
||||
mtcpc, err := s.mtp.Dial(context.TODO(), remote.Address)
|
||||
mtcpc, err := s.muxTP.Dial(context.TODO(), remote.Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -57,87 +59,28 @@ func (s *MTCPClient) dialRemote(remote *lb.Node) (net.Conn, error) {
|
||||
return mtcpc, nil
|
||||
}
|
||||
|
||||
func (s *MTCPClient) HandleTCPConn(c net.Conn, remote *lb.Node) error {
|
||||
clonedRemote := remote.Clone()
|
||||
mtcpc, err := s.dialRemote(clonedRemote)
|
||||
type MtcpServer struct {
|
||||
*RawServer
|
||||
*muxServerImpl
|
||||
}
|
||||
|
||||
func newMtcpServer(base *baseTransporter) (*MtcpServer, error) {
|
||||
raw, err := newRawServer(base)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
s.l.Infof("HandleTCPConn from:%s to:%s", c.LocalAddr(), remote.Address)
|
||||
relayConn := conn.NewRelayConn(s.relayLabel, c, mtcpc, conn.WithHandshakeDuration(clonedRemote.HandShakeDuration))
|
||||
s.cmgr.AddConnection(relayConn)
|
||||
defer s.cmgr.RemoveConnection(relayConn)
|
||||
return relayConn.Transport(remote.Label)
|
||||
s := &MtcpServer{
|
||||
RawServer: raw,
|
||||
muxServerImpl: newMuxServer(base.cfg.Listen, base.l.Named("mtcp")),
|
||||
}
|
||||
|
||||
type MTCPServer struct {
|
||||
raw *RawClient
|
||||
listenAddr string
|
||||
listener net.Listener
|
||||
l *zap.SugaredLogger
|
||||
|
||||
errChan chan error
|
||||
connChan chan net.Conn
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func NewMTCPServer(listenAddr string, raw *RawClient, l *zap.SugaredLogger) *MTCPServer {
|
||||
return &MTCPServer{
|
||||
l: l,
|
||||
raw: raw,
|
||||
listenAddr: listenAddr,
|
||||
errChan: make(chan error, 1),
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MTCPServer) mux(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
cfg := smux.DefaultConfig()
|
||||
cfg.KeepAliveDisabled = true
|
||||
session, err := smux.Server(conn, cfg)
|
||||
if err != nil {
|
||||
s.l.Debugf("server err %s - %s : %s", conn.RemoteAddr(), s.listenAddr, err)
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
s.l.Debugf("session init %s %s", conn.RemoteAddr(), s.listenAddr)
|
||||
defer s.l.Debugf("session close %s >-< %s", conn.RemoteAddr(), s.listenAddr)
|
||||
|
||||
for {
|
||||
stream, err := session.AcceptStream()
|
||||
if err != nil {
|
||||
s.l.Errorf("accept stream err: %s", err)
|
||||
break
|
||||
}
|
||||
select {
|
||||
case s.connChan <- stream:
|
||||
default:
|
||||
stream.Close()
|
||||
s.l.Infof("%s - %s: connection queue is full", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MTCPServer) Accept() (conn net.Conn, err error) {
|
||||
select {
|
||||
case conn = <-s.connChan:
|
||||
case err = <-s.errChan:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *MTCPServer) ListenAndServe() error {
|
||||
lis, err := net.Listen("tcp", s.listenAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.listener = lis
|
||||
|
||||
func (s *MtcpServer) ListenAndServe() error {
|
||||
go func() {
|
||||
for {
|
||||
c, err := lis.Accept()
|
||||
c, err := s.lis.Accept()
|
||||
if err != nil {
|
||||
s.errChan <- err
|
||||
continue
|
||||
@@ -152,14 +95,13 @@ func (s *MTCPServer) ListenAndServe() error {
|
||||
return e
|
||||
}
|
||||
go func(c net.Conn) {
|
||||
remote := s.raw.GetRemote()
|
||||
if err := s.raw.HandleTCPConn(c, remote); err != nil {
|
||||
s.l.Errorf("HandleTCPConn meet error from:%s to:%s err:%s", c.RemoteAddr(), remote.Address, err)
|
||||
if err := s.RelayTCPConn(c, s.relayer.TCPHandShake); err != nil {
|
||||
s.l.Errorf("RelayTCPConn error: %s", err.Error())
|
||||
}
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MTCPServer) Close() error {
|
||||
return s.listener.Close()
|
||||
func (s *MtcpServer) Close() error {
|
||||
return s.lis.Close()
|
||||
}
|
||||
|
||||
@@ -8,27 +8,35 @@ import (
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/labstack/echo/v4"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/Ehco1996/ehco/internal/conn"
|
||||
"github.com/Ehco1996/ehco/internal/constant"
|
||||
"github.com/Ehco1996/ehco/internal/metrics"
|
||||
"github.com/Ehco1996/ehco/internal/web"
|
||||
"github.com/Ehco1996/ehco/pkg/lb"
|
||||
)
|
||||
|
||||
var (
|
||||
_ RelayClient = &WsClient{}
|
||||
_ RelayServer = &WsServer{}
|
||||
)
|
||||
|
||||
type WsClient struct {
|
||||
*RawClient
|
||||
*baseTransporter
|
||||
|
||||
dialer *ws.Dialer
|
||||
}
|
||||
|
||||
func newWsClient(raw *RawClient) *WsClient {
|
||||
return &WsClient{RawClient: raw}
|
||||
func newWsClient(base *baseTransporter) (*WsClient, error) {
|
||||
s := &WsClient{
|
||||
baseTransporter: base,
|
||||
dialer: &ws.Dialer{Timeout: constant.DialTimeOut},
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *WsClient) dialRemote(remote *lb.Node) (net.Conn, error) {
|
||||
func (s *WsClient) TCPHandShake(remote *lb.Node) (net.Conn, error) {
|
||||
t1 := time.Now()
|
||||
d := ws.Dialer{Timeout: constant.DialTimeOut}
|
||||
wsc, _, _, err := d.Dial(context.TODO(), remote.Address+"/handshake/")
|
||||
wsc, _, _, err := s.dialer.Dial(context.TODO(), remote.Address+"/handshake/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,57 +46,51 @@ func (s *WsClient) dialRemote(remote *lb.Node) (net.Conn, error) {
|
||||
return wsc, nil
|
||||
}
|
||||
|
||||
func (s *WsClient) HandleTCPConn(c net.Conn, remote *lb.Node) error {
|
||||
clonedRemote := remote.Clone()
|
||||
wsc, err := s.dialRemote(clonedRemote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.l.Infof("HandleTCPConn from %s to %s", c.LocalAddr(), remote.Address)
|
||||
relayConn := conn.NewRelayConn(
|
||||
s.relayLabel, c, wsc,
|
||||
conn.WithHandshakeDuration(clonedRemote.HandShakeDuration))
|
||||
s.cmgr.AddConnection(relayConn)
|
||||
defer s.cmgr.RemoveConnection(relayConn)
|
||||
return relayConn.Transport(remote.Label)
|
||||
}
|
||||
type WsServer struct {
|
||||
*baseTransporter
|
||||
|
||||
type WSServer struct {
|
||||
raw *RawClient
|
||||
e *echo.Echo
|
||||
httpServer *http.Server
|
||||
l *zap.SugaredLogger
|
||||
relayer RelayClient
|
||||
}
|
||||
|
||||
func NewWSServer(listenAddr string, raw *RawClient, l *zap.SugaredLogger) *WSServer {
|
||||
s := &WSServer{
|
||||
l: l,
|
||||
raw: raw,
|
||||
httpServer: &http.Server{Addr: listenAddr, ReadHeaderTimeout: 30 * time.Second},
|
||||
func newWsServer(base *baseTransporter) (*WsServer, error) {
|
||||
localTCPAddr, err := base.GetTCPListenAddr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &WsServer{
|
||||
baseTransporter: base,
|
||||
httpServer: &http.Server{
|
||||
Addr: localTCPAddr.String(), ReadHeaderTimeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
e := web.NewEchoServer()
|
||||
e.GET("/", echo.WrapHandler(web.MakeIndexF()))
|
||||
e.GET("/handshake/", echo.WrapHandler(http.HandlerFunc(s.HandleRequest)))
|
||||
s.e = e
|
||||
s.httpServer.Handler = e
|
||||
return s
|
||||
relayer, err := NewRelayClient(base.cfg.TransportType, base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.relayer = relayer
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *WSServer) ListenAndServe() error {
|
||||
func (s *WsServer) ListenAndServe() error {
|
||||
return s.e.StartServer(s.httpServer)
|
||||
}
|
||||
|
||||
func (s *WSServer) Close() error {
|
||||
func (s *WsServer) Close() error {
|
||||
return s.e.Close()
|
||||
}
|
||||
|
||||
func (s *WSServer) HandleRequest(w http.ResponseWriter, req *http.Request) {
|
||||
func (s *WsServer) HandleRequest(w http.ResponseWriter, req *http.Request) {
|
||||
wsc, _, _, err := ws.UpgradeHTTP(req, w)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
remote := s.raw.GetRemote()
|
||||
if err := s.raw.HandleTCPConn(wsc, remote); err != nil {
|
||||
s.l.Errorf("HandleTCPConn meet error from:%s to:%s err:%s", wsc.RemoteAddr(), remote.Address, err)
|
||||
if err := s.RelayTCPConn(wsc, s.relayer.TCPHandShake); err != nil {
|
||||
s.l.Errorf("RelayTCPConn error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +1,38 @@
|
||||
// nolint: errcheck
|
||||
package transporter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/Ehco1996/ehco/internal/conn"
|
||||
"github.com/Ehco1996/ehco/internal/metrics"
|
||||
mytls "github.com/Ehco1996/ehco/internal/tls"
|
||||
"github.com/Ehco1996/ehco/pkg/lb"
|
||||
)
|
||||
|
||||
type WSSClient struct {
|
||||
WsClient
|
||||
var (
|
||||
_ RelayClient = &WssClient{}
|
||||
_ RelayServer = &WssServer{}
|
||||
)
|
||||
|
||||
type WssClient struct {
|
||||
*WsClient
|
||||
}
|
||||
|
||||
func newWSSClient(raw *RawClient) *WSSClient {
|
||||
return &WSSClient{*newWsClient(raw)}
|
||||
}
|
||||
|
||||
func (s *WSSClient) dialRemote(remote *lb.Node) (net.Conn, error) {
|
||||
t1 := time.Now()
|
||||
d := ws.Dialer{TLSConfig: mytls.DefaultTLSConfig}
|
||||
wssc, _, _, err := d.Dial(context.TODO(), remote.Address+"/handshake/")
|
||||
func newWssClient(base *baseTransporter) (*WssClient, error) {
|
||||
wc, err := newWsClient(base)
|
||||
if err != nil {
|
||||
println("wss called", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
latency := time.Since(t1)
|
||||
metrics.HandShakeDuration.WithLabelValues(remote.Label).Observe(float64(latency.Milliseconds()))
|
||||
remote.HandShakeDuration = latency
|
||||
return wssc, nil
|
||||
// insert tls config
|
||||
wc.dialer.TLSConfig = mytls.DefaultTLSConfig
|
||||
return &WssClient{WsClient: wc}, nil
|
||||
}
|
||||
|
||||
func (s *WSSClient) HandleTCPConn(c net.Conn, remote *lb.Node) error {
|
||||
clonedRemote := remote.Clone()
|
||||
wssc, err := s.dialRemote(clonedRemote)
|
||||
type WssServer struct {
|
||||
*WsServer
|
||||
}
|
||||
|
||||
func newWssServer(base *baseTransporter) (*WssServer, error) {
|
||||
wsServer, err := newWsServer(base)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
s.l.Infof("HandleTCPConn from %s to %s", c.RemoteAddr(), remote.Address)
|
||||
|
||||
relayConn := conn.NewRelayConn(s.relayLabel, c, wssc, conn.WithHandshakeDuration(clonedRemote.HandShakeDuration))
|
||||
s.cmgr.AddConnection(relayConn)
|
||||
defer s.cmgr.RemoveConnection(relayConn)
|
||||
return relayConn.Transport(remote.Label)
|
||||
}
|
||||
|
||||
type WSSServer struct{ WSServer }
|
||||
|
||||
func NewWSSServer(listenAddr string, raw *RawClient, l *zap.SugaredLogger) *WSSServer {
|
||||
wsServer := NewWSServer(listenAddr, raw, l)
|
||||
return &WSSServer{WSServer: *wsServer}
|
||||
}
|
||||
|
||||
func (s *WSSServer) ListenAndServe() error {
|
||||
s.httpServer.TLSConfig = mytls.DefaultTLSConfig
|
||||
return s.WSServer.ListenAndServe()
|
||||
// insert tls config
|
||||
wsServer.httpServer.TLSConfig = mytls.DefaultTLSConfig
|
||||
return &WssServer{WsServer: wsServer}, nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// nolint: errcheck
|
||||
package transporter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -11,31 +9,34 @@ import (
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/xtaci/smux"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/Ehco1996/ehco/internal/conn"
|
||||
"github.com/Ehco1996/ehco/internal/constant"
|
||||
"github.com/Ehco1996/ehco/internal/metrics"
|
||||
mytls "github.com/Ehco1996/ehco/internal/tls"
|
||||
"github.com/Ehco1996/ehco/internal/web"
|
||||
"github.com/Ehco1996/ehco/pkg/lb"
|
||||
)
|
||||
|
||||
type MWSSClient struct {
|
||||
*RawClient
|
||||
dialer *ws.Dialer
|
||||
mtp *smuxTransporter
|
||||
var (
|
||||
_ RelayClient = &MwssClient{}
|
||||
_ RelayServer = &MwssServer{}
|
||||
_ muxServer = &MwssServer{}
|
||||
)
|
||||
|
||||
type MwssClient struct {
|
||||
*WssClient
|
||||
|
||||
muxTP *smuxTransporter
|
||||
}
|
||||
|
||||
func newMWSSClient(raw *RawClient) *MWSSClient {
|
||||
dialer := &ws.Dialer{TLSConfig: mytls.DefaultTLSConfig, Timeout: constant.DialTimeOut}
|
||||
c := &MWSSClient{dialer: dialer, RawClient: raw}
|
||||
mtp := NewSmuxTransporter(raw.l.Named("mwss"), c.initNewSession)
|
||||
c.mtp = mtp
|
||||
return c
|
||||
func newMwssClient(base *baseTransporter) (*MwssClient, error) {
|
||||
wc, err := newWssClient(base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &MwssClient{WssClient: wc}
|
||||
c.muxTP = NewSmuxTransporter(c.l.Named("mwss"), c.initNewSession)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *MWSSClient) initNewSession(ctx context.Context, addr string) (*smux.Session, error) {
|
||||
func (c *MwssClient) initNewSession(ctx context.Context, addr string) (*smux.Session, error) {
|
||||
rc, _, _, err := c.dialer.Dial(ctx, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -51,68 +52,39 @@ func (c *MWSSClient) initNewSession(ctx context.Context, addr string) (*smux.Ses
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (s *MWSSClient) dialRemote(remote *lb.Node) (net.Conn, error) {
|
||||
func (s *MwssClient) TCPHandShake(remote *lb.Node) (net.Conn, error) {
|
||||
t1 := time.Now()
|
||||
mwssc, err := s.mtp.Dial(context.TODO(), remote.Address+"/handshake/")
|
||||
mwssc, err := s.muxTP.Dial(context.TODO(), remote.Address+"/handshake/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
latency := time.Since(t1)
|
||||
metrics.HandShakeDuration.WithLabelValues(remote.Label).Observe(float64(latency.Milliseconds()))
|
||||
remote.HandShakeDuration = latency
|
||||
return mwssc, nil
|
||||
}
|
||||
|
||||
func (s *MWSSClient) HandleTCPConn(c net.Conn, remote *lb.Node) error {
|
||||
clonedRemote := remote.Clone()
|
||||
mwsc, err := s.dialRemote(clonedRemote)
|
||||
type MwssServer struct {
|
||||
*WssServer
|
||||
*muxServerImpl
|
||||
}
|
||||
|
||||
func newMwssServer(base *baseTransporter) (*MwssServer, error) {
|
||||
wssServer, err := newWssServer(base)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
s.l.Infof("HandleTCPConn from:%s to:%s", c.LocalAddr(), remote.Address)
|
||||
relayConn := conn.NewRelayConn(s.relayLabel, c, mwsc, conn.WithHandshakeDuration(clonedRemote.HandShakeDuration))
|
||||
s.cmgr.AddConnection(relayConn)
|
||||
defer s.cmgr.RemoveConnection(relayConn)
|
||||
return relayConn.Transport(remote.Label)
|
||||
s := &MwssServer{
|
||||
WssServer: wssServer,
|
||||
muxServerImpl: newMuxServer(base.cfg.Listen, base.l.Named("mwss")),
|
||||
}
|
||||
s.e.GET("/handshake/", echo.WrapHandler(http.HandlerFunc(s.HandleRequest)))
|
||||
return s, nil
|
||||
}
|
||||
|
||||
type MWSSServer struct {
|
||||
raw *RawClient
|
||||
httpServer *http.Server
|
||||
l *zap.SugaredLogger
|
||||
|
||||
connChan chan net.Conn
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
func NewMWSSServer(listenAddr string, raw *RawClient, l *zap.SugaredLogger) *MWSSServer {
|
||||
s := &MWSSServer{
|
||||
raw: raw,
|
||||
l: l,
|
||||
errChan: make(chan error, 1),
|
||||
connChan: make(chan net.Conn, 1024),
|
||||
}
|
||||
|
||||
e := web.NewEchoServer()
|
||||
e.GET("/", echo.WrapHandler(web.MakeIndexF()))
|
||||
e.GET("/handshake/", echo.WrapHandler(http.HandlerFunc(s.HandleRequest)))
|
||||
s.httpServer = &http.Server{
|
||||
Addr: listenAddr,
|
||||
Handler: e,
|
||||
TLSConfig: mytls.DefaultTLSConfig,
|
||||
ReadHeaderTimeout: 30 * time.Second,
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *MWSSServer) ListenAndServe() error {
|
||||
lis, err := net.Listen("tcp", s.httpServer.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (s *MwssServer) ListenAndServe() error {
|
||||
go func() {
|
||||
s.errChan <- s.httpServer.Serve(tls.NewListener(lis, s.httpServer.TLSConfig))
|
||||
s.errChan <- s.e.StartServer(s.httpServer)
|
||||
}()
|
||||
|
||||
for {
|
||||
@@ -121,15 +93,14 @@ func (s *MWSSServer) ListenAndServe() error {
|
||||
return e
|
||||
}
|
||||
go func(c net.Conn) {
|
||||
remote := s.raw.GetRemote()
|
||||
if err := s.raw.HandleTCPConn(c, remote); err != nil {
|
||||
s.l.Errorf("HandleTCPConn meet error from:%s to:%s err:%s", c.RemoteAddr(), remote.Address, err)
|
||||
if err := s.RelayTCPConn(c, s.relayer.TCPHandShake); err != nil {
|
||||
s.l.Errorf("RelayTCPConn error: %s", err.Error())
|
||||
}
|
||||
}(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MWSSServer) HandleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *MwssServer) HandleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
conn, _, _, err := ws.UpgradeHTTP(r, w)
|
||||
if err != nil {
|
||||
s.l.Error(err)
|
||||
@@ -138,44 +109,6 @@ func (s *MWSSServer) HandleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
s.mux(conn)
|
||||
}
|
||||
|
||||
func (s *MWSSServer) mux(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
cfg := smux.DefaultConfig()
|
||||
cfg.KeepAliveDisabled = true
|
||||
session, err := smux.Server(conn, cfg)
|
||||
if err != nil {
|
||||
s.l.Debugf("server err %s - %s : %s", conn.RemoteAddr(), s.httpServer.Addr, err)
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
s.l.Debugf("session init %s %s", conn.RemoteAddr(), s.httpServer.Addr)
|
||||
defer s.l.Debugf("session close %s >-< %s", conn.RemoteAddr(), s.httpServer.Addr)
|
||||
|
||||
for {
|
||||
stream, err := session.AcceptStream()
|
||||
if err != nil {
|
||||
s.l.Errorf("accept stream err: %s", err)
|
||||
break
|
||||
}
|
||||
select {
|
||||
case s.connChan <- stream:
|
||||
default:
|
||||
stream.Close()
|
||||
s.l.Infof("%s - %s: connection queue is full", conn.RemoteAddr(), conn.LocalAddr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MWSSServer) Accept() (conn net.Conn, err error) {
|
||||
select {
|
||||
case conn = <-s.connChan:
|
||||
case err = <-s.errChan:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *MWSSServer) Close() error {
|
||||
return s.httpServer.Close()
|
||||
func (s *MwssServer) Close() error {
|
||||
return s.e.Close()
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package transporter
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"github.com/Ehco1996/ehco/internal/constant"
|
||||
@@ -133,8 +133,8 @@ func (p *Proxies) ToRelayConfig(listenHost string, listenPort string, newName st
|
||||
remoteAddr := net.JoinHostPort(p.Server, p.Port)
|
||||
r := &relay_cfg.Config{
|
||||
Label: newName,
|
||||
ListenType: constant.Listen_RAW,
|
||||
TransportType: constant.Transport_RAW,
|
||||
ListenType: constant.RelayTypeRaw,
|
||||
TransportType: constant.RelayTypeRaw,
|
||||
Listen: net.JoinHostPort(listenHost, listenPort),
|
||||
TCPRemotes: []string{remoteAddr},
|
||||
}
|
||||
|
||||
@@ -56,66 +56,66 @@ func init() {
|
||||
// raw cfg
|
||||
{
|
||||
Listen: RAW_LISTEN,
|
||||
ListenType: constant.Listen_RAW,
|
||||
ListenType: constant.RelayTypeRaw,
|
||||
TCPRemotes: []string{ECHO_SERVER},
|
||||
UDPRemotes: []string{ECHO_SERVER},
|
||||
TransportType: constant.Transport_RAW,
|
||||
TransportType: constant.RelayTypeRaw,
|
||||
},
|
||||
|
||||
// ws
|
||||
{
|
||||
Listen: WS_LISTEN,
|
||||
ListenType: constant.Listen_RAW,
|
||||
ListenType: constant.RelayTypeRaw,
|
||||
TCPRemotes: []string{WS_REMOTE},
|
||||
TransportType: constant.Transport_WS,
|
||||
TransportType: constant.RelayTypeWS,
|
||||
},
|
||||
{
|
||||
Listen: WS_SERVER,
|
||||
ListenType: constant.Listen_WS,
|
||||
ListenType: constant.RelayTypeWS,
|
||||
TCPRemotes: []string{ECHO_SERVER},
|
||||
TransportType: constant.Transport_RAW,
|
||||
TransportType: constant.RelayTypeRaw,
|
||||
},
|
||||
|
||||
// wss
|
||||
{
|
||||
Listen: WSS_LISTEN,
|
||||
ListenType: constant.Listen_RAW,
|
||||
ListenType: constant.RelayTypeRaw,
|
||||
TCPRemotes: []string{WSS_REMOTE},
|
||||
TransportType: constant.Transport_WSS,
|
||||
TransportType: constant.RelayTypeWSS,
|
||||
},
|
||||
{
|
||||
Listen: WSS_SERVER,
|
||||
ListenType: constant.Listen_WSS,
|
||||
ListenType: constant.RelayTypeWSS,
|
||||
TCPRemotes: []string{ECHO_SERVER},
|
||||
TransportType: constant.Transport_RAW,
|
||||
TransportType: constant.RelayTypeRaw,
|
||||
},
|
||||
|
||||
// mwss
|
||||
{
|
||||
Listen: MWSS_LISTEN,
|
||||
ListenType: constant.Listen_RAW,
|
||||
ListenType: constant.RelayTypeRaw,
|
||||
TCPRemotes: []string{MWSS_REMOTE},
|
||||
TransportType: constant.Transport_MWSS,
|
||||
TransportType: constant.RelayTypeMWSS,
|
||||
},
|
||||
{
|
||||
Listen: MWSS_SERVER,
|
||||
ListenType: constant.Listen_MWSS,
|
||||
ListenType: constant.RelayTypeMWSS,
|
||||
TCPRemotes: []string{ECHO_SERVER},
|
||||
TransportType: constant.Transport_RAW,
|
||||
TransportType: constant.RelayTypeRaw,
|
||||
},
|
||||
|
||||
// mtcp
|
||||
{
|
||||
Listen: MTCP_LISTEN,
|
||||
ListenType: constant.Listen_RAW,
|
||||
ListenType: constant.RelayTypeRaw,
|
||||
TCPRemotes: []string{MTCP_REMOTE},
|
||||
TransportType: constant.Transport_MTCP,
|
||||
TransportType: constant.RelayTypeMTCP,
|
||||
},
|
||||
{
|
||||
Listen: MTCP_SERVER,
|
||||
ListenType: constant.Listen_MTCP,
|
||||
ListenType: constant.RelayTypeMTCP,
|
||||
TCPRemotes: []string{ECHO_SERVER},
|
||||
TransportType: constant.Transport_RAW,
|
||||
TransportType: constant.RelayTypeRaw,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -88,16 +88,13 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
vehicle = resource.NewFileVehicle(path)
|
||||
case "http":
|
||||
var path string
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
if schema.Path != "" {
|
||||
path = C.Path.Resolve(schema.Path)
|
||||
if !features.CMFA && !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
} else {
|
||||
path = C.Path.GetPathByHash("proxies", schema.URL)
|
||||
}
|
||||
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||
|
||||
@@ -125,7 +125,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
||||
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
|
||||
if userInfoStr == "" {
|
||||
resp2, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
||||
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, "")
|
||||
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func InitGeoSite() error {
|
||||
func downloadGeoSite(path string) (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.GeoSiteUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func downloadGeoSite(path string) (err error) {
|
||||
func downloadGeoIP(path string) (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.GeoIpUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -16,7 +16,11 @@ import (
|
||||
"github.com/metacubex/mihomo/listener/inner"
|
||||
)
|
||||
|
||||
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader, specialProxy string) (*http.Response, error) {
|
||||
func HttpRequest(ctx context.Context, url, method string, header map[string][]string, body io.Reader) (*http.Response, error) {
|
||||
return HttpRequestWithProxy(ctx, url, method, header, body, "")
|
||||
}
|
||||
|
||||
func HttpRequestWithProxy(ctx context.Context, url, method string, header map[string][]string, body io.Reader, specialProxy string) (*http.Response, error) {
|
||||
method = strings.ToUpper(method)
|
||||
urlRes, err := URL.Parse(url)
|
||||
if err != nil {
|
||||
|
||||
@@ -82,7 +82,7 @@ func IPInstance() IPReader {
|
||||
func DownloadMMDB(path string) (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.MmdbUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -115,7 +115,7 @@ func ASNInstance() ASNReader {
|
||||
func DownloadASN(path string) (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.ASNUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, C.ASNUrl, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ func (h *HTTPVehicle) Path() string {
|
||||
func (h *HTTPVehicle) Read() ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, h.url, http.MethodGet, h.header, nil, h.proxy)
|
||||
resp, err := mihomoHttp.HttpRequestWithProxy(ctx, h.url, http.MethodGet, h.header, nil, h.proxy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
func downloadForBytes(url string) ([]byte, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, url, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ const MaxPackageFileSize = 32 * 1024 * 1024
|
||||
func downloadPackageFile() (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("http request failed: %w", err)
|
||||
}
|
||||
@@ -415,7 +415,7 @@ func copyFile(src, dst string) error {
|
||||
func getLatestVersion() (version string, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, "")
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get Latest Version fail: %w", err)
|
||||
}
|
||||
|
||||
@@ -62,15 +62,12 @@ func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(t
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
vehicle = resource.NewFileVehicle(path)
|
||||
case "http":
|
||||
var path string
|
||||
path := C.Path.GetPathByHash("rules", schema.URL)
|
||||
if schema.Path != "" {
|
||||
path = C.Path.Resolve(schema.Path)
|
||||
if !features.CMFA && !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
} else {
|
||||
path = C.Path.GetPathByHash("rules", schema.URL)
|
||||
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, nil)
|
||||
default:
|
||||
|
||||
@@ -11,7 +11,7 @@ LUCI_DEPENDS:=+curl +opkg +luci-base +tar +coreutils +coreutils-stat +libuci-lua
|
||||
LUCI_EXTRA_DEPENDS:=luci-lib-taskd (>=1.0.17)
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_VERSION:=0.1.14-2
|
||||
PKG_VERSION:=0.1.14-3
|
||||
# PKG_RELEASE MUST be empty for luci.mk
|
||||
PKG_RELEASE:=
|
||||
|
||||
|
||||
@@ -58,11 +58,13 @@ update() {
|
||||
return 1
|
||||
fi
|
||||
|
||||
fcurl -o ${OPKG_CONF_DIR}/meta.conf "${FEEDS_SERVER}/all/meta.conf" && \
|
||||
fcurl -o ${OPKG_CONF_DIR}/all.conf "${FEEDS_SERVER}/all/isfeeds.conf" && \
|
||||
fcurl -o ${OPKG_CONF_DIR}/arch.conf "${FEEDS_SERVER}/${ARCH}/isfeeds.conf" || \
|
||||
echo "Fetch feed list for ${ARCH}"
|
||||
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/meta.conf "${FEEDS_SERVER}/all/meta.conf" && \
|
||||
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/all.conf "${FEEDS_SERVER}/all/isfeeds.conf" && \
|
||||
fcurl --no-progress-meter -o ${OPKG_CONF_DIR}/arch.conf "${FEEDS_SERVER}/${ARCH}/isfeeds.conf" || \
|
||||
return 1
|
||||
|
||||
echo "Update feeds index"
|
||||
opkg -f ${IS_ROOT}/etc/opkg_o.conf --offline-root ${IS_ROOT} update
|
||||
|
||||
return 0
|
||||
|
||||
@@ -12,8 +12,8 @@ ifeq ($(ARCH),aarch64)
|
||||
else ifeq ($(ARCH),arm)
|
||||
ARM_CPU_FEATURES:=$(word 2,$(subst +,$(space),$(call qstrip,$(CONFIG_CPU_TYPE))))
|
||||
ifeq ($(ARM_CPU_FEATURES),)
|
||||
PKG_ARCH:=chinadns-ng@arm-linux-musleabi@generic+v7a@fast+lto
|
||||
PKG_HASH:=c2e0378e20f5fda376d4f52bfbe1a3dbc7dfe23879e8edc8ef7ab55694cfd676
|
||||
PKG_ARCH:=chinadns-ng@arm-linux-musleabi@generic+v6+soft_float@fast+lto
|
||||
PKG_HASH:=f9e597bbc060d7c5e19c837a3c0b3118f91c986f29ee7f55a23ace321aca1731
|
||||
else
|
||||
PKG_ARCH:=chinadns-ng@arm-linux-musleabihf@generic+v7a@fast+lto
|
||||
PKG_HASH:=d065c7d55b6c43b20dbb668d7bdafabe371c3360cc75bd0279cc2d7a83e342e9
|
||||
|
||||
@@ -525,7 +525,9 @@ o.default = "3"
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(Value, "timeout", translate("Timeout for establishing a connection to server(second)"))
|
||||
o.description = translate("Default value 0 indicatesno heartbeat.")
|
||||
o:depends("type", "tuic")
|
||||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||
o.datatype = "uinteger"
|
||||
o.default = "8"
|
||||
o.rmempty = true
|
||||
@@ -831,11 +833,22 @@ o:depends("transport", "kcp")
|
||||
o.rmempty = true
|
||||
|
||||
-- [[ WireGuard 部分 ]]--
|
||||
o = s:option(Flag, "kernelmode", translate("Enabled Kernel virtual NIC TUN(optional)"))
|
||||
o.description = translate("Virtual NIC TUN of Linux kernel can be used only when system supports and have root permission. If used, IPv6 routing table 1023 is occupied.")
|
||||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||
o.default = "0"
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(DynamicList, "local_addresses", translate("Local addresses"))
|
||||
o.datatype = "cidr"
|
||||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(DynamicList, "reserved", translate("Reserved bytes(optional)"))
|
||||
o.description = translate("Wireguard reserved bytes.")
|
||||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(Value, "private_key", translate("Private key"))
|
||||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||
o.password = true
|
||||
@@ -850,6 +863,13 @@ o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||
o.password = true
|
||||
o.rmempty = true
|
||||
|
||||
o = s:option(DynamicList, "allowedips", translate("allowedIPs(optional)"))
|
||||
o.description = translate("Wireguard allows only traffic from specific source IP.")
|
||||
o.datatype = "cidr"
|
||||
o:depends({type = "v2ray", v2ray_protocol = "wireguard"})
|
||||
o.default = "0.0.0.0/0"
|
||||
o.rmempty = true
|
||||
|
||||
-- [[ TLS ]]--
|
||||
o = s:option(Flag, "tls", translate("TLS"))
|
||||
o.rmempty = true
|
||||
|
||||
@@ -942,9 +942,21 @@ msgstr "写入缓冲区大小"
|
||||
msgid "Congestion"
|
||||
msgstr "拥塞控制"
|
||||
|
||||
msgid "Enabled Kernel virtual NIC TUN(optional)"
|
||||
msgstr "启用内核的虚拟网卡 TUN(可选)"
|
||||
|
||||
msgid "Virtual NIC TUN of Linux kernel can be used only when system supports and have root permission. If used, IPv6 routing table 1023 is occupied."
|
||||
msgstr "需要系统支持且有 root 权限才能使用 Linux 内核的虚拟网卡 TUN,使用后会占用 IPv6 的 1023 号路由表。"
|
||||
|
||||
msgid "Local addresses"
|
||||
msgstr "本地地址"
|
||||
|
||||
msgid "Reserved bytes(optional)"
|
||||
msgstr "保留字节(可选)"
|
||||
|
||||
msgid "Wireguard reserved bytes."
|
||||
msgstr "Wireguard 保留字节。"
|
||||
|
||||
msgid "Private key"
|
||||
msgstr "私钥"
|
||||
|
||||
@@ -954,6 +966,15 @@ msgstr "节点公钥"
|
||||
msgid "Pre-shared key"
|
||||
msgstr "预共享密钥"
|
||||
|
||||
msgid "Default value 0 indicatesno heartbeat."
|
||||
msgstr "默认为 0 表示无心跳。"
|
||||
|
||||
msgid "allowedIPs(optional)"
|
||||
msgstr "allowedIPs(可选)"
|
||||
|
||||
msgid "Wireguard allows only traffic from specific source IP."
|
||||
msgstr "Wireguard 仅允许特定源 IP 的流量。"
|
||||
|
||||
msgid "Network interface to use"
|
||||
msgstr "使用的网络接口"
|
||||
|
||||
|
||||
@@ -72,9 +72,13 @@ function wireguard()
|
||||
{
|
||||
publicKey = server.peer_pubkey,
|
||||
preSharedKey = server.preshared_key,
|
||||
endpoint = server.server .. ":" .. server.server_port
|
||||
endpoint = server.server .. ":" .. server.server_port,
|
||||
keepAlive = tonumber(server.heartbeat),
|
||||
allowedIPs = (server.allowedips) or nil,
|
||||
}
|
||||
},
|
||||
kernelMode = (server.kernelmode == "1") and true or false,
|
||||
reserved = {server.reserved} or nil,
|
||||
mtu = tonumber(server.mtu)
|
||||
}
|
||||
end
|
||||
@@ -172,7 +176,7 @@ local Xray = {
|
||||
protocol = server.v2ray_protocol,
|
||||
settings = outbound_settings,
|
||||
-- 底层传输配置
|
||||
streamSettings = {
|
||||
streamSettings = (server.v2ray_protocol ~= "wireguard") and {
|
||||
network = server.transport or "tcp",
|
||||
security = (server.tls == '1') and "tls" or (server.reality == '1') and "reality" or nil,
|
||||
tlsSettings = (server.tls == '1') and {
|
||||
@@ -258,14 +262,14 @@ local Xray = {
|
||||
tcpNoDelay = (server.mptcp == "1") and true or false, -- MPTCP
|
||||
tcpcongestion = server.custom_tcpcongestion -- 连接服务器节点的 TCP 拥塞控制算法
|
||||
}
|
||||
},
|
||||
mux = {
|
||||
} or nil,
|
||||
mux = (server.v2ray_protocol ~= "wireguard") and {
|
||||
-- mux
|
||||
enabled = (server.mux == "1") and true or false, -- Mux
|
||||
concurrency = tonumber(server.concurrency), -- TCP 最大并发连接数
|
||||
xudpConcurrency = tonumber(server.xudpConcurrency), -- UDP 最大并发连接数
|
||||
xudpProxyUDP443 = server.xudpProxyUDP443 -- 对被代理的 UDP/443 流量处理方式
|
||||
}
|
||||
} or nil
|
||||
}
|
||||
}
|
||||
local cipher = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:AES128-SHA:AES256-SHA:DES-CBC3-SHA"
|
||||
@@ -322,7 +326,7 @@ local ss = {
|
||||
}
|
||||
local hysteria = {
|
||||
server = (server.server_port and (server.port_range and (server.server .. ":" .. server.server_port .. "," .. server.port_range) or server.server .. ":" .. server.server_port) or (server.port_range and server.server .. ":" .. server.port_range or server.server .. ":443")),
|
||||
bandwidth = {
|
||||
bandwidth = (server.uplink_capacity or server.downlink_capacity) and {
|
||||
up = tonumber(server.uplink_capacity) and tonumber(server.uplink_capacity) .. " mbps" or nil,
|
||||
down = tonumber(server.downlink_capacity) and tonumber(server.downlink_capacity) .. " mbps" or nil
|
||||
},
|
||||
@@ -336,12 +340,11 @@ local hysteria = {
|
||||
hopInterval = (server.port_range and (tonumber(server.hopinterval) .. "s") or nil)
|
||||
} or nil)
|
||||
} or nil,
|
||||
|
||||
--[[
|
||||
tcpTProxy = (proto:find("tcp") and local_port ~= "0") and {
|
||||
listen = "0.0.0.0:" .. tonumber(local_port)
|
||||
} or nil,
|
||||
]]
|
||||
]]--
|
||||
tcpRedirect = (proto:find("tcp") and local_port ~= "0") and {
|
||||
listen = "0.0.0.0:" .. tonumber(local_port)
|
||||
} or nil,
|
||||
@@ -359,7 +362,7 @@ local hysteria = {
|
||||
maxConnReceiveWindow = (server.maxconnreceivewindow and server.maxconnreceivewindow or nil),
|
||||
maxIdleTimeout = (tonumber(server.maxidletimeout) and tonumber(server.maxidletimeout) .. "s" or nil),
|
||||
keepAlivePeriod = (tonumber(server.keepaliveperiod) and tonumber(server.keepaliveperiod) .. "s" or nil),
|
||||
disable_mtu_discovery = (server.disablepathmtudiscovery == "1") and true or false
|
||||
disablePathMTUDiscovery = (server.disablepathmtudiscovery == "1") and true or false
|
||||
} or nil,
|
||||
auth = server.hy2_auth,
|
||||
tls = (server.tls_host) and {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
# SPDX-FileCopyrightText: 2019 yuzu Emulator Project
|
||||
# SPDX-FileCopyrightText: 2024 suyu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
set -e
|
||||
@@ -9,12 +10,15 @@ set -e
|
||||
|
||||
ccache -sv
|
||||
|
||||
rm -rf build
|
||||
mkdir -p build && cd build
|
||||
cmake .. \
|
||||
/usr/bin/x86_64-w64-mingw32-cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_TOOLCHAIN_FILE="${PWD}/../CMakeModules/MinGWCross.cmake" \
|
||||
-DDISPLAY_VERSION="$1" \
|
||||
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
|
||||
-DDYNARMIC_USE_PRECOMPILED_HEADERS=OFF \
|
||||
-DSUYU_USE_PRECOMPILED_HEADERS=OFF \
|
||||
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=OFF \
|
||||
-DUSE_DISCORD_PRESENCE=ON \
|
||||
-DENABLE_QT_TRANSLATION=ON \
|
||||
-DUSE_CCACHE=ON \
|
||||
-DSUYU_USE_BUNDLED_SDL2=OFF \
|
||||
|
||||
@@ -63,8 +63,8 @@ jobs:
|
||||
image: linux-fresh
|
||||
- type: linux
|
||||
image: linux-fresh
|
||||
# - type: windows
|
||||
# image: linux-mingw
|
||||
- type: windows
|
||||
image: linux-mingw
|
||||
container: fijxu/build-environments:${{ matrix.image }}
|
||||
# User 1001 doesn't exists on the images.
|
||||
# options: -u 1001
|
||||
|
||||
@@ -2,12 +2,8 @@ package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io"
|
||||
"math/rand"
|
||||
gonet "net"
|
||||
"os"
|
||||
|
||||
"github.com/xtls/xray-core/app/proxyman"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/buf"
|
||||
@@ -25,6 +21,10 @@ import (
|
||||
"github.com/xtls/xray-core/transport/internet/stat"
|
||||
"github.com/xtls/xray-core/transport/internet/tls"
|
||||
"github.com/xtls/xray-core/transport/pipe"
|
||||
"io"
|
||||
"math/big"
|
||||
gonet "net"
|
||||
"os"
|
||||
)
|
||||
|
||||
func getStatCounter(v *core.Instance, tag string) (stats.Counter, stats.Counter) {
|
||||
@@ -319,16 +319,21 @@ func (h *Handler) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return random IPv6 in a CIDR block
|
||||
|
||||
func ParseRandomIPv6(address net.Address, prefix string) net.Address {
|
||||
addr := address.IP().String()
|
||||
_, network, _ := gonet.ParseCIDR(addr + "/" + prefix)
|
||||
_, network, _ := gonet.ParseCIDR(address.IP().String() + "/" + prefix)
|
||||
|
||||
ipv6 := network.IP.To16()
|
||||
prefixLen, _ := network.Mask.Size()
|
||||
for i := prefixLen / 8; i < 16; i++ {
|
||||
ipv6[i] = byte(rand.Intn(256))
|
||||
}
|
||||
maskSize, totalBits := network.Mask.Size()
|
||||
subnetSize := big.NewInt(1).Lsh(big.NewInt(1), uint(totalBits-maskSize))
|
||||
|
||||
return net.ParseAddress(gonet.IP(ipv6).String())
|
||||
// random
|
||||
randomBigInt, _ := rand.Int(rand.Reader, subnetSize)
|
||||
|
||||
startIPBigInt := big.NewInt(0).SetBytes(network.IP.To16())
|
||||
randomIPBigInt := big.NewInt(0).Add(startIPBigInt, randomBigInt)
|
||||
|
||||
randomIPBytes := randomIPBigInt.Bytes()
|
||||
randomIPBytes = append(make([]byte, 16-len(randomIPBytes)), randomIPBytes...)
|
||||
|
||||
return net.ParseAddress(gonet.IP(randomIPBytes).String())
|
||||
}
|
||||
|
||||
@@ -20,16 +20,16 @@ require (
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2.0.20230316163032-ced5aaba43e3
|
||||
github.com/xtls/reality v0.0.0-20231112171332-de1173cf2b19
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/net v0.23.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||
google.golang.org/grpc v1.63.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gvisor.dev/gvisor v0.0.0-20231104011432-48a6d7d5bd0b
|
||||
h12.io/socks v1.0.3
|
||||
lukechampine.com/blake3 v1.2.1
|
||||
lukechampine.com/blake3 v1.2.2
|
||||
)
|
||||
|
||||
require (
|
||||
|
||||
@@ -179,8 +179,8 @@ golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnf
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
@@ -201,8 +201,8 @@ golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -214,8 +214,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -228,8 +228,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220804214406-8e32c043e418/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -299,7 +299,7 @@ h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
|
||||
lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
lukechampine.com/blake3 v1.2.2 h1:wEAbSg0IVU4ih44CVlpMqMZMpzr5hf/6aqodLlevd/w=
|
||||
lukechampine.com/blake3 v1.2.2/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
|
||||
@@ -373,6 +373,9 @@ func (f *FragmentWriter) Write(b []byte) (int, error) {
|
||||
return f.writer.Write(b)
|
||||
}
|
||||
recordLen := 5 + ((int(b[3]) << 8) | int(b[4]))
|
||||
if len(b) < recordLen { // maybe already fragmented somehow
|
||||
return f.writer.Write(b)
|
||||
}
|
||||
data := b[5:recordLen]
|
||||
buf := make([]byte, 1024)
|
||||
for from := 0; ; {
|
||||
|
||||
54
yass/.github/workflows/releases-linux-binary.yml
vendored
54
yass/.github/workflows/releases-linux-binary.yml
vendored
@@ -57,10 +57,13 @@ jobs:
|
||||
qemu_suffix: mipsel
|
||||
- arch: mips64el
|
||||
qemu_suffix: mips64el
|
||||
- arch: loongarch64
|
||||
qemu_suffix: loongarch64
|
||||
- arch: riscv64
|
||||
qemu_suffix: riscv64
|
||||
- arch: riscv32
|
||||
qemu_suffix: riscv32
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
SDK_ROOT: ${{ github.workspace }}/debian_bullseye_${{ matrix.arch }}-sysroot
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout with shallow submodules
|
||||
@@ -73,6 +76,18 @@ jobs:
|
||||
run: |
|
||||
cd third_party/libc++abi
|
||||
patch -p1 < v8-6.7.17-fix-gcc-unwind-header.patch
|
||||
- name: Set SDK_ROOT
|
||||
if: ${{ matrix.arch != 'loongarch64' && matrix.arch != 'riscv64' && matrix.arch != 'riscv32' }}
|
||||
run: |
|
||||
echo "SDK_ROOT=${{ github.workspace }}/debian_bullseye_${{ matrix.arch }}-sysroot" >> $GITHUB_ENV
|
||||
- name: Set SDK_ROOT for loongarch64
|
||||
if: ${{ matrix.arch == 'loongarch64' }}
|
||||
run: |
|
||||
echo "SDK_ROOT=${{ github.workspace }}/debian_bullseye_${{ matrix.arch }}-sysroot/target" >> $GITHUB_ENV
|
||||
- name: Set SDK_ROOT for riscv64 and riscv32
|
||||
if: ${{ matrix.arch == 'riscv64' || matrix.arch == 'riscv32' }}
|
||||
run: |
|
||||
echo "SDK_ROOT=${{ github.workspace }}/debian_bullseye_${{ matrix.arch }}-sysroot/sysroot" >> $GITHUB_ENV
|
||||
- name: Cache clang
|
||||
id: clang-cache
|
||||
uses: actions/cache@v4
|
||||
@@ -101,8 +116,8 @@ jobs:
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ env.SDK_ROOT }}
|
||||
key: ${{ runner.os }}-sysroot-${{ matrix.arch }}-${{ hashFiles('scripts/sysroots.json') }}-v1
|
||||
${{ github.workspace }}/debian_bullseye_${{ matrix.arch }}-sysroot
|
||||
key: ${{ runner.os }}-sysroot-${{ matrix.arch }}-${{ hashFiles('scripts/sysroots.json') }}-v2
|
||||
- name: Build build tool
|
||||
run: |
|
||||
cd tools
|
||||
@@ -117,9 +132,27 @@ jobs:
|
||||
run: |
|
||||
wget http://ftp.us.debian.org/debian/pool/main/q/qemu/qemu-user-static_8.2.2+ds-2_amd64.deb
|
||||
- name: "Install dependency: sysroot"
|
||||
if: ${{ steps.sysroot-cache.outputs.cache-hit != 'true' }}
|
||||
if: ${{ steps.sysroot-cache.outputs.cache-hit != 'true' && matrix.arch != 'loongarch64' && matrix.arch != 'riscv64' && matrix.arch != 'riscv32' }}
|
||||
run: |
|
||||
./scripts/install-sysroot.py --arch ${{ env.ARCH }}
|
||||
./scripts/install-sysroot.py --arch ${{ matrix.arch }}
|
||||
- name: "Install dependency: sysroot (loongarch64)"
|
||||
if: ${{ steps.sysroot-cache.outputs.cache-hit != 'true' && matrix.arch == 'loongarch64' }}
|
||||
run: |
|
||||
mkdir -p debian_bullseye_loongarch64-sysroot
|
||||
cd debian_bullseye_loongarch64-sysroot
|
||||
curl -L https://github.com/loongson/build-tools/releases/download/2023.08.08/CLFS-loongarch64-8.1-x86_64-cross-tools-gcc-glibc.tar.xz | tar --strip-components=1 --xz -xf -
|
||||
cp -fv ./loongarch64-unknown-linux-gnu/lib/libgcc_s.so target/lib64/
|
||||
cp -fv ./loongarch64-unknown-linux-gnu/lib/libgcc_s.so.1 target/lib64/
|
||||
rm -rf bin include libexec loongarch64-unknown-linux-gnu share
|
||||
cd ..
|
||||
- name: "Install dependency: sysroot (riscv64 and riscv32)"
|
||||
if: ${{ steps.sysroot-cache.outputs.cache-hit != 'true' && (matrix.arch == 'riscv64' || matrix.arch == 'riscv32') }}
|
||||
run: |
|
||||
mkdir -p debian_bullseye_${{ matrix.arch }}-sysroot
|
||||
cd debian_bullseye_${{ matrix.arch }}-sysroot
|
||||
curl -L https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2023.07.07/${{ matrix.arch }}-glibc-ubuntu-20.04-gcc-nightly-2023.07.07-nightly.tar.gz | tar --strip-components=1 --gz -xf -
|
||||
rm -rf bin include libexec ${{ matrix.arch }}-unknown-linux-gnu share
|
||||
cd ..
|
||||
- name: Change ubuntu mirror
|
||||
run: |
|
||||
sudo sed -i 's/azure.archive.ubuntu.com/azure.archive.ubuntu.com/g' /etc/apt/sources.list
|
||||
@@ -134,11 +167,14 @@ jobs:
|
||||
sudo apt install qemu-user
|
||||
sudo apt remove -y qemu-user-binfmt
|
||||
sudo dpkg -i qemu-user-static_*.deb
|
||||
- name: Build TGZ packages
|
||||
- name: Build TGZ packages (CLI and Server)
|
||||
run: |
|
||||
./tools/build --variant gui --arch ${{ matrix.arch }} --system linux --sysroot ${{ env.SDK_ROOT }} -build-benchmark -build-test -nc
|
||||
./tools/build --variant cli --arch ${{ matrix.arch }} --system linux --sysroot ${{ env.SDK_ROOT }} -build-benchmark -build-test -nc
|
||||
./tools/build --variant server --arch ${{ matrix.arch }} --system linux --sysroot ${{ env.SDK_ROOT }} -build-benchmark -build-test -nc
|
||||
- name: Build TGZ packages (GUI)
|
||||
if: ${{ matrix.arch != 'loongarch64' && matrix.arch != 'riscv64' && matrix.arch != 'riscv32' }}
|
||||
run: |
|
||||
./tools/build --variant gui --arch ${{ matrix.arch }} --system linux --sysroot ${{ env.SDK_ROOT }} -build-benchmark -build-test -nc
|
||||
- name: Run tests (i386 and amd64)
|
||||
if: ${{ matrix.arch == 'i386' || matrix.arch == 'amd64' }}
|
||||
run: |
|
||||
|
||||
@@ -310,6 +310,9 @@ else()
|
||||
if (CMAKE_C_COMPILER_TARGET MATCHES "^riscv64-.*")
|
||||
set(OS_RISCV64 TRUE)
|
||||
endif()
|
||||
if (CMAKE_C_COMPILER_TARGET MATCHES "^riscv32-.*")
|
||||
set(OS_RISCV32 TRUE)
|
||||
endif()
|
||||
# Fix MINGW (native mingw)'s CMAKE_SYSTEM_PROCESSOR
|
||||
if (MINGW)
|
||||
if (OS_X86)
|
||||
@@ -529,6 +532,14 @@ if (UNIX AND CMAKE_CROSSCOMPILING AND CMAKE_SYSROOT AND COMPILER_CLANG AND NOT C
|
||||
# fix up for loongarch sysroot
|
||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "loongarch64")
|
||||
file(GLOB _GCC_SYSROOT "${CMAKE_SYSROOT}/../../lib/gcc/*/*.*.*")
|
||||
# required by loongarch64 sdk https://github.com/loongson/build-tools/releases
|
||||
if (NOT _GCC_SYSROOT)
|
||||
file(GLOB _GCC_SYSROOT "${CMAKE_SYSROOT}/../lib/gcc/*/*.*.*")
|
||||
endif()
|
||||
endif()
|
||||
# fix up for riscv64 and riscv32 sysroot
|
||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv32")
|
||||
file(GLOB _GCC_SYSROOT "${CMAKE_SYSROOT}/../lib/gcc/*/*.*.*")
|
||||
endif()
|
||||
if (_GCC_SYSROOT)
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --gcc-install-dir=${_GCC_SYSROOT}")
|
||||
@@ -2044,8 +2055,17 @@ elseif (GUI)
|
||||
set(GUI off)
|
||||
endif()
|
||||
|
||||
# fix up for loongarch gtk support
|
||||
# see https://github.com/microcai/gentoo-zh/pull/4486
|
||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "loongarch64")
|
||||
message(STATUS "Blacklist gtk4 for loongarch64 target")
|
||||
set(BLACKLIST_GTK4 ON)
|
||||
else()
|
||||
set(BLACKLIST_GTK4 OFF)
|
||||
endif()
|
||||
|
||||
pkg_check_modules(GTK4 gtk4)
|
||||
if (GTK4_FOUND)
|
||||
if (GTK4_FOUND AND NOT BLACKLIST_GTK4)
|
||||
set(GUI_FLAVOUR "gtk4")
|
||||
set(GUI_OTHER_FLAGS -Wno-deprecated-declarations)
|
||||
|
||||
@@ -2280,6 +2300,10 @@ if (USE_TCMALLOC)
|
||||
message(WARNING "tcmalloc: loongarch64 is not supported, disabling...")
|
||||
set(USE_TCMALLOC OFF)
|
||||
endif()
|
||||
if (CMAKE_SYSTEM_PROCESSOR STREQUAL "riscv32")
|
||||
message(WARNING "tcmalloc: riscv32 is not supported, disabling...")
|
||||
set(USE_TCMALLOC OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# tunings:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "core/debug.hpp"
|
||||
#include "core/check_op.hpp"
|
||||
|
||||
#include <absl/time/clock.h>
|
||||
#include <base/posix/eintr_wrapper.h>
|
||||
#include <base/strings/string_util.h>
|
||||
#include <build/buildflag.h>
|
||||
@@ -38,7 +39,7 @@ bool WaitForDebugger(int wait_seconds, bool silent) {
|
||||
BreakDebugger();
|
||||
return true;
|
||||
}
|
||||
// FIXME PlatformThread::Sleep(Milliseconds(100));
|
||||
absl::SleepFor(absl::Milliseconds(100));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -287,7 +288,7 @@ void DebugBreak() {
|
||||
#else
|
||||
volatile int go = 0;
|
||||
while (!go)
|
||||
PlatformThread::Sleep(Milliseconds(100));
|
||||
absl::SleepFor(absl::Milliseconds(100));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@ class URandomFd {
|
||||
// (https://chromium-review.googlesource.com/c/chromium/src/+/1545096) and land
|
||||
// it or some form of it.
|
||||
void RandBytes(void* output, size_t output_length) {
|
||||
#ifdef __linux__
|
||||
// FIXME currently lss doesn't like riscv32
|
||||
#if defined(__linux__) && !defined(ARCH_CPU_RISCV32)
|
||||
// We have to call `getrandom` via Linux Syscall Support, rather than through
|
||||
// the libc wrapper, because we might not have an up-to-date libc (e.g. on
|
||||
// some bots).
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#define EXPECTED_NR_getrandom 384
|
||||
#elif defined(OPENSSL_RISCV64)
|
||||
#define EXPECTED_NR_getrandom 278
|
||||
#elif defined(OPENSSL_RISCV)
|
||||
#define EXPECTED_NR_getrandom 278
|
||||
#elif defined(OPENSSL_LOONGARCH64)
|
||||
#define EXPECTED_NR_getrandom 278
|
||||
#endif
|
||||
|
||||
@@ -45,12 +45,13 @@
|
||||
#define OPENSSL_RISCV64
|
||||
#elif defined(__riscv) && __SIZEOF_POINTER__ == 4
|
||||
#define OPENSSL_32_BIT
|
||||
#elif defined(__loongarch__) && __loongarch_grlen == 64
|
||||
#define OPENSSL_RISCV
|
||||
#elif defined(__loongarch__) && !defined(__loongarch_lp64)
|
||||
#define OPENSSL_32_BIT
|
||||
#define OPENSSL_LOONGARCH
|
||||
#elif defined(__loongarch__) && defined(__loongarch_lp64)
|
||||
#define OPENSSL_64_BIT
|
||||
#define OPENSSL_LOONGARCH64
|
||||
#elif defined(__loongarch__) && __loongarch_grlen == 32
|
||||
#define OPENSSL_32_BIT
|
||||
#define OPENSSL_LOONGARCH32
|
||||
#elif defined(__pnacl__)
|
||||
#define OPENSSL_32_BIT
|
||||
#define OPENSSL_PNACL
|
||||
|
||||
@@ -358,6 +358,11 @@
|
||||
#define ARCH_CPU_RISCV64 1
|
||||
#define ARCH_CPU_64_BITS 1
|
||||
#define ARCH_CPU_LITTLE_ENDIAN 1
|
||||
#elif defined(__riscv) && (__riscv_xlen == 32)
|
||||
#define ARCH_CPU_RISCV_FAMILY 1
|
||||
#define ARCH_CPU_RISCV32 1
|
||||
#define ARCH_CPU_32_BITS 1
|
||||
#define ARCH_CPU_LITTLE_ENDIAN 1
|
||||
#else
|
||||
#error Please add support for your architecture in build/build_config.h
|
||||
#endif
|
||||
|
||||
1
yass/third_party/quiche/CMakeLists.txt
vendored
1
yass/third_party/quiche/CMakeLists.txt
vendored
@@ -730,7 +730,6 @@ target_include_directories(quiche PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/overrides
|
||||
${CMAKE_CURRENT_BINARY_DIR}/src
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../googleurl
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../
|
||||
|
||||
@@ -471,6 +471,10 @@ func getGNUTargetTypeAndArch(arch string, subsystem string) (string, string) {
|
||||
return "mips64el-linux-gnuabi64", "mips64el"
|
||||
} else if arch == "loongarch64" {
|
||||
return "loongarch64-linux-gnu", "loongarch64"
|
||||
} else if arch == "riscv64" {
|
||||
return "riscv64-linux-gnu", "riscv64"
|
||||
} else if arch == "riscv32" {
|
||||
return "riscv32-linux-gnu", "riscv32"
|
||||
}
|
||||
glog.Fatalf("Invalid arch: %s", arch)
|
||||
return "", ""
|
||||
|
||||
@@ -74,8 +74,11 @@ class FFmpegPostProcessor(PostProcessor):
|
||||
return FFmpegPostProcessor(downloader)._versions
|
||||
|
||||
def _determine_executables(self):
|
||||
programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe']
|
||||
# ordered to match prefer_ffmpeg!
|
||||
convs = ['ffmpeg', 'avconv']
|
||||
probes = ['ffprobe', 'avprobe']
|
||||
prefer_ffmpeg = True
|
||||
programs = convs + probes
|
||||
|
||||
def get_ffmpeg_version(path):
|
||||
ver = get_exe_version(path, args=['-version'])
|
||||
@@ -127,10 +130,13 @@ class FFmpegPostProcessor(PostProcessor):
|
||||
(p, get_ffmpeg_version(self._paths[p])) for p in programs)
|
||||
if x[1] is not None)
|
||||
|
||||
for p in ('ffmpeg', 'avconv')[::-1 if prefer_ffmpeg is False else 1]:
|
||||
basenames = [None, None]
|
||||
for i, progs in enumerate((convs, probes)):
|
||||
for p in progs[::-1 if prefer_ffmpeg is False else 1]:
|
||||
if self._versions.get(p):
|
||||
self.basename = self.probe_basename = p
|
||||
basenames[i] = p
|
||||
break
|
||||
self.basename, self.probe_basename = basenames
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
|
||||
@@ -194,7 +194,11 @@ def _firefox_browser_dirs():
|
||||
yield os.path.expanduser('~/Library/Application Support/Firefox/Profiles')
|
||||
|
||||
else:
|
||||
yield from map(os.path.expanduser, ('~/.mozilla/firefox', '~/snap/firefox/common/.mozilla/firefox'))
|
||||
yield from map(os.path.expanduser, (
|
||||
'~/.mozilla/firefox',
|
||||
'~/snap/firefox/common/.mozilla/firefox',
|
||||
'~/.var/app/org.mozilla.firefox/.mozilla/firefox',
|
||||
))
|
||||
|
||||
|
||||
def _firefox_cookie_dbs(roots):
|
||||
|
||||
@@ -8,6 +8,7 @@ from ..utils import (
|
||||
int_or_none,
|
||||
join_nonempty,
|
||||
parse_duration,
|
||||
remove_end,
|
||||
traverse_obj,
|
||||
try_call,
|
||||
unescapeHTML,
|
||||
@@ -19,8 +20,7 @@ from ..utils import (
|
||||
|
||||
class NhkBaseIE(InfoExtractor):
|
||||
_API_URL_TEMPLATE = 'https://nwapi.nhk.jp/nhkworld/%sod%slist/v7b/%s/%s/%s/all%s.json'
|
||||
_BASE_URL_REGEX = r'https?://www3\.nhk\.or\.jp/nhkworld/(?P<lang>[a-z]{2})/ondemand'
|
||||
_TYPE_REGEX = r'/(?P<type>video|audio)/'
|
||||
_BASE_URL_REGEX = r'https?://www3\.nhk\.or\.jp/nhkworld/(?P<lang>[a-z]{2})/'
|
||||
|
||||
def _call_api(self, m_id, lang, is_video, is_episode, is_clip):
|
||||
return self._download_json(
|
||||
@@ -83,7 +83,7 @@ class NhkBaseIE(InfoExtractor):
|
||||
def _extract_episode_info(self, url, episode=None):
|
||||
fetch_episode = episode is None
|
||||
lang, m_type, episode_id = NhkVodIE._match_valid_url(url).group('lang', 'type', 'id')
|
||||
is_video = m_type == 'video'
|
||||
is_video = m_type != 'audio'
|
||||
|
||||
if is_video:
|
||||
episode_id = episode_id[:4] + '-' + episode_id[4:]
|
||||
@@ -138,9 +138,10 @@ class NhkBaseIE(InfoExtractor):
|
||||
|
||||
else:
|
||||
if fetch_episode:
|
||||
audio_path = episode['audio']['audio']
|
||||
# From https://www3.nhk.or.jp/nhkworld/common/player/radio/inline/rod.html
|
||||
audio_path = remove_end(episode['audio']['audio'], '.m4a')
|
||||
info['formats'] = self._extract_m3u8_formats(
|
||||
'https://nhkworld-vh.akamaihd.net/i%s/master.m3u8' % audio_path,
|
||||
f'{urljoin("https://vod-stream.nhk.jp", audio_path)}/index.m3u8',
|
||||
episode_id, 'm4a', entry_protocol='m3u8_native',
|
||||
m3u8_id='hls', fatal=False)
|
||||
for f in info['formats']:
|
||||
@@ -155,9 +156,11 @@ class NhkBaseIE(InfoExtractor):
|
||||
|
||||
|
||||
class NhkVodIE(NhkBaseIE):
|
||||
# the 7-character IDs can have alphabetic chars too: assume [a-z] rather than just [a-f], eg
|
||||
_VALID_URL = [rf'{NhkBaseIE._BASE_URL_REGEX}/(?P<type>video)/(?P<id>[0-9a-z]+)',
|
||||
rf'{NhkBaseIE._BASE_URL_REGEX}/(?P<type>audio)/(?P<id>[^/?#]+?-\d{{8}}-[0-9a-z]+)']
|
||||
_VALID_URL = [
|
||||
rf'{NhkBaseIE._BASE_URL_REGEX}shows/(?:(?P<type>video)/)?(?P<id>\d{{4}}[\da-z]\d+)/?(?:$|[?#])',
|
||||
rf'{NhkBaseIE._BASE_URL_REGEX}(?:ondemand|shows)/(?P<type>audio)/(?P<id>[^/?#]+?-\d{{8}}-[\da-z]+)',
|
||||
rf'{NhkBaseIE._BASE_URL_REGEX}ondemand/(?P<type>video)/(?P<id>\d{{4}}[\da-z]\d+)', # deprecated
|
||||
]
|
||||
# Content available only for a limited period of time. Visit
|
||||
# https://www3.nhk.or.jp/nhkworld/en/ondemand/ for working samples.
|
||||
_TESTS = [{
|
||||
@@ -167,17 +170,16 @@ class NhkVodIE(NhkBaseIE):
|
||||
'ext': 'mp4',
|
||||
'title': 'Japan Railway Journal - The Tohoku Shinkansen: Full Speed Ahead',
|
||||
'description': 'md5:49f7c5b206e03868a2fdf0d0814b92f6',
|
||||
'thumbnail': 'md5:51bcef4a21936e7fea1ff4e06353f463',
|
||||
'thumbnail': r're:https://.+/.+\.jpg',
|
||||
'episode': 'The Tohoku Shinkansen: Full Speed Ahead',
|
||||
'series': 'Japan Railway Journal',
|
||||
'modified_timestamp': 1694243656,
|
||||
'modified_timestamp': 1707217907,
|
||||
'timestamp': 1681428600,
|
||||
'release_timestamp': 1693883728,
|
||||
'duration': 1679,
|
||||
'upload_date': '20230413',
|
||||
'modified_date': '20230909',
|
||||
'modified_date': '20240206',
|
||||
'release_date': '20230905',
|
||||
|
||||
},
|
||||
}, {
|
||||
# video clip
|
||||
@@ -188,15 +190,15 @@ class NhkVodIE(NhkBaseIE):
|
||||
'ext': 'mp4',
|
||||
'title': 'Dining with the Chef - Chef Saito\'s Family recipe: MENCHI-KATSU',
|
||||
'description': 'md5:5aee4a9f9d81c26281862382103b0ea5',
|
||||
'thumbnail': 'md5:d6a4d9b6e9be90aaadda0bcce89631ed',
|
||||
'thumbnail': r're:https://.+/.+\.jpg',
|
||||
'series': 'Dining with the Chef',
|
||||
'episode': 'Chef Saito\'s Family recipe: MENCHI-KATSU',
|
||||
'duration': 148,
|
||||
'upload_date': '20190816',
|
||||
'release_date': '20230902',
|
||||
'release_timestamp': 1693619292,
|
||||
'modified_timestamp': 1694168033,
|
||||
'modified_date': '20230908',
|
||||
'modified_timestamp': 1707217907,
|
||||
'modified_date': '20240206',
|
||||
'timestamp': 1565997540,
|
||||
},
|
||||
}, {
|
||||
@@ -208,7 +210,7 @@ class NhkVodIE(NhkBaseIE):
|
||||
'title': 'Living in Japan - Tips for Travelers to Japan / Ramen Vending Machines',
|
||||
'series': 'Living in Japan',
|
||||
'description': 'md5:0a0e2077d8f07a03071e990a6f51bfab',
|
||||
'thumbnail': 'md5:960622fb6e06054a4a1a0c97ea752545',
|
||||
'thumbnail': r're:https://.+/.+\.jpg',
|
||||
'episode': 'Tips for Travelers to Japan / Ramen Vending Machines'
|
||||
},
|
||||
}, {
|
||||
@@ -245,7 +247,7 @@ class NhkVodIE(NhkBaseIE):
|
||||
'title': 'おはよう日本(7時台) - 10月8日放送',
|
||||
'series': 'おはよう日本(7時台)',
|
||||
'episode': '10月8日放送',
|
||||
'thumbnail': 'md5:d733b1c8e965ab68fb02b2d347d0e9b4',
|
||||
'thumbnail': r're:https://.+/.+\.jpg',
|
||||
'description': 'md5:9c1d6cbeadb827b955b20e99ab920ff0',
|
||||
},
|
||||
'skip': 'expires 2023-10-15',
|
||||
@@ -255,17 +257,100 @@ class NhkVodIE(NhkBaseIE):
|
||||
'info_dict': {
|
||||
'id': 'nw_vod_v_en_3004_952_20230723091000_01_1690074552',
|
||||
'ext': 'mp4',
|
||||
'title': 'Barakan Discovers AMAMI OSHIMA: Isson\'s Treasure Island',
|
||||
'title': 'Barakan Discovers - AMAMI OSHIMA: Isson\'s Treasure Isla',
|
||||
'description': 'md5:5db620c46a0698451cc59add8816b797',
|
||||
'thumbnail': 'md5:67d9ff28009ba379bfa85ad1aaa0e2bd',
|
||||
'thumbnail': r're:https://.+/.+\.jpg',
|
||||
'release_date': '20230905',
|
||||
'timestamp': 1690103400,
|
||||
'duration': 2939,
|
||||
'release_timestamp': 1693898699,
|
||||
'modified_timestamp': 1698057495,
|
||||
'modified_date': '20231023',
|
||||
'upload_date': '20230723',
|
||||
'modified_timestamp': 1707217907,
|
||||
'modified_date': '20240206',
|
||||
'episode': 'AMAMI OSHIMA: Isson\'s Treasure Isla',
|
||||
'series': 'Barakan Discovers',
|
||||
},
|
||||
}, {
|
||||
# /ondemand/video/ url with alphabetical character in 5th position of id
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/video/9999a07/',
|
||||
'info_dict': {
|
||||
'id': 'nw_c_en_9999-a07',
|
||||
'ext': 'mp4',
|
||||
'episode': 'Mini-Dramas on SDGs: Ep 1 Close the Gender Gap [Director\'s Cut]',
|
||||
'series': 'Mini-Dramas on SDGs',
|
||||
'modified_date': '20240206',
|
||||
'title': 'Mini-Dramas on SDGs - Mini-Dramas on SDGs: Ep 1 Close the Gender Gap [Director\'s Cut]',
|
||||
'description': 'md5:3f9dcb4db22fceb675d90448a040d3f6',
|
||||
'timestamp': 1621962360,
|
||||
'duration': 189,
|
||||
'release_date': '20230903',
|
||||
'modified_timestamp': 1707217907,
|
||||
'upload_date': '20210525',
|
||||
'thumbnail': r're:https://.+/.+\.jpg',
|
||||
'release_timestamp': 1693713487,
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/video/9999d17/',
|
||||
'info_dict': {
|
||||
'id': 'nw_c_en_9999-d17',
|
||||
'ext': 'mp4',
|
||||
'title': 'Flowers of snow blossom - The 72 Pentads of Yamato',
|
||||
'description': 'Today’s focus: Snow',
|
||||
'release_timestamp': 1693792402,
|
||||
'release_date': '20230904',
|
||||
'upload_date': '20220128',
|
||||
'timestamp': 1643370960,
|
||||
'thumbnail': r're:https://.+/.+\.jpg',
|
||||
'duration': 136,
|
||||
'series': '',
|
||||
'modified_date': '20240206',
|
||||
'modified_timestamp': 1707217907,
|
||||
},
|
||||
}, {
|
||||
# new /shows/ url format
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/shows/2032307/',
|
||||
'info_dict': {
|
||||
'id': 'nw_vod_v_en_2032_307_20240321113000_01_1710990282',
|
||||
'ext': 'mp4',
|
||||
'title': 'Japanology Plus - 20th Anniversary Special Part 1',
|
||||
'description': 'md5:817d41fc8e54339ad2a916161ea24faf',
|
||||
'episode': '20th Anniversary Special Part 1',
|
||||
'series': 'Japanology Plus',
|
||||
'thumbnail': r're:https://.+/.+\.jpg',
|
||||
'duration': 1680,
|
||||
'timestamp': 1711020600,
|
||||
'upload_date': '20240321',
|
||||
'release_timestamp': 1711022683,
|
||||
'release_date': '20240321',
|
||||
'modified_timestamp': 1711031012,
|
||||
'modified_date': '20240321',
|
||||
},
|
||||
}, {
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/shows/3020025/',
|
||||
'info_dict': {
|
||||
'id': 'nw_vod_v_en_3020_025_20230325144000_01_1679723944',
|
||||
'ext': 'mp4',
|
||||
'title': '100 Ideas to Save the World - Working Styles Evolve',
|
||||
'description': 'md5:9e6c7778eaaf4f7b4af83569649f84d9',
|
||||
'episode': 'Working Styles Evolve',
|
||||
'series': '100 Ideas to Save the World',
|
||||
'thumbnail': r're:https://.+/.+\.jpg',
|
||||
'duration': 899,
|
||||
'upload_date': '20230325',
|
||||
'timestamp': 1679755200,
|
||||
'release_date': '20230905',
|
||||
'release_timestamp': 1693880540,
|
||||
'modified_date': '20240206',
|
||||
'modified_timestamp': 1707217907,
|
||||
},
|
||||
}, {
|
||||
# new /shows/audio/ url format
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/shows/audio/livinginjapan-20231001-1/',
|
||||
'only_matching': True,
|
||||
}, {
|
||||
# valid url even if can't be found in wild; support needed for clip entries extraction
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/shows/9999o80/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
@@ -273,18 +358,21 @@ class NhkVodIE(NhkBaseIE):
|
||||
|
||||
|
||||
class NhkVodProgramIE(NhkBaseIE):
|
||||
_VALID_URL = rf'{NhkBaseIE._BASE_URL_REGEX}/program{NhkBaseIE._TYPE_REGEX}(?P<id>\w+)(?:.+?\btype=(?P<episode_type>clip|(?:radio|tv)Episode))?'
|
||||
_VALID_URL = rf'''(?x)
|
||||
{NhkBaseIE._BASE_URL_REGEX}(?:shows|tv)/
|
||||
(?:(?P<type>audio)/programs/)?(?P<id>\w+)/?
|
||||
(?:\?(?:[^#]+&)?type=(?P<episode_type>clip|(?:radio|tv)Episode))?'''
|
||||
_TESTS = [{
|
||||
# video program episodes
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/program/video/sumo',
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/shows/sumo/',
|
||||
'info_dict': {
|
||||
'id': 'sumo',
|
||||
'title': 'GRAND SUMO Highlights',
|
||||
'description': 'md5:fc20d02dc6ce85e4b72e0273aa52fdbf',
|
||||
},
|
||||
'playlist_mincount': 0,
|
||||
'playlist_mincount': 1,
|
||||
}, {
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/program/video/japanrailway',
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/shows/japanrailway/',
|
||||
'info_dict': {
|
||||
'id': 'japanrailway',
|
||||
'title': 'Japan Railway Journal',
|
||||
@@ -293,40 +381,68 @@ class NhkVodProgramIE(NhkBaseIE):
|
||||
'playlist_mincount': 12,
|
||||
}, {
|
||||
# video program clips
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/program/video/japanrailway/?type=clip',
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/shows/japanrailway/?type=clip',
|
||||
'info_dict': {
|
||||
'id': 'japanrailway',
|
||||
'title': 'Japan Railway Journal',
|
||||
'description': 'md5:ea39d93af7d05835baadf10d1aae0e3f',
|
||||
},
|
||||
'playlist_mincount': 5,
|
||||
}, {
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/program/video/10yearshayaomiyazaki/',
|
||||
'only_matching': True,
|
||||
'playlist_mincount': 12,
|
||||
}, {
|
||||
# audio program
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/ondemand/program/audio/listener/',
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/shows/audio/programs/livinginjapan/',
|
||||
'info_dict': {
|
||||
'id': 'livinginjapan',
|
||||
'title': 'Living in Japan',
|
||||
'description': 'md5:665bb36ec2a12c5a7f598ee713fc2b54',
|
||||
},
|
||||
'playlist_mincount': 12,
|
||||
}, {
|
||||
# /tv/ program url
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/tv/designtalksplus/',
|
||||
'info_dict': {
|
||||
'id': 'designtalksplus',
|
||||
'title': 'DESIGN TALKS plus',
|
||||
'description': 'md5:47b3b3a9f10d4ac7b33b53b70a7d2837',
|
||||
},
|
||||
'playlist_mincount': 20,
|
||||
}, {
|
||||
'url': 'https://www3.nhk.or.jp/nhkworld/en/shows/10yearshayaomiyazaki/',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
@classmethod
|
||||
def suitable(cls, url):
|
||||
return False if NhkVodIE.suitable(url) else super().suitable(url)
|
||||
|
||||
def _extract_meta_from_class_elements(self, class_values, html):
|
||||
for class_value in class_values:
|
||||
if value := clean_html(get_element_by_class(class_value, html)):
|
||||
return value
|
||||
|
||||
def _real_extract(self, url):
|
||||
lang, m_type, program_id, episode_type = self._match_valid_url(url).group('lang', 'type', 'id', 'episode_type')
|
||||
episodes = self._call_api(
|
||||
program_id, lang, m_type == 'video', False, episode_type == 'clip')
|
||||
program_id, lang, m_type != 'audio', False, episode_type == 'clip')
|
||||
|
||||
entries = []
|
||||
def entries():
|
||||
for episode in episodes:
|
||||
episode_path = episode.get('url')
|
||||
if not episode_path:
|
||||
continue
|
||||
entries.append(self._extract_episode_info(
|
||||
urljoin(url, episode_path), episode))
|
||||
if episode_path := episode.get('url'):
|
||||
yield self._extract_episode_info(urljoin(url, episode_path), episode)
|
||||
|
||||
html = self._download_webpage(url, program_id)
|
||||
program_title = clean_html(get_element_by_class('p-programDetail__title', html))
|
||||
program_description = clean_html(get_element_by_class('p-programDetail__text', html))
|
||||
program_title = self._extract_meta_from_class_elements([
|
||||
'p-programDetail__title', # /ondemand/program/
|
||||
'pProgramHero__logoText', # /shows/
|
||||
'tAudioProgramMain__title', # /shows/audio/programs/
|
||||
'p-program-name'], html) # /tv/
|
||||
program_description = self._extract_meta_from_class_elements([
|
||||
'p-programDetail__text', # /ondemand/program/
|
||||
'pProgramHero__description', # /shows/
|
||||
'tAudioProgramMain__info', # /shows/audio/programs/
|
||||
'p-program-description'], html) # /tv/
|
||||
|
||||
return self.playlist_result(entries, program_id, program_title, program_description)
|
||||
return self.playlist_result(entries(), program_id, program_title, program_description)
|
||||
|
||||
|
||||
class NhkForSchoolBangumiIE(InfoExtractor):
|
||||
|
||||
@@ -92,7 +92,7 @@ class PatreonIE(PatreonBaseIE):
|
||||
'thumbnail': 're:^https?://.*$',
|
||||
'upload_date': '20150211',
|
||||
'description': 'md5:8af6425f50bd46fbf29f3db0fc3a8364',
|
||||
'uploader_id': 'TraciJHines',
|
||||
'uploader_id': '@TraciHinesMusic',
|
||||
'categories': ['Entertainment'],
|
||||
'duration': 282,
|
||||
'view_count': int,
|
||||
@@ -106,8 +106,10 @@ class PatreonIE(PatreonBaseIE):
|
||||
'availability': 'public',
|
||||
'channel_follower_count': int,
|
||||
'playable_in_embed': True,
|
||||
'uploader_url': 'http://www.youtube.com/user/TraciJHines',
|
||||
'uploader_url': 'https://www.youtube.com/@TraciHinesMusic',
|
||||
'comment_count': int,
|
||||
'channel_is_verified': True,
|
||||
'chapters': 'count:4',
|
||||
},
|
||||
'params': {
|
||||
'noplaylist': True,
|
||||
@@ -176,6 +178,27 @@ class PatreonIE(PatreonBaseIE):
|
||||
'uploader_url': 'https://www.patreon.com/thenormies',
|
||||
},
|
||||
'skip': 'Patron-only content',
|
||||
}, {
|
||||
# dead vimeo and embed URLs, need to extract post_file
|
||||
'url': 'https://www.patreon.com/posts/hunter-x-hunter-34007913',
|
||||
'info_dict': {
|
||||
'id': '34007913',
|
||||
'ext': 'mp4',
|
||||
'title': 'Hunter x Hunter | Kurapika DESTROYS Uvogin!!!',
|
||||
'like_count': int,
|
||||
'uploader': 'YaBoyRoshi',
|
||||
'timestamp': 1581636833,
|
||||
'channel_url': 'https://www.patreon.com/yaboyroshi',
|
||||
'thumbnail': r're:^https?://.*$',
|
||||
'tags': ['Hunter x Hunter'],
|
||||
'uploader_id': '14264111',
|
||||
'comment_count': int,
|
||||
'channel_follower_count': int,
|
||||
'description': 'Kurapika is a walking cheat code!',
|
||||
'upload_date': '20200213',
|
||||
'channel_id': '2147162',
|
||||
'uploader_url': 'https://www.patreon.com/yaboyroshi',
|
||||
},
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
@@ -250,20 +273,13 @@ class PatreonIE(PatreonBaseIE):
|
||||
v_url = url_or_none(compat_urllib_parse_unquote(
|
||||
self._search_regex(r'(https(?:%3A%2F%2F|://)player\.vimeo\.com.+app_id(?:=|%3D)+\d+)', embed_html, 'vimeo url', fatal=False)))
|
||||
if v_url:
|
||||
return {
|
||||
**info,
|
||||
'_type': 'url_transparent',
|
||||
'url': VimeoIE._smuggle_referrer(v_url, 'https://patreon.com'),
|
||||
'ie_key': 'Vimeo',
|
||||
}
|
||||
v_url = VimeoIE._smuggle_referrer(v_url, 'https://patreon.com')
|
||||
if self._request_webpage(v_url, video_id, 'Checking Vimeo embed URL', fatal=False, errnote=False):
|
||||
return self.url_result(v_url, VimeoIE, url_transparent=True, **info)
|
||||
|
||||
embed_url = try_get(attributes, lambda x: x['embed']['url'])
|
||||
if embed_url:
|
||||
return {
|
||||
**info,
|
||||
'_type': 'url',
|
||||
'url': embed_url,
|
||||
}
|
||||
if embed_url and self._request_webpage(embed_url, video_id, 'Checking embed URL', fatal=False, errnote=False):
|
||||
return self.url_result(embed_url, **info)
|
||||
|
||||
post_file = traverse_obj(attributes, 'post_file')
|
||||
if post_file:
|
||||
|
||||
@@ -155,6 +155,7 @@ class TikTokBaseIE(InfoExtractor):
|
||||
'locale': 'en',
|
||||
'ac2': 'wifi5g',
|
||||
'uoo': '1',
|
||||
'carrier_region': 'US',
|
||||
'op_region': 'US',
|
||||
'build_number': self._APP_INFO['app_version'],
|
||||
'region': 'US',
|
||||
|
||||
@@ -707,6 +707,7 @@ class VKWallPostIE(VKBaseIE):
|
||||
|
||||
|
||||
class VKPlayBaseIE(InfoExtractor):
|
||||
_BASE_URL_RE = r'https?://(?:vkplay\.live|live\.vkplay\.ru)/'
|
||||
_RESOLUTIONS = {
|
||||
'tiny': '256x144',
|
||||
'lowest': '426x240',
|
||||
@@ -765,7 +766,7 @@ class VKPlayBaseIE(InfoExtractor):
|
||||
|
||||
|
||||
class VKPlayIE(VKPlayBaseIE):
|
||||
_VALID_URL = r'https?://vkplay\.live/(?P<username>[^/#?]+)/record/(?P<id>[a-f0-9-]+)'
|
||||
_VALID_URL = rf'{VKPlayBaseIE._BASE_URL_RE}(?P<username>[^/#?]+)/record/(?P<id>[\da-f-]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://vkplay.live/zitsmann/record/f5e6e3b5-dc52-4d14-965d-0680dd2882da',
|
||||
'info_dict': {
|
||||
@@ -776,13 +777,16 @@ class VKPlayIE(VKPlayBaseIE):
|
||||
'uploader_id': '13159830',
|
||||
'release_timestamp': 1683461378,
|
||||
'release_date': '20230507',
|
||||
'thumbnail': r're:https://images.vkplay.live/public_video_stream/record/f5e6e3b5-dc52-4d14-965d-0680dd2882da/preview\?change_time=\d+',
|
||||
'thumbnail': r're:https://[^/]+/public_video_stream/record/f5e6e3b5-dc52-4d14-965d-0680dd2882da/preview',
|
||||
'duration': 10608,
|
||||
'view_count': int,
|
||||
'like_count': int,
|
||||
'categories': ['Atomic Heart'],
|
||||
},
|
||||
'params': {'skip_download': 'm3u8'},
|
||||
}, {
|
||||
'url': 'https://live.vkplay.ru/lebwa/record/33a4e4ce-e3ef-49db-bb14-f006cc6fabc9/records',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
@@ -802,7 +806,7 @@ class VKPlayIE(VKPlayBaseIE):
|
||||
|
||||
|
||||
class VKPlayLiveIE(VKPlayBaseIE):
|
||||
_VALID_URL = r'https?://vkplay\.live/(?P<id>[^/#?]+)/?(?:[#?]|$)'
|
||||
_VALID_URL = rf'{VKPlayBaseIE._BASE_URL_RE}(?P<id>[^/#?]+)/?(?:[#?]|$)'
|
||||
_TESTS = [{
|
||||
'url': 'https://vkplay.live/bayda',
|
||||
'info_dict': {
|
||||
@@ -813,7 +817,7 @@ class VKPlayLiveIE(VKPlayBaseIE):
|
||||
'uploader_id': '12279401',
|
||||
'release_timestamp': 1687209962,
|
||||
'release_date': '20230619',
|
||||
'thumbnail': r're:https://images.vkplay.live/public_video_stream/12279401/preview\?change_time=\d+',
|
||||
'thumbnail': r're:https://[^/]+/public_video_stream/12279401/preview',
|
||||
'view_count': int,
|
||||
'concurrent_view_count': int,
|
||||
'like_count': int,
|
||||
@@ -822,6 +826,9 @@ class VKPlayLiveIE(VKPlayBaseIE):
|
||||
},
|
||||
'skip': 'livestream',
|
||||
'params': {'skip_download': True},
|
||||
}, {
|
||||
'url': 'https://live.vkplay.ru/lebwa',
|
||||
'only_matching': True,
|
||||
}]
|
||||
|
||||
def _real_extract(self, url):
|
||||
|
||||
Reference in New Issue
Block a user