/* * Copyright 2014 ccminer team * * Implementation by tpruvot (based on cgminer) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. See COPYING for more details. */ #define APIVERSION "1.0" #ifdef _MSC_VER # define _WINSOCK_DEPRECATED_NO_WARNINGS # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "compat.h" #include "miner.h" #ifndef _MSC_VER # include # include # include # include # include # define SOCKETTYPE long # define SOCKETFAIL(a) ((a) < 0) # define INVSOCK -1 /* INVALID_SOCKET */ # define INVINETADDR -1 /* INADDR_NONE */ # define CLOSESOCKET close # define SOCKETINIT {} # define SOCKERRMSG strerror(errno) #else # define SOCKETTYPE SOCKET # define SOCKETFAIL(a) ((a) == SOCKET_ERROR) # define INVSOCK INVALID_SOCKET # define INVINETADDR INADDR_NONE # define CLOSESOCKET closesocket #define in_addr_t uint32_t #endif #define GROUP(g) (toupper(g)) #define PRIVGROUP GROUP('W') #define NOPRIVGROUP GROUP('R') #define ISPRIVGROUP(g) (GROUP(g) == PRIVGROUP) #define GROUPOFFSET(g) (GROUP(g) - GROUP('A')) #define VALIDGROUP(g) (GROUP(g) >= GROUP('A') && GROUP(g) <= GROUP('Z')) #define COMMANDS(g) (apigroups[GROUPOFFSET(g)].commands) #define DEFINEDGROUP(g) (ISPRIVGROUP(g) || COMMANDS(g) != NULL) struct APIGROUPS { // This becomes a string like: "|cmd1|cmd2|cmd3|" so it's quick to search char *commands; } apigroups['Z' - 'A' + 1]; // only A=0 to Z=25 (R: noprivs, W: allprivs) struct IP4ACCESS { in_addr_t ip; in_addr_t mask; char group; }; static int ips = 1; static struct IP4ACCESS *ipaccess = NULL; // Big enough for largest API request // though a PC with 100s of CPUs may exceed the size ... // Current code assumes it can socket send this size also #define MYBUFSIZ 16384 // Socket is on 127.0.0.1 #define QUEUE 10 #define ALLIP4 "0.0.0.0" static const char *localaddr = "127.0.0.1"; static const char *UNAVAILABLE = " - API will not be available"; static char *buffer = NULL; static time_t startup = 0; static int bye = 0; extern int opt_intensity; extern int opt_n_threads; extern char *opt_api_allow; extern int opt_api_listen; /* port */ extern uint64_t global_hashrate; extern uint32_t accepted_count; extern uint32_t rejected_count; #define gpu_threads opt_n_threads extern void get_currentalgo(char* buf, int sz); /***************************************************************/ static void gpustatus(int thr_id) { char buf[MYBUFSIZ]; float gt; int gf, gp; if (thr_id >= 0 && thr_id < gpu_threads) { struct cgpu_info *cgpu = &thr_info[thr_id].gpu; #ifdef HAVE_HWMONITORING // todo if (gpu->has_monitoring) { gt = gpu_temp(gpu); gf = gpu_fanspeed(gpu); gp = gpu_fanpercent(gpu); } else #endif { gt = 0.0; gf = gp = 0; } // todo: can be 0 if set by algo (auto) if (opt_intensity == 0 && opt_work_size) { int i = 0; uint32_t ws = opt_work_size; while (ws > 1 && i++ < 32) ws = ws >> 1; cgpu->intensity = i; } else { cgpu->intensity = opt_intensity; } // todo: per gpu cgpu->accepted = accepted_count; cgpu->rejected = rejected_count; cgpu->khashes = stats_get_speed(thr_id) / 1000.0; sprintf(buf, "GPU=%d;TEMP=%.1f;FAN=%d;FANP=%d;KHS=%.2f;" "HWF=%d;I=%d|", thr_id, gt, gf, gp, cgpu->khashes, cgpu->hw_errors, cgpu->intensity); strcat(buffer, buf); } } /*****************************************************************************/ static char *getsummary(char *params) { char algo[64] = ""; time_t uptime = (time(NULL) - startup); double accps = (60.0 * accepted_count) / (uptime ? (uint32_t) uptime : 1.0); get_currentalgo(algo, sizeof(algo)); *buffer = '\0'; sprintf(buffer, "NAME=%s;VER=%s;API=%s;" "ALGO=%s;KHS=%.2f;ACC=%d;REJ=%d;ACCMN=%.3f;UPTIME=%d|", PACKAGE_NAME, PACKAGE_VERSION, APIVERSION, algo, (double)global_hashrate / 1000.0, accepted_count, rejected_count, accps, uptime); return buffer; } static char *getstats(char *params) { *buffer = '\0'; for (int i = 0; i < gpu_threads; i++) gpustatus(i); return buffer; } struct CMDS { char *name; char *(*func)(char *); } cmds[] = { { "summary", getsummary }, { "stats", getstats }, }; #define CMDMAX 2 static void send_result(SOCKETTYPE c, char *result) { int n; if (result == NULL) result = ""; // ignore failure - it's closed immediately anyway n = send(c, result, strlen(result) + 1, 0); } /* * N.B. IP4 addresses are by Definition 32bit big endian on all platforms */ static void setup_ipaccess() { char *buf, *ptr, *comma, *slash, *dot; int ipcount, mask, octet, i; char group; buf = (char*) calloc(1, strlen(opt_api_allow) + 1); if (unlikely(!buf)) proper_exit(1);//, "Failed to malloc ipaccess buf"); strcpy(buf, opt_api_allow); ipcount = 1; ptr = buf; while (*ptr) if (*(ptr++) == ',') ipcount++; // possibly more than needed, but never less ipaccess = (struct IP4ACCESS *) calloc(ipcount, sizeof(struct IP4ACCESS)); if (unlikely(!ipaccess)) proper_exit(1);//, "Failed to calloc ipaccess"); ips = 0; ptr = buf; while (ptr && *ptr) { while (*ptr == ' ' || *ptr == '\t') ptr++; if (*ptr == ',') { ptr++; continue; } comma = strchr(ptr, ','); if (comma) *(comma++) = '\0'; group = NOPRIVGROUP; if (isalpha(*ptr) && *(ptr+1) == ':') { if (DEFINEDGROUP(*ptr)) group = GROUP(*ptr); ptr += 2; } ipaccess[ips].group = group; if (strcmp(ptr, ALLIP4) == 0) ipaccess[ips].ip = ipaccess[ips].mask = 0; else { slash = strchr(ptr, '/'); if (!slash) ipaccess[ips].mask = 0xffffffff; else { *(slash++) = '\0'; mask = atoi(slash); if (mask < 1 || mask > 32) goto popipo; // skip invalid/zero ipaccess[ips].mask = 0; while (mask-- >= 0) { octet = 1 << (mask % 8); ipaccess[ips].mask |= (octet << (24 - (8 * (mask >> 3)))); } } ipaccess[ips].ip = 0; // missing default to '.0' for (i = 0; ptr && (i < 4); i++) { dot = strchr(ptr, '.'); if (dot) *(dot++) = '\0'; octet = atoi(ptr); if (octet < 0 || octet > 0xff) goto popipo; // skip invalid ipaccess[ips].ip |= (octet << (24 - (i * 8))); ptr = dot; } ipaccess[ips].ip &= ipaccess[ips].mask; } ips++; popipo: ptr = comma; } free(buf); } static bool check_connect(struct sockaddr_in *cli, char **connectaddr, char *group) { bool addrok = false; *connectaddr = inet_ntoa(cli->sin_addr); *group = NOPRIVGROUP; if (opt_api_allow) { int client_ip = htonl(cli->sin_addr.s_addr); for (int i = 0; i < ips; i++) { if ((client_ip & ipaccess[i].mask) == ipaccess[i].ip) { addrok = true; *group = ipaccess[i].group; break; } } } else addrok = (strcmp(*connectaddr, localaddr) == 0); return addrok; } static void api() { const char *addr = opt_api_allow; short int port = opt_api_listen; // 4068 char buf[MYBUFSIZ]; int c, n, bound; char *connectaddr; char *binderror; char group; time_t bindstart; struct sockaddr_in serv; struct sockaddr_in cli; socklen_t clisiz; bool addrok = false; long long counter; char *result; char *params; int i; SOCKETTYPE *apisock; if (!opt_api_listen && opt_debug) { applog(LOG_DEBUG, "API disabled"); return; } if (opt_api_allow) { setup_ipaccess(); if (ips == 0) { applog(LOG_WARNING, "API not running (no valid IPs specified)%s", UNAVAILABLE); } } apisock = (SOCKETTYPE*) calloc(1, sizeof(*apisock)); *apisock = INVSOCK; sleep(1); *apisock = socket(AF_INET, SOCK_STREAM, 0); if (*apisock == INVSOCK) { applog(LOG_ERR, "API initialisation failed (%s)%s", strerror(errno), UNAVAILABLE); return; } memset(&serv, 0, sizeof(serv)); serv.sin_family = AF_INET; serv.sin_addr.s_addr = inet_addr(addr); if (serv.sin_addr.s_addr == (in_addr_t)INVINETADDR) { applog(LOG_ERR, "API initialisation 2 failed (%s)%s", strerror(errno), UNAVAILABLE); return; } serv.sin_port = htons(port); #ifndef WIN32 // On linux with SO_REUSEADDR, bind will get the port if the previous // socket is closed (even if it is still in TIME_WAIT) but fail if // another program has it open - which is what we want int optval = 1; // If it doesn't work, we don't really care - just show a debug message if (SOCKETFAIL(setsockopt(*apisock, SOL_SOCKET, SO_REUSEADDR, (void *)(&optval), sizeof(optval)))) applog(LOG_DEBUG, "API setsockopt SO_REUSEADDR failed (ignored): %s", SOCKERRMSG); #else // On windows a 2nd program can bind to a port>1024 already in use unless // SO_EXCLUSIVEADDRUSE is used - however then the bind to a closed port // in TIME_WAIT will fail until the timeout - so we leave the options alone #endif // try for 1 minute ... in case the old one hasn't completely gone yet bound = 0; bindstart = time(NULL); while (bound == 0) { if (bind(*apisock, (struct sockaddr *)(&serv), sizeof(serv)) < 0) { binderror = strerror(errno); if ((time(NULL) - bindstart) > 61) break; else { applog(LOG_ERR, "API bind to port %d failed - trying again in 15sec", port); sleep(15); } } else bound = 1; } if (bound == 0) { applog(LOG_ERR, "API bind to port %d failed (%s)%s", port, binderror, UNAVAILABLE); free(apisock); return; } if (SOCKETFAIL(listen(*apisock, QUEUE))) { applog(LOG_ERR, "API initialisation 3 failed (%s)%s", strerror(errno), UNAVAILABLE); CLOSESOCKET(*apisock); free(apisock); return; } buffer = (char *) calloc(1, MYBUFSIZ + 1); counter = 0; while (bye == 0) { counter++; clisiz = sizeof(cli); if (SOCKETFAIL(c = accept(*apisock, (struct sockaddr *)(&cli), &clisiz))) { applog(LOG_ERR, "API failed (%s)%s", strerror(errno), UNAVAILABLE); CLOSESOCKET(*apisock); free(apisock); free(buffer); return; } addrok = check_connect(&cli, &connectaddr, &group); if (opt_protocol) applog(LOG_DEBUG, "API: connection from %s - %s", connectaddr, addrok ? "Accepted" : "Ignored"); if (addrok) { n = recv(c, &buf[0], MYBUFSIZ - 1, 0); // applog(LOG_DEBUG, "API: recv command: (%d) '%s'", n, buf); if (!SOCKETFAIL(n)) { buf[n] = '\0'; params = strchr(buf, '|'); if (params != NULL) *(params++) = '\0'; for (i = 0; i < CMDMAX; i++) { if (strcmp(buf, cmds[i].name) == 0) { result = (cmds[i].func)(params); send_result(c, result); CLOSESOCKET(c); break; } } } } } CLOSESOCKET(*apisock); free(apisock); free(buffer); } /* external access */ void *api_thread(void *userdata) { struct thr_info *mythr = (struct thr_info*)userdata; startup = time(NULL); api(); tq_freeze(mythr->q); return NULL; }