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地址等。
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穿透解决方案介绍
最近公司要实现在各种网络环境下面的多屏互动(机顶盒.android phone.iphone及PC端)的需求:由于IP地址资源有限的原因,目前我们使用的各种终端设备都位于局域网后面也就是多台设备共享同 ...
- 使用TCP协议的NAT穿透技术
一直以来,说起NAT穿透,很多人都会被告知使用UDP打孔这个技术,基本上没有人会告诉你如何使用TCP协议去穿透(甚至有的人会直接告诉你TCP协议是无法实现穿透的).但是,众所周知的是,UDP是一个无连 ...
- 关于NAT穿透的一些理解
前些天在知乎回答了一个智能家居远程控制方面的问题,感觉自己对无公网IP地址环境下的穿透问题有些了解.昨天同事拿来一个网络摄像头,安装在ADSL路由器上网的环境下,可以远程查看视频,效果还挺不错,问我厂 ...
- 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 ...
- NAT穿透(UDP打洞)
1.NAT(Network Address Translator)介绍 NAT有两大类,基本NAT和NAPT. 1.1.基本NAT 静态NAT:一个公网IP对应一个内部IP,一对一转换 动态NAT:N ...
- 使用TCP协议的NAT穿透技术 (转载)
其实很早我就已经实现了使用TCP协议穿透NAT了,但是苦于一直没有时间,所以没有写出来,现在终于放假有一点空闲,于是写出来共享之. 一直以来,说起NAT穿透,很多人都会被告知使用UDP打孔这个技术,基 ...
随机推荐
- Request继承体系
ServletRequest——接口 ↑继承 HttpServletRequest——接口 ↑实现 org.apache.catalina.connector.RequestFacade——类(Tom ...
- 第五十二篇 Linux相关——数据库服务MySQL
No.1. MySQL基本操作 CentOS7默认安装mariadb数据库,先将其移除 移除命令:sudo yum -y remove mariadb-libs.x86_64 下载MySQL源 ...
- Spark 中 GroupByKey 相对于 combineByKey, reduceByKey, foldByKey 的优缺点
避免使用GroupByKey 我们看一下两种计算word counts 的方法,一个使用reduceByKey,另一个使用 groupByKey: val words = Array("on ...
- centos 7 pip install MySQL-python 报错
pip install MySQL-python 报错 pip install MySQL-python DEPRECATION: Python . Please upgrade your Pytho ...
- c++ google glog模块安装和基本使用(ubuntu)环境
1,如何安装 1 Git clone https://github.com/google/glog.git 2 cd glog 3 ./autogen.sh 4 ./configure --prefi ...
- 编程道拓扑bcd.top 0x01/ 开局第一篇: 随便聊聊/ 随笔
0x01 开局 编程道拓扑(bcd.top)是一个前端从业者的思考和总结, 如果你喜欢, 欢迎关注! 作者是一个前端从业者, 本系列会总结作者在工作和学习中的一些思考, 会有具体的技术点, 也会有关于 ...
- 命名元组nametuple
# coding:utf-8 from collections import namedtuple Student = namedtuple('Student', ['no', 'name', 'ag ...
- Qt核心剖析:信息隐藏
原文 http://devbean.blog.51cto.com/448512/326686 (一) 如果你阅读了 Qt 的源代码,你会看到一堆奇奇怪怪的宏,例如 Q_D,Q_Q.我们的Qt源码之旅就 ...
- AcWing 860. 染色法判定二分图
#include <cstring> #include <iostream> #include <algorithm> using namespace std; , ...
- AcWing 847. 图中点的层次
队列 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm ...