🌉 NAT:私网和公网的"桥梁"

108 阅读7分钟

知识点编号:033
难度等级:⭐⭐⭐(掌握)
面试频率:🔥🔥🔥🔥🔥


🎯 一句话总结

NAT就像酒店前台,用一个公网地址代表所有私网设备!🏨


🤔 NAT是什么?

NAT:Network Address Translation
网络地址转换

作用:
✅ 节约公网IP地址
✅ 隐藏内网结构(安全)
✅ 允许私网访问公网

为什么需要NAT?
- IPv4地址不够用(只有43亿个)
- 私网IP不能在公网路由
- 需要让私网设备访问互联网

生活比喻:
酒店前台的工作:
- 客人(私网设备)打电话
- 前台(路由器)用酒店电话(公网IP)拨出
- 对方只看到酒店号码,不知道是哪个房间打的

📊 NAT的类型

1. 静态NAT(Static NAT)

一对一映射
一个私网IP固定对应一个公网IP

示例:
私网IP         公网IP
192.168.1.10 ←→ 203.0.113.10
192.168.1.20 ←→ 203.0.113.20

特点:
✅ 映射固定不变
✅ 可以被外网主动访问
✅ 适用于服务器
❌ 不节约IP地址

应用场景:
- 内网服务器需要对外提供服务
- 需要固定公网IP

2. 动态NAT(Dynamic NAT)

多对多映射
从公网IP池中动态分配

示例:
私网IP池:192.168.1.0/24(254个)
公网IP池:203.0.113.10-203.0.113.20(11个)

过程:
1. 192.168.1.10发起连接 → 分配203.0.113.10
2. 192.168.1.20发起连接 → 分配203.0.113.11
3. 192.168.1.10断开 → 203.0.113.10释放
4. 192.168.1.30发起连接 → 分配203.0.113.10

特点:
✅ 比静态NAT灵活
✅ 节约一些IP
❌ 还是需要多个公网IP
❌ 不能被外网主动访问

应用场景:
- 中小型企业
- 需要多个公网IP但不是所有设备同时上网

3. NAPT(端口复用NAT)/ PAT

NAPT:Network Address Port Translation
也叫PAT(Port Address Translation)

多对一映射 + 端口区分
所有私网IP共享一个公网IP

示例:
内网设备                     路由器(NAT)              外网服务器
192.168.1.10:5000  -----→  203.0.113.1:50001  ----→  8.8.8.8:53
192.168.1.20:5000  -----→  203.0.113.1:50002  ----→  8.8.8.8:53
192.168.1.30:6000  -----→  203.0.113.1:50003  ----→  1.1.1.1:80

NAT映射表:
内网地址           外网地址           目标地址
192.168.1.10:5000  203.0.113.1:50001  8.8.8.8:53
192.168.1.20:5000  203.0.113.1:50002  8.8.8.8:53
192.168.1.30:6000  203.0.113.1:50003  1.1.1.1:80

特点:
✅ 最节约IP(只需1个公网IP)
✅ 家用路由器都是这种
✅ 支持成千上万个内网设备
❌ 外网无法主动访问内网(需要端口映射)

应用场景:
- 家庭网络(最常见)
- 小型企业
- 移动网络

🎮 NAPT工作过程

详细流程:

步骤1:内网设备发起连接
内网电脑A(192.168.1.10:5000)访问百度(39.156.66.10:80)

步骤2:数据包到达NAT路由器
源地址:192.168.1.10:5000
目标地址:39.156.66.10:80

步骤3:NAT路由器修改源地址
原始包:
  源IP:192.168.1.10
  源端口:5000
  目标IP:39.156.66.10
  目标端口:80

NAT转换后:
  源IP:203.0.113.1(公网IP)
  源端口:50001(分配的外部端口)
  目标IP:39.156.66.10
  目标端口:80

步骤4:记录映射表
192.168.1.10:5000 ←→ 203.0.113.1:50001 ←→ 39.156.66.10:80

步骤5:发送到公网
百度服务器收到:
  源:203.0.113.1:50001
  目标:39.156.66.10:80

步骤6:百度回复
回复包:
  源:39.156.66.10:80
  目标:203.0.113.1:50001

步骤7:NAT路由器查表
查到:203.0.113.1:50001 ←→ 192.168.1.10:5000

步骤8:修改目标地址
转换后:
  源:39.156.66.10:80
  目标:192.168.1.10:5000

步骤9:转发给内网设备
电脑A收到回复!

🔧 端口映射(Port Forwarding)

问题:
外网无法主动访问内网设备
(因为外网不知道内网IP)

解决:
配置端口映射(静态映射)

示例:
内网有Web服务器:192.168.1.100:80
公网IP:203.0.113.1

配置:
外部端口8080 → 192.168.1.100:80

效果:
访问 203.0.113.1:8080 → 自动转到 192.168.1.100:80

NAT表:
外部地址              内部地址
203.0.113.1:8080  →  192.168.1.100:80
203.0.113.1:3389  →  192.168.1.101:3389(远程桌面)
203.0.113.1:22    →  192.168.1.102:22(SSH)

💻 Java代码示例

import java.net.*;
import java.io.*;

public class NATExample {
    
    /**
     * 获取私网IP
     */
    public static String getPrivateIP() {
        try {
            InetAddress ip = InetAddress.getLocalHost();
            return ip.getHostAddress();
        } catch (Exception e) {
            return "未知";
        }
    }
    
    /**
     * 获取公网IP(通过外部服务)
     */
    public static String getPublicIP() {
        try {
            // 方法1:通过外部服务获取
            URL url = new URL("https://api.ipify.org");
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(url.openStream())
            );
            String publicIP = reader.readLine();
            reader.close();
            return publicIP;
            
        } catch (Exception e) {
            return "获取失败: " + e.getMessage();
        }
    }
    
    /**
     * 判断是否是私网IP
     */
    public static boolean isPrivateIP(String ip) {
        String[] parts = ip.split("\\.");
        if (parts.length != 4) {
            return false;
        }
        
        int first = Integer.parseInt(parts[0]);
        int second = Integer.parseInt(parts[1]);
        
        // 10.0.0.0/8
        if (first == 10) {
            return true;
        }
        
        // 172.16.0.0/12
        if (first == 172 && second >= 16 && second <= 31) {
            return true;
        }
        
        // 192.168.0.0/16
        if (first == 192 && second == 168) {
            return true;
        }
        
        return false;
    }
    
    /**
     * 检测NAT类型(简化版)
     */
    public static String detectNATType() {
        String privateIP = getPrivateIP();
        String publicIP = getPublicIP();
        
        if (isPrivateIP(privateIP)) {
            if (!publicIP.equals("获取失败")) {
                return "NAPT/PAT(使用NAT)";
            } else {
                return "无公网访问";
            }
        } else {
            return "公网IP(无NAT)";
        }
    }
    
    /**
     * 显示网络信息
     */
    public static void showNetworkInfo() {
        System.out.println("=== 网络信息 ===\n");
        
        // 私网IP
        String privateIP = getPrivateIP();
        System.out.println("私网IP: " + privateIP);
        System.out.println("是否私网: " + isPrivateIP(privateIP));
        System.out.println();
        
        // 公网IP
        System.out.println("正在获取公网IP...");
        String publicIP = getPublicIP();
        System.out.println("公网IP: " + publicIP);
        System.out.println();
        
        // NAT类型
        System.out.println("NAT类型: " + detectNATType());
        System.out.println();
        
        // 所有网络接口
        try {
            System.out.println("=== 所有网络接口 ===\n");
            for (NetworkInterface ni : 
                 Collections.list(NetworkInterface.getNetworkInterfaces())) {
                
                if (!ni.isUp()) continue;
                
                System.out.println("接口: " + ni.getName());
                System.out.println("  显示名: " + ni.getDisplayName());
                
                for (InetAddress addr : 
                     Collections.list(ni.getInetAddresses())) {
                    if (addr instanceof Inet4Address) {
                        String ip = addr.getHostAddress();
                        System.out.println("  IPv4: " + ip + 
                                         " (私网: " + isPrivateIP(ip) + ")");
                    }
                }
                System.out.println();
            }
        } catch (Exception e) {
            System.err.println("获取接口失败: " + e.getMessage());
        }
    }
    
    /**
     * 测试端口映射
     */
    public static void testPortMapping(String host, int port) {
        System.out.println("=== 测试端口映射 ===\n");
        System.out.println("目标: " + host + ":" + port);
        
        try (Socket socket = new Socket()) {
            socket.connect(new InetSocketAddress(host, port), 3000);
            
            System.out.println("✓ 端口开放");
            System.out.println("  本地地址: " + 
                             socket.getLocalAddress().getHostAddress() + 
                             ":" + socket.getLocalPort());
            System.out.println("  远程地址: " + 
                             socket.getInetAddress().getHostAddress() + 
                             ":" + socket.getPort());
            
        } catch (SocketTimeoutException e) {
            System.out.println("✗ 连接超时(端口可能关闭或被防火墙阻止)");
        } catch (ConnectException e) {
            System.out.println("✗ 连接被拒绝(端口未开放)");
        } catch (Exception e) {
            System.out.println("✗ 错误: " + e.getMessage());
        }
    }
    
    public static void main(String[] args) {
        // 显示网络信息
        showNetworkInfo();
        
        // 测试公网连接
        System.out.println("\n=== 测试公网连接 ===\n");
        testPortMapping("www.baidu.com", 80);
        
        // 测试本地端口(如果有服务)
        // testPortMapping("localhost", 8080);
    }
}

⚠️ NAT的问题

1. 无法被外网主动访问

问题:
外网主机不知道内网IP
无法主动发起连接

影响:
- P2P应用困难(如视频通话)
- 托管服务器需要端口映射
- 一些在线游戏无法联机

解决:
- 端口映射(Port Forwarding)
- UPnP(自动端口映射)
- NAT穿透技术(STUN/TURN)
- DMZ主机(将一台内网主机完全暴露)

2. 端口耗尽

问题:
一个公网IP只有65535个端口
大量内网设备同时上网可能耗尽端口

示例:
1000台设备,每台100个连接
总共需要100,000个端口 > 65535

解决:
- 使用多个公网IP
- 连接复用
- 限制每个设备的连接数

3. 性能开销

问题:
NAT路由器需要:
- 维护映射表
- 修改每个数据包
- 重新计算校验和

影响:
- 增加延迟(通常<1ms)
- 消耗CPU和内存
- 高并发时性能下降

解决:
- 使用硬件NAT(专用芯片)
- 优化NAT表大小
- 及时清理过期连接

🐛 常见面试题

Q1:NAT的工作原理是什么?

答案:

NAT(网络地址转换)允许私网设备使用公网IP访问互联网。

工作原理(以NAPT为例):

出站(内网→公网):
1. 内网设备发送数据包
   源:192.168.1.10:5000
   目标:8.8.8.8:53

2. 到达NAT路由器
   - 分配外部端口:50001
   - 记录映射:192.168.1.10:5000 ←→ 公网IP:50001
   - 修改源地址为:公网IP:50001

3. 发送到公网
   源:公网IP:50001
   目标:8.8.8.8:53

入站(公网→内网):
1. 公网回复
   源:8.8.8.8:53
   目标:公网IP:50001

2. NAT路由器查表
   - 找到映射:50001 ←→ 192.168.1.10:5000
   - 修改目标地址为:192.168.1.10:5000

3. 转发给内网设备

关键点:
- 修改IP地址和端口
- 维护映射表
- 重新计算校验和
- 出站时记录,入站时查表

Q2:NAT有哪些类型?各有什么特点?

答案:

NAT的3种类型:

1. 静态NAT(Static NAT)
   - 一对一映射
   - 私网IP ←→ 公网IP固定
   - 不节约IP,但可被外网访问
   - 用途:对外提供服务的服务器

2. 动态NAT(Dynamic NAT)
   - 多对多映射
   - 从公网IP池动态分配
   - 节约部分IP
   - 用途:中小型企业

3. NAPT/PAT(端口复用NAT)
   - 多对一映射
   - 所有内网设备共享1个公网IP
   - 通过端口区分不同连接
   - 最节约IP
   - 用途:家庭、小企业(最常用)

比较:
             静态NAT      动态NAT      NAPT/PAT
映射关系     一对一       多对多       多对一
节约IP      不节约       部分节约     最节约
外网访问    可以         不可以       不可以
应用场景    服务器       企业         家庭/小企业

家用路由器都是NAPT!

🎓 总结

NAT的关键点:

  1. 作用:节约公网IP,私网访问公网
  2. 类型:静态、动态、NAPT
  3. 原理:修改地址+端口,维护映射表
  4. 问题:无法被动访问、端口耗尽

记忆口诀

私网共享公网IP 🌐
修改地址加端口 🔄
出站记录映射表 📝
入站查表转发 📤

文档创建时间:2025-10-31