注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

windfly's sky

the sky I can fly like the wind

 
 
 

日志

 
 

树莓派内网穿透  

2016-09-07 00:12:00|  分类: linux learn |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
背景:我的树莓派运行在家里,当然时内网,而希望从单位能够访问,当然,单位也是内网,这就需要用到内网穿透了。
我要实现的,在单位也能访问树莓派的ssh和vnc。当然首先要启动vnc的服务#vncserver :1
关于内网穿透的介绍,百度百科上介绍的还是很详细的。参考:http://baike.baidu.com/view/2066783.htm
还有一篇P2P之UDP穿透NAT的原理与实现 写的也很好。参考:http://www.ppcn.net/p2ptech.html

选择了几种方案,下面一一列举:
1.如果能登录路由器的话,通过DMZ功能就可以把内网机器完全暴露给外网了,或者通过转发规则,把端口映射一下就可以通过外网地址访问内网计算机了。
可是adsl的外网地址是经常改变的,怎么办呢,配合个花生壳的动态dns,而且一般还集成在了路由器上。简直完美。但是,问题就出在了电信提供商上,现在大部分的运营商都禁用了这种直接外部访问的功能,所以这条路走不通了。
那就只能通过内网发起的方式来做内网穿透
2.花生壳。这个是鼎鼎大名的做动态DNS的,当然也做了内网穿透的功能,但是免费用户限制条件太多,最大的限制就是只能用于电信用户。只好放弃
3.ngrok。这个是个很好用的做内网穿透的,客户端把自己的端口映射到服务器的上,可以有二级域名,可以映射tcp,支持的客户端平台很丰富,地址ngrok.com。可是正是因为服务器在国外,所以网速有点慢,好处是如果注册了用户,可以在网站上看到自己的端口映射状态。
这个的使用方法,下载linux-arm版本的程序。在树莓派上运行
#./ngrok http 80
就把80端口映射出去了,可以看到分配的二级域名和端口。通过这个二级域名和端口就可以访问树莓派的80端口了。
#./ngrok tcp 22就把22端口映射出去了
如果要同时启动两个端口映射,可以通过修改配置文件的方式,配置文件默认为~/.ngrok2/ngrok.yml.直接修改就行了。
如果时注册用户可以通过
#./ngrok authtoken XXXXXXXXXXXXXXXXXXXXXXXXXXX来关联帐号,
修改yml可以参考https://ngrok.com/docs

4.使用国内的ngrok服务器。ngrok官网上的时2.0版本,实际上ngrok的1.x版本是开源的,因此就有了好心人自己假设了服务器来供大家使用,我找到的是这个:http://qydev.com  在此表示感谢。
使用方法与ngrok官方的稍有不同,毕竟版本变了。
启动方式稍有不同
#./de>ngrok -config=ngrok.cfg -subdomain xxx 80 这个是映射80端口
#./ngrok -proto=tcp -config=ngrok.cfg 22 启动22端口映射
如果需要同时映射多个端口,就需要修改配置文件即ngrok.cfg文件
修改为:

server_addr: "tunnel.qydev.com:4443"
trust_host_root_certs: false
tunnels:
vnc:
proto:
tcp: "5901"
ssh:
proto:
tcp: "22"

前两行是原有的定义服务器地址。后面是增加的两个新的端口。注意tcp:后面的空格和引号
然后启动 #./ngrok -config=ngrok.cfg start vnc ssh
后台启动#setsid ./ngrok -config=ngrok.cfg -log=log start vnc ssh
通过查看log文件可以看到映射到了服务器的哪个端口。
看到日志中有 Tunnel established at tcp://tunnel.qydev.com:11111和 Tunnel established at tcp://tunnel.qydev.com:22222两行就是映射的端口了。
通过在另一台机器上
#vncviewer tunnel.qydev.com::11111
#ssh tunnel.qydev.com -p22222
就可以了。
配置文件的编写部分参考了http://www.cnblogs.com/tianzhenyun/p/5543564.html
5.另一个国内的服务器natapp.cn,在ngrok的基础上做了一些封装,还提供了php版本的客户端,很可贵。
暂时没有试用
6.自己如果有外网服务器的话可以自参考这篇
http://www.cnblogs.com/tianzhenyun/p/5543564.html
7.还有一种方案nat123.com,还没有测试。

附录1;转自百度百科

内网穿透

编辑
所谓内网就是内部建立的局域网络或办公网络。举个例:一家公司或一个家庭有多台计算机,他们利用不同网络布局将 这一台或多台计算机或其它设备连接起来构成一个局部的办公或者资源共享网络,我们就称它为内部网络,也叫内网。所谓外网就是通过一个网关或网桥与其它网络 系统连接,相对于自己的内网来说,连接的其它网络系统就称为外部网络,也叫外网。举例说明:当一家公司或一个家庭的所有电脑网络想要与公司或家庭以外的网 络连接(比如连接互连网),相对于这家公司或家庭,其它网络(或互连网)就称为外网!
中文名
内网穿透
释    义
内部建立的局域网络办公网络
如何进行
端口映射
问    题
数据传输不稳定问题

名词解释

编辑
内网穿透即NAT穿透,网络连接时术语,计算机是局域网内时,外网与内网的计算机节点需要连接通信,有时就会出现不支持内网穿透。
就是说映射端口,能让外网的电脑找到处于内网的电脑,提高下载速度。
不管是内网穿透还是其他类型的网络穿透,都是网络穿透的统一方法来研究和解决。在百科词条
NAT穿越nat穿透中有关于网络穿透的详细信息。

如何进行

编辑
端口映射,其实就是常说的NAT地址转换的一种,其功能就是把在公网的地址转翻译成私有地址, 采用路由方式的ADSL宽带路由器拥有一个动态或固定的公网IP,ADSL直接接在HUB或交换机上,所有的电脑共享上网。
局域网内部的任一PC或服务器上运行到花生壳内网穿透客户端,此时域名解析到的IP地址是局域网网关出口处的公网IP地址,再在网关处做端口映射指向监控设备即可。

问题介绍

编辑
利用P2P点对点技术实现,需解决的两个问题。
1.实现内网之间机器的网络通信
2.需要解决UDP出现的数据传输不稳定问题。
假设一台在NAT211.133.*后的192.168.1.77:8000要向NAT211.134.*后的192.168.1.88:9000发送数据,假设你向
内网穿透 内网穿透
211.134.*这个IP地址的9000端口直接发送数据包, 则数据包在到达NAT211.134.*之后,会被当做无效非法的数据包被丢弃,NAT在此时相当于一个防火墙,会对没有建立起有效SESSION的数据 包进行拒绝转递。当然,你也不能直接用内网地址192.168.1.88进行发送数据包,这就好比你在广州要打电话到上海的某个地方,如果你不加区号,直 接拨打区域内电话是件很愚蠢的事。
首先我们要认识NAT设备,NAT英文全拼是Network Address Translator(网络地址转换器),说白了就是凡是经过NAT发出去的数据包,都会通过一定的端口转换(而非使用原端口)再发出去,也就是说内网和 外网之间的通信不是直接由内网机器与外网NAT进行,而是利用内网对外网的NAT建立起SESSION与外网NAT的SESSION进行。
根据SESSION的不同,NAT主要分成两种:SymmetricNAPT以及CONE NAPT。简单的说,Symmetric NAPT是属于动态端口映射的NAT,而CONE NAPT是属于静态端口映射的NAT。而市场上目前大多属于后者,CONE的意思就是一个端口可以对外部多台NAT设备通信。这个也正是我们做点对点穿透的基本,是我们所希望的,否则现在的大部分点对点软件将无法正常使用。
像上面的例子,NAT211.133.*和NAT211.134.*之间 需要进行通信,但开始不能直接就发数据包,我们需要一个中间人,这个就是外部索引服务器(我们假设是211.135.*:7000),当 NAT211.133.*向211.135.*:7000发送数据包,211.135.*:7000是可以正常接收到数据,因为它是属于对外型开放的服务 端口。当211.135.*:7000收到数据包后可以获知NAT211.133.*对外通信的临时SESSION信息(这个临时的端口,假设是6000 会过期,具体的时间不同,但我个人的测试是每30秒发送一个心跳包keep住连接以保证端口维持住通信连接不断开),索引服务器此时应将此信息保存起来。 而同时,NAT211.134.*也在时刻向索引服务器发送心跳包, 索引服务器就向NAT211.134.*发送一个通知,让它向NAT211.133.*:6000发送探测包(这个数据包最好多发几 个),NAT211.134.*在收到通知包之后再向索引服务器发送反馈包,说明自己已经向NAT211.133.*:6000发送了探测包,索引服务器 在接收到反馈包之后再向NAT211.133.*转发反馈包,NAT211.133.*在接收到数据包之后再向原本要请求的NAT211.134.*发送数据包,此时连接已经打通,实现穿透,NAT211.134.*会将信息转发给192.168.1.88的9000端口。
对于Symmetric NAPT的情况,网上有人说可以通过探测端口的方式,不过成功率并不高,我建议可用服务器进行中转。另外,最好在数据包发送前先检测是否进行的是同个NAT的情况,也就是内网发内网,如果是,直接发送即可,而无需通过外网再绕回来。
其次关于第二点,解决UDP传输的不稳定问题,其实这里涉及到另一个方面的知识,就是滑动窗口的东西,可以开一个缓冲区用于循环接收数据以及重组,另外加上超时重发机制以及确认发送机制,有点像TCP的传输原理,不过如果处理的好,效率绝对比采用TCP的方式要高。

附录2,转自http://www.ppcn.net/p2ptech.html

P2P之UDP穿透NAT的原理与实现(附源代码) | PPCN硬件网

时间: 2014-02-14 16:09 / 围观: 3,557 / 评论数:0 条 /

P2P 之 UDP穿透NAT的原理与实现(附源代码)

原创:shootingstars 参考:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt

论坛上经常有对P2P原理的讨论,但是讨论归讨论,很少有实质的东西产生(源代码)。呵呵,在这里我就用自己实现的一个源代码来说明UDP穿越NAT的原理。

首 先先介绍一些基本概念:     NAT(Network Address Translators),网络地址转换:网络地址转换是在IP地址日益缺乏的情况下产生的,它的主要 目的就是为了能够地址重用。NAT分为两大类,基本的NAT和NAPT(Network Address/Port Translator)。     最开始NAT是运行在路由器上的一个功能模块。          最先提出的是基本的NAT,它的产生基于如下事实:一个私有网络(域)中的节点中只有很少的节点需要与外网连接(呵呵,这是在上世纪90年代中期 提出的)。那么这个子网中其实只有少数的节点需要全球唯一的IP地址,其他的节点的IP地址应该是可以重用的。     因此,基本的NAT实现的功能很简单,在子网内使用一个保留的IP子网段,这些IP对外是不可见的。子网内只有少数一些IP地址可以对应到真正全 球唯一的IP地址。如果这些节点需要访问外部网络,那么基本NAT就负责将这个节点的子网内IP转化为一个全球唯一的IP然后发送出去。(基本的NAT会 改变IP包中的原IP地址,但是不会改变IP包中的端口)     关于基本的NAT可以参看RFC 1631          另外一种NAT叫做NAPT,从名称上我们也可以看得出,NAPT不但会改变经过这个NAT设备的IP数据报的IP地址,还会改变IP数据报的 TCP/UDP端口。基本NAT的设备可能我们见的不多(呵呵,我没有见到过),NAPT才是我们真正讨论的主角。看下图:                                 Server S1                                                   18.181.0.31:1235                                                                 |           ^  Session 1 (A-S1)  ^      |             |  18.181.0.31:1235  |      |              v 155.99.25.11:62000 v      |                                           |                                      NAT                                  155.99.25.11                                       |           ^  Session 1 (A-S1)  ^      |             |  18.181.0.31:1235  |      |             v   10.0.0.1:1234    v      |                                         |                                    Client A                                 10.0.0.1:1234     有一个私有网络10.*.*.*,Client A是其中的一台计算机,这个网络的网关(一个NAT设备)的外网IP是 155.99.25.11(应该还有一个内网的IP地址,比如10.0.0.10)。如果Client A中的某个进程(这个进程创建了一个 UDP Socket,这个Socket绑定1234端口)想访问外网主机18.181.0.31的1235端口,那么当数据包通过NAT时会发生什么事 情呢?     首先NAT会改变这个数据包的原IP地址,改为155.99.25.11。接着NAT会为这个传输创建一个Session(Session是一个 抽象的概念,如果是TCP,也许Session是由一个SYN包开始,以一个FIN包结束。而UDP呢,以这个IP的这个端口的第一个UDP开始,结束 呢,呵呵,也许是几分钟,也许是几小时,这要看具体的实现了)并且给这个Session分配一个端口,比如62000,然后改变这个数据包的源端口为 62000。所以本来是(10.0.0.1:1234->18.181.0.31:1235)的数据包到了互联网上变为了 (155.99.25.11:62000->18.181.0.31:1235)。     一旦NAT创建了一个Session后,NAT会记住62000端口对应的是10.0.0.1的1234端口,以后从18.181.0.31发送 到62000端口的数据会被NAT自动的转发到10.0.0.1上。(注意:这里是说18.181.0.31发送到62000端口的数据会被转发,其他的 IP发送到这个端口的数据将被NAT抛弃)这样Client A就与Server S1建立以了一个连接。

呵呵,上面的基础知识可能很多人 都知道了,那么下面是关键的部分了。     看看下面的情况:     Server S1                                     Server S2  18.181.0.31:1235                              138.76.29.7:1235         |                                             |         |                                             |         +———————-+———————-+                                |    ^  Session 1 (A-S1)  ^      |      ^  Session 2 (A-S2)  ^    |  18.181.0.31:1235  |      |      |  138.76.29.7:1235  |    v 155.99.25.11:62000 v      |      v 155.99.25.11:62000 v                                |                             Cone NAT                           155.99.25.11                                |    ^  Session 1 (A-S1)  ^      |      ^  Session 2 (A-S2)  ^    |  18.181.0.31:1235  |      |      |  138.76.29.7:1235  |    v   10.0.0.1:1234    v      |      v   10.0.0.1:1234    v                                |                             Client A                          10.0.0.1:1234     接上面的例子,如果Client A的原来那个Socket(绑定了1234端口的那个UDP Socket)又接着向另外一个 Server S2发送了一个UDP包,那么这个UDP包在通过NAT时会怎么样呢?     这时可能会有两种情况发生,一种是NAT再次创建一个Session,并且再次为这个Session分配一个端口号(比如:62001)。另外一 种是NAT再次创建一个Session,但是不会新分配一个端口号,而是用原来分配的端口号62000。前一种NAT叫做Symmetric NAT,后 一种叫做Cone NAT。我们期望我们的NAT是第二种,呵呵,如果你的NAT刚好是第一种,那么很可能会有很多P2P软件失灵。(可以庆幸的是,现在 绝大多数的NAT属于后者,即Cone NAT)         好了,我们看到,通过NAT,子网内的计算机向外连结是很容易的(NAT相当于透明的,子网内的和外网的计算机不用知道NAT的情况)。     但是如果外部的计算机想访问子网内的计算机就比较困难了(而这正是P2P所需要的)。     那么我们如果想从外部发送一个数据报给内网的计算机有什么办法呢?首先,我们必须在内网的NAT上打上一个“洞”(也就是前面我们说的在NAT上 建立一个Session),这个洞不能由外部来打,只能由内网内的主机来打。而且这个洞是有方向的,比如从内部某台主机(比 如:192.168.0.10)向外部的某个IP(比如:219.237.60.1)发送一个UDP包,那么就在这个内网的NAT设备上打了一个方向为 219.237.60.1的“洞”,(这就是称为UDP Hole Punching的技术)以后219.237.60.1就可以通过这个洞与内网的 192.168.0.10联系了。(但是其他的IP不能利用这个洞)。

呵呵,现在该轮到我们的正题P2P了。有了上面的理论,实现两个内网 的主机通讯就差最后一步了:那就是鸡生蛋还是蛋生鸡的问题了,两边都无法主动发出连接请求,谁也不知道谁的公网地址,那我们如何来打这个洞呢?我们需要一 个中间人来联系这两个内网主机。     现在我们来看看一个P2P软件的流程,以下图为例:

Server S (219.237.60.1)                           |                           |    +———————-+———————-+    |                                             |  NAT A (外网IP:202.187.45.3)                 NAT B (外网IP:187.34.1.56)    |   (内网IP:192.168.0.1)                      | (内网IP:192.168.0.1)    |                                             | Client A  (192.168.0.20:4000)             Client B (192.168.0.10:40000)

首 先,Client A登录服务器,NAT A为这次的Session分配了一个端口60000,那么Server S收到的Client A的地址是 202.187.45.3:60000,这就是Client A的外网地址了。同样,Client B登录Server S,NAT B给此次 Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000。     此时,Client A与Client B都可以与Server S通信了。如果Client A此时想直接发送信息给Client B,那么他 可以从Server S那儿获得B的公网地址187.34.1.56:40000,是不是Client A向这个地址发送信息Client B就能收到了 呢?答案是不行,因为如果这样发送信息,NAT B会将这个信息丢弃(因为这样的信息是不请自来的,为了安全,大多数NAT都会执行丢弃动作)。现在我们 需要的是在NAT B上打一个方向为202.187.45.3(即Client A的外网地址)的洞,那么Client A发送到 187.34.1.56:40000的信息,Client B就能收到了。这个打洞命令由谁来发呢,呵呵,当然是Server S。     总结一下这个过程:如果Client A想向Client B发送信息,那么Client A发送命令给Server S,请求Server S 命令Client B向Client A方向打洞。呵呵,是不是很绕口,不过没关系,想一想就很清楚了,何况还有源代码呢(侯老师说过:在源代码面前没有 秘密 8)),然后Client A就可以通过Client B的外网地址与Client B通信了。          注意:以上过程只适合于Cone NAT的情况,如果是Symmetric NAT,那么当Client B向Client A打洞的端口已经重 新分配了,Client B将无法知道这个端口(如果Symmetric NAT的端口是顺序分配的,那么我们或许可以猜测这个端口号,可是由于可能导致 失败的因素太多,我们不推荐这种猜测端口的方法)。          下面是一个模拟P2P聊天的过程的源代码,过程很简单,P2PServer运行在一个拥有公网IP的计算机上,P2PClient运行在两个不同 的NAT后(注意,如果两个客户端运行在一个NAT后,本程序很可能不能运行正常,这取决于你的NAT是否支持 loopback translation,详见http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt, 当然,此问题可以通过双方先尝试连接对方的内网IP来解决,但是这个代码只是为了验证原理,并没有处理这些问题),后登录的计算机可以获得先登录计算机的 用户名,后登录的计算机通过send username message的格式来发送消息。如果发送成功,说明你已取得了直接与对方连接的成功。     程序现在支持三个命令:send , getu , exit          send格式:send username message     功能:发送信息给username          getu格式:getu     功能:获得当前服务器用户列表          exit格式:exit     功能:注销与服务器的连接(服务器不会自动监测客户是否吊线)              代码很短,相信很容易懂,如果有什么问题,可以给我发邮件zhouhuis22@sina.com  或者在CSDN上发送短消息。同时,欢迎转发此文,但希望保留作者版权8-)。          最后感谢CSDN网友 PiggyXP 和 Seilfer的测试帮助

P2PServer.c

  1. /* P2P 程序服务端
  2.  *
  3.  * 文件名:P2PServer.c
  4.  *
  5.  * 日期:2004-5-21
  6.  *
  7.  * 作者:shootingstars(zhouhuis22@sina.com)
  8.  *
  9.  */
  10. #pragma comment(lib, “ws2_32.lib”)

  11. #include “windows.h”
  12. #include “..\proto.h”
  13. #include “..\Exception.h”

  14. UserList ClientList;

  15. void InitWinSock()
  16. {
  17.  WSADATA wsaData;

  18.  if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
  19.  {
  20.   printf(“Windows sockets 2.2 startup”);
  21.   throw Exception(“”);
  22.  }
  23.  else{
  24.   printf(“Using %s (Status: %s)\n”,
  25.    wsaData.szDescription, wsaData.szSystemStatus);
  26.   printf(“with API versions %d.%d to %d.%d\n\n”,
  27.    LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
  28.    LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));

  29.  }
  30. }

  31. SOCKET mksock(int type)
  32. {
  33.  SOCKET sock = socket(AF_INET, type, 0);
  34.  if (sock < 0)
  35.  {
  36.         printf(“create socket error”);
  37.   throw Exception(“”);
  38.  }
  39.  return sock;
  40. }

  41. stUserListNode GetUser(char *username)
  42. {
  43.  for(UserList::iterator UserIterator=ClientList.begin();
  44.       UserIterator!=ClientList.end();
  45.        ++UserIterator)
  46.  {
  47.   if( strcmp( ((*UserIterator)->userName), username) == 0 )
  48.    return *(*UserIterator);
  49.  }
  50.  throw Exception(“not find this user”);
  51. }

  52. int main(int argc, char* argv[])
  53. {
  54.  try{
  55.   InitWinSock();

  56.   SOCKET PrimaryUDP;
  57.   PrimaryUDP = mksock(SOCK_DGRAM);

  58.   sockaddr_in local;
  59.   local.sin_family=AF_INET;
  60.   local.sin_port= htons(SERVER_PORT);
  61.   local.sin_addr.s_addr = htonl(INADDR_ANY);
  62.   int nResult=bind(PrimaryUDP,(sockaddr*)&local,sizeof(sockaddr));
  63.   if(nResult==SOCKET_ERROR)
  64.    throw Exception(“bind error”);

  65.   sockaddr_in sender;
  66.   stMessage recvbuf;
  67.   memset(&recvbuf,0,sizeof(stMessage));

  68.   // 开始主循环.
  69.   // 主循环负责下面几件事情:
  70.   // 一:读取客户端登陆和登出消息,记录客户列表
  71.   // 二:转发客户p2p请求
  72.   for(;;)
  73.   {
  74.    int dwSender = sizeof(sender);
  75.    int ret = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(stMessage), 0, (sockaddr *)&sender, &dwSender);
  76.    if(ret <= 0)
  77.    {
  78.     printf(“recv error”);
  79.     continue;
  80.    }
  81.    else
  82.    {
  83.     int messageType = recvbuf.iMessageType;
  84.     switch(messageType){
  85.     case LOGIN:
  86.      {
  87.       //  将这个用户的信息记录到用户列表中
  88.       printf(“has a user login : %s\n”, recvbuf.message.loginmember.userName);
  89.       stUserListNode *currentuser = new stUserListNode();
  90.       strcpy(currentuser->userName, recvbuf.message.loginmember.userName);
  91.       currentuser->ip = ntohl(sender.sin_addr.S_un.S_addr);
  92.       currentuser->port = ntohs(sender.sin_port);

  93.       ClientList.push_back(currentuser);

  94.       // 发送已经登陆的客户信息
  95.       int nodecount = (int)ClientList.size();
  96.       sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));
  97.       for(UserList::iterator UserIterator=ClientList.begin();
  98.         UserIterator!=ClientList.end();
  99.         ++UserIterator)
  100.       {
  101.        sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender));
  102.       }

  103.       break;
  104.      }
  105.     case LOGOUT:
  106.      {
  107.       // 将此客户信息删除
  108.       printf(“has a user logout : %s\n”, recvbuf.message.logoutmember.userName);
  109.       UserList::iterator removeiterator = NULL;
  110.       for(UserList::iterator UserIterator=ClientList.begin();
  111.        UserIterator!=ClientList.end();
  112.        ++UserIterator)
  113.       {
  114.        if( strcmp( ((*UserIterator)->userName), recvbuf.message.logoutmember.userName) == 0 )
  115.        {
  116.         removeiterator = UserIterator;
  117.         break;
  118.        }
  119.       }
  120.       if(removeiterator != NULL)
  121.        ClientList.remove(*removeiterator);
  122.       break;
  123.      }
  124.     case P2PTRANS:
  125.      {
  126.       // 某个客户希望服务端向另外一个客户发送一个打洞消息
  127.       printf(“%s wants to p2p %s\n”,inet_ntoa(sender.sin_addr),recvbuf.message.translatemessage.userName);
  128.       stUserListNode node = GetUser(recvbuf.message.translatemessage.userName);
  129.       sockaddr_in remote;
  130.       remote.sin_family=AF_INET;
  131.       remote.sin_port= htons(node.port);
  132.       remote.sin_addr.s_addr = htonl(node.ip);

  133.       in_addr tmp;
  134.       tmp.S_un.S_addr = htonl(node.ip);
  135.       printf(“the address is %s,and port is %d\n”,inet_ntoa(tmp), node.port);

  136.       stP2PMessage transMessage;
  137.       transMessage.iMessageType = P2PSOMEONEWANTTOCALLYOU;
  138.       transMessage.iStringLen = ntohl(sender.sin_addr.S_un.S_addr);
  139.       transMessage.Port = ntohs(sender.sin_port);

  140.       sendto(PrimaryUDP,(const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr *)&remote, sizeof(remote));

  141.       break;
  142.      }

  143.     case GETALLUSER:
  144.      {
  145.       int command = GETALLUSER;
  146.       sendto(PrimaryUDP, (const char*)&command, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));

  147.       int nodecount = (int)ClientList.size();
  148.       sendto(PrimaryUDP, (const char*)&nodecount, sizeof(int), 0, (const sockaddr*)&sender, sizeof(sender));

  149.       for(UserList::iterator UserIterator=ClientList.begin();
  150.         UserIterator!=ClientList.end();
  151.         ++UserIterator)
  152.       {
  153.        sendto(PrimaryUDP, (const char*)(*UserIterator), sizeof(stUserListNode), 0, (const sockaddr*)&sender, sizeof(sender));
  154.       }
  155.       break;
  156.      }
  157.     }
  158.    }
  159.   }

  160.  }
  161.  catch(Exception &e)
  162.  {
  163.   printf(e.GetMessage());
  164.   return 1;
  165.  }

  166.  return 0;
  167. }
  1. /* P2P 程序客户端
  2.  *
  3.  * 文件名:P2PClient.c
  4.  *
  5.  * 日期:2004-5-21
  6.  *
  7.  * 作者:shootingstars(zhouhuis22@sina.com)
  8.  *
  9.  */

  10. #pragma comment(lib,”ws2_32.lib”)

  11. #include “windows.h”
  12. #include “..\proto.h”
  13. #include “..\Exception.h”
  14. #include <iostream>
  15. using namespace std;

  16. UserList ClientList;



  17. #define COMMANDMAXC 256
  18. #define MAXRETRY    5

  19. SOCKET PrimaryUDP;
  20. char UserName[10];
  21. char ServerIP[20];

  22. bool RecvedACK;

  23. void InitWinSock()
  24. {
  25.  WSADATA wsaData;

  26.  if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
  27.  {
  28.   printf(“Windows sockets 2.2 startup”);
  29.   throw Exception(“”);
  30.  }
  31.  else{
  32.   printf(“Using %s (Status: %s)\n”,
  33.    wsaData.szDescription, wsaData.szSystemStatus);
  34.   printf(“with API versions %d.%d to %d.%d\n\n”,
  35.    LOBYTE(wsaData.wVersion), HIBYTE(wsaData.wVersion),
  36.    LOBYTE(wsaData.wHighVersion), HIBYTE(wsaData.wHighVersion));
  37.  }
  38. }

  39. SOCKET mksock(int type)
  40. {
  41.  SOCKET sock = socket(AF_INET, type, 0);
  42.  if (sock < 0)
  43.  {
  44.         printf(“create socket error”);
  45.   throw Exception(“”);
  46.  }
  47.  return sock;
  48. }

  49. stUserListNode GetUser(char *username)
  50. {
  51.  for(UserList::iterator UserIterator=ClientList.begin();
  52.       UserIterator!=ClientList.end();
  53.        ++UserIterator)
  54.  {
  55.   if( strcmp( ((*UserIterator)->userName), username) == 0 )
  56.    return *(*UserIterator);
  57.  }
  58.  throw Exception(“not find this user”);
  59. }

  60. void BindSock(SOCKET sock)
  61. {
  62.  sockaddr_in sin;
  63.  sin.sin_addr.S_un.S_addr = INADDR_ANY;
  64.  sin.sin_family = AF_INET;
  65.  sin.sin_port = 0;

  66.  if (bind(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0)
  67.   throw Exception(“bind error”);
  68. }

  69. void ConnectToServer(SOCKET sock,char *username, char *serverip)
  70. {
  71.  sockaddr_in remote;
  72.  remote.sin_addr.S_un.S_addr = inet_addr(serverip);
  73.  remote.sin_family = AF_INET;
  74.  remote.sin_port = htons(SERVER_PORT);

  75.  stMessage sendbuf;
  76.  sendbuf.iMessageType = LOGIN;
  77.  strncpy(sendbuf.message.loginmember.userName, username, 10);

  78.  sendto(sock, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote,sizeof(remote));

  79.  int usercount;
  80.  int fromlen = sizeof(remote);
  81.  int iread = recvfrom(sock, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
  82.  if(iread<=0)
  83.  {
  84.   throw Exception(“Login error\n”);
  85.  }

  86.  // 登录到服务端后,接收服务端发来的已经登录的用户的信息
  87.  cout<<“Have “<<usercount<<” users logined server:”<<endl;
  88.  for(int i = 0;i<usercount;i++)
  89.  {
  90.   stUserListNode *node = new stUserListNode;
  91.   recvfrom(sock, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
  92.   ClientList.push_back(node);
  93.   cout<<“Username:”<<node->userName<<endl;
  94.   in_addr tmp;
  95.   tmp.S_un.S_addr = htonl(node->ip);
  96.   cout<<“UserIP:”<<inet_ntoa(tmp)<<endl;
  97.   cout<<“UserPort:”<<node->port<<endl;
  98.   cout<<“”<<endl;
  99.  }
  100. }

  101. void OutputUsage()
  102. {
  103.  cout<<“You can input you command:\n”
  104.   <<“Command Type:\”send\”,\”exit\”,\”getu\”\n”
  105.   <<“Example : send Username Message\n”
  106.   <<”          exit\n”
  107.   <<”          getu\n”
  108.   <<endl;
  109. }

  110. /* 这是主要的函数:发送一个消息给某个用户(C)
  111.  *流程:直接向某个用户的外网IP发送消息,如果此前没有联系过
  112.  *      那么此消息将无法发送,发送端等待超时。
  113.  *      超时后,发送端将发送一个请求信息到服务端,
  114.  *      要求服务端发送给客户C一个请求,请求C给本机发送打洞消息
  115.  *      以上流程将重复MAXRETRY次
  116.  */
  117. bool SendMessageTo(char *UserName, char *Message)
  118. {
  119.  char realmessage[256];
  120.  unsigned int UserIP;
  121.  unsigned short UserPort;
  122.  bool FindUser = false;
  123.  for(UserList::iterator UserIterator=ClientList.begin();
  124.       UserIterator!=ClientList.end();
  125.       ++UserIterator)
  126.  {
  127.   if( strcmp( ((*UserIterator)->userName), UserName) == 0 )
  128.   {
  129.    UserIP = (*UserIterator)->ip;
  130.    UserPort = (*UserIterator)->port;
  131.    FindUser = true;
  132.   }
  133.  }

  134.  if(!FindUser)
  135.   return false;

  136.  strcpy(realmessage, Message);
  137.  for(int i=0;i<MAXRETRY;i++)
  138.  {
  139.   RecvedACK = false;

  140.   sockaddr_in remote;
  141.   remote.sin_addr.S_un.S_addr = htonl(UserIP);
  142.   remote.sin_family = AF_INET;
  143.   remote.sin_port = htons(UserPort);
  144.   stP2PMessage MessageHead;
  145.   MessageHead.iMessageType = P2PMESSAGE;
  146.   MessageHead.iStringLen = (int)strlen(realmessage)+1;
  147.   int isend = sendto(PrimaryUDP, (const char *)&MessageHead, sizeof(MessageHead), 0, (const sockaddr*)&remote, sizeof(remote));
  148.   isend = sendto(PrimaryUDP, (const char *)&realmessage, MessageHead.iStringLen, 0, (const sockaddr*)&remote, sizeof(remote));

  149.   // 等待接收线程将此标记修改
  150.   for(int j=0;j<10;j++)
  151.   {
  152.    if(RecvedACK)
  153.     return true;
  154.    else
  155.     Sleep(300);
  156.   }

  157.   // 没有接收到目标主机的回应,认为目标主机的端口映射没有
  158.   // 打开,那么发送请求信息给服务器,要服务器告诉目标主机
  159.   // 打开映射端口(UDP打洞)
  160.   sockaddr_in server;
  161.   server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
  162.   server.sin_family = AF_INET;
  163.   server.sin_port = htons(SERVER_PORT);

  164.   stMessage transMessage;
  165.   transMessage.iMessageType = P2PTRANS;
  166.   strcpy(transMessage.message.translatemessage.userName, UserName);

  167.   sendto(PrimaryUDP, (const char*)&transMessage, sizeof(transMessage), 0, (const sockaddr*)&server, sizeof(server));
  168.   Sleep(100);// 等待对方先发送信息。
  169.  }
  170.  return false;
  171. }

  172. // 解析命令,暂时只有exit和send命令
  173. // 新增getu命令,获取当前服务器的所有用户
  174. void ParseCommand(char * CommandLine)
  175. {
  176.  if(strlen(CommandLine)<4)
  177.   return;
  178.  char Command[10];
  179.  strncpy(Command, CommandLine, 4);
  180.  Command[4]=’\0′;

  181.  if(strcmp(Command,”exit”)==0)
  182.  {
  183.   stMessage sendbuf;
  184.   sendbuf.iMessageType = LOGOUT;
  185.   strncpy(sendbuf.message.logoutmember.userName, UserName, 10);
  186.   sockaddr_in server;
  187.   server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
  188.   server.sin_family = AF_INET;
  189.   server.sin_port = htons(SERVER_PORT);

  190.   sendto(PrimaryUDP,(const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr *)&server, sizeof(server));
  191.   shutdown(PrimaryUDP, 2);
  192.   closesocket(PrimaryUDP);
  193.   exit(0);
  194.  }
  195.  else if(strcmp(Command,”send”)==0)
  196.  {
  197.   char sendname[20];
  198.   char message[COMMANDMAXC];
  199.   int i;
  200.   for(i=5;;i++)
  201.   {
  202.    if(CommandLine[i]!=’ ‘)
  203.     sendname[i-5]=CommandLine[i];
  204.    else
  205.    {
  206.     sendname[i-5]=’\0′;
  207.     break;
  208.    }
  209.   }
  210.   strcpy(message, &(CommandLine[i+1]));
  211.   if(SendMessageTo(sendname, message))
  212.    printf(“Send OK!\n”);
  213.   else
  214.    printf(“Send Failure!\n”);
  215.  }
  216.  else if(strcmp(Command,”getu”)==0)
  217.  {
  218.   int command = GETALLUSER;
  219.   sockaddr_in server;
  220.   server.sin_addr.S_un.S_addr = inet_addr(ServerIP);
  221.   server.sin_family = AF_INET;
  222.   server.sin_port = htons(SERVER_PORT);

  223.   sendto(PrimaryUDP,(const char*)&command, sizeof(command), 0, (const sockaddr *)&server, sizeof(server));
  224.  }
  225. }

  226. // 接受消息线程
  227. DWORD WINAPI RecvThreadProc(LPVOID lpParameter)
  228. {
  229.  sockaddr_in remote;
  230.  int sinlen = sizeof(remote);
  231.  stP2PMessage recvbuf;
  232.  for(;;)
  233.  {
  234.   int iread = recvfrom(PrimaryUDP, (char *)&recvbuf, sizeof(recvbuf), 0, (sockaddr *)&remote, &sinlen);
  235.   if(iread<=0)
  236.   {
  237.    printf(“recv error\n”);
  238.    continue;
  239.   }
  240.   switch(recvbuf.iMessageType)
  241.   {
  242.   case P2PMESSAGE:
  243.    {
  244.     // 接收到P2P的消息
  245.     char *comemessage= new char[recvbuf.iStringLen];
  246.     int iread1 = recvfrom(PrimaryUDP, comemessage, 256, 0, (sockaddr *)&remote, &sinlen);
  247.     comemessage[iread1-1] = ‘\0’;
  248.     if(iread1<=0)
  249.      throw Exception(“Recv Message Error\n”);
  250.     else
  251.     {
  252.      printf(“Recv a Message:%s\n”,comemessage);

  253.      stP2PMessage sendbuf;
  254.      sendbuf.iMessageType = P2PMESSAGEACK;
  255.      sendto(PrimaryUDP, (const char*)&sendbuf, sizeof(sendbuf), 0, (const sockaddr*)&remote, sizeof(remote));
  256.     }

  257.     delete []comemessage;
  258.     break;

  259.    }
  260.   case P2PSOMEONEWANTTOCALLYOU:
  261.    {
  262.     // 接收到打洞命令,向指定的IP地址打洞
  263.     printf(“Recv p2someonewanttocallyou data\n”);
  264.     sockaddr_in remote;
  265.     remote.sin_addr.S_un.S_addr = htonl(recvbuf.iStringLen);
  266.     remote.sin_family = AF_INET;
  267.     remote.sin_port = htons(recvbuf.Port);

  268.     // UDP hole punching
  269.     stP2PMessage message;
  270.     message.iMessageType = P2PTRASH;
  271.     sendto(PrimaryUDP, (const char *)&message, sizeof(message), 0, (const sockaddr*)&remote, sizeof(remote));

  272.     break;
  273.    }
  274.   case P2PMESSAGEACK:
  275.    {
  276.     // 发送消息的应答
  277.     RecvedACK = true;
  278.     break;
  279.    }
  280.   case P2PTRASH:
  281.    {
  282.     // 对方发送的打洞消息,忽略掉。
  283.     //do nothing …
  284.     printf(“Recv p2ptrash data\n”);
  285.     break;
  286.    }
  287.   case GETALLUSER:
  288.    {
  289.     int usercount;
  290.     int fromlen = sizeof(remote);
  291.     int iread = recvfrom(PrimaryUDP, (char *)&usercount, sizeof(int), 0, (sockaddr *)&remote, &fromlen);
  292.     if(iread<=0)
  293.     {
  294.      throw Exception(“Login error\n”);
  295.     }

  296.     ClientList.clear();

  297.     cout<<“Have “<<usercount<<” users logined server:”<<endl;
  298.     for(int i = 0;i<usercount;i++)
  299.     {
  300.      stUserListNode *node = new stUserListNode;
  301.      recvfrom(PrimaryUDP, (char*)node, sizeof(stUserListNode), 0, (sockaddr *)&remote, &fromlen);
  302.      ClientList.push_back(node);
  303.      cout<<“Username:”<<node->userName<<endl;
  304.      in_addr tmp;
  305.      tmp.S_un.S_addr = htonl(node->ip);
  306.      cout<<“UserIP:”<<inet_ntoa(tmp)<<endl;
  307.      cout<<“UserPort:”<<node->port<<endl;
  308.      cout<<“”<<endl;
  309.     }
  310.     break;
  311.    }
  312.   }
  313.  }
  314. }


  315. int main(int argc, char* argv[])
  316. {
  317.  try
  318.  {
  319.   InitWinSock();

  320.   PrimaryUDP = mksock(SOCK_DGRAM);
  321.   BindSock(PrimaryUDP);

  322.   cout<<“Please input server ip:”;
  323.   cin>>ServerIP;

  324.   cout<<“Please input your name:”;
  325.   cin>>UserName;

  326.   ConnectToServer(PrimaryUDP, UserName, ServerIP);

  327.   HANDLE threadhandle = CreateThread(NULL, 0, RecvThreadProc, NULL, NULL, NULL);
  328.   CloseHandle(threadhandle);
  329.   OutputUsage();

  330.   for(;;)
  331.   {
  332.    char Command[COMMANDMAXC];
  333.    gets(Command);
  334.    ParseCommand(Command);
  335.   }
  336.  }
  337.  catch(Exception &e)
  338.  {
  339.   printf(e.GetMessage());
  340.   return 1;
  341.  }
  342.  return 0;
  343. }

  344. /* 异常类
  345.  *
  346.  * 文件名:Exception.h
  347.  *
  348.  * 日期:2004.5.5
  349.  *
  350.  * 作者:shootingstars(zhouhuis22@sina.com)
  351.  */

  352. #ifndef __HZH_Exception__
  353. #define __HZH_Exception__

  354. #define EXCEPTION_MESSAGE_MAXLEN 256
  355. #include “string.h”

  356. class Exception
  357. {
  358. private:
  359.  char m_ExceptionMessage[EXCEPTION_MESSAGE_MAXLEN];
  360. public:
  361.  Exception(char *msg)
  362.  {
  363.   strncpy(m_ExceptionMessage, msg, EXCEPTION_MESSAGE_MAXLEN);
  364.  }

  365.  char *GetMessage()
  366.  {
  367.   return m_ExceptionMessage;
  368.  }
  369. };

  370. #endif

  371. /* P2P 程序传输协议
  372.  *
  373.  * 日期:2004-5-21
  374.  *
  375.  * 作者:shootingstars(zhouhuis22@sina.com)
  376.  *
  377.  */

  378. #pragma once
  379. #include <list>

  380. // 定义iMessageType的值
  381. #define LOGIN 1
  382. #define LOGOUT 2
  383. #define P2PTRANS 3
  384. #define GETALLUSER  4

  385. // 服务器端口
  386. #define SERVER_PORT 2280

  387. // Client登录时向服务器发送的消息
  388. struct stLoginMessage
  389. {
  390.  char userName[10];
  391.  char password[10];
  392. };

  393. // Client注销时发送的消息
  394. struct stLogoutMessage
  395. {
  396.  char userName[10];
  397. };

  398. // Client向服务器请求另外一个Client(userName)向自己方向发送UDP打洞消息
  399. struct stP2PTranslate
  400. {
  401.  char userName[10];
  402. };

  403. // Client向服务器发送的消息格式
  404. struct stMessage
  405. {
  406.  int iMessageType;
  407.  union _message
  408.  {
  409.   stLoginMessage loginmember;
  410.   stLogoutMessage logoutmember;
  411.   stP2PTranslate translatemessage;
  412.  }message;
  413. };

  414. // 客户节点信息
  415. struct stUserListNode
  416. {
  417.  char userName[10];
  418.  unsigned int ip;
  419.  unsigned short port;
  420. };

  421. // Server向Client发送的消息
  422. struct stServerToClient
  423. {
  424.  int iMessageType;
  425.  union _message
  426.  {
  427.   stUserListNode user;
  428.  }message;

  429. };

  430. //======================================
  431. // 下面的协议用于客户端之间的通信
  432. //======================================
  433. #define P2PMESSAGE 100               // 发送消息
  434. #define P2PMESSAGEACK 101            // 收到消息的应答
  435. #define P2PSOMEONEWANTTOCALLYOU 102  // 服务器向客户端发送的消息
  436.                                      // 希望此客户端发送一个UDP打洞包
  437. #define P2PTRASH        103          // 客户端发送的打洞包,接收端应该忽略此消息

  438. // 客户端之间发送消息格式
  439. struct stP2PMessage
  440. {
  441.  int iMessageType;
  442.  int iStringLen;         // or IP address
  443.  unsigned short Port;
  444. };

  445. using namespace std;
  446. typedef list<stUserListNode *> UserList;

工程下载地址:upload/2004_05/04052509317298.rar

附录3:

搭建 ngrok 服务实现内网穿透 - 忽而今夏

忽而今夏 关注 - 1 粉丝 - 4 +加关注

  目标:

    实现RASPBERRY PI 2 外网访问,包括web网站,及ssh远程登录...

  参考:

    https://imququ.com/post/self-hosted-ngrokd.html 

    http://www.ekan001.com/articles/38

    http://tonybai.com/2015/05/14/ngrok-source-intro/

    https://www.rpicn.org/documentation/remote-access/access-over-internet/ngrok/

  ngrok1.0是开源的,ngrok官网目前是2.0版本,https://ngrok.com/   二者功能和命令有一些区别,用的时候别搞混了,这里讲的是ngrok1.7版本

  操作如下:

    首先安装必要工具:golang mercurial git .

    获取ngrok源码:    

git clone https://github.com/inconshreveable/ngrok.git

cd ngrok


    生成并替换源码里默认的证书,注意域名修改为你自己的。(之后编译出来的服务端客户端会基于这个证书来加密通讯,保证了安全性)

NGROK_DOMAIN="rasp.pw"

openssl genrsa -out base.key 2048
openssl req -new -x509 -nodes -key base.key -days 10000 -subj "/CN=$NGROK_DOMAIN" -out base.pem
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=$NGROK_DOMAIN" -out server.csr
openssl x509 -req -in server.csr -CA base.pem -CAkey base.key -CAcreateserial -days 10000 -out server.crt

cp base.pem assets/client/tls/ngrokroot.crt

    开始编译

sudo make release-server release-client

    如果go版本是1.4,可能会遇到如下错误(日期:2016-05-30):

go install -tags 'release' ngrok/main/ngrok
# github.com/gorilla/websocket
src/github.com/gorilla/websocket/conn.go:647: c.br.Discard undefined (type *bufio.Reader has no field or method Discard)
make: *** [client] Error 2

    如果出现些错误,请更新go到1.5.3版本,(https://github.com/gorilla/websocket/issues/137)

    如果一切正常,ngrok/bin 目录下应该有 ngrok、ngrokd 两个可执行文件

    不同的客户端,需要指定环境变量再编译一次

    raspberry pi

sudo GOOS=linux GOARCH=arm make release-client

    ngrok/bin目录下会多出来一个linux_arm目录,这里的ngrok就可以在raspberry上运行了

    windows x64

sudo GOOS=windows GOARCH=amd64 make release-server release-client

    这样在 ngrok/bin 目录下会多出来一个 windows_amd64 目录,这里的ngrok.exe就可以在64位的windows上运行了。

    MAC同理

sudo GOOS=darwin GOARCH=amd64 make release-server release-client

  服务端:

    前面生成的 ngrokd 就是服务端程序了,指定证书、域名和端口启动它(证书就是前面生成的,注意修改域名)

sudo ./bin/ngrokd -tlsKey=server.key -tlsCrt=server.crt -domain="rasp.pw" -httpAddr=":8081" -httpsAddr=":8082"

    到这一步,ngrok 服务已经跑起来了,可以通过屏幕上显示的日志查看更多信息。httpAddr、httpsAddr 分别是 ngrok 用来转发 http、https 服务的端口,可以随意指定。ngrokd 还会开一个 4443 端口用来跟客户端通讯(可通过 -tunnelAddr=":xxx" 指定),如果你配置了 iptables 规则,需要放行这三个端口上的 TCP 协议

    现在,通过 https://rasp.pw:8081 和 https://rasp.pw:8082 就可以访问到 ngrok 提供的转发服务。为了使用方便,建议把域名泛解析到 VPS 上,这样能方便地使用不同子域转发不同的本地服务。我给 rasp.pw 做了泛解析,随便访问一个子域,如:http://pi.rasp.pw:8081,可以看到这样一行提示:

Tunnel pub.rasp.pw:8081 not found

    这说明万事俱备,只差客户端来连了。

  客户端:

    写一个简单的配置文件,随意命名如 ngrok.cfg:

server_addr: rasp.pw:4443
trust_host_root_certs: false

    注意因为客户端自带SSL证书,所以这里是false。

    指定子域、要转发的协议和端口,以及配置文件,运行客户端:

./ngrok -subdomain pi -proto=http -config=ngrok.cfg 80

    不出意外可以看到这样的界面,这说明已经成功连上远端服务了:

    TCP端口转发

    强大的功能,可以转发任意端口,例如22(SSH+sftp+scp),可以远程访问树莓派并传输文件。以及其他你需要功能。

ngrok -proto=tcp -config=ngrok.cfg 22 

    TCP端口转发不支持subdomain,需要手动设置协议参数为tcp。

    多端口转发,如果每转发一个端口就要运行一个ngrok程序太麻烦了,还好程序提供了配置文件可以同时转发多个端口,修改之前的ngrok.cfg文件

server_addr: rasp.pw:4443
trust_host_root_certs: false
tunnels:
    http:
        proto:
            http: 80
        subdomain: pi
    https:
        proto:
            https: 443
        subdomain: web
    ssh:
        remote_port: 5000
        proto:
            tcp: 22

    加粗斜体可以修改。其中de>remote_portde>是你想要请求的远端端口号,如果不指定,每次可能会变。另外英文冒号后面有空格,tunnels中不能使用TAB制表符,只能使用空格。格式可以参考ngrok官方配置文档 以及有关博客。

    启动方式为:

./ngrok -config=ngrok.cfg start http https ssh

     Raspberry 后台启动方式:

setsid ./ngrok -config=ngrok.cfg -log=log start http https ssh

  (update: 2016-07-08)

  关于路由R6300v2的内网穿透(梅林6.6.1) 

  参考:http://koolshare.cn/thread-6007-1-1.html

  路由刷的 梅林6.6.1 使用arm版本可以运行,但无法后台,并且时间不长,ngrok挂掉,不知道为什么 ,后来在koolshare网站中找到'小宝'修改的ngrok ,

  按上面链接教程配置好服务端, 客户端路径:/jffs/ngrok 运行如下命令

./tunnel -c default.json -p /tmp/tun.pid

  其中default.json和原版本ngrok.cfg配置相似,但也不同之处,

  虽然可以正常运行,但是运行时间也不长,不知道什么原因被Kill掉,此问题在koolshare论坛中也有反馈,目前解决方法是自己手动添加守护脚本,

  和tunnel同一目录添加一个tun.sh(此文件名不能和PRO_NAME重名,否则计算NUM值有误)文件,内容如下

#!/bin/sh

LOGTIME=$(date "+%Y-%m-%d %H:%M:%S")

PRO_NAME=tunnel
NUM=$(ps | grep $PRO_NAME | grep -v grep |wc -l)
if [ "${NUM}" -lt "1" ];then
    echo $LOGTIME $PRO_NAME is stop, restart.... >> /tmp/1.log
    /jffs/ngrok/tunnel -c /jffs/ngrok/default.json -p /tmp/tun.pid
else
    echo $LOGTIME $PRO_NAME is running! >> /tmp/1.log
fi

  把此脚本添加到crontab中每3分钟(根据自己需求调整)执行一次, 可以直接crontab -e进行添加

*/3 * * * * /bin/sh /jffs/ngrok/tun.sh

  可是路由重启后,crontab命令丢失,需要添加定时控制脚本到文件/jffs/scriipts/services-start,如果没有则自行新建。下一步设置权限755.

#!/bin/sh

cru a tunnel "*/3 * * * * /bin/sh /jffs/ngrok/tun.sh"

  路由重启后自动添加定时任务, 这样就可以保证在路由上内网穿透了.


  评论这张
 
阅读(352)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018