Skip to content

Instantly share code, notes, and snippets.

@TerrorBite
Last active July 18, 2020 22:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TerrorBite/4948812 to your computer and use it in GitHub Desktop.
Save TerrorBite/4948812 to your computer and use it in GitHub Desktop.
MCPing Minecraft ping utility
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/time.h>
#include <iconv.h>
#include <errno.h>
#include <argp.h>
void error(const char* msg) {
perror(msg);
exit(0);
}
/* Begin ArgP stuff */
/* Version string and maintainer */
const char *argp_program_version =
"MCPing v1.0";
const char *argp_program_bug_address =
"TerrorBite <terrorbite@lethargiclion.net>";
/* Documentation string */
static char doc[] = "Ping a Minecraft server via the Minecraft protocol\n\n\
HOST is the hostname or IP address to ping. By default, 5 packets are sent to port 25565.";
/* List of mandatory arguments, space-separated. Used for documentation */
static char args_doc[] = "HOST";
/* For ease of alteration, we have this */
#define MCPING_ARG_COUNT 1
/* List of optional arguments, with descriptions */
static struct argp_option options[] = {
{"count", 'c', "COUNT", 0, "Send COUNT pings to the server"},
{"port", 'p', "PORT", 0, "Use an alternate port instead of 25565"},
{0}
};
/* Data structure to hold the parsed arguments passed into the program */
struct arguments {
/* We have only one non-named (mandatory) argument.
* This code can be expanded to support more than one */
char *args[MCPING_ARG_COUNT];
/* Optional arguments */
long count;
uint16_t port;
};
static error_t parse_opt(int key, char* arg, struct argp_state* state) {
/* Get the input argument from argp_parse, which we
know is a pointer to our arguments structure. */
struct arguments* arguments = state->input;
long temp;
switch(key) {
case 'c':
temp = strtol(arg, (char**)NULL, 10);
if(errno) error("Error parsing COUNT");
if(temp<=0) {fprintf(stderr, "COUNT must be greater than 0\n"); exit(1); }
arguments->count = temp;
break;
case 'p':
temp = strtol(arg, (char**)NULL, 10);
if(errno) error("Error parsing COUNT");
if(temp<0||temp>65535) {fprintf(stderr, "PORT must be between 0 and 65535\n"); exit(1); }
arguments->port = (uint16_t)(temp & 0xFFFF);
break;
case ARGP_KEY_ARG:
/* Reached when parsing a non-named (mandatory) argument */
if(state->arg_num >= MCPING_ARG_COUNT) /* Too many arguments */
argp_usage(state);
arguments->args[state->arg_num] = arg;
break;
case ARGP_KEY_END:
/* Reached when all non-named (mandatory) arguments have been parsed */
if(state->arg_num < MCPING_ARG_COUNT) /* Not enough arguments were passed */
argp_usage(state);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
/* This is our argp parser */
static struct argp argp = { options, parse_opt, args_doc, doc };
/* End ArgP stuff */
typedef struct {
char* ptr;
int proto;
char* version;
char* motd;
int players;
int maxplayers;
} mc_resp;
char* nultok(char* src) {
static char* _src;
if(src!=NULL) _src = src;
while(*_src != '\0') ++_src;
++_src;
return _src;
}
mc_resp* decode(char* response, size_t length) {
/* Discard first three bytes (which are 0xFF = kick packet, then 2 bytes giving the length of the following data) */
char* src = response+3;
size_t srclen = (length-3);
mc_resp* data;
/* Set up buffer for iconv, and get an extra pointer to it */
char* buffer[256]; size_t buflen = 256;
char* bufptr = (char*)buffer; bzero(bufptr, buflen);
/* Convert UCS-2 multibyte data into UTF-8 */
iconv_t conv = iconv_open("UTF-8", "UCS-2BE");
iconv(conv, &src, &srclen, &bufptr, &buflen);
if(errno) error("Failed to decode server data");
iconv_close(conv);
/* Compute the size of the converted string and allocate enough space to hold it */
int len=257-buflen;
char* dest = malloc(len);
/* Copy buffer into allocated space */
bzero(dest, len);
bcopy(buffer, dest, len);
/* Tokenize data into struct and return a pointer to it*/
data = malloc(sizeof(mc_resp));
data->ptr = dest;
data->proto = atoi(nultok(dest));
data->version = nultok(NULL);
data->motd = nultok(NULL);
data->players = atoi(nultok(NULL));
data->maxplayers = atoi(nultok(NULL));
return data;
}
void decode_free(mc_resp* data) {
/* Free the data structure and its underlying memory */
free(data->ptr);
free(data);
}
unsigned long time_diff(struct timeval x , struct timeval y) {
time_t x_ms , y_ms , diff;
x_ms = x.tv_sec*1000000 + x.tv_usec;
y_ms = y.tv_sec*1000000 + y.tv_usec;
diff = y_ms - x_ms;
return (unsigned long)diff;
}
int main(int argc, char* argv[]) {
int sockfd, n;
long i, pings;
struct sockaddr_in serv_addr;
struct hostent *server;
struct timeval before, after;
long min, max, avg;
/* This buffer will hold the data returned by the server */
char buffer[256];
/* Set default option values. */
struct arguments args;
args.port = 25565;
args.count = 5;
/* Parse arguments into the args struct. */
argp_parse(&argp, argc, argv, 0, 0, &args);
/* Get server host */
server = gethostbyname(args.args[0]);
if (server == NULL) {
fprintf(stderr,"%s: no such host %s\n", argv[0], args.args[0]);
exit(0);
}
/* Set up server address struct */
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr,
(char *)&serv_addr.sin_addr.s_addr,
server->h_length);
serv_addr.sin_port = htons(args.port);
/* Set up stats vars */
avg=0;
pings=0;
printf("Pinging %s:%d with %ld Minecraft server list ping packets\n", args.args[0], args.port, args.count);
for(i=0; i<args.count; i++) {
long pingtime;
mc_resp* data;
/* Sleep 1 second between pings */
if(i) sleep(1);
/* Create socket */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("Failed to open socket");
/* Set read timeout to 5 seconds */
struct timeval timeout = {5, 0};
if (setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) < 0)
error("Failed to set socket timeout");
/* Set write timeout to 5 seconds */
if (setsockopt (sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) < 0)
error("Failed to set socket timeout");
/* Now connect */
if (connect(sockfd,(struct sockaddr *) &serv_addr,sizeof(serv_addr)) < 0) {
switch(errno) {
case ECONNREFUSED:
case ETIMEDOUT:
case ENETUNREACH:
perror(NULL);
close(sockfd);
continue;
case EINPROGRESS:
printf("%s\n", strerror(ETIMEDOUT));
close(sockfd);
continue;
default:
/* everything else, exit on error */
error("Failed to connect");
}
}
/* Clear response buffer */
bzero(buffer,256);
/* Send ping request */
n = write(sockfd,"\xFE\x01",2);
if (n < 0)
error("Failed writing to socket");
/* Start counting and get response*/
gettimeofday(&before , NULL);
n = read(sockfd,buffer,255);
if (n < 0)
error("Failed reading from socket");
/* Stop counting */
gettimeofday(&after , NULL);
if(i==0) {
/* basic sanity check */
if(n<3 || buffer[0] != '\xff') {fprintf(stderr, "Server returned invalid data, aborting...\n"); exit(1);}
/* Check length in endian-agnostic manner */
if((buffer[1]<<8)&buffer[2] != (n-3)) {fprintf(stderr, "Data length verification failed, aborting...\n"); exit(1);}
/* Decode response */
data = decode(buffer, n);
/* Print response */
printf("Server MOTD: %s\n %d/%d players, running %s (protocol %d)\n", data->motd, data->players, data->maxplayers, data->version, data->proto);
decode_free(data);
}
close(sockfd);
pingtime = time_diff(before , after);
if(i==0) {
min=max=pingtime;
}
else {
if(pingtime < min) min = pingtime;
if(pingtime > max) max = pingtime;
}
printf("Ping response: ");
if(pingtime>=100000) printf("%ld ms\n", pingtime/1000, pingtime%1000);
else if(pingtime>=10000) printf("%ld.%01ld ms\n", pingtime/1000, (pingtime%1000)/100);
else if(pingtime>=1000) printf("%ld.%02ld ms\n", pingtime/1000, (pingtime%1000)/10);
else printf("%ld.%03ld ms\n", pingtime/1000, pingtime%1000);
avg += pingtime;
pings++;
}
if(pings>0) {
avg = avg/pings;
printf("rtt min/avg/max %ld.%03ldms/%ld.%03ldms/%ld.%03ldms\n", min/1000, min%1000, avg/1000, avg%1000, max/1000, max%1000);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment