|
|
|
/*
|
|
|
|
* 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 <winsock2.h>
|
|
|
|
# include <mstcpip.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdint.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 "compat.h"
|
|
|
|
#include "miner.h"
|
|
|
|
|
|
|
|
#ifndef _MSC_VER
|
|
|
|
# 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;
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|