Jekyll2018-08-27T10:38:27+00:00https://cherrot.com/Cherrot’sHappy hacking :)Cherrot Luo破解华为HG8346R/HG8347R光猫 启用有线桥接2017-02-05T18:58:00+00:002017-02-05T18:58:00+00:00https://cherrot.com/snippet/2017/02/05/hack-huawei-hg8346r-to-enable-bridge-mode<h2 id="修订记录">修订记录</h2>
<ul>
<li>2018.02.23: 补充了破解操作细节,并验证了<code class="highlighter-rouge">HG8347R</code>的破解。</li>
</ul>
<p>本文兼顾多款光猫型号,并兼容Windows、OSX/Linux,内容略杂,请按目录跳跃浏览。</p>
<h2 id="为何破解">为何破解</h2>
<ol>
<li>联通定制版无线限制了无线最多5个客户端,根本无法满足日常使用。</li>
<li>无线实时性差,不支持5GHz频段,带宽也不高,虽然理论能到300Mbps,实际差得远。</li>
<li>定制版限制太多,很多设置作为普通用户无法修改,甚至连无线SSID都强制以<code class="highlighter-rouge">CU_</code>开头,日了狗。</li>
</ol>
<p>由于定制版无法开启WAN口桥接,所以需要恢复光猫原生系统并使用管理员账户设置。(WAN口桥接模式,其实就是将光猫作为交换机使用,转发二层数据帧比路由三层IP包快的多,减少性能损耗。)</p>
<h2 id="准备工作">准备工作</h2>
<p>本文适用于(北京)联通定制版的华为<code class="highlighter-rouge">HG8346R</code>、<code class="highlighter-rouge">HG8347R</code>光纤接入终端。软件版本(可以登录web界面在“状态”中找到):<code class="highlighter-rouge">V3R015C10S109</code>, <code class="highlighter-rouge">V3R016C10S135</code>, <code class="highlighter-rouge">V3R017C10S103</code>,后文使用R15, R16, R17指代。</p>
<p>需要下载华为ONT维护工具包用于开启维护模式和加解密配置文件,并开启tftp服务器用于备份路由器配置。可以在我的<a href="https://drive.google.com/open?id=0B0roQ8Jv0ddWWGhBczNFSEVaS1U">Google Drive</a>或<a href="https://mega.nz/#F!SMAEWb4B!4ZudCOxdlEzScMPmILPruw">Mega网盘</a>下载。其中ONT工具shasum: <code class="highlighter-rouge">beb12c70bf2871de4254176be42b6748176a6471</code>。</p>
<p>如果软件版本为R17,需要额外下载固件包:<a href="https://drive.google.com/open?id=1tOo7CLWCtk0-T-oonVFB-fEUtnkiIB5Z">Google Drive</a>或<a href="https://mega.nz/#!uM42hITa!uw9BkxOLxPKerZrNMkZ9LanIlUrE8w9eZxZbHFc292g">Mega</a>。shasum: <code class="highlighter-rouge">7029508ec8f7e6a78660892557b8acdb41317803</code>。</p>
<h3 id="windows">Windows</h3>
<p>工具包里带了<code class="highlighter-rouge">tftpd32</code>和<code class="highlighter-rouge">tftpd64</code>程序,直接运行就可以启动tftp服务器。</p>
<p>另外建议下载<code class="highlighter-rouge">putty.exe</code>运行<code class="highlighter-rouge">telnet</code>命令,比如<code class="highlighter-rouge">telnet 192.168.1.1</code>,那么Host Name (or IP address)填写<code class="highlighter-rouge">192.168.1.1</code>,Connection Type选择Telnet,点击”Open”即可。</p>
<h3 id="osx--linux">OSX / Linux</h3>
<p>华为的维护工具包是Windows程序,需要安装<code class="highlighter-rouge">wine</code>模拟器:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># on OSX</span>
brew install wine
<span class="c"># on Ubuntu Linux</span>
<span class="nb">sudo </span>apt-get install wine
</code></pre></div></div>
<p>不过tftp服务器不能直接用<code class="highlighter-rouge">wine</code>模拟,所以需要单独安装。建议使用<a href="https://wiki.python.org/moin/tftp">Python的开源实现</a>,我用的<a href="http://tftpy.sourceforge.net/sphinx/index.html">TFTPy</a>:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>pip install TFTPy
</code></pre></div></div>
<p>只要三行代码就能启动一个TFTP服务器了:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">tftpy</span>
<span class="n">server</span> <span class="o">=</span> <span class="n">tftpy</span><span class="o">.</span><span class="n">TftpServer</span><span class="p">(</span><span class="s">'./tftp-dir'</span><span class="p">)</span>
<span class="n">server</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="s">'0.0.0.0'</span><span class="p">,</span> <span class="mi">69</span><span class="p">)</span>
</code></pre></div></div>
<p>或者<code class="highlighter-rouge">cd</code>到工具包目录,<code class="highlighter-rouge">sudo python tftpd.py</code>启动tftp服务</p>
<p>OSX用户<strong>不要使用系统自带的tftpd服务,基于tftpd的GUI版软件也不行(比如tftpserver)</strong>,因为要想上传文件(<code class="highlighter-rouge">put</code>),必须更改tftpd的启动参数(<code class="highlighter-rouge">-s</code>),但<code class="highlighter-rouge">OS X EI Capitan</code>禁用了编辑服务配置文件的权限,除非重启进入Recovery模式。另外我尝试直接运行<code class="highlighter-rouge">tftpd</code>也没戏,折腾个把小时还不如3行Python代码来的简单。</p>
<h2 id="lets-hack">Let’s hack!</h2>
<ol>
<li>拔出光纤,电脑用网线连到光猫<strong>LAN1</strong>口,我们假定电脑获取的IP是<code class="highlighter-rouge">192.168.1.2</code></li>
<li>浏览器进入<code class="highlighter-rouge">192.168.1.1</code>,按照光猫背面的用户名密码登录,在网络中关闭无线功能。</li>
<li>重启光猫(光猫后部有按钮开关),启动完成后,光猫的电源灯常亮,LOS灯红色闪烁(表示未接入光纤),LAN1灯闪烁(表示有数据流量)。</li>
</ol>
<h3 id="r17版本降级固件进入维护模式">R17版本:降级固件进入维护模式</h3>
<p>R17版本必须降级固件到R16才能成功进入维护模式,直接尝试开启维护模式会发现所有灯闪烁几遍后全灭,光猫无响应(重启恢复)。</p>
<ol>
<li>运行<code class="highlighter-rouge">华为ONT组播版本配置工具.exe</code>(OSX执行<code class="highlighter-rouge">wine 华为ONT组播版本配置工具.exe</code>,下文不再赘述),选择“升级”,选择计算机本地网卡<code class="highlighter-rouge">192.168.1.2</code>,点击“浏览”选择刚刚下载的<code class="highlighter-rouge">hg8347r16-rom.bin</code>,其他参数不变。</li>
<li>点击“启动”,这时光猫上所有灯会一起闪烁,刷入过程大概需要8分半。等到所有灯常亮,点击停止(工具下方的进度条没有任何意义,不用管它)。</li>
<li>重启光猫,完成后便已开启<code class="highlighter-rouge">telnet</code>服务。</li>
<li>浏览器登录<a href="http://192.168.1.1">http://192.168.1.1</a>,在安全设置中禁用防火墙,以防<code class="highlighter-rouge">telnet</code>无法连接。</li>
</ol>
<h3 id="r15-r16版本开启维护模式">R15, R16版本:开启维护模式</h3>
<ol>
<li>运行<code class="highlighter-rouge">华为ONT组播版本配置工具.exe</code>,选择本地网卡<code class="highlighter-rouge">192.168.1.2</code>,其他保持默认,点击<code class="highlighter-rouge">启动</code>按钮。</li>
<li>耐心等待2分钟左右,如果所有灯常亮,说明成功开启了维护模式。点击<code class="highlighter-rouge">停止</code>并关闭工具。</li>
<li>重启光猫,完成后便已开启<code class="highlighter-rouge">telnet</code>服务。</li>
</ol>
<h3 id="备份配置">备份配置</h3>
<ol>
<li>
<p>打开终端或命令行:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>telnet 192.168.1.1
root <span class="c">#(Login用户名)</span>
admin <span class="c">#(密码不回显)</span>
</code></pre></div> </div>
</li>
<li>
<p>确保本机tftp服务已开启,在<code class="highlighter-rouge">telnet</code>中执行:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>backup cfg by tftp svrip 192.168.1.2 remotefile hw_ctree.xml
</code></pre></div> </div>
<p>如果命令执行成功,你设定的tftp目录下(tftp-dir)便有了<code class="highlighter-rouge">hw_ctree.xml</code>配置文件了。
如果遇到错误<code class="highlighter-rouge">ERROR: command is not existed</code>,请参照评论中贴出的方法导出配置。</p>
</li>
<li>运行<code class="highlighter-rouge">华为光猫配置文件加解密工具.exe</code>,输入文件选择<code class="highlighter-rouge">hw_ctree.xml</code>,输出文件设置为<code class="highlighter-rouge">hw_ctree.dec.xml.gz</code>,解密。</li>
<li>解压<code class="highlighter-rouge">hw_ctree.dec.xml.gz</code>(OSX:<code class="highlighter-rouge">gzip -d hw_ctree.dec.xml.gz</code>)得到<code class="highlighter-rouge">hw_ctree.dec.xml</code>。</li>
</ol>
<h3 id="恢复华为出厂模式">恢复华为出厂模式</h3>
<ol>
<li>
<p>回到<code class="highlighter-rouge">telnet</code></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>su
shell
restorehwmode.sh <span class="c"># 可以输入?查看所有可用命令</span>
<span class="nb">exit
</span>reset
</code></pre></div> </div>
</li>
<li>等待路由器重启完成(如果不成功就直接按开关重启)。这时候光猫恢复到了出厂模式,将电脑设置为静态IP<code class="highlighter-rouge">192.168.100.2</code>,子网掩码<code class="highlighter-rouge">255.255.255.0</code>,网关<code class="highlighter-rouge">192.168.100.1</code>,浏览器访问<a href="http://192.168.100.1">http://192.168.100.1</a>,就可以看到华为默认的登录界面了。</li>
<li>用户名<code class="highlighter-rouge">telecomadmin</code>密码<code class="highlighter-rouge">admintelecom</code>登录,在系统工具栏,导出当前配置留作备份。</li>
</ol>
<h3 id="导入配置">导入配置</h3>
<p>简单粗暴式:备份并编辑<code class="highlighter-rouge">hw_ctree.dec.xml</code>,搜索<code class="highlighter-rouge">X_HW_WebUserInfoInstance</code>,修改为<code class="highlighter-rouge"><X_HW_WebUserInfoInstance InstanceID="1" UserName="用户名" Password="密码" UserLevel="0" Enable="1" ModifyPasswordFlag="0" PassMode="0"/></code>即可设置管理员账户。</p>
<p>小心翼翼式:备份并编辑刚刚导出的<code class="highlighter-rouge">hw_ctree.xml</code>,把<code class="highlighter-rouge">hw_ctree.dec.xml</code>中的<code class="highlighter-rouge"><WANDevice NumberOfInstances="1">...</WANDevice></code>, <code class="highlighter-rouge"><LANDevice NumberOfInstances="1">...</LANDevice></code>, <code class="highlighter-rouge"><VoiceService NumberOfInstances="1">...</VoiceService></code>, <code class="highlighter-rouge"><X_HW_IPTV....../></code>的节点内容完整替换过来。并修改<code class="highlighter-rouge">X_HW_WebUserInfoInstance</code>中的用户属性(<code class="highlighter-rouge">UserLevel="0"</code>表示管理员用户,<code class="highlighter-rouge">PassMode="0"</code>表示明文密码)</p>
<p>将修改后的配置在浏览器中重新导入,等待光猫重启即可生效。将本机网络重新设为DHCP,待光猫重启完成后即可登录<a href="http://192.168.1.1">http://192.168.1.1</a>进行后续设置。</p>
<h3 id="设置wan桥接交换机模式">设置WAN桥接(交换机模式)</h3>
<ol>
<li>进入WAN设置,将第二个 2_INTERNET_B_VID_3961的<code class="highlighter-rouge">WAN类型</code>改为<code class="highlighter-rouge">桥接WAN</code>。</li>
<li>关闭WLAN,防火墙等无用功能</li>
<li>将光猫LAN1口接到自己的无线路由器WAN口上,在无线路由器中配置PPPoE拨号即可。</li>
</ol>
<h3 id="iptv">IPTV</h3>
<p>如果要使用联通的IPTV机顶盒,可以重新打开无线WLAN1,并将WAN设置中的IPTV连接端口绑定到SSID1,这样机顶盒就可以使用光猫的无线信号观看IPTV了。</p>
<h2 id="宽带测速">宽带测速</h2>
<h3 id="hg8346r">HG8346R</h3>
<p>参考<a href="https://www.zhihu.com/question/21739060/answer/60425087">知乎导购网</a>在京东249入了一台<a href="https://item.jd.com/1559009.html">网件R6220</a>在<a href="http://www.speedtest.net">SPEEDTEST</a>测速,下行可以到<code class="highlighter-rouge">93.28Mbps</code>:</p>
<p><img src="http://www.speedtest.net/result/6027628179.png" alt="speedtest结果: 93.28Mbps" title="speedtest" /></p>
<p>华为<code class="highlighter-rouge">HG8346R</code>的LAN口都是百兆网卡,除去包头和包间距的损耗,峰值也就<code class="highlighter-rouge">94Mbps</code>,这个测速足够满意了。<del>另外<a href="http://www.chinadsl.net/thread-123723-1-1.html">联通使用单模光纤入户,理论最大速率155Mbps,换千兆猫意义也并不大</a>,所以就这么用着吧。</del></p>
<h3 id="hg8347r">HG8347R</h3>
<p>联通去年给自动升级到了200M带宽,升级光猫后LAN1口可到千兆,下行能到<code class="highlighter-rouge">186.45Mbps</code>:
<img src="http://www.speedtest.net/result/7083094506.png" alt="200M测速: 186.45Mbps" title="speedtest" /></p>
<p>美滋滋~</p>
<h2 id="致谢">致谢</h2>
<p>本文参考了以下教程,在此感谢作者的无私分享:</p>
<ul>
<li><a href="http://post.smzdm.com/p/441624/">北京联通华为光猫HG8346R破解改桥接-什么值得买</a></li>
<li><a href="http://www.chinadsl.net/forum.php?mod=viewthread&tid=128938">北京联通华为HG8347R破解-ChinaADSL</a></li>
</ul>Cherrot Luo修订记录SSH隧道:内网穿透实战2017-01-08T19:00:00+00:002017-01-08T19:00:00+00:00https://cherrot.com/tech/2017/01/08/ssh-tunneling-practice<h2 id="ssh支持的端口转发模式">SSH支持的端口转发模式</h2>
<p>正文开始前先用5分钟过一遍ssh支持的端口转发模式,具体使用场景在下一节详述。太长不看的可直接跳到下一节。</p>
<ol>
<li>
<p>”动态“端口转发(SOCKS代理):</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-D</span> 1080 JumpHost <span class="c"># D is for Dynamic</span>
</code></pre></div> </div>
<p>区别于下面要讲的其他端口转发模式,<code class="highlighter-rouge">-D</code>是建立在<code class="highlighter-rouge">TCP/IP</code>应用层的动态端口转发。这条命令相当于监听本地1080端口作为SOCKS5代理服务器,所有到该端口的请求都会被代理(转发)到JumpHost,就好像请求是从JumpHost发出一样。由于是标准代理协议,只要是支持SOCKS代理的程序,都能从中受益,访问原先本机无法访问而JumpHost可以访问的网络资源,不限协议(HTTP/SSH/FTP, TCP/UDP),不限端口。</p>
</li>
<li>
<p>本地端口转发</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-L</span> 2222:localhost:22 JumpHost <span class="c"># L is for Local</span>
</code></pre></div> </div>
<p>这条命令的作用是,绑定本机2222端口,当有到2222端口的连接时,该连接会经由安全通道(secure channel)转发到JumpHost,由JumpHost建立一个到localhost(也就是JumpHost自己) 22端口的连接。<br />
如果上述命令执行成功,新开一个终端,执行<code class="highlighter-rouge">ssh -p 2222 localhost</code>,登录的其实是JumpHost。<br />
所以<code class="highlighter-rouge">-L</code>是一个建立在<strong>传输层</strong>的,<strong>端口到端口</strong>的转发模式,当然远程主机不仅限于localhost。<br />
后面的stdio转发和ProxyJump可以看做是本地端口转发的升级版和便利版(参见<a href="https://blog.rootshell.be/2010/03/08/openssh-new-feature-netcat-mode/" title="OpenSSH netcat mode">OpenSSH netcat mode</a>)</p>
</li>
<li>
<p>远程端口转发</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-R</span> 8080:localhost:80 JumpHost <span class="c"># R is for Remote</span>
</code></pre></div> </div>
<p>顾名思义,远程转发就是在ssh连接成功后,绑定<strong>目标主机</strong>的指定端口,并转发到<strong>本地网络</strong>的某主机和端口:和本地转发相比,转发的方向正好反过来。<br />
假如在本机80端口有一个HTTP服务器,上述命令执行成功后,JumpHost的用户就可以通过请求<code class="highlighter-rouge">http://localhost:8080</code>来访问本机的HTTP服务了。</p>
</li>
<li>
<p>stdio转发(netcat模式)与ProxyJump</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-W</span> localhost:23 JumpHost
</code></pre></div> </div>
<p>netcat模式可谓ssh的杀手特性:通过<code class="highlighter-rouge">-W</code>参数开启到目标网络某主机和端口的stdio转发,可以看做是组合了netcat(<code class="highlighter-rouge">nc</code>)和<code class="highlighter-rouge">ssh -L</code>。上述命令相当于将本机的标准输入输出连接到了JumpHost的telnet端口上,就像在JumpHost上执行<code class="highlighter-rouge">telnet localhost</code>一样,而且并不需要在本机运行telnet!<br />
既然是直接转发stdio,用来做ssh跳板再方便不过(可以看做不用执行两遍ssh命令就直接跳到了目标主机),所以在ProxyJump面世前(OpenSSH 7.3),<code class="highlighter-rouge">ssh -W</code>常被用于构建主机到主机的透明隧道代理,而ProxyJump其实就是基于stdio转发做的简化,专门用于链式的SSH跳板。</p>
</li>
</ol>
<h2 id="使用场景">使用场景</h2>
<h3 id="建立代理">建立代理</h3>
<p>假设你在局域网A,HostB在局域网B,JumpHost有双网卡可以同时连接到局域网A和B。此时的你想要访问HostB上的web服务,便可以通过如下命令建立代理:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-D</span> <span class="s1">'[::]:1080'</span> JumpHost
</code></pre></div></div>
<p>这样,浏览器设置代理<code class="highlighter-rouge">socks5://localhost:1080</code>后,就可以直接访问<code class="highlighter-rouge">http://HostB</code>了。</p>
<p>当然,还可以通过这个代理ssh登录到HostB:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-oProxyCommand</span><span class="o">=</span><span class="s2">"nc -X 5 -x localhost:1080 %h %p"</span> HostB
</code></pre></div></div>
<p>其中, <code class="highlighter-rouge">nc</code>需要BSD版(Ubuntu和OS X默认就是BSD版本),<code class="highlighter-rouge">-X 5</code>指定代理协议为SOCKS5,<code class="highlighter-rouge">-x</code>指定了代理地址,<code class="highlighter-rouge">%h %p</code>用于<code class="highlighter-rouge">ProxyCommand</code>中指代代理目的地(HostB)和目的端口。更多代理用法参见lainme姐的<a href="https://www.lainme.com/doku.php/blog/2011/01/%E9%80%8F%E8%BF%87%E4%BB%A3%E7%90%86%E8%BF%9E%E6%8E%A5ssh" title="透过代理连接ssh">通过代理连接SSH</a>和<a href="https://www.lainme.com/doku.php/blog/2011/07/%E9%80%9A%E8%BF%87%E4%BB%A3%E7%90%86%E4%BD%BF%E7%94%A8git" title="透过代理连接git">通过代理使用GIT</a>。</p>
<p><code class="highlighter-rouge">ssh -D</code>也是最基本的翻墙手段之一。</p>
<h3 id="通过公网主机穿透两个内网">通过公网主机穿透两个内网</h3>
<p>好,现在进入一种更复杂的情况:你(HostA)和目标主机(HostB)分属不同的内网,从外界都无法直接连通。不过好在这两个内网都可以访问公网(JumpHost),你考虑通过一台公网机器建立两个内网之间的隧道。</p>
<p>于是在目标网络,你吩咐现场人员帮你连通公网主机:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Host in LAN-B</span>
ssh <span class="nt">-qTfNn</span> <span class="nt">-R</span> 2222:localhost:22 JumpHost
</code></pre></div></div>
<p><code class="highlighter-rouge">-qTfNn</code>用于告知ssh连接成功后就转到后台运行,具体含义见下一节解释。</p>
<p>现在,你只需要同样登录到跳板机JumpHost,就可以通过2222端口登录HostB了:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># in JumpHost, login HostB</span>
ssh <span class="nt">-p</span> 2222 localhost
</code></pre></div></div>
<h4 id="更进一步">更进一步</h4>
<p>如果我们将2222绑定为公网端口,甚至都不用登录跳板机,从而直接穿透到HostB:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-qTfNn</span> <span class="nt">-R</span> <span class="s1">'[::]:2222:localhost:22'</span> JumpHost
</code></pre></div></div>
<p>(因为要绑定公网端口,请确保在JumpHost的<code class="highlighter-rouge">/etc/ssh/sshd_config</code>里,配置了<code class="highlighter-rouge">GatewayPorts yes</code>,否则SSH Server只能绑定回环地址端口。)</p>
<p>在HostA上执行:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-p</span> 2222 JumpHost <span class="c"># Login to HostB</span>
</code></pre></div></div>
<p>这样还有一个好处,作为管理员可以直接禁用跳板机的shell权限,使他作为纯粹的隧道主机存在(见“安全性”一节)。</p>
<p>当然还有粗暴的方式,通过组合<code class="highlighter-rouge">ssh -D</code>和<code class="highlighter-rouge">ssh -R</code>打开Socks5代理:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Host in LAN-B</span>
ssh <span class="nt">-qTfNn</span> <span class="nt">-D</span> :1080 localhost <span class="o">&&</span> <span class="se">\</span>
ssh <span class="nt">-qTfNn</span> <span class="nt">-R</span> <span class="s1">'[::]:12345:localhost:1080'</span> JumpHost
</code></pre></div></div>
<p>上述命令在HostB创建了SOCKS代理,并且映射到了公网JumpHost的12345端口,整个内网对我们而言已经<strong>一览无余</strong>,ssh登录更是手到擒来:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Host in LAN-A</span>
ssh <span class="nt">-oProxyCommand</span><span class="o">=</span><span class="s2">"nc -X 5 -x JumpHost:12345 %h %p"</span> localhost
</code></pre></div></div>
<h4 id="限制访问">限制访问</h4>
<p>然而,直接在公网主机上暴露穿透到内网的端口非常<strong>不安全</strong>。为提高安全性,我们把远程转发限制到回环地址,
这样就限制了只有有权限登录JumpHost的人才能穿透到局域网B。首先在HostB上设定远程转发:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Host in LAN-B</span>
ssh <span class="nt">-qTfNn</span> <span class="nt">-R</span> 2222:localhost:22 JumpHost
</code></pre></div></div>
<p>在HostA执行:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Host in LAN-A</span>
<span class="c"># 通过ProxyJump跳板登录到目标主机,即使跳板机用户不能分配tty也没关系</span>
ssh <span class="nt">-J</span> JumpHost <span class="nt">-p</span> 2222 localhost
</code></pre></div></div>
<p>(如果要限制socks5代理的使用,道理也一样,不过是加一层由本机端口到跳板机socks5端口的本地转发而已)</p>
<p>如果OpenSSH版本<7.3, 需要用stdio转发(<code class="highlighter-rouge">ssh -W</code>)代替<code class="highlighter-rouge">-J</code>,该命令会先登录JumpHost,继而转发本机stdio到JumpHost,所以接下来的ssh登录操作如同是在JumpHost完成一样:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-oProxyCommand</span><span class="o">=</span><span class="s2">"ssh -W %h:%p JumpHost"</span> <span class="nt">-p</span> 2222 localhost
</code></pre></div></div>
<h3 id="通常意义的跳板">通常意义的”跳板“</h3>
<p>通常意义的”跳板“,指的是连接发起端A,经由跳板机B->C->D,连接到目标主机E的过程。连接和数据流都是单向的,比起上述情况反而简单了许多。这里不再赘述,只举两个简单的例子说明。更多示例参见<a href="https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Proxies_and_Jump_Hosts" title="OpenSSH/Cookbook/Proxies and Jump Hosts">OpenSSH/Cookbook/Proxies and Jump Hosts</a></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-L</span> 1080:localhost:9999 JumpHost <span class="nt">-t</span> ssh <span class="nt">-D</span> 9999 HostB
</code></pre></div></div>
<p>这条命令会在登录JumpHost时,建立本机1080端口到JumpHost 9999端口的转发,同时在JumpHost上执行ssh登录HostB,同时监听9999端口动态转发到HostB。于是,所有到本机1080端口的连接,都被代理到了远程的HostB上去。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-J</span> user1@Host1:22,user2@Host2:2222 user3@Host3
</code></pre></div></div>
<p>这条命令就是经由Host1, Host2,ssh登录到Host3的过程(需ssh版本高于7.3)。</p>
<h2 id="tips">Tips</h2>
<h3 id="ssh执行为后台任务">ssh执行为后台任务</h3>
<p><code class="highlighter-rouge">ssh -qTfNn</code>用于建立纯端口转发用途的ssh连接,参数具体含义如下:</p>
<ul>
<li><code class="highlighter-rouge">-q</code>: quiet模式,忽视大部分的警告和诊断信息(比如端口转发时的各种连接错误)</li>
<li><code class="highlighter-rouge">-T</code>: 禁用tty分配(pseudo-terminal allocation)</li>
<li><code class="highlighter-rouge">-f</code>: 登录成功后即转为后台任务执行</li>
<li><code class="highlighter-rouge">-N</code>: 不执行远程命令(专门做端口转发)</li>
<li><code class="highlighter-rouge">-n</code>: 重定向stdin为<code class="highlighter-rouge">/dev/null</code>,用于配合<code class="highlighter-rouge">-f</code>后台任务</li>
</ul>
<h3 id="安全性">安全性</h3>
<ul>
<li>
<p>建议为端口转发建立专门的账户,使用随机密码(当然使用私钥登录更好),并且禁掉其执行命令的权限。最简单的方式为</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># add user tunnel-user for ssh port forwarding</span>
<span class="nb">sudo </span>useradd <span class="nt">-m</span> tunnel-user
<span class="c"># generate 10 random passwords with 16 length</span>
pwgen <span class="nt">-sy1</span> 16 10
<span class="c"># pick one password and set it to tunnel-user</span>
<span class="nb">sudo </span>passwd tunnel-user
<span class="c"># disable shell for tunnel-user</span>
<span class="nb">sudo </span>chsh <span class="nt">-s</span> /bin/false tunnel-user
</code></pre></div> </div>
<p>更多可参考<a href="http://askubuntu.com/questions/48129/how-to-create-a-restricted-ssh-user-for-port-forwarding" title="Howto create SSH user only for tunneling">Ask Ubuntu</a></p>
</li>
<li>
<p>避免在公网直接暴露动态代理转发,<strong>很危险</strong>。 尽量远程端口转发到目标主机的ssh端口。这样需要远程接入的人可以自行ssh登录或打开本地Socks代理。</p>
</li>
</ul>
<h3 id="保持连接">保持连接</h3>
<p>客户端设置(<code class="highlighter-rouge">~/.ssh/config</code>):</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host *
ServerAliveInterval 180
</code></pre></div></div>
<p>每180秒向SSH Server发送心跳包,默认累积三次超时即认为失去连接。</p>
<p>服务器端同样可以设置心跳(<code class="highlighter-rouge">/etc/ssh/sshd_config</code>),作用同理:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ClientAliveInterval 180
</code></pre></div></div>
<h3 id="windows-客户端">Windows 客户端</h3>
<p>(我是个不喜欢贴图的人。。)以<a href="http://www.putty.org/">PuTTY</a>为例,假如这台Windows主机在内网,我们要借助公网主机的远程端口转发建立隧道:</p>
<ol>
<li>和往常一样,在<code class="highlighter-rouge">Session</code>菜单输入公网主机的IP和SSH端口</li>
<li>在<code class="highlighter-rouge">SSH</code>菜单里勾选<code class="highlighter-rouge">Don't start a shell or command at all</code>,以建立一个纯隧道连接(不需要登录shell)</li>
<li>展开<code class="highlighter-rouge">SSH</code>菜单,进入<code class="highlighter-rouge">Tunnels</code>子菜单:
<ol>
<li>勾选<code class="highlighter-rouge">Remote ports do the same (SSH-2 only)</code>,使远程端口监听公网连接。</li>
<li>输入具体端口配置,比如<code class="highlighter-rouge">Source port</code>(也就是远程主机要监听的端口)填写22222,<code class="highlighter-rouge">Destination</code>填写<code class="highlighter-rouge">HostIP:22</code>,其中HostIP为内网中SSH服务器的IP。</li>
<li>选择<code class="highlighter-rouge">Remote</code>, <code class="highlighter-rouge">Auto</code>,表示建立远程端口转发。<strong>点击<code class="highlighter-rouge">Add</code>添加配置</strong></li>
</ol>
</li>
<li>点击<code class="highlighter-rouge">Open</code>登录公网主机即可建立隧道。</li>
</ol>Cherrot LuoSSH支持的端口转发模式 正文开始前先用5分钟过一遍ssh支持的端口转发模式,具体使用场景在下一节详述。太长不看的可直接跳到下一节。关于Blueair净化效果的非严谨测试2016-12-24T14:48:00+00:002016-12-24T14:48:00+00:00https://cherrot.com/snippet/2016/12/24/air-purifier-and-pm25-sensor-test<p>持续一周的重度雾霾刚散去两天,今早就有卷土重来了。正好拿出好久以前从朋友那拿的<a href="https://item.taobao.com/item.htm?spm=a2106.m5221.1000384.3.8lFVH4&id=525981687758&scm=1029.newlist-0.1.0&ppath=&sku=&ug=#detail">汉王PM2.5激光检测模组</a>,对室内的空气质量和空气净化器的净化能力做个大致测试。</p>
<h2 id="测试环境">测试环境</h2>
<p>室外pm2.5 200μg/m³(<a href="http://aqicn.org/?city=Beijing/USembassy&lang=cn">美国大使馆监测数据</a>)五级(重度污染):心脏病和肺病患者症状显著加剧,运动耐受力降低,健康人群普遍出现症状。</p>
<p><img src="http://wgt.aqicn.org/aqiwgt/20161224/1MzIwNDM0MjIxNNV_2jP9ya41VqHBCqm5SYnFxZX6yXkA.png" alt="aqi-20161224" /></p>
<p>卧室+阳台20m²左右。Blueair 550E 保持2档,滤芯使用半年左右。</p>
<p>租房,无新风系统,房屋密闭性一般,客厅正门关闭后能明显感觉门缝漏风。从昨夜开始所有门窗保持关闭。</p>
<p>PM2.5检测模组和汉王霾表同款,串口转USB接到笔记本上读数。测定原理是使用一个小涡轮风扇带动空气通过传感器,激光传感器检测颗粒物体积和通过速度,进而基于以往的统计数据估算颗粒物质量。所以看原理就知道,家用霾表的读数和监测站几十万一台的专业设备相比一定是有误差的,所以重点看相对变化就好。</p>
<h2 id="pm25浓度测试">PM2.5浓度测试</h2>
<p>卧室内净化器保持2档运行,PM2.5平均读数 <strong>20μg/m³</strong>,客厅没有净化器,平均 <strong>80μg/m³</strong>。</p>
<p>开窗检测,第一次测定平均浓度140μg/m³,隔10分钟后第二次测定读数95-130μg/m³,平均 <strong>110μg/m³</strong>。</p>
<p>怀疑第一次是因为窗子开的较小,风速大导致测数偏高。(下文开窗通风后室内浓度也是110μg/m³,故第二次测定值应该更可信)</p>
<h2 id="blueair净化能力测试">Blueair净化能力测试</h2>
<ol>
<li>净化器先切换到1档(最低档),1档的风量连安慰剂都算不上。。</li>
<li>卧室开窗,过堂风通风5分钟,检测室内浓度在 <strong>110μg/m³</strong>。</li>
<li>关闭门窗,保持卧室尽量密闭。将净化器切换到自动档,很可惜,Blueair认为当前空气质量很好,(Dust指数为1),保持在1档运行。</li>
<li>手动设定在3档(最大风量),观察读数:
<ol>
<li>5分钟, 55μg/m³</li>
<li>12分钟, 30μg/m³</li>
<li>18分钟, 20μg/m³</li>
<li>28分钟, 10μg/m³</li>
<li>35分钟, 5μg/m³, 此后稳定在该值上下。</li>
</ol>
</li>
<li>切换回2档(3档噪音太大),读数稍有上浮,最后稳定在15μg/m³上下。</li>
</ol>
<h2 id="测试结论">测试结论</h2>
<ol>
<li>手持霾表和监测站相比确实有较大误差。</li>
<li>室内如果无法保证密封,即使紧闭门窗,空气质量和外界也相差无几。</li>
<li>Blueair 550E的颗粒物浓度探测器就是个辣鸡。之前就觉得这玩意儿不靠谱,空气糟糕时都是手动控制档位,这次算是用数据印证了。当然它的有机气味检测很灵敏,放个屁都能调3档呜呜好几分钟。</li>
<li>Blueair的滤网寿命貌似就是简单的计时器,没什么卵用。</li>
</ol>
<p>总之Blueair在诚意和做工上都不咋地。结合滤网寿命来看,机器和滤网都不便宜,但至少净化能力摆在这,全靠队友衬托啊。。</p>Cherrot Luo持续一周的重度雾霾刚散去两天,今早就有卷土重来了。正好拿出好久以前从朋友那拿的汉王PM2.5激光检测模组,对室内的空气质量和空气净化器的净化能力做个大致测试。UNIX下多进程共享监听socket的方式2016-01-05T01:08:00+00:002016-01-05T01:08:00+00:00https://cherrot.com/tech/2016/01/05/share-a-socket-among-multiple-processes<h2 id="黑科技nodejs的cluster不科学啊">黑科技:Node.js的cluster不科学啊</h2>
<p>上周同事问我一个问题,为什么使用<code class="highlighter-rouge">Node.js</code>的<code class="highlighter-rouge">cluster</code>时可以在worker进程中随意用
<code class="highlighter-rouge">server.listen(1234)</code>来监听某个端口而不冲突?这在直观看来相当不科学啊,不然就
不会总是有UNIX网络编程新手问为什么bind socket时遇到”Address in use”的问题了。</p>
<p>今天小搜一下,果然在<a href="http://stackoverflow.com/questions/9830741/how-does-the-cluster-module-work-in-node-js" title="How does the cluster module work in Node.js?">StackOverFlow</a>有知音:</p>
<blockquote>
<p>The worker processes are spawned using the [child_process.fork][] method, so
that they can communicate with the parent via IPC and pass server handles back
and forth.</p>
</blockquote>
<blockquote>
<p>When you call server.listen(…) in a worker, it serializes the arguments and
passes the request to the master process. If the master process already has a
listening server matching the worker’s requirements, then it passes the handle
to the worker. If it does not already have a listening server matching that
requirement, then it will create one, and pass the handle to the worker.</p>
</blockquote>
<p>上述答案援引自<a href="https://nodejs.org/api/cluster.html#cluster_how_it_works" title="How Cluster Works">Cluster官网</a>,不过第二段在官网中已经删掉,
备忘在这里以更清晰的理解其中原理。所以看上去是违反了UNIX常识,实际上只是对
<code class="highlighter-rouge">listen</code>方法做了包装,最终还是由master进程监听端口,并派发/路由请求到对应的
worker进程(默认使用round-robin方式路由请求)</p>
<h2 id="unix科普file-descriptor可以共享和传递">UNIX科普:file descriptor可以共享和传递</h2>
<p>然而我的好奇心并未止于此。多年没碰UNIX网络编程的我依稀记得一个file descriptor
只能由一个进程持有,可是为什么这样规定?像<code class="highlighter-rouge">nginx</code>,<code class="highlighter-rouge">node.js</code>这种一个master派发
若干个worker进程后,master中创建的file descriptor (TCP socket)在worker进程中
不能被使用吗?Google一下才知道,原来我的直观印象是错的,file descriptor当然可以
<a href="http://stackoverflow.com/questions/1997622/can-i-open-a-socket-and-pass-it-to-another-process-in-linux" title="Can I open a socket and pass it to another process in Linux">共享和传递</a>:</p>
<blockquote>
<p>Yes you can, using sendmsg() with SCM_RIGHTS from one process to another:</p>
</blockquote>
<blockquote>
<blockquote>
<p>SCM_RIGHTS - Send or receive a set of open file descriptors from another
process. The data portion contains an integer array of the file descriptors.
The passed file descriptors behave as though they have been created with
dup(2).
http://linux.die.net/man/7/unix</p>
</blockquote>
</blockquote>
<blockquote>
<p>that is not the typical usage tho. more common is when a process inherits
sockets from its parent (after a fork()). any file handles (including sockets)
not closed will be available to the child process. so the child process
inherits the parent’s sockets.</p>
</blockquote>
<p>也就是说,<code class="highlighter-rouge">fork</code>后的子进程天生就能继承父进程创建的file descriptor。这也是当下
<code class="highlighter-rouge">nginx</code>等web server处理和派发请求的方式。</p>
<p>顺便贴个链接复习一下UNIX网络编程的基础(en):
<a href="http://www.jasonernst.com/2011/03/22/tutorial-sockets-3-ways-to-listen/" title="Tutorial: sockets – 3 ways to listen">单连接, fork, select: 三种监听socket的方式</a></p>
<h2 id="更进一步通过so_reuseport享受linux内核提供的进程间负载均衡">更进一步:通过SO_REUSEPORT享受Linux内核提供的进程间负载均衡!</h2>
<p>好奇心驱使我仍未止步,在搜索<code class="highlighter-rouge">cluster</code>工作原理时,无意中发现BSD后来推出了一个黑
科技一般的Socket Option: <code class="highlighter-rouge">SO_REUSEPORT</code>,有了这个选项,我们可以让任意进程(
linux下限制必须是同一用户的进程)同时<code class="highlighter-rouge">bind</code>相同的source address和port而不报错!
切记不要用成<code class="highlighter-rouge">SO_REUSEADDR</code>,这两个Option目的不同。Linux在3.9版本以后正式支持了
<code class="highlighter-rouge">SO_REUSEPORT</code>,并且提供了<strong>进程间负载均衡</strong>的隐形福利:</p>
<blockquote>
<p>Additionally the kernel performs some “special magic” for SO_REUSEPORT sockets
that isn’t found in any other operating system so far: For UDP sockets, it
tries to distribute datagrams evenly, for TCP listening sockets, it tries to
distribute incoming connect requests (those accepted by calling accept())
evenly across all the sockets that share the same address and port combination.
That means while it is more or less random which socket receives a datagram or
connect request in other operating systems that allow full address reuse, Linux
tries to optimize distribution so that, for example, multiple instances of a
simple server process can easily use SO_REUSEPORT sockets to achieve a kind of
simple load balancing and that absolutely for free as the kernel is doing “all
the hard work” for them.</p>
</blockquote>
<p>更多细节请深度阅读<a href="http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t" title="Socket options SO_REUSEADDR and SO_REUSEPORT, how do they differ? Do they mean the same across all major operating systems?">StackOverFlow上的答案</a>,
虽然略长,但很浅显易懂,建议通读一遍。</p>
<p>所以直觉上来讲,使用<code class="highlighter-rouge">SO_REUSEPORT</code>,让内核处理负载均衡应该比让master进程负责
监听和派发请求到对应worker进程的方式更有效率,果不其然,<code class="highlighter-rouge">nginx</code>在1.9.1版本中的
Socket Sharding就是通过<code class="highlighter-rouge">SO_REUSEPORT</code>实现的:</p>
<p><img src="https://cdn.wp.nginx.com/wp-content/uploads/2015/05/Slack-for-iOS-Upload-1-e1432652484191.png" alt="before" /></p>
<p>(默认策略:master监听socket,workers使用<code class="highlighter-rouge">accept_mutex</code>竞争request connections)</p>
<p><img src="https://cdn.wp.nginx.com/wp-content/uploads/2015/05/Slack-for-iOS-Upload-e1432652376641.png" alt="after" /></p>
<p>(启用Socket Sharding后)</p>
<p>benchmark能达到默认策略的3倍性能(req/s和latency):</p>
<p><img src="https://cdn.wp.nginx.com/wp-content/uploads/2015/05/reuseport-benchmark.png" alt="benchmark" /></p>
<p>详见<a href="https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/" title="Socket Sharding in NGINX Release 1.9.1">官方Blog</a>。顺便也科普一下<a href="https://www.nginx.com/blog/inside-nginx-how-we-designed-for-performance-scale/" title="Inside NGINX: How We Designed for Performance & Scale">nginx的架构和原理</a></p>
<p>所以,未来版本的<code class="highlighter-rouge">cluster</code>或许也可以扔掉依靠IPC通信的黑魔法,直接使用
<code class="highlighter-rouge">SO_REUSEPORT</code>,以获得更劲爆的性能。</p>
<h2 id="后记">后记</h2>
<p><code class="highlighter-rouge">shadowsocks-libev</code>也通过<code class="highlighter-rouge">SO_REUSEPORT</code>支持了TCP port reuse. 有多个代理节点的话,可以很方便的在Linux上实现负载均衡。
不过在OS X,内核只会将请求全部派发到第一个bind端口的进程,除非该进程挂掉。或许FreeBSD也是如此设计。</p>Cherrot Luo黑科技:Node.js的cluster不科学啊Git清理本应ignore却被track的文件2015-12-15T19:07:00+00:002015-12-15T19:07:00+00:00https://cherrot.com/snippet/2015/12/15/git-clean-files-should-be-ignored<p>来自 #archlinux-cn @freenode IRC 2015-12-15</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>18:46 < tg2arch> [Isaac Ge] 我在写根据当前 Git 目录下的 .gitignore 内容来查找出被引入 Git 的垃圾文件
18:46 < tg2arch> [Isaac Ge] 的脚本
18:47 < tg2arch> [farseerfc] 爲什麼不直接
18:47 < tg2arch> [farseerfc] git clean
18:48 < tg2arch> [Isaac Ge] 都被引入了
18:48 < tg2arch> [Isaac Ge] 都被 tracked 了
18:49 < tg2arch> [farseerfc] git rm --cached -n
18:50 < tg2arch> [farseerfc] git ls-files -ci --exclude-standard -z | xargs -0 git rm --cached
</code></pre></div></div>Cherrot Luo来自 #archlinux-cn @freenode IRC 2015-12-15在git hook中执行后台进程2015-12-07T21:39:00+00:002015-12-07T21:39:00+00:00https://cherrot.com/snippet/2015/12/07/run-background-command-in-git-hooks<p>试过N个方案,最终可行的是结合重定向与<code class="highlighter-rouge">disown</code>的方案(注释掉的都是不可行的方法):</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>COMMAND </dev/null &>/dev/null & disown
#exec COMMAND >&- 2>&- &
#COMMAND |at now
#nohup COMMAND 2>&1 >/dev/null &
</code></pre></div></div>
<p>附上我在生产环境中使用的前端代码自动部署脚本
(<code class="highlighter-rouge">/path/to/my/repo.git/hooks/post-receive</code>):</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
<span class="c">#to enable code rollback (by `git push -f`):</span>
<span class="c">#git config receive.denynonfastforwards false</span>
<span class="nv">SRC_DIR</span><span class="o">=</span>/Path/to/checkout/your/original/code
<span class="nv">ROOT_DIR</span><span class="o">=</span>/Path/to/your/deployment/target
<span class="nv">GIT_WORK_TREE</span><span class="o">=</span><span class="nv">$SRC_DIR</span> git checkout <span class="nt">-f</span>
<span class="c"># if set GIT_WORK_TREE twice, file modification time would be overwritten</span>
<span class="c"># GIT_WORK_TREE=$ROOT_DIR git checkout -f</span>
rm <span class="nt">-rf</span> <span class="k">${</span><span class="nv">ROOT_DIR</span><span class="k">}</span>.old
cp <span class="nt">-rp</span> <span class="nv">$SRC_DIR</span> <span class="k">${</span><span class="nv">ROOT_DIR</span><span class="k">}</span>.new
mv <span class="nv">$ROOT_DIR</span> <span class="k">${</span><span class="nv">ROOT_DIR</span><span class="k">}</span>.old
mv <span class="k">${</span><span class="nv">ROOT_DIR</span><span class="k">}</span>.new <span class="nv">$ROOT_DIR</span>
<span class="nv">config_path</span><span class="o">=</span><span class="nv">$ROOT_DIR</span>/static/js/lib/require/require-config.js
<span class="k">for </span>file <span class="k">in</span> <span class="k">$(</span><span class="nb">grep</span> <span class="nt">-oP</span> <span class="s2">"(?<=[</span><span class="se">\'\"</span><span class="s2">])([^</span><span class="se">\'</span><span class="s2">^</span><span class="se">\"</span><span class="s2">]+</span><span class="se">\.</span><span class="s2">js)(?=[^</span><span class="se">\'</span><span class="s2">^</span><span class="se">\"</span><span class="s2">]*[</span><span class="se">\'\"</span><span class="s2">])"</span> <span class="nv">$config_path</span><span class="k">)</span><span class="p">;</span> <span class="k">do
if</span> <span class="o">[</span> <span class="nt">-f</span> <span class="nv">$SRC_DIR</span>/static/js/<span class="nv">$file</span> <span class="o">]</span><span class="p">;</span> <span class="k">then
</span><span class="nv">last_modify</span><span class="o">=</span><span class="k">$(</span>date +%m%d%H%M <span class="nt">-r</span> <span class="nv">$SRC_DIR</span>/static/js/<span class="nv">$file</span><span class="k">)</span>
sed <span class="nt">-i</span> <span class="s2">"s@</span><span class="nv">$file</span><span class="s2">[^</span><span class="se">\'</span><span class="s2">^</span><span class="se">\"</span><span class="s2">]*@</span><span class="k">${</span><span class="nv">file</span><span class="k">}</span><span class="s2">?_=</span><span class="nv">$last_modify</span><span class="s2">@g"</span> <span class="nv">$config_path</span>
<span class="k">fi
done
</span>find <span class="nv">$ROOT_DIR</span>/static/js <span class="nt">-type</span> f <span class="nt">-name</span> <span class="s2">"*.js"</span> <span class="nt">-exec</span> uglifyjs <span class="o">{}</span> <span class="nt">-o</span> <span class="o">{}</span> <span class="se">\;</span> </dev/null &>/dev/null & <span class="nb">disown</span>
<span class="c">#exec find . -type f -name "*.js" -exec uglifyjs {} -o {} \; >&- 2>&- &</span>
<span class="c">#find . -type f -name "*.js" -exec uglifyjs {} -o {} \; |at now</span>
<span class="c">#nohup find . -type f -name "*.js" -exec uglifyjs {} -o {} \; 2>&1 >/dev/null &</span>
<span class="nb">exit</span>
</code></pre></div></div>Cherrot Luo试过N个方案,最终可行的是结合重定向与disown的方案(注释掉的都是不可行的方法):Milestones2015-12-07T10:35:00+00:002015-12-07T10:35:00+00:00https://cherrot.com/life/2015/12/07/milestones<p>看了看以前留下的blog,蛮有趣的哈哈。</p>
<ol>
<li>2010-08-26, 一个通宵+两天,终于真正意义上有了自己的blog site. <code class="highlighter-rouge">wordpress</code>
框架,在<a href="http://freewebhostingarea.com">freewebhostingarea.com</a>找的免费
虚拟主机。
那时候我还是连SQL都不会写的渣渣,到处找免费的PHP虚拟主机,免费的SSH(翻墙)
当时还抄答案挑战过<a href="http://hax.tor.hu/welcome/">hax.tor.hu</a></li>
<li>2010-09-29, 第一次修复硬盘分区表,之前只是用FinalData恢复数据的段位。</li>
<li>2010-09-30, 真想回到这天对那时的自己说,千万别碰那本Java书! XDDDDD</li>
<li>2010-11-04, 第一次知道自己到底有多弱,并决心变强。<code class="highlighter-rouge">技术就是正义</code>。</li>
<li>2010-11-25, 带一队志愿者沿昆明宝象河做水环境调查,并使用Google Maps+GPS生成
一份水环境地图。</li>
<li>2011-01-01, 使用Grub4Dos硬盘安装<code class="highlighter-rouge">Ubuntu10.10</code>,从此爱上了Linux。经典的Gnome2
桌面,鼓捣个3D桌面毫无卡顿。记得<code class="highlighter-rouge">11.04</code>就变成了<code class="highlighter-rouge">Unity</code>的窗口管理器,那叫一个
垃圾,又慢又难用。</li>
<li>2011-05 开始,因为<code class="highlighter-rouge">Meego</code>应用开发比赛,开始接触语音识别和NLP。折腾
<code class="highlighter-rouge">Festival</code>,<code class="highlighter-rouge">Festvox</code>,<code class="highlighter-rouge">CMU Sphinx</code>。</li>
<li>2011-09-08, 我也不知道为什么就折腾起<code class="highlighter-rouge">LaTeX</code>来了。<code class="highlighter-rouge">LyX</code>+<code class="highlighter-rouge">TexLive</code>+<code class="highlighter-rouge">XeTeX</code>
with <code class="highlighter-rouge">xeCJK</code>。</li>
<li>2011-10-10, HTML5来了,也跟着玩了玩CSS3和<code class="highlighter-rouge">canvas</code>。</li>
<li>2011-10-15,从那时到现在,就一直使用<code class="highlighter-rouge">gnome-shell</code>作为桌面环境了。</li>
<li>2012-01-13, 这是个很有意义的冬天,斯坦福发布了两门公开课,分别是人工智能和
<a href="http://www.ml-class.org/course/class/index">机器学习</a>,没多久就正式成立了
<a href="https://www.coursera.org">coursera.org</a>,讲AI的老师也创办了在线教育网站
<a href="https://www.udacity.com/">udacity.com</a>。Andrew Ng浅显易懂的讲课风格让我受
益匪浅。</li>
<li>2012-04, 我还折腾过<code class="highlighter-rouge">Google Web Toolkit</code>和<code class="highlighter-rouge">Google App Engine</code>,还傻傻记录过
一些教程。。</li>
<li>2012-05-03, 第一次使用<code class="highlighter-rouge">vim</code>忘记是什么时候了,但第一次配置出一个强大的<code class="highlighter-rouge">vim</code>是
在这天。</li>
<li>2012-07, 开始进入鹅厂实习,负责<code class="highlighter-rouge">javascript</code>前端逻辑开发。</li>
</ol>Cherrot Luo看了看以前留下的blog,蛮有趣的哈哈。下雪2015-11-19T02:00:00+00:002015-11-19T02:00:00+00:00https://cherrot.com/life/2015/11/19/snowing-again<p>窗外又下起了小雪,雪花小到夜幕中完全看不出来。只是把手伸出去,感受到了一丝丝的
凉意。比起前些天的初雪,实在是少了些美感在里面。</p>
<p>胖次(我的猫)估计还不知道下雪为何物,这时候正在努力跳上窗台向我炫耀。话说这些
天还真没好好陪他,以至于经常听到他哀怨的喵喵声。</p>
<p>去年初雪已经是即将离开帝都的时候。抬头看到路灯下簌簌的雪花,细细拍在脸上,有些
寒酸,大抵就如同现在一样。然而那一刻却定格成了最美。就像冬天竟然坐在草坪上傻傻
看到的星空,走了很远找到的大半夜还开着门的烧烤店,麦当劳里的那一杯热可可。</p>
<p>晚安。</p>Cherrot Luo窗外又下起了小雪,雪花小到夜幕中完全看不出来。只是把手伸出去,感受到了一丝丝的 凉意。比起前些天的初雪,实在是少了些美感在里面。有些难过2015-11-13T22:00:00+00:002015-11-13T22:00:00+00:00https://cherrot.com/life/2015/11/13/hope-to-see-u-again<p>放任时间冲淡了心里的起伏,</p>
<p>又怎能责怪它不给一次机会</p>
<p>只是我会很难过</p>
<p>或许也只能是难过</p>Cherrot Luo放任时间冲淡了心里的起伏,碎碎念: ORM, python non-blocking, Go vs Elixir, MySQL on NUMA2015-10-05T23:20:00+00:002015-10-05T23:20:00+00:00https://cherrot.com/snippet/2015/10/05/murmur-on-orm-python+unblocking+design-go+vs+elixir-numa+mysql<p>这篇文章也算是最近半年的一个总结,所以糅合了多个主题。鉴于可爱的歪果仁们已经有
了很棒的总结,我没打算把引用的链接再统统编译一遍,去读原文吧,我自己的吐槽反倒
无足轻重。</p>
<h2 id="先说orm">先说ORM</h2>
<p>简言之,ORM sucks: <a href="http://www.bigdatalittlegeek.com/blog/2014/3/18/orm-the-killer-of-scalability" title="ORM - the Killer of Scalability">ORM - the Killer of Scalability</a>。文中详述了ORM的七宗罪
(为刺激你点开链接我就不引用过来了嗯)。在我看来ORM是个反模式(anti-pattern)。</p>
<p>=======TL;DR; 碎碎念分割线,请无视下文=======</p>
<p>第一次接触ORM正是大学时代使用Java Web开发时。在很傻很天真,图样图森破的年纪,
努力去学习并迷恋起这种依靠复杂的配置、叠加各种设计模式以期”解耦”的编程方式。
然而随着开发的深入,我发现给SQL穿上这么一套华丽的Object衣服后,很多本来看似简
单的问题也会让我手足无措,这让我花在google上的时间远多于花在coding上的,而其中
有不少问题都是用plain SQL可以很简单就能做到的事情。这也难怪市面上大把大把的
JaveEE指南,Spring+Hibernate+BlahBlah权威教程之类了。这套衣服不光拖慢了开发的脚
步(懂SQL不够,还要精通ORM),甚至SQL走起路来也往往比裸奔慢得多。当时学识尚浅,
井中看到的天空是似乎是唯一真理,但我仍然隐约觉得,That’s not the right way.</p>
<p>后面进入了PHP,使用hacking(用于分库分表路由)的ActiveRecord+PDO,比起Java世界
轻量了很多,在提供足够自由度的基础上尽量减轻了对开发的羁绊。再往后又来到了
Python web的世界(2015.1),在这里sqlalchemy已然成为事实标准,而且它提供的ORM
看起来还算不赖,应付一个小项目足够。然而该来的总会来,java orm的历史在python
世界重演了。</p>
<p>sqlalchemy的世界分为两块大陆:core和ORM,贴个<a href="http://solovyov.net/en/2011/basic-sqlalchemy/">最简明教程</a>,
我准备将sqlalchemy ORM从我负责的Flask项目中移除,并基于sqlalchemy core封装更轻
量级的sql wrapper,从此Say farewell to all the ORM evils.</p>
<h2 id="python-non-blocking-web-programming">Python non-blocking (web) programming</h2>
<p>你猜我是不是又要写python sucks了?还真没错哈哈。在讲非阻塞前,先聊聊python的
并行/异步编程。</p>
<h3 id="python并行编程">Python并行编程</h3>
<p>Python是一门”一个赛艇”的语言,它的<code class="highlighter-rouge">multiprocessing</code>和<code class="highlighter-rouge">threading</code>库也非常优雅,
然而,<code class="highlighter-rouge">GIL</code>(Global Interpretor Lock)却让<code class="highlighter-rouge">threading</code>处于一个相当尴尬的境地:如
果多线程还没有单线程效率高,那还用它作甚?相关介绍请参考
<a href="http://lesliezhu.github.io/public/2015/04/20/python-multi-process-thread.html" title="Python中的GIL、多进程和多线程">Python中的GIL、多进程和多线程</a></p>
<p>在Web开发中,很多时候也会借助这两个库实现异步处理,不过在Flask开发框架下,这并
不总是件很容易的事情,这多是因为Flask的request context stack和application context
stack的原因:在派生新进程时,需要拷贝一份请求上下文到新进程中,具体方法就搜一下
StackOverFlow吧,之所以这样的原因可以参考<a href="http://www.zlovezl.cn/articles/charming-python-start-from-flask-request/" title="Charming Python: 从Flask的request说起">这篇文章</a></p>
<h3 id="gevent-python中的协程库">Gevent-Python中的协程库</h3>
<p>这里我们说的是<a href="https://en.wikipedia.org/wiki/Coroutine">协程</a>,不是<code class="highlighter-rouge">epoll</code>等非阻
塞的系统调用。关于<code class="highlighter-rouge">gevent</code>可以在<a href="http://xlambda.com/gevent-tutorial/" title="Gevent指南">Gevent tutorial</a>快速入门。生产
环境中的WSGI服务器,如<code class="highlighter-rouge">gunicorn</code>和<code class="highlighter-rouge">uWSGI</code>,都是可以通过<code class="highlighter-rouge">gevent</code>处理请求从而增大
单进程吞吐量的。</p>
<p>然而,<code class="highlighter-rouge">gevent</code>也并非看上去那么完美:</p>
<ol>
<li>Monkey patching - an useful evil. 猴子补丁可以在不改变引用库的条件下使一些原
生不支持非阻塞的库支持非阻塞的使用。可惜猴子补丁是error-prone的,尤其是引入多
进程后,这个下文会讲。</li>
<li>猴子补丁对C实现的库没有作用,而且python在当前线程执行C实现的库,这就会导致整
个主线程阻塞在执行C代码上。比如使用MySQL-python执行MySQL查询。</li>
<li>调试困难。<code class="highlighter-rouge">gevent</code>的上下文切换是通过<code class="highlighter-rouge">yield</code>完成的,在异常追踪和性能调优时,往
往会力不从心。</li>
</ol>
<p>在<a href="https://engineering.pinterest.com/blog/how-we-use-gevent-go-fast" title="How we use gevent go fast">How we use gevent to go fast</a>中,介绍了针对<code class="highlighter-rouge">gevent</code>的调优策略。</p>
<h3 id="并行协程并发看上去很美">并行+协程并发,看上去很美…</h3>
<p>好吧,长话短说,<code class="highlighter-rouge">multiprocessing</code>在启用了monkey-patching的<code class="highlighter-rouge">gevent</code>环境中是不可用
的!有人对<code class="highlighter-rouge">multiprocessing</code>做了hack,推出了<a href="https://gehrcke.de/gipc/">gipc</a>,在
一些简单的场景下,这么搭配也还不错。不过<code class="highlighter-rouge">gipc</code>还比较年轻,应用场景很有限。一些细
节文档中并没有做说明,这里列举两个:</p>
<ol>
<li>示例中都是主进程作为生产者,子进程为消费者。然而如果方向反过来以后,管道写端
关闭会导致读端抛出<code class="highlighter-rouge">EOFError</code>。好在合理处理异常后不会造成管道数据错误。</li>
<li>管道描述符对象(_GIPCHandle)一旦分配给某个进程,那么该对象在当前进程下就不可使
用了,在处理进程同步时还是会有点小问题…</li>
</ol>
<p>看来想要实现并行+协程并发,需要依赖很精巧的设计。可惜即便如此,我在实际开发中还
是遇到了各种诡异的问题,比如派生多进程后性能并没有多少提高(系统并未满载),甚至
还有性能下降,而原因却难以定位;比如脚本运行的好好地,可Ctrl-C中断执行时,中断处
理代码中的数据库逻辑(sqlalchemy session)却抱怨连接丢失等等等等。焦头烂额之下,我
决定放弃用python实现高性能处理脚本。</p>
<h2 id="elixir-vs-go">Elixir vs Go</h2>
<p>最近Elixir正吸引着越来越多的关注,甚至有人期待Elixir能再次带来当年Ruby on Rails
那样井喷式的革命。两门语言都是为了解决摩尔定律失效后,多核环境下的编程语言效率(
语言执行效率和开发效率)问题;通过语言级别的协程支持和进程调度,让编写并发程序和
编写一个函数一样简单且健壮。</p>
<p>关于两门语言的选择,应当视应用场景而定。一个简单(但不普适)的结论大概是Go更适合
命令行和底层应用的开发,无需虚拟机、直接编译为二进制单文件等特性在这方面具有天生
的便利;而Elixir更适合服务器应用开发:</p>
<blockquote>
<p>That being said, have you tried writing a web app in Go? You can do it, but it
isn’t exactly entertaining. All those nice form-handling libraries you are used
to in Python and Ruby? Yeah, they aren’t nearly as good. You can try writing
some validation functions for different form inputs, but you’ll probably run
into limitations with the type system and find there are certain things you
cannot express in the same way you could with the languages you came from.
Database handling gets more verbose, models get very ugly with tags for JSON,
databases, and whatever else. It isn’t an ideal situation. I’m ready to embrace
simplicity, but writing web apps is already pretty menial work, Go only
exacerbates that with so many simple tasks.</p>
</blockquote>
<p>引用自 <a href="http://lebo.io/2015/06/22/the-unix-philosophy-and-elixir-as-an-alternative-to-go.html" title="The UNIX Philosophy and Elixir as an Alternative to Go">The UNIX Philosophy and Elixir as an Alternative to Go</a></p>
<p>我的第一印象也是这样,Go更像C语言(类型的定义、命令式的语言风格等),而且Go本身也
将自己作为一门”System Language”来设计的。而Elixir更面向应用开发:继承Erlang/OTP
的衣钵(电信级的健壮性保证),Ruby like的语法,管道符等诸多语法糖,代码的热更新部
署,逐步完善的包管理体制和开发工具链…这也是在我的技术路线图上Elixir要早于Go的
原因。</p>
<p>不光是WhatsApp, 还有一大票应用和游戏(比如使命召唤)选择了Elixir/Erlang, 推荐一
篇传教文:<a href="http://www.creativedeletion.com/2015/04/19/elixir_next_language.html">Elixir - The next big language for the web</a></p>
<p>关于两个语言更多的讨论可以参考<a href="https://news.ycombinator.com/item?id=9761470">Hacker News</a>和
<a href="https://www.reddit.com/r/elixir/comments/3c8yfz/how_does_go_compare_to_elixir/" title="How does GO compare to Elixir?">Reddit</a></p>
<h2 id="mysql在numa架构服务器上的swap问题">MySQL在NUMA架构服务器上的SWAP问题</h2>
<p>问题用一句话描述就是,MySQL server跑在一台
<a href="https://en.wikipedia.org/wiki/Non-uniform_memory_access">NUMA</a>架构的机器上,明
明还有不少空闲内存,但MySQL却开始了swap数据导致线上服务处于假死不响应的状态。
这里是<a href="http://cenalulu.github.io/linux/numa/" title="NUMA架构的CPU -- 你真的用好了么?">一份简要的中文介绍</a>,MySQL specific的话题建议将下面两篇文章读
完,Jeremy Cole大神解释的很细致:</p>
<ol>
<li><a href="http://blog.jcole.us/2010/09/28/mysql-swap-insanity-and-the-numa-architecture/" title="The MySQL swap insanity problem and the effects of the NUMA architecture">The MySQL swap insanity problem and the effects of the NUMA architecture</a></li>
<li><a href="http://blog.jcole.us/2012/04/16/a-brief-update-on-numa-and-mysql/" title="A brief update on NUMA and MySQL">A brief update on NUMA and MySQL</a></li>
</ol>Cherrot Luo这篇文章也算是最近半年的一个总结,所以糅合了多个主题。鉴于可爱的歪果仁们已经有 了很棒的总结,我没打算把引用的链接再统统编译一遍,去读原文吧,我自己的吐槽反倒 无足轻重。