请看下面这段脚本片断

while read FN; do
    test -e "$FN" || rm -iv "$FN"
done < <(find . $level -type l)

它用来查找一个目录下是否存在损坏的符号链接,然后对符号链接执行“rm -iv“命令。其中一个严重的问题是:为了获取find输出的结果,标准输入被重定向成为了find命令的输出。从而导致循环内部的rm -iv命令不能读取确认删除的信息。其原因是当程序执行到rm -iv的时候,标准输入已经不是原先的通过键盘的输入了,而是find命令的输出。为了解决这个问题,我们可以事先将原始的标准输入保存下来,专门提供给rm -iv来使用。请看下面的脚本片断:

exec 3<&0 # copies STDIN, it prevents 'read' stealing STDIN from '$command'
while read FN; do
    test -e "$FN" || rm -iv "$FN" <&3
done < <(find . $level -type l)

首先我们把标准输入即&0复制给一个新的描述符-3号描述符。然后在循环内部,我们使用<&3来重定向rm -iv的标准输入到3号描述符-也就是最初的标准输入。从而解决了重定向导致的键盘输入丢失问题。

No votes yet

本文的受众

如果你遇到了以下问题,那么你应该阅读这篇文章

  1. 我听说过这种技术,我对它很感兴趣
  2. 我想在家里访问我在公司的机器(写程序,查数据,下电影)。
  3. 公司为了防止我们用XX软件封锁了它的端口或者服务器地址。
  4. 公司不让我们上XX网站,限制了网址甚至IP。
  5. 公司不让我们看关于XX的信息,甚至花血本买了XX设备,能够对内容进行过滤。一看XX内容,链接就中断了。
  6. 我爸是搞电脑的,他在家里的路由器上动了手脚,我不能看XXX了。

带着这些问题,我们先从什么是ssh隧道开始。

什么是SSH隧道

首先看下面这张图,我们所面临的大部分情况都和它类似。我们的电脑在右上角,通过公司带有防火墙功能的路由器接入互联网(当然可能还有交换机什么的在中间连接着你和路由器,但是在我们的问题中交换机并不起到什么关键性的作用)。右下脚的部分是一个网站的服务器,它是我们公司防火墙策略的一部分,也就是说公司不希望我们访问这个服务器。在右上角还有一台机器,它也是属于我们的。但是这台机器并不在我们公司里面,换句话说他不受到公司防火墙的限制。最后也是最重要的一点是,我们能够在公司通过互联网直接访问这台机器。或者说这台位于公司防火墙外面的机器需要拥有一个独立的互联网IP,同时公司的防火墙规则不会屏蔽这台机器,并且这台机器运行着一个OpenSSH服务器。

现在,我们清楚地知道了自己所处的网络环境。并且不难理解我们在公司无法访问那个服务器的原因是:线路A-B-C上A-B之间的防火墙屏蔽了对那个服务器的访问。与此同时,我们也很快注意到,线路A-B-D之间、D-C之间是不受阻碍的。相信你已经想到了,在A-B之间的防火墙不会屏蔽对机器d的访问。因此我们可以通过机器d建立一个通道A-B-D-C,从而访问到机器c上的数据。

这条通道可以用很多技术来建立,这里我们仅仅介绍如何使用SSH服务器来建立这样一个通道-他被称为SSH隧道。

如何建立本地SSH隧道

在我们计划建立一个本地SSH隧道之前,我们必须清楚下面这些数据:

  1. 中间服务器d的IP地址
  2. 要访问服务器c的IP地址
  3. 要访问服务器c的端口

现在,我们把上面这张图变得具体一些,给这些机器加上IP地址。并且根据下面这张图列出我们的计划:

  1. 需要访问234.234.234.234的FTP服务,也就是端口21
  2. 中间服务器是123.123.123.123

现在我们使用下面这条命令来达成我们的目的

ssh -N -f -L 2121:234.234.234.234:21 123.123.123.123
ftp localhost:2121 # 现在访问本地2121端口,就能连接234.234.234.234的21端口了

这里我们用到了SSH客户端的三个参数,下面我们一一做出解释:

  • -N 告诉SSH客户端,这个连接不需要执行任何命令。仅仅做端口转发
  • -f 告诉SSH客户端在后台运行
  • -L 做本地映射端口,被冒号分割的三个部分含义分别是
    • 需要使用的本地端口号
    • 需要访问的目标机器IP地址(IP: 234.234.234.234)
    • 需要访问的目标机器端口(端口: 21)
  • 最后一个参数是我们用来建立隧道的中间机器的IP地址(IP: 123.123.123.123)

我们再重复一下-L参数的行为。-L X:Y:Z的含义是,将IP为Y的机器的Z端口通过中间服务器映射到本地机器的X端口。

在这条命令成功执行之后,我们已经具有绕过公司防火墙的能力,并且成功访问到了我们喜欢的一个FTP服务器了。

如何建立远程SSH隧道

通过建立本地SSH隧道,我们成功地绕过防火墙开始下载FTP上的资源了。那么当我们在家里的时候想要察看下载进度怎么办呢?大多数公司的网络是通过路由器接入互联网的,公司内部的机器不会直接与互联网连接,也就是不能通过互联网直接访问。通过线路D-B-A访问公司里的机器a便是不可能的。也许你已经注意到了,虽然D-B-A这个方向的连接不通,但是A-B-D这个方向的连接是没有问题的。那么,我们能否利用一条已经连接好的A-B-D方向的连接来完成D-B-A方向的访问呢?答案是肯定的,这就是远程SSH隧道的用途。

与本地SSH一样,我们在建立远程SSH隧道之前要清楚下面几个参数:

  • 需要访问内部机器的远程机器的IP地址(这里是123.123.123.123)
  • 需要让远程机器能访问的内部机器的IP地址(这里因为是想把本机映射出去,因此IP是127.0.0.1)
  • 需要让远程机器能访问的内部机器的端口号(端口:22)

在清楚了上面的参数后,我们使用下面的命令来建立一个远程SSH隧道

ssh -N -f -R 2222:127.0.0.1:22 123.123.123.123

现在,在IP是123.123.123.123的机器上我们用下面的命令就可以登陆公司的IP是192.168.0.100的机器了。

ssh -p 2222 localhost

-N,-f 这两个参数我们已经在本地SSH隧道中介绍过了。我们现在重点说说参数-R。该参数的三个部分的含义分别是:

  • 远程机器使用的端口(2222)
  • 需要映射的内部机器的IP地址(127.0.0.1)
  • 需要映射的内部机器的端口(22)

例如:-R X:Y:Z 就是把我们内部的Y机器的Z端口映射到远程机器的X端口上。

建立SSH隧道的几个技巧

自动重连

隧道可能因为某些原因断开,例如:机器重启,长时间没有数据通信而被路由器切断等等。因此我们可以用程序控制隧道的重新连接,例如一个简单的循环或者使用 djb’s daemontools . 不管用哪种方法,重连时都应避免因输入密码而卡死程序。关于如何安全的避免输入密码的方法,请参考我的 如何实现安全的免密码ssh登录 。这里请注意,如果通过其他程序控制隧道连接,应当避免将SSH客户端放到后台执行,也就是去掉-f参数。

保持长时间连接

有些路由器会把长时间没有通信的连接断开。SSH客户端的TCPKeepAlive选项可以避免这个问题的发生,默认情况下它是被开启的。如果它被关闭了,可以在ssh的命令上加上-o TCPKeepAlive=yes来开启。

另一种方法是,去掉-N参数,加入一个定期能产生输出的命令。例如: top或者vmstat。下面给出一个这种方法的例子:

ssh -R 2222:localhost:22 123.123.123.123 "vmstat 30"

检查隧道状态

有些时候隧道会因为一些原因通信不畅而卡死,例如:由于传输数据量太大,被路由器带入stalled状态。这种时候,往往SSH客户端并不退出,而是卡死在那里。一种应对方法是,使用SSH客户端的ServerAliveInterval和ServerAliveCountMax选项。ServerAliveInterval会在隧道无通信后的一段设置好的时间后发送一个请求给服务器要求服务器响应。如果服务器在ServerAliveCountMax次请求后都没能响应,那么SSH客户端就自动断开连接并退出,将控制权交给你的监控程序。这两个选项的设置方法分别是在ssh时加入-o ServerAliveInterval=n和-o ServerAliveCountMax=m。其中n, m可以自行定义。

如何将端口绑定到外部地址上

使用上面的方法,映射的端口只能绑定在127.0.0.1这个接口上。也就是说,只能被本机自己访问到。如何才能让其他机器访问这个端口呢?我们可以把这个映射的端口绑定在0.0.0.0的接口上,方法是加上参数-b 0.0.0.0。同时还需要打开SSH服务器端的一个选项-GatewayPorts。默认情况下它应当是被打开的。如果被关闭的话,可以在/etc/sshd_config中修改GatewayPorts no为GatewayPorts yes来打开它。

如何寻找中间服务器

如果你家里使用ADSL上网,多半你会比较幸运。一般的ADSL(例如 联通 的ADSL)都是有互联网地址的。你只需要在家里的路由器上一台装有OpenSSH server机器的SSH端口映射出去即可。同时一些提供SSH访问的虚拟主机也可以用于这一用途。例如: Hostmonser 或者 Dreamhost .

通过SSH隧道建立SOCKS服务器

如果我们需要借助一台中间服务器访问很多资源,一个个映射显然不是高明的办法(事实上,高明确实没有用这个方法)。幸好,SSH客户端为我们提供了通过SSH隧道建立SOCKS服务器的功能。

通过下面的命令我们可以建立一个通过123.123.123.123的SOCKS服务器。

ssh -N -f -D 1080 123.123.123 # 将端口绑定在127.0.0.1上
ssh -N -f -D 0.0.0.0:1080 123.123.123.123 # 将端口绑定在0.0.0.0上

通过SSH建立的SOCKS服务器使用的是SOCKS5协议,在为应用程序设置SOCKS代理的时候要特别注意。

总结

至此,我们已经对如何利用SSH隧道有一个基本的认识了。现在,文章开始时的那些问题应该迎刃而解了吧。这里要特别说一下,由于SSH隧道也使用了SSH加密协议,因此是不会被防火墙上的内容过滤器监控到的。也就是说一切在隧道中传输的数据都是被加密的。当然,离开隧道后的数据还是会保持自己原有的样子,没有加密的数据还是会被后续的路由设备监控到。

参考文献

OpenSSH网站

Your rating: None Average: 5 (6 votes)

使用命令pbpaste来将当前系统剪贴版的内容输出到标准输出上,例如:

pbpaste | sort

使用命令pbcopy来将后续从标准输入读取的内容放入剪贴版,例如:

ps | pbcopy

参考:http://developer.apple.com/DOCUMENTATION/Darwin/Reference/ManPages/man1/pbcopy.1.html

No votes yet

这个补丁的功能是

  1. 可以通过ctrl + n切换tab
  2. 一个看着更舒服的tab样式
diff -rupN orig/tabbed new/tabbed
--- orig/tabbed	2009-04-20 10:23:35.000000000 +0800
+++ new/tabbed	2009-04-20 10:29:38.000000000 +0800
@@ -10,8 +10,8 @@ sub refresh {

    my @ofs;

-   substr $text, 0, 7, "[NEW] |";
-   @$rend[0 .. 5] = ($self->{rs_tab}) x 6;
+   substr $text, 0, 7, "  NEW  ";
+   @$rend[0 .. 5] = ($self->{rs_tabbar}) x 6;
    push @ofs, [0, 6, sub { $_[0]->new_tab }];

    my $ofs = 7;
@@ -26,7 +26,7 @@ sub refresh {
       my $txt = "$act$idx$act";
       my $len = length $txt;

-      substr $text, $ofs, $len + 1, "$txt|";
+      substr $text, $ofs, $len + 1, "$txt ";
       @$rend[$ofs .. $ofs + $len - 1] = ($self->{rs_tab}) x $len
          if $tab == $self->{cur};

@@ -224,10 +224,10 @@ sub on_init {
    my $tabfg = $self->x_resource ("tab-fg");
    my $tabbg = $self->x_resource ("tab-bg");

-   defined $fg    or $fg    = 3;
-   defined $bg    or $bg    = 0;
-   defined $tabfg or $tabfg = 0;
-   defined $tabbg or $tabbg = 1;
+   defined $fg    or $fg    = 0;
+   defined $bg    or $bg    = 7;
+   defined $tabfg or $tabfg = 12;
+   defined $tabbg or $tabbg = 0;

    $self->{rs_tabbar} = urxvt::SET_COLOR (urxvt::DEFAULT_RSTYLE, $fg    + 2, $bg    + 2);
    $self->{rs_tab}    = urxvt::SET_COLOR (urxvt::DEFAULT_RSTYLE, $tabfg + 2, $tabbg + 2);
@@ -333,7 +333,12 @@ sub tab_key_press {
          $self->make_current ($self->{tabs}[$idx2]);

          return 1;
-      }
+      } elsif ($keysym > 48 && $keysym < 58) {
+         my ($idx) = $keysym - 49;
+         $self->make_current ($self->{tabs}[$idx]) if $self->{tabs}[$idx] != $tab and $idx < @{ $self->{tabs}};
+
+         return 1;
+	  }
    }

    ()
附件: 
Your rating: None Average: 4 (1 vote)

大学时数据结构课的作业。用MASM32实现了J.H.Conway生命游戏. 翻箱倒柜找出这么个东西,还勾起了不少大学时的回忆,哎~~

源代码

截图

No votes yet

LD_DEBUG is an environment variable for debugging library problem. The following example shows how to use it.
LD_DEBUG是一个用来调试共享库的环境变量,将其设置为help并运行任意程序即可看到他的使用说明

[jianingy(0)@nby ~/devel/trie]$ LD_DEBUG=help src/trietool 
Valid options for the LD_DEBUG environment variable are:

  libs        display library search paths
  reloc       display relocation processing
  files       display progress for input file
  symbols     display symbol table processing
  bindings    display information about symbol binding
  versions    display version dependencies
  all         all previous options combined
  statistics  display relocation statistics
  unused      determined unused DSOs
  help        display this help message and exit

To direct the debugging output into a file instead of standard output
a filename can be specified using the LD_DEBUG_OUTPUT environment variable.


No votes yet

可以通过下面的实例来了解LD_PRELOAD环境变量的使用方法

[jianingy(0)@nby ~]$ ldd /opt/bamboo/bin/bamboo 
	linux-gate.so.1 =>  (0x0082a000)
	libdl.so.2 => /lib/libdl.so.2 (0x009c7000)
	libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x051fc000)
	libm.so.6 => /lib/libm.so.6 (0x0099c000)
	libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x05110000)
	libc.so.6 => /lib/libc.so.6 (0x00110000)
	/lib/ld-linux.so.2 (0x00801000)
[jianingy(0)@nby ~]$ LD_PRELOAD="libcrfpp.so" ldd /opt/bamboo/bin/bamboo 
	linux-gate.so.1 =>  (0x00205000)
	libcrfpp.so => /usr/lib/libcrfpp.so (0x005f8000)
	libdl.so.2 => /lib/libdl.so.2 (0x009c7000)
	libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x051fc000)
	libm.so.6 => /lib/libm.so.6 (0x0099c000)
	libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x05110000)
	libc.so.6 => /lib/libc.so.6 (0x00826000)
	libpthread.so.0 => /lib/libpthread.so.0 (0x009ce000)
	/lib/ld-linux.so.2 (0x00801000)


No votes yet

当我们使用脚本远程执行sudo命令的时候,例如 ‘ssh somebox sudo somecmd’, 会发现自己的密码被显示在屏幕上了。其根本原因在于ssh执行sudo的时候没有tty设备被创建,因此也就不会有关闭回显的功能。然后却可以使用ssh的-t参数来创建一个伪终端(pseudo-tty)来解决这个问题。下面是一个利用这个参数的expect脚本。

#!/usr/bin/expect -f
set passwd ""
set range [lindex $argv 0]
set command [lrange $argv 1 end]
send_user "execute '$command' on $range\n"
stty -echo
send_user "(password) "
expect_user -re "(.*)\n"
stty +echo
set passwd $expect_out(1,string)
send_user "\n"
foreach host [exec xnode -r $range] {
    send_user ">> $host: "
    eval spawn -noecho ssh -t -o StrictHostKeyChecking=no $host "$command"
    match_max 10000
    expect {
        "(yes/no)" {
            send -- "yes\r"
            exp_continue;
        }
        "assword:" {
            send -- "$passwd\r"
            exp_continue;
        }
        "Enter passphrase" {
            send -- "\r"
            exp_continue;
        }
        eof {
        }
    }
}
No votes yet

采用德国Cherry的茶色开关,日本封装。做工精细,手感相当好,击键时有哒哒的响声。很有十几年前老电脑的感觉。此款编号为: FKBN87M-EB.

正面:

侧面:

Your rating: None Average: 5 (1 vote)

在有些Linux发行版上,WiFi指示灯在通信时不停的闪烁。下面给出一种方法停止这种闪烁:

#!/bin/sh

if [ "`id -u`" != "`id -u root`" ]; then
    echo "`whoami`: you need root priviledge to run this application. Invkoing sudo ..." 
    exec sudo bash -c "$0 $@"
fi

for trigger in /sys/class/leds/iwl-*X/trigger; do
    echo none > $trigger
    cat $trigger
done


Your rating: None Average: 3 (10 votes)