Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions config/org.deepin.dde.network.json
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,17 @@
"description":"if check the network is unavailable and then switch to the available network",
"permissions":"readwrite",
"visibility":"private"
},
"reapplyFlags":{
"value":2,
"serial":0,
"flags":["global"],
"name":"reapplyFlags",
"name[zh_CN]":"reapplyConnection标志位",
"description[zh_CN]":"reapplyConnection的标志位,只能是0、1、2三个值之一。0:无标志;1:保留外部配置(PRESERVE_EXTERNAL);2:非阻塞(NONBLOCKING)",
"description":"Flags for reapplyConnection, must be 0, 1 or 2. 0: none; 1: PRESERVE_EXTERNAL; 2: NONBLOCKING",
"permissions":"readwrite",
"visibility":"private"
}
}
}
73 changes: 37 additions & 36 deletions network-service-plugin/src/system/connectivitychecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,10 @@ void LocalConnectionvityChecker::onConnectivityChanged(network::service::Connect
if (m_internetChecker && (connectivity == network::service::Connectivity::Limited
|| connectivity == network::service::Connectivity::Noconnectivity
|| connectivity == network::service::Connectivity::Unknownconnectivity)) {
// 在开启网络检测的情况下,如果当前网络不可用,则启动切换主连接的工作流程,尝试恢复网络连通性。
QMetaObject::invokeMethod(m_internetChecker, &InternetChecker::switchInternetAccess, Qt::QueuedConnection);
bool checkPrimary = m_statusChecker->primaryConnectionChanged();
QMetaObject::invokeMethod(m_internetChecker, [this, checkPrimary]() {
m_internetChecker->switchInternetAccess(checkPrimary);
}, Qt::QueuedConnection);
}
qCInfo(DSM) << "Connectivity changed, incomming: " << static_cast<int>(connectivity) << ", current: " << static_cast<int>(m_connectivity);
if (m_connectivity == connectivity)
Expand All @@ -115,6 +117,7 @@ StatusChecker::StatusChecker(QObject *parent)
, m_connectivity(network::service::Connectivity::Unknownconnectivity)
, m_checkCount(0)
, m_isStop(false)
, m_primaryConnectionChanged(false)
{
qRegisterMetaType<NetworkManager::ActiveConnection::State>("NetworkManager::ActiveConnection::State");
qRegisterMetaType<NetworkManager::Device::State>("NetworkManager::Device::State");
Expand Down Expand Up @@ -179,6 +182,11 @@ QString StatusChecker::detectionConnectionId() const
return m_primaryId;
}

bool StatusChecker::primaryConnectionChanged() const
{
return m_primaryConnectionChanged;
}

void StatusChecker::initDeviceConnect(const QList<QSharedPointer<NetworkManager::Device>> &devices)
{
for (const QSharedPointer<NetworkManager::Device> &device : devices) {
Expand Down Expand Up @@ -234,6 +242,12 @@ void StatusChecker::initConnectivityChecker()
void StatusChecker::stop()
{
m_isStop = true;
if (m_checkTimer && m_checkTimer->isActive())
m_checkTimer->stop();
if (m_timer && m_timer->isActive())
m_timer->stop();
if (m_pendingCheckTimer && m_pendingCheckTimer->isActive())
m_pendingCheckTimer->stop();
}

void StatusChecker::onUpdataActiveState(const QSharedPointer<NetworkManager::ActiveConnection> &networks)
Expand Down Expand Up @@ -261,17 +275,11 @@ void StatusChecker::realStartCheck()
return;
}

NetworkManager::ActiveConnection::Ptr pConnection = NetworkManager::primaryConnection();
if (!pConnection.isNull()) {
m_primaryId = pConnection->connection()->uuid();
m_checkStartPrimaryId = m_primaryId;
} else {
m_checkStartPrimaryId.clear();
}
NetworkManager::ActiveConnection::Ptr prePrimary = NetworkManager::primaryConnection();
QString prePrimaryId = !prePrimary.isNull() ? prePrimary->connection()->uuid() : QString();

int httpTimeout = SettingConfig::instance()->httpRequestTimeout();
bool networkIsOk = false;
network::service::Connectivity detectedConnectivity = m_connectivity;
QString detectedPortalUrl;
for (const QString &url : m_checkUrls) {
network::service::HttpManager http;
network::service::HttpReply *httpReply = http.get(url, httpTimeout);
Expand All @@ -280,41 +288,35 @@ void StatusChecker::realStartCheck()
break;
}
if (httpReply->isTimeout()) {
qCWarning(DSM) << "check network time out, network is unavaibled ";
detectedConnectivity = network::service::Connectivity::Limited;
// 如果是读取超时了,则无需进行第二次检测,一般超时的情况下基本上都是网络不通
qCWarning(DSM) << "request network timeout";
break;
}
int httpCode = httpReply->httpCode();
QString portalUrl = httpReply->portal();
if (httpCode == 0) {
qCWarning(DSM) << "Nework is unreachabel:" << url << httpReply->errorMessage();
detectedConnectivity = network::service::Connectivity::Limited;
continue;
}

QString portalUrl = httpReply->portal();
networkIsOk = true;
qCDebug(DSM) << "Http reply code:" << httpCode << ", portal url:" << portalUrl;
detectedPortalUrl = portalUrl;
detectedConnectivity = portalUrl.isEmpty() ? network::service::Connectivity::Full : network::service::Connectivity::Portal;
if (portalUrl.isEmpty()) {
// if the portal is empty, I think it ok
setConnectivity(network::service::Connectivity::Full);
} else {
setConnectivity(network::service::Connectivity::Portal);
}
setPortalUrl(portalUrl);
break;
}
if (m_isStop)
return;

// 主连接在检查期间可能已被 InternetChecker 切换,此时检测结果基于旧路由,必须丢弃
NetworkManager::ActiveConnection::Ptr currentPrimary = NetworkManager::primaryConnection();
QString currentPrimaryId;
if (!currentPrimary.isNull())
currentPrimaryId = currentPrimary->connection()->uuid();
if (m_checkStartPrimaryId != currentPrimaryId) {
qCInfo(DSM) << "Primary connection changed during check, discarding stale result"
<< "check:" << m_checkStartPrimaryId << "current:" << currentPrimaryId;
return;
// HTTP 检查完成后,再获取当前主连接,以检测检查期间是否发生了连接切换
NetworkManager::ActiveConnection::Ptr pConnection = NetworkManager::primaryConnection();
if (!pConnection.isNull()) {
m_primaryId = pConnection->connection()->uuid();
}

if (networkIsOk) {
setConnectivity(detectedConnectivity);
setPortalUrl(detectedPortalUrl);
} else {
m_primaryConnectionChanged = (prePrimaryId != m_primaryId);
if (!m_isStop && !networkIsOk) {
NetworkManager::Device::List devices = NetworkManager::networkInterfaces();
int disconnectCount = 0;
for (NetworkManager::Device::Ptr device : devices) {
Expand All @@ -324,13 +326,12 @@ void StatusChecker::realStartCheck()
}
}
qCDebug(DSM) << "Network is unreachabel, disconnect count:" << disconnectCount;
setPortalUrl(QString());
if (disconnectCount == devices.size()) {
// 如果所有的网络设备都断开了,就默认让其变为断开的状态
setConnectivity(network::service::Connectivity::Noconnectivity);
} else {
setConnectivity(network::service::Connectivity::Limited);
}
setPortalUrl(QString());
}
}

Expand Down
3 changes: 2 additions & 1 deletion network-service-plugin/src/system/connectivitychecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class StatusChecker : public QObject
QString portalUrl() const;
void checkConnectivity();
QString detectionConnectionId() const;
bool primaryConnectionChanged() const;

signals:
void portalDetected(const QString &);
Expand Down Expand Up @@ -113,7 +114,7 @@ private slots:
QStringList m_checkUrls;
bool m_isStop;
QString m_primaryId;
QString m_checkStartPrimaryId;
bool m_primaryConnectionChanged;
};

class NMConnectionvityChecker : public ConnectivityChecker
Expand Down
29 changes: 23 additions & 6 deletions network-service-plugin/src/system/internetchecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
#include "settingconfig.h"
#include "constants.h"

#include <QDebug>

Check warning on line 11 in network-service-plugin/src/system/internetchecker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QDebug> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QTimer>

Check warning on line 12 in network-service-plugin/src/system/internetchecker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QTimer> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QThread>
#include <QElapsedTimer>

Check warning on line 13 in network-service-plugin/src/system/internetchecker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QElapsedTimer> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QEventLoop>

Check warning on line 14 in network-service-plugin/src/system/internetchecker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QEventLoop> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <NetworkManagerQt/Manager>

Check warning on line 16 in network-service-plugin/src/system/internetchecker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <NetworkManagerQt/Manager> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <NetworkManagerQt/ActiveConnection>

Check warning on line 17 in network-service-plugin/src/system/internetchecker.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <NetworkManagerQt/ActiveConnection> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <NetworkManagerQt/Ipv4Setting>
#include <NetworkManagerQt/Ipv6Setting>
#include <NetworkManagerQt/Settings>
Expand Down Expand Up @@ -79,8 +79,9 @@
}
if (changed) {
conn->updateUnsaved(settingsMap);
if (!device.isNull())
device->reapplyConnection(conn->settings()->toMap(), 0, 0);
if (!device.isNull()) {
device->reapplyConnection(conn->settings()->toMap(), 0, SettingConfig::instance()->reapplyFlags());
}
qCInfo(DSM) << "Set connection" << conn->name() << "never-default to" << neverDefault;
}
return changed;
Expand Down Expand Up @@ -132,7 +133,11 @@
int httpTimeout = SettingConfig::instance()->httpRequestTimeout();
for (int i = 0; i < maxRetry; ++i) {
// 每次检测前等待1秒,让NM路由表和DHCP先稳定
QThread::sleep(1);
{
QEventLoop loop;
QTimer::singleShot(1000, &loop, &QEventLoop::quit);
loop.exec();
}
bool timedOut = false;
// 首次使用20秒超时,避免在无网线路由器的环境中长时间阻塞
QElapsedTimer timer;
Expand Down Expand Up @@ -216,14 +221,27 @@
}
}

void InternetChecker::switchInternetAccess()
void InternetChecker::switchInternetAccess(bool checkPrimaryConnection)
{
// 正在切换中,忽略新的切换请求
if (m_isSwitching) {
qCDebug(DSM) << "current is switching";
return;
}

qCDebug(DSM) << "need check primary connection" << checkPrimaryConnection;
// 主连接在检测期间发生变化,需要重新检查当前主连接是否可上网
NetworkManager::ActiveConnection::Ptr primaryConnection = NetworkManager::primaryConnection();
if (checkPrimaryConnection && !primaryConnection.isNull()) {
int httpTimeout = SettingConfig::instance()->httpRequestTimeout();
bool isTimeout = false;
if (checkInternetAccessible(httpTimeout, isTimeout)) {
qCInfo(DSM) << "Primary connection is accessible, skip switching";
emit switchSuccess();
return;
}
}

m_isSwitching = true;
NetworkManager::Device::List availableDevice;
NetworkManager::Device::List devices = NetworkManager::networkInterfaces();
Expand All @@ -244,7 +262,6 @@
return;
}

NetworkManager::ActiveConnection::Ptr primaryConnection = NetworkManager::primaryConnection();
if (primaryConnection.isNull()) {
qCWarning(DSM) << "primary connection is null, it will reset all never default";
resetAllNeverDefault();
Expand Down
2 changes: 1 addition & 1 deletion network-service-plugin/src/system/internetchecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class InternetChecker : public QObject
public:
explicit InternetChecker(QObject *parent = nullptr);
~InternetChecker() override;
void switchInternetAccess();
void switchInternetAccess(bool checkPrimaryConnection = false);

signals:
void switchSuccess();
Expand Down
37 changes: 23 additions & 14 deletions network-service-plugin/src/utils/httpmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

#include <QRegularExpression>
#include <QDebug>
#include <QUrl>

Check warning on line 11 in network-service-plugin/src/utils/httpmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QUrl> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <mutex>

Check warning on line 13 in network-service-plugin/src/utils/httpmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <mutex> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <atomic>

Check warning on line 14 in network-service-plugin/src/utils/httpmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <atomic> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <curl/curl.h>

Check warning on line 15 in network-service-plugin/src/utils/httpmanager.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <curl/curl.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
Expand All @@ -23,34 +24,40 @@
namespace {

struct GetAddrInfoParams {
const char *node;
const char *service;
std::string node;
std::string service;
const addrinfo *hints;
addrinfo *result = nullptr;
int ret = 0;
std::atomic<bool> delete_by_thread{false};
};

static void *getaddrinfo_thread(void *arg)
{
auto *params = static_cast<GetAddrInfoParams *>(arg);
params->ret = getaddrinfo(params->node, params->service, params->hints, &params->result);
GetAddrInfoParams *params = static_cast<GetAddrInfoParams *>(arg);
params->ret = getaddrinfo(params->node.c_str(), params->service.c_str(), params->hints, &params->result);
// deleteByThread 为 true 说明主线程已 detach,由本线程释放 params
if (params->delete_by_thread.load())
delete params;
return nullptr;
}

// 在独立线程中执行 getaddrinfo,主线程等待 timeoutMs 毫秒
// 如果超时返回 ETIMEDOUT,成功返回 0
static int getaddrinfo_with_timeout(const char *node, const char *service,
static int getaddrinfo_with_timeout(const std::string &node, const std::string &service,
const addrinfo *hints, addrinfo **result,
int timeoutMs)
{
GetAddrInfoParams params = {node, service, hints, nullptr, 0};
// 在堆上分配 params,防止 detach 的线程在超时后写入已释放的栈内存
GetAddrInfoParams *params = new GetAddrInfoParams{node, service, hints, nullptr, 0};
pthread_t thread;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

if (pthread_create(&thread, &attr, getaddrinfo_thread, &params) != 0) {
if (pthread_create(&thread, &attr, getaddrinfo_thread, params) != 0) {
pthread_attr_destroy(&attr);
delete params;
return EAI_SYSTEM;
}
pthread_attr_destroy(&attr);
Expand All @@ -67,13 +74,16 @@
void *threadRet = nullptr;
int joinRet = pthread_timedjoin_np(thread, &threadRet, &ts);
if (joinRet == ETIMEDOUT) {
// 线程超时,detach 让其自行结束(getaddrinfo 最终会返回)
// 通知子线程由其负责释放 params
params->delete_by_thread.store(true);
pthread_detach(thread);
return ETIMEDOUT;
}

*result = params.result;
return params.ret;
*result = params->result;
int ret = params->ret;
delete params;
return ret;
}

} // anonymous namespace
Expand Down Expand Up @@ -196,11 +206,10 @@

// getaddrinfo 本身会调用系统 DNS,如果路由器不通,glibc 会等待很久
// 用独立线程包装,只等待 timeoutSec 秒
// 注意:必须用局部变量持有 std::string,确保 getaddrinfo_with_timeout
// 返回前字符串生命周期有效,否则子线程会使用悬垂指针
// std::string 存入 GetAddrInfoParams 后独立持有数据,不依赖栈生命周期
std::string hostStd = host.toStdString();
int ret = getaddrinfo_with_timeout(hostStd.c_str(),
nullptr,
int ret = getaddrinfo_with_timeout(hostStd,
{},
&hints,
&result,
timeoutSec * 1000);
Expand Down
17 changes: 17 additions & 0 deletions network-service-plugin/src/utils/settingconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ bool SettingConfig::needCheckNetwork() const
return m_needCheckNetwork;
}

int SettingConfig::reapplyFlags() const
{
return m_reapplyFlags;
}

void SettingConfig::onValueChanged(const QString &key)
{
if (key == "reconnectIfIpConflicted") {
Expand Down Expand Up @@ -120,6 +125,9 @@ void SettingConfig::onValueChanged(const QString &key)
m_protalProcessMode = dConfig->value("portalProcessMode").toString();
} else if (key == QString("needCheckNetwork")) {
m_needCheckNetwork = dConfig->value("needCheckNetwork").toBool();
} else if (key == QString("reapplyFlags")) {
int val = dConfig->value("reapplyFlags").toInt();
m_reapplyFlags = (val >= 0 && val <= 2) ? val : 2;
}
}

Expand All @@ -134,6 +142,7 @@ SettingConfig::SettingConfig(QObject *parent)
, m_disableFailureNotify(false)
, m_resetWifiOSDEnableTimeout(300)
, m_needCheckNetwork(true)
, m_reapplyFlags(2)
{
if (!dConfig)
dConfig = Dtk::Core::DConfig::create("org.deepin.dde.network", "org.deepin.dde.network");
Expand Down Expand Up @@ -179,6 +188,14 @@ SettingConfig::SettingConfig(QObject *parent)
m_needCheckNetwork = dConfig->value("needCheckNetwork").toBool();

m_disableFailureNotify = dConfig->value("disableFailureNotify", false).toBool();

if (keys.contains("reapplyFlags")) {
int val = dConfig->value("reapplyFlags").toInt();
if (val >= 0 && val <= 2)
m_reapplyFlags = val;
else
m_reapplyFlags = 2;
}
}
if (m_networkUrls.isEmpty())
m_networkUrls = CheckUrls;
Expand Down
Loading
Loading