Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OTCv8 proxy + HAProxy #4860

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
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
80 changes: 80 additions & 0 deletions src/connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,82 @@ 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 +220,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
18 changes: 17 additions & 1 deletion src/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,20 @@ 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 +127,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
Loading