#2 DCC with Windows OS specific implementation

master
Tomatix 2 years ago
parent 433fdf747c
commit bb09217d7d
  1. 13
      CMakeLists.txt
  2. 10
      IRCClient/DCC.cpp
  3. 158
      IRCClient/Utilities.cpp
  4. 6
      IdealIRC/CMakeLists.txt
  5. 6
      NATUtils/CMakeLists.txt
  6. 10
      NATUtils/PortMapping.cpp
  7. 4
      NATUtils/PortMapping.h
  8. 14
      NATUtils/PublicAddress.cpp

@ -45,14 +45,15 @@ endif()
if (UNIX)
# set(CMAKE_CXX_FLAGS "-Wall -Werror")
set(CMAKE_CXX_FLAGS "-pthread")
#
# Remove warnings about deprecated stuff.
# In order to change a statement using a deprecated API, it has to be investigated what it would affect.
# It is safe to temporarily disable this during development but should be enabled for release and test builds.
#
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations")
endif()
#
# Remove warnings about deprecated stuff.
# In order to change a statement using a deprecated API, it has to be investigated what it would affect.
# It is safe to temporarily disable this during development but should be enabled for release and test builds.
#
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations")
#
# Qt setup

@ -117,14 +117,15 @@ DCC::DCC(IRCBase& ircctx, asio::io_context& ioctx)
auto ipAddress = findOwnIpAddress();
mp->startListener(); // also acquires a port for us
mp->port = std::to_string(port());
if (isPrivateIp(ipAddress) && NATPortMapping::add(ipAddress, port(), port())) {
if (isPrivateIp(ipAddress) && NATPortMapping::add(port(), port())) {
auto publicIp = NATPublicAddress::get();
if (publicIp.has_value()) {
ipAddress = longIpToNormal(*publicIp);
}
else {
NATPortMapping::remove(ipAddress, port(), port());
NATPortMapping::remove(port(), port());
// Reset internal state, effectively stopping the listening.
// This DCC instance will be destroyed and replaced when we get a response with the port to use in reverse.
mp = std::make_unique<DCCPriv>(*this, ircctx, Direction::Initiator, ioctx, "", "0");
@ -144,6 +145,7 @@ DCC::DCC(DCC&& other) noexcept
DCC::~DCC()
{
NATPortMapping::remove(port(), port());
disconnect();
}
@ -176,12 +178,12 @@ IRCError DCC::accept(const std::string& targetNickname)
auto ipAddress = findOwnIpAddress();
mp->startListener();
if (isPrivateIp(ipAddress) && NATPortMapping::add(ipAddress, port(), port())) {
if (isPrivateIp(ipAddress) && NATPortMapping::add(port(), port())) {
auto publicIp = NATPublicAddress::get();
if (publicIp.has_value())
ipAddress = longIpToNormal(*publicIp);
else
NATPortMapping::remove(ipAddress, port(), port());
NATPortMapping::remove(port(), port());
}
mp->ircctx.ctcpRequest(targetNickname, "DCC", fmt::format("CHAT chat {} {}",

@ -6,6 +6,13 @@
*/
#include "Utilities.h"
#if defined(WIN32) || defined(WIN64)
#include <winsock2.h>
#include <iphlpapi.h>
#include <cstdlib>
#endif
#include <fmt/format.h>
#include <cstdint>
#include <cstdio>
@ -39,6 +46,100 @@ std::string getIpFromSecondToken(const std::string& input)
return input.substr(start, len);
}
}
#if defined(__linux__) || defined(__linux)
std::string getIpAddress_Linux()
{
std::string ipaddr{ DefaultIpAddress };
std::string iface;
{
char buf[64]{};
// This command will return either empty or something like "dev eth0 some more data here" - we want the eth0 part.
auto fd = popen(R"(ip route show default | grep -E -o 'dev\s.+(\s|$)')", "r");
auto readres = fgets(buf, sizeof(buf), fd);
pclose(fd);
if (!readres)
return ipaddr;
iface.assign(buf, sizeof(buf));
iface = getIpFromSecondToken(iface);
}
if (!iface.empty()) {
char buf[64]{};
// This command will match either empty or exactly "inet 123.45.67.89" though with a proper IP address.
static constexpr auto regex{ R"(inet\s[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3})" };
const auto cmd{ fmt::format("ip address show {} | grep -E -o '{}'", iface, regex) };
auto fd = popen(cmd.c_str(), "r");
auto readres = fgets(buf, sizeof(buf) - 1, fd);
pclose(fd);
if (!readres)
return ipaddr;
// We use strlen() since buf will hold many zero-bytes and popen() doesn't give us a size.
ipaddr.assign(buf, strlen(buf));
auto start = ipaddr.find(' ') + 1;
ipaddr = ipaddr.substr(start);
if (ipaddr.back() == '\n')
ipaddr.erase(ipaddr.end() - 1);
}
return ipaddr;
}
#endif
#if defined(WIN32) || defined(WIN64)
std::string getIpAddress_Windows()
{
// This function is based on the example given here:
// https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersinfo
std::string ipaddr{ DefaultIpAddress };
/* Get the index of the adapter with default route (0.0.0.0) */
DWORD adpidx{};
if (GetBestInterface(0, &adpidx) != NO_ERROR)
return ipaddr;
PIP_ADAPTER_INFO pAdapterInfo;
PIP_ADAPTER_INFO pAdapter = NULL;
ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
pAdapterInfo = (IP_ADAPTER_INFO*)malloc(sizeof(IP_ADAPTER_INFO));
if (!pAdapterInfo)
return ipaddr;
/* Make an initial call to GetAdaptersInfo to get the necessary size into the ulOutBufLen variable */
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) == ERROR_BUFFER_OVERFLOW) {
free(pAdapterInfo);
pAdapterInfo = (IP_ADAPTER_INFO*)malloc(ulOutBufLen);
if (!pAdapterInfo)
return ipaddr;
}
/* Actual call for adapter info */
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) != NO_ERROR)
return ipaddr;
for (pAdapter = pAdapterInfo; pAdapter; pAdapter = pAdapter->Next) {
if (pAdapter->Index == adpidx) {
ipaddr = pAdapter->IpAddressList.IpAddress.String;
break;
}
}
if (pAdapterInfo)
free(pAdapterInfo);
return ipaddr;
}
#endif
} // anonymous namespace
std::pair<std::string,std::string> FormatCTCPLine(std::string line)
@ -173,9 +274,9 @@ bool strEquals(const std::string& l, const std::string& r)
return false;
return std::equal(l.begin(), l.end(), r.begin(),
[](char cl, char cr) {
return toupper(cl) == toupper(cr);
}
[](char cl, char cr) {
return toupper(cl) == toupper(cr);
}
);
}
@ -231,56 +332,13 @@ std::string toBase64(const std::string& input)
std::string findOwnIpAddress()
{
std::string ipaddr{ DefaultIpAddress };
#if defined(__linux__) || defined(__linux)
std::string iface;
{
char buf[64]{};
// This command will return either empty or something like "dev eth0 some more data here" - we want the eth0 part.
auto fd = popen(R"(ip route show default | grep -E -o 'dev\s.+(\s|$)')", "r");
auto readres = fgets(buf, sizeof(buf), fd);
pclose(fd);
if (!readres)
return ipaddr;
iface.assign(buf, sizeof(buf));
iface = getIpFromSecondToken(iface);
}
if (!iface.empty()) {
char buf[64]{};
// This command will match either empty or exactly "inet 123.45.67.89" though with a proper IP address.
static constexpr auto regex{ R"(inet\s[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3})" };
const auto cmd{ fmt::format("ip address show {} | grep -E -o '{}'", iface, regex) };
auto fd = popen(cmd.c_str(), "r");
auto readres = fgets(buf, sizeof(buf) - 1, fd);
pclose(fd);
if (!readres)
return ipaddr;
// We use strlen() since buf will hold many zero-bytes and popen() doesn't give us a size.
ipaddr.assign(buf, strlen(buf));
auto start = ipaddr.find(' ') + 1;
ipaddr = ipaddr.substr(start);
if (ipaddr.back() == '\n')
ipaddr.erase(ipaddr.end() - 1);
}
#elif defined(WI32) || defined(WIN64)
return "127.0.0.1"; // TODO
auto ipaddr = getIpAddress_Linux();
#elif defined(WIN32) || defined(WIN64)
auto ipaddr = getIpAddress_Windows();
#else
#error Unsupported platform, implementation of findOwnIpAddress() missing!
#error Unsupported platform, implementation of findOwnIpAddress() missing!
#endif
return ipaddr.empty() ? DefaultIpAddress : ipaddr;
}

@ -38,14 +38,12 @@ list(APPEND ${component}_SOURCES
add_executable(${component} ${${component}_SOURCES} ${WINDOWS_RC_FILE})
target_link_libraries(${component} fmt NATUtils)
qt5_use_modules(${component} Widgets)
# This will hide the Windows Command Prompt.
if (MSVC)
set_property(TARGET ${component} PROPERTY WIN32_EXECUTABLE true)
endif()
qt5_use_modules(${component} Widgets)
target_link_libraries(${component}
ICommand
IConfig
@ -56,6 +54,8 @@ target_link_libraries(${component}
ScriptDialog
ScriptFunctions
Resources
NATUtils
fmt
)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -10,6 +10,10 @@ list(APPEND ${component}_COMMANDS
${CMAKE_CURRENT_SOURCE_DIR}/PublicAddress.cpp
)
if (MSVC)
set(WINDOWS_EXTRA_LIBS "Iphlpapi")
endif()
add_library(${component} STATIC ${${component}_SOURCES} ${${component}_COMMANDS})
target_link_libraries(${component} fmt natpmp)
target_link_libraries(${component} fmt natpmp ${WINDOWS_EXTRA_LIBS})
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -11,7 +11,7 @@
#include <thread>
namespace {
bool portmapRequest(const std::string& ip, uint16_t intPort, uint16_t extPort, uint32_t timeout)
bool portmapRequest(uint16_t intPort, uint16_t extPort, uint32_t timeout)
{
natpmp_t request;
natpmpresp_t response;
@ -43,14 +43,14 @@ bool portmapRequest(const std::string& ip, uint16_t intPort, uint16_t extPort, u
}
}
bool NATPortMapping::add(const std::string& ip, uint16_t intPort, uint16_t extPort)
bool NATPortMapping::add(uint16_t intPort, uint16_t extPort)
{
constexpr auto SecondsInWeek = 60 * 60 * 24 * 7;
return portmapRequest(ip, intPort, extPort, SecondsInWeek);
return portmapRequest(intPort, extPort, SecondsInWeek);
}
bool NATPortMapping::remove(const std::string& ip, uint16_t intPort, uint16_t extPort)
bool NATPortMapping::remove(uint16_t intPort, uint16_t extPort)
{
// Setting new timeout to '0' would cause the port mapping to be removed right away.
return portmapRequest(ip, intPort, extPort, 0);
return portmapRequest(intPort, extPort, 0);
}

@ -18,13 +18,13 @@ namespace NATPortMapping {
* The reservation is set to expire after one week to accommodate for clients on slow connections doing for example a file transfer taking a few days (it is theoretically possible.)
* Returns true on success.
*/
bool add(const std::string& ip, uint16_t intPort, uint16_t extPort);
bool add(uint16_t intPort, uint16_t extPort);
/**
* Remove a port forward using libnatpmp.
* Returns true on success.
*/
bool remove(const std::string& ip, uint16_t intPort, uint16_t extPort);
bool remove(uint16_t intPort, uint16_t extPort);
}

@ -44,5 +44,17 @@ std::optional<std::uint32_t> NATPublicAddress::get()
if (retry == 0)
return std::nullopt;
return response.pnu.publicaddress.addr.s_addr;
/*
* It appears that the DCC requests wants the IP address bytes in reversed order,
* therefore s_addr comes out with the bytes in the reversed order of what we want them.
*/
uint32_t address = response.pnu.publicaddress.addr.s_addr;
uint32_t ret = address & 0xFF;
for (auto i = 1; i < 4; ++i) {
ret <<= 8;
address >>= 8;
ret |= address & 0xFF;
}
return ret;
}

Loading…
Cancel
Save