Skip to content

Commit

Permalink
[WIP] OTCv8 proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
gesior committed Dec 1, 2024
1 parent ccfd367 commit 199b64d
Show file tree
Hide file tree
Showing 16 changed files with 242 additions and 7 deletions.
15 changes: 15 additions & 0 deletions config.lua.dist
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,18 @@ ownerName = ""
ownerEmail = ""
url = "https://otland.net/"
location = "Sweden"

-- Proxy settings
-- NOTE:
-- if you allow any proxy connections, you should configure your firewalls to not allow direct connections to OTS
-- only your own HAProxy server IP addresses should be allowed to connect to OTS ports / OTCv8 proxy server ports,
-- otherwise anyone can set up their own modified HAProxy server and spoof their in-game IP

-- allow OTCv8 proxy connections
allowOtcProxy = false
-- allow HAProxy connections - for players or for server status protocol ex. otservlist
allowHaProxy = false
-- if you allow HAProxy connections and want to add the HAProxy VPS IP address as OTS IP address to otservlist etc.,
-- here you can set IP address that will be returned in OTS status as the IP address of the server
-- empty string = return actual IP address of the server
statusIp = ""
5 changes: 5 additions & 0 deletions data/cpplinter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,8 @@ Creature = {}
---@field isPlayer fun(self: Player): boolean
---@field getGuid fun(self: Player): number
---@field getIp fun(self: Player): number
---@field isOtcProxy fun(self: Player): boolean
---@field isHaProxy fun(self: Player): boolean
---@field getAccountId fun(self: Player): number
---@field getLastLoginSaved fun(self: Player): number
---@field getLastLogout fun(self: Player): number
Expand Down Expand Up @@ -2202,6 +2204,8 @@ configKeys = {
MANASHIELD_BREAKABLE = 36,
CHECK_DUPLICATE_STORAGE_KEYS = 37,
MONSTER_OVERSPAWN = 38,
ALLOW_OTC_PROXY = 39,
ALLOW_HAPROXY = 40,

-- ConfigKeysString
MAP_NAME = 0,
Expand All @@ -2221,6 +2225,7 @@ configKeys = {
DEFAULT_PRIORITY = 14,
MAP_AUTHOR = 15,
CONFIG_FILE = 16,
STATUS_IP = 17,

-- ConfigKeysInteger
SQL_PORT = 0,
Expand Down
3 changes: 3 additions & 0 deletions src/configmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ bool ConfigManager::load()
boolean[TWO_FACTOR_AUTH] = getGlobalBoolean(L, "enableTwoFactorAuth", true);
boolean[CHECK_DUPLICATE_STORAGE_KEYS] = getGlobalBoolean(L, "checkDuplicateStorageKeys", false);
boolean[MONSTER_OVERSPAWN] = getGlobalBoolean(L, "monsterOverspawn", false);
boolean[ALLOW_OTC_PROXY] = getGlobalBoolean(L, "allowOtcProxy", false);
boolean[ALLOW_HAPROXY] = getGlobalBoolean(L, "allowHaProxy", false);

string[DEFAULT_PRIORITY] = getGlobalString(L, "defaultPriority", "high");
string[SERVER_NAME] = getGlobalString(L, "serverName", "");
Expand All @@ -253,6 +255,7 @@ bool ConfigManager::load()
string[URL] = getGlobalString(L, "url", "");
string[LOCATION] = getGlobalString(L, "location", "");
string[WORLD_TYPE] = getGlobalString(L, "worldType", "pvp");
string[STATUS_IP] = getGlobalString(L, "statusIp", "");

integer[MAX_PLAYERS] = getGlobalNumber(L, "maxPlayers");
integer[PZ_LOCKED] = getGlobalNumber(L, "pzLocked", 60000);
Expand Down
3 changes: 3 additions & 0 deletions src/configmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ enum boolean_config_t
MANASHIELD_BREAKABLE,
CHECK_DUPLICATE_STORAGE_KEYS,
MONSTER_OVERSPAWN,
ALLOW_OTC_PROXY,
ALLOW_HAPROXY,

LAST_BOOLEAN_CONFIG /* this must be the last one */
};
Expand All @@ -70,6 +72,7 @@ enum string_config_t
DEFAULT_PRIORITY,
MAP_AUTHOR,
CONFIG_FILE,
STATUS_IP,

LAST_STRING_CONFIG /* this must be the last one */
};
Expand Down
86 changes: 86 additions & 0 deletions src/connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,88 @@ void Connection::accept()
}
}

void Connection::parseOtcProxyPacket(const boost::system::error_code& error)
{
std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
readTimer.cancel();
if (error) {
return close();
}

const uint8_t* msgBuffer = msg.getBuffer();
uint32_t realIP = *reinterpret_cast<const uint32_t*>(msgBuffer);
realIpAddress = boost::asio::ip::address(boost::asio::ip::address_v4(realIP));
otcProxy = true;
accept();
}

void Connection::parseHaProxyPacket(const boost::system::error_code& error)
{
std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
readTimer.cancel();
if (error) {
return close();
}

const uint8_t* msgBuffer = msg.getBuffer();
uint32_t realIP = *reinterpret_cast<const uint32_t*>(&msgBuffer[14]);
realIpAddress = boost::asio::ip::address(boost::asio::ip::address_v4(realIP));
haProxy = true;
accept();
}

bool Connection::tryParseProxyPacket()
{
// only first packet may contain IP from OTCv8 proxy / haproxy
if (receivedFirstHeader) {
return false;
}

receivedFirstHeader = true;

uint16_t size = msg.getLengthHeader();
// OTCv8 proxy, 6 bytes packet
// starts from 2 bytes 0xFFFEu, then 4 bytes with IP uint32_t
if (getBoolean(ConfigManager::ALLOW_OTC_PROXY) && size == 0xFFFEu) {
readTimer.expires_after(std::chrono::seconds(CONNECTION_READ_TIMEOUT));
readTimer.async_wait(
[thisPtr = std::weak_ptr<Connection>(shared_from_this())](const boost::system::error_code& error) {
Connection::handleTimeout(thisPtr, error);
});

auto self(shared_from_this());
boost::asio::async_read(
socket,
boost::asio::buffer(msg.getBuffer(), 4),
[&, thisPtr = shared_from_this()](const boost::system::error_code &error2, size_t) {
thisPtr->parseOtcProxyPacket(error2);
}
);
return true;
}

// HAProxy send-proxy-v2, 28 bytes packet
// starts from 2 bytes 0x0A0Du, then 26 bytes, IP uint32_t starts from 17th byte (of 28 bytes)
if (getBoolean(ConfigManager::ALLOW_HAPROXY) && size == 0x0A0Du) {
readTimer.expires_after(std::chrono::seconds(CONNECTION_READ_TIMEOUT));
readTimer.async_wait(
[thisPtr = std::weak_ptr<Connection>(shared_from_this())](const boost::system::error_code& error) {
Connection::handleTimeout(thisPtr, error);
});

boost::asio::async_read(
socket,
boost::asio::buffer(msg.getBuffer(), 26),
[&, thisPtr = shared_from_this()](const boost::system::error_code &error2, size_t) {
thisPtr->parseHaProxyPacket(error2);
}
);
return true;
}

return false;
}

void Connection::parseHeader(const boost::system::error_code& error)
{
std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
Expand All @@ -144,6 +226,10 @@ void Connection::parseHeader(const boost::system::error_code& error)
return;
}

if (tryParseProxyPacket()) {
return;
}

uint32_t timePassed = std::max<uint32_t>(1, (time(nullptr) - timeConnected) + 1);
if ((++packetsSent / timePassed) > static_cast<uint32_t>(getNumber(ConfigManager::MAX_PACKETS_PER_SECOND))) {
std::cout << getIP() << " disconnected for exceeding packet per second limit." << std::endl;
Expand Down
17 changes: 16 additions & 1 deletion src/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,19 @@ class Connection : public std::enable_shared_from_this<Connection>

void send(const OutputMessage_ptr& msg);

const Address& getIP() const { return remoteAddress; };
const Address& getIP() const {
if (isOtcProxy() || isHaProxy()) {
return realIpAddress;
}
return remoteAddress;
};
bool isOtcProxy() const { return otcProxy; };
bool isHaProxy() const { return haProxy; };

private:
void parseOtcProxyPacket(const boost::system::error_code& error);
void parseHaProxyPacket(const boost::system::error_code& error);
bool tryParseProxyPacket();
void parseHeader(const boost::system::error_code& error);
void parsePacket(const boost::system::error_code& error);

Expand Down Expand Up @@ -116,6 +126,11 @@ class Connection : public std::enable_shared_from_this<Connection>
time_t timeConnected;
uint32_t packetsSent = 0;

Address realIpAddress;
bool otcProxy = false;
bool haProxy = false;
bool receivedFirstHeader = false;

ConnectionState_t connectionState = CONNECTION_STATE_PENDING;
bool receivedFirst = false;
bool receivedName = false;
Expand Down
11 changes: 9 additions & 2 deletions src/http/login.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,20 @@ std::pair<status, json::value> tfs::http::handle_login(const json::object& body,
} while (result->next());
}

std::string serverIp = getString(ConfigManager::IP);
if (getBoolean(ConfigManager::ALLOW_OTC_PROXY)) {
serverIp = std::string("127.0.0.1");
} else if (getBoolean(ConfigManager::ALLOW_HAPROXY)) {
serverIp = getString(ConfigManager::STATUS_IP);
}

json::array worlds{
{
{"id", 0}, // not implemented
{"name", getString(ConfigManager::SERVER_NAME)},
{"externaladdressprotected", getString(ConfigManager::IP)},
{"externaladdressprotected", serverIp},
{"externalportprotected", getNumber(ConfigManager::GAME_PORT)},
{"externaladdressunprotected", getString(ConfigManager::IP)},
{"externaladdressunprotected", serverIp},
{"externalportunprotected", getNumber(ConfigManager::GAME_PORT)},
{"previewstate", 0}, // not implemented
{"location", getString(ConfigManager::LOCATION)},
Expand Down
29 changes: 29 additions & 0 deletions src/luascript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2312,6 +2312,8 @@ void LuaScriptInterface::registerFunctions()
registerEnumIn(L, "configKeys", ConfigManager::SERVER_SAVE_SHUTDOWN);
registerEnumIn(L, "configKeys", ConfigManager::ONLINE_OFFLINE_CHARLIST);
registerEnumIn(L, "configKeys", ConfigManager::CHECK_DUPLICATE_STORAGE_KEYS);
registerEnumIn(L, "configKeys", ConfigManager::ALLOW_OTC_PROXY);
registerEnumIn(L, "configKeys", ConfigManager::ALLOW_HAPROXY);

registerEnumIn(L, "configKeys", ConfigManager::MAP_NAME);
registerEnumIn(L, "configKeys", ConfigManager::HOUSE_RENT_PERIOD);
Expand All @@ -2329,6 +2331,7 @@ void LuaScriptInterface::registerFunctions()
registerEnumIn(L, "configKeys", ConfigManager::MYSQL_SOCK);
registerEnumIn(L, "configKeys", ConfigManager::DEFAULT_PRIORITY);
registerEnumIn(L, "configKeys", ConfigManager::MAP_AUTHOR);
registerEnumIn(L, "configKeys", ConfigManager::STATUS_IP);

registerEnumIn(L, "configKeys", ConfigManager::SQL_PORT);
registerEnumIn(L, "configKeys", ConfigManager::MAX_PLAYERS);
Expand Down Expand Up @@ -2763,6 +2766,8 @@ void LuaScriptInterface::registerFunctions()

registerMethod(L, "Player", "getGuid", LuaScriptInterface::luaPlayerGetGuid);
registerMethod(L, "Player", "getIp", LuaScriptInterface::luaPlayerGetIp);
registerMethod(L, "Player", "isOtcProxy", LuaScriptInterface::luaPlayerIsOtcProxy);
registerMethod(L, "Player", "isHaProxy", LuaScriptInterface::luaPlayerIsHaProxy);
registerMethod(L, "Player", "getAccountId", LuaScriptInterface::luaPlayerGetAccountId);
registerMethod(L, "Player", "getLastLoginSaved", LuaScriptInterface::luaPlayerGetLastLoginSaved);
registerMethod(L, "Player", "getLastLogout", LuaScriptInterface::luaPlayerGetLastLogout);
Expand Down Expand Up @@ -9063,6 +9068,30 @@ int LuaScriptInterface::luaPlayerGetIp(lua_State* L)
return 1;
}

int LuaScriptInterface::luaPlayerIsOtcProxy(lua_State* L)
{
// player:isOtcProxy()
Player* player = tfs::lua::getUserdata<Player>(L, 1);
if (player) {
tfs::lua::pushBoolean(L, player->isOtcProxy());
} else {
lua_pushnil(L);
}
return 1;
}

int LuaScriptInterface::luaPlayerIsHaProxy(lua_State* L)
{
// player:isHaProxy()
Player* player = tfs::lua::getUserdata<Player>(L, 1);
if (player) {
tfs::lua::pushBoolean(L, player->isHaProxy());
} else {
lua_pushnil(L);
}
return 1;
}

int LuaScriptInterface::luaPlayerGetAccountId(lua_State* L)
{
// player:getAccountId()
Expand Down
2 changes: 2 additions & 0 deletions src/luascript.h
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,8 @@ class LuaScriptInterface

static int luaPlayerGetGuid(lua_State* L);
static int luaPlayerGetIp(lua_State* L);
static int luaPlayerIsOtcProxy(lua_State* L);
static int luaPlayerIsHaProxy(lua_State* L);
static int luaPlayerGetAccountId(lua_State* L);
static int luaPlayerGetLastLoginSaved(lua_State* L);
static int luaPlayerGetLastLogout(lua_State* L);
Expand Down
18 changes: 18 additions & 0 deletions src/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2046,6 +2046,24 @@ Connection::Address Player::getIP() const
return {};
}

bool Player::isOtcProxy() const
{
if (client) {
return client->isOtcProxy();
}

return {};
}

bool Player::isHaProxy() const
{
if (client) {
return client->isHaProxy();
}

return {};
}

void Player::death(Creature* lastHitCreature)
{
loginPosition = town->templePosition;
Expand Down
2 changes: 2 additions & 0 deletions src/player.h
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ class Player final : public Creature, public Cylinder
}
}
Connection::Address getIP() const;
bool isOtcProxy() const;
bool isHaProxy() const;

void addContainer(uint8_t cid, Container* container);
void closeContainer(uint8_t cid);
Expand Down
18 changes: 18 additions & 0 deletions src/protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,21 @@ Connection::Address Protocol::getIP() const

return {};
}

bool Protocol::isOtcProxy() const
{
if (auto connection = getConnection()) {
return connection->isOtcProxy();
}

return false;
}

bool Protocol::isHaProxy() const
{
if (auto connection = getConnection()) {
return connection->isHaProxy();
}

return false;
}
2 changes: 2 additions & 0 deletions src/protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class Protocol : public std::enable_shared_from_this<Protocol>
Connection_ptr getConnection() const { return connection.lock(); }

Connection::Address getIP() const;
bool isOtcProxy() const;
bool isHaProxy() const;

// Use this function for autosend messages only
OutputMessage_ptr getOutputBuffer(int32_t size);
Expand Down
16 changes: 14 additions & 2 deletions src/protocollogin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,27 @@ void ProtocolLogin::getCharacterList(const std::string& accountName, const std::
for (uint8_t i = 0; i < 2; i++) {
output->addByte(i); // world id
output->addString(i == 0 ? "Offline" : "Online");
output->addString(getString(ConfigManager::IP));
if (getConnection()->isOtcProxy()) {
output->addString("127.0.0.1");
} else if (getConnection()->isHaProxy()) {
output->addString(getString(ConfigManager::STATUS_IP));
} else {
output->addString(getString(ConfigManager::IP));
}
output->add<uint16_t>(getNumber(ConfigManager::GAME_PORT));
output->addByte(0);
}
} else {
output->addByte(1); // number of worlds
output->addByte(0); // world id
output->addString(getString(ConfigManager::SERVER_NAME));
output->addString(getString(ConfigManager::IP));
if (getConnection()->isOtcProxy()) {
output->addString("127.0.0.1");
} else if (getConnection()->isHaProxy()) {
output->addString(getString(ConfigManager::STATUS_IP));
} else {
output->addString(getString(ConfigManager::IP));
}
output->add<uint16_t>(getNumber(ConfigManager::GAME_PORT));
output->addByte(0);
}
Expand Down
Loading

0 comments on commit 199b64d

Please sign in to comment.