diff --git a/client/core/networkUtilities.cpp b/client/core/networkUtilities.cpp index 7ffd4c41..aff08bc1 100644 --- a/client/core/networkUtilities.cpp +++ b/client/core/networkUtilities.cpp @@ -29,6 +29,13 @@ #include #include #include + #include + #include + #include + #include + #include + #include + #include #endif #include @@ -460,3 +467,112 @@ QString NetworkUtilities::getGatewayAndIface() return gateway; #endif } + +#if defined(Q_OS_MAC) +QString NetworkUtilities::ipAddressByInterfaceName(const QString &interfaceName) +{ + struct ifaddrs *ifaddr, *ifa; + char host[NI_MAXHOST]; + + if (getifaddrs(&ifaddr) == -1) + { + return ""; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == NULL) + { + continue; + } + int family = ifa->ifa_addr->sa_family; + QString iname = QString::fromStdString(ifa->ifa_name); + + if (family == AF_INET && iname == interfaceName) + { + int s = getnameinfo(ifa->ifa_addr, + (family == AF_INET) ? sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6), + host, NI_MAXHOST, + NULL, 0, NI_NUMERICHOST); + if (s != 0) + { + continue; + } + + return QString::fromStdString(host); + } + } + + freeifaddrs(ifaddr); + return ""; +} + +QString NetworkUtilities::lastConnectedNetworkInterfaceName() +{ + QString ifname(""); + + struct ifaddrs * interfaces = NULL; + struct ifaddrs * temp_addr = NULL; + + if( getifaddrs(&interfaces) == 0 ) + { + //Loop through linked list of interfaces + temp_addr = interfaces; + while( temp_addr != NULL ) + { + if( temp_addr->ifa_addr->sa_family == AF_INET ) + { + QString tname = temp_addr->ifa_name; + if( tname.startsWith("utun") ) + ifname = tname; + else if( tname.startsWith("ipsec") ) + ifname = tname; + else if( tname.startsWith("ppp") ) + ifname = tname; + } + + temp_addr = temp_addr->ifa_next; + } + + freeifaddrs(interfaces); + } + return ifname; +} + +QString execCmd(const QString &cmd) +{ + char buffer[1024]; + QString result = ""; + FILE* pipe = popen(cmd.toStdString().c_str(), "r"); + if (!pipe) return ""; + while (!feof(pipe)) + { + if (fgets(buffer, 1024, pipe) != NULL) + { + result += buffer; + } + } + pclose(pipe); + return result; +} + +QStringList NetworkUtilities::getListOfDnsNetworkServiceEntries() +{ + QStringList result; + QString command = "echo 'list' | scutil | grep /Network/Service | grep DNS"; + QString cmdOutput = execCmd(command).trimmed(); + // qDebug() << "Raw result: " << cmdOutput; + + QStringList lines = cmdOutput.split('\n'); + for (QString line : lines) + { + if (line.contains("=")) + { + QString entry = line.mid(line.indexOf("=")+1).trimmed(); + result.append(entry); + } + } + return result; +} +#endif diff --git a/client/core/networkUtilities.h b/client/core/networkUtilities.h index 3057b852..2c5c7109 100644 --- a/client/core/networkUtilities.h +++ b/client/core/networkUtilities.h @@ -18,19 +18,24 @@ public: static QString getGatewayAndIface(); // Returns the Interface Index that could Route to dst static int AdapterIndexTo(const QHostAddress& dst); - + static QRegularExpression ipAddressRegExp(); static QRegularExpression ipAddressPortRegExp(); static QRegExp ipAddressWithSubnetRegExp(); static QRegExp ipNetwork24RegExp(); static QRegExp ipPortRegExp(); static QRegExp domainRegExp(); - + static QString netMaskFromIpWithSubnet(const QString ip); static QString ipAddressFromIpWithSubnet(const QString ip); - + static QStringList summarizeRoutes(const QStringList &ips, const QString cidr); - + +#if defined(Q_OS_MAC) + static QString ipAddressByInterfaceName(const QString &interfaceName); + static QString lastConnectedNetworkInterfaceName(); + static QStringList getListOfDnsNetworkServiceEntries(); +#endif }; #endif // NETWORKUTILITIES_H diff --git a/client/protocols/ikev2_vpn_protocol_mac.h b/client/protocols/ikev2_vpn_protocol_mac.h index 8d2e52f1..b2ef4dcf 100644 --- a/client/protocols/ikev2_vpn_protocol_mac.h +++ b/client/protocols/ikev2_vpn_protocol_mac.h @@ -20,7 +20,6 @@ public: bool connect_to_vpn(const QString & vpn_name); bool disconnect_vpn(); void closeWindscribeActiveConnection(); - ErrorCode start() override; void stop() override; @@ -30,31 +29,15 @@ private slots: void handleNotificationImpl(int status); private: - enum {STATE_DISCONNECTED, STATE_START_CONNECT, STATE_START_DISCONNECTING, STATE_CONNECTED, STATE_DISCONNECTING_AUTH_ERROR, STATE_DISCONNECTING_ANY_ERROR}; - - int state_; - - bool bConnected_; mutable QRecursiveMutex mutex_; void *notificationId_; - bool isStateConnectingAfterClick_; - bool isDisconnectClicked_; - - QString overrideDnsIp_; - QJsonObject m_config; + QJsonObject m_ikev2_config; - static constexpr int STATISTICS_UPDATE_PERIOD = 1000; - QTimer statisticsTimer_; QString ipsecAdapterName_; - - int prevConnectionStatus_; - bool isPrevConnectionStatusInitialized_; - - // True if startConnect() method was called and NEVPNManager emitted notification NEVPNStatusConnecting. - // False otherwise. + bool isConnectingStateReachedAfterStartingConnection_; - + void handleNotification(void *notification); bool isFailedAuthError(QMap &logs); bool isSocketError(QMap &logs); diff --git a/client/protocols/ikev2_vpn_protocol_mac.mm b/client/protocols/ikev2_vpn_protocol_mac.mm index 6752e6f7..cfea857c 100644 --- a/client/protocols/ikev2_vpn_protocol_mac.mm +++ b/client/protocols/ikev2_vpn_protocol_mac.mm @@ -1,7 +1,6 @@ #include "ikev2_vpn_protocol_mac.h" - - +#include #include #include #include @@ -20,16 +19,15 @@ #include #include -static NSString * const IKEv1ServiceName = @"AmneziaVPN"; static NSString * const IKEv2ServiceName = @"AmneziaVPN IKEv2"; static Ikev2Protocol* self = nullptr; - Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) : VpnProtocol(configuration, parent) { qDebug() << "IpsecProtocol::IpsecProtocol()"; + m_routeGateway = NetworkUtilities::getGatewayAndIface(); self = this; readIkev2Configuration(configuration); } @@ -38,6 +36,7 @@ Ikev2Protocol::~Ikev2Protocol() { qDebug() << "IpsecProtocol::~IpsecProtocol()"; disconnect_vpn(); + QThread::msleep(1000); Ikev2Protocol::stop(); } @@ -47,12 +46,13 @@ void Ikev2Protocol::stop() qDebug() << "IpsecProtocol::stop()"; } - void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) { qDebug() << "IpsecProtocol::readIkev2Configuration"; - QJsonObject ikev2_data = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject(); - m_config = QJsonDocument::fromJson(ikev2_data.value(config_key::config).toString().toUtf8()).object(); + m_config = configuration; + auto ikev2_data = m_config.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject(); + m_ikev2_config = QJsonDocument::fromJson(ikev2_data.value(config_key::config).toString().toUtf8()).object(); + } CFDataRef CreatePersistentRefForIdentity(SecIdentityRef identity) @@ -75,16 +75,16 @@ CFDataRef CreatePersistentRefForIdentity(SecIdentityRef identity) NSData *searchKeychainCopyMatching(const char *certName) { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - [dict setObject:(__bridge id)kSecClassCertificate forKey:(__bridge id)kSecClass]; - [dict setObject:[NSString stringWithUTF8String:certName] forKey:(__bridge id)kSecAttrLabel]; - [dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; - [dict setObject:@YES forKey:(__bridge id)kSecReturnPersistentRef]; + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + [dict setObject:(__bridge id)kSecClassCertificate forKey:(__bridge id)kSecClass]; + [dict setObject:[NSString stringWithUTF8String:certName] forKey:(__bridge id)kSecAttrLabel]; + [dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; + [dict setObject:@YES forKey:(__bridge id)kSecReturnPersistentRef]; - CFTypeRef result = NULL; - SecItemCopyMatching((__bridge CFDictionaryRef)dict, &result); + CFTypeRef result = NULL; + SecItemCopyMatching((__bridge CFDictionaryRef)dict, &result); - return (NSData *)result; + return (NSData *)result; } ErrorCode Ikev2Protocol::start() @@ -122,11 +122,11 @@ ErrorCode Ikev2Protocol::start() EVP_PKEY *pkey; X509 *cert; - BIO_write(p12, QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()), - QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()).size()); + BIO_write(p12, QByteArray::fromBase64(m_ikev2_config[config_key::cert].toString().toUtf8()), + QByteArray::fromBase64(m_ikev2_config[config_key::cert].toString().toUtf8()).size()); PKCS12 *pkcs12 = d2i_PKCS12_bio(p12, NULL); - PKCS12_parse(pkcs12, m_config[config_key::password].toString().toStdString().c_str(), &pkey, &cert, &certstack); + PKCS12_parse(pkcs12, m_ikev2_config[config_key::password].toString().toStdString().c_str(), &pkey, &cert, &certstack); // We output everything in PEM obio = BIO_new(BIO_s_mem()); @@ -152,7 +152,7 @@ ErrorCode Ikev2Protocol::start() output = [NSData dataWithBytes: bptr->data length: bptr->length]; - NSData *PKCS12Data = [[NSData alloc] initWithBase64EncodedString:m_config[config_key::cert].toString().toNSString() options:0] ; + NSData *PKCS12Data = [[NSData alloc] initWithBase64EncodedString:m_ikev2_config[config_key::cert].toString().toNSString() options:0]; CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); OSStatus ret = SecPKCS12Import( @@ -168,12 +168,12 @@ ErrorCode Ikev2Protocol::start() SecIdentityRef identity = (__bridge SecIdentityRef)(firstItem[(__bridge id)kSecImportItemIdentity]); NEVPNProtocolIKEv2 *protocol = [[NEVPNProtocolIKEv2 alloc] init]; - protocol.serverAddress = m_config.value(amnezia::config_key::hostName).toString().toNSString(); + protocol.serverAddress = m_ikev2_config.value(amnezia::config_key::hostName).toString().toNSString(); protocol.certificateType = NEVPNIKEv2CertificateTypeRSA; - protocol.remoteIdentifier = m_config.value(amnezia::config_key::hostName).toString().toNSString(); + protocol.remoteIdentifier = m_ikev2_config.value(amnezia::config_key::hostName).toString().toNSString(); protocol.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate; - protocol.identityReference = searchKeychainCopyMatching(m_config.value(amnezia::config_key::userName).toString().toLocal8Bit().data()); + protocol.identityReference = searchKeychainCopyMatching(m_ikev2_config.value(amnezia::config_key::userName).toString().toLocal8Bit().data()); protocol.useExtendedAuthentication = NO; protocol.enablePFS = YES; @@ -187,15 +187,17 @@ ErrorCode Ikev2Protocol::start() protocol.childSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup19; protocol.childSecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithmSHA256; protocol.childSecurityAssociationParameters.lifetimeMinutes = 1440; - + [manager setEnabled:YES]; [manager setProtocolConfiguration:(protocol)]; [manager setOnDemandEnabled:NO]; [manager setLocalizedDescription:@"Amnezia VPN"]; +#ifdef QT_DEBUG NSString *strProtocol = [NSString stringWithFormat:@"{Protocol: %@", protocol]; qDebug() << QString::fromNSString(strProtocol); - +#endif + // do config stuff [manager saveToPreferencesWithCompletionHandler:^(NSError *err) { @@ -253,7 +255,6 @@ ErrorCode Ikev2Protocol::start() } }]; - // waitConditionLocal.wait(&mutexLocal); mutexLocal.unlock(); setConnectionState(Vpn::ConnectionState::Connected); @@ -261,24 +262,22 @@ ErrorCode Ikev2Protocol::start() } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bool Ikev2Protocol::create_new_vpn(const QString & vpn_name, - const QString & serv_addr){ + const QString & serv_addr) { qDebug() << "Ikev2Protocol::create_new_vpn()"; return true; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name){ +bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name) { return false; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){ +bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name) { return false; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bool Ikev2Protocol::disconnect_vpn() { - - QMutexLocker locker(&mutex_); - + NEVPNManager *manager = [NEVPNManager sharedManager]; // #713: If user had started connecting to IKev2 on Mac and quickly started after this connecting to Wireguard @@ -303,17 +302,12 @@ bool Ikev2Protocol::disconnect_vpn() { void Ikev2Protocol::closeWindscribeActiveConnection() { - static QWaitCondition waitCondition; - static QMutex mutex; - - mutex.lock(); NEVPNManager *manager = [NEVPNManager sharedManager]; if (manager) { [manager loadFromPreferencesWithCompletionHandler:^(NSError *err) { - mutex.lock(); if (!err) { NEVPNConnection * connection = [manager connection]; @@ -326,12 +320,9 @@ void Ikev2Protocol::closeWindscribeActiveConnection() } } } - waitCondition.wakeAll(); - mutex.unlock(); }]; } - waitCondition.wait(&mutex); - mutex.unlock(); + } void Ikev2Protocol::handleNotificationImpl(int status) @@ -349,44 +340,44 @@ void Ikev2Protocol::handleNotificationImpl(int status) else if (status == NEVPNStatusDisconnected) { qDebug() << "Connection status changed: NEVPNStatusDisconnected"; + IpcClient::Interface()->disableKillSwitch(); + setConnectionState(Vpn::ConnectionState::Disconnected); - if (state_ == STATE_DISCONNECTING_ANY_ERROR) - { - [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; - // state_ = STATE_DISCONNECTED; - // emit error(IKEV_FAILED_TO_CONNECT); - setConnectionState(Vpn::ConnectionState::Disconnected); - } - else if (state_ != STATE_DISCONNECTED) - { + [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; - [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; - // state_ = STATE_DISCONNECTED; - setConnectionState(Vpn::ConnectionState::Disconnected); - } } else if (status == NEVPNStatusConnecting) { isConnectingStateReachedAfterStartingConnection_ = true; + setConnectionState(Vpn::ConnectionState::Connecting); qDebug() << "Connection status changed: NEVPNStatusConnecting"; } else if (status == NEVPNStatusConnected) { - if (!overrideDnsIp_.isEmpty()) { - if (!setCustomDns(overrideDnsIp_)) { - qDebug() << "Failed to set custom DNS ip for ikev2"; - } + qDebug() << "Connection status changed: NEVPNStatusConnected"; + + QString ipsecAdapterName_ = NetworkUtilities::lastConnectedNetworkInterfaceName(); + m_vpnLocalAddress = NetworkUtilities::ipAddressByInterfaceName(ipsecAdapterName_); + m_vpnGateway = m_vpnLocalAddress; + + QList dnsAddr; + dnsAddr.push_back(QHostAddress(m_config.value(config_key::dns1).toString())); + dnsAddr.push_back(QHostAddress(m_config.value(config_key::dns2).toString())); + + IpcClient::Interface()->updateResolvers(ipsecAdapterName_, dnsAddr); + + if (QVariant(m_config.value(config_key::killSwitchOption).toString()).toBool()) { + qDebug() << "enable killswitch"; + IpcClient::Interface()->enableKillSwitch(m_config, 0); } - qDebug() << "Connection status changed: NEVPNStatusConnected"; - + if (m_config.value(amnezia::config_key::splitTunnelType).toInt() == 0) { + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_config.value(amnezia::config_key::hostName).toString()); + } + setConnectionState(Vpn::ConnectionState::Connected); - // note: route gateway not used for ikev2 in AdapterGatewayInfo - // AdapterGatewayInfo cai; - // ipsecAdapterName_ = NetworkUtils_mac::lastConnectedNetworkInterfaceName(); - // cai.setAdapterName(ipsecAdapterName_); - // cai.setAdapterIp(NetworkUtils_mac::ipAddressByInterfaceName(ipsecAdapterName_)); - //cai.setDnsServers(NetworkUtils_mac::getDnsServersForInterface(ipsecAdapterName_)); } else if (status == NEVPNStatusReasserting) { @@ -397,33 +388,8 @@ void Ikev2Protocol::handleNotificationImpl(int status) { qDebug() << "Connection status changed: NEVPNStatusDisconnecting"; setConnectionState(Vpn::ConnectionState::Disconnecting); - /* if (state_ == STATE_START_CONNECT) - { - QMap logs = networkExtensionLog_.collectNext(); - for (QMap::iterator it = logs.begin(); it != logs.end(); ++it) - { - qDebug() << it.value(); - } - if (isSocketError(logs)) - { - state_ = STATE_DISCONNECTING_ANY_ERROR; - } - else - { - if (isFailedAuthError(logs)) - { - state_ = STATE_DISCONNECTING_AUTH_ERROR; - } - else - { - state_ = STATE_DISCONNECTING_ANY_ERROR; - } - } - }*/ } - prevConnectionStatus_ = status; - isPrevConnectionStatusInitialized_ = true; } @@ -435,62 +401,3 @@ void Ikev2Protocol::handleNotification(void *notification) QMetaObject::invokeMethod(this, "handleNotificationImpl", Q_ARG(int, (int)connection.status)); } -bool Ikev2Protocol::isFailedAuthError(QMap &logs) -{ - for (QMap::iterator it = logs.begin(); it != logs.end(); ++it) - { - if (it.value().contains("Failed", Qt::CaseInsensitive) && it.value().contains("IKE", Qt::CaseInsensitive) && it.value().contains("Auth", Qt::CaseInsensitive)) - { - if (!(it.value().contains("Failed", Qt::CaseInsensitive) && it.value().contains("IKEv2 socket", Qt::CaseInsensitive))) - { - return true; - } - } - } - return false; -} - -bool Ikev2Protocol::isSocketError(QMap &logs) -{ - for (QMap::iterator it = logs.begin(); it != logs.end(); ++it) - { - if (it.value().contains("Failed", Qt::CaseInsensitive) && it.value().contains("initialize", Qt::CaseInsensitive) && it.value().contains("socket", Qt::CaseInsensitive)) - { - return true; - } - } - return false; -} - -bool Ikev2Protocol::setCustomDns(const QString &overrideDnsIpAddress) -{ - // get list of entries of interest - // QStringList networkServices = NetworkUtils_mac::getListOfDnsNetworkServiceEntries(); - - // filter list to only ikev2 entries - QStringList dnsNetworkServices; - // for (const QString &service : networkServices) - // if (MacUtils::dynamicStoreEntryHasKey(service, "ConfirmedServiceID")) - // dnsNetworkServices.append(service); - - qDebug() << "Applying custom 'while connected' DNS change to network services: " << dnsNetworkServices; - - if (dnsNetworkServices.isEmpty()) { - qDebug() << "No network services to configure 'while connected' DNS"; - return false; - } - - // change DNS on each entry - bool successAll = true; - for (const QString &service : dnsNetworkServices) { - // if (!helper_->setDnsOfDynamicStoreEntry(overrideDnsIpAddress, service)) { - // successAll = false; - // qDebug() << "Failed to set network service DNS: " << service; - // break; - // } - } - - return successAll; -} - -