如何搭建一个基于WebSocket的反向隧道

背景

如何利用VPS构建一个反向隧道?

  • 有了一台VPS,但是因为在大陆访问速度慢的原因,所以需要使用CloudFlare加速
  • 我需要在外网访问家里的一些服务,所以需要一个反向tunnel,即能够从外网访问内网服务
  • 问题是,frp等工具都不太好用,一是直接使用容易被流量识别;二是不支持通过CloudFlare加速
  • 还有就是,原本的域名上也有一个博客,所以需要特定path才走tunnel

在上面的限制下,试了好几个工具,最终发现wstunnel能够满足上面的要求。

最终方案画成图就是:

按照图中的方案,我们一步一步搭建。

服务端/VPS

从图中可以看出,在VPS上,我们需要一个nginx,用来:

  1. 正常访问流量走web服务,特定前缀的websocket流量转发给wstunnel-server
  2. wstunnel-server从nginx接收数据,由于是本地访问,所以只需要使用ws协议接收
  3. wstunnel-server需要有一定的认证能力
阅读更多

在WSL上开启IPv6

最近在折腾IPv6,很多IPv6的命令,在 WSL下没法执行。

比如 ssh root@<ipv6> 或者 curl -6 ipv6.google.com,都是提示不支持IPv6,但实际上Windows是有IPv6地址的。

于是搜索了下,也是有很多人吐槽,有挺多英文版的”都2023年了,WSL还是不支持IPv6”等吐槽。

但最近,WSL 2023年9月的更新,引入了(实验性的)IPv6支持。

当然,还有一些别的特性,一并介绍如下:

  1. autoMemoryReclaim 特性允许 WSL 虚拟机通过在使用期间回收缓存内存来减少内存使用。
  2. Sparse VHD 特性会在使用 WSL 时自动缩小它的虚拟硬盘。
  3. 镜像组网模式(Mirrored mode networking)增强了网络兼容性,并为 WSL 引入了新特性(IPv6支持等)。
  4. dnsTunneling 特性修改了 WSL 解析 DNS 请求的方式,以提高网络兼容性。
  5. 防火墙(firewall)特性将 Windows 防火墙规则应用于 WSL,并为 WSL 虚拟机提供高级防火墙控制。
  6. autoProxy 特性使 WSL 能够自动使用来自 Windows 的代理信息,从而提高网络兼容性。

目前来看,前三点都是比较有用的。我们看下如何开启。

要启用这些功能,请在 Windows Home目录中创建一个 .wslconfig 文件 (例如C:\Users\<yourusername>\.wslconfig),并将以下部分添加到该文件中。

1
2
3
[experimental]
autoMemoryReclaim=gradual
networkingMode=mirrored
阅读更多

谈谈Java Volatile的设计

java volatile为什么要这么设计?

最近在知乎上看见一个有意思的问题:

java volatile为什么要这么设计?
如图所示,这种指令重排规则背后设计的是出于什么原因考虑?我想知道why? 而不是What/How

这个问题看似简单,但是后面其实隐藏着计算机架构的演变:

CPU的多核心时代

说到这儿,就要开始聊一下多线程/多处理器的发展史了。初期计算机性能的提升,主要靠的是主频的提升,后来主频提升遇到了困难,然后就开始了多核处理器的时代。

这里面有一个问题,一个程序可不是天然就能同时跑在多个CPU核心上的,如何让程序员利用多核心处理器提升效率,是一个难题。

这个问题乍一看很简单,我们已经有了线程模型了,直接套用到多核架构上不就可以了么?

但是不行,在原来的单核心架构中,我们的内存模型是 顺序一致性(Sequential consistency)

  1. 每个线程内部的指令都是按照程序规定的顺序(program order)执行的(单个线程的视角)
  2. 线程执行的交错顺序可以是任意的,但是所有线程所看见的整个程序的总体执行顺序都是一样的(整个程序的视角)
阅读更多

听说你没法在JRE中使用arthas?不,你可以

本文是《容器中的Java》系列文章之 5/n ,欢迎关注后续连载 :) 。

之前经常遇到的问题是,排查问题需要挂arthas,但客户用的是JRE,没法挂载arthas。就只能让客户更换成JDK,再重新部署、排查问题。

很多有用的现场,在这个过程中也会丢失,最终导致问题排查效率降低。于是就探索了下如何在JRE环境中,使用artahs。

复现问题

如果一个Bug 没法复现,研发大概率是无法修复的。 —— by 网友

我们写一个Java例子和Dockerfile:

./src/main/java/Main.java
1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) throws Exception {
while (true) {
System.out.println("hello!");
Thread.sleep(30 * 1000);
}
}
}
./Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM openjdk:8-jdk-alpine as builder
COPY ./ /app
WORKDIR /app/src/main/java/
# 编译java文件
RUN javac Main.java

# 运行时容器使用JRE
FROM openjdk:8-jre-alpine
RUN apk add bash curl busybox-extras
WORKDIR /app/src/main/java/
# 将arthas copy 到容器中
COPY --from=hengyunabc/arthas:latest /opt/arthas /opt/arthas
COPY --from=builder /app/src/main/java/ /app/src/main/java/
CMD ["java", "Main"]

构建并正常启动应用,并尝试用arthas attach,此处为了便于了解原理,我们使用as.sh来执行:

阅读更多

为什么在容器中1号进程挂不上arthas?

本文是《容器中的Java》系列文章之 4/n ,欢迎关注后续连载 :) 。

最近在容器环境中,发现在Java进程是1号进程的情况下,无法使用arthas,提示AttachNotSupportedException: Unable to get pid of LinuxThreads manager thread。具体操作和报错如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# java -jar arthas-boot.jar
[INFO] arthas-boot version: 3.5.6
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 1 com.alibabacloud.mse.demo.ZuulApplication
1
[INFO] arthas home: /home/admin/.opt/ArmsAgent/arthas
[INFO] Try to attach process 1
[ERROR] Start arthas failed, exception stack trace:
com.sun.tools.attach.AttachNotSupportedException: Unable to get pid of LinuxThreads manager thread
at sun.tools.attach.LinuxVirtualMachine.<init>(LinuxVirtualMachine.java:86)
at sun.tools.attach.LinuxAttachProvider.attachVirtualMachine(LinuxAttachProvider.java:78)
at com.sun.tools.attach.VirtualMachine.attach(VirtualMachine.java:250)
at com.taobao.arthas.core.Arthas.attachAgent(Arthas.java:117)
at com.taobao.arthas.core.Arthas.<init>(Arthas.java:27)
at com.taobao.arthas.core.Arthas.main(Arthas.java:166)
[INFO] Attach process 1 success.

之前也遇到过,总是调整了下镜像,让Java进程不是1号进程就可以了。但这个不是长久之计,还是要抽时间看下这个问题。

复现问题

我们创建如下项目,来复现这个问题:

./src/main/java/Main.java
1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) throws Exception {
while (true) {
System.out.println("hello!");
Thread.sleep(30 * 1000);
}
}
}
./Dockerfile
1
2
3
4
5
FROM openjdk:8u212-jdk-alpine
COPY ./ /app
WORKDIR /app/src/main/java/
RUN javac Main.java
CMD ["java", "Main"]

然后正常启动应用,并尝试用arthas,或者jstack:

阅读更多

Z-Library挂了?怎么办?

Z-Library

Z-Library 是一个免费下载电子书的网站。不过最近由于Z-Library的域名被查封,所以现在没法访问了。

Z-Library 刚开始的时候是 LibGen 的镜像站点,正因如此,Z-Library 上的大部分图书,都能在 LibGen找到;但有一些书是用户自己上传到 Z-Library 的,所以就没法在 LibGen 找到了。

这种情况,一般可以在维基百科上找下官方信息,就能发现 Z-Library 还提供了一个 onion 域名可以访问。

所以,我们可以下载一个 Tor 浏览器,或者 Brave 浏览器,然后访问 http://zlibrary​24tuxziyiyfr7​zd46ytefdqbqd2axkmxm​4o5374ptpc52fad.onion 来继续访问了:

当然, Tor 网络虽然链路是分布式的,但是其中的 server 却是集中式的,个人觉得不算是“分布式Web”

  • 网上也会有一些 Z-Library 镜像,也可以搜索下使用,比如 zlib.wikizlib.quest等。

Z-Library 存档

当然,这种网站一般也会有人收集离线存档,可以在 pilimi.org 找到 Z-Library 存档。

阅读更多

如何使用rust写内核模块

近年来,Rust语言以内存安全、高可靠性、零抽象等能力获得大量开发者关注,而这些特性恰好是内核编程中所需要的,所以我们来尝试下如何用rust来写Linux内核模块。

Rust与内核模块

虽然Rust支持已经在Linux Kernel 6.1版本合并到主线了,所以理论上来说,开发者可以使用Rust来为Linux 6.1写内核模块。
但实际开发工作中,内核版本不是最新的,比如Debian 11的内核就是5.10版本的,那么在这种情况下,该如何用Rust写内核模块呢?

原理

  1. Rust如何向内核注册回调、如何调用内核代码。Rust和C的互操作性
  2. Rust如何编译到目标平台上。Rust的target配置
  3. Rust如何申明内核模块入口、并添加特殊section。Rust内核模块的二进制约定

Rust和C的互操作性

第一个问题基本上就是C和Rust的互操作性了。
得益于Rust的抽象层次,C语言和Rust的互相调用都是比较容易的。rust官方也提供了bindgen这样,根据.h文件生成.rs文件的库。
这样一来,貌似直接使用bindgen将内核头文件翻译成.rs就可以了?
但还有一个问题,如何获取内核头文件路径呢?
可以使用一个dummy内核模块,在编译过程中把编译参数导出来,其中包含了头文件路径,编译参数等,用于bindgen生成代码。

Rust的target配置

内核模块和普通的程序相比,主要的不同在于:

  1. 内核模块是freestanding的,没有libc、内存分配也比较原始
  2. 内核模块对于异常处理等有特殊约定
阅读更多

如果java虚拟线程稳定了,是不是有一大批框架和工具要重写?

知乎上有一个提问,“如果java虚拟线程稳定了,是不是有一大批框架和工具要重写?”,我整理了下我的知乎回答:

首先,抛开虚拟线程、协程,我们看看现在的架构。

在顶层,开辟出很多调度单位(线程、协程);在底层,有很多的IO原语(accept、read、write);在中间,有很多的组织逻辑。

协程主要做的就是,在底层执行IO原语的时候,暂时保存上下文,等执行结束后,再恢复上下文继续执行。

那么在现有的协程实现方案上,就出现了两种模式:

有栈协程,就是协程上下文包括了callstack,当IO完成、恢复上下的时候,连带着callstack恢复。那么对于callstack中的各个caller(调用者)和callee(被调用者),都感知不到整个协程的调用过程,自然代码就不用修改了。

有栈协程的优点就是不需要现有的代码改动太多,只需要调度单位创建+调度器+IO操作方面改动即可。但开发者的把控就比较弱,不能干涉调度过程。

Go和Java的Loom,都是有栈协程。

无栈协程,就是协程上下问不包含callstack,恢复上下文的时候,也只是通知上下文完成了。接下来干什么事情,还是得重新构建callstack。

比如 A -> B -> C -> IO函数,当出现IO调用,需要保留上下文,那么就需要C->B->A逐个返回上下文,恢复的时候也是 A -> B ->C去恢复执行。

阅读更多

让 Java Agent 在 Dragonwell 上更好用

本文是《容器中的Java》系列文章之 3/n ,欢迎关注后续连载 :) 。

背景

Java Agent 技术能够动态修改 Java 应用程序行为,而不用重新修改代码。

正是因为这些特点,很多中间件团队、云厂商团队、开源产品,开始使用 Java Agent 技术来提供一些基础能力,比如 Apache Skywalking、OpenTelemetry 都提供了 Java Agent。

在早前,中间件团队通过SDK提供能力(比如可观测、微服务治理能力等);但中间件团队每次新增特性、修复缺陷,都需要各个业务方更新SDK版本、重新发布。

随着公司架构越来越复杂,随着云厂商开始提供中间件能力,这种逐个推动SDK使用方更新的方式越来越麻烦。

而用了 Java Agent 之后,业务同学只需要写业务代码;中间件能力通过设置环境变量来动态注入Java Agent来实现。Java Agent的更新,也只需要重启应用即可。

问题

我们以一个微服务demo为例。先在一个Kubernetes集群中部署demo,然后通过JAVA_TOOL_OPTIONS使用用Java Agent:

1
2
$ echo $JAVA_TOOL_OPTIONS
-javaagent:/home/admin/.opt/ArmsAgent/arms-bootstrap-1.7.0-SNAPSHOT.jar ...
阅读更多

LeetCode 周赛 311

第一题 2413. Smallest Even Multiple

给你一个正整数 n ,返回 2 和 n 的最小公倍数(正整数)。

显而易见的:

1
2
3
4
5
6
7
func smallestEvenMultiple(n int) int {
if n%2==1{
return n*2
}else{
return n
}
}

第二题 2414. Length of the Longest Alphabetical Continuous Substring

最近恰好做过dp的题目,所以立马dp:

  • 状态定义为 下标i -> 以i结尾的string中,字母序连续的字符串最大长度
  • 状态转移 s[i]和s[i-1]连续,那么在前面基础上+1,如果不连续,则置为1

最后,遍历i,拿最大长度即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func longestContinuousSubstring(s string) int {
dp := make([]int, len(s))
dp[0] = 1
for i := 1; i < len(s); i++ {
if s[i] == s[i-1]+1 {
dp[i] = dp[i-1] + 1
} else {
dp[i] = 1
}
}
res := 1
for i := 0; i < len(dp); i++ {
if res < dp[i] {
res = dp[i]
}
}
return res
}
阅读更多