Jim

Talk is cheap. Show me the code.


  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

  • 搜索

Hexo next 主题加载自定义 js 文件

发表于 2019-06-02 | 分类于 Hexo

为什么要配置 hexo next 主题自定义 js 文件呢?主要原因有两点:

  • 不可靠:加载第三方站点的 js 依赖其站点的稳定性,如果第三方站点给挂了或者不维护了,那么加载的地址就失效了,访问直接 404… 比如最近就遇到 next 主题”不蒜子”文章 PV 统计功能用不了了,Chrome 抓包发现 https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js 这个地址 404 了,看了 “不蒜子”官方 blog 通知 才发现原来换成了 https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js。

  • 加载速度慢:比如之前将 gitalk 功能用到的 https://rawgit.com/qhh0205/78e9e0b1f3114db6737f3ed8cdd51d3a/raw/3894c5be5aa2378336b1f5ee0f296fa0b22d06e9/md5.min.js 文件嵌入到主题,发现每次打开 blog 网站都加载很慢,Chrome 抓包发现是该文件加载缓慢,一直 pending 很久…

那么解决上面两个问题的办法就是可以将远程加载的 js 文件下载下来,放到本地 netx 主题 source/js/src/ 目录下,让 hexo 生成静态网站时,加载生成静态站点本身的 js。下面举两个例子。

next 主题 gitalk 评论功能加载自定义 js

  1. 将 https://github.com/blueimp/JavaScript-MD5/blob/master/js/md5.min.js 文件下载下来放到 themes/next/source/js/src/ 路径下。
  2. 修改 themes/next/layout/_third-party/comments/gitalk.swig,加载 md5.min.js 改为 <script src="/js/src/md5.min.js"></script>:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {% if page.comments && theme.gitalk.enable %}
    <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
    <script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
    <script src="/js/src/md5.min.js"></script>
    <script type="text/javascript">
    var gitalk = new Gitalk({
    clientID: '3840ba8c8d80c18be7e3',
    clientSecret: '1b00f2efe5285973c24da9ed9ac895775eacc8ea',
    repo: '{{ theme.gitalk.repo }}',
    owner: '{{ theme.gitalk.githubID }}',
    admin: ['{{ theme.gitalk.adminUser }}'],
    id: md5(location.pathname),
    distractionFreeMode: '{{ theme.gitalk.distractionFreeMode }}'
    })
    gitalk.render('gitalk-container')
    </script>
    {% endif %}

next 主题 “不蒜子” PV 统计功能加载自定义 js

  1. 将 https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js 文件下载
    下来放到 themes/next/source/js/src/ 路径下。
  2. 修改 themes/next/layout/_third-party/analytics/busuanzi-counter.swig,将原先 <script async src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script> 改为 <script async src="/js/src/busuanzi.pure.mini.js"></script>:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    {% if theme.busuanzi_count.enable %}
    <div class="busuanzi-count">
    <script async src="/js/src/busuanzi.pure.mini.js"></script>

    {% if theme.busuanzi_count.site_uv %}
    <span class="site-uv">
    {{ theme.busuanzi_count.site_uv_header }}
    <span class="busuanzi-value" id="busuanzi_value_site_uv"></span>
    {{ theme.busuanzi_count.site_uv_footer }}
    </span>
    {% endif %}

    {% if theme.busuanzi_count.site_pv %}
    <span class="site-pv">
    {{ theme.busuanzi_count.site_pv_header }}
    <span class="busuanzi-value" id="busuanzi_value_site_pv"></span>
    {{ theme.busuanzi_count.site_pv_footer }}
    </span>
    {% endif %}
    </div>
    {% endif %}

配置完成后需要 hexo clean && hexo g 生效…

附件

怕以下两个远程站点 js 丢了,在此备份一下吧…
https://github.com/blueimp/JavaScript-MD5/blob/master/js/md5.min.js:

1
2
!function(n){"use strict";function t(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function r(n,t){return n<<t|n>>>32-t}function e(n,e,o,u,c,f){return t(r(t(t(e,n),t(u,f)),c),o)}function o(n,t,r,o,u,c,f){return e(t&r|~t&o,n,t,u,c,f)}function u(n,t,r,o,u,c,f){return e(t&o|r&~o,n,t,u,c,f)}function c(n,t,r,o,u,c,f){return e(t^r^o,n,t,u,c,f)}function f(n,t,r,o,u,c,f){return e(r^(t|~o),n,t,u,c,f)}function i(n,r){n[r>>5]|=128<<r%32,n[14+(r+64>>>9<<4)]=r;var e,i,a,d,h,l=1732584193,g=-271733879,v=-1732584194,m=271733878;for(e=0;e<n.length;e+=16)i=l,a=g,d=v,h=m,g=f(g=f(g=f(g=f(g=c(g=c(g=c(g=c(g=u(g=u(g=u(g=u(g=o(g=o(g=o(g=o(g,v=o(v,m=o(m,l=o(l,g,v,m,n[e],7,-680876936),g,v,n[e+1],12,-389564586),l,g,n[e+2],17,606105819),m,l,n[e+3],22,-1044525330),v=o(v,m=o(m,l=o(l,g,v,m,n[e+4],7,-176418897),g,v,n[e+5],12,1200080426),l,g,n[e+6],17,-1473231341),m,l,n[e+7],22,-45705983),v=o(v,m=o(m,l=o(l,g,v,m,n[e+8],7,1770035416),g,v,n[e+9],12,-1958414417),l,g,n[e+10],17,-42063),m,l,n[e+11],22,-1990404162),v=o(v,m=o(m,l=o(l,g,v,m,n[e+12],7,1804603682),g,v,n[e+13],12,-40341101),l,g,n[e+14],17,-1502002290),m,l,n[e+15],22,1236535329),v=u(v,m=u(m,l=u(l,g,v,m,n[e+1],5,-165796510),g,v,n[e+6],9,-1069501632),l,g,n[e+11],14,643717713),m,l,n[e],20,-373897302),v=u(v,m=u(m,l=u(l,g,v,m,n[e+5],5,-701558691),g,v,n[e+10],9,38016083),l,g,n[e+15],14,-660478335),m,l,n[e+4],20,-405537848),v=u(v,m=u(m,l=u(l,g,v,m,n[e+9],5,568446438),g,v,n[e+14],9,-1019803690),l,g,n[e+3],14,-187363961),m,l,n[e+8],20,1163531501),v=u(v,m=u(m,l=u(l,g,v,m,n[e+13],5,-1444681467),g,v,n[e+2],9,-51403784),l,g,n[e+7],14,1735328473),m,l,n[e+12],20,-1926607734),v=c(v,m=c(m,l=c(l,g,v,m,n[e+5],4,-378558),g,v,n[e+8],11,-2022574463),l,g,n[e+11],16,1839030562),m,l,n[e+14],23,-35309556),v=c(v,m=c(m,l=c(l,g,v,m,n[e+1],4,-1530992060),g,v,n[e+4],11,1272893353),l,g,n[e+7],16,-155497632),m,l,n[e+10],23,-1094730640),v=c(v,m=c(m,l=c(l,g,v,m,n[e+13],4,681279174),g,v,n[e],11,-358537222),l,g,n[e+3],16,-722521979),m,l,n[e+6],23,76029189),v=c(v,m=c(m,l=c(l,g,v,m,n[e+9],4,-640364487),g,v,n[e+12],11,-421815835),l,g,n[e+15],16,530742520),m,l,n[e+2],23,-995338651),v=f(v,m=f(m,l=f(l,g,v,m,n[e],6,-198630844),g,v,n[e+7],10,1126891415),l,g,n[e+14],15,-1416354905),m,l,n[e+5],21,-57434055),v=f(v,m=f(m,l=f(l,g,v,m,n[e+12],6,1700485571),g,v,n[e+3],10,-1894986606),l,g,n[e+10],15,-1051523),m,l,n[e+1],21,-2054922799),v=f(v,m=f(m,l=f(l,g,v,m,n[e+8],6,1873313359),g,v,n[e+15],10,-30611744),l,g,n[e+6],15,-1560198380),m,l,n[e+13],21,1309151649),v=f(v,m=f(m,l=f(l,g,v,m,n[e+4],6,-145523070),g,v,n[e+11],10,-1120210379),l,g,n[e+2],15,718787259),m,l,n[e+9],21,-343485551),l=t(l,i),g=t(g,a),v=t(v,d),m=t(m,h);return[l,g,v,m]}function a(n){var t,r="",e=32*n.length;for(t=0;t<e;t+=8)r+=String.fromCharCode(n[t>>5]>>>t%32&255);return r}function d(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t<r.length;t+=1)r[t]=0;var e=8*n.length;for(t=0;t<e;t+=8)r[t>>5]|=(255&n.charCodeAt(t/8))<<t%32;return r}function h(n){return a(i(d(n),8*n.length))}function l(n,t){var r,e,o=d(n),u=[],c=[];for(u[15]=c[15]=void 0,o.length>16&&(o=i(o,8*n.length)),r=0;r<16;r+=1)u[r]=909522486^o[r],c[r]=1549556828^o[r];return e=i(u.concat(d(t)),512+8*t.length),a(i(c.concat(e),640))}function g(n){var t,r,e="";for(r=0;r<n.length;r+=1)t=n.charCodeAt(r),e+="0123456789abcdef".charAt(t>>>4&15)+"0123456789abcdef".charAt(15&t);return e}function v(n){return unescape(encodeURIComponent(n))}function m(n){return h(v(n))}function p(n){return g(m(n))}function s(n,t){return l(v(n),v(t))}function C(n,t){return g(s(n,t))}function A(n,t,r){return t?r?s(t,n):C(t,n):r?m(n):p(n)}"function"==typeof define&&define.amd?define(function(){return A}):"object"==typeof module&&module.exports?module.exports=A:n.md5=A}(this);
//# sourceMappingURL=md5.min.js.map

https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js:

1
var bszCaller,bszTag;!function(){var c,d,e,a=!1,b=[];ready=function(c){return a||"interactive"===document.readyState||"complete"===document.readyState?c.call(document):b.push(function(){return c.call(this)}),this},d=function(){for(var a=0,c=b.length;c>a;a++)b[a].apply(document);b=[]},e=function(){a||(a=!0,d.call(window),document.removeEventListener?document.removeEventListener("DOMContentLoaded",e,!1):document.attachEvent&&(document.detachEvent("onreadystatechange",e),window==window.top&&(clearInterval(c),c=null)))},document.addEventListener?document.addEventListener("DOMContentLoaded",e,!1):document.attachEvent&&(document.attachEvent("onreadystatechange",function(){/loaded|complete/.test(document.readyState)&&e()}),window==window.top&&(c=setInterval(function(){try{a||document.documentElement.doScroll("left")}catch(b){return}e()},5)))}(),bszCaller={fetch:function(a,b){var c="BusuanziCallback_"+Math.floor(1099511627776*Math.random());window[c]=this.evalCall(b),a=a.replace("=BusuanziCallback","="+c),scriptTag=document.createElement("SCRIPT"),scriptTag.type="text/javascript",scriptTag.defer=!0,scriptTag.src=a,document.getElementsByTagName("HEAD")[0].appendChild(scriptTag)},evalCall:function(a){return function(b){ready(function(){try{a(b),scriptTag.parentElement.removeChild(scriptTag)}catch(c){bszTag.hides()}})}}},bszCaller.fetch("//busuanzi.ibruce.info/busuanzi?jsonpCallback=BusuanziCallback",function(a){bszTag.texts(a),bszTag.shows()}),bszTag={bszs:["site_pv","page_pv","site_uv"],texts:function(a){this.bszs.map(function(b){var c=document.getElementById("busuanzi_value_"+b);c&&(c.innerHTML=a[b])})},hides:function(){this.bszs.map(function(a){var b=document.getElementById("busuanzi_container_"+a);b&&(b.style.display="none")})},shows:function(){this.bszs.map(function(a){var b=document.getElementById("busuanzi_container_"+a);b&&(b.style.display="inline")})}};

docker-compose 启动 Redis 服务

发表于 2019-05-31 | 分类于 Redis

使用 docker-compose 以 aof 持久化方式启动单节点 Redis。
docker-compose.yml:

1
2
3
4
5
6
7
8
9
10
11
12
---
version: '3'
services:
redis:
image: redis:4.0.13
container_name: redis
restart: always
command: --appendonly yes
ports:
- 6379:6379
volumes:
- ./redis_data:/data

Nginx root 和 alias 指令的区别

发表于 2019-05-31 | 分类于 Nginx

nginx 的 root 和 alias 指令都是用于访问服务器本地文件的,两者区别如下:

  • 配置语法及适用范围

    1
    2
    3
    4
    5
    6
    7
    8
    [root]
    语法:root path
    默认值:root html
    配置段:http、server、location、if

    [alias]
    语法:alias path
    配置段:location
  • root 的处理结果是:root 路径+location 路径;

  • alias 的处理结果是:使用 alias 路径替换 location 路径;
  • alias 后面的路径结尾必须是 ‘/‘,而 root 可有可无;

举例

当 location 配置为如下时:
1
2
3
location ^~ /documents/ {
root /var/www-html/documents/;
}

请求:GET /documents/a.js —> 相当于请求本地路径:/var/www-html/documents/documents/a.js

请求:GET /documents/html/index.html —> 相当于请求本地路径:/var/www-html/documents/documents/html/index.html

当 location 配置为如下时:
1
2
3
location ^~ /documents/ {
alias /var/www-html/documents/;
}

请求:GET /documents/a.js —> 相当于请求本地路径:/var/www-html/documents/a.js

请求:GET /documents/html/index.html —> 相当于请求本地路径:/var/www-html/documents/html/index.html

Wireshark 抓包理解 HTTPS 协议

发表于 2019-05-26 | 分类于 HTTPS

HTTPS 简介

HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer)协议是 HTTP 协议的安全版,在 HTTP 应用层和传输层加入了 SSL/TLS 层,确保数据传输的安全性,所以 HTTPS 协议并不是什么新的协议,仅仅是 HTTP 协议和安全协议的组合。

HTTPS 协议主要解决如下三个通信安全问题:

  • 窃听风险(eavesdropping):第三方可以获知通信内容。
  • 篡改风险(tampering):第三方可以修改通信内容。
  • 冒充风险(pretending):第三方可以冒充他人身份参与通信。

HTTPS 通过 SSL/TLS 协议解决了上述三个问题,可以达到:

  • 加密数据以防止数据中途被窃取;
  • 维护数据的完整性,确保数据在传输过程中不被改变;
  • 认证用户和服务器,确保数据发送到正确的客户机和服务器;

既然安全问题是 SSL/TLS 保证的,那么就有必要仔细探索下 SSL/TLS 协议的机制,如下为 HTTPS 通信的整个网络协议栈,其中 SSL/TLS 协议又分为两层:

  • 握手协议(SSL Handshake Protocol):它建立在记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
  • 记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。

关于更多 SSL 和 TLS 知识见之前的文章: 基于 OpenSSL 生成自签名证书。
Alt text

SSL/TLS 通信过程

开始加密通信之前,客户端和服务器首先必须建立连接和交换参数,这个过程叫做握手(handshake)。SSL/TLS 握手其实就是通过非对称加密,生成对称加密的 session key 的过程。

假定客户端叫做爱丽丝服务器叫做鲍勃,整个握手过程可以用下图说明:
Alt text

整个握手过程通俗地说分为如下五步(真实的过程涉及的细节比这个多):

  • 第一步,爱丽丝给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。
  • 第二步,鲍勃确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。
  • 第三步,爱丽丝确认数字证书有效,然后生成一个新的随机数(Premaster secret),并使 用数字证书中的公钥,加密这个随机数,发给鲍勃。
  • 第四步,鲍勃使用自己的私钥,获取爱丽丝发来的随机数(即Premaster secret)。
  • 第五步,爱丽丝和鲍勃根据约定的加密方法,使用前面的三个随机数,生成”对话密钥”(session key),用来加密接下来的整个对话过程。

Alt text

SSL 握手的过程为双方发送消息的过程,这里所说的消息并不是一个独立的 TCP 数据包,而是 SSL 协议的术语。根据服务端实现的不同,可能一个 TCP 包中包含多条消息,而不是每条消息单独发送(每条单独发送效率太低),这个我们后面通过 Wireshark 抓包可以看到。

下图为双方握手过程中互相发送的 SSL 消息:
Alt text

客户端发送的初始消息

Client Hello 消息

客户端发送 Client Hello 消息给服务端来初始化会话消息,该消息包含如下信息:

  • Version Number: 客户端发送它所支持的最高 SSL/TLS 版本。版本 2 代表 SSL 2.0,版本 3 代表 SSL 3.0,版本 3.1 代表 TLS。
  • Randomly Generated Data:一个 32 字节的客户端随机数,该随机数被服务端生成通信用的对称密钥(master secret);
  • Session Identification :session ID 被客户端用于恢复之前的会话(只有恢复 session 时该字段才有值),这样可以简化 SSL 握手过程,避免每次请求都建立新的连接而握手,握手过程是需要消耗很多计算资源的。已建立的连接信息存储在客户端和服务端各自的 session 缓存中,用 session ID 标识;
  • Cipher Suite: 客户端发送它所支持的加密套件列表;
  • Compression Algorithm: 压缩算法,目前该字段几乎不在使用;

服务端响应

Server Hello 消息

服务端回复 Server Hello 消息给客户端:

  • Version Number:服务端发送双发所支持的最高的 SSL/TLS 版本;
  • Randomly Generated Data:一个 32 字节的服务端随机数,被客户端用于生成通信用的对称密钥(master secret);
  • Session Identification:该字段有如下三中情况:
    • New session ID:客户端和服务端初次建立连接时生成的 session ID。或者客户端尝试恢复 session,但是服务端无法恢复,因此也会生成新的 session ID;
    • Resumed Session ID:和客户端发送的恢复会话 ID 一致,用于恢复会话;
    • Null:该字段为 Null,表明这是一个新的 Session,但是服务端不打算用于后续的会话恢复,因此不会产生 session ID,该字段为空;
  • Cipher Suite: 服务端发送双发支持的最安全的加密套件;
  • Compression Algorithm:指定双方使用的压缩算法,目前该字段几乎不在使用;
Server Certificate 消息

服务端发送自己的 SSL 证书给客户端,证书中包含服务端的公钥,客户端用该证书验证服务端的身份。

Server Key Exchange 消息

这个消息是可选的,该消息主要用来传递双方协商密钥的参数,比如双方使用 Diffie-Hellman (迪菲) 算法生成 premaster secret 时,会用该字段传递双方的公共参数。所以具体该字段是什么内容取决于双方协商密钥的加密套件。

Client Certificate Request 消息

这个消息也是可选的,只有当服务端也需要验证客户端会用到。有的安全度高的网站会要求验证客户端,确认客户的真实身份,比如银行之类的网站。

Server Hello Done 消息

服务器发送 ServerHelloDone 消息,告知客户端服务端这边握手相关的消息发送完毕,等待客户端响应。

客户端回复

Client Certificate 消息

如果服务端发送了 Client Certificate Request 消息,那么客户端会发送该消息给服务端,包含自己的证书信息,供服务端进行客户端身份认证。

Client Key Exchange 消息

根据协商的密钥算法不同,该消息的内容会不同,该消息主要包含密钥协商的参数。比如双方使用 Diffie-Hellman (迪菲) 算法生成 premaster secret 时,会用该字段传递双方的公共参数。

Certificate Verify 消息

该消息只有在 Client Certificate message 消息发送时才发送。客户端通过自己的私钥签名从开始到现在的所有发送过的消息,然后服务端会用客户端的公钥验证这个签名。

Change Cipher Spec 消息

通知服务器此消息以后客户端会以之前协商的密钥加密发送数据。

Client Finished 消息

客户端计算生成对称密钥,然后使用该对称密钥加密之前所有收发握手消息的 Hash 值,发送给服务器,服务器将用相同的会话密钥(使用相同方法生成)解密此消息,校验其中的Hash 值。该消息是 SSL 握手协议记录层加密的第一条消息。

服务端最后对客户端响应

Change Cipher Spec 消息

通知客户端此消息以后服务端将会以之前协商的密钥加密发送数据。

Server Finished 消息

服务器使用对称密钥加密(生成方式与客户端相同)之前所发送的所有握手消息的hash值,发送给客户端去校验。

至此 SSL 握手过程结束,双发之后的通信数据都会用双方协商的对称密钥 Session Key 加密传输。

下图为 SSL/TLS 通信的整个过程:TCP 三次握手 + SSL/TLS 握手:
Alt text

Wireshark 抓包分析 SSL/TLS 握手过程

本节使用 wireshark 抓包工具分析一个完整的 HTTPS 通信过程,看看通信过程中双方消息是如何传送的。前面我们说过,根据服务端实现的不同,可能一个 TCP 包中包含多条 SSL/TLS 消息,而不是每条消息单独发送(每条单独发送效率太低)。

使用如下 wireshark https 包过滤器:

1
tcp.port==443 and (ip.dst==104.18.40.252 or ip.src==104.18.40.252)

下面为 Wireshark 抓取的 https 流量包,展示了整个通信过程:建立 TCP 连接 –> SSL/TLS 握手 –> 应用数据加密传输:
Alt text

上面是一个实际的 SSL/TLS 握手过程,分为如下 5 步:

  1. 客户端发送 Client Hello 消息给服务端;
  2. 服务端回应 Server Hello 消息;
  3. 服务端同时回应 Server Certificate、Server Key Exchange 和 Server Hello Done 消息;
  4. 客户端发送 Client Key Exchange、Change Cipher Spec 和 Client Finished 消息;
  5. 服务端最后发送 Change Cipher Spec 和 Server Finished 消息;

下面我们分步分析每个阶段的包的内容,看是否和前面的理论一致。

客户端发送 Client Hello 消息给服务端

Alt text

可以看出 TLS 协议确实分为两层:TLS 记录层、TLS 握手层,其中 TLS 握手层基于 TLS 记录层。

另外客户端发送的 Client Hello 消息当中包含的信息也可以看到:

  • Version:客户端支持的 TLS 版本号;
  • Random:客户端生成的 32 字节随机数;
  • Session ID:会话 ID;
  • Cipher Suites:客户端支持的加密套件列表;
  • Compression Methods:客户端支持的压缩算法;

服务端回应 Server Hello 消息

Alt text

Server Hello 包含如下信息:

  • Version:双方支持的 TLS 版本号;
  • Random:服务端生成的 32 字节随机数;
  • Session ID:会话 ID;
  • Cipher Suites:双方协商的加密套件;
  • Compression Methods:压缩算法;

服务端同时回应 Server Certificate、Server Key Exchange 和 Server Hello Done 消息

Alt text

可以看出每个 TLS 记录层是一个消息,服务端同时回复了有 3 个消息:Server Certificate、Server Key Exchange、Server Hello Done。

从 Server Key Exchange 消息可以看出双方密钥协商使用的是 Diffie-Hellman (迪菲) 算法,该消息用于传递 Diffie-Hellman (迪菲) 算法的参数。

客户端发送 Client Key Exchange、Change Cipher Spec 和 Client Finished 消息

Alt text

可以看出客户端同时回复了 3 个消息:Client Key Exchange、Change Cipher Spec 和 Client Finished 消息。Client Key Exchange 的内容为 Diffie-Hellman (迪菲) 算法的参数,用于生成 premaster key,然后和双方之前的随机数结合生成对称密钥。

服务端最后发送 Change Cipher Spec 和 Server Finished 消息

Alt text

服务端最后发送 Change Cipher Spec 和 Server Finished 消息,至此 SSL/TLS 握手完毕,接下来双方会用对称加密的方式加密传输数据。

相关资料

https://segmentfault.com/a/1190000002554673 | SSL/TLS 原理详解
http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html | SSL/TLS 协议运行机制概述
http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html | 图解 SSL/TLS 协议
https://www.jianshu.com/p/a3a25c6627ee | Https详解+wireshark抓包演示
https://segmentfault.com/a/1190000007283514 | TLS/SSL 高级进阶
https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc785811(v=ws.10) | 微软 Windows 文档

基于 OpenSSL 生成自签名证书

发表于 2019-05-18 | 分类于 HTTPS

PKI、CA、SSL、TLS、OpenSSL几个概念

PKI 和 CA

PKI 就是 Public Key Infrastructure 的缩写,翻译过来就是公开密钥基础设施。它是利用公开密钥技术所构建的,解决网络安全问题的,普遍适用的一种基础设施。

PKI 是目前唯一的能够基本全面解决安全问题的可能的方案。 PKI 通过电子证书以及管理这些电子证书的一整套设施,维持网络世界的秩序;通过提供一系列的安全服务,为网络电子商务、电子政务提供有力的安全保障。

通俗点说 PKI 就是一整套安全相关标准,然后基于这套标准体系衍生一系列安全相关的产品,主要目的是保证数据在网络上安全、可靠地传输。

PKI 主要由以下组件组成:

  • 认证中心 CA(证书签发) ;
  • X.500目录服务器(证书保存) ;
  • 具有高强度密码算法(SSL)的安全WWW服务器(即配置了 HTTPS 的 apache) ;
  • Web(安全通信平台): Web 有 Web Client 端和 Web Server 端两部分
  • 自开发安全应用系统 自开发安全应用系统是指各行业自开发的各种具体应用系统,例如银行、证券的应用系统等。

CA 是 PKI 的”核心”,即数字证书的申请及签发机关,CA 必须具备权威性的特征,它负责管理 PKI 结构下的所有用户(包括各种应用程序)的证书,把用户的公钥和用户的其他信息捆绑在一起,在网上验证用户的身份,CA 还要负责用户证书的黑名单登记和黑名单发布 。

CA 实现了 PKI 中一些很重要的功能:

  • 接收验证最终用户数字证书的申请;
  • 确定是否接受最终用户数字证书的申请-证书的审批;
  • 向申请者颁发、拒绝颁发数字证书-证书的发放;
  • 接收、处理最终用户的数字证书更新请求-证书的更新;
  • 接收最终用户数字证书的查询、撤销;
  • 产生和发布证书废止列表(CRL);
  • 数字证书的归档;
  • 密钥归档;
  • 历史数据归档;

在这么多功能中,CA 的核心功能就是”发放”和”管理”数字证书:
Alt text

SSL 和 TLS

SSL 和 TLS 协议是介于 HTTP 协议与 TCP 之间的一个可选层,主要用于 Web 客户端和服务器之间进行数据的安全传输:
Alt text

  • SSL: Secure Socket Layer,安全套接字层),为Netscape所研发,用以保障在Internet上数据传输之安全,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取。当前版本为3.0。它已被广泛地用于Web浏览器与服务器之间的身份认证和加密数据传输。SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层:
    SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。
    SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
  • TLS: (Transport Layer Security,传输层安全协议),用于两个应用程序之间提供保密性和数据完整性。
    TLS 1.0是IETF(Internet Engineering Task Force,Internet工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本,可以理解为SSL 3.1,它是写入了 RFC 的。该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。较低的层为 TLS 记录协议,位于某个可靠的传输协议(例如 TCP)上面。

SSL/TLS协议提供的服务主要有:

  • 认证用户和服务器,确保数据发送到正确的客户机和服务器;
  • 加密数据以防止数据中途被窃取;
  • 维护数据的完整性,确保数据在传输过程中不被改变;

OpenSSL

Alt text
OpenSSL 是一个开源的加密工具包,主要包括如下三部分:

  • libssl (with platform specific naming):
    Provides the client and server-side implementations for SSLv3 and TLS.
  • libcrypto (with platform specific naming):
    Provides general cryptographic and X.509 support needed by SSL/TLS but
    not logically part of it.
  • openssl:
    A command line tool that can be used for:
    Creation of key parameters
    Creation of X.509 certificates, CSRs and CRLs
    Calculation of message digests
    Encryption and decryption
    SSL/TLS client and server tests
    Handling of S/MIME signed or encrypted mail
    And more...
    

使用 OpenSSL 生产自签名 SSL 证书过程

Alt text

以下为 Centos7 环境下生成自签名 SSL 证书的具体过程:

  1. 修改 openssl 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    vi /etc/pki/tls/openssl.cnf
    # match 表示后续生成的子证书的对应项必须和创建根证书时填的值一样,否则报错。以下配置只规定子证书的 countryName 必须和根证书一致。
    [ policy_match ] 段配置改成如下:
    countryName = match
    stateOrProvinceName = optional
    organizationName = optional
    organizationalUnitName = optional
    commonName = supplied
    emailAddress = optional
  2. 在服务器 pki 的 CA 目录下新建两个文件

    1
    cd /etc/pki/CA && touch index.txt serial && echo 01 > serial
  3. 生成 CA 根证书密钥

    1
    cd /etc/pki/CA/ && openssl genrsa -out private/cakey.pem 2048 && chmod 400 private/cakey.pem
  4. 生成根证书(根据提示输入信息,除了 Country Name 选项需要记住的,后面的随便填)

    1
    openssl req -new -x509 -key private/cakey.pem -out cacert.pem
  5. 生成密钥文件

    1
    openssl genrsa -out nginx.key 2048
  6. 生成证书请求文件(CSR):
    A. 根据提示输入信息,除了 Country Name 与前面根证书一致外,其他随便填写
    B. Common Name 填写要保护的域名,比如:*.qhh.me

    1
    openssl req -new -key nginx.key -out nginx.csr
  7. 使用 openssl 签署 CSR 请求,生成证书

    1
    2
    3
    4
    5
    6
    7
    8
    openssl ca -in nginx.csr -cert /etc/pki/CA/cacert.pem -keyfile /etc/pki/CA/private/cakey.pem -days 365 -out nginx.crt

    参数项说明:
    -in: CSR 请求文件
    -cert: 用于签发的根 CA 证书
    -keyfile: 根 CA 的私钥文件
    -days: 生成的证书的有效天数
    -out: 生成证书的文件名

至此自签名证书生成完成,最终需要:nginx.key 和 nginx.crt

配置 Nginx 使用自签名证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
server_name domain;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
ssl_certificate ssl/nginx.crt; # 前面生成的 crt 证书文件
ssl_certificate_key ssl/nginx.key; # 前面生成的证书私钥
server_name domain;
location / {
root /var/www-html;
index index.html;
}
}

相关资料

http://seanlook.com/2015/01/18/openssl-self-sign-ca/ | 基于 OpenSSL 自签署证书
http://www.cnblogs.com/littlehann/p/3738141.html | openSSL命令、PKI、CA、SSL证书原理
https://cnzhx.net/blog/ssl-on-lamp-on-vps/
http://seanlook.com/2015/01/15/openssl-certificate-encryption/ | OpenSSL 与 SSL 数字证书概念贴
https://kb.cnblogs.com/page/194742/ | 数字证书及 CA 的扫盲
http://netsecurity.51cto.com/art/200602/21066.htm | PKI/CA 技术的介绍
http://cnzhx.net/blog/self-signed-certificate-as-trusted-root-ca-in-windows/ | 浏览器添加自签名证书
https://aotu.io/notes/2016/08/16/nginx-https/index.html | Nginx 配置 HTTPS 服务器

tcpdump使用技巧

发表于 2019-05-07 | 分类于 计算机网络

tcpdump 是一个开源的命令行网络封包分析工具,一般用于服务器端网络流量、协议分析。今天有幸看到了有人很好地总结了关于 tcpdump 的使用技巧,在此转载下。
本文转载自这里:
https://github.com/linuxwiki/SourceWiki/blob/master/%E7%BD%91%E7%BB%9C/tcpdump.md


作者: 潜水大叔

一般情况下,非HTTP协议的网络分析,在服务器端用tcpdump比较多,在客户端用wireshark比较多,两个抓包软件的语法是一样的。

一、基本语法

1.1、过滤主机

  • 抓取所有经过eth1,目的或源地址是192.168.1.1的网络数据
1
tcpdump -i eth1 host 192.168.1.1
  • 指定源地址
1
tcpdump -i eth1 src host 192.168.1.1
  • 指定目的地址
1
tcpdump -i eth1 dst host 192.168.1.1

1.2、过滤端口

  • 抓取所有经过eth1,目的或源端口是25的网络数据
1
tcpdump -i eth1 port 25
  • 指定源端口
1
tcpdump -i eth1 src port 25
  • 指定目的端口
1
tcpdump -i eth1 dst port 25

1.3、网络过滤

1
2
3
tcpdump -i eth1 net 192.168
tcpdump -i eth1 src net 192.168
tcpdump -i eth1 dst net 192.168

1.4、协议过滤

1
2
3
4
5
tcpdump -i eth1 arp
tcpdump -i eth1 ip
tcpdump -i eth1 tcp
tcpdump -i eth1 udp
tcpdump -i eth1 icmp

1.5、常用表达式

非 : ! or "not" (去掉双引号)  
且 : && or "and"  
或 : || or "or"  
  • 抓取所有经过eth1,目的地址是192.168.1.254或192.168.1.200端口是80的TCP数据
1
tcpdump -i eth1 '((tcp) and (port 80) and ((dst host 192.168.1.254) or (dst host 192.168.1.200)))'
  • 抓取所有经过eth1,目标MAC地址是00:01:02:03:04:05的ICMP数据
1
tcpdump -i eth1 '((icmp) and ((ether dst host 00:01:02:03:04:05)))'
  • 抓取所有经过eth1,目的网络是192.168,但目的主机不是192.168.1.200的TCP数据
1
tcpdump -i eth1 '((tcp) and ((dst net 192.168) and (not dst host 192.168.1.200)))'

二、高级包头过滤

首先了解如何从包头过滤信息

1
2
3
4
5
proto[x:y]          : 过滤从x字节开始的y字节数。比如ip[2:2]过滤出3、4字节(第一字节从0开始排)
proto[x:y] & z = 0 : proto[x:y]和z的与操作为0
proto[x:y] & z !=0 : proto[x:y]和z的与操作不为0
proto[x:y] & z = z : proto[x:y]和z的与操作为z
proto[x:y] = z : proto[x:y]等于z

操作符 : >, <, >=, <=, =, !=

2.1、IP头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding | <-- optional
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| DATA ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

本文只针对IPv4。

2.2、IP选项设置了吗?

“一般”的IP头是20字节,但IP头有选项设置,不能直接从偏移21字节处读取数据。IP头有个长度字段可以知道头长度是否大于20字节。

1
2
3
+-+-+-+-+-+-+-+-+
|Version| IHL |
+-+-+-+-+-+-+-+-+

通常第一个字节的二进制值是:01000101,分成两个部分:

0100 = 4 表示IP版本
0101 = 5 表示IP头32 bit的块数,5 x 32 bits = 160 bits or 20 bytes

如果第一字节第二部分的值大于5,那么表示头有IP选项。

下面介绍两种过滤方法(第一种方法比较操蛋,可忽略):

a. 比较第一字节的值是否大于01000101,这可以判断IPv4带IP选项的数据和IPv6的数据。

01000101十进制等于69,计算方法如下(小提示:用计算器更方便)

1
2
3
4
5
6
7
8
9
0 : 0  \
1 : 2^6 = 64 \ 第一部分 (IP版本)
0 : 0 /
0 : 0 /
-
0 : 0 \
1 : 2^2 = 4 \ 第二部分 (头长度)
0 : 0 /
1 : 2^0 = 1 /

64 + 4 + 1 = 69

如果设置了IP选项,那么第一自己是01000110(十进制70),过滤规则:

1
tcpdump -i eth1 'ip[0] > 69'

IPv6的数据也会匹配,看看第二种方法。

b. 位操作

0100 0101 : 第一字节的二进制
0000 1111 : 与操作
<=========
0000 0101 : 结果

正确的过滤方法

1
tcpdump -i eth1 'ip[0] & 15 > 5'

或者

1
tcpdump -i eth1 'ip[0] & 0x0f > 5'

2.3、分片标记

当发送端的MTU大于到目的路径链路上的MTU时就会被分片,这段话有点拗口,权威的请参考《TCP/IP详解》。唉,32借我的书没还,只能凑合写,大家记得看书啊。

分片信息在IP头的第七和第八字节:

1
2
3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Bit 0: 保留,必须是0
Bit 1: (DF) 0 = 可能分片, 1 = 不分片
Bit 2: (MF) 0 = 最后的分片, 1 = 还有分片

Fragment Offset字段只有在分片的时候才使用。

要抓带DF位标记的不分片的包,第七字节的值应该是:

01000000 = 64

1
tcpdump -i eth1 'ip[6] = 64'

2.4、抓分片包

  • 匹配MF,分片包
1
tcpdump -i eth1 'ip[6] = 32'

最后分片包的开始3位是0,但是有Fragment Offset字段。

  • 匹配分片和最后分片
1
tcpdump -i eth1 '((ip[6:2] > 0) and (not ip[6] = 64))'

测试分片可以用下面的命令:

1
ping -M want -s 3000 192.168.1.1

2.5、匹配小TTL

TTL字段在第九字节,并且正好是完整的一个字节,TTL最大值是255,二进制为11111111。

可以用下面的命令验证一下:

1
2
$ ping -M want -s 3000 -t 256 192.168.1.200
ping: ttl 256 out of range
1
2
3
+-+-+-+-+-+-+-+-+
| Time to Live |
+-+-+-+-+-+-+-+-+
  • 在网关可以用下面的命令看看网络中谁在使用traceroute
1
tcpdump -i eth1 'ip[8] < 5'

2.6、抓大于X字节的包

  • 大于600字节
1
tcpdump -i eth1 'ip[2:2] > 600'

2.7、更多的IP过滤

首先还是需要知道TCP基本结构,再次推荐《TCP/IP详解》,卷一就够看的了,避免走火入魔。

  • TCP头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 | |C|E|U|A|P|R|S|F| |
| Offset| Res. |W|C|R|C|S|S|Y|I| Window |
| | |R|E|G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  • 抓取源端口大于1024的TCP数据包
1
tcpdump -i eth1 'tcp[0:2] > 1024'
  • 匹配TCP数据包的特殊标记

TCP标记定义在TCP头的第十四个字节

1
2
3
4
5
+-+-+-+-+-+-+-+-+
|C|E|U|A|P|R|S|F|
|W|C|R|C|S|S|Y|I|
|R|E|G|K|H|T|N|N|
+-+-+-+-+-+-+-+-+

重复一下TCP三次握手,两个主机是如何勾搭的:

  1. 源发送SYN
  2. 目标回答SYN, ACK
  3. 源发送ACK

没女朋友的童鞋要学习一下:

  1. MM,你的手有空吗?-_-
  2. 有空,你呢?~_~
  3. 我也有空 *_*

失败的loser是酱紫的:

  1. MM,这是你掉的板砖吗?(SYN)  ̄▽ ̄
  2. 不是,找拍啊?(RST-ACK) ˋ﹏ˊ
  • 只抓SYN包,第十四字节是二进制的00000010,也就是十进制的2
1
tcpdump -i eth1 'tcp[13] = 2'
  • 抓SYN, ACK (00010010 or 18)
1
tcpdump -i eth1 'tcp[13] = 18'
  • 抓SYN或者SYN-ACK
1
tcpdump -i eth1 'tcp[13] & 2 = 2'

用到了位操作,就是不管ACK位是啥。

  • 抓PSH-ACK
1
tcpdump -i eth1 'tcp[13] = 24'
  • 抓所有包含FIN标记的包(FIN通常和ACK一起,表示幽会完了,回见)
1
tcpdump -i eth1 'tcp[13] & 1 = 1'
  • 抓RST(勾搭没成功,伟大的greatwall对她认为有敏感信息的连接发RST包,典型的棒打鸳鸯)
1
tcpdump -i eth1 'tcp[13] & 4 = 4'

下图详细描述了TCP各种状态的标记,方便分析。

tcp_state_machine.jpg

2.8、大叔注

tcpdump考虑了一些数字恐惧症者的需求,提供了部分常用的字段偏移名字:

icmptype (ICMP类型字段)
icmpcode (ICMP符号字段)
tcpflags (TCP标记字段)

ICMP类型值有:

icmp-echoreply, icmp-unreach, icmp-sourcequench, icmp-redirect, icmp-echo, icmp-routeradvert, icmp-routersolicit, icmp-timxceed, icmp-paramprob, icmp-tstamp, icmp-tstampreply, icmp-ireq, icmp-ireqreply, icmp-maskreq, icmp-maskreply

TCP标记值:

tcp-fin, tcp-syn, tcp-rst, tcp-push, tcp-push, tcp-ack, tcp-urg

这样上面按照TCP标记位抓包的就可以写直观的表达式了:

  • 只抓SYN包
1
tcpdump -i eth1 'tcp[tcpflags] = tcp-syn'
  • 抓SYN, ACK
1
tcpdump -i eth1 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack != 0'

2.9、抓SMTP数据

1
tcpdump -i eth1 '((port 25) and (tcp[(tcp[12]>>2):4] = 0x4d41494c))'

抓取数据区开始为”MAIL”的包,”MAIL”的十六进制为0x4d41494c。

2.10、抓HTTP GET数据

1
tcpdump -i eth1 'tcp[(tcp[12]>>2):4] = 0x47455420'

“GET “的十六进制是47455420

2.11、抓SSH返回

1
tcpdump -i eth1 'tcp[(tcp[12]>>2):4] = 0x5353482D'

“SSH-“的十六进制是0x5353482D

1
tcpdump -i eth1 '(tcp[(tcp[12]>>2):4] = 0x5353482D) and (tcp[((tcp[12]>>2)+4):2] = 0x312E)'

抓老版本的SSH返回信息,如”SSH-1.99..”

三、大叔注

如果是为了查看数据内容,建议用tcpdump -s 0 -w filename把数据包都保存下来,然后用wireshark的Follow TCP Stream/Follow UDP Stream来查看整个会话的内容。

-s 0是抓取完整数据包,否则默认只抓68字节。

另外,用tcpflow也可以方便的获取TCP会话内容,支持tcpdump的各种表达式。

3.1、UDP头

1
2
3
4
5
6
7
8
9
10
11
 0      7 8     15 16    23 24    31
+--------+--------+--------+--------+
| Source | Destination |
| Port | Port |
+--------+--------+--------+--------+
| | |
| Length | Checksum |
+--------+--------+--------+--------+
| |
| DATA ... |
+-----------------------------------+
  • 抓DNS请求数据
1
tcpdump -i eth1 udp dst port 53

3.2、其他

-c参数对于运维人员来说也比较常用,因为流量比较大的服务器,靠人工CTRL+C还是抓的太多,甚至导致服务器宕机,于是可以用-c参数指定抓多少个包。

1
time tcpdump -nn -i eth0 'tcp[tcpflags] = tcp-syn' -c 10000 > /dev/null

上面的命令计算抓10000个SYN包花费多少时间,可以判断访问量大概是多少。

四、参考资料

tcpdump advanced filters

Amazon CloudFront CDN + s3 源站跨域配置

发表于 2019-05-06 | 分类于 Aws

问题描述

使用 Amazon CloudFront CDN + s3 源站托管前端静态页面,前端跨域请求时报错:

1
...blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

解决方法

配置 Amazon CloudFront CDN 和 s3 支持跨域请求

1. s3 存储桶添加 CORS 配置

存储桶—>权限—>CORS配置,添加类似下面 xml 格式的 CORS 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>HEAD</AllowedMethod>
<AllowedMethod>PUT</AllowedMethod>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<ExposeHeader>x-amz-server-side-encryption</ExposeHeader>
<ExposeHeader>x-amz-request-id</ExposeHeader>
<ExposeHeader>x-amz-id-2</ExposeHeader>
<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

s3 CORS 相关配置项说明:

  • <AllowedOrigin>*</AllowedOrigin>: 允许访问来源,* 表示允许所有来源访问,具体根据实际情况配置;
  • <AllowedMethod>HEAD</AllowedMethod>: 允许的请求方法:GET、PUT、POST、DELETE、HEAD,不包含 OPTIONS 请求;
  • <MaxAgeSeconds>3000</MaxAgeSeconds>: 指定在 Amazon S3 针对特定资源的预检 OPTIONS 请求做出响应后,浏览器缓存该响应的时间(以秒为单位,在本示例中为 3000 秒)。通过缓存响应,在需要重复原始请求时,浏览器无需向 Amazon S3 发送预检请求。
  • 其他配置项解释见这里:https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/cors.html

使用 curl 测试存储桶 CORS 配置是否正确:

1
curl -I -v -L -H 'origin: <跨域请求的来源域名>' <s3资源地址>

如果响应头中有如下请求头,则表示配置正确:

1
2
Access-Control-Allow-Origin: <curl 请求时 -H 参数指定的值>
Access-Control-Allow-Methods: <s3 存储桶 CORS 配置指定的请求方法>

2. CloudFront 分发行为中配置正确的”白名单标头”:

打开 Amazon CloudFront 控制台—>点击要配置的分发—>选中”行为”列—>选中某条行为配置行,点击”编辑”—>”白名单标头”添加如下标头(CORS 相关配置,必须得添加,否则跨域请求时会出问题):

1
2
3
4
Access-Control-Request-Headers
Access-Control-Request-Method
CloudFront-Forwarded-Proto
Origin

3. CloudFront 缓存行为允许 OPTIONS 请求:

打开 Amazon CloudFront 控制台—>点击要配置的分发—>选中”行为”列—>选中某条行为配置行,点击”编辑”—>”缓存的 HTTP 方法” 下面勾选 OPTIONS 复选框

相关资料

https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/dev/cors.html
https://aws.amazon.com/cn/premiumsupport/knowledge-center/no-access-control-allow-origin-error/?nc1=h_ls

计算机网络协议学习总结

发表于 2019-05-01 | 分类于 计算机网络

计算机网络协议分层

目前关于网络通信的标准有两套协议,分别是 OSI 参考模型和 TCP/IP 模型。OSI 参考模型只用于理论研究,而 TCP/IP 模型更注重于实际应用,他们的关系如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
      +-------------------------+             +----------------------+
| | | |
| OSI Model | | TCP/IP Model |
| | | |
+-------------------------+ +----------------------+
| Application Layer | | |
+-------------------------+ | |
| Presentation Layer | | Application Layer |
+-------------------------+ | |
| Session Layer | | |
-------------------------------------------------------------------------------
| Transport Layer | | Transport Layer |
+-------------------------+ +----------------------+
| Network Layer | | Internet Layer |
-------------------------------------------------------------------------------
| Data Link Layer | | |
+-------------------------+ | Network Access Layer |
| Physical Layer | | |
+-------------------------+ +----------------------+

TCP/IP 模型各层常见协议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

+--------+ +--------+ +-----------+ +--------+ |
Application Layer | ping | | telnet | | OSPF | | DNS | User Space |
+---+----+ +----+---+ +----+------+ +---+----+ |
| | | | |
--------------------------------------------------------------------------------------socket------------
| | | | |
| +----+---+ | +---+----+ |
Transport Layer | | TCP | | | UDP | |
| +--------+----+ | +-------+--------+ |
| | | | |
---------------------------------------------------------------------------------- |
| | | | |
+---+----+ +--+--+--+-+ |
Internet Layer | ICMP +-------------+ IP | Kernel Space |
+--------+ +-----+----+ |
| |
---------------------------------------------------------------------------------- |
| |
+--------+ +-----+----+ +-------+ |
Network Access Layer | ARP +------------+ DataLink +------+ RARP | |
+--------+ +-----+----+ +-------+ |
| v
-------------------------------------------------------+-------------------------- Physical media

TCP/IP 数据包的封装与解封

在 TCP/IP 网络中,通信双方的交互过程就是对协议的封装与解封装的过程,发送方数据处理的方式是从高层到底层,逐层进行数据封装。接收方数据处理的方式是从底层到高层,逐层进行数据解封装。接收方的每一层只把对该层有意义的数据拿走,或者说每一层只能处理发送方同等层的数据,然后把其余的部分传递给上一层,这就是对等层通信的概念。

数据封装(Data Encapsulation)是指将协议数据单元(PDU)封装在一组协议头和尾中的过程。该过程是在协议数据单元(PDU)中实现的,其中每层的 PDU 一般由本层的协议头、协议尾和数据封装构成。

数据解封装是指对端的同等层对数据包解析的过程,通过解拆获取需要的数据,然后将数据传递到上层处理。

不同的协议层对数据包有不同的称谓:

  • TCP 传给 IP 的数据单元称作 TCP 报文段或简称为 TCP 段(TCP segment);
  • UDP 数据与 TCP 数据基本一致,唯一的不同是 UDP 传给 IP 的信息单元称作 UDP 数据报(UDP datagram),而且 UDP 的首部长为 8 字节;
  • IP 传给网络接口层的数据单元称作 IP 数据报(IP datagram);
  • 在数据链路层传输的数据单元称作帧(Frame );

下面为应用层数据进入 TCP/IP 协议栈时的封装过程(解封装的过程与该过程相反):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
                                    +-----------------+
| Application data|
+-----------------+
| |
v v
+--------------------------------+
|TCP/UDP header| Application data|
+--------------------------------+
| |
+<-- TCP segment/UDP datagram -->+
v v
+------------------------------------------+
|IP header|TCP/UDP header| Application data|
+------------------------------------------+
| |
+<-------------- IP datagram ------------->+
v v
+--------------------------------------------------------------+
|Eth header|IP header|TCP/UDP header| Application data|Eth tail|
+--------------------------------------------------------------+
| |
<-------------------- Ethernet frame --------------->+
| |
| <----------- 40~1500 Bytes ------------> |

以太网数据帧封装

以太网数据帧(也叫 MAC 帧)是网络硬件上面传送数据的最小单位,它承载了上层(网络层)的通信数据,是网络接口层的封包格式。我们一般接触比较多的是网络接口,比如 eht0、eht1…,这些网络接口上处理的主要数据包就是以太网数据帧。以太网数据帧封装格式在 RFC 894 中定义,具体格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

+----------+---------+------+-----------------------------------+------+
| MAC dst | Mac src | Type | Data | CRC |
+----------+---------+-------------------------------------------------+
6 6 2 | 46~1500 Bytes | 4


| Type |
+------------------------------------------+
|0x800 | IP datagram |
+------+-----------------------------------+
2 46~1500 Bytes

| Type |
+-------------------------+
|0x806 | ARP |PAD|
+------+------------------+
2 28 18

| Type |
+-------------------------+
|0x8035| RARP |PAD|
+-------------------------+
2 28 18

IP 数据报封装

IP 协议处于网络层,几乎所有上层协议都会使用到 IP 协议。 IP 有两种版本,一种是目前使用最广泛的 IPv4 (Internet Protocol version 4, 因特网协定第四版), 一种则是预期未来会热门的 IPv6 。
IPv4 记录的地址长度为 32 bit(4 Bytes),IPv6 的地址可以达到 128 bit(16 Bytes)。
下面为 IP 数据报的封装格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| 4 bits  | 4 bits  |    8 bitys     |3bits|          13 bits             |
+-------------------------------------------------------------------------+
| Version | IHL |Type of Service | Total Length |
+---------+---------------------------------------------------------------+
| Identification |Flags| Fragmentation Offset |
+-------------------+-----------------------------------------------------+
| Time To Live | Protocol | Header Checksum |
+-------------------+----------------+------------------------------------+
| Source Address |
+-------------------------------------------------------------------------+
| Destination Address |
+-------------------------------------------------------------------------+
| Options(Up to 40 bytes) |
+-------------------------------------------------------------------------+
| |
| Data |
| |
+-------------------------------------------------------------------------+
  • Version(版本): 版本号指定 IP 协议的版本,长度为 4 bits。对于 IPv4 来说,其值为 4;
  • IHL(Internet Header Length, IP 数据报头部长度):IP 数据报的头部长度,不包括数据部分,单位为 4 字节。由于该字段为 4 bits,所以 IP 封包头部最长为 (2^4-1)x4=60 字节;
  • Type of Service(服务类型):服务类型包括一个 3 位的优先权字段(现已被弃用),4 位的 TOS 字段和 1 位保留字段(必须置0)。4 位 TOS 字段分别表示最小延时、最大吞吐量、最高可靠性和最小费用;
  • Total Length(总长度):总长度表示整个 IP 数据报的长度,以字节为单位,可以看出最大值为 2^16-1=65535 字节;
  • Identification(标识码):标示字段唯一的标示主机发送的每一个 IP 数据报,初始值由系统随机生成;
  • Flags: 标志的第一位保留,第二位表示「禁止分片」。如果设置了这个位,系统不对 IP 报文分片。在这种情况下,如果 IP 数据报的长度超过 MTU(Max Transfer Unit,最大传输单元),IP 模块将丢弃该数据报并返回一个 ICMP 差错报文。第三位表示「更多分片」,如果为 1,表示后续还有该 IP 报文的分片;
  • Fragment Offset(分片偏移): 分片偏移是分片相对原始 IP 数据报开始处的偏移,在接收端组合分片时,根据这个字段决定各分片的先后顺序;
  • Time To Live(TTL, 存活时间): 表示这个IP封包的存活时间,范围为0-255。当这个IP封包通过一个路由器时, TTL就会减一,当TTL为0时,这个封包将会被直接丢弃;
  • Protocol(协议代码): 协议字段用来区分上层的协议,其中 ICMP 是 1,TCP 是6,UDP是 17,更多可查看 /etc/protocols 文件;
  • Header Checksum(头部校验码): 头部校验码由发送端填充,接收端使用 CRC 循环冗余校验算法检查 IP 数据报是否损坏;
  • Source Address: 发送端 IP 地址
  • Destination Address: 接收端 IP 地址
  • Options: 选项部分长度最大为 40 字节,最小为 0 字节。因为头部长度字段最大可表示 15,也就是说 IP 数据报的报头最大可以有 60 字节,而前面这些已经占了 20 字节,故选项部分最多只能有 40 字节。

IP 分片

当要发送的数据大于 MTU 的时候,通常需要进行 IP 分片,将数据分成多个 IP 数据报发送。MTU 一般为 1500 字节。

由上文可知,在 3 位的标志字段中,如果允许分片,则相同的 16 位的标识字段标识这些分片属于同一个数据块,片偏移标识这些分片的先后顺序。

IP 相关的概念

IP 地址的组成

IP 地址是一个 32 bits 的数值,为了记忆方便,一般将其写成 4 段以 . 号分隔的十进制形式:

1
2
3
IP的表示式: 
00000000.00000000.00000000.00000000 ==> 0.0.0.0
11111111.11111111.11111111.11111111 ==> 255.255.255.255

一个 IP 地址分为 Net_ID(网络号)和 Host_ID(主机号)两部分:

1
2
3
4
192.168.0.0~192.168.0.255 这个Class C 的说明:
11000000.10101000.00000000.00000000
11000000.10101000.00000000.11111111
|----------Net_ID---------|HOST_ID|

同一网段

在同一个物理网段内,主机的 IP 具有相同的 Net_ID(网络号) ,并且具有唯一的 Host_ID(主机号),那么这些 IP 群就是在同一个网段。

在同一个网段内,Net_ID 是不变的,而 Host_ID 则是不可重复,此外,Host_ID 在二进位的表示法当中,不可同时为 0 也不可同时为 1 ,因为全为 0 表示整个网段的地址(Network IP),而全为 1 则表示为广播的地址(Broadcast IP)。

IP 地址的分类

InterNIC 将整个 IP 网段分为五种等级, 每种等级的范围主要与 IP 那 32 bits 数值的前面几个位有关,基本定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
以二进位说明Network第一个数字的定义: 
Class A : 0 xxxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx ==> NetI_D的开头是0
|--net--|---------host------------|
Class B : 10 xxxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx ==> NetI_D的开头是10
|------net-------|------host------|
Class C : 110 xxxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx ==> NetI_D的开头是110
|-----------net-----------|-host--|
Class D : 1110 xxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx ==> NetI_D的开头是1110
Class E : 1111 xxxx.xxxxxxxx.xxxxxxxx.xxxxxxxx ==> NetI_D的开头是1111

五种分级在十进位的表示:
Class A : 0.xx.xx.xx ~ 127.xx.xx.xx
Class B : 128.xx.xx.xx ~ 191.xx.xx.xx
Class C : 192. xx.xx.xx ~ 223.xx.xx.xx
Class D : 224.xx.xx.xx ~ 239.xx.xx.xx
Class E : 240.xx.xx.xx ~ 255.xx.xx.xx

子网划分

前面我们知道了标准的 5 种 IP 等级划分,在一个 A 类网络中最多能有 2^24-2=16777214 台主机。其实在实际应用中一个网络不可能有这么多主机,有这么多主机意味着时时刻刻都会发生广播(比如 ARP 广播)风暴,导致一个网络的主机通信阻塞,根本不能正常工作。

为了解决上述问题,我们可以将一个大网络切分的更细点,让同一个网络中主机的数据量控制在合理的范围。具体的做法就是通过向 IP 的主机位借位,使其成为网络位,这样主机位就缩短了,从而减少了一个网络中主机的数量。

举例:一个 C 类 IP 地址网络位有 24 位,主机位有 8 位,那么这种情况下一个 C 类网络中就最多有 2^8-2=254 台主机。如果我们向主机位借一位,使其成为网络位,那么此时网络位就有 25 bit,主机位有 7 bit,所以此时一个网络中最多有 2^7-2=126 台主机。

子网掩码

子网掩码(Netmask)也是 32 位数值,在数值上,位于 Net_ID(网络位)的为 1,而 Host_ID(主机位)为 0

1
2
3
4
5
6
192.168.0.0~192.168.0.255 这个C Class 的Netmask 说明
第一个IP: 11000000.10101000.00000000.00000000
最后一个: 11000000.10101000.00000000.11111111
|----------Net_ID---------|--host--|
Netmask : 11111111.11111111.11111111.00000000 <== Netmask 二进位
: 255 . 255 . 255 . 0 <== Netmask十进位

网络相关的几个参数

  • Network:一个网络中的第一个 IP, Host_ID(网络位) 全为 0 时的 IP 地址,此时表示整个网段;
  • Broadcast:一个网络中的最后一个 IP,Host_ID(网络位) 全为 1 时的 IP 地址,即广播地址;
  • Netmask:位于 Net_ID(网络位)的为 1,而 Host_ID(主机位)为 0,此时的 IP 即为子网掩码;
    例题:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    试着计算出 172.16.0.0,但 Net_ID 占用 23 位时,这个网络的 Netmask, 
    Network, Broadcast 等参数?
    ---------------------------------------------------------------------
    解答:
    由于 172.16.xxx.xxx 是在 Class B 的等级当中,亦即 Net_ID 是16 位才
    对。不过题目给的Net_ID 占用了23 个位!等于是向 Host_ID 借了(23-16)
    7 个位用在 Net_ID 当中。所以整个 IP 的位址会变成这样:

    预设: 172 . 16 .0000000 0.00000000
    |----Net_ID-------|--Host--|
    Network: 172.16.0000000 0.00000000 172.16.0.0
    Broadcast: 172.16.0000000 1.11111111 172.16.1.255
    Netmask: 11111111.11111111.1111111 0.00000000 255.255.254.0

CIDR(Classless Interdomain Routing)

一般来说,如果我们知道了 Network 以及 Netmask 之后,就可以定义出该网段的所有 IP 了,因此,我们常常会以 Network 以及 Netmask 来表示一个网段,例如这样的写法:

1
2
3
Network/Netmask
192.168.0.0/255.255.255.0
192.168.0.0/24 <==因为 Net_ID 共有 24 bits

一般我们将一个网段 Network/网络位数量 这种写法称作 CIDR,比如:192.168.0.0/24。

路由概念

在同一个网段中可以通过局域网广播的方式传递数据报,但是在不同的网段中通信就需要借助于路由器的帮忙了。

我们以下面图示的例子来做说明。下列图示当中共有两个不同的网段,分别是Network A 与Network B,这两个网段是经由一部路由器(Server A) 来进行资料转递的,那么当 PC01 这部主机想要传送资料到 PC11 时, 它的IP 封包该如何传输呢?

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
+--------------------------------------------------+
| Network A |
| |
| +-------+ +-------+ |
| | | | | |
| | PC 01 | | PC 02 | |
| | | | | |
| +-------+--------+---------+-------+ |
| IP: 192.168.0.1 | IP: 192.168.0.2 |
| GW:192.168.0.254 | GW:192.168.0.254 |
+--------------------------------------------------+
|
192.168.0.254
+--------+
| |
|Server A|
| |
+--------+
192.168.1.254
|
+--------------------------------------------------+
| | |
| +-------+--------+-----------+-------+ |
| | | | | |
| | PC 11 | | PC 12 | |
| | | | | |
| +-------+ +-------+ |
| IP: 192.168.1.1 IP: 192.168.1.2 |
| GW:192.168.1.254 GW:192.168.1.254 |
| |
| Network B |
+--------------------------------------------------+

  1. 查询 IP 封包的目标 IP 地址:
    当 PC01 有 IP 封包需要传送时,主机会查阅 IP 封包头部的目标 IP 地址;

  2. 查询本机的路由配置:
    PC01 主机会分析自己的路由表,当发现目标 IP 与本机 IP 的 Net_ID 相同时(同一网段),则PC01 会直接透过局域网功能,将数据包直接传送给目的地主机。

  3. 查询默认网关(default gateway):
    但在本案例中, PC01 与 PC11 并非在同一网段,因此 PC01 会分析路由表当中是否有其他相符合的路由设定,如果没有的话,就直接将该 IP 封包送到默认网关(default gateway)上头去,在本案例当中 default gateway 则是Server A。

  4. 送出封包至 gateway 后,不理会封包流向:
    当 IP 由 PC01 送给 Server A 之后, PC01 就不理会接下来的工作。而 Server A 接收到这个封包后,会依据上述的流程,也分析自己的路由表,然后向后继续传输到正确的目的地主机上。

TCP 协议

TCP 协议处于四层参考模型的第三层,即传输层。同一层的还有 UDP 协议。TCP 和 UDP 的区别是:TCP 是可靠的,面向连接的,基于字节流的服务;UDP 是不可靠的,无连接的,面向数据块的服务。

解释如下:

  • 每次使用 TCP 传输数据时,都要先建立一对一的连接,即面向连接的。而使用 UDP 时不必先建立连接,而是直接将数据广播出去就可以,即无连接;
  • 因为 TCP 必须先建立连接,所以 TCP 传输的速度要比 UDP 慢;
  • TCP 使用一系列机制来保证数据的可靠传输,包括数据确认机制,超时重传机制等。
    发送 TCP 数据时,应用层需将数据写入到 OS 提供的 buffer 里面,操作系统将其看作一连串的,没有边界的数据流,通过相对序号进行定位;而发送 UDP 数据时,应用层交给 OS 多大的数据包,操作系统就直接发送出去。根本不考虑效率,如果数据太大,可能会在 IP 层进行分片,如果数据太小,则每个数据包的有效载荷会比较低,浪费带宽;

TCP 报头格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|4 bits|  6 bits |  6 bits |          16 bits           |
+----------------+--------------------------------------+
| Source Port | Destination Port |
+--------------------------+----------------------------+
| Sequence Number |
+-------------------------------------------------------+
| Acknowledge Number |
+----------------+---------+----------------------------+
|Data |Reserved | Code | Window |
|Offset| | | |
+----------------+--------------------------------------+
| Checksum | Urgent Pointer |
+--------------------------+----------------------------+
| Options(0~40 bytes) |
+-------------------------------------------------------+
  • Source Port & Destination Port(来源和目的端口):指明该 TCP 报文是由哪一个应用程序发送给哪一个应用程序的。因为端口号标示这应用层的一个服务进程;
  • Sequence Number(序号):序号表明该报文段在整个数据流中相对于开始位置的偏移量;
  • Acknowledge Number(确认号):确认号表明该报文是对对端的哪一个报文的确认,特别声明的是,只有当 ACK 标志为 1 时,确认号才有效。TCP 的数据确认机制就是通过这两个字段来实现的;
  • Data Offset:标识该 TCP 头部有多少个 4 字节,共表示最长 15x4=60 字节。同IP头部;
  • Reserved:保留不用,以便于将来扩展;
  • Code(Control Flag,控制标志码):共 6 位:S、A、F、U、R、P,含义分别为 S –> SYN,若为 1,表明这是一个请求报文。A –> ACK,若为 1,表明确认号有效,这是一个确认报文。F –> FIN,若为 1,表明这是一个断开连接的请求报文。U –> URG,若为 1,表明紧急指针有效。R –> RST,若为 1,表明这是一个复位报文段,接收端会清空自己的发送缓冲区。P –> PSH,若为 1,提示接收端应用程序应立即从缓冲区中读走数据;
  • Window(滑动窗口):用于流量控制,告诉对端自己的缓冲区大小,用于 TCP 的滑动窗口机制;
  • Checksum(确认检查码):由发送端对 TCP 头部和数据部分进行 CRC 循环冗余校验后填充,接收端以此确定该数据报是否损坏;
  • Urgent Pointer(紧急指针):若 URG 标志为 1,则紧急指针有效,指明 TCP 带外数据的相对位置;
  • Options(选项):目前此字段仅应用于表示接收端可以接收的最大数据区段容量,若此字段不使用, 表示可以使用任意数据区段的大小,这个字段较少使用。选项最大长度为 40 字节,计算方法和 IP 头部选项的计算方法一致;

公有云服务器磁盘分区扩展

发表于 2019-04-29 | 分类于 Linux

问题描述

在 web 控制台增加了磁盘空间大小,但是登陆到服务器 df -h 查看还是原先大小,并没有变化。

解决方法

1
2
3
4
5
6
# 查看设备名称
lsblk
# /dev/xvdb : 磁盘设备名称,扩展该磁盘的第一个分区
growpart /dev/xvdb 1
# /dev/xvdb1: 分区名称,扩展分区文件系统大小
resize2fs /dev/xvdb1

用 Dnsmasq 搭建一个简单的 DNS 服务器

发表于 2019-04-27 | 分类于 DNS

本文主要介绍如何通过 Dnsmasq 工具搭建一个简单的 DNS 服务器,搭建完成后就可以马上测试使用了。

Dnsmasq 简介

Dnsmasq 是一个轻量级的 DNS 缓存、DHCP、TFTP、PXE 服务器。

作为域名解析服务器,dnsmasq 可以通过缓存 DNS 请求来提高对访问过域名的解析速度。

作为 DHCP 服务器,Dnsmasq 可以用于为局域网电脑分配内网 IP 地址和提供路由。DNS 和 DHCP 两个功能可以同时或分别单独实现。

Dnsmasq 的应用场景

我们一般使用 Dnsmasq 的 DNS 功能,总结了下基于该功能有如下使用场景:

  • 作为内部局域网的一个 DNS 缓存服务器。通过 DNS 缓存的功能,可以提高应用程序域名解析的速度。比如 Kubernetes 的 kube-dns 组件中就用 dnsmasq 容器作为 DNS 服务器,用 kube-dns 容器作为 dnsmasq 的上游服务器。dnsmasq 本身具有缓存功能,所以可以大大提高集群中服务名的解析速度,而不需要每次解析请求都访问 kube-dns 容器。

  • 实现 DNS 劫持功能。在局域网中,我们有时候可能希望暂时将某个公网域名解析到一个临时的地址,不走公网 DNS。

Dnsmasq 的工作原理

Dnsmasq 在接受到用户的一个 DNS 请求时,首先会查找 /etc/hosts 这个文件,如果 /etc/hosts 文件没有请求的记录,然后查找 /etc/resolv.conf 中定义的外部 DNS(也叫上游 DNS 服务器,nameserver 配置),外部 DNS 通过递归查询查找到请求后响应给客户端,然后 dnsmasq 将请求结果缓存下来(缓存到内存)供后续的解析请求。

配置 Dnsmasq 为 DNS 缓存服务器,同时在 /etc/hosts 文件中加入本地内网解析,这样一来每当内网机器查询时就会优先查询 hosts 文件,这就等于将 /etc/hosts 共享给全内网机器使用,从而解决内网机器互相识别的问题。相比逐台机器编辑 hosts 文件或者添加 Bind DNS 记录,仅编辑一个 hosts 文件,这简直太容易了。

Dnsmasq 安装

Dnsmasq 的安装特别简单,以 Centos7 下安装为例:

1
sudo yum install -y dnsmasq

Dnsmasq 配置及启动

配置

Dnsmasq 的所有的配置都在 /etc/dnsmasq.conf 这一个文件中完成 。官方在配置文件 /etc/dnsmasq.conf 中针对选项和参数等做了比较好的注释说明,我们可以将配置做一次备份,以便以后查阅。默认情况下 dnsmasq.conf 中只开启了最后 include 项,因此可以在 /etc/dnsmasq.conf 的前提下,将自定义的配置放到 /etc/dnsmasq.d 目录下的一个任意名字的配置文件当中。

注意: /etc/dnsmasq.d/*.conf 的优先级大于 /etc/dnsmasq.conf

关于 dnsmasq 的配置项非常多,具体配置项含义在 /etc/dnsmasq.conf 中有详细的说明,本文如下配置实现一个简单的 DNS 服务器(配置文件放到了 /etc/dnsmasq.d/ 目录下,命名为 dnsmasq.conf):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#dnsmasq 启动监听的端口号
port=53

#从不转发格式错误的域名
domain-needed

#默认情况下Dnsmasq会发送查询到它的任何上游DNS服务器上,如果取消注释,
#则Dnsmasq则会严格按照/etc/resolv.conf中的 DNS Server 顺序进
#行查询,直到第一个成功解析成功为止。
strict-order

# dnsmasq 缓存大小,默认 150
cache-size=8192

#address 可以将指定的域解析为一个IP地址,即泛域名解析。
# 将 *.taobao.com 解析到 10.10.10.10
address=/taobao.com/10.10.10.10

#把所有.cn的域名全部通过 114.114.114.114 这台国内DNS服务器来解析
server=/cn/114.114.114.114

为了验证 /etc/hosts 文件解析是否起作用,我们也向 hosts 文件添加几条记录:

1
2
10.4.29.106      ansible
10.4.24.116 www.baidu.com

注意:/etc/hosts 文件修改后需要重启 dnsmasq,否则修改不会生效。
重启方法:systemctl restart dnsmasq

启动

1
2
3
4
# 设置为开机自启动
systemctl enable dnsmasq
# 启动 dnsmasq 服务
systemctl start dnsmasq

测试使用 Dnsmasq

我们搭建的 DNS 服务器地址为:192.168.10.200

使用 dig 命令指定 DNS 服务器地址来查看解析是否生效:

1
2
3
dig @192.168.10.200 ansible
dig @192.168.10.200 www.taobao.com
dig @192.168.10.200 ip.cn

验证 Dnsmasq 缓存功能是否生效

首先使用 dig 查询一个之前未查询过的域名,然后看响应时间是多少:
第一次 dig:

1
2
3
4
5
6
dig @192.168.10.200 qhh.me
......
;; Query time: 478 msec
;; SERVER: 192.168.10.200#53(192.168.10.200)
;; WHEN: Sat Apr 27 21:45:24 CST 2019
;; MSG SIZE rcvd: 56

第二次 dig:

1
2
3
4
5
6
dig @192.168.10.200 qhh.me                                                                                                                                   
......
;; Query time: 0 msec
;; SERVER: 192.168.10.200#53(192.168.10.200)
;; WHEN: Sat Apr 27 21:45:32 CST 2019
;; MSG SIZE rcvd: 67

可以看到两次同样的 dig 查询的时间不一样,第一次 478 ms,第二次 0 ms,说明第二次直接是从缓存中取的数据,没有向上游服务器发起请求。

Dnsmasq 的缓存在哪里?如何查看?

dnsmasq 的缓存并不是保存在本地磁盘的某个文件,而是存储在内存中,因此是无法直接查看的。当然作为一个 Geek,想要查看缓存的内容也是有办法的:

  1. dnsmasq 启动参数添加 –log-queries

    1
    2
    vi /usr/lib/systemd/system/dnsmasq.service
    ExecStart=/usr/sbin/dnsmasq -k 改为:ExecStart=/usr/sbin/dnsmasq -k --log-queries
  2. 重新加载 Systemd Unit 配置文件

    1
    systemctl daemon-reload
  3. 重启 dnsmasq

    1
    systemctl restart dnsmasq
  4. 执行如下命令 dump 出来缓存内容到 journal 日志

    1
    kill -SIGUSR1 <PID>
  5. 查看 dump 出来的 dns 记录(dnsmasq 当前缓存的内容)

    1
    journalctl -u dnsmasq

参考资料

http://www.thekelleys.org.uk/dnsmasq/doc.html | dnsmasq 官方文档
https://www.hi-linux.com/posts/30947.html | 一篇比较全面的博客
https://yq.aliyun.com/articles/582537 | 一篇比较精简的博客
http://flux242.blogspot.com/2012/06/dnsmasq-cache-size-tuning.html | 介绍了 dnsmasq 的基本概念、缓存淘汰机制等相关内容

1…567…14

haohao

Talk is cheap. Show me the code.

134 日志
35 分类
43 标签
GitHub CSDN 开源中国 E-Mail
© 2017 — 2021 haohao
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.3
访问人数 总访问量 次