容器化博客的编译环境

前言 在上一篇博客 从 Wordpress 到 Jekyll 中,提到了把博客从原来的 Workpress 迁移到了 Jekyll, 开始用 Markdown 语法写博客正文,用 jekyll build 生成博客内容并部署到 Web 服务器。 最近这两天在考虑把 “jekyll build” 这个过程容器化,达到传说中 一次部署到处运行 的终极目标。 :-) 目标 废话不多说,开始折腾。 首先要制定一下具体的目标: jekyll build 是将 Markdown 语法写成的纯文本文件生成 html 文件,这也是我希望将它容器化的部分,因为我并不希望再次搭建 jekyll 的环境,只要有一个稳定能用的编译环境就可以了。 生成了 html 文件以后,拷贝到 nginx 的目录下,这样就可以通过浏览器访问博客内容了,一般来说也可以将 nginx 运行在容器里面。 但是我目前在学习 nginx 源码,希望时不时的能把我修改过的 nginx 部署到博客上,因此,不容器化 nginx 。 步骤 首先去 docker hub 上找了一个可用的镜像 jekyll/jekyll,这个镜像已经部署好了一个基本的 jekyll 环境。 docker pull jekyll/jekyll:latest 然后启动这个镜像并进入到容器中。 这一步如果非常清楚需要安装哪些软件的话可以用 Dockerfile 代替完成。我在这里直接进入到容器中是因为并不清楚需要安装哪些依赖软件,需要一步一步 debug。 docker run --rm --volume=/LOCAL_DIR:/srv/jekyll -it jekyll/jekyll /bin/bash ...

2018-05-16 · Me

RFC 793 传输控制协议 TCP

RFC 793 是 TCP 正式成为标准时的文档,虽然距今已有 30 多年的历史,并且已经多次被更新,但是要学习 TCP 这份文档仍然值得一读。 文本算是一个读书笔记,把阅读过程中我认为需要注意的部分记录下来,方便自己以后查漏补缺。后续也会有阅读其他相关 RFC 的读书笔记。 RFC 的第一二章节是按照惯例的声明,正式内容从 第三章 FUNCTIONAL SPECIFICATION 开始。 3.1 头部格式 IP 头部带有源地址和目地址等信息,这两者也同样会被 TCP 头部的某些字段使用(比如计算 checksum 的时候)。 TCP 头部格式如下: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Port | Destination Port | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Acknowledgment Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Data | |U|A|P|R|S|F| | | Offset| Reserved |R|C|S|S|Y|I| Window | | | |G|K|H|T|N|N| | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum | Urgent Pointer | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ TCP Header Format Source Port,16位,源端口号 ...

2018-03-12 · Me

博客十年的变迁: 从 Wordpress 到 Jekyll

前言 印象中我的第一个博客是在大约2007年左右创建的,那个时候是个人博客非常流行的几年,各个网站都推出了自己的博客服务,如今已经是2018年,转眼间10年过去了,个人博客不如以前那么盛行了,现在流行的是微博自媒体和微信公众号。但是作为一个程序员,总觉得还是自己的博客比较 “geek”,所以一直还在折腾。 一开始的博客是建立在《电脑爱好者》网站推出的“博墅”服务上的,跟所有提供博客服务的网站一样,用户只要在网上点几下鼠标博客就建立好了。 然后就折腾更加高级的:自己买域名,买主机空间,自己建数据库,自己安装博客程序(也就是当时最流行的博客程序 WordPress)。 Wordpress 是我目前用的最长的博客系统了,从2009年左右一直到现在2018年,当初买的域名,买的主机空间,一直续费到现在,算一算有近10年了。 现在,准备把 wordpress 博客停用了,换成更 “geek” 的方式,用 Markdown 语法写内容,用 Jekyll 程序生成博客。生成的博客可以托管在 github pages 上,也可以放在云主机上。 博客源代码 博客的源码已经放在了我的 github 上,通过 github pages 服务这个博客已经可以通过浏览器访问了。 但是还是想在自己的云主机上也创建一个(太喜欢折腾了…),因此同样的一个博客内容在 xxx.github.io 上和云主机上各有一份,我的博客域名 blog.nlogn.cn 随意指向其中一个(看我心情咯)。 Ubuntu 部署 Jekyll 1. 安装 ruby sudo apt install ruby-full ruby-bundler 2. 安装 jekyll sudo gem install jekyll bundler minima 3. git clone github.io 上博客源码。 4. jekyll build 博客 jekyll build --destination /usr/local/nginx/html/myblog 自动将生成的博客部署到指定目录下。至此,一个静态博客就部署完成了。 云主机上我用的是自己编译的 nginx 服务器,通过设置 nginx 的配置文件,指定一个针对域名 “blog.nlogn.cn” 的虚拟主机,并把博客网站根目录指定为 “html/myblog” 就 OK 了。 ...

2018-03-04 · Me

TCP 的 FIN_WAIT1 状态

最近看到了一篇有关 TCP 关闭连接时 FIN-WAIT1 状态的文章(见参考资料),觉得很有意思,于是也在自己的电脑上验证了一下。 首先,开局一张图: {:height=“300” width=“300”} FIN-WAIT1 状态出现在 主动关闭链接方发出 FIN 报文后,收到对应 ACK 之前。通常 Server 在收到 FIN 报文之后,会在很短的时间内回复 ACK(这个 ACK 可能携带数据,也可能只是一个纯 ACK),所以 FIN-WAIT1 状态存在的时间非常短暂,很难被观察到。 于是准备两台虚拟机,我们可以设计这样一个实验: 1)服务端监听 1234 端口:nc -l 1234 2)客户端连接服务端:nc 192.168.122.183 1234, 此时 TCP 的状态为 ESTABLISHED $ sudo netstat -anp | grep tcp tcp 0 0 192.168.122.167:60482 192.168.122.183:1234 ESTABLISHED 3712/nc 3)服务端配置 iptables,拦截从服务端发送到客户端的任何报文:iptables -A OUTPUT -d 192.168.122.167 -j DROP 4)客户端按下 ctrl + c 断开连接,这一步的目的是让操作系统自动发送 FIN 报文给服务端。 在完成第 4 步之后,客户端就会进入 FIN-WAIT-1 状态,服务端也会收到 FIN 报文,并且马上会发出一个 ACK,但是因为配置了 iptables,因此客户端会一直等待服务端的 ACK。 ...

2017-08-09 · Me

TLS Perfect Forward Secrecy 之 DH/ECDHE

接着上一篇 TLS Perfect Forward Secrecy 之 RSA 缺陷 继续来看看 DH/ECDHE 如何解决这个问题。 前面提到 RSA 做密钥协商过程中,最关键的缺陷是客户端用公钥加密了 PreMaster Key,服务器用私钥解密 PreMaster Key。理想中更好的方法是 公钥/私钥只用来对证书签名,不参与到密钥协商的过程中来,换句话说,希望通信的双方能独立计算出对称加密的密钥。于是密码学家找到了尘封已久的,几乎与 RSA 同时出现的 DH 算法。 其实我一直有一个疑问:既然问题出在传送 PreMaster Key,那么客户端不发送 PreMaster Key 不就行了?握手的过程中已经有了 2 个随机数了,难道一定要 3 个随机数才能生成 master key 吗? 首先来看一下 DH 算法的数学基础。 +-------------------------------------------------------------------+ | Global Pulic Elements | | | | p prime number | | a prime number, a < p | +-------------------------------------------------------------------+ +-------------------------------------------------------------------+ | User A Key Generation | | | | Select private Xa Xa < p | | Calculate public Ya Ya = a^Xa mod p | +-------------------------------------------------------------------+ +-------------------------------------------------------------------+ | User B Key Generation | | | | Select private Xb Xb < p | | Calculate public Yb Yb = a^Xb mod p | +-------------------------------------------------------------------+ +-------------------------------------------------------------------+ | Calculation of Secret Key by User A | | | | Secret Key K K = Yb^Xa mod p | +-------------------------------------------------------------------+ +-------------------------------------------------------------------+ | Calculation of Secret Key by User B | | | | Secret Key K K = Ya^Xb mod p | +-------------------------------------------------------------------+ 上面一共出现了 a, p, Xa, Ya, Xb, Yb, K 共 7 个数,其中: ...

2017-03-21 · Me

TLS Perfect Forward Secrecy 之 RSA 的缺陷

HTTPS 这样的加密传输,最关键的是要解决两个问题, 一个是通信双方如何协商出一个对称加密的密钥(密钥交换) 二是自己如何确认对方的服务器就是我想访问的,即认证。 第一个问题可以由非对称加密解决,第二个问题是证书的合法性校验,“签发数字签名”,指的是用hash函数,对证书中的发行者,有效期,证书名等信息计算得到一个摘要(digest),然后用私钥进行加密,得到签名A。而对应的“校验数字签名”,则是先用相同的hash函数得到digest, 再利用相应的公钥解密A,然后对比自己hash出的和解密出的A是否相同,以此来认证对方是不是我们想要通信的人。 写到这里,正好最近有一个有趣的新闻:SHA1签名算法被破解,也就是说,安全研究人员根据理论上的SHA1破解算法,运用大量的计算资源,终于生成了两个不同的 PDF文件,但是它们的SHA1 值是一样的,所以以后我们应该使用更高级的签名算法来避免可能的安全漏洞。有兴趣的可以移步到上面的链接查看。 |算法 | 密钥交换 | 认证 | |--------|---------|---------| |基于 RSA | RSA | RSA | |基于 DH | DH | RSA/DSA| 相比传统的 RSA 握手,ECDHE 能支持 forward secrecy(DH 算法本身没有forward secrecy,要 ECDHE 才行)。DH 算法让 client 和 server 分别独立的计算出同步加密的密钥,注意:是独立计算出,而不是通过一方传递给另一方。 加密套件,类似于 “ECDHE-ECDSA-AES256-SHA384” 这样的一串,主要包含这些信息: 密钥协商的算法,要么 RSA,要么 DH 认证方法,比如 SHA 同步加密的方法,即 session key 所属的类型 hash 函数,保证传输的用户数据的完整性 在探讨 Perfect Forward Secrecy 之前,先来回顾一下旧的 TLS 握手过程会有什么缺陷。 没有 PFS 的 RSA 握手过程 第一步,ClientHello 这一步,客户端向服务器提供以下信息: ...

2017-02-21 · Me

抓包分析 TLS 1.2 连接过程

本文是《Traffic Analysis of an SSL/TLS Session》的笔记,原文见此处。 在 TLS 协议格式 中详细分析了协议数据的各个字段, 现在就来实战 ——用 Wireshark 抓包并观察数据。 第一发(Client -> Server) Full contents of the packet 0000 02 00 00 00 45 00 00 98 13 ed 40 00 40 06 00 00 ....E.....@.@... 0010 7f 00 00 01 7f 00 00 01 ec 26 01 bb 43 7c ee 74 .........&..C|.t 0020 60 b5 50 0a 80 18 31 d7 fe 8c 00 00 01 01 08 0a `.P...1......... 0030 21 62 1e 1e 21 62 1e 1e 16 03 01 00 5f 01 00 00 !b..!b......_... 0040 5b 03 01 54 9a ab 72 98 65 11 2f da 9e cf c9 db [..T..r.e./..... 0050 6c bd 4b 4c 56 4b 0c a5 68 2b aa 60 1f 38 66 e7 l.KLVK..h+.`.8f. 0060 87 46 b2 00 00 2e 00 39 00 38 00 35 00 16 00 13 .F.....9.8.5.... 0070 00 0a 00 33 00 32 00 2f 00 9a 00 99 00 96 00 05 ...3.2./........ 0080 00 04 00 15 00 12 00 09 00 14 00 11 00 08 00 06 ................ 0090 00 03 00 ff 01 00 00 04 00 23 00 00 .........#.. ------------ Family: IP 0000 02 00 00 00 .... IP protocol <--- IP协议首部 20 个字节,无“选项”字段。 0000 45 00 00 98 13 ed 40 00 40 06 00 00 7f 00 00 01 E.....@.@....... 0010 7f 00 00 01 .... TCP protocol <-- TCP 协议首部标准的为 20 个字节,这里一共32个字节,说明有“选项”字段。 0000 ec 26 01 bb 43 7c ee 74 60 b5 50 0a 80 18 31 d7 .&..C|.t`.P...1. 0010 fe 8c 00 00 01 01 08 0a 21 62 1e 1e 21 62 1e 1e ........!b..!b.. ------------ TLSv1 protocol 0000 16 03 01 00 5f 01 00 00 5b 03 01 54 9a ab 72 98 ...._...[..T..r. 0010 65 11 2f da 9e cf c9 db 6c bd 4b 4c 56 4b 0c a5 e./.....l.KLVK.. 0020 68 2b aa 60 1f 38 66 e7 87 46 b2 00 00 2e 00 39 h+.`.8f..F.....9 0030 00 38 00 35 00 16 00 13 00 0a 00 33 00 32 00 2f .8.5.......3.2./ 0040 00 9a 00 99 00 96 00 05 00 04 00 15 00 12 00 09 ................ 0050 00 14 00 11 00 08 00 06 00 03 00 ff 01 00 00 04 ................ 0060 00 23 00 00 .#.. TLSv1 Record protocol 0000 16 03 01 00 5f ...._ 16 Handshake protocol type 03 01 SSL version (TLS 1.0) 00 5f Record length (95 bytes) TLSv1 Handshake protocol 0000 01 00 00 5b 03 01 54 9a ab 72 98 65 11 2f da 9e ...[..T..r.e./.. 0010 cf c9 db 6c bd 4b 4c 56 4b 0c a5 68 2b aa 60 1f ...l.KLVK..h+.`. 0020 38 66 e7 87 46 b2 00 00 2e 00 39 00 38 00 35 00 8f..F.....9.8.5. 0030 16 00 13 00 0a 00 33 00 32 00 2f 00 9a 00 99 00 ......3.2./..... 0040 96 00 05 00 04 00 15 00 12 00 09 00 14 00 11 00 ................ 0050 08 00 06 00 03 00 ff 01 00 00 04 00 23 00 00 ............#.. 01 ClientHello message type 00 00 5b Message length <---- 这里消息长度为3字节,大于记录协议的2字节, 也就是说,一个记录块放不下可以放到下一个? 03 01 SSL version (TLS 1.0) 54 .. b2 32-bytes random number 00 Session Id length 00 2e Cipher Suites length (46 bytes, 23 suites) 00 39 .. ff 23 2-byte Cipher Suite Id numbers 01 Compression methods length (1 byte) 00 Compression method (null) 00 04 Extensions length (4 bytes) 00 23 SessionTicket TLS extension Id 00 00 Extension data length (0) 第二发(Server -> Client) 这个例子中,服务端发送了三种消息,ServerHello,Certificate,ServerHelloDone。这是一种最简单的方式,服务端没有要求验证客户端证书。 ...

2016-06-19 · Me

TLS 1.2 协议格式

本文内容来自《Traffic Analysis of an SSL/TLS Session》的阅读笔记,原文见此处。 TLS 全称为 Transport Layer Security,它本身也分为了 Lower 和 Higher 两层协议。 Lower Layer Lower Layer 协议在 TCP 协议之上,提供面向连接的可靠传输,在这一层的协议主要是记录协议(TLS Record Protocol)。 记录协议首先把上层协议传来的数据分成小于2^14字节的数据块,如果设置了压缩选项,则第二步为压缩数据,然后在数据块的末尾增加 MAC(Message Authentication Code),第三步将数据块加密,最后增加记录头。该过程如下所示。 each block is packed into a structure that does not preserve client message boundaries, meaning that mulitiple message of the same type maybe coalesced into a single structure -----------+ data --+--------------> 1. Fragment data -----------+ +------------------------+ | | | | +------------------------+ 2. Compress data (generally no compression applied) +------------------------+----+ | |MAC | Add a Message Authentication Code | | | +------------------------+----+ 3. Encrypt data +-----------------------------+ |ciphertext | | | +-----------------------------+ 4. Add header +----+-----------------------------+ TLS Record | |ciphertext | Add a TLS Record header header | | | +----+-----------------------------+ Higher Layer 主要有四种协议。 ...

2016-06-18 · Me

Linux 内核在 x86-64 上的内存分区

如果稍微了解过 Linux 内核的内存管理,那么对内存分区的概念一定不陌生,Linux内核把物理内存分成了3个区, 0 – 16M 为ZONE_DMA区, 16M – 896M 为ZONE_NORMAL区, 高于896M 为ZONE_HIGHMEM区 我没有去考证过为什么要取896这个数字,但是可以肯定的是这样的划分在当时看来是合理的,然而计算机发展今非昔比,现在4G的物理内存已经成为PC的标配了,CPU也进入了64位时代,很多事情都发生着改变。 在CPU还是32位的时代,CPU最大的物理寻址范围是0-4G, 在这里为了方便讨论,我们不考虑物理地址扩展(PAE)。进程的虚拟地址空间也是 4G,Linux内核把 0-3G虚拟地址空间作为用户空间,3G-4G虚拟地址空间作为内核空间。 目前几乎所有介绍Linux内存管理的书籍还是停留在32位寻址的时代,所以大家对下面这张图一定很熟悉! (这个图画得非常详细,本篇文章我们关注的重点是 3个分区 以及最右边的线性地址空间,也就是虚拟地址空间之间的关系,另外,应该是ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM, 图中把ZONE 写成了ZUNE) 然而,现在是64位的时代了, 64位CPU的寻址空间是多大呢? 16EB, 1EB = 1024 TB = 1024 * 1024 GB,我想很多人这辈子还没见过大于1TB的内存吧,事实上也是这样,几乎没有哪个服务器能有16EB的内存,实现64位长的地址只会增加系统的复杂度和地址转换的成本,所以目前的x86_64架构CPU都遵循AMD的 Canonical Form, 即只有虚拟地址的最低48位才会在地址转换时被使用, 且任何虚拟地址的48位至63位必须与47位一致, 也就是说总的虚拟地址空间为256TB。 那么在64位架构下,如何分配虚拟地址空间的呢? 0000000000000000 – 00007fffffffffff(128TB)为用户空间, ffff800000000000 – ffffffffffffffff(128TB)为内核空间。 而且内核空间中有很多空洞, 越过第一个空洞后, ffff880000000000 – ffffc7ffffffffff(64TB) 才是直接映射物理内存的区域, 也就是说默认的PAGE_OFFSET为 ffff880000000000. 请关注下图的最左边,这就是目前64位的虚拟地址布局。 在本文的一开头提到的物理内存分区 ZONE_DMA, ZONE_NORMAL, ZONE_HIGHMEM 就是与内核虚拟地址的直接映射有关的,如果读者不了解 内核直接映射物理地址这个概念的话,建议你去google一下,这个很简单的一一映射的概念。 既然现在内核直接映射的物理内存区域有64TB, 而且一般情况下,极少有计算机的内存能达到64TB(别说64TB了,1TB内存的也很少很少),所以整个内核虚拟地址空间都能够一一映射到计算机的物理内存上,因此,不再需要 ZONE_HIGHMEM这个分区了,现在对物理内存的划分,只有ZONE_DMA, ZONE_NORMAL。 ...

2014-01-02 · Me

一个简单的跳表(SkipList)实现

跳表(skiplist) 是一个非常有趣的、简单的数据结构, 应用也非常广泛, 著名的NoSQL内存数据库Redis, 就用到了skiplist作为排序集合的基础数据结构。 跳表最大的特点就是插入、删除操作的性能均为O(logn) 。 关于它的原理网上有一大堆,如果不了解的话,可以先看看文章末尾的参考资料, 或者动手google一下。 需要指出的是,网上搜的一些的文章原理介绍的不错, 但是代码写的有点乱, 排版的时候也没有语法高亮, 所以我自己也参照着写了一份, 并且在这里简单的注释一下。 首先, 一个“跳表” 基本上长的是这个样子的: 再来看下抽象的数据结构: #define object int typedef struct _node { int key; object *obj; struct _node *forward[1]; } node; typedef struct _skiplist { int level; struct _node *head; } skiplist; 值得注意的是, 结构体 node 的 forward 指针数组长度为1, 而我们从 skiplist 的长相和定义来看, forward 数组大小是随机的, 在 1 – MAX_LEVEL 之间, 因此, 一个技巧就是这样: node *nd = (node *)malloc(sizeof(node) + level * sizeof(node *)); 每次创建新的节点的时候, 都必须用到上面这条语句。 ...

2013-02-22 · Me