NAT穿透解决方案介绍(转)--Java ICE实现
转:http://www.cnblogs.com/javaminer/p/3575282.html
最近公司要实现在各种网络环境下面的多屏互动(机顶盒、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穿透解决方案介绍(转)--Java ICE实现的更多相关文章
- NAT穿透解决方案介绍
最近公司要实现在各种网络环境下面的多屏互动(机顶盒.android phone.iphone及PC端)的需求:由于IP地址资源有限的原因,目前我们使用的各种终端设备都位于局域网后面也就是多台设备共享同 ...
- PJNATH介绍 -- 开源的用于NAT穿透的ICE, STUN和TURN
原文地址:http://blog.pjsip.org/2007/04/06/introducing-pjnath-open-source-ice-stun-and-turn/ ICE是什么? 对于那些 ...
- p2p网络中的NAT穿透技术----常见NAT穿越解决方案
转:http://blog.csdn.net/cllzw/article/details/46438257 常见NA丁穿越解决方案 NAT技术在缓解IPv4地址紧缺问题.构建防火墙.保证网络安全等方面 ...
- NAT穿透解决
1.各种网络环境下的P2P通信解决方法: (1)如果通信双方在同一个局域网内,这种情况下可以不借助任何外力直接通过内网地址通信即可: (2)如果通信双方都在有独立的公网地址,这种情况下当然可以不借 ...
- NAT穿透的方式
目前主要的NAT类型有如下几种: 1)Full-cone NAT, also known as one-to-one NAT 一旦一个内网地址 (iAddr:iPort) 被映射到一个外部地址 (eA ...
- 网络协议之NAT穿透
NAT IPv4地址只有32位,最多只能提供大致42.9亿个唯一IP地址,当设备越来越多时,IP地址变得越来越稀缺,不能为每个设备都分配一个IP地址.于是,作为NAT规范就出现了.NAT(Networ ...
- #研发解决方案介绍#IdCenter(内部统一认证系统)
郑昀 基于朱传志的设计文档 最后更新于2014/11/13 关键词:LDAP.认证.权限分配.IdCenter. 本文档适用人员:研发 曾经一个IT内部系统配一套帐号体系和授权 线上生产环境里 ...
- #研发解决方案介绍#Recsys-Evaluate(推荐评测)
郑昀 基于刘金鑫文档 最后更新于2014/12/1 关键词:recsys.推荐评测.Evaluation of Recommender System.piwik.flume.kafka.storm.r ...
- 使用TCP协议的NAT穿透技术
一直以来,说起NAT穿透,很多人都会被告知使用UDP打孔这个技术,基本上没有人会告诉你如何使用TCP协议去穿透(甚至有的人会直接告诉你TCP协议是无法实现穿透的).但是,众所周知的是,UDP是一个无连 ...
随机推荐
- How To Use PostgreSQL with Your Ruby on Rails Application on Ubuntu 14.04
How To Use PostgreSQL with Your Ruby on Rails Application on Ubuntu 14.04 链接来自于:https://www.digitalo ...
- PHP ftp_login() 函数
定义和用法 ftp_login() 函数登录 FTP 服务器. 如果成功,该函数返回 TRUE.如果失败,则返回 FALSE 和一个警告. 语法 ftp_login(ftp_connection,us ...
- 数学二分——cf700A
二分答案 #include<bits/stdc++.h> using namespace std; #define ll long long int n,l,v1,v2,k; double ...
- kafka为什么快?
为什么Kafka那么快 原创 2016-07-18 fireflyc 写程序的康德 网上有很多Kafka的测试文章,测试结果通常都是"吊打"其他MQ.感慨它的牛B之余我觉得必要仔细 ...
- BZOJ 5120: [2017国家集训队测试]无限之环(费用流)
传送门 解题思路 神仙题.调了一个晚上+半个上午..这道咋看咋都不像图论的题竟然用费用流做,将行+列为奇数的点和偶数的点分开,也就是匹配问题,然后把一个点复制四份,分别代表这个点的上下左右接头,如果有 ...
- (14)centos7 进程管理
一.查询进程 1. 进程显示 ps -a 显示当前所有的进程信息 -u 以用户的格式显示进程信息 -x 显示后台进程运行的参数 ps -aux #通常查看内存 USER #执行进程的用户 PID #进 ...
- struts基础2
Result配置详解 说明:在前面的许多案例中我们所用到的Action基本都继承自ActionSupport这个类,而在这个类中我们定义了五个字段:SUCCESS,NONE,ERROR,INPUT,L ...
- iptables简介及常用命令
相关文件 iptables服务配置文件 -rw-------. 1 root root 2374 9月 4 2017 /etc/sysconfig/iptables-config iptables规则 ...
- Spark中的多线程并发处理
Spark中的多任务处理 Spark的一个非常常见的用例是并行运行许多作业. 构建作业DAG后,Spark将这些任务分配到多个Executor上并行处理.但这并不能帮助我们在同一个Spark应用程序中 ...
- 深入理解JAVA虚拟机原理之垃圾回收器机制(一)
更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680 对于程序计数器.虚拟机栈.本地方法栈这三个部分而言,其生命周期与相关线程有关,随 ...