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 的效果与原文相差较大。

【Java】J.U.C并发包 - AQS机制

简介

Java并发包(java.util.concurrent)中提供了很多并发工具,这其中,很多我们耳熟能详的并发工具,譬如ReentrantLock、Semaphore,CountDownLatch,CyclicBarrier,它们的实现都用到了一个共同的基类 - AbstractQueuedSynchronizer,简称AQS。AQS提供了一种原子式管理同步状态、阻塞和唤醒线程功能以及队列模型的简单框架。是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。

设计思想

同步器背后的基本思想非常简单,可以参考AQS作者 Doug Lea 的论文:The java.util.concurrent Synchronizer Framework。同步器一般包含两种方法,一种是acquire,另一种是release。

  • acquire操作阻塞调用的线程,直到同步状态允许其继续执行。
  • release操作则是改变同步状态,使得一或多个被acquire阻塞的线程继续执行。

其中acquire操作伪代码如下:

1
2
3
4
5
while (synchronization state does not allow acquire) {
enqueue current thread if not already queued;
possibly block current thread;
}
dequeue current thread if it was queued;

release操作伪代码如下:

1
2
3
update synchronization state;
if (state may permit a blocked thread to acquire)
unblock one or more queued threads;

为了实现上述操作,需要下面三个基本组件的相互协作:

  • 同步状态的原子性管理;
  • 线程的阻塞与解除阻塞;
  • 队列的管理;

AQS框架借助于两个类:Unsafe(提供CAS操作) 和 LockSupport(提供park/unpark操作)。

1. 同步状态的原子性管理

AQS类使用单个int(32位)来保存同步状态,并暴露出getStatesetState以及compareAndSet操作来读取和更新这个状态。该整数可以表现任何状态。比如, Semaphore 用它来表现剩余的许可数,ReentrantLock 用它来表现拥有它的线程已经请求了多少次锁;FutureTask 用它来表现任务的状态(尚未开始、运行、完成和取消)。

如JDK的文档中所说,使用AQS来实现一个同步器需要覆盖实现如下几个方法,并且使用getStatesetStatecompareAndSetState这几个方法来设置或者获取状态。

  • boolean tryAcquire(int arg)

  • boolean tryRelease(int arg)

  • int tryAcquireShared(int arg)

  • boolean tryReleaseShared(int arg)

  • boolean isHeldExclusively()

以上方法不需要全部实现,根据获取的锁的种类可以选择实现不同的方法,支持独占(排他)获取锁的同步器应该实现tryAcquiretryReleaseisHeldExclusively而支持共享获取的同步器应该实现tryAcquireSharedtryReleaseSharedisHeldExclusively。下面以 CountDownLatch 举例说明基于AQS实现同步器, CountDownLatch 用同步状态持有当前计数,countDown方法调用 release从而导致计数器递减;当计数器为0时,解除所有线程的等待;await调用acquire,如果计数器为0,acquire 会立即返回,否则阻塞。

参考资料

【Android】动态链接库so的加载原理

前言

最近开发的组件时常出现了运行时加载so库失败问题,每天都会有java.lang.UnsatisfiedLinkError的错误爆出来,而且线上总是偶然复现,很疑惑。所以本文将从AOSP源码简单跟踪Android中的动态链接库so的加载原理,试图找出一丝线索。

加载入口

首先我们知道在Android(Java)中加载一个动态链接库非常简单。就是我们日常调用的 System.load(Sring filename) 或者System.loadLibrary(String libname)开始。 看过《理解JNI技术》的应该知道上述代码执行过程中会调用native层的JNI_OnLoad()方法,一般用于动态注册native方法。

# System.loadLibrary

[System.java]

1
2
3
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}

此处VMStack.getCallingClassLoader()拿到的是调用者的ClassLoader,一般情况下是PathClassLoader。我们进入Runtime类的loadLibrary0()方法看看。

[Runtime.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
synchronized void loadLibrary0(ClassLoader loader, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError("Directory separator should not appear in library name: " + libname);
}
String libraryName = libname;
// 1. 如果classloder存在,通过loader.findLibrary()查找到so路径
if (loader != null) {
String filename = loader.findLibrary(libraryName);
if (filename == null) {
// It's not necessarily true that the ClassLoader used
// System.mapLibraryName, but the default setup does, and it's
// misleading to say we didn't find "libMyLibrary.so" when we
// actually searched for "liblibMyLibrary.so.so".
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}
String error = doLoad(filename, loader);
if (error != null) {
throw new UnsatisfiedLinkError(error);
}
return;
}

// 2. 如果classloder不存在,通过loader.findLibrary()查找到so路径
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : getLibPaths()) { // getLibPaths()代码在最下方
String candidate = directory + filename;
candidates.add(candidate);

if (IoUtils.canOpenReadOnly(candidate)) {
String error = doLoad(candidate, loader);
if (error == null) {
return; // We successfully loaded the library. Job done.
}
lastError = error;
}
}

// 3. 都没找到,抛出 UnsatisfiedLinkError 异常
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}

【Java】使用Atomic变量实现锁

Atomic原子操作

Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。

在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。

  • 原子更新基本类型类: AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 原子更新数组类:AtomicIntegerArray,AtomicLongArray
  • 原子更新引用类型:AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray
  • 原子更新字段类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

详细介绍可以参考:Java中的Atomic包使用指南

Atomic的原理

下面通过AtomicInteger的源码来看一下是怎么在没有锁的情况下保证数据正确性。首先看一下incrementAndGet()方法的实现:

1
2
3
4
5
6
7
/**
* Atomically increments by one the current value.
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

【Java】NIO的原理与浅析

几个概念

用户空间与内核空间

现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。

在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由用户态模式切换至内核态模式,通过系统调用访问硬件设备。

文件描述符fd

文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

【Java】Thread类中的join()方法原理

简介

join()是Thread类的一个方法。根据jdk文档的定义:

public final void join()throws InterruptedException: Waits for this thread to die.

join()方法的作用,是等待这个线程结束;但显然,这样的定义并不清晰。个人认为”Java 7 Concurrency Cookbook”的定义较为清晰:

join() method suspends the execution of the calling thread until the object called finishes its execution.

也就是说,t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;通常用于在main()主线程内,等待其它线程完成再结束main()主线程。我们来看看下面的例子。

【Android】Retrofit源码分析

Retrofit简介

retrofit n. 式样翻新,花样翻新 vt. 给机器设备装配(新部件),翻新,改型

Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装。注意这里并没有说它是网络请求框架,主要原因在于网络请求的工作并不是 Retrofit 来完成的。Retrofit 2.0 开始内置 OkHttp,前者专注于接口的封装,后者专注于真正的网络请求。即通过 大量的设计模式 封装了 OkHttp ,使得简洁易用。

我们的应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp完成后续的请求操作,在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,后者根据用户的需求对结果进行解析的过程。Retrofit的大概原理过程如下:

  1. RetrofitHttp请求 抽象 成 Java接口
  2. 在接口里用 注解 描述和配置 网络请求参数
  3. 用动态代理 的方式,动态将网络请求接口的注解 解析 成HTTP请求
  4. 最后执行HTTP请求

这篇文章我将从Retrofit的基本用法出发,按照其使用步骤,一步步的探究Retrofit的实现原理及其源码的设计模式。

Retrofit Github: https://github.com/square/retrofit

使用步骤

使用 Retrofit 非常简单,首先需要在 build.gradle 中添加依赖:

1
implementation 'com.squareup.retrofit2:retrofit:2.4.0'

如果需要使用Gson解析器,也需要在build.gradle中添加依赖(后文会详细讲到Retrofit的Converter):

1
implementation 'com.squareup.retrofit2:converter-gson:2.0.2'

1. 定义Interface

Retrofit使用java interface和注解描述了HTTP请求的API参数,比如Github的一个API:

1
2
3
4
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2019 iTimeTraveler All Rights Reserved.

访客数 : | 访问量 :