#2 Inbound and outbound DCC chat, still need better NAT handling.

master
Tomatix 2 years ago
parent 4bfc9b2380
commit 29ea535243
  1. 3
      CMakeLists.txt
  2. 3
      ICommand/CMakeLists.txt
  3. 15
      ICommand/Commands.h
  4. 11
      ICommand/ICommand.cpp
  5. 4
      ICommand/ICommandPriv.h
  6. 43
      ICommand/Internal/dcc.cpp
  7. 37
      IRCClient/DCC.cpp
  8. 6
      IRCClient/DCC.h
  9. 12
      IRCClient/IRCBase.cpp
  10. 2
      IRCClient/IRCBase.h
  11. 5
      IRCClient/IRCError.cpp
  12. 10
      IRCClient/IRCError.h
  13. 33
      IRCClient/Utilities.cpp
  14. 1
      IRCClient/Utilities.h
  15. 11
      IWin/IWinDCCChat.cpp
  16. 2
      IWin/IWinDCCChat.h
  17. 2
      IdealIRC/CMakeLists.txt
  18. 15
      IdealIRC/DCCQuery.cpp
  19. 6
      IdealIRC/MdiManager.cpp
  20. 2
      IdealIRC/MdiManager.h
  21. 15
      NATUtils/CMakeLists.txt
  22. 56
      NATUtils/PortMapping.cpp
  23. 31
      NATUtils/PortMapping.h
  24. 48
      NATUtils/PublicAddress.cpp
  25. 24
      NATUtils/PublicAddress.h
  26. BIN
      Resources/Icons/dccchat.png

@ -9,7 +9,7 @@ set(BUILD_TYPE "packaged")
set(VERSION_MAJOR 1)
set(VERSION_MINOR 1)
set(VERSION_PATCH 0)
set(VERSION_APPEND "dev8")
set(VERSION_APPEND "dev9")
#
# CMake build environment setup
@ -82,6 +82,7 @@ add_subdirectory(Script)
add_subdirectory(ScriptDialog)
add_subdirectory(ScriptFunctions)
add_subdirectory(Widgets)
add_subdirectory(NATUtils)
add_subdirectory(IdealIRC) # This one builds the binary.
if (BUILD_DEV_STUFF)

@ -12,20 +12,19 @@ list(APPEND ${component}_SOURCES
list(APPEND ${component}_SOURCES_Internal
${CMAKE_CURRENT_SOURCE_DIR}/Internal/ctcp.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Internal/dcc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Internal/ctcpreply.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Internal/me.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Internal/echo.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Internal/server.cpp
)
list(APPEND ${component}_SOURCES_External
${CMAKE_CURRENT_SOURCE_DIR}/External/kick.cpp
${CMAKE_CURRENT_SOURCE_DIR}/External/notice.cpp
${CMAKE_CURRENT_SOURCE_DIR}/External/privmsg.cpp
)
add_library(${component} STATIC ${${component}_SOURCES} ${${component}_SOURCES_Internal} ${${component}_SOURCES_External})
qt5_use_modules(${component} Widgets)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -9,14 +9,15 @@
#define ICOMMAND_COMMANDS_H
namespace Command::Internal {
constexpr auto* ACTION = "ACTION";
constexpr auto* CTCP = "CTCP";
constexpr auto* ACTION = "ACTION";
constexpr auto* CTCP = "CTCP";
constexpr auto* DCC = "DCC";
constexpr auto* CTCPREPLY = "CTCPREPLY";
constexpr auto* ME = "ME";
constexpr auto* ECHO = "ECHO";
constexpr auto* QUERY = "QUERY";
constexpr auto* MUTERESP = "MUTERESP";
constexpr auto* UNMUTERESP = "UNMUTERESP";
constexpr auto* ME = "ME";
constexpr auto* ECHO = "ECHO";
constexpr auto* QUERY = "QUERY";
constexpr auto* MUTERESP = "MUTERESP";
constexpr auto* UNMUTERESP = "UNMUTERESP";
constexpr auto* CLEAR = "CLEAR";
constexpr auto* MSG = "MSG";
constexpr auto* QUOTE = "QUOTE";

@ -492,6 +492,17 @@ bool ICommand::tryParseInternal(QString cmdLine)
return true;
}
else if (command == Command::Internal::DCC) { // namespace specified since it conflicts with the class DCC. oops :)
CommandData argv(cmdLine, { &ICommand::prd_AnyWordToUpper, &ICommand::prd_AnyWord, &ICommand::prd_Message });
if (!connection.isOnline())
print_notConnectedToServer(command);
else if (!argv[0] || !argv[1])
print_notEnoughParameters(command);
else
mp->cmd_dcc(*argv[0], *argv[1], argv[2].value_or(""));
return true;
}
else if (command == ME) {
IWin* cw = status.getActiveWindow();

@ -12,11 +12,10 @@
#include "IWin/IWinStatus.h"
#include "MdiManager.h"
#include "IRCClient/Commands.h"
#include "IRCClient/Utilities.h"
#include "Widgets/IIRCView.h"
#include <string>
constexpr char CTCPflag { 0x01 };
struct ICommandPriv
{
ICommandPriv(ICommand& super_, IRC& connection_, IWinStatus& status_);
@ -30,6 +29,7 @@ struct ICommandPriv
void cmd_notice(const std::string& target, const std::string& message);
void cmd_ctcp(const std::string& target, const std::string& command, std::string message);
void cmd_dcc(const std::string& command, const std::string& target, const std::string& message);
void cmd_ctcpreply(const std::string& target, const std::string& command, const std::string& message);
void cmd_me(const std::string& target, const std::string& message);
void cmd_echo(const std::string& arg1, const std::string& arg2, const std::string& text);

@ -0,0 +1,43 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "ICommand/ICommandPriv.h"
#include "IRCClient/DCC.h"
#include <fmt/format.h>
void ICommandPriv::cmd_dcc(const std::string& command, const std::string& target, const std::string& message)
{
auto& mdi = MdiManager::instance();
if (command == "CHAT") {
auto result = connection.initiateDCC();
if (result.second != IRCError::NoError) {
const auto code = static_cast<int>(result.second);
const auto errorStr = QString::fromStdString(IRCErrorToString(result.second));
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, QObject::tr("/DCC: Unable to initiate DCC Chat to %1 [%2 (%3)]")
.arg( QString::fromStdString(target), errorStr, QString::number(code)) );
}
else {
const auto portno = result.first->port();
auto* window = mdi.createDCCSubwindow(mdi.currentStatus(), IWin::Type::DCCChat, result.first, IRCPrefix(target));
window->print(PrintType::ProgramInfo, QObject::tr("Initiated CHAT with %1 on port %2")
.arg( QString::fromStdString(target) )
.arg(portno));
const auto myIp = findOwnIpAddress();
const auto msg = fmt::format( "CHAT chat {} {}",
normalIpToLong(myIp),
portno
);
cmd_ctcp(target, "DCC", msg);
}
}
else {
mdi.currentStatus()->printToActive(PrintType::ProgramInfo, QObject::tr("/DCC: Unknown DCC method: %1")
.arg( QString::fromStdString(command) ));
}
}

@ -59,8 +59,11 @@ struct DCCPriv
void disconnected(const asio::error_code& ec)
{
socket.close();
super.onDisconnected(SystemErrorToIRCError(ec));
try {
socket.close();
super.onDisconnected(SystemErrorToIRCError(ec));
}
catch (...) {} // Might already be closed, and so we don't care about any exceptions.
}
void read(const asio::error_code& ec)
@ -72,7 +75,9 @@ struct DCCPriv
super.onRead(readbuf, rl);
read(ec);
}
else {
/* Operation aborted would happen during destruction and socket closing has already taken place. */
else if (ec != asio::error::operation_aborted) {
disconnected(ec);
}
});
@ -86,13 +91,17 @@ struct DCCPriv
}
};
DCC::DCC(IRCBase& ircctx, asio::io_context& ioctx, const std::string& port)
: mp(std::make_unique<DCCPriv>(*this, ircctx, Direction::Initiator, ioctx, "", port))
DCC::DCC(IRCBase& ircctx, asio::io_context& ioctx)
: mp(std::make_unique<DCCPriv>(*this, ircctx, Direction::Initiator, ioctx, "", "0"))
{
mp->acceptor.open(mp->endpoint.protocol());
mp->acceptor.set_option(tcp::acceptor::reuse_address(true));
mp->acceptor.bind(mp->endpoint);
mp->acceptor.listen(1);
mp->acceptor.async_accept(mp->socket, [this](const asio::error_code& ec){
if (ec == asio::error::operation_aborted)
return;
if (ec)
mp->disconnected(ec);
else
@ -148,9 +157,8 @@ void DCC::disconnect()
mp->acceptor.close();
}
catch(const asio::system_error& e) {
// Properly log e.what() or e.code() maybe?
std::cerr << fmt::format("~DCC() caught exception ({}): {}\n IP={}, Port={}, Direction={}",
e.code().value(), e.what(), mp->ip, mp->port, mp->direction) << std::endl;
std::cerr << fmt::format("DCC::disconnect() caught exception ({}): {}\n IP={}, Port={}, Direction={}",
e.code().value(), e.what(), mp->ip, mp->port, static_cast<int>(mp->direction)) << std::endl;
}
}
@ -182,6 +190,19 @@ bool DCC::isConnected() const
return mp->socket.is_open();
}
uint16_t DCC::port() const
{
if (mp->port.empty() || mp->port == "0")
return mp->acceptor.local_endpoint().port();
else
return std::stoi(mp->port);
}
const std::string& DCC::ip() const
{
return mp->ip;
}
void DCC::setCallbackRead(DCC::CallbackRead&& cb)
{
mp->cbRead = std::move(cb);

@ -37,11 +37,11 @@ public:
Target
};
~DCC();
virtual ~DCC();
DCC(DCC&& other) noexcept;
//! Constructs an Initiator (server)
DCC(IRCBase& ircctx, asio::io_context& ioctx, const std::string& port);
DCC(IRCBase& ircctx, asio::io_context& ioctx);
//! Constructs a Target (client)
DCC(IRCBase& ircctx, asio::io_context& ioctx, const std::string& ip, const std::string& port);
@ -59,6 +59,8 @@ public:
const IRCBase& context() const;
bool isConnected() const;
uint16_t port() const;
const std::string& ip() const;
/*
* Callbacks used by 'users' of this class.

@ -18,6 +18,7 @@
#include <fmt/format.h>
#include <iostream>
#include <stdexcept>
IRCBase::IRCBase()
: mp(new IRCBasePriv(*this))
@ -422,9 +423,16 @@ std::shared_ptr<IRCMember> IRCBase::getMember(const std::string& nickname) const
return nullptr;
}
std::pair<std::shared_ptr<DCC>, IRCError> IRCBase::initiateDCC(const std::string& /*port*/)
std::pair<std::shared_ptr<DCC>, IRCError> IRCBase::initiateDCC()
{
return std::pair<std::shared_ptr<DCC>, IRCError>();
try {
auto dcc{ std::make_shared<DCC>(*this, mp->ioctx) };
return { dcc, IRCError::NoError };
}
catch (const std::exception& e) {
std::cerr << "initiateDCC exception: " << e.what() << std::endl;
return { nullptr, IRCError::DCC_InitiateError };
}
}
const IRCMessage* IRCBase::getCurrentMessageRaw() const

@ -119,7 +119,7 @@ public:
[[nodiscard]] std::shared_ptr<IRCChannel> getChannel(const std::string& name) const;
[[nodiscard]] std::shared_ptr<IRCMember> getMember(const std::string& nickname) const;
std::pair<std::shared_ptr<DCC>, IRCError> initiateDCC(const std::string& port); // Creates a DCC initiator (tcp server)
std::pair<std::shared_ptr<DCC>, IRCError> initiateDCC(); // Creates a DCC initiator (tcp server)
[[nodiscard]] const IRCMessage* getCurrentMessageRaw() const;

@ -7,7 +7,6 @@
#include "IRCError.h"
#include <iostream>
#include <fmt/format.h>
#include <asio/error.hpp>
int lastAsioErrorCode{ 0 };
@ -74,6 +73,8 @@ std::string IRCErrorToString(IRCError e)
return "DCC: We are not a DCC target";
case IRCError::DCC_TimedOut:
return "DCC: Request timed out";
case IRCError::DCC_InitiateError:
return "DCC: Unable to initiate";
case IRCError::UnhandledException:
return "Unhandled exception";
}
@ -83,7 +84,7 @@ std::string IRCErrorToString(IRCError e)
* I don't wanna use a 'default' so that my IDE and tools can pick up on missing enumerations.
* A return here would suffice.
*/
return fmt::format("Uncaught error code {}", e);
return fmt::format("Uncaught error code {}", static_cast<int>(e));
}
IRCError SystemErrorToIRCError(const std::system_error& e)

@ -8,6 +8,7 @@
#ifndef IRCERROR_H
#define IRCERROR_H
#include <fmt/format.h>
#include <system_error>
#include <string>
@ -50,6 +51,7 @@ enum class IRCError
DCC_NotATarget,
DCC_TimedOut,
DCC_InitiateError,
UnhandledException
};
@ -57,4 +59,12 @@ enum class IRCError
std::string IRCErrorToString(IRCError e);
IRCError SystemErrorToIRCError(const std::system_error& e);
template <> struct fmt::formatter<IRCError> : formatter<string_view> {
template <typename FormatContext>
auto format(IRCError e, FormatContext& ctx) {
auto txt = IRCErrorToString(e);
return formatter<string_view>::format(txt, ctx);
}
};
#endif // IRCERROR_H

@ -284,39 +284,6 @@ std::string findOwnIpAddress()
return ipaddr.empty() ? DefaultIpAddress : ipaddr;
}
std::string findOwnGatewayIpAddress()
{
std::string ipaddr{ DefaultIpAddress };
#if defined(__linux__) || defined(__linux)
std::string gateway;
{
char buf[64]{};
// This command will return either empty or something like "via 10.0.0.1 some more data here" - we want the 10.0.0.1 part.
auto fd = popen(R"(ip route show default | grep -E -o 'via\s.+(\s|$)')", "r");
auto readres = fgets(buf, sizeof(buf), fd);
pclose(fd);
if (!readres)
return ipaddr;
gateway.assign(buf, sizeof(buf));
ipaddr = getIpFromSecondToken(gateway);
}
#elif defined(WI32) || defined(WIN64)
return "127.0.0.1"; // TODO
#else
#error Unsupported platform, implementation of findOwnIpAddress() missing!
#endif
return ipaddr.empty() ? DefaultIpAddress : ipaddr;
}
bool isPrivateIp(const std::string& ip)
{
/*

@ -22,7 +22,6 @@ std::string concatenateModes(const std::unordered_map<char, std::string>& modes)
bool strEquals(const std::string& l, const std::string& r);
std::string toBase64(const std::string& input);
std::string findOwnIpAddress();
std::string findOwnGatewayIpAddress();
bool isPrivateIp(const std::string& ip);
#endif //IRCUTILITIES_H

@ -35,8 +35,6 @@ IWinDCCChat::IWinDCCChat(std::shared_ptr<DCC> dcc_, const QString& targetNicknam
dcc->setCallbackConnected([this]{ onConnected(); });
dcc->setCallbackDisconnected([this](IRCError e){ onDisconnected(e); });
dcc->setCallbackRead([this](const DCC::ByteString& data, std::size_t length){ onRead(data, length); });
dcc->accept();
}
bool IWinDCCChat::printWithCustomTime(const QDateTime& timestamp, PrintType ptype, const QString& text)
@ -50,6 +48,15 @@ void IWinDCCChat::refreshWindowTitle()
setWindowTitle(tr("DCC Chat with: %1").arg(getButtonText()));
}
void IWinDCCChat::acceptAsTarget()
{
const auto ip = QString::fromStdString(dcc->ip());
const auto port = QString::number(dcc->port());
print(PrintType::ProgramInfo, tr("Connecting to %1:%2...").arg(ip, port));
dcc->accept();
}
void IWinDCCChat::closeEvent(QCloseEvent* evt)
{
auto btn = QMessageBox::question(this, tr("Disconnect DCC Chat"),

@ -26,6 +26,8 @@ public:
void refreshWindowTitle() override;
void clear() override { view->resetView(); }
void acceptAsTarget();
void disconnectFromDCC();
private:

@ -38,7 +38,7 @@ list(APPEND ${component}_SOURCES
add_executable(${component} ${${component}_SOURCES} ${WINDOWS_RC_FILE})
target_link_libraries(${component} fmt)
target_link_libraries(${component} fmt NATUtils)
qt5_use_modules(${component} Widgets)
# This will hide the Windows Command Prompt.

@ -8,7 +8,9 @@
#include "DCCQuery.h"
#include "ui_DCCQuery.h"
#include "MdiManager.h"
#include "IWin/IWinStatus.h"
#include "IWin/IWinDCCChat.h"
DCCQuery::DCCQuery(QWidget *parent)
: QDialog(parent)
@ -35,13 +37,22 @@ void DCCQuery::on_btnRejectAll_clicked()
void DCCQuery::accepted(const QModelIndex& index)
{
auto setupChatWindow = [](IWin* base) {
auto* window = dynamic_cast<IWinDCCChat*>(base);
if (!window)
return;
window->acceptAsTarget();
};
auto& mdi = MdiManager::instance();
auto dcc = m_model.getDCCPtr(index);
auto sender = m_model.getSender(index);
auto& context = dynamic_cast<IRC&>(dcc->context());
// TODO check window type.
mdi.createDCCSubwindow(&(context.getStatus()), IWin::Type::DCCChat, dcc, *sender);
auto* basewin = mdi.createDCCSubwindow(&(context.getStatus()), IWin::Type::DCCChat, dcc, *sender);
setupChatWindow(basewin);
m_model.removeEntry(index);
}

@ -109,11 +109,11 @@ IWin* MdiManager::createSubwindow(IWin* parent, const QString& buttonText, IWin:
return createSubwindowCommons(parent, basePtr, buttonText, iconPath, activate, buttonHighlight);
}
IWin* MdiManager::createDCCSubwindow(IWin* parent, IWin::Type windowType, std::shared_ptr<DCC> dcc, const IRCPrefix& sender)
IWin* MdiManager::createDCCSubwindow(IWin* parent, IWin::Type windowType, std::shared_ptr<DCC> dcc, const IRCPrefix& target)
{
IWin* basePtr{ nullptr };
QString iconPath;
auto targetNickname{ QString::fromStdString(sender.toString()) };
auto targetNickname{ QString::fromStdString(target.toString()) };
switch (windowType) {
case IWin::Type::DCCChat:
@ -126,7 +126,7 @@ IWin* MdiManager::createDCCSubwindow(IWin* parent, IWin::Type windowType, std::s
}
if (!basePtr) {
qWarning() << "Failed to create DCC subwindow of type" << IWin::TypeString[windowType] << "with target" << QString::fromStdString(sender.toString());
qWarning() << "Failed to create DCC subwindow of type" << IWin::TypeString[windowType] << "with target" << QString::fromStdString(target.toString());
return nullptr;
}

@ -34,7 +34,7 @@ public:
static MdiManager& instance();
IWin* createSubwindow(IWin* parent, const QString& buttonText, IWin::Type windowType, bool activate = true, Highlight buttonHighlight = HL_None);
IWin* createDCCSubwindow(IWin* parent, IWin::Type windowType, std::shared_ptr<DCC> dcc, const IRCPrefix& sender);
IWin* createDCCSubwindow(IWin* parent, IWin::Type windowType, std::shared_ptr<DCC> dcc, const IRCPrefix& target);
IWin* currentWindow() const;
IWinStatus* currentStatus() const;

@ -0,0 +1,15 @@
set(component "NATUtils")
list(APPEND ${component}_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/PortMapping.h
${CMAKE_CURRENT_SOURCE_DIR}/PublicAddress.h
)
list(APPEND ${component}_COMMANDS
${CMAKE_CURRENT_SOURCE_DIR}/PortMapping.cpp
${CMAKE_CURRENT_SOURCE_DIR}/PublicAddress.cpp
)
add_library(${component} STATIC ${${component}_SOURCES} ${${component}_COMMANDS})
target_link_libraries(${component} fmt natpmp)
target_include_directories(${component} PRIVATE ${CMAKE_SOURCE_DIR})

@ -0,0 +1,56 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "PortMapping.h"
#include <natpmp.h>
#include <chrono>
#include <thread>
namespace {
bool portmapRequest(const std::string& ip, uint16_t intPort, uint16_t extPort, uint32_t timeout)
{
natpmp_t request;
natpmpresp_t response;
if (initnatpmp(&request, 0, 0) != 0)
return false;
auto retry = 10;
auto res = sendnewportmappingrequest(&request, NATPMP_PROTOCOL_TCP, intPort, extPort, timeout);
while (res != 12 && retry > 0) {
if (res == NATPMP_ERR_NOGATEWAYSUPPORT)
return false;
std::this_thread::sleep_for( std::chrono::milliseconds(10) );
res = sendnewportmappingrequest(&request, NATPMP_PROTOCOL_TCP, intPort, extPort, timeout);
--retry;
}
auto readres = readnatpmpresponseorretry(&request, &response);
retry = 10;
while (readres != 0 && retry > 0) {
std::this_thread::sleep_for( std::chrono::milliseconds(10) );
readres = readnatpmpresponseorretry(&request, &response);
--retry;
}
return retry != 0;
}
}
bool NATPortMapping::add(const std::string& ip, uint16_t intPort, uint16_t extPort)
{
constexpr auto SecondsInWeek = 60 * 60 * 24 * 7;
return portmapRequest(ip, intPort, extPort, SecondsInWeek);
}
bool NATPortMapping::remove(const std::string& ip, 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);
}

@ -0,0 +1,31 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#ifndef NATPORTMAPPING_H
#define NATPORTMAPPING_H
#include <cstdint>
#include <string>
namespace NATPortMapping {
/**
* Add a port forwarding onto local network router using libnatpmp.
* 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);
/**
* Remove a port forward using libnatpmp.
* Returns true on success.
*/
bool remove(const std::string& ip, uint16_t intPort, uint16_t extPort);
}
#endif //NATPORTMAPPING_H

@ -0,0 +1,48 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#include "PublicAddress.h"
#include <natpmp.h>
#include <chrono>
#include <thread>
std::optional<std::uint32_t> NATPublicAddress::get()
{
natpmp_t request;
natpmpresp_t response;
if (initnatpmp(&request, 0, 0) != 0)
return std::nullopt;
auto retry = 10;
auto res = sendpublicaddressrequest(&request);
while (res != 2 && retry > 0) {
if (res == NATPMP_ERR_NOGATEWAYSUPPORT)
return std::nullopt;
std::this_thread::sleep_for( std::chrono::milliseconds(10) );
res = sendpublicaddressrequest(&request);
--retry;
}
if (retry == 0)
return std::nullopt;
auto readres = readnatpmpresponseorretry(&request, &response);
retry = 10;
while (readres != 0 && retry > 0) {
std::this_thread::sleep_for( std::chrono::milliseconds(10) );
readres = readnatpmpresponseorretry(&request, &response);
--retry;
}
if (retry == 0)
return std::nullopt;
return response.pnu.publicaddress.addr.s_addr;
}

@ -0,0 +1,24 @@
/*
* IdealIRC - Internet Relay Chat client
* Copyright (c) 2022 Tom-Andre Barstad.
* This software is licensed under the Software Attribution License.
* See LICENSE for more information.
*/
#ifndef PUBLICADDRESS_H
#define PUBLICADDRESS_H
#include <cstdint>
#include <optional>
#include <string>
namespace NATPublicAddress {
/**
* Get the IP address used externally (aka. public IP address) using libnatpmp.
* Returns a nullopt on error.
*/
std::optional<std::uint32_t> get();
}
#endif //PUBLICADDRESS_H

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Loading…
Cancel
Save