NAT穿透解决方案介绍
最近公司要实现在各种网络环境下面的多屏互动(机顶盒、android phone、iphone及PC端)的需求;由于IP地址资源有限的原因,目前我们使用的各种终端设备都位于局域网后面也就是多台设备共享同一个公网IP;例如:如果位于局域网里面的一个终端Agent A要与互联网上的另一个终端Agent B通信,当A发送的data packet经过局域网出口处的NAT设备时,NAT会将data packet里面的source address字段替换成相应的公网IP和Port,然后再发送data packet到Agent B。Agent B看到的source address就是经过转换后的IP和Port并不知道Agent A的局域网地址;当Agent B的响应到达Agent A的NAT设备后,NAT设备查找内存中保存的和这个外网地址相对应的内网地址,如果找到后就将这个data packet转发到这个地址,这样就实现了通信。
然而由于目前存在着各种不同类型的NAT设备对NAT有着不同的实现方式(将内外地址映射成外网地址的时候有着不同的行为方式),这就给NAT的穿透带来了麻烦;目前主要的NAT类型有如下几种:
1)Full-cone NAT, also known as one-to-one NAT
- 一旦一个内网地址 (iAddr:iPort) 被映射到一个外部地址 (eAddr:ePort), 来自 iAddr:iPort 的任何数据包将通过 eAddr:ePort 发送.
- 任何外部主机能够通过eAddr:ePort这个地址发送数据包到iAddr:iPort.
2)Address-restricted-cone NAT
- 一旦一个内网地址 (iAddr:iPort) 被映射到一个外部地址 (eAddr:ePort), 来自 iAddr:iPort 的任何数据包将通过 eAddr:ePort 发送.
- 仅只有接收到主机(iAddr:iPort)通过eAddr:ePort发送的数据包的外部主机通过该主机的任何端口发送到eAddr:ePort的数据包才能够被正确的转发到iAddr:iPort.也就是说主机有关端口无关.
3)Port-restricted cone NAT
类似于address restricted cone NAT, 但是端口号有限制.
- 一旦一个内网地址 (iAddr:iPort) 被映射到一个外部地址 (eAddr:ePort), 来自 iAddr:iPort 的任何数据包将通过 eAddr:ePort 发送.
- 仅只有接收到主机(iAddr:iPort)通过eAddr:ePort发送的数据包的外部主机通过该主机的相同端口发送到eAddr:ePort的数据包才能够被正确的转发到iAddr:iPort.
4)Symmetric NAT
- 来自相同内部ip和port发送到相同目的地ip和port的请求被映射到唯一的外部ip和port地址;如果相同的内部主机采用相同的ip和port地址发送到不同的目的地,那么重新分配映射地址。
- 只有先前收到内部主机发送的包的外部主机才能够发送返回包到内部主机。
针对前面三种NAT类型(即cone NAT)只要通信双方彼此知道对方的内部地址和外部地址的映射关系,然后通过UDP打洞的方式就可以建立相互连接的通信;但是第四种也就是Symmetric NAT的话由于每次向不同目的地发送数据包时采用不同的外部地址,也就没办法通过直接的方式建立P2P连接。
1.各种网络环境下的P2P通信解决方法:
(3)如果通信双方一方拥有独立的公网地址另一方在NAT后面,那么可以由位于NAT后面的一方主动发起通信请求;
(4)如果通信双方都位于NAT后面,且双方的NAT类型都是cone NAT,那么可以通过一个STUN服务器发现自己的NAT类型以及内网和外网传输地址映射信息,然后通过Signaling(信令服务器,实现了SIP协议的主机)交换彼此的NAT类型及内网和外网传输地址映射信息,然后通过UDP打洞的方式建立通信连接;
2.协议及用到的相关技术介绍:
v=0
o=ice4j.org 0 0 IN IP4 192.168.106.215
s=-
t=0 0
a=ice-options:trickle
a=ice-ufrag:bc01a
a=ice-pwd:1boove7ehnpo1lqho7unefni36
m=audio 3030 RTP/AVP 0
c=IN 192.168.106.215 IP4
a=mid:audio
a=candidate:1 1 udp 2130706431 192.168.106.215 3030 typ host
a=candidate:2 1 udp 1694498815 121.15.130.xxx 64923 typ srflx raddr 192.168.106.215 rport 3030
STUN(Session Traversal Utilities for NAT)
NAT会话穿透工具;STUN提供了一种方式使一个端点能够确定NAT分配的和本地私有IP地址和端口相对应的公网IP地址和端口以及NAT的类型信息。它也为端点提供了一种方式保持一个NAT绑定不过期。NAT绑定过期则表示为相同的内网地址重新分配外网地址也就是端口号。
TURN(Traversal Using Relay NAT)
TURN是STUN协议的扩展,在实际应用中他也可以充当STUN的角色;如果一个位于NAT后面的设备想要和另外一个位于NAT后面的设备建立通信,当采用UDP打洞技术不能改实现的时候就必须要一台中间服务器扮演数据包转发的角色,这台TURN服务器需要拥有公网的IP地址;
ICE(Interactive Connectivity Establishment)
是实现NAT穿透的一种技术方案;ICE是一种NAT穿透技术,通过offer/answer模型建立基于UDP的媒介流。ICE是offer/answer模型的扩展,通过在offer和answer的SDP里面包含多种IP地址和端口,然后对本地SDP和远程SDP里面的IP地址进行配对,然后通过P2P连通性检查进行连通性测试工作,如果测试通过即表明该传输地址对可以建立连接。其中IP地址和端口(也就是地址)有以下几种:本机地址、通过STUN服务器反射后获取的server-reflexive地址(内网地址被NAT映射后的地址)、relayed地址(和TURN转发服务器相对应的地址)及Peer reflexive地址等。

/**
* Copyright (c) 2014 All Rights Reserved.
* TODO
*/ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.util.List; import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.ice4j.Transport;
import org.ice4j.TransportAddress;
import org.ice4j.ice.Agent;
import org.ice4j.ice.Component;
import org.ice4j.ice.IceMediaStream;
import org.ice4j.ice.IceProcessingState;
import org.ice4j.ice.LocalCandidate;
import org.ice4j.ice.NominationStrategy;
import org.ice4j.ice.RemoteCandidate;
import org.ice4j.ice.harvest.StunCandidateHarvester;
import org.ice4j.ice.harvest.TurnCandidateHarvester;
import org.ice4j.security.LongTermCredential; import test.SdpUtils; public class IceClient { private int port; private String streamName; private Agent agent; private String localSdp; private String remoteSdp; private String[] turnServers = new String[] { "stun.jitsi.net:3478" }; private String[] stunServers = new String[] { "stun.stunprotocol.org:3478" }; private String username = "guest"; private String password = "anonymouspower!!"; private IceProcessingListener listener; static Logger log = Logger.getLogger(IceClient.class); public IceClient(int port, String streamName) {
this.port = port;
this.streamName = streamName;
this.listener = new IceProcessingListener();
} public void init() throws Throwable { agent = createAgent(port, streamName); agent.setNominationStrategy(NominationStrategy.NOMINATE_HIGHEST_PRIO); agent.addStateChangeListener(listener); agent.setControlling(false); agent.setTa(10000); localSdp = SdpUtils.createSDPDescription(agent); log.info("=================== feed the following"
+ " to the remote agent ==================="); System.out.println(localSdp); log.info("======================================"
+ "========================================\n");
} public DatagramSocket getDatagramSocket() throws Throwable { LocalCandidate localCandidate = agent
.getSelectedLocalCandidate(streamName); IceMediaStream stream = agent.getStream(streamName);
List<Component> components = stream.getComponents();
for (Component c : components) {
log.info(c);
}
log.info(localCandidate.toString());
LocalCandidate candidate = (LocalCandidate) localCandidate;
return candidate.getDatagramSocket(); } public SocketAddress getRemotePeerSocketAddress() {
RemoteCandidate remoteCandidate = agent
.getSelectedRemoteCandidate(streamName);
log.info("Remote candinate transport address:"
+ remoteCandidate.getTransportAddress());
log.info("Remote candinate host address:"
+ remoteCandidate.getHostAddress());
log.info("Remote candinate mapped address:"
+ remoteCandidate.getMappedAddress());
log.info("Remote candinate relayed address:"
+ remoteCandidate.getRelayedAddress());
log.info("Remote candinate reflexive address:"
+ remoteCandidate.getReflexiveAddress());
return remoteCandidate.getTransportAddress();
} /**
* Reads an SDP description from the standard input.In production
* environment that we can exchange SDP with peer through signaling
* server(SIP server)
*/
public void exchangeSdpWithPeer() throws Throwable {
log.info("Paste remote SDP here. Enter an empty line to proceed:");
BufferedReader reader = new BufferedReader(new InputStreamReader(
System.in)); StringBuilder buff = new StringBuilder();
String line = new String(); while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.length() == 0) {
break;
}
buff.append(line);
buff.append("\r\n");
} remoteSdp = buff.toString(); SdpUtils.parseSDP(agent, remoteSdp);
} public void startConnect() throws InterruptedException { if (StringUtils.isBlank(remoteSdp)) {
throw new NullPointerException(
"Please exchange sdp information with peer before start connect! ");
} agent.startConnectivityEstablishment(); // agent.runInStunKeepAliveThread(); synchronized (listener) {
listener.wait();
} } private Agent createAgent(int rtpPort, String streamName) throws Throwable {
return createAgent(rtpPort, streamName, false);
} private Agent createAgent(int rtpPort, String streamName,
boolean isTrickling) throws Throwable { long startTime = System.currentTimeMillis(); Agent agent = new Agent(); agent.setTrickling(isTrickling); // STUN
for (String server : stunServers){
String[] pair = server.split(":");
agent.addCandidateHarvester(new StunCandidateHarvester(
new TransportAddress(pair[0], Integer.parseInt(pair[1]),
Transport.UDP)));
} // TURN
LongTermCredential longTermCredential = new LongTermCredential(username,
password); for (String server : turnServers){
String[] pair = server.split(":");
agent.addCandidateHarvester(new TurnCandidateHarvester(
new TransportAddress(pair[0], Integer.parseInt(pair[1]), Transport.UDP),
longTermCredential));
}
// STREAMS
createStream(rtpPort, streamName, agent); long endTime = System.currentTimeMillis();
long total = endTime - startTime; log.info("Total harvesting time: " + total + "ms."); return agent;
} private IceMediaStream createStream(int rtpPort, String streamName,
Agent agent) throws Throwable {
long startTime = System.currentTimeMillis();
IceMediaStream stream = agent.createMediaStream(streamName);
// rtp
Component component = agent.createComponent(stream, Transport.UDP,
rtpPort, rtpPort, rtpPort + 100); long endTime = System.currentTimeMillis();
log.info("Component Name:" + component.getName());
log.info("RTP Component created in " + (endTime - startTime) + " ms"); return stream;
} /**
* Receive notify event when ice processing state has changed.
*/
public static final class IceProcessingListener implements
PropertyChangeListener { private long startTime = System.currentTimeMillis(); public void propertyChange(PropertyChangeEvent event) { Object state = event.getNewValue(); log.info("Agent entered the " + state + " state.");
if (state == IceProcessingState.COMPLETED) {
long processingEndTime = System.currentTimeMillis();
log.info("Total ICE processing time: "
+ (processingEndTime - startTime) + "ms");
Agent agent = (Agent) event.getSource();
List<IceMediaStream> streams = agent.getStreams(); for (IceMediaStream stream : streams) {
log.info("Stream name: " + stream.getName());
List<Component> components = stream.getComponents();
for (Component c : components) {
log.info("------------------------------------------");
log.info("Component of stream:" + c.getName()
+ ",selected of pair:" + c.getSelectedPair());
log.info("------------------------------------------");
}
} log.info("Printing the completed check lists:");
for (IceMediaStream stream : streams) { log.info("Check list for stream: " + stream.getName()); log.info("nominated check list:" + stream.getCheckList());
}
synchronized (this) {
this.notifyAll();
}
} else if (state == IceProcessingState.TERMINATED) {
log.info("ice processing TERMINATED");
} else if (state == IceProcessingState.FAILED) {
log.info("ice processing FAILED");
((Agent) event.getSource()).free();
}
}
}
} import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.util.concurrent.TimeUnit; public class PeerA { public static void main(String[] args) throws Throwable {
try {
IceClient client = new IceClient(2020, "audio");
client.init();
client.exchangeSdpWithPeer();
client.startConnect();
final DatagramSocket socket = client.getDatagramSocket();
final SocketAddress remoteAddress = client
.getRemotePeerSocketAddress();
System.out.println(socket.toString());
new Thread(new Runnable() { public void run() {
while (true) {
try {
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf,
buf.length);
socket.receive(packet);
System.out.println("receive:"
+ new String(packet.getData(), 0, packet
.getLength()));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
}
}).start(); new Thread(new Runnable() { public void run() {
int count = 1;
while (true) {
try {
byte[] buf = ("send msg " + count++ + "").getBytes();
DatagramPacket packet = new DatagramPacket(buf,
buf.length); packet.setSocketAddress(remoteAddress);
socket.send(packet);
System.out.println("send msg");
TimeUnit.SECONDS.sleep(10);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
}
}).start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} } }
NAT穿透解决方案介绍的更多相关文章
- NAT穿透解决方案介绍(转)--Java ICE实现
转:http://www.cnblogs.com/javaminer/p/3575282.html 最近公司要实现在各种网络环境下面的多屏互动(机顶盒.android phone.iphone及PC端 ...
- p2p网络中的NAT穿透技术----常见NAT穿越解决方案
转:http://blog.csdn.net/cllzw/article/details/46438257 常见NA丁穿越解决方案 NAT技术在缓解IPv4地址紧缺问题.构建防火墙.保证网络安全等方面 ...
- PJNATH介绍 -- 开源的用于NAT穿透的ICE, STUN和TURN
原文地址:http://blog.pjsip.org/2007/04/06/introducing-pjnath-open-source-ice-stun-and-turn/ ICE是什么? 对于那些 ...
- 使用TCP协议的NAT穿透技术
一直以来,说起NAT穿透,很多人都会被告知使用UDP打孔这个技术,基本上没有人会告诉你如何使用TCP协议去穿透(甚至有的人会直接告诉你TCP协议是无法实现穿透的).但是,众所周知的是,UDP是一个无连 ...
- 解决外网与内网或内网之间的通信,NAT穿透
在网络编码中会发现程序在局域网中是可以适用的,但是在外网与内网之间和内网与内网之间就不可行.问题就在于NAT.首先介绍下NAT. NAT的作用NAT(Network Address Translato ...
- NAT穿透解决
1.各种网络环境下的P2P通信解决方法: (1)如果通信双方在同一个局域网内,这种情况下可以不借助任何外力直接通过内网地址通信即可: (2)如果通信双方都在有独立的公网地址,这种情况下当然可以不借 ...
- NAT穿透的详解及分析
一.什么是NAT?为什么要使用NAT?NAT是将私有地址转换为合法IP地址的技术,通俗的讲就是将内网与内网通信时怎么将内网私有IP地址转换为可在网络中传播的合法IP地址.NAT的出现完美地解决了lP地 ...
- 网络协议之NAT穿透
NAT IPv4地址只有32位,最多只能提供大致42.9亿个唯一IP地址,当设备越来越多时,IP地址变得越来越稀缺,不能为每个设备都分配一个IP地址.于是,作为NAT规范就出现了.NAT(Networ ...
- NAT穿透的详细讲解及分析.RP
原创出处:https://bbs.pediy.com/thread-131961.htm 转载来源: https://blog.csdn.net/g_brightboy/article/details ...
随机推荐
- 学习Spring(一) 实例化Spring IoC容器
实例化Spring IoC容器 1,读取其配置来创建bean实例 2,然后从Spring IoC容器中得到可用的bean实例 Spring提供两种IoC容器实现类型 a,一种为bean工厂 b,应用程 ...
- javac 编译与 JIT 编译
编译过程 不论是物理机还是虚拟机,大部分的程序代码从开始编译到最终转化成物理机的目标代码或虚拟机能执行的指令集之前,都会按照如下图所示的各个步骤进行: 其中绿色的模块可以选择性实现.很容易看出,上图中 ...
- iOS 使用AFN 进行单图和多图上传
图片上传时必要将图片进行压缩,不然会上传失败 1.单张图上传 AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManag ...
- python3 异常处理
什么是异常 Python用异常对象(exception object)来表示异常情况.遇到错误会引发异常,如果异常对象未被处理或者捕捉,程序就会用回溯(traceback)终止执行. Raise语句: ...
- Python 中文编码问题小结
1. 下面的语句要放在代码开头,指定文件编码, 可以识别 脚本中的所有字符和中文. # -*- coding:utf-8 -*- 2. codecd 编码转换 如果想要读取文本中的中文,需要借助于co ...
- css-transition和transform实现图片悬浮移动动画
今天在京东首页上看到一个效果,鼠标悬浮在图片上时,图片发生移动,鼠标移走时再移回,并且有一个过渡效果. 貌似很简单,自己做做试试吧 我首先使用的是jquery在鼠标悬浮到图片上给图片增加一个类,这个类 ...
- Leetcode 376. Wiggle Subsequence
本题要求在O(n)时间内求解.用delta储存相邻两个数的差,如果相邻的两个delta不同负号,那么说明子序列摇摆了一次.参看下图的nums的plot.这个例子的答案是7.平的线段部分我们支取最左边的 ...
- 【BZOJ-3052】糖果公园 树上带修莫队算法
3052: [wc2013]糖果公园 Time Limit: 200 Sec Memory Limit: 512 MBSubmit: 883 Solved: 419[Submit][Status] ...
- SQLMAP源码分析-目录结构
-----------------------------------------------------------------------------│ README.md│ sqlmap.c ...
- hdu 4403 dfs
巨麻烦的模拟题orz.... 先确定等号的位置,然后两层dfs,分别算等号前面的和后面的,再比较 话说这题竟然不开long long都能水过 #include <iostream> #in ...