分类 默认分类 下的文章

前言

最近在研究Wireguard异地组网,打算将家里和机房的局域网组合在一起。同时朋友有使用Wireguard IPv6上网的需求,决定再在组网的基础上添加一个IPv6隧道。

准备工作

  • 两个网段不同的局域网(例如10.10.10.0/24192.168.1.0/24
  • 两台OpenWrt(至少一个接入公网IPv6)
  • 任意客户端

为机房PVE添加NAT网桥

PVE默认的网桥是采用桥接方式,这次我要为它添加一个IPv4 / IPv6双栈NAT网桥

添加网桥

添加一个网桥vmbr1,如图所示,将10.10.10.1预留给OpenWrt,IPv6 ULA地址给OpenWrt自动分配

截屏2023-06-03 08.56.13

为了给vmbr1网桥下的设备分配IP地址,我们需要安装DHCP服务,这里由于组网需要我选择安装OpenWrt

安装OpenWrt

创建虚拟机openwrt,配置如图所示

注意:先添加vmbr1再添加vmbr0,这样OpenWrt才会将vmbr0(互联网)视为WAN口

截屏2023-06-03 08.59.48

下载OpenWrt镜像并导入硬盘,这里可以参考我一篇没写完的文章(bushi

{% link https://rickg.cn/2022/05/25/pve-openwrt/ %}

推荐的镜像:

{% link https://openwrt.mpdn.fun:8443/ %}

qm importdisk [vmid] /var/lib/vz/template/iso/bleach-plus-20230601-openwrt-x86-64-generic-squashfs-combined-efi.img local --format=qcow2

导入完成后,启动OpenWrt,编辑/etc/config/network,修改LAN口IP

截屏2023-06-03 09.10.01

重启OpenWrt后,在vmbr1网桥下任意虚拟机访问10.10.10.1,即可登录OpenWrt

配置NAT6

安装完OpenWrt后,尽管此时我们拥有了IPv6 ULA地址,但未配置NAT6,因此并不能访问IPv6互联网

编辑/etc/sysctl.d/forward.conf ,添加以下内容:

net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.eth1.autoconf=1
net.ipv6.conf.eth1.accept_ra=2

其中eth1为WAN口接口名

重启OpenWrt后,访问10.10.10.1,打开网络->防火墙->自定义规则,添加以下内容:

# Enable IPv6 NAT
ip6tables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

点击重启防火墙后,vmbr1下的IPv6设备应该可以访问IPv6互联网了

截屏2023-06-03 09.31.43

配置Wireguard VPN

配置家中OpenWrt

ssh连接到路由器,创建密钥目录并设定掩码:

mkdir keys
cd keys 
umask 077

创建并查看密钥:

wg genkey > openwrt-home.key
cat openwrt-home.key
wg pubkey < openwrt-home.key > openwrt-home.pub
cat openwrt-home.pub

打开OpenWrt,点击网络->接口->添加新接口,名称为wg1,协议为WireGuard VPN

截屏2023-06-03 09.36.03

创建后自动打开接口编辑界面

截屏2023-06-03 13.15.01

将私钥填入配置界面,监听端口任意,建议选高位端口,防止被运营商干扰

IP地址一栏,我这里选用172.17.0.1/32的B类地址,个人建议用10.0.0.0/8的A类地址,自定义程度更高。对于IPv6地址,使用fd00::/8开头的本地地址即可,如图中的fd45:da8::1/64,配置完后保存并应用。

配置机房OpenWrt

生成公私钥等同上,但是IP填172.17.0.2/32fd45:da8::2/64,如图所示:

截屏2023-06-03 13.26.01

配置对端(Peer)

登入任意路由器,生成一个预共享密钥,增强安全性:

wg genpsk

该密钥在所有端通用,须保存好。

在Peers处点击添加,公钥填写对方的(如家里OpenWrt填写机房公钥),预共享密钥输入刚刚生成的,端点主机输入对端IP(这里我两端均为家宽,因此做了DDNS),端口输入配置的端口,持续Keep-Alive不填。

对于允许的IP,需要输入两项,第一项是WireGuard的IP段,即172.17.0.0/24,第二项即对方机器所处内网IP段,如对端为家则为192.168.1.0/24,如图所示:

截屏2023-06-03 13.36.24

配置完后保存并应用,点击WireGuard接口旁的“连接”,重启接口。

若要查看是否成功连接,点击LuCI界面中的状态->WireGuard状态即可看到对端连接信息:

截屏2023-06-03 13.39.43

此时ping一下对端内网段的IP,哈哈,是不是很激动?

截屏2023-06-03 13.40.38

配置客户端

创建密钥

对于macOS / Windows / Android / iOS等具有GUI等客户端,直接生成一对公私钥即可:截屏2023-06-03 17.09.05

image-20230603170949355

对于Linux,参照上文

编写配置文件

添加空隧道,填写以下配置文件:

[Interface]
PrivateKey = [你的私钥]
Address = 172.17.0.4/32, fd45:da8::4/128
DNS = 119.29.29.29, 2402:4e00::
MTU = 1420

[Peer]
PublicKey = [服务端公钥]
PresharedKey = [预共享密钥]
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = [服务端IP]:端口
PersistentKeepalive = 25

对于允许的IPAllowedIPs:填写0.0.0.0/0, ::/0即代理IPv4和IPv6所有流量,其他的一些示例:

# 只需访问内网段10.10.10.0/24,其他流量不走WireGuard
AllowedIPs = 10.10.10.0/24
# 全局代理,只允许IPv4流量
AllowedIPs = 0.0.0.0/0
# 全局代理,只允许IPv6流量
AllowedIPs = ::/0

保存配置文件后,在WireGuard客户端中导入即可,当然,在正式使用之前,我们还需在服务端中添加客户端作为对端。

配置服务端

打开OpenWrt的wg0接口,添加一个Peers,但是这次无需输入端点主机和端口,因为客户端可能位于多层NAT之后:

截屏2023-06-03 17.32.29

保存后,重启wg0接口,现在即可打开客户端享受安全、快速的WireGuard VPN了!

前言

最近在计划加一台服务器,与原本的R720组成40G内网,然而路由器的网口已经不够了,心想能否直接通过40G网线来将R720的互联网共享给新机器,于是便有了这篇文章。

准备工作

  • 计算机两台(一台要有双网卡
  • 网线一根
  • 互联网接入点一个

由于上面的我统统没有(bushi,所以这次技术验证放在了PVE中进行。

  1. 创建网桥vmbr1,无需绑定物理接口,模拟两机通过网线相连
  2. 创建两台虚拟机,名字分别为test-networktest-nonetwork ,配置如下(串行接口可以忽略

image-20230531150654332

image-20230531150731478

  1. 在两台机器上安装Debian 11系统

配置计算机A

Well,现在我们来配置计算机A,也就是共享网络的机器

配置IP地址

编辑/etc/network/interfaces ,添加如下内容

auto ens18
iface ens18 inet dhcp

allow-hotplug ens19
iface ens19 inet static
        address 172.16.0.1/24
        gateway 172.16.0.1
iface ens19 inet6 static
        address 2001:db8::1234/64
        gateway 2001:db8::1234

ens18 即连接互联网的端口,ens19 即与计算机B相连的端口

从上面的内容可以得知,我们要将ens19端口的IPv4地址配置为172.16.0.1,子网掩码为255.255.255.0,默认网关为本机IPIPv6地址为2001:db8::1234,子网掩码为64,默认网关同样为本机IP

配置iptables

接下来是重头戏,没有iptables,就算两机的IP正确,计算机B也无法访问互联网。

安装iptables:

sudo apt install iptables

输入以下指令:

# IPv4 NAT
sudo iptables -t nat -A POSTROUTING -o ens18 -j MASQUERADE
sudo iptables -A FORWARD -i ens18 -o ens19 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i ens19 -o ens18 -j ACCEPT
# IPv6 NAT
sudo ip6tables -t nat -A POSTROUTING -o ens18 -j MASQUERADE
sudo ip6tables -A FORWARD -i ens18 -o ens19 -m state --state RELATED,ESTABLISHED -j ACCEPT
sudo ip6tables -A FORWARD -i ens19 -o ens18 -j ACCEPT

这配置了网络地址转换(NAT)

保存iptables

安装iptables-persistent:

sudo apt-get install iptables-persistent

安装过程中会提示是否保存iptables,选择Yes即可

日后若要保存刚输入的iptables,键入sudo netfilter-persistent save即可

启用IP转发

现在让我们进行最后一步:打开IPv4和IPv6的转发

编辑/etc/sysctl.conf , 找到以下两行:

#net.ipv4.ip_forward=1
#net.ipv6.conf.all.forwarding=1

分别将其取消注释,同时在文件尾部添加以下两行:

net.ipv6.conf.ens18.autoconf=1
net.ipv6.conf.ens18.accept_ra=2

其中ens18为互联网端口号,由于IPv6转发的启用会全局禁用SLAAC获取IPv6地址,这两行将重新启用在该端口上的SLAAC功能。

保存文件,重新启动计算机,计算机A的配置部分就完成了。

配置计算机B

计算机B的配置非常简单,只需要配置IP和默认网关即可

编辑/etc/network/interfaces ,添加以下内容:

auto ens18
iface ens18 inet static
        address 172.16.0.2/24
        gateway 172.16.0.2
iface ens18 inet6 static
        address 2001:db8::5678/64
        gateway 2001:db8::1234

保存后重启计算机,就可以查看实际效果了。

测试

ifconfig

截屏2023-05-31 15.33.21

IPv4 Ping

截屏2023-05-31 15.32.05

IPv6 Ping

截屏2023-05-31 15.31.25

Speedtest

截屏2023-05-31 15.37.40

缺点

很显然,这种基于NAT的上网方式的缺点在于没有独立的IP地址。由于上游计算机无法获得IPv6前缀,本机也无法获得公网IPv6地址,对于一些需要公网IP的服务(例如BT)不是很友好。同时,NAT会降低网络性能。

介绍

Zero Trust是Cloudflare公司推出的一项企业级虚拟网关服务,官方对其介绍如下:

Cloudflare Zero Trust

Cloudflare Zero Trust是一个安全框架,旨在通过验证和保护所有的网络访问(无论用户或地点)来保护组织免受网络威胁。该框架基于 "不信任任何人 "的原则,这意味着所有访问请求在被批准之前必须经过验证和授权。

Zero Trust "旨在提供一种全面的安全方法,涵盖一个组织的网络和基础设施的所有方面。这包括用户认证、设备安全、网络分段和应用安全。

Zero Trust的主要好处之一是它能够提供细化的访问控制,使企业能够根据用户角色和权限限制对敏感资源的访问。这可以通过最小化攻击面和限制任何潜在漏洞的影响来帮助防止数据泄露和其他安全事件。

Cloudflare Zero Trust是一项基于云的服务,可以很容易地与组织的现有安全基础设施集成。它的设计具有可扩展性和灵活性,允许企业定制框架,以满足其特定需求和要求。

(通过DeepL翻译)

本文我们将使用其中的Tunnel功能,从外网访问家中内网的服务。对于其WARP代理功能,本文不做讨论。

你需要

{% checkbox color:green checked:true 一个功能正常的Cloudflare账户 %}

{% checkbox color:green checked:true 一个有效的付款方式(Visa / Mastercard /Paypal) %}

{% checkbox color:green checked:true 一个绑定在Cloudflare下的域名 %}

启用Zero Trust

image-20230225191137594

配置内网穿透

创建隧道

点击Access -> Tunnels,点击Create a tunnel创建一个隧道

image-20230225191410406

输入名称后继续,提示安装connector

image-20230225193025132

这里以Debian 64-bit和Windows为例

安装connector(Debian)

键入以下命令:

curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb && sudo dpkg -i cloudflared.deb

若访问github.com过慢可在https://前面添加https://ghproxy.com/

而后:

sudo cloudflared service install [你的Token]

若运行此命令时出错

image-20230225192231356

则执行:

sudo cloudflared service uninstall

而后重新执行cloudflared service install一行即可

安装connector(Windows)

下载https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.msi并安装

以管理员权限运行的cmd / Powershell中键入:

cloudflare service install [你的Token]

进行内网穿透

按照如图所示进行配置即可

image-20230225193655754

配置完成后,通过https://[你的子域名].[你的域名]/[你的访问路径(如果有的话)]即可访问内网服务

附:关于对内网HTTPS服务的特殊配置

由于内网https服务证书一般为未受信任证书,若直接绑定到域名,访问时会提示Bad Gateway,因此需要对穿透的HTTPS做一些配置

打开Tunnels,点击隧道旁的Configure进入配置界面,选择Public Hostname

image-20230225194302124

选择你要修改的域名,点击Edit

image-20230225194404224

选择Additional application settings -> TLS,将No TLS Verify一项启用

image-20230225194534026

保存后即可正常访问

前言

最近在研究Arma 3的SQF脚本,心想要是能收集一些游戏基础数据就好了,于是就有了这个项目。

所需Mod

FileXT

3den Enhanced(打开任务场景时需要)

制作流程

1. 创建任务文件

打开任务编辑器,创建一个空场景。

在场景内放置步枪兵 (未武装) x 1,类名为 B_Soldier_unarmed_F

在场景内放置野营桌若干 (要测试几种导弹就放几个),类名为 Land_CampingTable_F

image-20221125152852400

打开任务所在文件夹,路径为:文档 -> Arma 3 - Other Profiles -> 你的 ID -> missions -> 任务名

创建Description.ext文件,输入:

class CfgFunctions
{
    class rickg
    {
        class Utilities
        {
            class missileTrack {};
        };
    };
};

创建Functions文件夹,并在其中创建Utilities文件夹

Utilities文件夹中创建fn_missileTrack.sqf文件

2. 编写相关脚本

在任务编辑器中双击野营桌,打开属性编辑页面。

image-20221125153845020

初始化一栏中填入以下脚本:

private _position = getPos this; // 获取桌子当前位置
_position set [2, (_position select 2) + 1.3]; // 使导弹悬浮在桌子上方
private _missile; 
private _missileType = "PylonMissile_1Rnd_Missile_AA_04_F"; // 这里和下面的_missileType为CfgMagazines中的导弹类型,可在配置查看器中查看
_missile = createVehicle [getText (configFile >> "CfgMagazines" >> _missileType >> "ammo"), _position, [], 0, "CAN_COLLIDE"]; 
[_missile, [225.103, 0, 0]] call BIS_fnc_setObjectRotation; // 这行的225.103按你摆放桌子的方向来
_missile enableSimulation false; // 禁用模拟,使导弹静止
this addAction [format ["测试 %1", getText (configFile >> "CfgMagazines" >> _missileType >> "displayName")], { 
    private _missileType = "PylonMissile_1Rnd_Missile_AA_04_F"; 
    [_missileType] call rickg_fnc_missileTrack; 
}]; // 添加测试导弹的选项,这里使用了读取CfgMagazines动态获取导弹名

打开任务路径下的Functions/Utilities文件夹

编辑fn_missileTrack.sqf

params ["_type"];
[_type] spawn {
    params ["_type"]; // 获取参数
    _typePlayer = player;
    _positionMissile = [0, 0, 1000]; // 设置导弹,跟踪无人机初始坐标为[0, 0, 1000]
    _positionDrone = [0, 0, 1000];
    _missile = createVehicle [getText (configFile >> "CfgMagazines" >> _type >> "ammo"), _positionMissile]; // 创建导弹实体
    _launchTime = time;
    _UAV = createVehicle ["B_UAV_01_F", _positionDrone, [], 0, "CAN_COLLIDE"]; // 创建无人机实体
    _UAV attachTo [_missile, [0, 0, 0]]; // 将无人机附属在导弹上
    _UAV engineOn false;
    _UAV setFuel 0;
    _UAV allowDamage false; // 禁用无人机损害
    hideObject _UAV; // 隐藏无人机模型
    createVehicleCrew _UAV;
    gunner _UAV allowDamage false; // 禁用无人机损害
    selectPlayer gunner _UAV; // 将玩家视角移动到无人机上
    // FileXT 获取文件名
    private _fileName_Time = getText (configFile >> "CfgMagazines" >> _type >> "displayName") + "_Time.txt";
    [_fileName_Time] call filext_fnc_deleteFile; // 删除旧文件
    [_fileName_Time] call filext_fnc_open; // 打开文件
    private _fileName_Dist = getText (configFile >> "CfgMagazines" >> _type >> "displayName") + "_Dist.txt";
    [_fileName_Dist] call filext_fnc_deleteFile;
    [_fileName_Dist] call filext_fnc_open;
    while {alive _missile} do
    {    
        hintSilent format ["型号: %1\n速度: %2 km/h\n高度变化: %3 m\n飞行距离: %4 m\n飞行时间: %5 s", getText (configFile >> "CfgMagazines" >> _type >> "displayName"), speed _missile, (getPos _missile select 2) - 1000, _positionMissile distance getPos _missile, time - _launchTime]; // 输出速度、高度变化、飞行距离等相关信息

        [_fileName_Time, str(time - _launchTime), str(speed _missile)] call filext_fnc_set; // 添加键值到待写入项
        [_fileName_Dist, str(_positionMissile distance getPos _missile), str((getPos _missile select 2) - 1000)] call filext_fnc_set;
    };
    [_fileName_Time] call filext_fnc_write; // 写入到文件
    [_fileName_Dist] call filext_fnc_write;
    [_fileName_Time] call filext_fnc_close; // 关闭文件流
    [_fileName_Dist] call filext_fnc_close;
    selectPlayer _typePlayer; // 切换回玩家
    deleteVehicle _UAV;
};

3. 测试

挂载FileXT Mod,运行任务,导弹测试结果应该如下图

image-20221125155345765

测试完成后,打开Arma3安装目录/!Workshop/@FileXT/storage文件夹,可以发现生成了导弹名_Time.txt导弹名_Dist.txt两个文件

image-20221125155841329

4. 整理数据文件

用Visual Studio Code打开数据文件,可以发现有大量特殊符号,这是FileXT模组的存储模式导致的(详见https://github.com/Vindicta-Team/FileXT/wiki

image-20221125160355097

打开侧边栏的搜索一项,点击搜索框中的第三个图标启用正则表达式,而后将f.*\u0001\u0000{3}替换为空白,去掉文件开头的特殊符号

再将(.)\u0000(.)替换为$1,$2,替换掉两个数字之间的NUL

而后将剩下的\u0000替换为空白,这样所有特殊符号就被消除了

image-20221125161100799

在两个文件中搜索"1000",0的无效数据项,将其删除

在两个文件中搜索"a",0的无效数据项,将其删除(a的值与导弹飞行的时间相似)

5. 分析数据文件并导出为图片

新建一个文件夹,在其下创建processData.py,内容如下:

import pandas as pd
import os
import matplotlib.pyplot as plt
import re


def dirProcess(fileList: list, missileType: str):
    fileListTime = []
    fileListDist = []
    for i in fileList:
        if (re.search('.*_Time.txt', i) != None):
            fileListTime.append(re.search('.*_Time.txt', i).group())
        if (re.search('.*_Dist.txt', i) != None):
            fileListDist.append(re.search('.*_Dist.txt', i).group())

    for i in fileListTime:
        dataFrame = pd.read_table(
            './导弹飞行数据/' + missileType + '/' + i, sep=',', header=None)
        dataFrame = dataFrame.sort_values(by=0)
        x = []
        y = []
        for index, row in dataFrame.iterrows():
            x.append(row[0])
            y.append(row[1])
        figTime = plt.figure()
        ax = figTime.add_axes([0.12, 0.12, 0.8, 0.8])
        ax.grid(True)
        ax.plot(x, y)
        ax.set_xlabel("飞行时间 (s)")
        ax.set_ylabel("速度 (km/h)")
        ax.set_title(i.split('_')[0] + "导弹数据")
        plt.savefig('./导弹飞行数据/图片/' + missileType + '/' + i.split('_')
                    [0] + "导弹数据_时间-速度.png", dpi=288)
        plt.close()

    for i in fileListDist:
        dataFrame = pd.read_table(
            './导弹飞行数据/' + missileType + '/' + i, sep=',', header=None)
        dataFrame = dataFrame.sort_values(by=0)
        x = []
        y = []
        for index, row in dataFrame.iterrows():
            x.append(row[0])
            y.append(row[1])
        figDist = plt.figure()
        ax = figDist.add_axes([0.12, 0.12, 0.8, 0.8])
        ax.grid(True)
        ax.plot(x, y)
        ax.set_xlabel("飞行距离 (m)")
        ax.set_ylabel("相对高度 (m)")
        ax.set_title(i.split('_')[0] + "导弹数据")
        plt.savefig('./导弹飞行数据/图片/' + missileType + '/' + i.split('_')
                    [0] + "导弹数据_距离-高度.png", dpi=288)
        plt.close()


def dirProcessinAll(fileList: list, missileType: str):
    fileListTime = []
    fileListDist = []
    for i in fileList:
        if (re.search('.*_Time.txt', i) != None):
            fileListTime.append(re.search('.*_Time.txt', i).group())
        if (re.search('.*_Dist.txt', i) != None):
            fileListDist.append(re.search('.*_Dist.txt', i).group())
    figure, axes = plt.subplots(1, 2, figsize=(38.4, 10.8), dpi=200)
    labels_time = []
    labels_dist = []
    axes[0].set_xlabel("飞行时间 (s)")
    axes[0].set_ylabel("速度 (km/h)")
    axes[0].set_title(missileType + " 时间 - 速度 表")
    axes[0].grid(True)
    axes[1].set_xlabel("飞行距离 (m)")
    axes[1].set_ylabel("相对高度 (m)")
    axes[1].set_title(missileType + " 距离 - 高度 表")
    axes[1].grid(True)
    for i in fileListTime:
        dataFrame = pd.read_table(
            './导弹飞行数据/' + missileType + '/' + i, sep=',', header=None)
        dataFrame = dataFrame.sort_values(by=0)
        x = []
        y = []
        for index, row in dataFrame.iterrows():
            x.append(row[0])
            y.append(row[1])
        axes[0].plot(x, y)
        labels_time.append(i.split('_')[0])
    axes[0].legend(tuple(labels_time), loc='best')
    for i in fileListDist:
        dataFrame = pd.read_table(
            './导弹飞行数据/' + missileType + '/' + i, sep=',', header=None)
        dataFrame = dataFrame.sort_values(by=0)
        x = []
        y = []
        for index, row in dataFrame.iterrows():
            x.append(row[0])
            y.append(row[1])
        axes[1].plot(x, y)
        labels_dist.append(i.split('_')[0])
    axes[1].legend(tuple(labels_dist), loc='best')
    # plt.show()
    plt.savefig('./导弹飞行数据/图片/' + missileType + '/' + "各导弹数据对比.png")
    plt.close()


print("Please enter what the version you want to process\n")
print("1. Single")
print("2. All in One")
cin = input()
if (cin == '1'):
    # Process Ground to Ground Missiles
    dirProcess(os.listdir('./导弹飞行数据/地地导弹'), '地地导弹')
    # Process Air to Ground Missiles
    dirProcess(os.listdir('./导弹飞行数据/空地导弹'), '空地导弹')
    # Process Surface to Air Missiles
    dirProcess(os.listdir('./导弹飞行数据/地空导弹'), '地空导弹')
    # Process AA SR Missiles
    dirProcess(os.listdir('./导弹飞行数据/近距弹'), '近距弹')
    # Process AA MR Missiles
    dirProcess(os.listdir('./导弹飞行数据/中距弹'), '中距弹')
elif (cin == '2'):
    # Process Ground to Ground Missiles
    dirProcessinAll(os.listdir('./导弹飞行数据/地地导弹'), '地地导弹')
    # Process Air to Ground Missiles
    dirProcessinAll(os.listdir('./导弹飞行数据/空地导弹'), '空地导弹')
    # Process Surface to Air Missiles
    dirProcessinAll(os.listdir('./导弹飞行数据/地空导弹'), '地空导弹')
    # Process AA SR Missiles
    dirProcessinAll(os.listdir('./导弹飞行数据/近距弹'), '近距弹')
    # Process AA MR Missiles
    dirProcessinAll(os.listdir('./导弹飞行数据/中距弹'), '中距弹')

在其下创建目录,目录树如下:

└─导弹飞行数据
    ├─中距弹
    ├─图片
    │  ├─中距弹
    │  ├─地地导弹
    │  ├─地空导弹
    │  ├─空地导弹
    │  └─近距弹
    ├─地地导弹
    ├─地空导弹
    ├─空地导弹
    └─近距弹

将你分析得到的导弹数据按分类放入导弹飞行数据文件夹下的分类即可

运行Python文件,在图片目录下即可查看输出的数据

6. 关于测试场景

测试场景与Python代码我会一同打包放到网盘

下载地址:https://wwb.lanzoue.com/iTmkn0gvll7c