Tom's Blog

写一些有意思的东西。

0%

全局热键,即在所有界面都生效的热键。由于它无需打开软件界面即可对软件做出操作,它极大地提升我们的工作效率。出于便捷的目的,大部分软件都会注册全局热键。然而,随着软件的增多,不同软件之间的全局热键极有可能发生冲突。微软官方的Spy++小工具可以很好地解决这个问题。

本文以网易云音乐的Ctrl+Alt+Up热键失效为例。

安装Spy++

安装Spy++有两种方法。第一,安装Visual Studio 2019的C++开发组件,Spy++就会作为调试工具被附带安装(How to: Start Spy++);第二,Spy++是独立运行的工具,因此可以从GitHub上下载提取版(链接)。

使用Spy++

打开Spy++。选择Spy - Log Messages...

Windows选项卡中,勾选All Windows in System

Messages选项卡中,先点击Clear All取消选中全部,再找到并选中WM_HOTKEY

点击OK后,出现了一个新的空白的子窗口。所有的热键事件都会在这里显示。这时候按下需要检查的热键,Ctrl+Alt+Up,子窗口中增加了一条消息记录。

右键这条消息记录,选择Properties...

窗口中显示的是这条消息的信息。点击Window Handler,查看处理了该消息的窗口的信息。

窗口中显示的是处理了该消息的窗口的信息。

我们可以发现,是Intel GCC处理了这次热键事件,也就是说,是它占用了这个热键。进入Intel GCC的设置界面,关闭热键,网易云的热键就恢复了。

参考文章

  1. win10 快捷键冲突检测工具与方法,https://blog.csdn.net/zw521cx/article/details/102665663

C中常常可以看到诸如const int *const int * constint const *这样的写法。这些语法外表相似,实际却有很大不同。为了分辨它们,可以用从右向左阅读的技巧(原帖地址:https://stackoverflow.com/questions/1143262/what-is-the-difference-between-const-int-const-int-const-and-int-const)。

  • int * - pointer to int,指针指向整数
  • int const * - pointer to const int,指针指向常量整数
  • int * const - const pointer to int,常量指针指向整数
  • int const * const - const pointer to const int,常量指针指向常量整数

第一个const和类型可以互换:

  • const int * == int const *
  • const int * const == int const * const

如此一来,就算忘记了常量指针指针常量谁是谁也没有关系,通过字面上的顺序就能分辨指针的不同。确实是一个很不错的方法!

Minecraft Java 版的服务器选择十分多样,除了官方的 Vanilla 服务器,还有 Bukkit,Spigot 等选择。如果需要增加功能,官方服可以安装 Mod,非官方服也可以安装插件。然而,Minecraft 基岩版的服务器却是截然不同的情况,第三方服务器支持安装插件,但却有诸如原生不支持生成实体的致命缺陷;官方服务器虽然很多功能还未完善,但至少能提供一个完整的游戏体验。根据官方的说法,Linux 版的基岩服务器只支持 Ubuntu 系统。不幸的是,我现有的服务器均为 CentOS 系统,最粗暴的办法是使用 Docker 运行 Ubuntu 来模拟环境,但这样拐弯抹角的方法让管理变得麻烦了。经过我的一番摸索,我找到一个可以让基岩版服务器在 CentOS 上运行的方法,记录如下。

首先,我参考了https://www.bilibili.com/read/cv2982307/中的方案。之所以 Minecraft 基岩版服务器不能在 CentOS 上运行,是因为 CentOS 缺少它需要的库。网上有的方案选择手工安装很多软件包来补齐这些库,缺点是麻烦、有损坏系统的风险。这个方案则是将缺失的库复制到服务器目录下,再用 LD_PRELOAD 来指定程序在运行时使用这些库。然而,我按照这个方法下载了前人提供的库之后,依然无法运行服务器,报错:

1
-bash: ./libnghttp2.so.14: No such file or directory

看起来还是少了一个库,于是我从 Docker 的 Ubuntu 容器中复制了这个库出来,服务器终于可以正常运行了。我整理了一下这些库,重新打包了一份上传到了 GitHub(仓库地址)。

以下是完整的服务器安装教程:

新建目录:

1
2
mkdir bedrock-server
cd bedrock-server

下载、解压服务器:

1
2
3
4
wget https://minecraft.azureedge.net/bin-linux/bedrock-server-1.16.0.2.zip
yum install unzip -y
unzip bedrock-server-1.16.0.2.zip
rm -f bedrock-server-1.16.0.2.zip

下载、解压库:

1
2
3
4
wget https://github.com/tomzhu1024/bedrock-server-libraries/raw/master/packed.tar.gz
tar -xzvf packed.tar.gz
rm -f packed.tar.gz
chmod +x start.sh

启动服务器:

1
./start.sh

年前在家,发现所有的设备都会时不时地遇到网速极慢的情况。一番排查后发现,问题来自华硕的黑科技——Smart Connect 三频合一功能。每当路由器觉得某个客户端的信号不好时,就会主动把它踢到 2.4G 频道上面去;而由于邻居的路由器信号过强,我家中的无线电环境——尤其是 2.4G 频道——十分的错综复杂。过强的干扰往往会降低信噪比,增加冲突的可能性。在网上一番搜查后,我发现了一种看似很高深的说法——降低路由器的 RTS 值即可提升路由器在强干扰环境中的性能。我首先搜索了一番什么是 RTS。Wikipedia 是这么说的:

This protocol was designed under the assumption that all nodes have the same transmission ranges, and does not solve the hidden terminal problem. The RTS/CTS frames can cause a new problem called the exposed terminal problem in which a wireless node that is nearby, but is associated with another access point, overhears the exchange and then is signaled to back off and cease transmitting for the time specified in the RTS.

RTS/CTS is an additional method to implement virtual carrier sensing in carrier sense multiple access with collision avoidance (CSMA/CA). By default, 802.11 relies on physical carrier sensing only, which is known to suffer from the hidden node problem.

The RTS/CTS packet size threshold is 0–2347 octets. Typically, sending RTS/CTS frames does not occur unless the packet size exceeds this threshold. If the packet size that the node wants to transmit is larger than the threshold, the RTS/CTS handshake gets triggered. Otherwise, the data frame gets sent immediately.

IEEE 802.11 RTS/CTS mechanism could help solve exposed node problem as well, only if the nodes are synchronized and packet sizes and data rates are the same for both the transmitting nodes. When a node hears an RTS from a neighboring node, but not the corresponding CTS, that node can deduce that it is an exposed node and is permitted to transmit to other neighboring nodes.[1] If the nodes are not synchronized (or if the packet sizes are different or the data rates are different) the problem may occur that the exposed node will not hear the CTS or the ACK during the transmission of data of its neighbor.

——摘自Wikipedia

简单的来说,RTS/CTS 机制是用来减少无线信道中的冲突的。在经典的“隐藏终端”问题中,基站A向基站B发送了数据,而基站C并未侦测到基站A的存在(这可能是基站的位置分布导致的),也向基站B发送了数据,最后基站A和基站C的数据同时到达基站B,导致所有的数据都丢失了。RTS/CTS 机制则相当于要求所有基站在发送数据前先要说一声“我要发数 据了”,也就是 Request To Send (RTS);而接受数据的基站会在信道变空闲后回复“你可以发数据了”,也就是 Clear To Send (CTS)。这样就多了一步握手的协议,确保了之前那样的冲突不会发生。

路由器中的 RTS 门槛说的是同一个东西。RTS 门槛是一个位于 0-2347 之间的整数。它的意思是,发送大小大于这个值的封包前需要进行 RTS/CTS 机制。如果设置成 0,就意味着总是进行 RTS/CTS 机制;如果设置成 2347,那么就意味着从不进行 RTS/CTS 机制,因为以太网的最大封包大小是 1518 字节(参考文章)。我的路由器中的默认值便是 2347,如图:

那么改变 RTS 门槛的值,到底会不会提升 2.4G WiFi 的性能呢?我决定实际测试一下。为了测试的科学性,我关闭了两个 5G 频道、断开了无关设备的连接、仅将两台测试电脑连接到了 2.4G 频道上。接着,我在不同的 RTS 门槛下,运行相同的网络性能测试。我设置的测试含有 3 条 1M 封包的线程和 3 条 500K 封包的线程,每个线程发送 100M 数据。

RTS 门槛=2347:

RTS 门槛=2200:

RTS 门槛=2000:

RTS 门槛=1800:

RTS 门槛=1600:

RTS 门槛=1400:

RTS 门槛=1200:

RTS 门槛=1000:

RTS 门槛=800:

RTS 门槛=600:

RTS 门槛=0:

分析一下这些数据点,做了个简单的统计:

横轴是 RTS 门槛值,纵轴是带宽和响应时间。我发现,不同的 RTS 门槛值对于无线网络的性能几乎没有影响。当然由于我没有进一步地去测试 0-600 这个值域,可能在这个值域中,网络性能会表现出某种规律。但是,至少基于现有的测试结果,我可以认为降低 RTS 门槛的值无法提升网络性能。关闭 Smart Connect,或许是一个更为有效的办法。

Promise 是2015年推出的 ECMAScript 6 中最重要的一个特征之一。Async/await 则是 AsyncFunction 特性中的关键字。异步在 JavaScript 中是很常用的,一是因为 JavaScript 是单线程运行的,二是因为JavaScript主要服务于Web,而Web中经常有需要异步处理的情景(如 AJAX)。Promise 和 async/await 关键字组合起来,可以极大地简化异步逻辑的代码,使代码更易阅读、维护。有关两者,网上已经有很多文章,也讲得十分清晰(比如关于 Promise 的这篇关于 async/await 的这篇),这里便不再赘述。本文主要记录我初学这两个概念时感到不易理解的几个点,以及后来我自行理解它们的方法。

问题

Promise 很优雅的一点就是它支持链式调用。这样一来,原先层层相套的回调可以写成更加规整的链式调用。比如以下就是一个简单的链式调用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
new Promise(resolve => {
setTimeout(() => {
console.log('one');
resolve();
}, 1000);
}).then(() => {
console.log('two');
});

// 输出:
// one
// two

首先,我们创建了一个 Promise 的实例,并且传入一个函数,这个函数接受一个参数 resolve,并且延时 1秒后调用了 resolveresolve 的功能其实就是将这个 Promise 的状态设置为 fulfilled。当这个 Promise 的状态转为 fulfilled 后,第一个 then() 方法中的函数就会被执行。因此我们会得到上面的输出。

但是,当我想串联多个 then() 方法、且其中含有耗时(异步调用)操作时,链式调用似乎就不再按照顺序执行了。比如,以下面的代码为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
new Promise(resolve => {
setTimeout(() => {
console.log('one');
resolve();
}, 1000);
}).then(() => {
setTimeout(() => {
console.log('two');
}, 1000);
}).then(() => {
setTimeout(() => {
console.log('three');
}, 500);
});

// 输出:
// one
// three
// two

可以看到, three 先于 two 被打印了出来。这是为什么呢?如何才能让 two 先被打印出来呢?

解答

上面的问题困扰了我很久,直到我看到了 async/await 关键字。其实,这对关键字在 C# 中也有出现——

无论是在 C# 还是 JavaScript 中,async/await 都是非常棒的特性,它们也都是非常甜的语法糖。C# 的 async/await 实现离不开 Task 或 Task 类,而 JavaScript 的 async/await 实现,也离不开 Promise

——摘自https://segmentfault.com/a/1190000007535316

async关键字

Async 关键字是用来修饰函数的,它的功能是——如果函数返回了 Promise 对象,async 关键字什么也不会做;但如果函数返回了非 Promise 对象,它就起到了自动封装器的作用,它会用 Promise.resolve() 把返回值封装起来。因此,它好比一个约束,约束了函数返回类型一定是 Promise。以下用一些代码举例:

1
2
3
4
5
6
7
8
9
10
11
(() => 1)();
// 输出:1

(() => Promise.resolve(1))();
// 输出:Promise {<resolved>: 1}

(async () => 1)();
// 输出:Promise {<resolved>: 1}

(async () => Promise.resolve(1))();
// 输出:Promise {<resolved>: 1}

await关键字

Await关键字可以看作一个运算符,它后面连接的是一个表达式——如果表达式的计算结果是非 Promise 对象,那么 await 就不会起到实际作用;如果表达式的计算结果是 Promise 对象,那么 await 就会等待这个 Promise 被 resolve,然后得到 resolve 的值。以下也用一些代码举例:

1
2
3
4
5
6
7
8
9
10
11
let a = 1;
// a = 1

let b = await 1;
// b = 1

let c = Promise.resolve(1);
// c = Promise {<resolved>: 1}

let d = await Promise.resolve(1);
// d = 1

then()和async关键字的相似性

在了解了 async/await 关键字后,我就能更好地理解链式调用中的 then() 了。用易于理解的方法来说,then() 所做的事情和 async/await 关键字非常接近——首先,它等待前面的 Promise 被 resolve;然后,它接受一个函数作为参数,和 async 关键字一样的,如果该函数返回的不是 Promise 对象,将其封装成 Promise 对象;最后,它返回这个 Promise 对象,交由接下来的 then() 处理。当然这段解释是我由果索因的揣测,有关 then() 的语法可以阅读Promise.prototype.then(),里面有十分严谨而详细的解释,例如,我在写这篇文章时意外发现 then() 在接受非函数对象作为参数时会有很不同的行为……

再返回去看看之前的代码,发现能够说得通了。我传给第一个 then() 的,是一个匿名函数;第二个 then() 会像 await 关键字一样,等待这个 Promise 被 resolve。因为 setTimeout() 中的回调函数是在指定的延时之后异步执行的,这段代码本身会立即返回;由于它没有返回值,第一个 then() 会把它封装成 Promise.resolve(undefined),第二个 then() 也会立即等到这个值。而第一个 then() 中的延时要高于第二个 then() 中的,这就是为什么 three 要先于 two 被打印出来。

将回调封装成Promise

那么现在我需要做的就是将回调封装成 Promise 的格式。这并不复杂,还是以之前的代码为例,如果把所有的回调都用 Promise 来写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
new Promise(resolve => {
setTimeout(() => {
console.log('one');
resolve();
}, 1000);
}).then(r => new Promise(resolve => {
setTimeout(() => {
console.log('two');
resolve();
}, 1000);
})).then(r => new Promise(resolve => {
setTimeout(() => {
console.log('three');
resolve();
}, 500);
}));

// 输出:
// one
// two
// three

这样我就得到了期望的运行结果。想要在 Promise 之间传递值也很简单,前一个 Promise resolve 的值就会在下一个 then() 的回调函数中作为参数传入(这里是 r)。当然,根据 MDN web docs 的示例代码,then() 是用来获取 async 函数的运行结果的,链式执行多个异步任务的使命可以由 async/await 关键字更好地完成,还是以之前的代码,可以写成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
async function myFunc() {
let r = await new Promise(resolve => {
setTimeout(() => {
console.log('one');
resolve();
}, 1000);
});
r = await new Promise(resolve => {
setTimeout(() => {
console.log('two');
resolve();
}, 1000);
});
r = await new Promise(resolve => {
setTimeout(() => {
console.log('three');
resolve();
}, 500);
});
}

myFunc().then();

// 输出:
// one
// two
// three

相同的输出结果,但代码更加简洁了。

async/await的调用死循环

语法上,关于 async/await 关键字有这么一个限制——await 关键字只能出现在 async 函数中,而 async 关键字约束了函数返回值类型只会是 Promise 对象,因此我们需要用 await 关键字来等它的结果。那是不是就形成了一个死循环呢?不是的,就像之前说的那样,在最外层的函数中,async 函数的返回值需要用 then() 来获取;否则假使在最外层我们还是得阻塞式地等待异步函数的运行结果的话,那异步也就没有意义了,不是吗?

如下一段利用PyAudio库从麦克风实时采样并录音的代码(代码出处):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import pyaudio
import wave

chunk = 1024 # Record in chunks of 1024 samples
sample_format = pyaudio.paInt16 # 16 bits per sample
channels = 2
fs = 44100 # Record at 44100 samples per second
seconds = 3
filename = "output.wav"

p = pyaudio.PyAudio() # Create an interface to PortAudio

print('Recording')

stream = p.open(format=sample_format,
channels=channels,
rate=fs,
frames_per_buffer=chunk,
input=True)

frames = [] # Initialize array to store frames

# Store data in chunks for 3 seconds
for i in range(0, int(fs / chunk * seconds)):
data = stream.read(chunk)
frames.append(data)

# Stop and close the stream
stream.stop_stream()
stream.close()
# Terminate the PortAudio interface
p.terminate()

print('Finished recording')

# Save the recorded data as a WAV file
wf = wave.open(filename, 'wb')
wf.setnchannels(channels)
wf.setsampwidth(p.get_sample_size(sample_format))
wf.setframerate(fs)
wf.writeframes(b''.join(frames))
wf.close()

这里用的是传统的阻塞模式。当收集到的采样数少于chunk时,stream.read(chunk)语句将会阻塞。但是,有个问题随即而来——如果收集到的采样没有及时被程序读取,那么这些采样会被保留吗?

一个相似的情况是网络套接字通讯。假设一个程序每次只从套接字读取A个字节,而网卡缓冲区的大小是B字节(A<B)。在不断有新的数据传入的情况下,如果程序没有及时读取数据,那么这些数据将会留在网卡缓冲区中;直到网卡缓冲区满了,才会发生数据丢失的情况。那么从程序的角度来看,无论相邻的两次读取的操作间隔了多久,读取得到的数据都是连续的。这是因为网卡缓冲区起到了暂时保管数据的作用。

为了试验PyAudio实时录音是不是相同的机制,我在每个读取操作之间加入了显著的延迟(计算可得每次读取的数据时长为:1024÷44100=0.0232秒,那么0.1秒的延迟就是显著的)。如下是新的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
chunk = 1024
sample_format = pyaudio.paInt16
channels = 1
fs = 44100
read = 0
p = pyaudio.PyAudio()
stream = p.open(format=sample_format,
channels=channels,
rate=fs,
frames_per_buffer=chunk,
input=True)

while read < 10 * 44100:
data = stream.read(chunk)
read += chunk
time.sleep(0.1)
data = np.fromstring(data, dtype=np.int16)
self.audio_frames.append(data)
stream.stop_stream()
stream.close()
p.terminate()

wf = wave.open('delay_test.wav', 'wb')
wf.setnchannels(channels)
wf.setsampwidth(p.get_sample_size(sample_format))
wf.setframerate(fs)
wf.writeframes(b''.join(self.audio_frames))
wf.close()

播放所得的音频,发现有明显的断续感,有丢帧的情况。这说明,实时录音和网络通讯的机制不同,如果数据没有被及时读取,就会被丢弃,底层机制只会为程序缓存chunk个采样。

在IPv4地址池枯竭的当下,家庭宽带只能拥有NAT后的内网地址已经成了常态。找ISP软磨硬泡讨一个公网IP不失为一种方法,但这样做往往会把战线拉得很长。于是,花生壳/Ngrok/frp等方案便显得十分合适。这些方案有的是开源项目,有的是商业服务。但是,它们的原理基本相——即让一个拥有公网IP的机器,“借”几个端口给没有公网IP的机器,从而让没有公网IP的机器上的服务能够对公网暴露。

frp的原理

常规情况

在常规的情况下,服务器需要拥有公网IP。IP就如同门牌号,有了门牌号,别人才能找到你。客户端即便是处在NAT环境下,也能通过公网IP访问到服务器提供的服务。NAT环境内的客户端发起的连接建立之后,NAT网关便会对外打开一个端口来接收服务器返回的数据,并把接收到的数据转发给客户端。但是这个通讯,必须由NAT环境内的机器来发起。而提供服务的一方总是被动地等待传入连接的,因此NAT环境下的机器是无法对NAT环境外的机器提供服务的(这里暂时不考虑端口映射、DMZ、UDP穿透等因素)。

frp情况

如果用了frp,那么处在NAT环境下的服务器也能对NAT环境外的机器提供服务了。这需要一台有公网IP的机器来做frp服务器。没有公网IP的应用服务器首先要告诉frp服务器:“我有一个服务运行在A端口,我想借你的B端口来对外开放这个服务。”frp服务器答应了。于是应用服务器便和frp服务器建立起了一个长连接,这使得frp服务器可以随时发送数据给应用服务器(NAT环境下,连接的建立只能是单向的;但连接建立后的通讯仍然可以是双向的)。frp服务器还同时开始监听B端口上的传入连接。当有客户端想要访问应用服务器A端口上的服务时,它需要访问frp服务器的B端口。frp服务器接到这个请求后,就通过刚才建立的那个长连接把请求转发给应用服务器,应用服务器处理完这个请求,把响应发送给了frp服务器,frp服务器再次转发给客户端。整个流程就完成了。这就是frp的基本原理。

之前就在家里的路由器上尝试过搭建frps来提供内网穿透服务,但出于路由器系统更新后不再提供frps的软件包、以及家庭宽带连通性欠佳等因素,决定将frps转移到我的阿里云服务器上。那么,开始吧!

服务端(frps)

参考文章:https://www.itgeeker.net/centos-7-x64%E5%A6%82%E4%BD%95%E5%AE%89%E8%A3%85%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E5%B7%A5%E5%85%B7frps%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%AB%AF/

首先,从GitHub下载最新的frps。frp的官方地址是:https://github.com/fatedier/frp/releases。由于我的服务器系统是CentOS 64位,我需要下载后缀为linux_amd64的文件。

1
wget https://github.com/fatedier/frp/releases/download/v0.33.0/frp_0.33.0_linux_amd64.tar.gz

下载完成后解压文件:

1
tar -xzvf frp_0.33.0_linux_amd64.tar.gz

将可执行文件和配置文件复制到系统相应位置:

1
2
3
4
cp frps /usr/bin/frps
mkdir /etc/frp
cp frps.ini /etc/frp/frps.ini
cp systemd/frps.service /usr/lib/systemd/system/frps.service

重新加载systemd的服务项:

1
systemctl daemon-reload

在开启服务之前,需要对frps进行配置,frps的配置文件位于/etc/frp/frps.ini,如何配置可以参考:https://github.com/fatedier/frp/blob/master/README_zh.md。最后,启动服务并设置开机自启:

1
2
systemctl start frps
systemctl enable frps

如果启用了Dashboard,在浏览器输入http://[服务器地址]:[Dashboard端口]/,就能看到frps的Dashboard界面了。服务端的部署到此结束。

frps Dashboard界面

客户端(frpc)

首先在https://github.com/fatedier/frp/releases下载frp,由于我使用的是Windows 64位的系统,我需要下载后缀为windows_amd64的文件。接着,再到https://github.com/winsw/winsw/releases下载WinSW,下载后缀为NET461的文件即可。WinSW可以把任何EXE打包成一个服务,而免去了每次都要手动启动的麻烦。解压frp,和WinSW一起放在一个文件夹内。

配置frpc.ini,如何配置可以参考https://github.com/fatedier/frp/blob/master/README_zh.md。将WinSW.NET461.exe重命名为frpc_service.exe。新建一个名为frpc_service.xml的文件,输入以下内容:

1
2
3
4
5
6
7
8
<service>
<id>frpc</id>
<name>FRPC</name>
<description>This service runs frp client.</description>
<executable>frpc.exe</executable>
<arguments>-c frpc.ini</arguments>
<logmode>rotate</logmode>
</service>

删除多余的文件,只留下以下几个文件:

把整个文件夹藏到C:\Program Files\中,在文件夹中打开命令提示符,启动打包好的服务:

1
2
.\frpc_service.exe install
.\frpc_service.exe start

打开service.msc,可以看到出现了一个新的服务,并且已经被设置为了开机自启:

为了让frpc能够在连接错误退出后重启,需要在恢复选项卡中设置自动重启:

如果启用了Admin UI,在浏览器输入http://localhost:[Admin UI端口]/,就能看到frpc的Admin UI界面了。客户端的部署到此结束。

frpc Admin UI界面

接下来,就能体验frp的各种功能了!

大多数Linux服务器使用SSH进行远程管理。而作为一个众所周知的服务,SSH也总是成为被攻击的对象。Fail2Ban则是帮助服务器抵御暴力破解的一个软件。为了便于日后维护,这里整理了一些有关SSH的常用的命令。

列出最新日志文件中登录失败的条目:

1
cat /var/log/secure  grep 'Failed password'

列出所有日志文件中登录失败的条目:

1
cat /var/log/secure*  grep 'Failed password'

列出最新日志文件中登录失败的IP和次数:

1
cat /var/log/secure  awk '/Failed/{print $(NF-3)}'  sort  uniq -c  awk '{print $2" = "$1;}'

列出所有日志文件中登录失败的IP和次数:

1
cat /var/log/secure*  awk '/Failed/{print $(NF-3)}'  sort  uniq -c  awk '{print $2" = "$1;}'

查看被识别为暴力破解而被禁的IP地址列表(sshd是服务名称,不同情况可能有所不同):

1
iptables -L f2b-sshd

查看Fail2Ban最近的100条日志:

1
tail -100 /var/log/fail2ban.log

检查Fail2Ban总服务状态:

1
fail2ban-client status

检查Fail2Ban的SSH保护服务状态(sshd是服务名称,不同情况可能有所不同):

1
fail2ban-client status sshd

在SSH保护服务中添加/删除白名单(sshd是服务名称,不同情况可能有所不同;1.2.3.4是IP地址,按需修改):

1
2
fail2ban-client set sshd addignoreip 1.2.3.4
fail2ban-client set sshd delignoreip 1.2.3.4

参考文章:
https://www.howtoing.com/how-to-install-fail2ban-on-centos
https://www.jianshu.com/p/1eb53a0200e8

近日遇到一个怪事,即资源管理器左侧的导航面板中的One Drive图标消失了,而One Drive for Business的图标却还在。尝试了重新登录One Drive、重新安装One Drive、修改注册表等方法,均无果。最后通过修改注册表找回了图标,注册表项文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Windows Registry Editor Version 5.00

; Created by: Shawn Brink
; Created on: March 20th 2015
; Updated on: December 5th 2019
; Tutorial: https://www.tenforums.com/tutorials/4818-add-remove-onedrive-navigation-pane-windows-10-a.html


[HKEY_CURRENT_USER\Software\Classes\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}]
"System.IsPinnedToNameSpaceTree"=dword:00000001

[HKEY_CURRENT_USER\Software\Classes\WOW6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}]
"System.IsPinnedToNameSpaceTree"=dword:00000001

[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{018D5C66-4533-4307-9B53-224DE2ED1FE6}]
@="OneDrive"

安装

首先,确保你的电脑上安装了Google Chrome或Microsoft Edge Chromium。如果已经安装好了,请前往Chrome网上应用店安装NYU Toolbox。

自动跳过MFA的配置

安装完成后,右上角会出现一个图标。右键图标、点击“选项”。

在随即出现的设置页面上,找到并打开“Smart Configure”这个开关。这个功能十分实用,它可以帮助你一键配置各种参数。

打开开关后,在浏览器地址栏输入:https://start.nyu.edu/ibin/start0.cgi。进入页面后,按正常步骤登录(可能需要手工完成一次MFA),直到如下的界面,选择“NYU Multi-Factor Authentication Registration & Update”这一项:

这时候还需要手工完成一次MFA,完成后页面如下,点击这个黄色按钮,即可全自动配置参数。

走完流程之后,页面会回到原处,并且按钮上的文字会提示配置完毕。如果你看到这样的界面,那么恭喜你,你已经完成了自动跳过MFA的配置。

这里还有一个重要的提醒,请检查“When I log in”一项为“Ask me to choose an authentication method”。如图:

注意事项

如果在自动配置过程中,卡在了某一个步骤上,请尝试手动点击一下“上一步”。

如果在访问start.nyu.edu时,频繁出现“Error: Session error. Please start over.”错误信息,请尝试使用浏览器的隐身模式。并请右键右上角插件图标 - Manage Extensions - 找到并启用“Allow in incognito”。