飞羽小随笔-个人技术文章 - 所有文章
很久没更新了，由于本人工作原因以及个人原因（懒），现更新一个中间件编译和使用方法，以便以后进行翻阅。fastdds源码编译一、fastdds介绍[scode type="blue"]fastDDS 是由OMG定义的数据分发服务（DDS）规范的 C++实现。fastdds库提供了应用程序编程接口（API）和通信协议，部署了以数据为中心的发布-订阅（DCPS）模型，旨在实现实时系统之间高效且可靠的信息分布。DDS 采用的通信模型是一种多对多的单向数据交换，其中生成数据的应用程序将数据发布到属于消费数据应用程序的订阅者的本地缓存中。 信息流由负责数据交换的实体之间建立的服务质量 (QoS) 策略进行调节。<来自官网翻译>以下为DDS 数据流图，具体详细讲解请参考官网。[/scode]二、FASTDDS编译在windows中，你需要安装好visual studio，作者本人使用的visual studio版本为2019企业版，当前FASTDDS版本为3.3.0第一步，克隆代码git clone https://github.com/eProsima/Fast-DDS.git cd Fast-DDS git submodule update --init #拉取子模块android,asio,fascdr,tinyxml2 git submodule add https://github.com/foonathan/memory.git thirdparty/foonathan-memory #需要添加一个子模块第二步，编译依赖库找到vs2019控制台(x64 Native Tools Command Prompt for VS 2019)，打开，以下编译过程需要在vs2019控制台中操作，如下图2.1 编译 foonathan-memorycd thirdparty/foonathan-memory #进入foonathan-memory目录 mkdir build cd build #构建build目录 cmake ../ -DFOONATHAN_MEMORY_BUILD_EXAMPLES=OFF -DFOONATHAN_MEMORY_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=F:\fastdds\cmake\Fast-DDS\thirdparty\install #指定安装目录和配置foonathan-memory相关功能 cmake --build . --target install --config Release #在vs2019下执行完毕后foonathan-memory安装在F:fastddscmakeFast-DDSthirdpartyinstall目录。2.2 编译 fastcdr编译fastcdr与编译foonathan-memory类似，这里就不多赘述了。cd ../.. cd fastcdr mkdir build cd build cmake ../ -DCMAKE_INSTALL_PREFIX=F:\fastdds\cmake\Fast-DDS\thirdparty\install cmake --build . --target install --config Release2.3 编译 tinyxml2编译tinyxml2与编译foonathan-memory类似，同样不多赘述了。cd ../.. cd tinyxml2 mkdir build cd build cmake ../ -DCMAKE_INSTALL_PREFIX=F:\fastdds\cmake\Fast-DDS\thirdparty\install cmake --build . --target install --config Release2.3 编译 Fast-DDS编译Fast-DDS需要指定上述第三方库的安装位置和asio，然后进行编译。cd ../../../../ cd Fast-DDS mkdir build cd build cmake ../ -DCMAKE_INSTALL_PREFIX=F:\fastdds\cmake\Fast-DDS\install -DCMAKE_PREFIX_PATH="F:\fastdds\cmake\Fast-DDS\thirdparty\asio\asio;F:\fastdds\cmake\Fast-DDS\thirdparty\install" -DNO_TLS=ON -DCOMPILE_EXAMPLES=ON cmake --build . --target install --config Release如下图为编译后第三方库目录结构：如下图为编译后FASTDDS目录结构<未截全>：在bin目录下，生成以下文件：就此，编译成功使用方法3.1 编写IDL文件，以hello world为例(helloworld.idl):module HelloWorldModule { struct HelloWorld { unsigned long index; string message; }; };3.2 IDL生成使用fastddsgen.bat进行代码生成fastddsgen.bat helloworld.idl -d hello/ -language c++生成后截图：三、fastdds使用3.3 工程使用在vs2019中新建工程，引入头文件和cpp文件，如下图所示：3.4 配置预处理器需要你在工程，属性C/C++选项，预处理器选项卡，预处理器定义中添加以下内容,告诉编译器使用 Fast CDR和FASTDDS 的动态链接版本：FASTCDR_DYN_LINK FASTDDS_DYN_LINK截图如下：3.5 添加订阅者在工程中添加订阅者代码#include <chrono> #include <fastdds/dds/domain/DomainParticipant.hpp> #include <fastdds/dds/domain/DomainParticipantFactory.hpp> #include <fastdds/dds/publisher/DataWriter.hpp> #include <fastdds/dds/publisher/DataWriterListener.hpp> #include <fastdds/dds/publisher/Publisher.hpp> #include <fastdds/dds/topic/TypeSupport.hpp> #include <thread> #include "helloworldPubSubTypes.hpp" using namespace eprosima::fastdds::dds; using namespace HelloWorldModule; class HelloWorldPublisher { private: HelloWorld hello_; DomainParticipant* participant_; Publisher* publisher_; Topic* topic_; DataWriter* writer_; TypeSupport type_; class PubListener : public DataWriterListener { public: PubListener() : matched_(0) {} ~PubListener() override {} void on_publication_matched(DataWriter*, const PublicationMatchedStatus& info) override { if (info.current_count_change == 1) { matched_ = info.total_count; std::cout << "Publisher matched." << std::endl; } else if (info.current_count_change == -1) { matched_ = info.total_count; std::cout << "Publisher unmatched." << std::endl; } else { std::cout << info.current_count_change << " is not a valid value for PublicationMatchedStatus current count change." << std::endl; } } std::atomic_int matched_; } listener_; public: HelloWorldPublisher() : participant_(nullptr), publisher_(nullptr), topic_(nullptr), writer_(nullptr), type_(new HelloWorldPubSubType()) {} virtual ~HelloWorldPublisher() { if (writer_ != nullptr) { publisher_->delete_datawriter(writer_); } if (publisher_ != nullptr) { participant_->delete_publisher(publisher_); } if (topic_ != nullptr) { participant_->delete_topic(topic_); } DomainParticipantFactory::get_instance()->delete_participant(participant_); } //! Initialize the publisher bool init() { hello_.index(0); hello_.message("HelloWorld"); DomainParticipantQos participantQos; participantQos.name("Participant_publisher"); participant_ = DomainParticipantFactory::get_instance()->create_participant(0, participantQos); if (participant_ == nullptr) { std::cout << "Error creating participant." << std::endl; return false; } // Register the Type type_.register_type(participant_); // Create the publications Topic topic_ = participant_->create_topic("HelloWorldTopic1", type_.get_type_name(), TOPIC_QOS_DEFAULT); if (topic_ == nullptr) { std::cout << "Error creating topic." << std::endl; return false; } // Create the Publisher publisher_ = participant_->create_publisher(PUBLISHER_QOS_DEFAULT, nullptr); if (publisher_ == nullptr) { std::cout << "Error creating publisher." << std::endl; return false; } // Create the DataWriter writer_ = publisher_->create_datawriter(topic_, DATAWRITER_QOS_DEFAULT, &listener_); if (writer_ == nullptr) { std::cout << "Error creating writer." << std::endl; return false; } return true; } //! Send a publication bool publish() { if (listener_.matched_ > 0) { hello_.index(hello_.index() + 1); writer_->write(&hello_); return true; } return false; } //! Run the Publisher void run(uint32_t samples) { uint32_t samples_sent = 0; while (true) { if (publish()) { samples_sent++; std::cout << "Message: " << hello_.message() << " with index: " << hello_.index() << " SENT" << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } }; int main(int argc, char** argv) { std::cout << "Starting publisher." << std::endl; uint32_t samples = 10; HelloWorldPublisher* mypub = new HelloWorldPublisher(); if (mypub->init()) { mypub->run(samples); } delete mypub; return 0; }编译，不出意外订阅者编译成功。3.6 添加发布者在工程中添加发布者代码#include <chrono> #include <fastdds/dds/domain/DomainParticipant.hpp> #include <fastdds/dds/domain/DomainParticipantFactory.hpp> #include <fastdds/dds/publisher/DataWriter.hpp> #include <fastdds/dds/publisher/DataWriterListener.hpp> #include <fastdds/dds/publisher/Publisher.hpp> #include <fastdds/dds/topic/TypeSupport.hpp> #include <thread> #include "helloworldPubSubTypes.hpp" using namespace eprosima::fastdds::dds; using namespace HelloWorldModule; class HelloWorldPublisher { private: HelloWorld hello_; DomainParticipant* participant_; Publisher* publisher_; Topic* topic_; DataWriter* writer_; TypeSupport type_; class PubListener : public DataWriterListener { public: PubListener() : matched_(0) {} ~PubListener() override {} void on_publication_matched(DataWriter*, const PublicationMatchedStatus& info) override { if (info.current_count_change == 1) { matched_ = info.total_count; std::cout << "Publisher matched." << std::endl; } else if (info.current_count_change == -1) { matched_ = info.total_count; std::cout << "Publisher unmatched." << std::endl; } else { std::cout << info.current_count_change << " is not a valid value for PublicationMatchedStatus current count change." << std::endl; } } std::atomic_int matched_; } listener_; public: HelloWorldPublisher() : participant_(nullptr), publisher_(nullptr), topic_(nullptr), writer_(nullptr), type_(new HelloWorldPubSubType()) {} virtual ~HelloWorldPublisher() { if (writer_ != nullptr) { publisher_->delete_datawriter(writer_); } if (publisher_ != nullptr) { participant_->delete_publisher(publisher_); } if (topic_ != nullptr) { participant_->delete_topic(topic_); } DomainParticipantFactory::get_instance()->delete_participant(participant_); } //! Initialize the publisher bool init() { hello_.index(0); hello_.message("HelloWorld"); DomainParticipantQos participantQos; participantQos.name("Participant_publisher"); participant_ = DomainParticipantFactory::get_instance()->create_participant(0, participantQos); if (participant_ == nullptr) { std::cout << "Error creating participant." << std::endl; return false; } // Register the Type type_.register_type(participant_); // Create the publications Topic topic_ = participant_->create_topic("HelloWorldTopic1", type_.get_type_name(), TOPIC_QOS_DEFAULT); if (topic_ == nullptr) { std::cout << "Error creating topic." << std::endl; return false; } // Create the Publisher publisher_ = participant_->create_publisher(PUBLISHER_QOS_DEFAULT, nullptr); if (publisher_ == nullptr) { std::cout << "Error creating publisher." << std::endl; return false; } // Create the DataWriter writer_ = publisher_->create_datawriter(topic_, DATAWRITER_QOS_DEFAULT, &listener_); if (writer_ == nullptr) { std::cout << "Error creating writer." << std::endl; return false; } return true; } //! Send a publication bool publish() { if (listener_.matched_ > 0) { hello_.index(hello_.index() + 1); writer_->write(&hello_); return true; } return false; } //! Run the Publisher void run(uint32_t samples) { uint32_t samples_sent = 0; while (true) { if (publish()) { samples_sent++; std::cout << "Message: " << hello_.message() << " with index: " << hello_.index() << " SENT" << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } }; int main(int argc, char** argv) { std::cout << "Starting publisher." << std::endl; uint32_t samples = 10; HelloWorldPublisher* mypub = new HelloWorldPublisher(); if (mypub->init()) { mypub->run(samples); } delete mypub; return 0; }编译，不出意外，发布者编译成功四、运行截图左边为订阅者，右侧为发布者。
鉴于Routeros的DDNS解析脚本不能提取返回Header，而华为DNS可以任意设置TTL，而华为目前没有完整API，因此借助了Cloudflare Worker，封装了一个API。视频链接：☺教程和文章更配哦~☺1、起因1、想要让DDNS解析快点，需要减小DNS系统的TTL2、常年使用DNSPOD，但是专业版DNSPOD更改TTL只能更改60，企业版才能改为13、想要不受服务器的限制2、技术分享首先需要你拥有一个Cloudflare的账号，没有可以注册一个创建worker2.1 点击创建服务2.2 增加一个服务名称，并创建服务2.3 快速编辑，复制代码，保存代码我会放到文末，需要的可以自取！！！创建完毕，点击发送，可以看到执行内容下面介绍一下如何使用通过GET提交方式，如下图所示，需要用到账户名、密码、zoneid、recordsetid、要更新IP地址、TTL信息相关信息获取教程在下面5. 附加内容华为云账户名获取1.1 在右上角，找到基本信息，点进去2.1 账号名就是华为云账户zoneid和recordsetid获取2.1 首先，找到你的域名，创建一个，如图所示2.2 点击F12，出现控制台，调整为Network栏2.3 修改值为某个IP，点击确定，搜索recordsets2.4 找到如下图的链接/** * Example someHost is set up to take in a JSON request * Replace url with the host you wish to send requests to * @param {string} url the URL to send the request to * @param {BodyInit} body the JSON data to send in the request */ //iam.ap-southeast-3.myhuaweicloud.com const someHost = 'https://iam.ap-southeast-3.myhuaweicloud.com'; //const someHost = 'https://iam.myhuaweicloud.com'; const url = someHost + '/v3/auth/tokens'; /** * gatherResponse awaits and returns a response body as a string. * Use await gatherResponse(..) in an async function to get the response body * @param {Response} response */ async function gatherResponse(response) { const { headers } = response; return headers.get('x-subject-token'); } async function handleRequest(request) { const { searchParams } = new URL(request.url); let name = searchParams.get('name'); let password = searchParams.get('password'); let hwZONEID = searchParams.get('zoneid'); let hwRECORDSETID = searchParams.get('recordsetid'); let hwip = searchParams.get('ip'); let ttl = searchParams.get('ttl'); let requestURL = new URL(request.url) let path = requestURL.pathname.split('/')[1]; if(name == null){ const ip = request.headers.get("cf-connecting-ip") return new Response("华为dns更新\n使用方法：https://"+request.url+"?name=华为云账户&password=华为云密码&zoneid=域名zoneid&recordsetid=域名recordsetid&ip=IP地址&ttl=生效时间\n当前ip:"+ip,{status: 200}); } console.log(name) const body = { "auth": { "identity": { "methods": ["password"], "password": { "user": { "name": name, "password": password, "domain": { "name": name } } } }, "scope": { "domain": { "name": name } } } }; const init = { body: JSON.stringify(body), method: 'POST', headers: { 'content-type': 'application/json;charset=UTF-8', }, }; const response = await fetch(url, init); const results = await gatherResponse(response); //return new Response(results, init); ddnsbody={ "records":[hwip], "ttl":ttl } ddnsinit = { body: JSON.stringify(ddnsbody), method: 'PUT', headers: { 'content-type': 'application/json;charset=UTF-8', 'X-Auth-Token': results }, }; //dns.ap-southeast-3.myhuaweicloud.com //ddnsurl='https://dns.myhuaweicloud.com/v2/zones/'+hwZONEID+'/recordsets/'+hwRECORDSETID ddnsurl='https://dns.ap-southeast-3.myhuaweicloud.com/v2/zones/'+hwZONEID+'/recordsets/'+hwRECORDSETID const myresponse = await fetch(ddnsurl, ddnsinit); return myresponse; return new Response(myresponse.text(), init); //return new Response(results, init); } addEventListener('fetch', event => { return event.respondWith(handleRequest(event.request)); });群晖支持代码1. 使用方法1.1 使用方法很简单，只需要把代码放到worker中，自定义DDNS服务提供商，填写下面内容，保存，如图所示：https://你的CLOUDFLARE WORKER地址/update?name=__USERNAME__&password=__PASSWORD__&zoneid=你的ZONEID&recordsetid=你的RECORDSETID&ip=__MYIP__&ttl=11.2 点击新增，选择服务提供商，填写下面内容即可/** * Example someHost is set up to take in a JSON request * Replace url with the host you wish to send requests to * @param {string} url the URL to send the request to * @param {BodyInit} body the JSON data to send in the request */ //iam.ap-southeast-3.myhuaweicloud.com const someHost = 'https://iam.ap-southeast-3.myhuaweicloud.com'; //const someHost = 'https://iam.myhuaweicloud.com'; const url = someHost + '/v3/auth/tokens'; /** * gatherResponse awaits and returns a response body as a string. * Use await gatherResponse(..) in an async function to get the response body * @param {Response} response */ async function gatherResponse(response) { const { headers } = response; return headers.get('x-subject-token'); } async function getresponse(response) { const { headers } = response; const contentType = headers.get('content-type') || ''; if (contentType.includes('application/json')) { return JSON.stringify(await response.json()); } return response.text(); } async function handleRequest(request) { const { searchParams } = new URL(request.url); let name = searchParams.get('name'); let password = searchParams.get('password'); let hwZONEID = searchParams.get('zoneid'); let hwRECORDSETID = searchParams.get('recordsetid'); let hwip = searchParams.get('ip'); let ttl = searchParams.get('ttl'); let requestURL = new URL(request.url) let path = requestURL.pathname.split('/')[1]; if(name == null){ const ip = request.headers.get("cf-connecting-ip") return new Response("华为dns更新\n使用方法：https://"+request.url+"?name=华为云账户&password=华为云密码&zoneid=域名zoneid&recordsetid=域名recordsetid&ip=IP地址&ttl=生效时间\n当前ip:"+ip,{status: 200}); } console.log(name) const body = { "auth": { "identity": { "methods": ["password"], "password": { "user": { "name": name, "password": password, "domain": { "name": name } } } }, "scope": { "domain": { "name": name } } } }; const init = { body: JSON.stringify(body), method: 'POST', headers: { 'content-type': 'application/json;charset=UTF-8', }, }; const response = await fetch(url, init); const results = await gatherResponse(response); //return new Response(results, init); ddnsbody={ "records":[hwip], "ttl":ttl } ddnsinit = { body: JSON.stringify(ddnsbody), method: 'PUT', headers: { 'content-type': 'application/json;charset=UTF-8', 'X-Auth-Token': results }, }; //dns.ap-southeast-3.myhuaweicloud.com //ddnsurl='https://dns.myhuaweicloud.com/v2/zones/'+hwZONEID+'/recordsets/'+hwRECORDSETID ddnsurl='https://dns.ap-southeast-3.myhuaweicloud.com/v2/zones/'+hwZONEID+'/recordsets/'+hwRECORDSETID const myresponse = await fetch(ddnsurl, ddnsinit); //return myresponse; const rcode=await getresponse(myresponse); console.log(rcode); if(JSON.parse(rcode)['error_code']=='APIGW.0301'){ return new Response("badauth", init); } if(JSON.parse(rcode)['code']=='DNS.0302'){ return new Response("nohost", init); } if(JSON.parse(rcode)['code']=='DNS.0313'){ return new Response("nohost", init); } if(JSON.parse(rcode)['code']=='DNS.0308'){ return new Response("abuse", init); } //return rcode; //return new Response(myresponse.body, init); //good return new Response("good", init); //return new Response(results, init); } addEventListener('fetch', event => { return event.respondWith(handleRequest(event.request)); });另附PHP版<?php function PostMain($url,$data){ $curl = curl_init(); curl_setopt($curl, CURLOPT_URL,$url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POST,1); curl_setopt($curl, CURLOPT_POSTFIELDS,$data); curl_setopt($curl, CURLOPT_HEADER, true); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); $data = curl_exec($curl); curl_close($curl); $arr = explode("\r\n", $data); $token = explode(":", $arr[9]); return $token[1]; } function PutMeth($url,$data,$token){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json','X-Auth-Token: '.$token)); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($ch, CURLOPT_POSTFIELDS,$data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); curl_close($ch); return $response; } function codeecho($jsoncode,$domain_name){ if(isset($jsoncode['error_code'])){ if($jsoncode['error_code']=='APIGW.0301'){ return "badauth"; } } if(isset($jsoncode['code'])){ if($jsoncode['code']=='DNS.0302'){ return "nohost"; } if($jsoncode['code']=='DNS.0313'){ return "nohost"; } if($jsoncode['code']=='DNS.0308'){ return "abuse"; } } if($jsoncode['name']!=$domain_name."."){ return "nochg"; } return "good"; } function ddns($name,$password,$hwip,$ttl,$zoneid,$recordsetid,$domain_name){ $url = "https://iam.myhuaweicloud.com/v3/auth/tokens"; $ddnsurl="https://dns.myhuaweicloud.com/v2/zones/".$zoneid."/recordsets/".$recordsetid; $loginbody="{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"name\":\"".$name."\",\"password\":\"".$password."\" ,\"domain\":{\"name\":\"".$name."\"}}}},\"scope\":{\"domain\":{\"name\":\"".$name."\"}}}}"; $ddnsbody="{\"records\":[\"$hwip\"],\"ttl\":".$ttl."}"; $token=PostMain($url,$loginbody); $result= PutMeth($ddnsurl,$ddnsbody,$token); $jsoncode=json_decode($result,true); return codeecho($jsoncode,$domain_name); } function usage(){ echo " 华为dns更新 使用方法： http://域名/update.php?name=华为云账户&password=华为云密码&zoneid=域名zoneid&recordsetid=域名recordsetid&ip=IP地址&ttl=生效时间&domain=域名 "; } header("Content-Type: text/html; charset=utf-8"); $myip=$_SERVER["REMOTE_ADDR"]; if (is_array($_GET) && count($_GET) > 0) { if (isset($_GET['name']) && isset($_GET['name']) && isset($_GET['password']) && isset($_GET['ttl']) && isset($_GET['zoneid']) && isset($_GET['recordsetid']) && isset($_GET['domain'])) { $name = $_GET['name']; $password=$_GET['password']; if(!isset($_GET['ip'])){ $_GET['ip']=$myip; } //$_GET['ip']=$myip; $hwip= $_GET['ip']; $ttl= $_GET['ttl']; $zoneid= $_GET['zoneid']; $recordsetid= $_GET['recordsetid']; $domain_name=$_GET['domain']; echo ddns($name,$password,$hwip,$ttl,$zoneid,$recordsetid,$domain_name); }else{ usage(); echo $myip; } }else{ usage(); echo $myip; } ?>思路参考的大佬的，在此感谢直达链接https://github.com/lllvcs/huaweicloud_ddns
前几天想要把服务器中远程ip分配给家里的某台服务器使用，因此写了这个教程记录一下过程。传统的frp、ngrok虽然可以达到内网穿透的效果，但是对于全端口映射是非常不方便的，因此想到使用Wireguard和RouterOS将服务器的ip分配给本地的方案。参考这个大佬的链接：https://aoyouer.com/posts/wireguard-l3-forward/主要分为以下几个过程：1、开启net.ipv4.ip_forward2、安装wireguard3、配置wireguard服务端4、更改ssh端口5、routeros配置wireguard6、添加路由规则7、测试1、开启net.ipv4.ip_forwardecho "net.ipv4.ip_forward=1">>/etc/sysctl.conf sysctl -p2、安装wireguardapt install wireguard iptables -y #安装wireguard modprobe wireguard && lsmod | grep wireguard #验证安装是否成功2、配置wireguard服务端公钥私钥生产cd /etc/wireguard # 首先进入配置文件目录 wg genkey | tee sprivatekey | wg pubkey > spublickey wg genkey | tee cprivatekey | wg pubkey > cpublickey #生成公钥和私钥服务端配置echo "[Interface] # 服务器的私匙，对应客户端配置中的公匙（自动读取上面刚刚生成的密匙内容） PrivateKey = $(cat sprivatekey) # 本机的内网IP地址，一般默认即可，除非和你服务器或客户端设备本地网段冲突 Address = 192.168.145.1/24 PostUp = iptables -t nat -A PREROUTING -d 10.0.24.11 -p tcp -m multiport --dports 65533,65534 -j ACCEPT; iptables -t nat -A PREROUTING -d 10.0.24.11 -p udp -m multiport --dports 65533,65534 -j ACCEPT; iptables -t nat -A PREROUTING -d 10.0.24.11 -j DNAT --to-destination 10.10.0.34; iptables -t nat -A POSTROUTING -s 10.10.0.34 -j SNAT --to-source 10.0.24.11; #10.0.24.11替换为你自己的网卡地址 65533为ssh端口 65534为wireguard端口 PostDown = iptables -t nat -D PREROUTING -d 10.0.24.11 -p tcp -m multiport --dports 65533,65534 -j ACCEPT; iptables -t nat -D PREROUTING -d 10.0.24.11 -p udp -m multiport --dports 65533,65534 -j ACCEPT; iptables -t nat -D PREROUTING -d 10.0.24.11 -j DNAT --to-destination 10.10.0.34; iptables -t nat -D POSTROUTING -s 10.10.0.34 -j SNAT --to-source 10.0.24.11; #10.0.24.11替换为你自己的网卡地址 # 服务端监听端口，可以自行修改 ListenPort = 65534 # 保持默认 MTU = 1420 # [Peer] 代表客户端配置，每增加一段 [Peer] 就是增加一个客户端账号。 [Peer] # 该客户端账号的公匙，对应客户端配置中的私匙 PublicKey = $(cat cpublickey) # 该客户端账号的内网IP地址 AllowedIPs = 192.168.145.0/24,10.10.0.0/16"|sed '/^#/d;/^\s*$/d' > wg0.conf修改ssh端口vim /etc/ssh/sshd_config 修改 Port 22 改为 Port 65533 systemctl restart sshd服务端启动wg-quick up wg04、routeros配置wireguardecho "[Interface] # 客户端的私匙，对应服务器配置中的客户端公匙（自动读取上面刚刚生成的密匙内容） PrivateKey = $(cat cprivatekey) # 客户端的内网IP地址 Address = 10.0.0.2/24 # 解析域名用的DNS DNS = 8.8.8.8 # 保持默认 MTU = 1420 [Peer] # 服务器的公匙，对应服务器的私匙（自动读取上面刚刚生成的密匙内容） PublicKey = $(cat spublickey) # 服务器地址和端口 Endpoint = X.X.X.X:443 # 因为是客户端，所以这个设置为全部IP段即可 AllowedIPs = 0.0.0.0/0, ::0/0 # 保持连接 PersistentKeepalive = 25"|sed '/^#/d;/^\s*$/d' > client.conf在routeros终端中输入以下内容interface/wireguard/add name="wireguard1" mtu=1300 private-key="你的interface私钥" /interface/wireguard/peers/add interface="wireguard1" public-key="你的peers公钥" endpoint-address="服务端ip" endpoint-port=端口 allowed-address="0.0.0.0/0" persistent-keepalive=5 /ip/address/add address="wireguard网卡网段" interface="wireguard1" routing/table/add name="wgt" fib /ip/route/add dst-address="0.0.0.0/0" gateway="wireguard1" routing-table="wgt" /routing/rule/add src-address="本地目标主机" routing-mark=main action=lookup-only-in-table table=wgt /ip/firewall/nat/add chain="srcnat" action=masquerade如下图为正常ping值如下图为通过wireguard把ip分配给本地延迟可以看到延迟增加了一倍，因此最好大家使用离自己区域进的地方使用。由于使用的是腾讯云8M轻量，因此测试带宽只有8M，算是把服务器网速跑满了。
分享一个自己写的DNSPOD DDNS ipv6脚本1、脚本分享#!/bin/bash DNSPOD_ID='你申请的DNSPOD ID' # DNSPOD 域名ID DNSPOD_PASSWORD='你的DNSPOD TOKEN' # DNSPOD Token RECORD_ID='域名RECORD_ID' #DNSPOD TOKEN ID SUB_DOMAIN='域名前缀' # SUB DOMAIN DOMAIN='域名后缀' # DOMAIN dns='2400:3200::1' check_ipv6_connect(){ check_connect=$(ping6 -w 3 240c::6666 -c 1 | grep ttl) if [ "$check_connect" == "" ] then echo "connect error" sleep 2 systemctl restart networking sleep 2 dhclient exit 1 fi } source /etc/profile check_ipv6_connect ipv6ustc=$(dig @240C::6666 AAAA test6.ustc.edu.cn +short) a=$(curl -s -H 'Host:test6.ustc.edu.cn' http://[$ipv6ustc]/backend/getIP.php | awk -F '"' '{print $4}') b=$(curl -s -X POST https://dnsapi.cn/Record.Info -d 'login_token='$DNSPOD_ID','$DNSPOD_PASSWORD'&format=json&domain='$DOMAIN'&record_id='$RECORD_ID'' |awk -F '"' '{print $60}') if [ "$a" != "$b" ] then curl -X POST https://dnsapi.cn/Record.Modify -d 'login_token='$DNSPOD_ID','$DNSPOD_PASSWORD'&format=json&domain='$DOMAIN'&record_id='$RECORD_ID'&sub_domain='$SUB_DOMAIN'&value='$a'&record_type=AAAA&record_line_id=0' echo "changed" else echo "no change" fi echo "success"2、参数说明DNSPOD_ID、DNSPOD_PASSWORD：点我进入DNSPOD密钥地址，创建密钥，如图所示：这两个参数填上RECORD_ID获取需要你的打卡域名解析记录，如图所示需要你使用先新建你的域名，例如blog.8086k.cn接下来打开F12，并切换到network选项按照图上方法操作可以看到两条记录，点开，如下操作添加上即可。SUB_DOMAIN:域名前缀 例如你的域名 blog.8086k.cn，那么SUB_DOMAIN就是blogDOMAIN:域名后缀 例如你的域名 blog.8086k.cn，那么DOMAIN就是8086k.cn3、使用方法放入到linux系统中，crontab中即可*/1 * * * * /root/ipv6.sh
本文主要介绍openwrt打包成lxc模板，并启动。1、LXC是什么[collapse title="LXC介绍" status="true"]Linux Container容器是一种内核虚拟化技术，可以提供轻量级的虚拟化，以便隔离进程和资源。[/collapse]2、openwrt介绍[collapse title="openwrt介绍" status="true"]OpenWrt是适合于嵌入式设备的一个Linux发行版。相对原厂固件而言，OpenWrt不是一个单一、静态的固件，而是提供了一个可添加软件包的可写的文件系统。这使用户可以自由的选择应用程序和配置，而不必受设备提供商的限制，并且可以使用一些适合某方面应用的软件包来定制你的设备。对于开发者来说，OpenWrt是一个框架，开发者不必麻烦地构建整个固件就能得到想要的应用程序；对于用户来说，这意味着完全定制的能力，与以往不同的方式使用设备，OPKG包含超过3500个软件。 默认使用LuCI作为web交互界面。[/collapse]3、LXC安装安装非常简单，apt即可。apt-get install bridge-utils apt-get install lxc lxc-checkconfig apt-get install apparmor-profiles4、配置首先桥接网卡在/etc/network/interfaces文件中添加br0网桥# /etc/network/interfaces auto br0 iface br0 inet static address ipv4地址 netmask ipv4子网掩码 bridge_ports 桥接网卡名 bridge_stp off bridge_fd 2 bridge_maxwait 20 iface br0 inet6 static address ipv6地址/120 gateway ipv6网关 dns-nameservers ipv6dns配置lxc默认文件在/etc/lxc/default.conf中添加如下内容#/etc/lxc/default.conf lxc.apparmor.profile = generated lxc.apparmor.allow_nesting = 1 lxc.net.0.type=veth lxc.net.0.link = br0 lxc.net.0.flags = up lxc.net.0.hwaddr = 00:11:22:xx:xx:xx5、使用创建镜像模板lxc-create -t download -n openwrt -- --no-validate --server mirrors.tuna.tsinghua.edu.cn/lxc-images启动容器lxc-start -n ubuntu1 -d停止容器lxc-stop --name ubuntu1销毁容器lxc-destroy --name ubuntu16、openwrt镜像制作首先下载你心仪的openwrt系统在linux下解压，使用如下命令gzip -d openwrt-x86-64-generic-squashfs-rootfs.img.gz解压之后出现openwrt-x86-64-generic-squashfs-rootfs.img文件安装squashfs-toolsapt install squashfs-tools解包会解压到squashfs-root目录进入 cd squashfs-rootunsquashfs openwrt-x86-64-generic-squashfs-rootfs.img mv squashfs-root rootfs解包后如图所示：这样lxc的rootfs就制作好了制作openwrt模板1、在/var/lib/lxc新建openwrt目录,mkdir /var/lib/lxc/openwrt touch /var/lib/lxc/openwrt/config2、把以下内容写入到config中# Template used to create this container: /usr/share/lxc/templates/lxc-download # Parameters passed to the template: --server mirrors.tuna.tsinghua.edu.cn/lxc-images # Template script checksum (SHA-1): 273c51343604eb85f7e294c8da0a5eb769d648f3 # For additional config options, please look at lxc.container.conf(5) # Uncomment the following line to support nesting containers: #lxc.include = /usr/share/lxc/config/nesting.conf # (Be aware this has security implications) # Distribution configuration lxc.include = /usr/share/lxc/config/common.conf lxc.arch = x86_64 # Container specific configuration lxc.apparmor.profile = generated lxc.apparmor.allow_nesting = 1 lxc.rootfs.path = dir:/var/lib/lxc/openwrt/rootfs lxc.uts.name = openwrt # Network configuration lxc.net.0.type=veth lxc.net.0.link = br0 lxc.net.0.flags = up lxc.net.0.hwaddr = 00:16:3e:35:10:cd接下来可以启动openwrt启动openwrtlxc-start -n openwrt -d配置openwrt系统使用lxc-attach进入openwrt系统vim /etc/config/network按照以下内容更改即可，这里做一个范例：config interface 'loopback' option ifname 'lo' option proto 'static' option ipaddr '127.0.0.1' option netmask '255.0.0.0' config globals 'globals' option ula_prefix 'fd8c:255f:3d91::/48' option packet_steering '1' config interface 'lan' option ifname 'eth0' option proto 'static' option ipaddr 'ipv4地址' option netmask 'ipv4掩码' option gateway 'ipv4网关' option dns 'DNS' config interface 'lan6' option proto 'static' option ifname '@lan' option ip6addr 'ipv6地址/120' option ip6gw 'ipv6网关' option delegate '0'【注】其他的配置参数，可以参考openwrt ipv6配置报错以及解决有可能openwrt系统stop之后会出现以下错误lxc-start: mylxc: conf.c: lxc_setup_ttydir_console: 1831 Read-only file system - Failed to set mode "0110" to "/dev/pts/3"解决方法：mount -o remount,rw /dev/ptslxc常用使用命令6.1 lxc开机启动命令### 修改容器配置文件，添加如下内容 ### lxc.start.auto = 1 ### lxc.start.delay = 15 ### lxc.group = onboot ### 第一项表示开启自启动，第二项表示启动延迟，第三项表示自启动顺序 # vim /var/lib/var/lib/lxc/<container-name>/config ### 自启动onboot组的容器 # lxc-autostart -g onboot6.2 lxc开启tun设备在/var/lib/lxc/你的容器下/目录下创建如下脚本：#!/bin/bash cd ${LXC_ROOTFS_MOUNT}/dev mkdir net mknod net/tun c 10 200 chmod 0666 net/tun赋予可执行权限chmod +x autodev最后，在容量的config文件最后，添加以下内容:lxc.hook.autodev=/var/lib/lxc/yourcontainer/autodev # /dev/net/tun lxc.cgroup.devices.allow = c 10:200 rwm重启动容器后，出现/dev/net/tun设备文件，需要tun设备的程序就可以执行了。最后放一张截图
自我折腾记录，校园网接入Padavan路由器没有ipv6，因此如果想要内网拥有IPV6，那么此教程正好适合你。1. 配置IPV6首先padavan设置需要这么设置，把IPV6打开，自动获取IPV6地址2. 配置脚本添加以下脚本到自定义设置->脚本->在路由器启动后执行的最后一行，如图所示modprobe ip6table_mangle ebtables -t broute -A BROUTING -p ! ipv6 -j DROP -i eth2.2 brctl addif br0 eth2.23. 重启电脑，IPV6就可以连上了。[scode type="blue"]参考博客：https://www.jianshu.com/p/ed34cbf3b90c[/scode]
本文主要内容是打包arm的spk文件,为以后自己方便查阅。一、nps介绍[collapse title="nps介绍" status="true"]nps是一款轻量级、高性能、功能强大的内网穿透代理服务器。balabala...具体详情请见github作者地址[/collapse]二、起因[scode type="yellow"]发现nps作者github下又群晖的安装包，但是是x86的，想着自己有个扎矿猫盘刷了群晖，是arm架构的，那么没办法，自己编译吧。-_-自己动手丰衣足食.jpg[/scode]三、逐渐的看代码过程。。。pass本次关键：从nps项目下的build.sh脚本看出来怎么部署，那么就按照build.sh脚本来吧废话不说了 进入正题吧四、正题开始4.1 部署环境[scode type="blue"]首先安装docker，安装过程pass，不会的请参照本博客其他帖子。[/scode]4.2 下载nps、docker镜像、spksrcroot@server:~# mkdir WorkShop #新建工作目录 root@server:~/WorkShop# https://github.com/ehang-io/nps.git # git clone nps项目 root@server:~/WorkShop# git clone https://github.com/cnlh/spksrc.git ~/WorkShop/spksrc # git clone spksrc项目 root@server:~/WorkShop# mkdir ~/WorkShop/spksrc/nps && cp -rf /root/WorkShop/nps/* ~/WorkShop/spksrc/nps/ # 拷贝文件 root@server:~/WorkShop/nps# docker pull synocommunity/spksrc #拉取镜像 有点大-_- root@server:~/WorkShop/nps# docker run -itd --name spksrc --env VERSION=0.26.6 -e GOPROXY=direct -v ~/WorkShop/spksrc:/spksrc synocommunity/spksrc /bin/bash # 运行容器 root@server:~/WorkShop/nps# docker exec -it spksrc /bin/bash -c 'cd /spksrc && make setup && cd /spksrc/spk/npc && make' # a few moments later... # 先试着编译X86的 没问题进行下一步[scode type="blue"]到这里有两种方法，一种简单点，另一种需要改Makefile文件[/scode][scode type="red"]第一种方法[/scode]root@server:~/WorkShop/nps# docker exec -it spksrc /bin/bash -c 'cd /spksrc/spk/npc && make clean && cd /spksrc && make setup && cd /spksrc/spk/npc && make arch-aarch64' # 直接加架构，编译成功 #可以直接把~/WorkShop/spksrc/packages/里的spk文件复制出来直接用了[scode type="red"]第二种方法[/scode]#更改Makefile root@server:~/WorkShop/spksrc/spk/npc# pwd /root/WorkShop/spksrc/spk/npc[scode type="blue"]把47行左右改为[/scode]ifeq ($(findstring $(ARCH),armada37xx),$(ARCH)) GO_ARCH = arm64 endif[scode type="blue"]之后执行一下代码，同样可以编译成功[/scode]root@harbor:~/WorkShop/spksrc/spk/npc# docker exec -it spksrc /bin/bash -c 'cd /spksrc/spk/npc && make clean && cd /spksrc && make setup && cd /spksrc/spk/npc && make arch-armada37xx-6.2' root@harbor:~/WorkShop/spksrc/packages# pwd /root/WorkShop/spksrc/packages root@harbor:~/WorkShop/spksrc/packages# ls npc_armada37xx-6.2_0.26.6-1.spk[scode type="red"]下载地址:https://github.com/balckwilliam/ds119j-nps/releases[/scode]其他架构请参照https://github.com/SynoCommunity/spksrc/wiki/Compile-and-build-rules最后放一张截图
本文主要内容是搭建harbor,并且记录自己的一些踩坑记录，为以后自己方便查阅。一、harbor1.1 harbor介绍[collapse title="harbor介绍" status="true"]Harbor是VMware公司开源的企业级Docker Registry项目，其目标是帮助用户迅速搭建一个企业级的Docker私有镜像仓库。Harbor基于官方Registry V2实现，提供了管理UI，基于角色的访问控制，LDAP集成、镜像复制、以及审计日志等企业用户需求的功能。balabala...具体详情请见baidu[/collapse]二、harbor安装[scode type="blue"]harbor安装没什么太大难度，具体请看代码。[/scode]2.1 首先需要安装docker[scode type="blue"]我选择的docker版本是19.03.8，系统是ubuntu16.04[/scode]参照官方安装教程安装root@vps:~# apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common #安装依赖 root@vps:~# curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - root@vps:~# apt-key fingerprint 0EBFCD88 pub rsa4096 2017-02-22 [SCEA] 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88 uid [ unknown] Docker Release (CE deb) <docker@docker.com> sub rsa4096 2017-02-22 [S] root@vps:~# add-apt-repository \ "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable" root@vps:~# apt-get update root@vps:~# apt-get install docker-ce docker-ce-cli containerd.io #安装docker，不出意外应该可以成功安装[scode type="blue"]如果是其他系统，请参考官网文档[/scode]2.2 安装docker-composeroot@vps:~# wget https://github.com/docker/compose/releases/download/1.26.0-rc3/docker-compose-Linux-x86_64 #下载docker-compose root@vps:~# cp ./docker-compose-Linux-x86_64 /usr/local/bin/docker-compose #放入/usr/local/bin/文件夹 root@vps:~# chmod +x /usr/local/bin/docker-compose #给执行权限 root@vps:~# docker-compose --version docker-compose version 1.26.0-rc3, build 46118bc5 #查看版本2.3 接下来安装harbor[scode type="blue"]我个人使用的是在线安装harbor[/scode]root@vps:~# wget https://github.com/goharbor/harbor/releases/download/v1.10.1/harbor-online-installer-v1.10.1.tgz #下载harbor root@vps:~# tar -zxvf harbor-online-installer-v1.10.1.tgz #解压 root@vps:~# cd harbor/ #进入harbor目录 root@vps:~# vim harbor.yml #编辑harbor.yml配置文件，每个参数后都有注释，这里就不解释了 root@vps:~# ./install.sh #安装2.4 harbor安装完成了可以去登录进去看看了，默认有一个library公有仓库，可以删除，放个截图：[scode type="blue"]随心所欲修改吧。[/scode]2.5 问题如果你有问题，请跟我接着看：[scode type="blue"]caddy反代问题[/scode]那么接下来问题来了，我使用的是caddy反向代理的，但是现在不是login登录不成功就是push不成功，显示认证失败，反正是各种错误。[scode type="blue"]解决方法：[/scode]如果想要caddy反代，开启https，并且外网使用的话，那么你就得大费周章了，我是这么做的：首先再caddy下配置一下你镜像仓库的域名，并且添加自签证书，像这样[root@server ~]# cat /usr/local/caddy/Caddyfile https://你的域名 { gzip tls 你的邮箱 proxy / https://你的域名:端口号 { websocket transparent } } 并且把caddy的自签证书保存到本地，一会要用。我的caddy自签证书在这个文件夹里[root@server sites]# pwd /root/.caddy/acme/acme-v02.api.letsencrypt.org/sites配置好之后，先不要访问，这时我们需要改hosts[root@server ~]# cat /etc/hosts ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 127.0.0.1 你的域名改好hosts之后,你需要再次修改harbor.yml的配置文件，把hostname: 你的域名,并且把刚刚储存的自签证书上传到一个地方，更改公钥和key文件路径，就像一下这样# Configuration file of Harbor # The IP address or hostname to access admin UI and registry service. # DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients. hostname: 你的域名 #填写你的域名 # http related config http: # port for http, default is 80. If https enabled, this port will redirect to https port port: 80 # https related config https: # https port for harbor, default is 443 port: 443 # The path of cert and key files for nginx certificate: /root/crt/xxx.crt private_key: /root/crt/xxx.key #填写域名对应的证书文件和key文件 ... 以下省略END这样部署好，访问你的域名，就可以成功登录，上传镜像了,如下所示：root@vps:~# docker pull xxx/test/nps:0.26.5 0.26.5: Pulling from test/nps 89b6e6d4b47e: Pull complete f65b3d41449d: Pull complete Digest: sha256:d987274633ad3458f8d8437c15cd87b44f5b70145037729523b37b5943cf6e82 Status: Downloaded newer image for xxx/test/nps:0.26.5 三、备份一下命令docker tag 镜像名[:版本号] 你的仓库地址/仓库名称/镜像名称[:版本号] # 为镜像文件打tag docker push 你的仓库地址/仓库名称/镜像名称[:版本号] #上传镜像四、截图至此，完成！！！
Proxmox VE(PVE)开启IOMMO并开启网卡(82576)SRIOV功能【时隔半年，我回来了】
82576在KVM虚拟机下使用网卡的SRIOV功能直通进虚拟机，减少使用Proxmox VE虚拟网卡的NAT功能，提高网卡性能，降低CPU占用。哔哩哔哩链接：文字不带劲，来视频一、pve介绍简单来说：Proxmox VE是一个集成OPENVZ 支持KVM应用的环境。二、单根I/O虚拟化(SRIOV)介绍[collapse title="单根I/O虚拟化(SRIOV)介绍" status="false"]I/O虚拟化可以提高服务器性能和简化硬件要求，而且同时可以在服务器上增加I/O密集型虚拟机的最大数量。SR-IOV技术现已可以开始把具有SR-IOV功能的设备定义成为一种外围设备物理功能模块（PF）并且使之能与主机hypervisor系统直接相互交互信息。PF主要用于在服务器中告诉hypervisor系统关于物理PCI设备运行的状态是否可用。 SR-IOV在操作系统层，现在能在所有的外围物理设备PF下，创建不只一个的虚拟功能设备（VFs）。VFs能共享外围设备的物理资源（像网卡端口或网卡缓存空间）并且与SR-IOV服务器上的虚拟机系统进行关联。SR-IOV能允许一个物理PCIe设备把自身虚拟为多个虚拟PCIe设备。[/collapse]三、基础环境以及软件安装Proxmox VE 5.4CPU:E5645*2网卡：intel 825763.1 软件安装安装基本的软件root@pve:~# apt update root@pve:~# apt-get install net-tools vim ethtool -y\3.2 打开IOMMU在/etc/default/grub文件里，GRUB_CMDLINE_LINUX_DEFAULT行添加以下内容那么使用vim打开文件，添加如果你是intel CPU。GRUB_CMDLINE_LINUX_DEFAULT=" intel_iommu=on pcie_acs_override=downstream"如果你是AMD CPU，那么添加一下内容：GRUB_CMDLINE_LINUX_DEFAULT=" amd_iommu=on pcie_acs_override=downstream"更新grubroot@pve:~# update-grub在/etc/modules文件里添加如下内容：使用vim打开/etc/modules，添加一下内容： vfio vfio_iommu_type1 vfio_pci vfio_virqfd更新并重启root@pve:~# update-initramfs -u -k all root@pve:~# reboot3.3 检查IOMMO功能是否开启root@pve:~# dmesg | grep -e DMAR -e IOMMU -e AMD-Vi [ 1.140539] DMAR: Host address width 40 [ 1.140626] DMAR: DRHD base: 0x000000fbffe000 flags: 0x1 [ 1.140740] DMAR: dmar0: reg_base_addr fbffe000 ver 1:0 cap c90780106f0462 ecap f020f6 [ 1.140865] DMAR: RMRR base: 0x000000000ed000 end: 0x000000000effff [ 1.140959] DMAR: RMRR base: 0x000000bf7ed000 end: 0x000000bf7fffff [ 1.141053] DMAR: ATSR flags: 0x0 [ 1.141445] DMAR: dmar0: Using Queued invalidation [ 1.141552] DMAR: Setting RMRR: [ 1.141911] DMAR: Setting identity map for device 0000:00:1a.0 [0xbf7ed000 - 0xbf7fffff] [ 1.142336] DMAR: Setting identity map for device 0000:00:1a.7 [0xbf7ed000 - 0xbf7fffff] [ 1.142711] DMAR: Setting identity map for device 0000:00:1d.0 [0xbf7ed000 - 0xbf7fffff] [ 1.143088] DMAR: Setting identity map for device 0000:00:1d.1 [0xbf7ed000 - 0xbf7fffff] [ 1.144741] DMAR: Setting identity map for device 0000:00:1d.2 [0xbf7ed000 - 0xbf7fffff] [ 1.145156] DMAR: Setting identity map for device 0000:00:1d.7 [0xbf7ed000 - 0xbf7fffff] [ 1.145301] DMAR: Setting identity map for device 0000:00:1a.0 [0xed000 - 0xeffff] [ 1.145438] DMAR: Setting identity map for device 0000:00:1a.7 [0xed000 - 0xeffff] [ 1.145574] DMAR: Setting identity map for device 0000:00:1d.0 [0xed000 - 0xeffff] [ 1.145710] DMAR: Setting identity map for device 0000:00:1d.1 [0xed000 - 0xeffff] [ 1.145846] DMAR: Setting identity map for device 0000:00:1d.2 [0xed000 - 0xeffff] [ 1.145982] DMAR: Setting identity map for device 0000:00:1d.7 [0xed000 - 0xeffff] [ 1.146120] DMAR: Prepare 0-16MiB unity mapping for LPC [ 1.146460] DMAR: Setting identity map for device 0000:00:1f.0 [0x0 - 0xffffff] [ 1.146781] DMAR: Intel(R) Virtualization Technology for Directed I/O 如果出现一下内容，就代表IOMMO功能开启成功。如果没有出现，那么在/etc/modprobe.d/的任意一个.conf下增加这些内容： options vfio_iommu_type1 allow_unsafe_interrupts=13.3 部署SRIOV首先确定你的intel网卡支持SR-IOV，如果支持，那么继续root@pve:~# echo "options igb max_vfs=6,6,6,6,6,6" > /etc/modprobe.d/igb.conf root@pve:~# depmod -a root@pve:~# update-initramfs -u root@pve:~# reboot解释一下，max_vfs=6,6,6,6,6,6：1.1 这条语句说明最大的虚拟网卡是6个，也就是虚拟6个1.2 6,6···说明我这里有6个intel 82576网卡，每个网卡虚拟出6个，总共是36个虚拟网卡3.4 检查虚拟网卡root@pve:~# dmesg | grep 82576 [ 2.527369] igbvf 0000:07:10.1: Intel(R) 82576 Virtual Function [ 2.532109] igbvf 0000:07:10.3: Intel(R) 82576 Virtual Function [ 2.535583] igbvf 0000:07:10.5: Intel(R) 82576 Virtual Function [ 2.539074] igbvf 0000:07:10.7: Intel(R) 82576 Virtual Function [ 2.542565] igbvf 0000:07:11.1: Intel(R) 82576 Virtual Function [ 2.546055] igbvf 0000:07:11.3: Intel(R) 82576 Virtual Function [ 2.768637] igbvf 0000:07:10.0: Intel(R) 82576 Virtual Function [ 2.771872] igbvf 0000:07:10.2: Intel(R) 82576 Virtual Function [ 2.775080] igbvf 0000:07:10.4: Intel(R) 82576 Virtual Function [ 2.778273] igbvf 0000:07:10.6: Intel(R) 82576 Virtual Function [ 2.781481] igbvf 0000:07:11.0: Intel(R) 82576 Virtual Function [ 2.784682] igbvf 0000:07:11.2: Intel(R) 82576 Virtual Function [ 2.920650] igbvf 0000:05:10.0: Intel(R) 82576 Virtual Function [ 3.128489] igbvf 0000:05:10.2: Intel(R) 82576 Virtual Function [ 3.412422] igbvf 0000:05:10.4: Intel(R) 82576 Virtual Function [ 3.540328] igbvf 0000:05:10.6: Intel(R) 82576 Virtual Function [ 3.580426] igbvf 0000:05:11.0: Intel(R) 82576 Virtual Function [ 3.608433] igbvf 0000:05:11.2: Intel(R) 82576 Virtual Function [ 3.940652] igbvf 0000:05:10.1: Intel(R) 82576 Virtual Function [ 3.960418] igbvf 0000:05:10.3: Intel(R) 82576 Virtual Function [ 3.976422] igbvf 0000:05:10.5: Intel(R) 82576 Virtual Function [ 3.992427] igbvf 0000:05:10.7: Intel(R) 82576 Virtual Function [ 4.008435] igbvf 0000:05:11.1: Intel(R) 82576 Virtual Function [ 4.024426] igbvf 0000:05:11.3: Intel(R) 82576 Virtual Function [ 4.359456] igbvf 0000:04:10.0: Intel(R) 82576 Virtual Function [ 4.380383] igbvf 0000:04:10.2: Intel(R) 82576 Virtual Function [ 4.400395] igbvf 0000:04:10.4: Intel(R) 82576 Virtual Function [ 4.424404] igbvf 0000:04:10.6: Intel(R) 82576 Virtual Function [ 4.440398] igbvf 0000:04:11.0: Intel(R) 82576 Virtual Function [ 4.464396] igbvf 0000:04:11.2: Intel(R) 82576 Virtual Function [ 4.795365] igbvf 0000:04:10.1: Intel(R) 82576 Virtual Function [ 4.820409] igbvf 0000:04:10.3: Intel(R) 82576 Virtual Function [ 4.836453] igbvf 0000:04:10.5: Intel(R) 82576 Virtual Function [ 4.856446] igbvf 0000:04:10.7: Intel(R) 82576 Virtual Function [ 4.872448] igbvf 0000:04:11.1: Intel(R) 82576 Virtual Function [ 4.888444] igbvf 0000:04:11.3: Intel(R) 82576 Virtual Function出现VF代表成功了，那么在你的Proxmox VE 控制面板上，网络一栏就会出现很多网卡[scode type="blue"]注意把原来网卡标记一下，否则会乱的[/scode]以下是我的网卡界面截图四、【问题1】网卡直通windows不识别怎么办windows下网卡提前必须是up状态【重点】就像这样：首先在intel官方下载intel驱动【找到自己的型号】intel网卡驱动下载传送门输入如下指令：pnputil -a PRO1000\Winx64\NDIS62\v1q62x64.inf之后进行正常安装即可参考网址:https://peterkieser.com/2014/07/22/intel-e1g42et-82576-controller-sr-iov-with-windows-2008-r2-guest/五、【问题2】解决群晖不支持驱动问题下载boot文件【支持最新的6.2.2】boot文件下载地址:链接:https://pan.baidu.com/s/1vfeznXGAwvfedskn73vL5Q 提取码:cqjt 复制这段内容后打开百度网盘手机App，操作更方便哦参考网址：https://xpenology.com
因为需要4.19的内核，需要制作树莓派的img镜像，因此，就顺便编译一下树莓派3B+的内核，并制作成树莓派镜像。树莓派镜像制作一、树莓派介绍[collapse title="树莓派介绍" status="false"]树莓派由注册于英国的慈善组织“Raspberry Pi 基金会”开发，Eben·Upton/埃·厄普顿为项目带头人。2012年3月，英国剑桥大学埃本·阿普顿（Eben Epton）正式发售世界上最小的台式机，又称卡片式电脑，外形只有信用卡大小，却具有电脑的所有基本功能，这就是Raspberry Pi电脑板，中文译名"树莓派"。[/collapse]二、基础环境使用X86交叉编译需要10G的硬盘空间[scode type="blue"]本文使用的是ubuntu 16.04的系统[/scode]三、基础环境安装3.1 编译内核用的工具gcc[scode type="blue"]本文使用gcc-4.9下载地址：https://releases.linaro.org/components/toolchain/binaries/[/scode]3.1.1下载并配置gcc64位下载root@ubuntu16:~# wget https://releases.linaro.org/components/toolchain/binaries/4.9-2017.01/aarch64-linux-gnu/gcc-linaro-4.9.4-2017.01-x86_64_aarch64-linux-gnu.tar.xz # 下载64位gcc root@ubuntu16:~# mkdir ~/toolchains # 创建文件夹 root@ubuntu16:~# tar xf gcc-linaro-4.9.4-2017.01-x86_64_aarch64-linux-gnu.tar.xz -C ~/toolchains/ # 解压缩 root@ubuntu16:~# cd ~/toolchains/gcc-linaro-4.9.4-2017.01-x86_64_aarch64-linux-gnu/ root@ubuntu16:~# echo 'export PATH=${PATH}:$(pwd)/bin' > env.sh # 创建环境变量 root@ubuntu16:~# source env.sh # 让环境变量在当前终端生效32位下载root@ubuntu16:~# wget https://releases.linaro.org/components/toolchain/binaries/4.9-2017.01/aarch64-linux-gnu/gcc-linaro-4.9.4-2017.01-i686_aarch64-linux-gnu.tar.xz # 下载32位gcc root@ubuntu16:~# mkdir ~/toolchains # 创建文件夹 root@ubuntu16:~# tar xf gcc-linaro-4.9-2017.01-i686_aarch64-linux-gnu.tar.xz -C ~/toolchains/ # 解压缩 root@ubuntu16:~# cd ~/toolchains/gcc-linaro-4.9.4-2017.01-i686_aarch64-linux-gnu/ root@ubuntu16:~# echo 'export PATH=${PATH}:$(pwd)/bin' > env.sh # 配置环境变量 root@ubuntu16:~# source env.sh # 让环境变量在当前终端生效3.2 安装依赖环境[scode type="blue"]依赖环境安装[/scode]root@ubuntu16:~# sudo apt install build-essential bison make debootstrap qemu-user-static kpartx util-linux vim git libncurses5-dev bc git dosfstools -y # 安装依赖 root@ubuntu16:~# sudo service binfmt-support start # 启动binfmt服务3.3 下载内核并配置3.3.1 下载内核root@ubuntu16:~# mkdir ~/kernel root@ubuntu16:~# cd ~/kernel # 创建文件夹并进入 root@ubuntu16:~# git clone https://github.com/raspberrypi/linux.git # 下载linux内核3.4 下载树莓派的启动文件root@ubuntu16:~# git clone --depth=1 https://github.com/raspberrypi/firmware/ # git上下载boot文件3.5 配置树莓派固件wifi固件树莓派3b1.1 固件1：brcmfmac43430-sdio.binroot@ubuntu16:~# mkdir ~/wifi root@ubuntu16:~# cd ~/wifi # 新建文件夹并进入 root@ubuntu16:~# wget https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/tree/brcm/brcmfmac43430-sdio.bin # 下载brcmfmac43430-sdio.bin1.2 固件2：brcmfmac43430-sdio.txtroot@ubuntu16:~# wget https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/tree/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.txt # 下载tree/brcm/brcmfmac43430-sdio.raspberrypi,3-model-b.txt root@ubuntu16:~# cp brcmfmac43430-sdio.raspberrypi,3-model-b.txt brcmfmac43430-sdio.txt # 复制并改名树莓派3b+2.1 固件1：brcmfmac43455-sdio.binroot@ubuntu16:~# mkdir ~/wifi root@ubuntu16:~# cd ~/wifi # 创建文件夹并进入 root@ubuntu16:~# https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/tree/brcm/brcmfmac43455-sdio.bin # 下载brcmfmac43455-sdio.bin2.2 固件2：brcmfmac43455-sdio.txtroot@ubuntu16:~# https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git/tree/brcm/brcmfmac43455-sdio.raspberrypi,3-model-b-plus.txt # 下载brcmfmac43455-sdio.raspberrypi,3-model-b-plus.txt root@ubuntu16:~# cp brcmfmac43455-sdio.raspberrypi,3-model-b-plus.txt brcmfmac43455-sdio.txt # 复制并改名蓝牙固件树莓派3b1.1 固件：BCM43430A1.hcdroot@ubuntu16:~# mkdir ~/bluetooth root@ubuntu16:~# cd ~/bluetooth root@ubuntu16:~# wget https://github.com/RPi-Distro/bluez-firmware/raw/master/broadcom/BCM43430A1.hcd树莓派3b+2.1 固件：BCM4345C0.hcdroot@ubuntu16:~# mkdir ~/bluetooth root@ubuntu16:~# cd ~/bluetooth root@ubuntu16:~# wget https://github.com/RPi-Distro/bluez-firmware/raw/master/broadcom/BCM4345C0.hcd四、编译内核4.1 检查环境变量root@ubuntu16:~# which aarch64-linux-gnu-gcc4.2 创建内核编译环境变量并检查root@ubuntu16:~# echo 'export ARCH=arm64' >> env.sh root@ubuntu16:~# echo 'export CROSS_COMPILE=aarch64-linux-gnu-' >> env.sh # 创建环境变量 root@ubuntu16:~# source env.sh # 初始化环境变量 root@ubuntu16:~# echo "${ARCH}" # 检查ARCH root@ubuntu16:~# echo "${CROSS_COMPILE}" # 检查CROSS_COMPILE # 如果ARCH为arm64，CROSS_COMPILE为aarch64-linux-gnu-，环境配置正确4.3 编译内核4.3.1 内核编译1root@ubuntu16:~# cd ~/kernel/linux root@ubuntu16:~# make bcmrpi3_defconfig # 生成默认配置文件 root@ubuntu16:~# make menuconfig # 修改默认配置文件4.3.2 内核配置默认为配置如下图所示：如果需要虚拟化支持，请选择[*] Virtualization --- [*] Kernel-based Virtual Machine (KVM) support <M> Host kernel accelerator for virtio net [*] Cross-endian support for vhost # 在Virtualization下的所有选项选中 # 然后按住左右按键找到save保存一下如果需要cgroup支持，请选择General setup ---> -*- Control Group support ---> [*] Memory controller [*] Swap controller [*] Swap controller enabled by default (NEW) [*] IO controller -*- CPU controller ---> [*] CPU bandwidth provisioning for FAIR_GROUP_SCHED [*] Group scheduling for SCHED_RR/FIFO [*] PIDs controller [*] RDMA controller [*] Freezer controller [*] Cpuset controller [*] Include legacy /proc/<pid>/cpuset file [*] Device controller [*] Simple CPU accounting controller 如果需要Checkpoint/restore支持，请选择：[scode type="blue"]使用CRIU必须开启[/scode]General setup ---> [*] Checkpoint/restore support 在内核里选择你需要的驱动例如usb网卡驱动Device Drivers ---> [*] Network device support ---> [*] Wireless LAN ---> [*] Realtek devices <M> Realtek 8187 and 8187B USB support <M> Realtek rtlwifi family of devices ---> <M> Realtek 8192C USB WiFi <M> RTL8723AU/RTL8188[CR]U/RTL819[12]CU (mac80211) support [scode type="blue"]选择完成后选择save后按exit钮退出即可[/scode]4.3.3 内核编译root@ubuntu16:~# make -j 4 # 编译内核五、制作rootfs首先创建rootfs文件夹root@ubuntu16:~# mkdir ~/rootfs/[scode type="blue"]请从以下地址选取rootfs[/scode][collapse title="基本rootfs镜像" status="false"]Archlinux 64 位官方rootfs:http://mirrors.ustc.edu.cn/archlinuxarm/os/ArchLinuxARM-aarch64-latest.tar.gzGentoo 64 位官方rootfs:http://distfiles.gentoo.org/experimental/arm64/Ubuntu 64 位官方rootfs:xenial:http://mirrors.ustc.edu.cn/ubuntu-cdimage/ubuntu-base/releases/xenial/release/ubuntu-base-16.04.5-base-arm64.tar.gzbionic:http://mirrors.ustc.edu.cn/ubuntu-cdimage/ubuntu-base/releases/bionic/release/ubuntu-base-18.04.1-base-arm64.tar.gz[/collapse]我选取的位ubuntu16.04版本root@ubuntu16:~# mkdir -p ~/rootfs/4.19/ # 创建文件夹 root@ubuntu16:~# cd rootfs/4.19 # 进入 root@ubuntu16:~# cp /usr/bin/qemu-aarch64-static usr/bin # 拷贝qemu-aarch64-static到usr/bin挂载文件并进入root@ubuntu16:~# sudo mount -o bind /dev/ dev root@ubuntu16:~# sudo mount -o bind /dev/pts dev/pts root@ubuntu16:~# sudo mount -o bind /proc proc root@ubuntu16:~# sudo mount -o bind /sys sys root@ubuntu16:~# $ sudo cp etc/hosts etc/hosts.bak # 备份原HOSTS root@ubuntu16:~# sudo cp etc/resolv.conf etc/resolv.conf.bak #备份原resolv.conf root@ubuntu16:~# sudo cp /etc/hosts etc/hosts # 复制系统文件 root@ubuntu16:~# sudo cp /etc/resolv.conf etc/resolv.conf # 复制系统文件 root@ubuntu16:~# sudo chroot ./ /bin/sh # 使用chroot进入安装依赖chroot # # 进入后 chroot # passwd root # 更改密码，注意登入需要 chroot # apt update # 更新 chroot # apt install vim wpasupplicant net-tools openssh-server bash-com* netbase iproute2 iputils-ping git # 安装依赖 chroot # exit # 退出卸载文件系统并恢复网络配置root@ubuntu16:~# sudo umount dev/pts root@ubuntu16:~# sudo umount dev root@ubuntu16:~# sudo umount proc root@ubuntu16:~# sudo umount sys # 卸载文件系统 root@ubuntu16:~# sudo mv etc/hosts.bak etc/hosts root@ubuntu16:~# sudo mv etc/resolv.conf.bak etc/resolv.conf # 恢复网络配置计算一下rootfs大小root@ubuntu16:~# sudo du -sh ./ # 有用镜像大小计算公式镜像大小= rootfs大小 + 300M + 200M五、制作镜像文件5.1 分区[scode type="blue"]首先创建images文件[/scode]创建images文件root@ubuntu16:~# mkdir ~/images/ root@ubuntu16:~# cd ~/images/ root@ubuntu16:~# dd bs=1M count=545 if=/dev/zero of=ubuntu.img # 根据镜像大小计算公式计算出的镜像大小来创建，本文创建的大小位545M[scode type="blue"]为镜像分区[/scode]为镜像分区root@ubuntu16:~# cfdisk ubuntu.img[scode type="blue"]分区表选择dos[/scode]分区表选择dos ┌ Select label type ───┐ │ gpt │ │ dos * │ │ sgi │ │ sun │ └──────────────────────┘ Device does not contain a recognized partition table. Select a type to create a new label or press 'L' to load script file. [scode type="blue"]选 new 创建分区,分区大小为100M，并且选择主分区，分区如下所示[/scode] Disk: ubuntu.img Size: 545 MiB, 571473920 bytes, 1116160 sectors Label: dos, identifier: 0xd5f9f036 Device Boot Start End Sectors Size Id Type >> ubuntu.img1 * 2048 206847 204800 100M 83 Linux Free space 206848 1116159 909312 444M ┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ │Partition type: Linux (83) │ │ Attributes: 80 │ └──────────────────────────────────────────────────────────────────────────────────────────────────┘ [ * Bootable] [ Delete ] [ Resize ] [ Quit ] [ Type ] [ Help ] [ Write ] [ Dump ][scode type="blue"]选bootable使刚才创建的分区设定为可启动,Boot下，有一个星号代表设置完成[/scode] Device Boot Start End Sectors Size Id Type >> ubuntu.img1 * 2048 206847 204800 100M 83 Linux Free space 206848 1116159 909312 444M [scode type="blue"]选“type”改变分区标识,找到“W95 FAT32” 并回车，成功之后“Id Type”显示“b W95 FAT32”字样[/scode] Device Boot Start End Sectors Size Id Type >> ubuntu.img1 * 2048 206847 204800 100M 83 Linux b W95 FAT32 Free space 206848 1116159 909312 444M [scode type="blue"]按方向健下，切换到空余空间.选择new新健一个分区，分区使用所有空余空间，选择primary 主分区，成功之后如下显示[/scode] Device Boot Start End Sectors Size Id Type >> ubuntu.img1 * 2048 206847 204800 100M 83 Linux b W95 FAT32 ubuntu.img2 206848 1116159 909312 444M [scode type="blue"]选择Write 将更改写入镜像[/scode]5.2 写入内容到img文件5.2.1用kpartx挂载镜像到looproot@ubuntu16:~/images# kpartx -av ubuntu.img add map loop0p1 (252:2): 0 204800 linear 7:0 2048 add map loop0p2 (252:3): 0 909312 linear 7:0 206848 # 输出的信息就是需要挂载的文件 # loop0p1 为第一个分区 # loop0p2 为第二个分区5.2.2 格式化分区格式化为ext4root@ubuntu16:~/images# sudo mkfs.vfat /dev/mapper/loop0p1 # 格式化第一个分区 root@ubuntu16:~/images# sudo mkfs.ext4 /dev/mapper/loop0p2 # 格式化第二个分区格式化为f2fs【如果你想把第二个分区格式化为f2fs】root@ubuntu16:~/images# sudo apt install f2fs-tools root@ubuntu16:~/images# sudo mkfs.f2fs -f /dev/mapper/loop0p2 # 格式化第二个分区5.2.3挂载镜像root@ubuntu16:~/images# sudo mkdir /mnt/loop0p1 # 创建文件夹 root@ubuntu16:~/images# sudo mount /dev/mapper/loop0p1 /mnt/loop0p1 # 挂载loop0p1 root@ubuntu16:~/images# sudo mkdir /mnt/loop0p2 # 创建文件夹 root@ubuntu16:~/images# sudo mount /dev/mapper/loop0p2 /mnt/loop0p2 # 挂载loop0p25.2.4向镜像里写入内容[scode type="blue"]向第一个分区写入内容[/scode]向第一个分区写入内容[scode type="blue"]本节内容是向第一个分区写入启动引导。[/scode]为第一个分区写入内容root@ubuntu16:~/images# cp -r /root/firmware/boot/* /mnt/loop0p1/ # 复制boot文件到loop0p1 root@ubuntu16:~/images# cd ~/kernel/linux/ # 进入编译的内核的工作目录 root@ubuntu16:~/kernel/linux# sudo cp ./arch/arm64/boot/Image /mnt/loop0p1/kernel8.img # 复制编译好的内核 root@ubuntu16:~/kernel/linux# sudo mv /mnt/loop0p1/bcm2710-rpi-3-b.dtb /mnt/loop0p1/bcm2710-rpi-3-b.dtb_32 root@ubuntu16:~/kernel/linux# sudo mv /mnt/loop0p1/bcm2710-rpi-3-b-plus.dtb /mnt/loop0p1/bcm2710-rpi-3-b-plus.dtb_32 # 将32位的dtb文件更名 root@ubuntu16:~/kernel/linux# sudo cp ./arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dtb /mnt/loop0p1/bcm2710-rpi-3-b.dtb root@ubuntu16:~/kernel/linux# sudo cp ./arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b-plus.dtb /mnt/loop0p1/bcm2710-rpi-3-b-plus.dtb # 复制64位的dtb文件写cmdline.txtroot@ubuntu16:~/kernel/linux# sudo vim /mnt/loop0p1/cmdline.txt写入内容root=/dev/sda2 rootfstype=ext4 rw rootwait fsck.repair=yes program_usb_boot_mode=1[scode type="blue"]注意：“program_usb_boot_mode=1”为u盘启动，如果内存卡启动请删除“program_usb_boot_mode=1”这行代码[/scode]其他说明1.1 root=/dev/sda2 将第二分区设置为根分区1.2 rootfstype=ext4 根分区类型 f2fs应更换为f2fs1.3 rw 可写挂载跟分区1.4 rootwait 等待内核识别根分区设备后再挂载1.5 fsck.repair=yes 启动时自动检查修复文件系统错误[scode type="blue"]向第二个分区写入内容[/scode]向第二个分区写入内容[scode type="blue"]本节主要内容是安装内核模块、固件和rootfs系统到镜像的第二个分区。[/scode]1.安装内核模块root@ubuntu16:~/kernel/linux# cd ~/kernel/linux #进入编译内核的工作目录 root@ubuntu16:~/kernel/linux# make modules_install INSTALL_MOD_PATH=/mnt/loop0p22.放入wifi和蓝牙固件root@ubuntu16:~/kernel/linux# mkdir -p /mnt/loop0p2/lib/firmware/brcm root@ubuntu16:~/kernel/linux# cp -r ~/wifi/* /mnt/loop0p2/lib/firmware/brcm/ root@ubuntu16:~/kernel/linux# cp -r ~/bluetooth/* /mnt/loop0p2/lib/firmware/brcm/ root@ubuntu16:~/kernel/linux# sync # 同步写入3.写入rootfs系统root@ubuntu16:~# sudo apt install rsync # 安装rsync同步工具 root@ubuntu16:~# sudo rsync -HPavz -q ./ /mnt/loop0p2 # 使用rsync同步到loop0p2分区 root@ubuntu16:~# sync # 同步一下4.卸载分区root@ubuntu16:~/kernel/linux# sudo umount /dev/mapper/loop0p1 root@ubuntu16:~/kernel/linux# sudo umount /dev/mapper/loop0p2 root@ubuntu16:~/kernel/linux# sudo kpartx -dv /dev/loop0 root@ubuntu16:~/kernel/linux# sudo losetup -d /dev/loop0六、测试[scode type="blue"]请自己进行测试，如果按照我的步骤来，应该不会出错，本人已经实验完成。[/scode]