├── .gitignore ├── docs └── how-works.png ├── app.js ├── package.json ├── opcodes.js ├── RealmSession.js ├── RealmClient.js ├── config.js.sample ├── RelayServer.js ├── README.md ├── AuthClient.js ├── AuthSession.js └── README_ES.md /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | config.js -------------------------------------------------------------------------------- /docs/how-works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masterking32/WoW-Server-Relay/HEAD/docs/how-works.png -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // Developed by: Amin.MasterkinG (https://masterking32.com) 2 | // Github: https://github.com/masterking32/WoW-Server-Relay 3 | // Year: 2024 4 | 5 | import RelayServer from "./RelayServer.js"; 6 | import { config } from "./config.js"; 7 | 8 | const server = new RelayServer(config); 9 | server.run(); 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wow-relay-server", 3 | "version": "1.0.0", 4 | "description": "This is a Relay Server for World of Warcraft, designed to function as a Content Delivery Network (CDN) for the game. It conceals the actual server IP to safeguard it from DDoS attacks. Additionally, it enhances the ping for players, providing a smoother gaming experience.", 5 | "main": "app.js", 6 | "type": "module", 7 | "author": "masterking32", 8 | "dependencies": { 9 | "@ptkdev/logger": "^1.8.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /opcodes.js: -------------------------------------------------------------------------------- 1 | // Developed by: Amin.MasterkinG (https://masterking32.com) 2 | // Github: https://github.com/masterking32/WoW-Server-Relay 3 | // Year: 2024 4 | 5 | // Opcodes and packets https://wowdev.wiki/Login_Packet 6 | export const CMD_AUTH_LOGON_CHALLENGE = 0x00; 7 | export const CMD_AUTH_LOGON_PROOF = 0x01; 8 | export const CMD_AUTH_RECONNECT_CHALLENGE = 0x02; 9 | export const CMD_AUTH_RECONNECT_PROOF = 0x03; 10 | export const CMD_SURVEY_RESULT = 0x03; 11 | export const CMD_REALM_LIST = 0x10; 12 | export const CMD_XFER_INITIATE = 0x30; 13 | export const CMD_XFER_DATA = 0x31; 14 | export const CMD_XFER_ACCEPT = 0x32; 15 | export const CMD_XFER_RESUME = 0x33; 16 | export const CMD_XFER_CANCEL = 0x34; 17 | export const RELAY_SERVER_CMD_AUTH = 0x64; // Custom opcode for auth relay server 18 | export const RELAY_SERVER_CMD_WORLD = 0xA32; // Custom opcode for world relay server 19 | -------------------------------------------------------------------------------- /RealmSession.js: -------------------------------------------------------------------------------- 1 | // Developed by: Amin.MasterkinG (https://masterking32.com) 2 | // Github: https://github.com/masterking32/WoW-Server-Relay 3 | // Year: 2024 4 | 5 | import RealmClient from "./RealmClient.js"; 6 | 7 | class RealmSession { 8 | constructor(realm_id, realm, config, socket, logger) { 9 | this.config = config; 10 | this.socket = socket; 11 | this.logger = logger; 12 | this.isEnded = false; 13 | this.realm_id = realm_id; 14 | this.realm = realm; 15 | } 16 | 17 | run() { 18 | this.client = new RealmClient( 19 | this.config, 20 | this.socket.remoteAddress.includes("::ffff:") 21 | ? this.socket.remoteAddress.replace("::ffff:", "") 22 | : this.socket.remoteAddress, 23 | this.realm, 24 | this.logger, 25 | this.stop.bind(this), 26 | (data) => { 27 | this.socket.write(data); 28 | } 29 | ); 30 | this.client.run(); 31 | 32 | this.socket.on("data", (data) => { 33 | if (this.isEnded) { 34 | return; 35 | } 36 | if (!this.client) { 37 | this.logger.error("[RealmSession] Client is not ready yet"); 38 | return; 39 | } 40 | this.logger.debug(`[RealmSession] Received ${data.length} bytes`); 41 | this.client.socket.write(data); 42 | }); 43 | 44 | this.socket.on("close", () => { 45 | this.stop(); 46 | }); 47 | 48 | this.socket.on("error", (error) => { 49 | this.logger.error("[RealmSession] Socket error", error); 50 | this.stop(); 51 | }); 52 | } 53 | 54 | stop() { 55 | if (this.isEnded) { 56 | return; 57 | } 58 | 59 | this.logger.debug("[AuthSession] Stopping session"); 60 | this.isEnded = true; 61 | 62 | if (this.client) { 63 | this.client.stop(); 64 | } 65 | 66 | this.socket.destroy(); 67 | } 68 | } 69 | 70 | export default RealmSession; 71 | -------------------------------------------------------------------------------- /RealmClient.js: -------------------------------------------------------------------------------- 1 | // Developed by: Amin.MasterkinG (https://masterking32.com) 2 | // Github: https://github.com/masterking32/WoW-Server-Relay 3 | // Year: 2024 4 | 5 | import Net from "net"; 6 | import { RELAY_SERVER_CMD_WORLD } from "./opcodes.js"; 7 | 8 | class RealmClient { 9 | constructor(config, client_ip, realm, logger, onStop, onData) { 10 | this.ip = realm.realm_ip; 11 | this.port = realm.realm_port; 12 | this.logger = logger; 13 | this.secret_key = config.secret_key; 14 | this.client_ip = client_ip; 15 | this.realm = realm; 16 | this.onStop = onStop; 17 | this.onData = onData; 18 | this.isReady = false; 19 | this.isEnded = false; 20 | this.config = config; 21 | } 22 | 23 | run() { 24 | this.socket = Net.createConnection(this.port, this.ip, () => { 25 | this.logger.info(`[RealmClient] Connected to ${this.ip}:${this.port}`); 26 | 27 | if (this.config.send_relay_packet) { 28 | this.logger.debug( 29 | `[RealmClient] Sending Relay Packet: ${this.secret_key} ${this.client_ip}` 30 | ); 31 | 32 | const secret_key = this.secret_key + "\0"; 33 | const client_ip = this.client_ip + "\0"; 34 | const packet_data_size = secret_key.length + client_ip.length + 10; 35 | 36 | let relay_packet = Buffer.alloc(packet_data_size); 37 | 38 | relay_packet.writeUint16BE(packet_data_size - 2); // Packet size (excluding size field but including opcode) 39 | relay_packet.writeUint32LE(RELAY_SERVER_CMD_WORLD, 2); 40 | relay_packet.write(secret_key, 6); // Terminal null byte is included in the secret key 41 | relay_packet.write(client_ip, 6 + secret_key.length); // Terminal null byte is included in the client IP 42 | this.socket.write(relay_packet); 43 | this.logger.debug( 44 | `[RealmClient] Sent Relay Packet: ${relay_packet.toString( 45 | "hex" 46 | )}, length: ${relay_packet.length} bytes` 47 | ); 48 | } 49 | 50 | this.isReady = true; 51 | }); 52 | 53 | this.socket.on("data", this.onSocketData.bind(this)); 54 | this.socket.on("error", this.onSocketError.bind(this)); 55 | this.socket.on("close", this.onSocketClose.bind(this)); 56 | this.socket.on("timeout", this.onSocketTimeout.bind(this)); 57 | } 58 | 59 | async onSocketData(data) { 60 | this.logger.debug(`[RealmClient] Received ${data.length} bytes`); 61 | this.onData(data); 62 | } 63 | 64 | onSocketError(error) { 65 | this.logger.error(`[RealmClient] Error: ${error.message}`); 66 | this.stop(); 67 | } 68 | 69 | onSocketClose() { 70 | this.logger.info("[RealmClient] Connection closed"); 71 | this.stop(); 72 | } 73 | 74 | onSocketTimeout() { 75 | this.logger.info("[RealmClient] Connection timeout"); 76 | this.stop(); 77 | } 78 | 79 | WriteData(data) { 80 | if (this.isEnded) { 81 | return; 82 | } 83 | 84 | this.logger.debug(`[RealmClient] Sending ${data.length} bytes`); 85 | this.socket.write(data); 86 | } 87 | 88 | stop() { 89 | if (this.isEnded) { 90 | return; 91 | } 92 | 93 | this.isEnded = true; 94 | this.logger.info("[RealmClient] Stopping"); 95 | this.onStop(); 96 | this.socket.destroy(); 97 | } 98 | } 99 | 100 | export default RealmClient; 101 | -------------------------------------------------------------------------------- /config.js.sample: -------------------------------------------------------------------------------- 1 | // Developed by: Amin.MasterkinG (https://masterking32.com) 2 | // Github: https://github.com/masterking32/WoW-Server-Relay 3 | // Year: 2024 4 | 5 | export const config = { 6 | log_level: "info", // Set the log level: debug, info, warning, error 7 | 8 | // Specify the game version: 1.12.1, 2.4.3, 3.3.5 (based on the game version and packet structure) 9 | // If you are unsure, change the log_level to debug and create a connection to this server with a valid game version client. Check the console log for the game version. 10 | 11 | game_version: "3.3.5", 12 | 13 | // Specify the build number: 12340, ... (based on the game version and packet structure) 14 | // If you are unsure, change the log_level to debug and create a connection to this server with a valid game version client. Check the console log for the build number. 15 | 16 | build: 12340, 17 | 18 | // Set this to true if your core supports relay packets. 19 | // This will allow the relay server to send the relay packet to the main server, and the main server will receive and process it. With the relay packet, the main server can get the real user IP and enable IP banning functionality. 20 | // If this is set to false, the relay server will not send the relay packet to the main server, and the main server will not receive the real user IP. 21 | // If you don't have any custom changes in your core regarding this, make sure this is set to false. 22 | 23 | send_relay_packet: false, 24 | 25 | // Secret Key for Relay Packet validation on the main server. 26 | // This key should be the same as the main server's secret key. 27 | // Please use a secure key that is between 32 and 64 characters in length. Note that the maximum length allowed is 64 characters. 28 | 29 | secret_key: "secret", 30 | 31 | // Specify the Relay Public IP Address to allow players to connect to the relay server. 32 | 33 | relay_ip: "127.0.0.1", 34 | 35 | // You can use another port for relay server auth requests, the default is 3724. 36 | 37 | auth_port: 3724, 38 | 39 | main_server_auth: { 40 | // Specify the IP address of the main server for auth requests. 41 | // This IP is used by the relay server to send the auth request to the main server. 42 | // This IP should be the same as the main server's IP. If you have a private connection between the relay and main server, you can use the private IP. 43 | 44 | host: "192.168.32.32", 45 | 46 | // Specify the port of the main server for auth requests in authserver.conf. 47 | 48 | port: 3724, 49 | }, 50 | 51 | // Configuration for server realms. Fill this part with your main server realmlist table in the auth database. 52 | 53 | realms: [ 54 | { 55 | // realm_id should be the same as the id in the realmlist table. 56 | 57 | realm_id: 1, 58 | 59 | // realm_name should be the same as the name in the realmlist table. 60 | 61 | realm_name: "Realm 1", 62 | 63 | // realm_ip should be the same as the address in the realmlist table. 64 | // This IP is used by the relay server to connect and receive the game packets from the main server. 65 | // If you have a private connection between the relay and main server, you can use the private IP here. Set the address in the realmlist table to the private IP, and in the CMD_REALM_LIST packet, we will replace that IP with the relay IP. 66 | 67 | realm_ip: "192.168.32.32", 68 | 69 | // realm_port should be the same as the port in the realmlist table. 70 | // This port is used by the relay server to connect and receive the game packets from the main server. 71 | // This port is also used by the client to connect to the relay server. 72 | 73 | realm_port: 8085, 74 | }, 75 | // { 76 | // realm_id: 2, 77 | // realm_name: "Realm 1", 78 | // realm_ip: "192.168.32.32", 79 | // realm_port: 8086, 80 | // }, 81 | ], 82 | }; 83 | -------------------------------------------------------------------------------- /RelayServer.js: -------------------------------------------------------------------------------- 1 | // Developed by: Amin.MasterkinG (https://masterking32.com) 2 | // Github: https://github.com/masterking32/WoW-Server-Relay 3 | // Year: 2024 4 | 5 | import Logger from "@ptkdev/logger"; 6 | import Net from "net"; 7 | import AuthSession from "./AuthSession.js"; 8 | import RealmSession from "./RealmSession.js"; 9 | 10 | class RelayServer { 11 | constructor(config) { 12 | const options = { 13 | language: "en", 14 | colors: true, 15 | debug: config.log_level === "debug", 16 | info: config.log_level === "info" || config.log_level === "debug", 17 | warning: 18 | config.log_level === "warning" || 19 | config.log_level === "info" || 20 | config.log_level === "debug", 21 | error: 22 | config.log_level === "error" || 23 | config.log_level === "warning" || 24 | config.log_level === "info" || 25 | config.log_level === "debug", 26 | sponsor: false, 27 | write: false, 28 | type: "log", 29 | }; 30 | 31 | this.logger = new Logger(options); 32 | this.config = config; 33 | 34 | if (this.config.secret_key.length > 64) { 35 | this.logger.error( 36 | "Unable to start the server. The secret key length is too long. The maximum length is 64 characters." 37 | ); 38 | 39 | process.exit(1); 40 | } 41 | 42 | if (this.config.send_relay_packet && this.config.secret_key.length < 1) { 43 | this.logger.error( 44 | "Unable to start the server. The secret key is required when send_relay_packet is enabled. Please set the secret key in the config file." 45 | ); 46 | this.logger.error( 47 | "Please note: The secret key should be the same as the main server's secret key, and the recommended length is between 32 and 64 characters." 48 | ); 49 | 50 | process.exit(1); 51 | } 52 | 53 | if (this.config.send_relay_packet && this.config.secret_key === "secret") { 54 | this.logger.error( 55 | "The secret key is set to the default value. Please change the secret key in the config file." 56 | ); 57 | this.logger.error( 58 | "Please note: The secret key should be the same as the main server's secret key, and the recommended length is between 32 and 64 characters." 59 | ); 60 | 61 | process.exit(1); 62 | } 63 | 64 | this.auth_server = Net.createServer((socket) => { 65 | this.logger.info( 66 | `New connection from ${socket.remoteAddress} to auth server` 67 | ); 68 | let session = new AuthSession(config, socket, this.logger); 69 | session.run(); 70 | }); 71 | 72 | this.realms = {}; 73 | for (let realm of config.realms) { 74 | this.realms[realm.realm_id] = Net.createServer((socket) => { 75 | const remoteAddress = socket.remoteAddress; 76 | this.logger.info( 77 | `New connection from ${ 78 | remoteAddress && remoteAddress.includes("::ffff:") 79 | ? remoteAddress.replace("::ffff:", "") 80 | : remoteAddress 81 | } to realm "${realm.realm_name}"` 82 | ); 83 | 84 | let session = new RealmSession( 85 | realm.realm_id, 86 | realm, 87 | config, 88 | socket, 89 | this.logger 90 | ); 91 | session.run(); 92 | }); 93 | } 94 | } 95 | 96 | run() { 97 | this.auth_server.listen(this.config.auth_port, () => { 98 | this.logger.info( 99 | `Auth server listening on port ${this.config.auth_port}` 100 | ); 101 | }); 102 | 103 | for (let realm of this.config.realms) { 104 | this.realms[realm.realm_id].listen(realm.realm_port, () => { 105 | this.logger.info( 106 | `Realm server listening on port ${realm.realm_port} - ${realm.realm_name}` 107 | ); 108 | }); 109 | } 110 | } 111 | 112 | stop() { 113 | this.auth_server.close(); 114 | for (let realm of this.realms) { 115 | realm.close(); 116 | } 117 | 118 | this.logger.info("Server stopped"); 119 | } 120 | } 121 | 122 | export default RelayServer; 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # World of Warcraft (WoW) Relay Server 2 | 3 | [English](README.md) | [Spanish](README_ES.md) 4 | 5 | This project enables the creation of additional servers that function as a Content Delivery Network (CDN) for World of Warcraft private servers. It allows you to conceal your main server IP from users. Players connect to the relay servers, which then handle and forward packets to the main server. This setup not only protects your main server from DDoS attacks but also provides a smoother gameplay experience by having CDNs at different locations. 6 | 7 | ### ⭐ If you find this project useful, feel free to give it a star! ⭐ 8 | 9 | This project reads and handles packets from the client for AuthServer and functions like a WoW Auth Server and WoW Client. Additionally, it edits the `REALMLIST_PACKET` to replace the main server IP with the relay IP. Contributions are welcome. 10 | 11 | ## How It Works? 12 | 13 | ![How it Works?](https://raw.githubusercontent.com/masterking32/WoW-Server-Relay/main/docs/how-works.png) 14 | 15 | # Why Should We Use This Tool and What Makes It Different? 16 | 17 |
18 |

1) Does adding another node in the network increase ping?

19 | Contrary to what some may believe, adding another node can actually decrease ping for users. For instance, if your server is located in the EU, but you have players in North and South America, each player will have a different network route to the EU. If you establish a server in the US with a better route to your EU server, players can connect to your US server. This server will then forward packets via the better route, resulting in improved ping for players. 20 |
21 | 22 |
23 |

2) How does it mitigate DDoS attacks?

24 | Most DDoS attacks utilize packet types such as UDP, ACK, SYN, etc. This tool does not forward all types of these attacks to your main server. By implementing rate limits on your UFW/IPtable, you can further protect your main server from DDoS attacks. If one of your servers is under attack, some users connected to that server may get disconnected, but others can still play. While this tool can help mitigate the effects of DDoS attacks, it does not provide 100% protection. It simply adds an additional layer of network security. 25 |
26 | 27 |
28 |

3) Why should we use this instead of Load Balancers, IPTable forwards, and other proxy tools?

29 | 30 | #### Issue 1: 31 | 32 | While you can use other tools to forward packets, load balancers, etc., it's important to understand that by default, TrinityCore/AzerothCore retrieves the user's IP from the remote socket IP. This means that when you use something like IPTable, the user's IP on the WoW server is your relay server's IP. For instance, if `us-relay1`'s IP is `8.8.8.8`, and a player connected to that server attempts the wrong password multiple times, the server will ban `8.8.8.8` instead of the user's IP. Consequently, no one can connect to the server from the `us-relay1` node. For users connected to the WoW server from the `us-relay1` node, the IP will always be `8.8.8.8`, and in the game, if you cannot retrieve the real player's IP, you will always see the relay node IPs. 33 | 34 | #### How did you fix it? 35 | 36 | This project works like other forwarders by default, but with a difference: it only works for WoW and reads, parses, and handles packets. To fix the read-ip issue, we added a custom packet for WorldServer and AuthServer with these Opcodes: 37 | 38 | ``` 39 | RELAY_SERVER_CMD_AUTH = 0x64 // 100 40 | RELAY_SERVER_CMD_WORLD = 0xA32 // 2610 41 | ``` 42 | 43 | If you enable `send_relay_packet` in the config file, this project will send a relay packet to the auth and world server after opening a socket connection. This packet includes a secret key and the real IP of the user. Your Auth and World servers need to parse this packet and replace the user IP with the IP inside this packet. 44 | 45 | #### Packet Structure for AuthServer 46 | 47 | | Offset | Size | Type | Name | Description | 48 | | ------ | ---- | ------ | ---------- | -------------------------------------------------------------- | 49 | | 0x0 | 1 | uint8 | OpCode | Opcode for relay custom packet. `RELAY_SERVER_CMD_AUTH = 0x64` | 50 | | 0x1 | 2 | uint16 | Secret_Len | Secret key length | 51 | | 0x3 | 2 | uint16 | IP_len | The length of user IP | 52 | | 0x5 | - | String | Secret_Key | The secret key value starts from 0x5 and ends with Secret_Len | 53 | | - | - | String | User_IP | User IP address | 54 | 55 | #### Packet Structure for WorldServer 56 | 57 | #### HEADER 58 | 59 | | Offset | Size | Type | Name | Description | 60 | | ------ | ---- | ------ | ---- | ------------------------------------------------------------------------------------------- | 61 | | 0x0 | 2 | uint16 | Size | Packet Header - Size of Packet (Size of the packet including the opcode field.) | 62 | | 0x2 | 4 | uint32 | CMD | Packet Header - Opcode or Command for relay custom packet. `RELAY_SERVER_CMD_WORLD = 0xA32` | 63 | 64 | #### BODY 65 | 66 | | Offset | Size | Type | Name | Description | 67 | | ------ | ---- | ------ | ---------- | ----------------------------------------------------------------------------------------- | 68 | | 0x0 | - | String | Secret_Key | The secret key value starts from 0x6 and ends with Secret_Len. `(Null terminated string)` | 69 | | - | - | String | User_IP | User IP address. `(Null terminated string)` | 70 |
71 | 72 | --- 73 | 74 | ## Does TrinityCore/AzerothCore support this packet? 75 | 76 | - ## TrinityCore Custom Changes: 77 | 78 | For TrinityCore, you can refer to [masterking32/TrinityCore-Relay-Support](https://github.com/masterking32/TrinityCore-Relay-Support) and [this specific commit](https://github.com/masterking32/TrinityCore-Relay-Support/commit/cb5aa9eefd4caec032864b9249fd16341ab64b73) for version 3.3.5. These resources will guide you on how to make custom changes to your core to support handling and parsing of the relay packet. 79 | 80 | - ## AzerothCore Custom Changes/Module: 81 | 82 | [Check these changes for AzerothCore](https://github.com/azerothcore/azerothcore-wotlk/pull/19998/files) 83 | 84 | --- 85 | 86 | **Please Note: If you haven't made any custom changes to the core, ensure that `send_relay_packet` is set to `false`. If you have made custom changes, set `send_relay_packet` to `true` and establish a secure `secret_key` that is between 32 to 64 characters long (the maximum allowed value is 64). This `secret_key` should be the same in both this project's `config.js` file and your core configuration files, `authserver.conf` and `worldserver.conf`.** 87 | 88 | --- 89 | 90 | # Installation Guide for Ubuntu/Debian 91 | 92 | 1. **Install the required packages:** 93 | 94 | ```bash 95 | apt install curl git nano sudo 96 | ``` 97 | 98 | 2. **Install NodeJS (version 20 or higher):** 99 | 100 | ```bash 101 | curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash - 102 | sudo apt-get install -y nodejs 103 | ``` 104 | 105 | 3. **Download the project:** 106 | 107 | ```bash 108 | git clone https://github.com/masterking32/WoW-Server-Relay 109 | cd WoW-Server-Relay 110 | ``` 111 | 112 | 4. **Install NPM Packages:** 113 | 114 | ```bash 115 | npm install 116 | ``` 117 | 118 | 5. **Configure the project:** 119 | 120 | ```bash 121 | cp config.js.sample config.js 122 | nano config.js 123 | ``` 124 | 125 | 6. **Run the project:** 126 | 127 | ```bash 128 | node app.js 129 | ``` 130 | 131 | 7. **Run as Service/Startup:** 132 | 133 | ```bash 134 | npm install pm2 -g 135 | pm2 start app.js 136 | pm2 startup 137 | pm2 save 138 | ``` 139 | 140 | **Note:** For optimal performance, support for real user IP, and to ensure the IP ban function works on your server, you need to make some modifications to your core. Please read [this section](https://github.com/masterking32/WoW-Server-Relay?tab=readme-ov-file#does-trinitycoreazerothcore-support-this-packet) and apply the necessary changes to your core. 141 | 142 | --- 143 | 144 | # Windows Installation: 145 | 146 | 1. Download and install the latest version of [NodeJS](https://nodejs.org/en). 147 | 2. Download the project and extract the files. 148 | 3. Navigate to the project directory and rename `config.js.sample` to `config.js`. 149 | 4. Modify the `config.js` file with your server information. 150 | 5. Open the `Command Prompt`, navigate to the project directory. 151 | 6. Run the command `node app.js`. 152 | 7. Ensure that the necessary ports are open in your firewall. 153 | 154 | **Note:** For optimal performance, support for real user IP, and to ensure the IP ban function works on your server, you need to make some modifications to your core. Please read [this section](https://github.com/masterking32/WoW-Server-Relay?tab=readme-ov-file#does-trinitycoreazerothcore-support-this-packet) and apply the necessary changes to your core. 155 | 156 | --- 157 | 158 | ## Developer Information 159 | 160 | This project was developed by [Amin.MasterkinG](https://masterking32.com). You can also find me on [Github](https://github.com/masterking32). 161 | -------------------------------------------------------------------------------- /AuthClient.js: -------------------------------------------------------------------------------- 1 | // Developed by: Amin.MasterkinG (https://masterking32.com) 2 | // Github: https://github.com/masterking32/WoW-Server-Relay 3 | // Year: 2024 4 | 5 | import Net from "net"; 6 | import { 7 | CMD_AUTH_LOGON_CHALLENGE, 8 | CMD_AUTH_LOGON_PROOF, 9 | CMD_AUTH_RECONNECT_CHALLENGE, 10 | CMD_AUTH_RECONNECT_PROOF, 11 | CMD_REALM_LIST, 12 | CMD_XFER_DATA, 13 | CMD_XFER_INITIATE, 14 | RELAY_SERVER_CMD_AUTH, 15 | } from "./opcodes.js"; 16 | 17 | class AuthClient { 18 | constructor(config, client_ip, authChallengePayload, logger, onStop, onData) { 19 | this.ip = config.main_server_auth.host; 20 | this.port = config.main_server_auth.port; 21 | this.logger = logger; 22 | this.secret_key = config.secret_key; 23 | this.client_ip = client_ip; 24 | this.authChallengePayload = authChallengePayload; 25 | this.send_relay_packet = config.send_relay_packet; 26 | this.onStop = onStop; 27 | this.onData = onData; 28 | this.isReady = false; 29 | this.isEnded = false; 30 | this.config = config; 31 | } 32 | 33 | run() { 34 | this.socket = Net.createConnection(this.port, this.ip, () => { 35 | this.logger.info(`[AuthClient] Connected to ${this.ip}:${this.port}`); 36 | 37 | if (this.send_relay_packet) { 38 | let relay_packet = Buffer.alloc( 39 | this.secret_key.length + this.client_ip.length + 5 40 | ); 41 | relay_packet.writeUInt8(RELAY_SERVER_CMD_AUTH, 0); 42 | relay_packet.writeUint16LE(this.secret_key.length, 1); 43 | relay_packet.writeUint16LE(this.client_ip.length, 3); 44 | relay_packet.write(this.secret_key, 5); 45 | relay_packet.write(this.client_ip, 5 + this.secret_key.length); 46 | this.socket.write(relay_packet); 47 | this.logger.debug( 48 | `[AuthClient] Sent Relay Packet: ${relay_packet.toString( 49 | "hex" 50 | )}, length: ${relay_packet.length} bytes` 51 | ); 52 | } 53 | 54 | this.socket.write(this.authChallengePayload); 55 | this.logger.debug( 56 | `[AuthClient] Sent Auth Challenge Payload: ${this.authChallengePayload.toString()}` 57 | ); 58 | this.isReady = true; 59 | }); 60 | 61 | this.socket.on("data", this.onSocketData.bind(this)); 62 | this.socket.on("error", this.onSocketError.bind(this)); 63 | this.socket.on("close", this.onSocketClose.bind(this)); 64 | this.socket.on("timeout", this.onSocketTimeout.bind(this)); 65 | } 66 | 67 | async onSocketData(data) { 68 | this.logger.debug(`[AuthClient] Received ${data.length} bytes`); 69 | 70 | let bytes = data.length; 71 | let position = 0; 72 | while (bytes > 0) { 73 | const opcode = data.readUInt8(position); 74 | position += 1; 75 | bytes -= 1; 76 | const new_position = await this.HandleOpcode( 77 | opcode, 78 | data.slice(position) 79 | ); 80 | if (!new_position) { 81 | break; 82 | } 83 | position += new_position; 84 | bytes -= new_position; 85 | } 86 | 87 | this.logger.debug(`[AuthClient] Remaining bytes: ${bytes}`); 88 | } 89 | 90 | async HandleOpcode(opcode, data) { 91 | let position = 0; 92 | this.logger.debug(`[AuthClient] Opcode: ${opcode}`); 93 | switch (opcode) { 94 | case CMD_AUTH_LOGON_CHALLENGE: 95 | case CMD_AUTH_RECONNECT_CHALLENGE: 96 | const ChallengeResponse = await this.HandleAuthLogonChallenge( 97 | opcode, 98 | data 99 | ); 100 | if (!ChallengeResponse) { 101 | this.stop(); 102 | } 103 | 104 | position = ChallengeResponse.position; 105 | break; 106 | case CMD_AUTH_LOGON_PROOF: 107 | case CMD_AUTH_RECONNECT_PROOF: 108 | this.logger.debug(`[AuthClient] Received Auth Logon Proof`); 109 | const AuthLogonProofResponse = await this.HandleAuthLogonProof( 110 | opcode, 111 | data 112 | ); 113 | if (!AuthLogonProofResponse) { 114 | this.stop(); 115 | } 116 | position = AuthLogonProofResponse.position; 117 | break; 118 | 119 | case CMD_REALM_LIST: 120 | this.logger.debug(`[AuthClient] Received Realm List`); 121 | const RealmListResponse = await this.HandleRealmList(data); 122 | if (!RealmListResponse) { 123 | this.stop(); 124 | } 125 | position = RealmListResponse.position; 126 | break; 127 | case CMD_XFER_INITIATE: 128 | case CMD_XFER_DATA: 129 | const packet = Buffer.alloc(1 + data.length); 130 | packet.writeUInt8(opcode, 0); 131 | data.copy(packet, 1, 0); 132 | this.onData(packet); 133 | position = data.length; 134 | break; 135 | default: 136 | this.logger.error(`[AuthClient] Unknown opcode: ${opcode}`); 137 | this.stop(); 138 | position = false; 139 | break; 140 | } 141 | 142 | return position; 143 | } 144 | 145 | async HandleAuthLogonProof(opcode, data) { 146 | this.logger.debug("[AuthClient] Handling Auth Logon Proof"); 147 | const packet = Buffer.alloc(1 + data.length); 148 | packet.writeUInt8(opcode, 0); 149 | data.copy(packet, 1, 0); 150 | this.onData(packet); 151 | 152 | const result = data.readUInt8(0x0); 153 | this.logger.debug(`[AuthClient] Auth Logon Proof Result: ${result}`); 154 | if (result === 0x00) { 155 | // success 156 | let position = data.length; 157 | return { position: position, payload: packet }; 158 | } else { 159 | // fail 160 | return false; 161 | } 162 | } 163 | 164 | async HandleAuthLogonChallenge(opcode, data) { 165 | this.logger.debug("[AuthClient] Handling Auth Logon Challenge"); 166 | let position = 1; 167 | let result = data.readUInt8(0x1 - position); 168 | if (CMD_AUTH_LOGON_CHALLENGE === opcode) { 169 | result = data.readUInt8(0x2 - position); 170 | } 171 | 172 | this.logger.debug(`[AuthClient] Result: ${result}`); 173 | 174 | const payload = Buffer.alloc(data.length + 1); 175 | payload.writeUInt8(opcode, 0); 176 | data.copy(payload, 1, 0); 177 | this.logger.debug( 178 | `[AuthClient] Auth Logon Challenge Payload: ${payload.toString("hex")}` 179 | ); 180 | this.onData(payload); 181 | 182 | if (result === 0x00) { 183 | // Success 184 | return { position: payload.length - 1, payload: payload }; 185 | } 186 | 187 | return false; 188 | } 189 | 190 | async HandleRealmList(data) { 191 | this.logger.debug("[AuthClient] Handling Realm List"); 192 | let position = 1; 193 | const size = data.readUInt8(0x1 - position); 194 | const realm_count = data.readUInt8(0x7 - position); 195 | 196 | this.logger.debug( 197 | `[AuthClient] Realm Count: ${realm_count}, size: ${size}, packetSize: ${data.length}` 198 | ); 199 | 200 | let new_data = data; 201 | 202 | position = 0x7; 203 | for (let i = 0; i < realm_count; i++) { 204 | let realmName = ""; 205 | position = 0x4 + position; 206 | while (data.readUInt8(position) !== 0) { 207 | realmName += String.fromCharCode(data.readUInt8(position)); 208 | position++; 209 | } 210 | 211 | position++; 212 | let address_port = ""; 213 | let start_address_position = position; 214 | let end_address_position = position; 215 | while (data.readUInt8(position) !== 0) { 216 | address_port += String.fromCharCode(data.readUInt8(position)); 217 | if (String.fromCharCode(data.readUInt8(position)) == ":") { 218 | end_address_position = position; 219 | } 220 | position++; 221 | } 222 | 223 | position += 7; 224 | const realm_id = data.readUInt8(position); 225 | position += 1; 226 | 227 | this.logger.debug( 228 | `[AuthClient] Realm Name: ${realmName}, Address: ${address_port}, Realm ID: ${realm_id}` 229 | ); 230 | 231 | let new_address_port = this.config.relay_ip; 232 | 233 | let different_length = 234 | new_address_port.length - 235 | (end_address_position - start_address_position); 236 | let temp_packet = Buffer.alloc(new_data.length + different_length); 237 | new_data.copy(temp_packet, 0, 0, start_address_position); 238 | temp_packet.write(new_address_port, start_address_position); 239 | new_data.copy( 240 | temp_packet, 241 | start_address_position + new_address_port.length, 242 | end_address_position 243 | ); 244 | 245 | temp_packet.writeUInt8(temp_packet.length - 2, 0); 246 | new_data = temp_packet; 247 | 248 | if (size <= position) { 249 | break; 250 | } 251 | 252 | position -= 1; 253 | } 254 | 255 | const new_packet = Buffer.alloc(new_data.length + 1); 256 | new_packet.writeUInt8(CMD_REALM_LIST, 0); 257 | new_data.copy(new_packet, 1, 0); 258 | this.onData(new_packet); 259 | return { position: data.length, payload: new_packet }; 260 | } 261 | 262 | onSocketError(error) { 263 | this.logger.error(`[AuthClient] Error: ${error.message}`); 264 | this.stop(); 265 | } 266 | 267 | onSocketClose() { 268 | this.logger.info("[AuthClient] Connection closed"); 269 | this.stop(); 270 | } 271 | 272 | onSocketTimeout() { 273 | this.logger.info("[AuthClient] Connection timeout"); 274 | this.stop(); 275 | } 276 | 277 | WriteData(data) { 278 | if (this.isEnded) { 279 | return; 280 | } 281 | 282 | this.logger.debug(`[AuthClient] Sending ${data.length} bytes`); 283 | this.socket.write(data); 284 | } 285 | 286 | stop() { 287 | if (this.isEnded) { 288 | return; 289 | } 290 | 291 | this.isEnded = true; 292 | this.logger.info("[AuthClient] Stopping"); 293 | this.onStop(); 294 | this.socket.destroy(); 295 | } 296 | } 297 | 298 | export default AuthClient; 299 | -------------------------------------------------------------------------------- /AuthSession.js: -------------------------------------------------------------------------------- 1 | // Developed by: Amin.MasterkinG (https://masterking32.com) 2 | // Github: https://github.com/masterking32/WoW-Server-Relay 3 | // Year: 2024 4 | 5 | import AuthClient from "./AuthClient.js"; 6 | import { 7 | CMD_AUTH_LOGON_CHALLENGE, 8 | CMD_AUTH_LOGON_PROOF, 9 | CMD_AUTH_RECONNECT_CHALLENGE, 10 | CMD_AUTH_RECONNECT_PROOF, 11 | CMD_REALM_LIST, 12 | CMD_SURVEY_RESULT, 13 | CMD_XFER_ACCEPT, 14 | CMD_XFER_CANCEL, 15 | CMD_XFER_RESUME, 16 | RELAY_SERVER_CMD_AUTH, 17 | } from "./opcodes.js"; 18 | class AuthSession { 19 | constructor(config, socket, logger) { 20 | this.config = config; 21 | this.socket = socket; 22 | this.logger = logger; 23 | this.status = "init"; 24 | this.ClientIP = socket.remoteAddress.includes("::ffff:") 25 | ? socket.remoteAddress.replace("::ffff:", "") 26 | : socket.remoteAddress; 27 | this.isEnded = false; 28 | } 29 | 30 | run() { 31 | this.socket.on("data", this.onSocketData.bind(this)); 32 | this.socket.on("close", this.onSocketClose.bind(this)); 33 | this.socket.on("error", this.onSocketError.bind(this)); 34 | this.socket.on("timeout", this.onSocketTimeout.bind(this)); 35 | } 36 | 37 | async onSocketData(data) { 38 | let bytes = data.length; 39 | this.logger.debug(`[AuthSession] Received ${data.length} bytes`); 40 | let position = 0; 41 | while (bytes > 0) { 42 | const opcode = data.readUInt8(position); 43 | position += 1; 44 | bytes -= 1; 45 | const new_position = await this.HandleOpcode( 46 | opcode, 47 | data.slice(position) 48 | ); 49 | if (!new_position) { 50 | break; 51 | } 52 | position += new_position; 53 | bytes -= new_position; 54 | } 55 | } 56 | 57 | async HandleOpcode(opcode, data) { 58 | let position = 0; 59 | switch (opcode) { 60 | case CMD_AUTH_LOGON_CHALLENGE: 61 | case CMD_AUTH_RECONNECT_CHALLENGE: 62 | const ChallengeResponse = await this.HandleAuthLogonChallenge(data); 63 | if (!ChallengeResponse) { 64 | this.stop(); 65 | } 66 | 67 | position = ChallengeResponse.position; 68 | let AuthChallengePayload = Buffer.alloc( 69 | ChallengeResponse.payload.length + 1 70 | ); 71 | AuthChallengePayload.writeUInt8(opcode, 0); 72 | ChallengeResponse.payload.copy(AuthChallengePayload, 1); 73 | 74 | this.onClientStop = () => { 75 | this.stop(); 76 | }; 77 | 78 | this.onClientData = (data) => { 79 | this.socket.write(data); 80 | this.logger.debug(`[AuthSession] Sent ${data.toString("hex")}`); 81 | }; 82 | 83 | this.client = new AuthClient( 84 | this.config, 85 | this.ClientIP, 86 | AuthChallengePayload, 87 | this.logger, 88 | this.onClientStop, 89 | this.onClientData 90 | ); 91 | 92 | this.client.run(); 93 | break; 94 | case CMD_AUTH_LOGON_PROOF: 95 | case CMD_AUTH_RECONNECT_PROOF: 96 | this.logger.debug("[AuthSession] Auth logon proof"); 97 | if (this.client) { 98 | const packet = Buffer.alloc(data.length + 1); 99 | packet.writeUInt8(opcode, 0); 100 | data.copy(packet, 1); 101 | this.client.WriteData(packet); 102 | } else { 103 | this.logger.error( 104 | "[AuthSession] Auth logon proof received without client" 105 | ); 106 | this.stop(); 107 | } 108 | 109 | position = data.length; 110 | break; 111 | case CMD_REALM_LIST: 112 | this.logger.debug("[AuthSession] Realm list"); 113 | const packet = Buffer.alloc(data.length + 1); 114 | packet.writeUInt8(CMD_REALM_LIST, 0); 115 | data.copy(packet, 1); 116 | this.client.WriteData(packet); 117 | position = data.length; 118 | break; 119 | case RELAY_SERVER_CMD_AUTH: 120 | this.logger.debug("[AuthSession] Relay server command"); 121 | const RelayServerResponse = await this.HandleRelayServerCommand(data); 122 | 123 | if (!RelayServerResponse) { 124 | this.stop(); 125 | } 126 | 127 | position = RelayServerResponse.position; 128 | break; 129 | case CMD_XFER_ACCEPT: 130 | case CMD_XFER_RESUME: 131 | case CMD_XFER_CANCEL: 132 | case CMD_SURVEY_RESULT: 133 | position = data.length; 134 | if (this.client) { 135 | const packet = Buffer.alloc(data.length + 1); 136 | packet.writeUInt8(opcode, 0); 137 | data.copy(packet, 1); 138 | this.client.WriteData(packet); 139 | } else { 140 | this.stop(); 141 | } 142 | break; 143 | default: 144 | this.logger.error(`[AuthSession] Unknown opcode ${opcode}`); 145 | this.stop(); 146 | } 147 | 148 | return position; 149 | } 150 | 151 | async HandleAuthLogonChallenge(data) { 152 | let position = 1; 153 | const protocol_version = await data.readUInt8(0x01 - position); 154 | const packet_size = await data.readUInt16LE(0x02 - position); 155 | const game_name = await data.toString( 156 | "utf8", 157 | 0x04 - position, 158 | 0x08 - position 159 | ); 160 | const versionArray = []; 161 | for (let i = 0; i < 3; i++) { 162 | const versionByte = await data.readUInt8(0x08 + i - position); 163 | versionArray.push(versionByte); 164 | } 165 | 166 | const version = versionArray.join("."); 167 | const build = await data.readUInt16LE(0x0b - position); 168 | const platform = await data.toString( 169 | "utf8", 170 | 0x0d - position, 171 | 0x0d + 4 - position 172 | ); 173 | const os = await data.toString( 174 | "utf8", 175 | 0x11 - position, 176 | 0x11 + 4 - position 177 | ); 178 | const locale = await data.toString( 179 | "utf8", 180 | 0x15 - position, 181 | 0x15 + 4 - position 182 | ); 183 | const timezone_bias = await data.readInt32LE(0x19 - position); 184 | const ip = await data.readUInt32LE(0x1d - position); 185 | const username_length = await data.readUInt8(0x21 - position); 186 | const username = await data.toString( 187 | "utf8", 188 | 0x22 - position, 189 | 0x22 + username_length - position 190 | ); 191 | 192 | this.logger.debug( 193 | `[AuthSession] Protocol Version: ${protocol_version}, Packet Size: ${packet_size}, Game Name: ${game_name}, Version: ${version}, Build: ${build}, Platform: ${platform 194 | .split("") 195 | .reverse() 196 | .join("")}, OS: ${os.split("").reverse().join("")}, Locale: ${locale 197 | .split("") 198 | .reverse() 199 | .join( 200 | "" 201 | )}, Timezone Bias: ${timezone_bias}, IP: ${ip}, Username Length: ${username_length}, Username: ${username}` 202 | ); 203 | 204 | if (version !== this.config.game_version) { 205 | this.logger.error(`[AuthSession] Invalid version: ${version}`); 206 | return false; 207 | } 208 | 209 | if (build !== this.config.build) { 210 | this.logger.error(`[AuthSession] Invalid build: ${build}`); 211 | return false; 212 | } 213 | if (!username_length) { 214 | this.logger.error( 215 | `[AuthSession] Invalid username length: ${username_length}` 216 | ); 217 | return false; 218 | } 219 | 220 | if (username_length + 0x22 - 4 !== packet_size) { 221 | this.logger.error(`[AuthSession] Invalid packet size: ${packet_size}`); 222 | return false; 223 | } 224 | 225 | position = 0x22 + username_length - position; 226 | 227 | const output = { 228 | protocol_version: protocol_version, 229 | packet_size: packet_size, 230 | game_name: game_name, 231 | version: version, 232 | build: build, 233 | platform: platform, 234 | os: os, 235 | locale: locale, 236 | timezone_bias: timezone_bias, 237 | ip: ip, 238 | position: position, 239 | payload: data, 240 | }; 241 | 242 | return output; 243 | } 244 | 245 | // ? With this custom packet we can get the real user IP and forward it to the main server 246 | // ? Then if you ban the user, you can ban the user by IP without any problems! 247 | async HandleRelayServerCommand(data) { 248 | const secret_key_length = await data.readUInt16LE(0); 249 | const client_ip_length = await data.readUInt16LE(2); 250 | const secret_key = await data.toString("utf8", 4, 4 + secret_key_length); 251 | const client_ip = await data.toString( 252 | "utf8", 253 | 4 + secret_key_length, 254 | 4 + secret_key_length + client_ip_length 255 | ); 256 | 257 | this.logger.debug( 258 | `[AuthSession] Secret Key Length: ${secret_key_length}, Secret Key: ${secret_key}, Client IP Length: ${client_ip_length}, Client IP: ${client_ip}` 259 | ); 260 | 261 | if (secret_key !== this.config.secret_key) { 262 | this.logger.error("[AuthSession] Invalid secret key"); 263 | this.stop(); 264 | } 265 | 266 | const output = { 267 | secret_key_length: secret_key_length, 268 | secret_key: secret_key, 269 | client_ip_length: client_ip_length, 270 | client_ip: client_ip, 271 | position: 4 + secret_key_length + client_ip_length, 272 | }; 273 | 274 | return output; 275 | } 276 | 277 | onSocketClose() { 278 | this.logger.debug("[AuthSession] Connection closed"); 279 | this.stop(); 280 | } 281 | 282 | onSocketError(error) { 283 | this.logger.debug("[AuthSession] Connection error: " + error.message); 284 | this.stop(); 285 | } 286 | onSocketTimeout() { 287 | this.logger.debug("[AuthSession] Connection timeout"); 288 | this.stop(); 289 | } 290 | 291 | stop() { 292 | if (this.isEnded) { 293 | return; 294 | } 295 | 296 | this.logger.debug("[AuthSession] Stopping session"); 297 | this.isEnded = true; 298 | 299 | if (this.client) { 300 | this.client.stop(); 301 | } 302 | 303 | this.socket.destroy(); 304 | } 305 | } 306 | 307 | export default AuthSession; 308 | -------------------------------------------------------------------------------- /README_ES.md: -------------------------------------------------------------------------------- 1 | # World of Warcraft (WoW) Servidor de retransmisión 2 | 3 | [Ingles](README.md) | [Español](README_ES.md) 4 | 5 | Este proyecto permite la creación de servidores adicionales que funcionan como una Red de Entrega de Contenido (CDN) para servidores privados de World of Warcraft. Le permite ocultar la IP de su servidor principal a los usuarios. Los jugadores se conectan a los servidores de retransmisión, que luego manejan y reenvían paquetes al servidor principal. Esta configuración no solo protege su servidor principal de ataques DDoS, sino que también brinda una experiencia de juego más fluida al tener CDN en diferentes ubicaciones. 6 | 7 | ### ⭐ Si encuentra útil este proyecto, ¡no dude en darle una estrella! ⭐ 8 | 9 | Este proyecto lee y maneja paquetes del cliente para AuthServer y funciona como un servidor de autenticación WoW y un cliente WoW. Además, edita `REALMLIST_PACKET` para reemplazar la IP del servidor principal con la IP de retransmisión. Las contribuciones son bienvenidas. 10 | 11 | ## ¿Cómo funciona? 12 | 13 | ![How it Works?](https://raw.githubusercontent.com/masterking32/WoW-Server-Relay/main/docs/how-works.png) 14 | 15 | # ¿Por qué deberíamos utilizar esta herramienta y qué la hace diferente? 16 | 17 |
18 |

1) ¿Agregar otro nodo en la red aumenta el ping?

19 | Al contrario de lo que algunos puedan creer, agregar otro nodo en realidad puede disminuir el ping de los usuarios. Por ejemplo, si tu servidor está ubicado en la UE, pero tienes jugadores en América del Norte y del Sur, cada jugador tendrá una ruta de red diferente hacia la UE. Si establece un servidor en EE. UU. con una mejor ruta a su servidor de la UE, los jugadores pueden conectarse a su servidor de EE. UU. Este servidor luego reenviará los paquetes a través de la mejor ruta, lo que resultará en un ping mejorado para los jugadores. 20 |
21 | 22 |
23 |

2) ¿Cómo mitiga los ataques DDoS?

24 | La mayoría de los ataques DDoS utilizan tipos de paquetes como UDP, ACK, SYN, etc. Esta herramienta no reenvía todos los tipos de estos ataques a su servidor principal. Al implementar límites de velocidad en su UFW/IPtable, puede proteger aún más su servidor principal de ataques DDoS. Si uno de tus servidores está siendo atacado, algunos usuarios conectados a ese servidor pueden desconectarse, pero otros aún pueden jugar. Si bien esta herramienta puede ayudar a mitigar los efectos de los ataques DDoS, no proporciona una protección del 100%. Simplemente agrega una capa adicional de seguridad de la red. 25 |
26 | 27 |
28 |

3) ¿Por qué deberíamos usar esto en lugar de Load Balancers, IPTable forwards y otras herramientas proxy?

29 | 30 | #### Número 1: 31 | 32 | Si bien puede utilizar otras herramientas para reenviar paquetes, balanceadores de carga, etc., es importante comprender que, de forma predeterminada, TrinityCore/AzerothCore recupera la IP del usuario de la IP del socket remoto. Esto significa que cuando usas algo como IPTable, la IP del usuario en el servidor WoW es la IP de tu servidor de retransmisión. Por ejemplo, si la IP de `us-relay1` es `8.8.8.8` y un jugador conectado a ese servidor intenta ingresar la contraseña incorrecta varias veces, el servidor prohibirá `8.8.8.8` en lugar de la IP del usuario. En consecuencia, nadie puede conectarse al servidor desde el nodo `us-relay1`. Para los usuarios conectados al servidor WoW desde el nodo `us-relay1`, la IP siempre será `8.8.8.8`, y en el juego, si no puedes recuperar la IP del jugador real, siempre verás las IP del nodo de retransmisión. 33 | 34 | #### ¿Cómo lo arreglaste? 35 | 36 | Este proyecto funciona como otros reenviadores de forma predeterminada, pero con una diferencia: solo funciona para WoW y lee, analiza y maneja paquetes. Para solucionar el problema de lectura de IP, agregamos un paquete personalizado para WorldServer y AuthServer con estos códigos de operación: 37 | 38 | ``` 39 | RELAY_SERVER_CMD_AUTH = 0x64 // 100 40 | RELAY_SERVER_CMD_WORLD = 0xA32 // 2610 41 | ``` 42 | 43 | Si habilita `send_relay_packet` en el archivo de configuración, este proyecto enviará un paquete de retransmisión al servidor mundial y de autenticación después de abrir una conexión de socket. Este paquete incluye una clave secreta y la IP real del usuario. Sus servidores Auth y World deben analizar este paquete y reemplazar la IP del usuario con la IP dentro de este paquete. 44 | 45 | #### Estructura de paquetes para AuthServer 46 | 47 | | Offset | Size | Type | Name | Description | 48 | | ------ | ---- | ------ | ---------- | -------------------------------------------------------------- | 49 | | 0x0 | 1 | uint8 | OpCode | Opcode for relay custom packet. `RELAY_SERVER_CMD_AUTH = 0x64` | 50 | | 0x1 | 2 | uint16 | Secret_Len | Secret key length | 51 | | 0x3 | 2 | uint16 | IP_len | The length of user IP | 52 | | 0x5 | - | String | Secret_Key | The secret key value starts from 0x5 and ends with Secret_Len | 53 | | - | - | String | User_IP | User IP address | 54 | 55 | #### Estructura de paquetes para WorldServer 56 | 57 | #### ENCABEZADO 58 | 59 | | Offset | Size | Type | Name | Description | 60 | | ------ | ---- | ------ | ---- | ------------------------------------------------------------------------------------------- | 61 | | 0x0 | 2 | uint16 | Size | Packet Header - Size of Packet (Size of the packet including the opcode field.) | 62 | | 0x2 | 4 | uint32 | CMD | Packet Header - Opcode or Command for relay custom packet. `RELAY_SERVER_CMD_WORLD = 0xA32` | 63 | 64 | #### CUERPO 65 | 66 | | Offset | Size | Type | Name | Description | 67 | | ------ | ---- | ------ | ---------- | ----------------------------------------------------------------------------------------- | 68 | | 0x0 | - | String | Secret_Key | The secret key value starts from 0x6 and ends with Secret_Len. `(Null terminated string)` | 69 | | - | - | String | User_IP | User IP address. `(Null terminated string)` | 70 |
71 | 72 | --- 73 | 74 | ## ¿TrinityCore/AzerothCore admite este paquete? 75 | 76 | - ## Cambios personalizados de TrinityCore: 77 | 78 | Para TrinityCore, puede consultar [masterking32/TrinityCore-Relay-Support](https://github.com/masterking32/TrinityCore-Relay-Support) y [este compromiso específico](https://github.com/masterking32/TrinityCore-Relay-Support/commit/cb5aa9eefd4caec032864b9249fd16341ab64b73) para la versión 3.3.5. Estos recursos lo guiarán sobre cómo realizar cambios personalizados en su núcleo para admitir el manejo y análisis del paquete de retransmisión. 79 | 80 | - ## Módulo/cambios personalizados de AzerothCore: 81 | 82 | Esta sección aún no está lista. Puedes implementarlo de manera similar a TrinityCore, con algunas modificaciones. Si logra hacerlo, hágamelo saber para que podamos actualizar esta parte. 83 | 84 | --- 85 | 86 | **Tenga en cuenta: si no ha realizado ningún cambio personalizado en el núcleo, asegúrese de que `send_relay_packet` esté configurado en `false`. Si ha realizado cambios personalizados, establezca `send_relay_packet` en `true` y establezca una `secret_key` segura que tenga entre 32 y 64 caracteres (el valor máximo permitido es 64). Esta `clave_secreta` debe ser la misma tanto en el archivo `config.js` de este proyecto como en sus archivos de configuración principales, `authserver.conf` y `worldserver.conf`.** 87 | 88 | --- 89 | 90 | # Guía de instalación para Ubuntu/Debian 91 | 92 | 1. **Instale los paquetes necesarios:** 93 | 94 | ```bash 95 | apt install curl git nano sudo 96 | ``` 97 | 98 | 2. **Instale NodeJS (versión 20 o superior):** 99 | 100 | ```bash 101 | curl -fsSL https://deb.nodesource.com/setup_20.x | sudo bash - 102 | sudo apt-get install -y nodejs 103 | ``` 104 | 105 | 3. **Clone el proyecto:** 106 | 107 | ```bash 108 | git clone https://github.com/masterking32/WoW-Server-Relay 109 | cd WoW-Server-Relay 110 | ``` 111 | 112 | 4. **Instalar dependencias (npm):** 113 | 114 | ```bash 115 | npm install 116 | ``` 117 | 118 | 5. **Configurar el proyecto:** 119 | 120 | ```bash 121 | cp config.js.sample config.js 122 | nano config.js 123 | ``` 124 | 125 | 6. **Ejecute el proyecto:** 126 | 127 | ```bash 128 | node app.js 129 | ``` 130 | 131 | 7. **Ejecutar como servicio/inicio:** 132 | 133 | ```bash 134 | npm install pm2 -g 135 | pm2 start app.js 136 | pm2 startup 137 | pm2 save 138 | ``` 139 | 140 | **Nota:** Para un rendimiento óptimo, soporte para IP de usuario real y para garantizar que la función de prohibición de IP funcione en su servidor, debe realizar algunas modificaciones en su núcleo. Lea [esta sección](https://github.com/masterking32/WoW-Server-Relay?tab=readme-ov-file#does-trinitycoreazerothcore-support-this-packet) y aplique los cambios necesarios a su núcleo. 141 | 142 | --- 143 | 144 | # Instalación en Windows: 145 | 146 | 1. Descargue e instale la última versión de [NodeJS](https://nodejs.org/en). 147 | 2. Descargue el proyecto y extraiga los archivos. 148 | 3. Navegue hasta el directorio del proyecto y cambie el nombre de `config.js.sample` a `config.js`. 149 | 4. Modifique el archivo `config.js` con la información de su servidor. 150 | 5. Abra el `Símbolo del sistema`, navegue hasta el directorio del proyecto. 151 | 6. Ejecute el comando `node app.js`. 152 | 7. Asegúrese de que los puertos necesarios estén abiertos en su firewall. 153 | 154 | **Nota:** Para un rendimiento óptimo, soporte para IP de usuario real y para garantizar que la función de prohibición de IP funcione en su servidor, debe realizar algunas modificaciones en su núcleo. Lea [esta sección](https://github.com/masterking32/WoW-Server-Relay?tab=readme-ov-file#does-trinitycoreazerothcore-support-this-packet) y aplique los cambios necesarios a su núcleo. 155 | 156 | --- 157 | 158 | ## Información del desarrollador 159 | 160 | Este proyecto fue desarrollado por [Amin.MasterkinG](https://masterking32.com). También puedes encontrarme en [Github](https://github.com/masterking32). 161 | --------------------------------------------------------------------------------