From cce43e2bce24d4028c9f2a4a9fcb7c72b4e935eb Mon Sep 17 00:00:00 2001 From: Jose Marcial Vieira Bisneto Date: Fri, 21 Jul 2017 00:40:43 -0300 Subject: [PATCH 1/4] Initial API implementation. --- core/api.c | 215 ++++++++++++++++++++++++++++++++++++++++ include/ipfs/core/api.h | 35 +++++++ 2 files changed, 250 insertions(+) create mode 100644 core/api.c create mode 100644 include/ipfs/core/api.h diff --git a/core/api.c b/core/api.c new file mode 100644 index 0000000..5898053 --- /dev/null +++ b/core/api.c @@ -0,0 +1,215 @@ +/** + * Methods for lightweight/specific HTTP for API communication. + */ +#include +#include +#include +#include +#include +#include + +#include "libp2p/net/p2pnet.h" +#include "libp2p/utils/logger.h" +#include "ipfs/core/api.h" + +volatile int conns_count; + +pthread_t listen_thread = 0; + +struct s_list api_list; + +/** + * Create a new bitswap exchange + * @param ptr is the connection index in api_list, integer not pointer, cast required. + * @returns nothing + */ +void *api_connection_thread (void *ptr) +{ + int timeout, s, r; + const INT_TYPE i = (INT_TYPE) ptr; + char buf[MAX_READ+1]; + char client[INET_ADDRSTRLEN]; + + buf[MAX_READ] = '\0'; + + s = api_list.conns[i]->socket; + timeout = api_list.timeout; + + if (socket_read_select4(s, timeout) <= 0) { + libp2p_logger_error("api", "Client connection timeout.\n"); + goto quit; + } + r = read(s, buf, sizeof buf); + if (r <= 0) { + libp2p_logger_error("api", "Read from client fail.\n"); + goto quit; + } + buf[r] = '\0'; + if (cstrstart(buf, "GET ")) { + // just an error message, because it's not used. + write_cstr (s, "HTTP/1.1 404 Not Found\r\n" + "Content-Type: text/plain; charset=utf-8\r\n" + "X-Content-Type-Options: nosniff\r\n" + "Content-Length: 19\r\n\r\n" + "404 page not found\n"); + //} else if (cstrstart(buf, "POST ")) { + // TODO: Handle chunked/gzip/form-data/json POST requests. + } else { + write_cstr (s, "HTTP/1.1 400 Bad Request\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n\r\n" + "400 Bad Request"); + } + +quit: + if (inet_ntop(AF_INET, &(api_list.conns[i]->ipv4), client, INET_ADDRSTRLEN) == NULL) + strcpy(client, "UNKNOW"); + libp2p_logger_error("api", "Closing client connection %s:%d (%d).\n", client, api_list.conns[i]->port, i+1); + close(s); + free (api_list.conns[i]); + api_list.conns[i] = NULL; + conns_count--; + + return NULL; +} + +/** + * Close all connections stopping respectives pthreads and free allocated memory. + */ +void api_connections_cleanup (void) +{ + int i; + + if (conns_count > 0 && api_list.conns) { + for (i = 0 ; i < api_list.max_conns ; i++) { + if (api_list.conns[i]->pthread) { + pthread_cancel (api_list.conns[i]->pthread); + close (api_list.conns[i]->socket); + free (api_list.conns[i]); + api_list.conns[i] = NULL; + } + } + conns_count = 0; + } + if (api_list.conns) { + free (api_list.conns); + api_list.conns = NULL; + } +} + +/** + * Pthread to keep in background dealing with client connections. + * @param ptr is not used. + * @returns nothing + */ +void *api_listen_thread (void *ptr) +{ + int s; + INT_TYPE i; + uint32_t ipv4; + uint16_t port; + char client[INET_ADDRSTRLEN]; + + conns_count = 0; + + for (;;) { + s = socket_accept4(api_list.socket, &ipv4, &port); + if (s <= 0) { + break; + } + if (conns_count >= api_list.max_conns) { // limit reached. + libp2p_logger_error("api", "Limit of connections reached (%d).\n", api_list.max_conns); + close (s); + continue; + } + + conns_count++; + for (i = 0 ; i < api_list.max_conns && api_list.conns[i] ; i++); + api_list.conns[i] = malloc (sizeof (struct s_conns)); + if (!api_list.conns[i]) { + libp2p_logger_error("api", "Fail to allocate memory to accept connection.\n"); + close (s); + continue; + } + if (inet_ntop(AF_INET, &ipv4, client, INET_ADDRSTRLEN) == NULL) + strcpy(client, "UNKNOW"); + libp2p_logger_error("api", "Accept connection %s:%d (%d/%d), pthread %d.\n", client, port, conns_count, api_list.max_conns, i+1); + api_list.conns[i]->socket = s; + api_list.conns[i]->ipv4 = ipv4; + api_list.conns[i]->port = port; + if (pthread_create(&(api_list.conns[i]->pthread), NULL, api_connection_thread, (void*)i)) { + libp2p_logger_error("api", "Create pthread fail.\n"); + free (api_list.conns[i]); + api_list.conns[i] = NULL; + conns_count--; + close(s); + } + } + api_connections_cleanup (); + return NULL; +} + +/** + * Start API interface daemon. + * @param port. + * @param max_conns. + * @param timeout time out of client connection. + * @returns 0 when failure or 1 if success. + */ +int api_start (uint16_t port, int max_conns, int timeout) +{ + int s; + size_t alloc_size = sizeof(void*) * max_conns; + + api_list.ipv4 = hostname_to_ip("127.0.0.1"); // api is listening only on loopback. + api_list.port = port; + + if (listen_thread != 0) { + libp2p_logger_error("api", "API already running.\n"); + return 0; + } + + if ((s = socket_listen(socket_tcp4(), &(api_list.ipv4), &(api_list.port))) <= 0) { + libp2p_logger_error("api", "Failed to init API. port: %d\n", port); + return 0; + } + + api_list.socket = s; + api_list.max_conns = max_conns; + api_list.timeout = timeout; + + api_list.conns = malloc (alloc_size); + if (!api_list.conns) { + close (s); + libp2p_logger_error("api", "Error allocating memory.\n"); + return 0; + } + memset(api_list.conns, 0, alloc_size); + + if (pthread_create(&listen_thread, NULL, api_listen_thread, NULL)) { + close (s); + free (api_list.conns); + api_list.conns = NULL; + listen_thread = 0; + libp2p_logger_error("api", "Error creating thread for API.\n"); + return 0; + } + + return 1; +} + +/** + * Stop API. + * @returns 0 when failure or 1 if success. + */ +int api_stop (void) +{ + if (!listen_thread) return 0; + pthread_cancel(listen_thread); + + api_connections_cleanup (); + + listen_thread = 0; + + return 1; +} diff --git a/include/ipfs/core/api.h b/include/ipfs/core/api.h new file mode 100644 index 0000000..1c749eb --- /dev/null +++ b/include/ipfs/core/api.h @@ -0,0 +1,35 @@ +#pragma once + +#ifdef __x86_64__ + #define INT_TYPE uint64_t +#else + #define INT_TYPE uint32_t +#endif + +#define MAX_READ (32*1024) // 32k + +struct s_list { + int socket; + uint32_t ipv4; + uint16_t port; + int max_conns; + int timeout; + struct s_conns { + int socket; + uint32_t ipv4; + uint16_t port; + pthread_t pthread; + } **conns; +}; + +#define write_cstr(f,s) write(f,s,sizeof(s)-1) +#define write_str(f,s) write(f,s,strlen(s)) + +#define cstrstart(a,b) (memcmp(a,b,sizeof(b)-1)==0) +#define strstart(a,b) (memcmp(a,b,strlen(b))==0) + +void *api_connection_thread (void *ptr); +void api_connections_cleanup (void); +void *api_listen_thread (void *ptr); +int api_start (uint16_t port, int max_conns, int timeout); +int api_stop (void); From b26c94ec2d2f07d941c5d4ba8ce0e0563b5a0c25 Mon Sep 17 00:00:00 2001 From: Payas Date: Fri, 21 Jul 2017 22:39:50 -0700 Subject: [PATCH 2/4] Fixed broken links --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 45d7f4e..55123e1 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ libp2p: https://github.com/libp2p/specs

Prerequisites: To compile the C version you will need:
lmdb https://github.com/jmjatlanta/lmdb
-c-protobuf https://github.com/kenCode/c-protobuf
-c-multihash https://github.com/kenCode/c-multihash
-c-multiaddr https://github.com/kenCode/c-multiaddr
-c-libp2p https://github.com/kenCode/c-libp2p
+c-protobuf https://github.com/kenCode-de/c-protobuf
+c-multihash https://github.com/kenCode-de/c-multihash
+c-multiaddr https://github.com/kenCode-de/c-multiaddr
+c-libp2p https://github.com/kenCode-de/c-libp2p

-And of course this project at https://github.com/kenCode/c-ipfs
+And of course this project at https://github.com/kenCode-de/c-ipfs

The compilation at this point is simple, but not very flexible. Place all of these projects in a directory. Compile all (the order above is recommended) by going into each one and running "make all". From f7ddfa0088333b7b790e4d3beb271bf85b149a35 Mon Sep 17 00:00:00 2001 From: Jose Marcial Vieira Bisneto Date: Sun, 23 Jul 2017 11:49:40 -0300 Subject: [PATCH 3/4] Fixed thread-safe in API. --- core/api.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/api.c b/core/api.c index 5898053..cb69c6b 100644 --- a/core/api.c +++ b/core/api.c @@ -12,14 +12,15 @@ #include "libp2p/utils/logger.h" #include "ipfs/core/api.h" -volatile int conns_count; +pthread_mutex_t conns_lock; +int conns_count; pthread_t listen_thread = 0; struct s_list api_list; /** - * Create a new bitswap exchange + * Pthread to take care of each client connection. * @param ptr is the connection index in api_list, integer not pointer, cast required. * @returns nothing */ @@ -65,10 +66,12 @@ quit: if (inet_ntop(AF_INET, &(api_list.conns[i]->ipv4), client, INET_ADDRSTRLEN) == NULL) strcpy(client, "UNKNOW"); libp2p_logger_error("api", "Closing client connection %s:%d (%d).\n", client, api_list.conns[i]->port, i+1); + pthread_mutex_lock(&conns_lock); close(s); free (api_list.conns[i]); api_list.conns[i] = NULL; conns_count--; + pthread_mutex_unlock(&conns_lock); return NULL; } @@ -80,6 +83,7 @@ void api_connections_cleanup (void) { int i; + pthread_mutex_lock(&conns_lock); if (conns_count > 0 && api_list.conns) { for (i = 0 ; i < api_list.max_conns ; i++) { if (api_list.conns[i]->pthread) { @@ -95,6 +99,7 @@ void api_connections_cleanup (void) free (api_list.conns); api_list.conns = NULL; } + pthread_mutex_unlock(&conns_lock); } /** @@ -123,7 +128,7 @@ void *api_listen_thread (void *ptr) continue; } - conns_count++; + pthread_mutex_lock(&conns_lock); for (i = 0 ; i < api_list.max_conns && api_list.conns[i] ; i++); api_list.conns[i] = malloc (sizeof (struct s_conns)); if (!api_list.conns[i]) { @@ -143,7 +148,10 @@ void *api_listen_thread (void *ptr) api_list.conns[i] = NULL; conns_count--; close(s); + } else { + conns_count++; } + pthread_mutex_unlock(&conns_lock); } api_connections_cleanup (); return NULL; From bde4d4debef355e1794c90b4b531f43c773c61fd Mon Sep 17 00:00:00 2001 From: Jose Marcial Vieira Bisneto Date: Sun, 23 Jul 2017 22:24:14 -0300 Subject: [PATCH 4/4] Initial handling of http request structure. --- core/api.c | 77 +++++++++++++++++++++++++++++++++-------- include/ipfs/core/api.h | 29 ++++++++++++++++ 2 files changed, 91 insertions(+), 15 deletions(-) diff --git a/core/api.c b/core/api.c index cb69c6b..dde67fd 100644 --- a/core/api.c +++ b/core/api.c @@ -28,8 +28,11 @@ void *api_connection_thread (void *ptr) { int timeout, s, r; const INT_TYPE i = (INT_TYPE) ptr; - char buf[MAX_READ+1]; + char buf[MAX_READ+1], *p; char client[INET_ADDRSTRLEN]; + struct s_request req; + + req.buf = NULL; // sanity. buf[MAX_READ] = '\0'; @@ -46,23 +49,67 @@ void *api_connection_thread (void *ptr) goto quit; } buf[r] = '\0'; - if (cstrstart(buf, "GET ")) { - // just an error message, because it's not used. - write_cstr (s, "HTTP/1.1 404 Not Found\r\n" - "Content-Type: text/plain; charset=utf-8\r\n" - "X-Content-Type-Options: nosniff\r\n" - "Content-Length: 19\r\n\r\n" - "404 page not found\n"); - //} else if (cstrstart(buf, "POST ")) { - // TODO: Handle chunked/gzip/form-data/json POST requests. + + p = strstr(buf, "\r\n\r\n"); + + if (p) { + req.size = p - buf + 1; + req.buf = malloc(req.size); + if (!req.buf) { + // memory allocation fail. + libp2p_logger_error("api", "malloc fail.\n"); + write_cstr (s, HTTP_500); + goto quit; + } + memcpy(req.buf, buf, req.size - 1); + req.buf[req.size-1] = '\0'; + + req.method = req.buf; + p = strchr(req.method, ' '); + if (!p) { + write_cstr (s, HTTP_400); + goto quit; + } + *p++ = '\0'; // End of method. + req.path = p; + p = strchr(req.path, ' '); + if (!p) { + write_cstr (s, HTTP_400); + goto quit; + } + *p++ = '\0'; // End of path. + req.http_ver = p; + p = strchr(req.http_ver, '\r'); + if (!p) { + write_cstr (s, HTTP_400); + goto quit; + } + *p++ = '\0'; // End of http version. + while (*p == '\r' || *p == '\n') p++; + req.header = p; + req.body = req.buf + req.size; + req.body_size = 0; + + libp2p_logger_error("api", "method = '%s'\n" + "path = '%s'\n" + "http_ver = '%s'\n" + "header {\n%s\n}\n" + "body_size = %d\n", + req.method, req.path, req.http_ver, req.header, req.body_size); + + if (strcmp(req.method, "GET")==0) { + // just an error message, because it's not used. + write_cstr (s, HTTP_404); + //} else if (cstrstart(buf, "POST ")) { + // TODO: Handle chunked/gzip/form-data/json POST requests. + } } else { - write_cstr (s, "HTTP/1.1 400 Bad Request\r\n" - "Content-Type: text/plain\r\n" - "Connection: close\r\n\r\n" - "400 Bad Request"); + write_cstr (s, HTTP_400); } quit: + if (req.buf) + free(req.buf); if (inet_ntop(AF_INET, &(api_list.conns[i]->ipv4), client, INET_ADDRSTRLEN) == NULL) strcpy(client, "UNKNOW"); libp2p_logger_error("api", "Closing client connection %s:%d (%d).\n", client, api_list.conns[i]->port, i+1); @@ -138,7 +185,6 @@ void *api_listen_thread (void *ptr) } if (inet_ntop(AF_INET, &ipv4, client, INET_ADDRSTRLEN) == NULL) strcpy(client, "UNKNOW"); - libp2p_logger_error("api", "Accept connection %s:%d (%d/%d), pthread %d.\n", client, port, conns_count, api_list.max_conns, i+1); api_list.conns[i]->socket = s; api_list.conns[i]->ipv4 = ipv4; api_list.conns[i]->port = port; @@ -151,6 +197,7 @@ void *api_listen_thread (void *ptr) } else { conns_count++; } + libp2p_logger_error("api", "Accept connection %s:%d (%d/%d), pthread %d.\n", client, port, conns_count, api_list.max_conns, i+1); pthread_mutex_unlock(&conns_lock); } api_connections_cleanup (); diff --git a/include/ipfs/core/api.h b/include/ipfs/core/api.h index 1c749eb..663f797 100644 --- a/include/ipfs/core/api.h +++ b/include/ipfs/core/api.h @@ -22,6 +22,35 @@ struct s_list { } **conns; }; +struct s_request { + char *buf; + size_t size; + + char *method; + char *path; + char *http_ver; + char *header; + char *body; + size_t body_size; +}; + +#define HTTP_400 "HTTP/1.1 400 Bad Request\r\n" \ + "Content-Type: text/plain\r\n" \ + "Connection: close\r\n\r\n" \ + "400 Bad Request" + + +#define HTTP_404 "HTTP/1.1 404 Not Found\r\n" \ + "Content-Type: text/plain; charset=utf-8\r\n" \ + "X-Content-Type-Options: nosniff\r\n" \ + "Content-Length: 19\r\n\r\n" \ + "404 page not found\n" + +#define HTTP_500 "HTTP/1.1 500 Internal server error\r\n" \ + "Content-Type: text/plain\r\n" \ + "Connection: close\r\n\r\n" \ + "500 Internal server error" + #define write_cstr(f,s) write(f,s,sizeof(s)-1) #define write_str(f,s) write(f,s,strlen(s))