GOSTcoin support for ccminer CUDA miner project, compatible with most nvidia cards
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1353 lines
34 KiB

/*
* 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 <winsock2.h>
#endif
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <inttypes.h>
#include <unistd.h>
#include <sys/time.h>
#include <time.h>
#include <math.h>
#include <stdarg.h>
#include <assert.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "miner.h"
#include "nvml.h"
#include "algos.h"
#ifndef WIN32
# include <errno.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
# 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[512];
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://<user>:<pass>@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);
}