Update On Sun Apr 7 20:25:36 CEST 2024

This commit is contained in:
github-action[bot]
2024-04-07 20:25:37 +02:00
parent e2eecadc74
commit 258ae1804d
80 changed files with 1034 additions and 1053 deletions

1
.github/update.log vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -384,41 +384,46 @@ 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 msg = t!("dialog.custom_app_dir_migrate", path = path_str).to_string();
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 || {
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::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)?;
}
Ok::<_, anyhow::Error>(())
})
.await;
} else {
set_app_dir(&path)?;
}
Ok::<_, anyhow::Error>(())
})
.await;
wrap_err!(wrap_err!(res)?)?;
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 {

View File

@@ -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()? {

View File

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

View File

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

View File

@@ -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")]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"),

View File

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

View File

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

View File

@@ -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>`
![](monitor/web.png)
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 上看到流量监控哟
![](monitor/proxy-traffic.png)
</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 的延迟监控
![](monitor/ping.png)
- 流量监控
![](monitor/traffic.png)
## 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/)

View File

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

View File

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

View File

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

View File

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

View File

@@ -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{
cfg: cfg,
l: zap.S().Named("relay"),
Name: cfg.Label,
LocalTCPAddr: localTCPAddr,
ListenType: cfg.ListenType,
TransportType: cfg.TransportType,
TP: transporter.NewRelayTransporter(cfg, connMgr),
relayServer: s,
cfg: cfg,
l: zap.S().Named("relay"),
}
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()
}()
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()
}()
}
}
go func() {
r.l.Infof("Start TCP Relay Server:%s", r.cfg.DefaultLabel())
errCh <- r.relayServer.ListenAndServe()
}()
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.Errorf(err.Error())
}
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()
}

View File

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

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

View File

@@ -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")
}
}
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")
}
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)
}
return nil
}

View File

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

View File

@@ -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
lis *net.TCPListener
l *zap.SugaredLogger
*baseTransporter
localTCPAddr *net.TCPAddr
lis *net.TCPListener
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)
}

View File

@@ -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")),
}
return s, nil
}
type MTCPServer struct {
raw *RawClient
listenAddr string
listener net.Listener
l *zap.SugaredLogger
errChan chan error
connChan chan net.Conn
}
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()
}

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package transporter
package buffer
import (
"github.com/Ehco1996/ehco/internal/constant"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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:=

View File

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

View File

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

View File

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

View File

@@ -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 "使用的网络接口"

View File

@@ -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,
]]
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 {
@@ -394,7 +397,7 @@ local chain_sslocal = {
mode = (proto:find("tcp,udp") and "tcp_and_udp") or proto .. "_only",
protocol = "redir",
tcp_redir = "redirect",
--tcp_redir = "tproxy",
--tcp_redir = "tproxy",
udp_redir = "tproxy"
},
socks_port ~= "0" and {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 "", ""

View File

@@ -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]:
if self._versions.get(p):
self.basename = self.probe_basename = p
break
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):
basenames[i] = p
break
self.basename, self.probe_basename = basenames
@property
def available(self):

View File

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

View File

@@ -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': 'Todays 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 = []
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))
def entries():
for episode in episodes:
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):

View File

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

View 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',

View File

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