├── .gitignore ├── edit.sh ├── run.sh ├── compile.sh ├── LICENSE.txt ├── include └── srcon.h ├── README.md └── src └── srcon.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | 3 | src/client.cpp 4 | 5 | **/*.swp 6 | -------------------------------------------------------------------------------- /edit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | vim -p ./src/* ./include/* 4 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./compile.sh 4 | 5 | PROJECT_NAME=`basename $(pwd)` 6 | 7 | if [ -f ./bin/${PROJECT_NAME} ]; then 8 | ./bin/${PROJECT_NAME} 9 | fi 10 | -------------------------------------------------------------------------------- /compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PROJECT_NAME=`basename $(pwd)` 4 | rm -f bin/${PROJECT_NAME} 5 | echo Compiling $PROJECT_NAME... 6 | 7 | g++ `ls -d $PWD/src/*` -o bin/${PROJECT_NAME} -static-libgcc -static-libstdc++ -std=c++17 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Filip Tomaszewski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/srcon.h: -------------------------------------------------------------------------------- 1 | #ifndef SCRON_H 2 | #define SCRON_H 3 | 4 | 5 | #include 6 | 7 | 8 | #define SERVERDATA_AUTH 3 9 | #define SERVERDATA_EXECCOMMAND 2 10 | #define SERVERDATA_AUTH_RESPONSE 2 11 | #define SERVERDATA_RESPONSE_VALUE 0 12 | 13 | #define SRCON_DEFAULT_TIMEOUT 4 14 | #define SRCON_HEADER_SIZE 14 15 | #define SRCON_SLEEP_THRESHOLD 1024 16 | #define SRCON_SLEEP_MILLISECONDS 500 17 | 18 | struct srcon_addr{ 19 | std::string addr; 20 | int port; 21 | std::string pass; 22 | }; 23 | 24 | class srcon{ 25 | const srcon_addr addr; 26 | const int sockfd; 27 | unsigned int id; 28 | bool connected; 29 | 30 | public: 31 | srcon(const srcon_addr addr, const int timeout=SRCON_DEFAULT_TIMEOUT); 32 | srcon(const std::string address, const int port, const std::string password, const int timeout=SRCON_DEFAULT_TIMEOUT); 33 | virtual ~srcon(); 34 | 35 | std::string send(const std::string message, int type=SERVERDATA_EXECCOMMAND); 36 | 37 | inline bool get_connected() const{ 38 | return connected; 39 | } 40 | 41 | inline bool is_connected() const{ 42 | return get_connected(); 43 | } 44 | 45 | inline srcon_addr get_addr() const{ 46 | return addr; 47 | } 48 | 49 | private: 50 | bool connect(int timeout=SRCON_DEFAULT_TIMEOUT) const; 51 | std::string recv(unsigned long) const; 52 | size_t read_packet_len() const; 53 | void pack(unsigned char packet[], std::string data, int packet_len, int id, int type) const; 54 | unsigned char* read_packet(unsigned int&, bool&) const; 55 | size_t byte32_to_int(unsigned char*) const; 56 | }; 57 | 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C++ Source RCON implementation 2 | 3 | Uses Valve's [Source RCON protocol](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol) for communicating with remote Source engine servers. 4 | 5 | # Contents 6 | * [Features](#features-) 7 | * [Example Usage](#example-usage-) 8 | * [Constructors](#constructors-) 9 | * [Address Structure](#address-structure-) 10 | * [Multi-packet Response](#multi-packet-response-) 11 | 12 | # Features [^](#contents) 13 | 14 | * Implemented as a C++ class 15 | * Exposes a very simple interface 16 | * Supports multi-packet responses 17 | * Non-threaded, delays for larger reponses 18 | * Connection timeout (default 4 seconds) 19 | 20 | **Notes about this implementation**: 21 | 22 | * This is not a standalone application. 23 | * It works on UNIX systems **only** as of now. 24 | 25 | # Example Usage [^](#contents) 26 | 27 | Prints `test`. 28 | 29 | ```c++ 30 | srcon client = srcon("127.0.0.1", 27015, "password"); 31 | std::string response = client.send("echo test"); 32 | std::cout << response << std::endl; 33 | ``` 34 | 35 | # Constructors [^](#contents) 36 | 37 | 1. `srcon(srcon_addr addr, int timeout=SRCON_DEFAULT_TIMEOUT)` 38 | * `addr` — [`srcon_addr` struct](#address-structure-) 39 | * `timeout` — connection timeout in seconds (defaults to 4) 40 | 1. `srcon(std::string address, int port, std::string pass, int timeout=SRCON_DEFAULT_TIMEOUT)` 41 | * `address` — address of the server 42 | * `port` — port of the server 43 | * `pass` — rcon password of the server 44 | * `timeout` — connection timeout in seconds (defaults to 4) 45 | 46 | # Address Structure [^](#contents) 47 | 48 | `srcon_addr` struct consists of: 49 | 50 | 1. `std::string addr` — address of the server 51 | 1. `int port` — port of the server 52 | 1. `std::string pass` — rcon password of the server 53 | 54 | # Multi-packet Response [^](#contents) 55 | 56 | Using a method by Koraktor as explained [here](https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Multiple-packet_Responses). 57 | 58 | After each `SERVERDATA_EXECCOMMAND` request, a `SERVERDATA_RESPONSE_VALUE` packet is sent, which is used as a terminator package. This adds overhead to communication, but assures a reliable reception of all responses. For lengths of over 1024 bytes, the thread halts to let the buffer get filled (0.5 seconds). 59 | -------------------------------------------------------------------------------- /src/srcon.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../include/srcon.h" 12 | 13 | //#define DEBUG 1 14 | #ifdef DEBUG 15 | #define LOG(str) std::clog << str << std::endl; 16 | #else 17 | #define LOG(str) 18 | #endif 19 | 20 | srcon::srcon(const std::string address, const int port, const std::string pass, const int timeout) 21 | : srcon(srcon_addr{address, port, pass}, timeout){} 22 | 23 | srcon::srcon(const srcon_addr addr, const int timeout): 24 | addr(addr), 25 | sockfd(socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)), 26 | id(0), 27 | connected(false) 28 | { 29 | if(sockfd == -1){ 30 | LOG("Error opening socket."); 31 | return; 32 | } 33 | 34 | LOG("Socket (" << sockfd << ") opened, connecting..."); 35 | if(!connect(timeout)){ 36 | LOG("Connection not established."); 37 | close(sockfd); 38 | return; 39 | } 40 | 41 | LOG("Connection established!"); 42 | connected = true; 43 | 44 | send(addr.pass, SERVERDATA_AUTH); 45 | unsigned char buffer[SRCON_HEADER_SIZE]; 46 | ::recv(sockfd, buffer, SRCON_HEADER_SIZE, SERVERDATA_RESPONSE_VALUE); 47 | ::recv(sockfd, buffer, SRCON_HEADER_SIZE, SERVERDATA_RESPONSE_VALUE); 48 | } 49 | 50 | srcon::~srcon(){ 51 | if(get_connected()) 52 | close(sockfd); 53 | } 54 | 55 | bool srcon::connect(const int timeout) const{ 56 | struct sockaddr_in server; 57 | bzero((char*)&server, sizeof(server)); 58 | server.sin_family = AF_INET; 59 | server.sin_addr.s_addr = inet_addr(addr.addr.c_str()); 60 | server.sin_port = htons(addr.port); 61 | 62 | fcntl(sockfd, F_SETFL, O_NONBLOCK); 63 | 64 | struct timeval tv; 65 | tv.tv_sec = timeout; 66 | tv.tv_usec = 0; 67 | fd_set fds; 68 | FD_ZERO(&fds); 69 | FD_SET(sockfd, &fds); 70 | 71 | int status = -1; 72 | if((status = ::connect(sockfd, (struct sockaddr *)&server, sizeof(server))) == -1) 73 | if(errno != EINPROGRESS) 74 | return false; 75 | 76 | status = select(sockfd +1, NULL, &fds, NULL, &tv); 77 | fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) & ~O_NONBLOCK); 78 | return status != 0; 79 | } 80 | 81 | std::string srcon::send(const std::string data, const int type){ 82 | LOG("Sending: " << data); 83 | if(!get_connected()) 84 | return "Connection has not been established."; 85 | 86 | int packet_len = data.length() +SRCON_HEADER_SIZE; 87 | unsigned char packet[packet_len]; 88 | pack(packet, data, packet_len, srcon::id++, type); 89 | if(::send(sockfd, packet, packet_len, 0) < 0) 90 | return "Sending failed!"; 91 | 92 | if(type != SERVERDATA_EXECCOMMAND) 93 | return ""; 94 | 95 | unsigned long halt_id = id; 96 | send("", SERVERDATA_RESPONSE_VALUE); 97 | return recv(halt_id); 98 | } 99 | 100 | std::string srcon::recv(unsigned long halt_id) const{ 101 | unsigned int bytes = 0; 102 | unsigned char* buffer = nullptr; 103 | std::string response; 104 | bool can_sleep = true; 105 | while(1){ 106 | delete [] buffer; 107 | buffer = read_packet(bytes, can_sleep); 108 | if(byte32_to_int(buffer) == halt_id) 109 | break; 110 | int offset = bytes -SRCON_HEADER_SIZE +3; 111 | if(offset == -1) 112 | continue; 113 | std::string part(&buffer[8], &buffer[8] +offset); 114 | response += part; 115 | } 116 | delete [] buffer; 117 | buffer = read_packet(bytes, can_sleep); 118 | delete [] buffer; 119 | return response; 120 | } 121 | 122 | unsigned char* srcon::read_packet(unsigned int& size, bool& can_sleep) const{ 123 | size_t len = read_packet_len(); 124 | if(can_sleep && len > SRCON_SLEEP_THRESHOLD){ 125 | std::this_thread::sleep_for(std::chrono::milliseconds(SRCON_SLEEP_MILLISECONDS)); 126 | can_sleep = false; 127 | } 128 | unsigned char* buffer = new unsigned char[len]{0}; 129 | unsigned int bytes = 0; 130 | do bytes += ::recv(sockfd, buffer, len -bytes, 0); 131 | while(bytes < len); 132 | size = len; 133 | return buffer; 134 | } 135 | 136 | size_t srcon::read_packet_len() const{ 137 | unsigned char* buffer = new unsigned char[4]{0}; 138 | ::recv(sockfd, buffer, 4, 0); 139 | const size_t len = byte32_to_int(buffer); 140 | delete [] buffer; 141 | return len; 142 | } 143 | 144 | void srcon::pack(unsigned char packet[], const std::string data, int packet_len, int id, int type) const{ 145 | int data_len = packet_len -SRCON_HEADER_SIZE; 146 | bzero(packet, packet_len); 147 | packet[0] = data_len +10; 148 | packet[4] = id; 149 | packet[8] = type; 150 | for(int i = 0; i < data_len; i++) 151 | packet[12 +i] = data.c_str()[i]; 152 | } 153 | 154 | size_t srcon::byte32_to_int(unsigned char* buffer) const{ 155 | return static_cast( 156 | static_cast(buffer[0]) | 157 | static_cast(buffer[1]) << 8 | 158 | static_cast(buffer[2]) << 16 | 159 | static_cast(buffer[3]) << 24 ); 160 | } 161 | --------------------------------------------------------------------------------