WIP socket module

This commit is contained in:
K. Lange 2021-03-21 21:45:51 +09:00
parent d750b9600c
commit 492d85a2d6

397
src/module_socket.c Normal file
View File

@ -0,0 +1,397 @@
/**
* @file module_socket.c
* @brief Lightweight, low-level wrapper around the standard Berkley sockets API.
* @author K. Lange <klange@toaruos.org>
*
*/
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include "vm.h"
#include "util.h"
static KrkClass * SocketError = NULL;
static KrkClass * SocketClass = NULL;
struct socket {
KrkInstance inst;
int sockfd;
int family;
int type;
int proto;
};
#ifndef AF_INET6
#define AF_actual_INET6 -1
#else
#define AF_actual_INET6 AF_INET6
#endif
#ifndef AF_UNIX
#define AF_actual_UNIX -1
#else
#define AF_actual_UNIX AF_UNIX
#endif
#define IS_socket(o) (krk_isInstanceOf(o,SocketClass))
#define AS_socket(o) ((struct socket*)AS_OBJECT(o))
#define CURRENT_CTYPE struct socket *
#define CURRENT_NAME self
#define NAMED_ARG(name,type,ctype,def,ind) \
ctype name = def; \
if (argc > ind) { \
CHECK_ARG(ind,type,ctype,_tmp); \
name = _tmp; \
} \
if (hasKw) { \
KrkValue tmp; \
if (krk_tableGet(AS_DICT(argv[argc]), OBJECT_VAL(S(#name)), &tmp)) { \
if (!IS_ ## type (tmp)) return TYPE_ERROR(type,tmp); \
name = AS_ ## type (tmp); \
} \
}
KRK_METHOD(socket,__init__,{
METHOD_TAKES_AT_MOST(3);
/* Complex argument processing time... */
NAMED_ARG(family,int,krk_integer_type,AF_INET,1);
NAMED_ARG(type,int,krk_integer_type,SOCK_STREAM,2);
NAMED_ARG(proto,int,krk_integer_type,0,3);
int result = socket(family,type,proto);
if (result < 0) {
return krk_runtimeError(SocketError, "Socket error: %s", strerror(errno));
}
self->sockfd = result;
self->family = family;
self->type = type;
self->proto = proto;
return argv[0];
})
static char * _af_name(int afval) {
static char tmp[30];
switch (afval) {
case AF_INET: return "AF_INET";
#ifdef AF_INET6
case AF_INET6: return "AF_INET6";
#endif
#ifdef AF_UNIX
case AF_UNIX: return "AF_UNIX";
#endif
default:
snprintf(tmp,30,"%d",afval);
return tmp;
}
}
static char * _sock_type(int type) {
static char tmp[30];
switch (type) {
case SOCK_STREAM: return "SOCK_STREAM";
case SOCK_DGRAM: return "SOCK_DGRAM";
#ifdef SOCK_RAW
case SOCK_RAW: return "SOCK_RAW";
#endif
default:
snprintf(tmp,30,"%d",type);
return tmp;
}
}
KRK_METHOD(socket,__repr__,{
char tmp[4096];
size_t len = snprintf(tmp, 4096, "<socket.socket fd=%d, family=%s, type=%s, proto=%d>",
self->sockfd, _af_name(self->family), _sock_type(self->type), self->proto);
return OBJECT_VAL(krk_copyString(tmp,len));
})
KRK_METHOD(socket,bind,{
METHOD_TAKES_EXACTLY(1);
struct sockaddr_storage sock_addr;
socklen_t sock_size;
/* What do we take? I guess a tuple for AF_INET */
if (self->family == AF_INET) {
/* Should be 2-tuple */
CHECK_ARG(1,tuple,KrkTuple*,addr);
if (addr->values.count != 2) return TYPE_ERROR(2-tuple,argv[1]);
if (!IS_str(addr->values.values[0])) return TYPE_ERROR(str,addr->values.values[0]);
if (!IS_int(addr->values.values[1])) return TYPE_ERROR(int,addr->values.values[0]);
if (!AS_STRING(addr->values.values[0])->length) {
struct sockaddr_in * sin = (struct sockaddr_in*)&sock_addr;
sock_size = sizeof(struct sockaddr_in);
sin->sin_family = AF_INET;
sin->sin_port = htons(AS_int(addr->values.values[1]));
sin->sin_addr.s_addr = INADDR_ANY;
} else {
struct addrinfo *result;
struct addrinfo *res;
int error = getaddrinfo(AS_CSTRING(addr->values.values[0]), NULL, NULL, &result);
if (error < 0) {
return krk_runtimeError(SocketError, "Socket error: %s", strerror(errno));
}
int found = 0;
res = result;
while (res) {
if (res->ai_family == AF_INET) {
found = 1;
sock_size = res->ai_addrlen;
memcpy(&sock_addr, res->ai_addr, sock_size);
break;
}
res = res->ai_next;
}
freeaddrinfo(result);
if (!found) {
return krk_runtimeError(SocketError, "No suitable address.");
}
}
} else if (self->family == AF_actual_INET6) {
/* Should be 4-tuple */
CHECK_ARG(1,tuple,KrkTuple*,addr);
if (addr->values.count != 4) return TYPE_ERROR(4-tuple,argv[1]);
return krk_runtimeError(vm.exceptions->notImplementedError, "Not implemented.");
} else if (self->family == AF_actual_UNIX) {
/* Should be string */
CHECK_ARG(1,str,KrkString*,addr);
return krk_runtimeError(vm.exceptions->notImplementedError, "Not implemented.");
} else {
return krk_runtimeError(vm.exceptions->notImplementedError, "Not implemented.");
}
int result = bind(self->sockfd, (struct sockaddr*)&sock_addr, sock_size);
if (result < 0) {
return krk_runtimeError(SocketError, "Socket error: %s", strerror(errno));
}
})
KRK_METHOD(socket,listen,{
METHOD_TAKES_AT_MOST(1);
int backlog = 0;
if (argc > 1) {
CHECK_ARG(1,int,krk_integer_type,val);
backlog = val >= 0 ? val : 0;
}
int result = listen(self->sockfd, backlog);
if (result < 0) {
return krk_runtimeError(SocketError, "Socket error: %s", strerror(errno));
}
})
KRK_METHOD(socket,accept,{
struct sockaddr_storage addr;
socklen_t addrlen;
int result = accept(self->sockfd, (struct sockaddr*)&addr, &addrlen);
if (result < 0) {
return krk_runtimeError(SocketError, "Socket error: %s", strerror(errno));
}
KrkTuple * outTuple = krk_newTuple(2);
krk_push(OBJECT_VAL(outTuple));
struct socket * out = (struct socket*)krk_newInstance(SocketClass);
krk_push(OBJECT_VAL(out));
out->sockfd = result;
out->family = self->family;
out->type = self->type;
out->proto = self->proto;
outTuple->values.values[0] = krk_peek(0);
outTuple->values.count = 1;
krk_pop();
KrkTuple * addrTuple = krk_newTuple(2); /* TODO: Other formats */
krk_push(OBJECT_VAL(addrTuple));
if (self->family == AF_INET) {
char hostname[NI_MAXHOST] = "";
getnameinfo((struct sockaddr*)&addr, addrlen, hostname, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
addrTuple->values.values[0] = OBJECT_VAL(krk_copyString(hostname,strlen(hostname)));
addrTuple->values.count = 1;
addrTuple->values.values[1] = INTEGER_VAL(htons(((struct sockaddr_in*)&addr)->sin_port));
addrTuple->values.count = 2;
} else {
krk_push(NONE_VAL());
}
outTuple->values.values[1] = krk_peek(0);
outTuple->values.count = 2;
krk_pop();
return krk_pop();
})
KRK_METHOD(socket,shutdown,{
METHOD_TAKES_EXACTLY(1);
CHECK_ARG(1,int,krk_integer_type,how);
int result = shutdown(self->sockfd, how);
if (result < 0) {
return krk_runtimeError(SocketError, "Socket error: %s", strerror(errno));
}
})
KRK_METHOD(socket,recv,{
METHOD_TAKES_AT_LEAST(1);
METHOD_TAKES_AT_MOST(2);
CHECK_ARG(1,int,krk_integer_type,bufsize);
int flags = 0;
if (argc > 2) {
CHECK_ARG(2,int,krk_integer_type,_flags);
flags = _flags;
}
void * buf = malloc(bufsize);
ssize_t result = recv(self->sockfd, buf, bufsize, flags);
if (result < 0) {
free(buf);
return krk_runtimeError(SocketError, "Socket error: %s", strerror(errno));
}
KrkBytes * out = krk_newBytes(result,buf);
free(buf);
return OBJECT_VAL(out);
})
KRK_METHOD(socket,send,{
METHOD_TAKES_AT_LEAST(1);
METHOD_TAKES_AT_MOST(2);
CHECK_ARG(1,bytes,KrkBytes*,buf);
int flags = 0;
if (argc > 2) {
CHECK_ARG(2,int,krk_integer_type,_flags);
flags = _flags;
}
ssize_t result = send(self->sockfd, buf->bytes, buf->length, flags);
if (result < 0) {
return krk_runtimeError(SocketError, "Socket error: %s", strerror(errno));
}
return INTEGER_VAL(result);
})
KRK_METHOD(socket,fileno,{
return INTEGER_VAL(self->sockfd);
})
KRK_METHOD(socket,setsockopt,{
METHOD_TAKES_EXACTLY(3);
CHECK_ARG(1,int,krk_integer_type,level);
CHECK_ARG(2,int,krk_integer_type,optname);
int result;
if (IS_INTEGER(argv[3])) {
int val = AS_INTEGER(argv[3]);
result = setsockopt(self->sockfd, level, optname, &val, sizeof(int));
} else if (IS_BYTES(argv[3])) {
result = setsockopt(self->sockfd, level, optname, AS_BYTES(argv[3])->bytes, AS_BYTES(argv[3])->length);
} else {
return TYPE_ERROR(int or bytes,argv[3]);
}
if (result < 0) {
return krk_runtimeError(SocketError, "Socket error: %s", strerror(errno));
}
})
KRK_FUNC(htons,{
FUNCTION_TAKES_EXACTLY(1);
CHECK_ARG(0,int,krk_integer_type,value);
return INTEGER_VAL(htons(value));
})
KrkValue krk_module_onload_socket(void) {
KrkInstance * module = krk_newInstance(vm.baseClasses->moduleClass);
krk_push(OBJECT_VAL(module));
KRK_DOC(module, "Lightweight wrapper around the standard Berkeley sockets interface.");
KrkClass * socket = krk_makeClass(module, &SocketClass, "socket", vm.baseClasses->objectClass);
SocketClass->allocSize = sizeof(struct socket);
BIND_METHOD(socket,__init__);
BIND_METHOD(socket,__repr__);
BIND_METHOD(socket,bind);
BIND_METHOD(socket,listen);
BIND_METHOD(socket,accept);
BIND_METHOD(socket,shutdown);
BIND_METHOD(socket,recv);
BIND_METHOD(socket,send);
BIND_METHOD(socket,fileno);
BIND_METHOD(socket,setsockopt);
krk_defineNative(&socket->methods,".__str__", FUNC_NAME(socket,__repr__));
krk_finalizeClass(SocketClass);
BIND_FUNC(module, htons);
/* Constants */
#define CONST(o) krk_attachNamedValue(&module->fields, #o, INTEGER_VAL(o));
/**
* AF_ constants
* Taken from the manpages for Linux, and all shoved behind ifdefs, so this
* should build fine on most anything even if most of these aren't widely
* supported by other platforms.
*/
CONST(AF_INET);
#ifdef AF_INET6
CONST(AF_INET6);
#endif
#ifdef AF_UNIX
CONST(AF_UNIX);
#endif
/* SOCK_ constants, similarly */
CONST(SOCK_STREAM);
CONST(SOCK_DGRAM);
#ifdef SOCK_RAW
CONST(SOCK_RAW);
#endif
/* These are OR'd together with the above on Linux */
#ifdef SOCK_NONBLOCK
CONST(SOCK_NONBLOCK);
#endif
#ifdef SOCK_CLOEXEC
CONST(SOCK_CLOEXEC);
#endif
CONST(SHUT_RD);
CONST(SHUT_WR);
CONST(SHUT_RDWR);
CONST(SOL_SOCKET);
CONST(SO_REUSEADDR);
krk_makeClass(module, &SocketError, "SocketError", vm.exceptions->baseException);
KRK_DOC(SocketError, "Raised on faults from socket functions.");
krk_finalizeClass(SocketError);
return krk_pop();
}