/* * 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.9" #ifdef WIN32 # define _WINSOCK_DEPRECATED_NO_WARNINGS # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "miner.h" #include "nvml.h" #include "algos.h" #ifndef WIN32 # 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; #define MYBUFSIZ 16384 #define SOCK_REC_BUFSZ 1024 #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 const char *MUNAVAILABLE = " - API multicast listener will not be available"; static char *buffer = NULL; static time_t startup = 0; static int bye = 0; extern char *opt_api_bind; extern int opt_api_port; extern char *opt_api_allow; extern char *opt_api_groups; extern bool opt_api_mcast; extern char *opt_api_mcast_addr; extern char *opt_api_mcast_code; extern char *opt_api_mcast_des; extern int opt_api_mcast_port; // current stratum... extern struct stratum_ctx stratum; // sysinfos.cpp extern int num_cpus; extern float cpu_temp(int); extern uint32_t cpu_clock(int); char driver_version[32] = { 0 }; /***************************************************************/ static void gpustatus(int thr_id) { struct pool_infos *p = &pools[cur_pooln]; if (thr_id >= 0 && thr_id < opt_n_threads) { struct cgpu_info *cgpu = &thr_info[thr_id].gpu; double khashes_per_watt = 0; int gpuid = cgpu->gpu_id; char buf[512]; *buf = '\0'; char* card; cuda_gpu_info(cgpu); cgpu->gpu_plimit = device_plimit[cgpu->gpu_id]; #ifdef USE_WRAPNVML cgpu->has_monitoring = true; cgpu->gpu_bus = gpu_busid(cgpu); cgpu->gpu_temp = gpu_temp(cgpu); cgpu->gpu_fan = (uint16_t) gpu_fanpercent(cgpu); cgpu->gpu_fan_rpm = (uint16_t) gpu_fanrpm(cgpu); cgpu->gpu_power = gpu_power(cgpu); // mWatts cgpu->gpu_plimit = gpu_plimit(cgpu); // mW or % #endif cgpu->khashes = stats_get_speed(thr_id, 0.0) / 1000.0; if (cgpu->monitor.gpu_power) { cgpu->gpu_power = cgpu->monitor.gpu_power; khashes_per_watt = (double)cgpu->khashes / cgpu->monitor.gpu_power; khashes_per_watt *= 1000; // power in mW //gpulog(LOG_BLUE, thr_id, "KHW: %g", khashes_per_watt); } card = device_name[gpuid]; snprintf(buf, sizeof(buf), "GPU=%d;BUS=%hd;CARD=%s;TEMP=%.1f;" "POWER=%u;FAN=%hu;RPM=%hu;" "FREQ=%u;MEMFREQ=%u;GPUF=%u;MEMF=%u;" "KHS=%.2f;KHW=%.5f;PLIM=%u;" "ACC=%u;REJ=%u;HWF=%u;I=%.1f;THR=%u|", gpuid, cgpu->gpu_bus, card, cgpu->gpu_temp, cgpu->gpu_power, cgpu->gpu_fan, cgpu->gpu_fan_rpm, cgpu->gpu_clock/1000, cgpu->gpu_memclock/1000, // base freqs in MHz cgpu->monitor.gpu_clock, cgpu->monitor.gpu_memclock, // current cgpu->khashes, khashes_per_watt, cgpu->gpu_plimit, cgpu->accepted, (unsigned) cgpu->rejected, (unsigned) cgpu->hw_errors, cgpu->intensity, cgpu->throughput); // append to buffer for multi gpus strcat(buffer, buf); } } /** * Returns gpu/thread specific stats */ static char *getthreads(char *params) { *buffer = '\0'; for (int i = 0; i < opt_n_threads; i++) gpustatus(i); return buffer; } /*****************************************************************************/ /** * Returns miner global infos */ static char *getsummary(char *params) { char algo[64] = { 0 }; time_t ts = time(NULL); double accps, uptime = difftime(ts, startup); uint32_t wait_time = 0, solved_count = 0; uint32_t accepted_count = 0, rejected_count = 0; for (int p = 0; p < num_pools; p++) { wait_time += pools[p].wait_time; accepted_count += pools[p].accepted_count; rejected_count += pools[p].rejected_count; solved_count += pools[p].solved_count; } accps = (60.0 * accepted_count) / (uptime ? uptime : 1.0); get_currentalgo(algo, sizeof(algo)); *buffer = '\0'; sprintf(buffer, "NAME=%s;VER=%s;API=%s;" "ALGO=%s;GPUS=%d;KHS=%.2f;SOLV=%d;ACC=%d;REJ=%d;" "ACCMN=%.3f;DIFF=%.6f;NETKHS=%.0f;" "POOLS=%u;WAIT=%u;UPTIME=%.0f;TS=%u|", PACKAGE_NAME, PACKAGE_VERSION, APIVERSION, algo, active_gpus, (double)global_hashrate / 1000., solved_count, accepted_count, rejected_count, accps, net_diff > 1e-6 ? net_diff : stratum_diff, (double)net_hashrate / 1000., num_pools, wait_time, uptime, (uint32_t) ts); return buffer; } /** * Returns some infos about current pool */ static char *getpoolnfo(char *params) { char *s = buffer; char jobid[128] = { 0 }; char extra[96] = { 0 }; int pooln = params ? atoi(params) % num_pools : cur_pooln; struct pool_infos *p = &pools[pooln]; uint32_t last_share = 0; if (p->last_share_time) last_share = (uint32_t) (time(NULL) - p->last_share_time); *s = '\0'; if (stratum.job.job_id) strncpy(jobid, stratum.job.job_id, sizeof(stratum.job.job_id)); if (stratum.job.xnonce2) { /* used temporary to be sure all is ok */ sprintf(extra, "0x"); if (p->algo == ALGO_DECRED) { char compat[32] = { 0 }; cbin2hex(&extra[2], (const char*) stratum.xnonce1, min(36, stratum.xnonce2_size)); cbin2hex(compat, (const char*) stratum.job.xnonce2, 4); memcpy(&extra[2], compat, 8); // compat extranonce } else { cbin2hex(&extra[2], (const char*) stratum.job.xnonce2, stratum.xnonce2_size); } } snprintf(s, MYBUFSIZ, "POOL=%s;ALGO=%s;URL=%s;USER=%s;SOLV=%d;ACC=%d;REJ=%d;STALE=%u;H=%u;JOB=%s;DIFF=%.6f;" "BEST=%.6f;N2SZ=%d;N2=%s;PING=%u;DISCO=%u;WAIT=%u;UPTIME=%u;LAST=%u|", strlen(p->name) ? p->name : p->short_url, algo_names[p->algo], p->url, p->type & POOL_STRATUM ? p->user : "", p->solved_count, p->accepted_count, p->rejected_count, p->stales_count, stratum.job.height, jobid, stratum_diff, p->best_share, (int) stratum.xnonce2_size, extra, stratum.answer_msec, p->disconnects, p->wait_time, p->work_time, last_share); return s; } /*****************************************************************************/ static void gpuhwinfos(int gpu_id) { char buf[256]; char pstate[8]; char* card; struct cgpu_info *cgpu = NULL; for (int g = 0; g < opt_n_threads; g++) { if (device_map[g] == gpu_id) { cgpu = &thr_info[g].gpu; break; } } if (cgpu == NULL) return; cuda_gpu_info(cgpu); cgpu->gpu_plimit = device_plimit[cgpu->gpu_id]; #ifdef USE_WRAPNVML cgpu->has_monitoring = true; cgpu->gpu_bus = gpu_busid(cgpu); cgpu->gpu_temp = gpu_temp(cgpu); cgpu->gpu_fan = (uint16_t) gpu_fanpercent(cgpu); cgpu->gpu_fan_rpm = (uint16_t) gpu_fanrpm(cgpu); cgpu->gpu_pstate = (int16_t) gpu_pstate(cgpu); cgpu->gpu_power = gpu_power(cgpu); cgpu->gpu_plimit = gpu_plimit(cgpu); gpu_info(cgpu); #ifdef WIN32 if (opt_debug) nvapi_pstateinfo(cgpu->gpu_id); #endif #endif memset(pstate, 0, sizeof(pstate)); if (cgpu->gpu_pstate != -1) snprintf(pstate, sizeof(pstate), "P%d", (int) cgpu->gpu_pstate); card = device_name[gpu_id]; snprintf(buf, sizeof(buf), "GPU=%d;BUS=%hd;CARD=%s;SM=%hu;MEM=%u;" "TEMP=%.1f;FAN=%hu;RPM=%hu;FREQ=%u;MEMFREQ=%u;GPUF=%u;MEMF=%u;" "PST=%s;POWER=%u;PLIM=%u;" "VID=%hx;PID=%hx;NVML=%d;NVAPI=%d;SN=%s;BIOS=%s|", gpu_id, cgpu->gpu_bus, card, cgpu->gpu_arch, (uint32_t) cgpu->gpu_mem, cgpu->gpu_temp, cgpu->gpu_fan, cgpu->gpu_fan_rpm, cgpu->gpu_clock/1000U, cgpu->gpu_memclock/1000U, // base clocks cgpu->monitor.gpu_clock, cgpu->monitor.gpu_memclock, // current pstate, cgpu->gpu_power, cgpu->gpu_plimit, cgpu->gpu_vid, cgpu->gpu_pid, cgpu->nvml_id, cgpu->nvapi_id, cgpu->gpu_sn, cgpu->gpu_desc); strcat(buffer, buf); } #ifndef WIN32 static char os_version[64] = "linux "; #endif static const char* os_name() { #ifdef WIN32 return "windows"; #else FILE *fd = fopen("/proc/version", "r"); if (!fd) return "linux"; if (!fscanf(fd, "Linux version %48s", &os_version[6])) { fclose(fd); return "linux"; } fclose(fd); os_version[48] = '\0'; return (const char*) os_version; #endif } /** * System and CPU Infos */ static void syshwinfos() { char buf[256]; int cputc = (int) cpu_temp(0); uint32_t cpuclk = cpu_clock(0); memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "OS=%s;NVDRIVER=%s;CPUS=%d;CPUTEMP=%d;CPUFREQ=%d|", os_name(), driver_version, num_cpus, cputc, cpuclk/1000); strcat(buffer, buf); } /** * Returns gpu and system (todo) informations */ static char *gethwinfos(char *params) { *buffer = '\0'; for (int i = 0; i < cuda_num_devices(); i++) gpuhwinfos(i); syshwinfos(); return buffer; } /*****************************************************************************/ /** * Returns the last 50 scans stats * optional param thread id (default all) */ static char *gethistory(char *params) { struct stats_data data[50]; int thrid = params ? atoi(params) : -1; char *p = buffer; int records = stats_get_history(thrid, data, ARRAY_SIZE(data)); *buffer = '\0'; for (int i = 0; i < records; i++) { time_t ts = data[i].tm_stat; p += sprintf(p, "GPU=%d;H=%u;KHS=%.2f;DIFF=%g;" "COUNT=%u;FOUND=%u;ID=%u;TS=%u|", data[i].gpu_id, data[i].height, data[i].hashrate, data[i].difficulty, data[i].hashcount, data[i].hashfound, data[i].uid, (uint32_t)ts); } return buffer; } /** * Returns the job scans ranges (debug purpose, only with -D) */ static char *getscanlog(char *params) { struct hashlog_data data[50]; char *p = buffer; int records = hashlog_get_history(data, ARRAY_SIZE(data)); *buffer = '\0'; for (int i = 0; i < records; i++) { time_t ts = data[i].tm_upd; p += sprintf(p, "H=%u;P=%u;JOB=%u;ID=%d;DIFF=%g;" "N=0x%x;FROM=0x%x;SCANTO=0x%x;" "COUNT=0x%x;FOUND=%u;TS=%u|", data[i].height, data[i].npool, data[i].njobid, (int)data[i].job_nonce_id, data[i].sharediff, data[i].nonce, data[i].scanned_from, data[i].scanned_to, (data[i].scanned_to - data[i].scanned_from), data[i].tm_sent ? 1 : 0, (uint32_t)ts); } return buffer; } /** * Some debug infos about memory usage */ static char *getmeminfo(char *params) { uint64_t smem, hmem, totmem; uint32_t srec, hrec; stats_getmeminfo(&smem, &srec); hashlog_getmeminfo(&hmem, &hrec); totmem = smem + hmem; *buffer = '\0'; sprintf(buffer, "STATS=%u;HASHLOG=%u;MEM=%lu|", srec, hrec, totmem); return buffer; } /*****************************************************************************/ /** * Set pool by index (pools array in json config) * switchpool|1| */ static char *remote_switchpool(char *params) { bool ret = false; *buffer = '\0'; if (!params || strlen(params) == 0) { // rotate pool test ret = pool_switch_next(-1); } else { int n = atoi(params); if (n == cur_pooln) ret = true; else if (n < num_pools) ret = pool_switch(-1, n); } sprintf(buffer, "%s|", ret ? "ok" : "fail"); return buffer; } /** * Change pool url (see --url parameter) * seturl|stratum+tcp://:@mine.xpool.ca:1131| */ static char *remote_seturl(char *params) { bool ret; *buffer = '\0'; if (!params || strlen(params) == 0) { // rotate pool test ret = pool_switch_next(-1); } else { ret = pool_switch_url(params); } sprintf(buffer, "%s|", ret ? "ok" : "fail"); return buffer; } /** * Ask the miner to quit */ static char *remote_quit(char *params) { *buffer = '\0'; bye = 1; sprintf(buffer, "%s", "bye|"); return buffer; } /*****************************************************************************/ static char *gethelp(char *params); struct CMDS { const char *name; char *(*func)(char *); bool iswritemode; } cmds[] = { { "summary", getsummary, false }, { "threads", getthreads, false }, { "pool", getpoolnfo, false }, { "histo", gethistory, false }, { "hwinfo", gethwinfos, false }, { "meminfo", getmeminfo, false }, { "scanlog", getscanlog, false }, /* remote functions */ { "seturl", remote_seturl, true }, /* prefer switchpool, deprecated */ { "switchpool", remote_switchpool, true }, { "quit", remote_quit, true }, /* keep it the last */ { "help", gethelp, false }, }; #define CMDMAX ARRAY_SIZE(cmds) static char *gethelp(char *params) { *buffer = '\0'; char * p = buffer; for (int i = 0; i < CMDMAX-1; i++) { bool displayed = !cmds[i].iswritemode || opt_api_allow; if (displayed) p += sprintf(p, "%s\n", cmds[i].name); } sprintf(p, "|"); return buffer; } /*****************************************************************************/ static int send_result(SOCKETTYPE c, char *result) { int n; if (!result) { n = send(c, "", 1, 0); } else { // ignore failure - it's closed immediately anyway n = send(c, result, (int) strlen(result) + 1, 0); } return n; } /* ---- Base64 Encoding/Decoding Table --- */ static const char table64[]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static size_t base64_encode(const uchar *indata, size_t insize, char *outptr, size_t outlen) { uchar ibuf[3]; uchar obuf[4]; int i, inputparts, inlen = (int) insize; size_t len = 0; char *output, *outbuf; memset(outptr, 0, outlen); outbuf = output = (char*)calloc(1, inlen * 4 / 3 + 4); if (outbuf == NULL) { return -1; } while (inlen > 0) { for (i = inputparts = 0; i < 3; i++) { if (inlen > 0) { inputparts++; ibuf[i] = (uchar) *indata; indata++; inlen--; } else ibuf[i] = 0; } obuf[0] = (uchar) ((ibuf[0] & 0xFC) >> 2); obuf[1] = (uchar) (((ibuf[0] & 0x03) << 4) | ((ibuf[1] & 0xF0) >> 4)); obuf[2] = (uchar) (((ibuf[1] & 0x0F) << 2) | ((ibuf[2] & 0xC0) >> 6)); obuf[3] = (uchar) (ibuf[2] & 0x3F); switch(inputparts) { case 1: /* only one byte read */ snprintf(output, 5, "%c%c==", table64[obuf[0]], table64[obuf[1]]); break; case 2: /* two bytes read */ snprintf(output, 5, "%c%c%c=", table64[obuf[0]], table64[obuf[1]], table64[obuf[2]]); break; default: snprintf(output, 5, "%c%c%c%c", table64[obuf[0]], table64[obuf[1]], table64[obuf[2]], table64[obuf[3]] ); break; } if ((len+4) > outlen) break; output += 4; len += 4; } len = snprintf(outptr, len, "%s", outbuf); // todo: seems to be missing on linux if (strlen(outptr) == 27) strcat(outptr, "="); free(outbuf); return len; } #include "compat/curl-for-windows/openssl/openssl/crypto/sha/sha.h" /* websocket handshake (tested in Chrome) */ static int websocket_handshake(SOCKETTYPE c, char *result, char *clientkey) { char answer[256]; char inpkey[128] = { 0 }; char seckey[64]; uchar sha1[20]; SHA_CTX ctx; if (opt_protocol) applog(LOG_DEBUG, "clientkey: %s", clientkey); sprintf(inpkey, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", clientkey); // SHA-1 test from rfc, returns in base64 "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" //sprintf(inpkey, "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); SHA1_Init(&ctx); SHA1_Update(&ctx, inpkey, strlen(inpkey)); SHA1_Final(sha1, &ctx); base64_encode(sha1, 20, seckey, sizeof(seckey)); sprintf(answer, "HTTP/1.1 101 Switching Protocol\r\n" "Upgrade: WebSocket\r\nConnection: Upgrade\r\n" "Sec-WebSocket-Accept: %s\r\n" "Sec-WebSocket-Protocol: text\r\n" "\r\n", seckey); // data result as tcp frame uchar hd[10] = { 0 }; hd[0] = 129; // 0x1 text frame (FIN + opcode) uint64_t datalen = (uint64_t) strlen(result); uint8_t frames = 2; if (datalen <= 125) { hd[1] = (uchar) (datalen); } else if (datalen <= 65535) { hd[1] = (uchar) 126; hd[2] = (uchar) (datalen >> 8); hd[3] = (uchar) (datalen); frames = 4; } else { hd[1] = (uchar) 127; hd[2] = (uchar) (datalen >> 56); hd[3] = (uchar) (datalen >> 48); hd[4] = (uchar) (datalen >> 40); hd[5] = (uchar) (datalen >> 32); hd[6] = (uchar) (datalen >> 24); hd[7] = (uchar) (datalen >> 16); hd[8] = (uchar) (datalen >> 8); hd[9] = (uchar) (datalen); frames = 10; } size_t handlen = strlen(answer); uchar *data = (uchar*) calloc(1, handlen + frames + (size_t) datalen + 1); if (data == NULL) return -1; else { uchar *p = data; // HTTP header 101 memcpy(p, answer, handlen); p += handlen; // WebSocket Frame - Header + Data memcpy(p, hd, frames); memcpy(p + frames, result, (size_t)datalen); send(c, (const char*)data, (int) (strlen(answer) + frames + datalen + 1), 0); free(data); } return 0; } /* * Interpret --api-groups G:cmd1:cmd2:cmd3,P:cmd4,*,... */ static void setup_groups() { const char *api_groups = opt_api_groups ? opt_api_groups : ""; char *buf, *cmd, *ptr, *next, *colon; char commands[512] = { 0 }; char cmdbuf[128] = { 0 }; char group; bool addstar; int i; buf = (char *)calloc(1, strlen(api_groups) + 1); if (unlikely(!buf)) proper_exit(1); //, "Failed to malloc ipgroups buf"); strcpy(buf, api_groups); next = buf; // for each group defined while (next && *next) { ptr = next; next = strchr(ptr, ','); if (next) *(next++) = '\0'; // Validate the group if (*(ptr+1) != ':') { colon = strchr(ptr, ':'); if (colon) *colon = '\0'; proper_exit(1); //, "API invalid group name '%s'", ptr); } group = GROUP(*ptr); if (!VALIDGROUP(group)) proper_exit(1); //, "API invalid group name '%c'", *ptr); if (group == PRIVGROUP) proper_exit(1); //, "API group name can't be '%c'", PRIVGROUP); if (group == NOPRIVGROUP) proper_exit(1); //, "API group name can't be '%c'", NOPRIVGROUP); if (apigroups[GROUPOFFSET(group)].commands != NULL) proper_exit(1); //, "API duplicate group name '%c'", *ptr); ptr += 2; // Validate the command list (and handle '*') cmd = &(commands[0]); *(cmd++) = '|'; *cmd = '\0'; addstar = false; while (ptr && *ptr) { colon = strchr(ptr, ':'); if (colon) *(colon++) = '\0'; if (strcmp(ptr, "*") == 0) addstar = true; else { bool did = false; for (i = 0; i < CMDMAX-1; i++) { if (strcasecmp(ptr, cmds[i].name) == 0) { did = true; break; } } if (did) { // skip duplicates sprintf(cmdbuf, "|%s|", cmds[i].name); if (strstr(commands, cmdbuf) == NULL) { strcpy(cmd, cmds[i].name); cmd += strlen(cmds[i].name); *(cmd++) = '|'; *cmd = '\0'; } } else { proper_exit(1); //, "API unknown command '%s' in group '%c'", ptr, group); } } ptr = colon; } // * = allow all non-iswritemode commands if (addstar) { for (i = 0; i < CMDMAX-1; i++) { if (cmds[i].iswritemode == false) { // skip duplicates sprintf(cmdbuf, "|%s|", cmds[i].name); if (strstr(commands, cmdbuf) == NULL) { strcpy(cmd, cmds[i].name); cmd += strlen(cmds[i].name); *(cmd++) = '|'; *cmd = '\0'; } } } } ptr = apigroups[GROUPOFFSET(group)].commands = (char *)calloc(1, strlen(commands) + 1); if (unlikely(!ptr)) proper_exit(1); //, "Failed to malloc group commands buf"); strcpy(ptr, commands); } // Now define R (NOPRIVGROUP) as all non-iswritemode commands cmd = &(commands[0]); *(cmd++) = '|'; *cmd = '\0'; for (i = 0; i < CMDMAX-1; i++) { if (cmds[i].iswritemode == false) { strcpy(cmd, cmds[i].name); cmd += strlen(cmds[i].name); *(cmd++) = '|'; *cmd = '\0'; } } ptr = apigroups[GROUPOFFSET(NOPRIVGROUP)].commands = (char *)calloc(1, strlen(commands) + 1); if (unlikely(!ptr)) proper_exit(1); //, "Failed to malloc noprivgroup commands buf"); strcpy(ptr, commands); // W (PRIVGROUP) is handled as a special case since it simply means all commands free(buf); return; } /* * Interpret [W:]IP[/Prefix][,[R|W:]IP2[/Prefix2][,...]] --api-allow option * special case of 0/0 allows /0 (means all IP addresses) */ #define ALLIPS "0/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, ALLIPS) == 0 || 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 if (strcmp(opt_api_bind, ALLIP4) == 0) addrok = true; else addrok = (strcmp(*connectaddr, localaddr) == 0); return addrok; } static void mcast() { struct sockaddr_in listen; struct ip_mreq grp; struct sockaddr_in came_from; time_t bindstart; char *binderror; SOCKETTYPE mcast_sock; SOCKETTYPE reply_sock; socklen_t came_from_siz; char *connectaddr; ssize_t rep; int bound; int count; int reply_port; bool addrok; char group; char expect[] = "ccminer-"; // first 8 bytes constant char *expect_code; size_t expect_code_len; char buf[1024]; char replybuf[1024]; memset(&grp, 0, sizeof(grp)); grp.imr_multiaddr.s_addr = inet_addr(opt_api_mcast_addr); if (grp.imr_multiaddr.s_addr == INADDR_NONE) proper_exit(1); //, "Invalid Multicast Address"); grp.imr_interface.s_addr = INADDR_ANY; mcast_sock = socket(AF_INET, SOCK_DGRAM, 0); int optval = 1; if (SOCKETFAIL(setsockopt(mcast_sock, SOL_SOCKET, SO_REUSEADDR, (const char *)(&optval), sizeof(optval)))) { applog(LOG_ERR, "API mcast setsockopt SO_REUSEADDR failed (%s)%s", strerror(errno), MUNAVAILABLE); goto die; } memset(&listen, 0, sizeof(listen)); listen.sin_family = AF_INET; listen.sin_addr.s_addr = INADDR_ANY; listen.sin_port = htons(opt_api_mcast_port); // try for more than 1 minute ... in case the old one hasn't completely gone yet bound = 0; bindstart = time(NULL); while (bound == 0) { if (SOCKETFAIL(bind(mcast_sock, (struct sockaddr *)(&listen), sizeof(listen)))) { binderror = strerror(errno);; if ((time(NULL) - bindstart) > 61) break; else sleep(30); } else bound = 1; } if (bound == 0) { applog(LOG_ERR, "API mcast bind to port %d failed (%s)%s", opt_api_port, binderror, MUNAVAILABLE); goto die; } if (SOCKETFAIL(setsockopt(mcast_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char *)(&grp), sizeof(grp)))) { applog(LOG_ERR, "API mcast join failed (%s)%s", strerror(errno), MUNAVAILABLE); goto die; } expect_code_len = sizeof(expect) + strlen(opt_api_mcast_code); expect_code = (char *)calloc(1, expect_code_len + 1); if (!expect_code) proper_exit(1); //, "Failed to malloc mcast expect_code"); snprintf(expect_code, expect_code_len + 1, "%s%s-", expect, opt_api_mcast_code); count = 0; while (42) { sleep(1); count++; came_from_siz = sizeof(came_from); if (SOCKETFAIL(rep = recvfrom(mcast_sock, buf, sizeof(buf) - 1, 0, (struct sockaddr *)(&came_from), &came_from_siz))) { applog(LOG_DEBUG, "API mcast failed count=%d (%s) (%d)", count, strerror(errno), (int)mcast_sock); continue; } addrok = check_connect(&came_from, &connectaddr, &group); applog(LOG_DEBUG, "API mcast from %s - %s", connectaddr, addrok ? "Accepted" : "Ignored"); if (!addrok) { continue; } buf[rep] = '\0'; if (rep > 0 && buf[rep - 1] == '\n') buf[--rep] = '\0'; applog(LOG_DEBUG, "API mcast request rep=%d (%s) from %s:%d", (int)rep, buf, inet_ntoa(came_from.sin_addr), ntohs(came_from.sin_port)); if ((size_t)rep > expect_code_len && memcmp(buf, expect_code, expect_code_len) == 0) { reply_port = atoi(&buf[expect_code_len]); if (reply_port < 1 || reply_port > 65535) { applog(LOG_DEBUG, "API mcast request ignored - invalid port (%s)", &buf[expect_code_len]); } else { applog(LOG_DEBUG, "API mcast request OK port %s=%d", &buf[expect_code_len], reply_port); came_from.sin_port = htons(reply_port); reply_sock = socket(AF_INET, SOCK_DGRAM, 0); snprintf(replybuf, sizeof(replybuf), "ccm-%s-%d-%s", opt_api_mcast_code, opt_api_port, opt_api_mcast_des); rep = sendto(reply_sock, replybuf, (int) strlen(replybuf) + 1, 0, (struct sockaddr *)(&came_from), (int) sizeof(came_from)); if (SOCKETFAIL(rep)) { applog(LOG_DEBUG, "API mcast send reply failed (%s) (%d)", strerror(errno), (int)reply_sock); } else { applog(LOG_DEBUG, "API mcast send reply (%s) succeeded (%d) (%d)", replybuf, (int)rep, (int)reply_sock); } CLOSESOCKET(reply_sock); } } else applog(LOG_DEBUG, "API mcast request was no good"); } die: CLOSESOCKET(mcast_sock); } static void *mcast_thread(void *userdata) { struct thr_info *mythr = (struct thr_info *)userdata; pthread_detach(pthread_self()); pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); mcast(); //PTH(mythr) = 0L; return NULL; } void mcast_init() { struct thr_info *thr; thr = (struct thr_info *)calloc(1, sizeof(*thr)); if (!thr) proper_exit(1); //, "Failed to calloc mcast thr"); if (unlikely(pthread_create(&thr->pth, NULL, mcast_thread, thr))) proper_exit(1); //, "API mcast thread create failed"); } static void api() { const char *addr = opt_api_bind; unsigned short port = (unsigned short) opt_api_port; // 4068 char buf[MYBUFSIZ]; int 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 c; SOCKETTYPE *apisock; if (!opt_api_port && opt_debug) { applog(LOG_DEBUG, "API disabled"); return; } setup_groups(); 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); // TODO: allow bind to ip/interface if (serv.sin_addr.s_addr == (in_addr_t)INVINETADDR) { applog(LOG_ERR, "API initialisation 2 failed (%s)%s", strerror(errno), UNAVAILABLE); // free(apisock); FIXME!! 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 if (opt_api_port == 4068) { /* when port is default one, use first available */ if (opt_debug) applog(LOG_DEBUG, "API bind to port %d failed, trying port %u", port, (uint32_t) port+1); port++; serv.sin_port = htons(port); sleep(1); } else { if (!opt_quiet || opt_debug) applog(LOG_WARNING, "API bind to port %u failed - trying again in 20sec", (uint32_t) port); sleep(20); } } else { bound = 1; if (opt_api_port != port) { applog(LOG_WARNING, "API bind to port %d failed - using port %u", opt_api_port, (uint32_t)port); opt_api_port = port; } } } if (bound == 0) { applog(LOG_WARNING, "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; } if (opt_api_allow && strcmp(opt_api_bind, "127.0.0.1") == 0) applog(LOG_WARNING, "API open locally in full access mode on port %d", opt_api_port); else if (opt_api_allow) applog(LOG_WARNING, "API open in full access mode to %s on port %d", opt_api_allow, opt_api_port); else if (strcmp(opt_api_bind, "127.0.0.1") != 0) applog(LOG_INFO, "API open to the network in read-only mode on port %d", opt_api_port); if (opt_api_mcast) mcast_init(); buffer = (char *) calloc(1, MYBUFSIZ + 1); counter = 0; while (bye == 0 && !abort_flag) { counter++; clisiz = sizeof(cli); c = accept(*apisock, (struct sockaddr*) (&cli), &clisiz); if (SOCKETFAIL(c)) { 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_debug && opt_protocol) applog(LOG_DEBUG, "API: connection from %s - %s", connectaddr, addrok ? "Accepted" : "Ignored"); if (addrok) { bool fail; char *wskey = NULL; n = recv(c, &buf[0], SOCK_REC_BUFSZ, 0); fail = SOCKETFAIL(n) || n < 0; if (fail) buf[0] = '\0'; else if (n > 0 && buf[n-1] == '\n') { /* telnet compat \r\n */ buf[n-1] = '\0'; n--; if (n > 0 && buf[n-1] == '\r') buf[n-1] = '\0'; } else buf[n] = '\0'; //if (opt_debug && opt_protocol && n > 0) // applog(LOG_DEBUG, "API: recv command: (%d) '%s'+char(%x)", n, buf, buf[n-1]); if (!fail) { char *msg = NULL; /* Websocket requests compat. */ if ((msg = strstr(buf, "GET /")) && strlen(msg) > 5) { char cmd[256] = { 0 }; sscanf(&msg[5], "%s\n", cmd); params = strchr(cmd, '/'); if (params) *(params++) = '|'; params = strchr(cmd, '/'); if (params) *(params++) = '\0'; wskey = strstr(msg, "Sec-WebSocket-Key"); if (wskey) { char *eol = strchr(wskey, '\r'); if (eol) *eol = '\0'; wskey = strchr(wskey, ':'); wskey++; while ((*wskey) == ' ') wskey++; // ltrim } n = sprintf(buf, "%s", cmd); } params = strchr(buf, '|'); if (params != NULL) *(params++) = '\0'; if (opt_debug && opt_protocol && n > 0) applog(LOG_DEBUG, "API: exec command %s(%s)", buf, params ? params : ""); for (i = 0; i < CMDMAX; i++) { if (strcmp(buf, cmds[i].name) == 0 && strlen(buf)) { if (params && strlen(params)) { // remove possible trailing | if (params[strlen(params)-1] == '|') params[strlen(params)-1] = '\0'; } result = (cmds[i].func)(params); if (wskey) { websocket_handshake(c, result, wskey); break; } send_result(c, result); break; } } } CLOSESOCKET(c); } } 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); if (bye) { // quit command proper_exit(1); } return NULL; } /* to be able to report the default value set in each algo */ void api_set_throughput(int thr_id, uint32_t throughput) { if (thr_id < MAX_GPUS && thr_info) { struct cgpu_info *cgpu = &thr_info[thr_id].gpu; cgpu->intensity = throughput2intensity(throughput); if (cgpu->throughput != throughput) cgpu->throughput = throughput; } // to display in bench results if (opt_benchmark) bench_set_throughput(thr_id, throughput); }