分类目录归档:个人日记

SSH反向代理使用心得

有一天想在家里用ssh控制公司的电脑,但是公司的机器处在内网中没有办法直接连上。而我家里的路由器装了Linux系统,而且通过DDNS有独立的域名,这样就可以用ssh反向代理,用公司的电脑ssh反向连接到家里的路由器,然后用家里的电脑ssh到路由器,从而连上公司的电脑。这时家里的路由器相当于是做了个ssh中继。
数据流是:家里电脑->路由器->公司电脑

ssh反向代理简单的来讲,就是用公司电脑在路由器上开了个端口,发送到这个端口的数据都会被转发到公司机器上指定的端口。下面是具体的命令,假设路由器的域名是router.zhoumz.com

ssh -gNfR *:2222:localhost:22 root@router.zhoumz.com

上面的这个命令是在公司电脑上执行的,它表示的含义是:

  • 用root用户登录到router.zhoumz.com
  • 告诉router.zhoumz.com去监听2222端口上来自所有IP的数据
  • 将数据发送到执行此命令机器(公司电脑)上的22端口

参数的含义分别是:

-g    Allows remote hosts to connect to local forwarded ports.

-N    Do not execute a remote command.  This is useful for just 
      forwarding ports (protocol version 2 only).

-f    Requests ssh to go to background just before command execution.

-R    [bind_address:]port:host:hostport
      Specifies that the given port on the remote (server) host is to
      be forwarded to the given host and port on the local side.  This
      works by allocating a socket to listen to port on the remote
      side, and whenever a connection is made to this port, the
      connection is forwarded over the secure channel, and a connection
      is made to host port hostport from the local machine.

      Port forwardings can also be specified in the configuration file.
      Privileged ports can be forwarded only when logging in as root on
      the remote machine.  IPv6 addresses can be specified by enclosing
      the address in square brackets.

      By default, the listening socket on the server will be bound to
      the loopback interface only.  This may be overridden by
      specifying a bind_address.  An empty bind_address, or the address
      `*', indicates that the remote socket should listen on all
      interfaces. Specifying a remote bind_address will only succeed
      if the server's GatewayPorts option is enabled (sshd_config(5)).

      If the port argument is `0', the listen port will be dynamically
      allocated on the server and reported to the client at run time.
      When used together with -O forward the allocated port will be
      printed to the standard output.

值得注意的是:想监听来自所有IP的数据,要在远程机器(本例中是路由器)上把GatewayPorts配置项打开 (see sshd_config(5))。

做好上面的步骤之后,回家用电脑ssh路由器的2222端口就可以直接控制公司电脑了。

ssh -p 2222 michael@router.zhoumz.com

这里注意:michael为公司电脑的登录用户名。

MacOS的launchd使用

MacOS的launchd可以完成开机启动任务、文件监控、守护进程的有用的工作。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Disabled</key>
	<false/>
	<key>Label</key>
	<string>com.zhoumingzhi.watcher</string>
	<key>ProgramArguments</key>
	<array>
		<string>/command/to/be/executed</string>
	</array>
	<key>WatchPaths</key>
	<array>
		<string>/path/to/be/watched</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>WorkingDirectory</key>
	<string>/Users/Michael</string>
</dict>
</plist>

将上面的文件保存成.plist后缀名的文件,然后放在~/Library/LaunchAgents目录中,每次登录后可以监控文件或文件夹的改变,当有变化时执行ProgramArguments指定的命令。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Disabled</key>
	<false/>
	<key>Label</key>
	<string>com.zhoumz.ssh-tunnel</string>
	<key>ProgramArguments</key>
	<array>
		<string>ssh</string>
		<string>-qTnN</string>
		<string>-D</string>
		<string>7070</string>
		<string>you@yourdomain.com</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>KeepAlive</key>
	<dict>
		<key>NetworkState</key>
		<true/>
	</dict>
</dict>
</plist>

将上面的文件保存成.plist后缀名的文件,然后放在~/Library/LaunchAgents目录中,每次登录后可以自动开启SSH反向代理,而且会在网络畅通时自动重连。

用launchctl load 和 launchctl unload命令可以很方便的加载和卸载.plist文件,而不用重启系统。

关于上面文件的含义,请参考:https://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man5/launchd.plist.5.html

关于LaunchAgents目录的作用,请参考:https://developer.apple.com/library/mac/#documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html#//apple_ref/doc/uid/10000172i-SW7-BCIEDDBJ

缓存的一点测试

做了一下动态生成内容的客户端缓存测试,记录一下用到的代码。

C#的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using System.Globalization;

namespace WebApplication
{
  [WebService(Namespace = "http://tempuri.org/")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  public class demo : IHttpHandler
  {

    public void ProcessRequest(HttpContext context)
    {
      // 设置缓存的秒数。
      TimeSpan timeout_duration = TimeSpan.FromSeconds(10);

      context.Response.ContentType = "application/javascript";
      context.Response.Cache.SetCacheability(HttpCacheability.Public);

      DateTime modifiedSince;
      // 解析请求的头的If-Modified-Since信息。
      DateTime.TryParse(context.Request.Headers["If-Modified-Since"], DateTimeFormatInfo.InvariantInfo, System.Globalization.DateTimeStyles.AdjustToUniversal, out modifiedSince);
      DateTime utcNow = DateTime.UtcNow;
      if (modifiedSince.AddTicks(timeout_duration.Ticks) < utcNow)
      {
        // 内容已过期,重新生成信息,响应200。
        context.Response.Cache.SetMaxAge(timeout_duration);
        context.Application["counter"] = int.Parse(context.Application["counter"].ToString()) + 1;
        context.Response.Cache.SetLastModified(utcNow);
        context.Response.StatusCode = 200;
        context.Response.Write(string.Format("document.write({0})", context.Application["counter"]));
      }
      else
      {
        // 内容未更改,响应304。
        context.Response.Cache.SetMaxAge(modifiedSince + timeout_duration - utcNow);
        context.Response.Cache.SetLastModified(modifiedSince);
        context.Response.StatusCode = 304;
      }
    }

    public bool IsReusable
    {
      get
      {
        return false;
      }
    }
  }
}

php的代码:

<?php
// 设置缓存的秒数。
$timeout_duration = 10;
// 解析请求的头的If-Modified-Since信息。
// 按CTRL+F5强制刷新的时候,这里将被设置为NULL。
$client_time_stamp = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
$client_time_stamp = (isset($client_time_stamp)? strtotime($client_time_stamp): 0);
$now = time();
$server_now_time_str = gmdate('D, d M Y H:i:s'). ' GMT';
header('Content-Type: application/javascript');
if(($client_time_stamp + $timeout_duration) < $now) {
	// 内容已过期,重新生成信息,响应200。
	header("Last-Modified: $server_now_time_str", true, 200);
	header("Cache-Control: max-age={$timeout_duration}");
	$micro = microtime();
	$script = <<<SCRIPT
	document.write('{$micro} <br />');
SCRIPT;
	echo $script;
} else {
	// 内容未更改,响应304。
	header("Last-Modified: {$_SERVER['HTTP_IF_MODIFIED_SINCE']}", true, 304);
	$max_age = $client_time_stamp + $timeout_duration - $now;
	header("Cache-Control: max-age={$max_age}");
}
exit(0);
?>

把无损音乐文件转到iPhone里

从网上下载了一张理查德.克莱德曼的CD,是.flac格式的,还带了一个.cue文件。我想把这张专辑转到iPhone里,用DAEMON Tools试着加载了一下.cue文件,结果报错了。在网上查了一下,用Foobar把.flac转换成.wav格式,然后用记事本把.cue文件打开,并且编辑下对应的CD文件名,比如把理查德.克莱德曼.flac改成理查德.克莱德曼.wav。然后用DAEMON Tools加载.cue文件,之后就可以在iTunes里进行转换了。