Uber使用QUIC协议优化App网络性能

原文:Employing QUIC Protocol to Optimize Uber’s App Performance

Uber operates on a global scale across more than 600 cities, with our apps relying entirely on wireless connectivity from over 4,500 mobile carriers. To deliver the real-time performance expected from Uber’s users, our mobile apps require low-latency and highly reliable network communication. Unfortunately, the HTTP/2 stack fares poorly in dynamic, lossy wireless networks, and we learned that poor performance can often be traced directly to Transmission Control Protocol (TCP) implementations buried in OS kernels.

Uber在全球600个城市运营,我们的APP完全依赖于4500家移动网络运营商提供的无线网络服务。为了提供Uber用户期望的实时性能,我们的手机应用需要低延迟、高可用的网络通信。不幸的是,HTTP/2协议在动态、有损的无线网络中表现不佳,我们了解到低性能的原因通常被追溯定位到内核中的TCP实现。

To address these pain points, we adopted the QUIC protocol, a stream-multiplexed modern transport protocol implemented over UDP, which enables us to better control the transport protocol performance. QUIC is currently being standardized by the Internet Engineering Task Force (IETF) as HTTP/3.

After thorough testing of QUIC, we concluded that integrating QUIC in our apps would fchroreduce the tail-end latencies compared to TCP. We witnessed a reduction of 10-30 percent in tail-end latencies for HTTPS traffic at scale in our rider and driver apps. In addition to improving the performance of our apps in low connectivity networks, QUIC gives us end-to-end control over the flow of packets in the user space.

In this article, we share our experiences optimizing TCP performance for Uber’s apps by moving to a network stack that supports the QUIC protocol.

State of the Art: TCP

On today’s Internet, TCP is the most widely adopted transport protocol for carrying HTTPS traffic. TCP provides a reliable byte stream, dealing with the complexities of network congestion and link layer losses. The widespread use of TCP for HTTPS traffic is mainly attributed to ubiquity (almost every OS includes TCP), availability on a wide range of infrastructure, such as load balancers, HTTPS proxies, and CDNs, and it’s out-of-the-box functionality for most platforms and networks.

Most users access Uber’s services on the move, and the tail-end latencies of our applications running on TCP were far from meeting the requirements of the real-time nature of our HTTPS traffic. Specifically, users perceived high tail-end latencies across the world. In Figure 1, below, we plot the tail-end latencies of our HTTPS network calls across major cities:

Figure 1. Tail-end latencies vary across the major cities where Uber operates.

Although the latencies in India and Brazil’s networks were worse than those in the US and UK, the tail-end latencies are significantly higher than the average latencies, even in the case of the US and UK.

【Android】Wifi代理抓包原理

无论是Android还是iOS,都可以在wifi配置中设置一个代理服务器ip和port,很多人用这个功能来对移动设备APP的网络请求(准确来说是HTTP请求)进行抓包。这其中的原理是什么?怎么实现的?

Android源码分析

从wifi设置中配置了代理服务ip和port之后的生效流程,可以参考这篇文章:[原创]Android4.4 wifi代理流程

移动端(Android、iOS)接入Cronet实践

QUIC协议

QUIC, a multiplexed stream transport over UDP 是Chromium使用的通信协议,是基于UDP实现的类似于 TCP+TLS+HTTP/2 的协议。也是HTTP 3.0的设计方案。有兴趣的话大家可参考文档: Playing with QUIC

Chromium项目是开源的,The Chromium Projects(http://dev.chromium.org/chromium-projects) 文档详细介绍了Chromium项目的实现原理,以及如何获取源码并进行编译。

Cronet 库是Chrome使用的移动端网络库。支持 HTTP、HTTP/2 以及 QUIC 协议。支持 Android 和 iOS 平台。 其编译工具是 gn 和 ninja,类似于 cmake 与 make 的关系。 下面介绍 Cronet 库的编译及编译注意事项。

获取Chromium源码

可以参考官方文档:Checking out and building Chromium for Mac

获取源码之前,首先需要下载安装 depot_tools 工具。在一个适当的目录下clone depot_tools包:

1
$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

depot_tools的路径(最好是绝对路径,~需替换为$HOME)加进环境变量PATH中,假设 depot_tools 工程在/path/to/depot_tools目录下:

1
export PATH=$PATH:/path/to/depot_tools

如果从来没有下载过Chromium的代码的话,为源码创建一个文件夹并下载源码:

1
2
mkdir chromium && cd chromium
fetch --no-history chromium # 可能花费30min或几小时,依网络速度而不同

–no-history 可以节省代码下载时间,它忽略仓库的历史信息;整个代码量较大,约 14G,且需要翻墙,1M 左右的速度需要 20~30 分钟。若中间拉取失败,可以执行 gclient sync 继续拉取, 拉取结束后,该目录会生成一个 src 目录,包含 cronet 源码。

【Android】 使用VPN实现抓包

VPN抓包

使用VPN技术可以直接获得网络三层IP报文,可以不可以基于此实现移动端抓包呢?肯定可以,Android已经有大批开源库基于该思路实现抓包。我们来学习下原理。

VpnService

VpnService是开发Android VPN的基础,下面是官方文档的阐释

VpnService is a base class for applications to extend and build their own VPN solutions. In general, it creates a virtual network interface, configures addresses and routing rules, and returns a file descriptor to the application. Each read from the descriptor retrieves an outgoing packet which was routed to the interface. Each write to the descriptor injects an incoming packet just like it was received from the interface. The interface is running on Internet Protocol (IP), so packets are always started with IP headers. The application then completes a VPN connection by processing and exchanging packets with the remote server over a tunnel.

上面的阐释的重点是:

  • 虚拟一个网卡
  • 返回文件描述符
  • 读写的内容是ip数据报

首先推荐Android官方提供了的Example:ToyVpn。这个例子比较简单,实操一遍可以帮助理解VPN原理。

初步搭一个vpn应用框架也可以参考这篇文章,这个仅仅是搭建了框架,功能(ip数据包的收发)则没有实现。

源码分析

Android系统提供的API是VpnService,我们调用establish()方法之后会返回一个FD。然后我们读取该FD就能获得ip数据报文,通过发往VPN Server,再获得Server的回复报文之后再写入该FD,就可以实现VPN通信。

VpnService#establish()

[-> frameworks/base/core/java/android/net/VpnService.java]

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* Create a VPN interface using the parameters supplied to this
* builder. The interface works on IP packets, and a file descriptor
* is returned for the application to access them. Each read
* retrieves an outgoing packet which was routed to the interface.
* Each write injects an incoming packet just like it was received
* from the interface. The file descriptor is put into non-blocking
* mode by default to avoid blocking Java threads. To use the file
* descriptor completely in native space, see
* {@link ParcelFileDescriptor#detachFd()}. The application MUST
* close the file descriptor when the VPN connection is terminated.
* The VPN interface will be removed and the network will be
* restored by the system automatically.
*
* <p>To avoid conflicts, there can be only one active VPN interface
* at the same time. Usually network parameters are never changed
* during the lifetime of a VPN connection. It is also common for an
* application to create a new file descriptor after closing the
* previous one. However, it is rare but not impossible to have two
* interfaces while performing a seamless handover. In this case, the
* old interface will be deactivated when the new one is created
* successfully. Both file descriptors are valid but now outgoing
* packets will be routed to the new interface. Therefore, after
* draining the old file descriptor, the application MUST close it
* and start using the new file descriptor. If the new interface
* cannot be created, the existing interface and its file descriptor
* remain untouched.
*
* <p>An exception will be thrown if the interface cannot be created
* for any reason. However, this method returns {@code null} if the
* application is not prepared or is revoked. This helps solve
* possible race conditions between other VPN applications.
*
* @return {@link ParcelFileDescriptor} of the VPN interface, or
* {@code null} if the application is not prepared.
* @throws IllegalArgumentException if a parameter is not accepted
* by the operating system.
* @throws IllegalStateException if a parameter cannot be applied
* by the operating system.
* @throws SecurityException if the service is not properly declared
* in {@code AndroidManifest.xml}.
* @see VpnService
*/
@Nullable
public ParcelFileDescriptor establish() {
mConfig.addresses = mAddresses;
mConfig.routes = mRoutes;

try {
return getService().establishVpn(mConfig);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
}


/**
* Use IConnectivityManager since those methods are hidden and not
* available in ConnectivityManager.
*/
private static IConnectivityManager getService() {
return IConnectivityManager.Stub.asInterface(
ServiceManager.getService(Context.CONNECTIVITY_SERVICE));
}

其实是调用了ConnectivityService 这个系统服务的 establishVpn(mConfig)方法。

ConnectivityService#establishVpn()

[-> frameworks/base/services/core/java/com/android/server/ConnectivityService.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected final SparseArray<Vpn> mVpns = new SparseArray<>();

/**
* Configure a TUN interface and return its file descriptor. Parameters
* are encoded and opaque to this class. This method is used by VpnBuilder
* and not available in ConnectivityManager. Permissions are checked in
* Vpn class.
* @hide
*/
@Override
public ParcelFileDescriptor establishVpn(VpnConfig config) {
int user = UserHandle.getUserId(Binder.getCallingUid());
synchronized (mVpns) {
throwIfLockdownEnabled();
// mVpns其实是个Vpn数组
return mVpns.get(user).establish(config);
}
}

Vpn#establish()

[-> frameworks/base/services/core/java/com/android/server/connectivity/Vpn.java]

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* Establish a VPN network and return the file descriptor of the VPN interface. This methods
* returns {@code null} if the application is revoked or not prepared.
*
* @param config The parameters to configure the network.
* @return The file descriptor of the VPN interface.
*/
public synchronized ParcelFileDescriptor establish(VpnConfig config) {
// Check if the caller is already prepared.
UserManager mgr = UserManager.get(mContext);
if (Binder.getCallingUid() != mOwnerUID) {
return null;
}
// Check to ensure consent hasn't been revoked since we were prepared.
if (!isVpnUserPreConsented(mPackage)) {
return null;
}
// Check if the service is properly declared.
Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
intent.setClassName(mPackage, config.user);
long token = Binder.clearCallingIdentity();
try {
// Restricted users are not allowed to create VPNs, they are tied to Owner
UserInfo user = mgr.getUserInfo(mUserHandle);
if (user.isRestricted()) {
throw new SecurityException("Restricted users cannot establish VPNs");
}

ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent,
null, 0, mUserHandle);
if (info == null) {
throw new SecurityException("Cannot find " + config.user);
}
if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) {
throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
}
} catch (RemoteException e) {
throw new SecurityException("Cannot find " + config.user);
} finally {
Binder.restoreCallingIdentity(token);
}

// Save the old config in case we need to go back.
VpnConfig oldConfig = mConfig;
String oldInterface = mInterface;
Connection oldConnection = mConnection;
NetworkAgent oldNetworkAgent = mNetworkAgent;
Set<UidRange> oldUsers = mNetworkCapabilities.getUids();

// 调用jniCreate方法创建了一个FD
// Configure the interface. Abort if any of these steps fails.
ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
try {
String interfaze = jniGetName(tun.getFd());

// TEMP use the old jni calls until there is support for netd address setting
StringBuilder builder = new StringBuilder();
for (LinkAddress address : config.addresses) {
builder.append(" ");
builder.append(address);
}
if (jniSetAddresses(interfaze, builder.toString()) < 1) {
throw new IllegalArgumentException("At least one address must be specified");
}
Connection connection = new Connection();
if (!mContext.bindServiceAsUser(intent, connection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
new UserHandle(mUserHandle))) {
throw new IllegalStateException("Cannot bind " + config.user);
}

mConnection = connection;
mInterface = interfaze;

// Fill more values.
config.user = mPackage;
config.interfaze = mInterface;
config.startTime = SystemClock.elapsedRealtime();
mConfig = config;

// Set up forwarding and DNS rules.
// First attempt to do a seamless handover that only changes the interface name and
// parameters. If that fails, disconnect.
if (oldConfig != null
&& updateLinkPropertiesInPlaceIfPossible(mNetworkAgent, oldConfig)) {
// Keep mNetworkAgent unchanged
} else {
mNetworkAgent = null;
updateState(DetailedState.CONNECTING, "establish");
// Set up forwarding and DNS rules.
agentConnect();
// Remove the old tun's user forwarding rules
// The new tun's user rules have already been added above so they will take over
// as rules are deleted. This prevents data leakage as the rules are moved over.
agentDisconnect(oldNetworkAgent);
}

if (oldConnection != null) {
mContext.unbindService(oldConnection);
}

if (oldInterface != null && !oldInterface.equals(interfaze)) {
jniReset(oldInterface);
}

try {
IoUtils.setBlocking(tun.getFileDescriptor(), config.blocking);
} catch (IOException e) {
throw new IllegalStateException(
"Cannot set tunnel's fd as blocking=" + config.blocking, e);
}
} catch (RuntimeException e) {
IoUtils.closeQuietly(tun);
// If this is not seamless handover, disconnect partially-established network when error
// occurs.
if (oldNetworkAgent != mNetworkAgent) {
agentDisconnect();
}
// restore old state
mConfig = oldConfig;
mConnection = oldConnection;
mNetworkCapabilities.setUids(oldUsers);
mNetworkAgent = oldNetworkAgent;
mInterface = oldInterface;
throw e;
}
Log.i(TAG, "Established by " + config.user + " on " + mInterface);
return tun;
}

establish()方法除了权限校验之外,主要是通过jniCreate方法创建了一个FD并返回,这个FD其实就是tun设备。而此处的jniCreate方法是native方法,如下:

1
2
3
4
5
6
7
private native int jniCreate(int mtu);
private native String jniGetName(int tun);
private native int jniSetAddresses(String interfaze, String addresses);
private native void jniReset(String interfaze);
private native int jniCheck(String interfaze);
private native boolean jniAddAddress(String interfaze, String address, int prefixLen);
private native boolean jniDelAddress(String interfaze, String address, int prefixLen);

可以看到一系列的native方法,这些方法都位于com_android_server_connectivity_Vpn.cpp中:

jniCreate()

[-> frameworks/base/services/core/jni/com_android_server_connectivity_Vpn.cpp]

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
static const JNINativeMethod gMethods[] = {
{"jniCreate", "(I)I", (void *)create},
{"jniGetName", "(I)Ljava/lang/String;", (void *)getName},
{"jniSetAddresses", "(Ljava/lang/String;Ljava/lang/String;)I", (void *)setAddresses},
{"jniReset", "(Ljava/lang/String;)V", (void *)reset},
{"jniCheck", "(Ljava/lang/String;)I", (void *)check},
{"jniAddAddress", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)addAddress},
{"jniDelAddress", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)delAddress},
};

// 创建tun设备
static jint create(JNIEnv *env, jobject /* thiz */, jint mtu)
{
int tun = create_interface(mtu);
if (tun < 0) {
throwException(env, tun, "Cannot create interface");
return -1;
}
return tun;
}


static int create_interface(int mtu)
{
int tun = open("/dev/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC);

ifreq ifr4;
memset(&ifr4, 0, sizeof(ifr4));

// Allocate interface.
ifr4.ifr_flags = IFF_TUN | IFF_NO_PI;
if (ioctl(tun, TUNSETIFF, &ifr4)) {
ALOGE("Cannot allocate TUN: %s", strerror(errno));
goto error;
}

// Activate interface.
ifr4.ifr_flags = IFF_UP;
if (ioctl(inet4, SIOCSIFFLAGS, &ifr4)) {
ALOGE("Cannot activate %s: %s", ifr4.ifr_name, strerror(errno));
goto error;
}

// Set MTU if it is specified.
ifr4.ifr_mtu = mtu;
if (mtu > 0 && ioctl(inet4, SIOCSIFMTU, &ifr4)) {
ALOGE("Cannot set MTU on %s: %s", ifr4.ifr_name, strerror(errno));
goto error;
}

return tun;

error:
close(tun);
return SYSTEM_ERROR;
}

这里就很明显能看到最终调用的是create_interface方法,该方法其实就是打开/dev/tun文件,然后设置一下MTU并返回这个FD。

/dev/tun文件就是Linux中的TUN设备。

TUN/TAP是什么

tap/tun 是 Linux 内核 2.4.x 版本之后实现的虚拟网络设备,不同于物理网卡,tap/tun 虚拟网卡完全由软件来实现,功能和硬件实现完全没有差别,它们都属于网络设备,都可以配置 IP,都归 Linux 网络设备管理模块统一管理。
TAP 设备与 TUN 设备都是虚拟网络设备,工作方式完全相同,但它们的工作层次不太一样:

  • tap 是一个二层设备(或者以太网设备),只能处理二层的以太网帧;
  • tun 是一个点对点的三层设备(或网络层设备),只能处理三层的 IP 数据包。

作为网络设备,tap/tun 也需要配套相应的驱动程序才能工作。tap/tun 驱动程序包括两个部分,一个是字符设备驱动,一个是网卡驱动。这两部分驱动程序分工不太一样,字符驱动负责数据包在内核空间和用户空间的传送,网卡驱动负责数据包在 TCP/IP 网络协议栈上的传输和处理。

字符驱动 - 内核空间和用户空间的数据传输

在 Linux 中,用户空间和内核空间的数据传输有多种方式,字符设备就是其中的一种。tap/tun 通过驱动程序和一个与之关联的字符设备,来实现用户空间和内核空间的通信接口。

在 Linux 内核 2.6.x 之后的版本中,tap/tun 对应的字符设备文件分别为:

  • tap:/dev/tap0
  • tun:/dev/tun0

设备文件即充当了用户空间和内核空间通信的接口。当应用程序打开设备文件时,驱动程序就会创建并注册相应的虚拟设备接口,一般以 tunXtapX 命名。当应用程序关闭文件时,驱动也会自动删除 tunXtapX 设备,还会删除已经建立起来的路由等信息。

tap/tun 设备文件就像一个管道,一端连接着用户空间,一端连接着内核空间。当用户程序向文件 /dev/net/tun/dev/tap0 写数据时,内核就可以从对应的 tunXtapX 接口读到数据,反之,内核可以通过相反的方式向用户程序发送数据。

网卡驱动 - 数据包在 TCP/IP 协议栈的传输

参考网络虚拟化技术(二): TUN/TAP MACVLAN MACVTAP,先来看看物理网卡是如何工作的:

真实网卡的工作机制

普通网卡通过网线收发数据包,所有物理网卡收到的包会交给内核的 Network Stack 处理,然后通过 Socket API 通知给用户程序。

但是 TUN 设备通过一个/dev/tunX文件收发数据包。所有对该文件的写操作会通过 TUN 设备转换成一个数据包送给内核;当内核发送一个包给 TUN 设备时,通过读这个文件可以拿到包的内容。

如果我们使用 TUN 设备搭建一个基于 UDP VPN,那么整个处理过程就是这样:

基于 UDP 的 VPN 工作机制

数据包会通过内核网络栈两次。但是经过 App 的处理后,数据包可能已经加密,并且原有的 ip 头被封装在 udp 内部,所以第二次通过网络栈内核看到的添加了新的IP头和UDP头的数据包。

第二次进入网络栈内核后的IP数据包变化

tap/tun 通过实现相应的网卡驱动程序来和网络协议栈通信。一般的流程和物理网卡和协议栈的交互流程是一样的,不同的是物理网卡一端是连接物理网络,而 tap/tun 虚拟网卡一般连接到用户空间。

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
+----------------------------------------------------------------+
| |
| +--------------------+ +--------------------+ |
| | User Application A | | User Application B |<-----+ |
| +--------------------+ +--------------------+ | |
| | 1 | 5 | |
|...............|......................|...................|.....|
| ↓ ↓ | |
| +----------+ +----------+ | |
| | socket A | | socket B | | |
| +----------+ +----------+ | |
| | 2 | 6 | |
|.................|.................|......................|.....|
| ↓ ↓ | |
| +------------------------+ 4 | |
| | Newwork Protocol Stack | | |
| +------------------------+ | |
| | 7 | 3 | |
|................|...................|.....................|.....|
| ↓ ↓ | |
| +----------------+ +----------------+ | |
| | eth0 | | tun0 | | |
| +----------------+ +----------------+ | |
| 10.32.0.11 | | 192.168.3.11 | |
| | 8 +---------------------+ |
| | |
+----------------|-----------------------------------------------+

Physical Network

上图中有两个应用程序A和B,都在用户层,而其它的socket、协议栈(Newwork Protocol Stack)和网络设备(eth0和tun0)部分都在内核层,其实socket是协议栈的一部分,这里分开来的目的是为了看的更直观。

【Android】ADB工具原理探究

ADB简介

Android Debug Bridge (adb) 是一个Android的命令行工具。可以用来连接模拟器或实际的移动设备。比如 adb logcat, adb shell。Dalvik Debug Monitor Server(DDMS) 后台也是运行的adb来实现监控调试移动设备。

总体而言,adb有两个用途:

  • 监控连接设备 :adb会监控所有已经连接设备(包括模拟器),譬如设备所处的状态:ONLINE,OFFLINE, BOOTLOADER或RECOVERY。
  • 提供操作命令 :adb提供了很多命令(adb shelladb pull),来实现对设备的操控。这些操作命令在adb的体系里面,都称为“服务”。

ADB 实现原理

Adb的全称为 Android Debug Bridge:Android调试桥,下图为Android官方对adb的介绍:

【Android】Doze模式识别与检测

从 Android 6.0(API 级别 23)开始,Android 引入了两个省电功能:Doze模式(官方翻译为低电耗模式)和 App Standby模式(官方翻译为应用待机模式),可通过管理应用在设备未连接至电源时的行为方式为用户延长电池寿命。Doze模式通过在设备长时间处于闲置状态时推迟应用的后台 CPU 和网络 Activity 来减少电池消耗。App Standby模式可推迟用户近期未与之交互的应用的后台网络 Activity。

Doze模式

满足以下条件的设备会进入Doze低电耗模式。

  • 用户设备未插接电源
  • 处于静止状态一段时间
  • 屏幕关闭

在低电耗模式下,系统会尝试通过限制应用对网络和 CPU 密集型服务的访问来节省电量。 这还可以阻止应用访问网络并推迟其作业、同步和标准闹铃。

系统会定期退出Doze低电耗模式一会儿,好让应用完成其已推迟的 Activity。在此维护时段内,系统会运行所有待定同步、作业和闹铃并允许应用访问网络。

图 1. 低电耗模式提供了定期维护时段,可供应用使用网络并处理待定 Activity。

IPv6安全浅析

原文链接 :IPv6安全浅析 - Huawei - 2010.12 第52期

“缺乏安全性是互联网天生的弱点,这与是否采用IPv6关系不大。事实上,IPv6并没有引入新的安全问题,反而由于IPSec的引入以及发送设备采用永久性IP地址而解决了网络层溯源难题,给网络安全提供了根本的解决途径,有望实现端到端安全性。”中国电信科技委主任韦乐平这样评价IPv6安全。

IPv6协议设计的安全考虑

从协议的角度,IPv6作为IPv4的下一代,与IPv4同属于网络层的传输协议。然而,协议上最核心、最本质的差别就是地址空间的扩大,由IPv4下的32位地址空间变为128位的地址空间,这正是IPv6被选作新网络的承载协议并逐渐商用部署的根本驱动力。IPv6拥有如此巨大的地址空间,甚至可以为每一粒沙子都分配一个IP地址。而IPv4网络的地址分配是不规则的,并且很多时候是一个地址被多台主机共用。使用IPv6之后,我们能够将每个地址指定给一个责任体,就像给每个人一个身份证号,每辆车一个车牌号一样,每个地址都是唯一的;IPv6的地址分配采用逐级、层次化的结构,这就使得追踪定位、攻击溯源有了很大的改善。

另外,IPv6提出了新的地址生成方式——密码生成地址。密码生成地址与公私钥对绑定,保证地址不能被他人伪造。这如同汽车的车牌印上了指纹,别人不可能伪造这样的车牌,因为指纹造不了假。在IPv6协议设计之初,IP Sec(IPSecurity)协议族中的AH(AuthenticationHeader,报文认证头)和ESP(EncapsulationSecurity Payload,报文封装安全载荷)就内嵌到协议栈中,作为IPv6的扩展头出现在IP报文中,提供完整性、保密性和源认保护,这无疑是从协议上较大地提升安全性。

整体上看,IPv4协议的设计没有任何的安全考虑,特别是报文地址的伪造与欺骗使得无法对网络进行有效的监管和控制。因此,当出现网络攻击与安全威胁时,我们只能围绕攻击事件做好事前、事中和事后的防范、检测和过滤防御,缺乏有效的技术支撑手段,无法对攻击者形成真正的打击和管控。

而在IPv6网络的安全体系下,用户、报文和攻击可以一一对应,用户对自己的任何行为都必须负责,具有不可否认性,所以IPv6建立起严密的围绕攻击者的管控机制,实现对用户行为的安全监控。

HTTPS原理与证书生成

HTTPS

HTTPS与HTTP是什么关系呢?我们可以对比下HTTP与HTTPS的请求过程:

HTTP请求过程

HTTPS 在 TCP 和 HTTP 之间增加了 TLS(Transport Layer Security,传输层安全),提供了内容加密身份认证数据完整性三大功能。

HTTPS请求过程

WebSocket协议浅析

HTTP协议的缺点

HTTP协议的缺点

  1. 单向请求:只能是客户端发起,服务端处理并响应
  2. 请求/响应模式
  3. 无状态协议
  4. 半双工协议

半双工数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。HTTP协议这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用轮询:每隔一段时候,就发出一个询问,了解服务器有没有新的信息。轮询的效率低,非常浪费资源。WebSocket就可以解决这些问题。

CPU Cache与缓存行

引言

先看下面这两个循环遍历哪个快?

1
2
3
4
5
6
7
8
9
10
11
int[][] array = new int[64 * 1024][1024];

// 横向遍历
for(int i = 0; i < 64 * 1024; i ++)
for(int j = 0; j < 1024; j ++)
array[i][j] ++;

// 纵向遍历
for(int i = 0; i < 1024; i ++)
for(int j = 0; j < 64 * 1024; j ++)
array[j][i] ++;

在CPU处理器参数为 2.3 GHz Intel Core i5 的Mac上的结果是:

横向遍历: 80ms
纵向遍历: 2139ms

横向遍历的 CPU cache 命中率高,所以它比纵向遍历约快这么多倍!

Gallery of Processor Cache Effects 用 7 个源码示例生动的介绍 cache 原理,深入浅出!但是可能因操作系统的差异、编译器是否优化,以及近些年 cache 性能的提升,有些样例在 Mac 的效果与原文相差较大。


Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2019 iTimeTraveler All Rights Reserved.

访客数 : | 访问量 :