From 6be00706aac89d85dc7768d2923f65c5fd81930f Mon Sep 17 00:00:00 2001 From: Matt Arnold Date: Fri, 5 Sep 2025 16:55:37 -0400 Subject: [PATCH] add network transparent i/o demo --- daemons.py | 68 +++++++++++++++------ lstnr.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 18 deletions(-) create mode 100644 lstnr.c diff --git a/daemons.py b/daemons.py index cff37bf..e4705a1 100644 --- a/daemons.py +++ b/daemons.py @@ -17,7 +17,7 @@ import signal # I get back more and more AI slop every time I look. # This trick is not something i see often anymore. # Therefore let's recreate a classic. -# Daemon's Demystified 2.0 starts right now + # Sometimes programs need to run unattended, without tying up # a terminal session. This is less of a problem now today @@ -75,6 +75,18 @@ Never gonna tell a lie and hurt you """ +OTHER_RESP = """ +HTTP/1.1 403 Forbidden +Date: Fri, 01 Jan 1988 12:56:49 GMT +Content-Type: application/json +Content-Length: 110 + +{ + "error": "TogetherForever", + "message": "Together forever and never to part Together forever we two ." +} + +""" client_procs = [] svr_proc = None @@ -108,7 +120,7 @@ def server_handler(): client, addr = serversock.accept() print(addr) client_procs.append(gevent.spawn(client_handler, client)) - gevent.sleep(0) + gevent.sleep(0.25) serversock.close() return @@ -120,14 +132,34 @@ def server_handler(): # # This is somewhat useful in elfing with ai scraper bots. # There are better methods if you wanna try that + +# this is done with a bit of defensive programing +# to ensure a client can't cause us not to release control +# of the greenlet. + +# gevent is a cooperative concurrency model. +# it multiplexs tasks on to one os thread +# each task is done inside a "greenlet".abs +# Which is like a thread except that it is +# the programers responsibility to handle scheduling +# By putting the greenlet to sleep when that's possible + + def client_handler(sock): print("Client handler spawn") + junk_counter = 0 while True: + if junk_counter > 3: + sock.close() + return data = sock.recv(4096) dstring = data.decode("UTF-8") if dstring.startswith("GET"): break - gevent.sleep(0) + else: + sock.send(OTHER_RESP.encode("utf-8")) + junk_counter += 1 + gevent.sleep(0.25) sock.send(RICKROLL_LYRICS.encode("utf-8")) sock.close() @@ -163,22 +195,22 @@ else: os.setpgrp() os.umask(0) -print(os.getpid()) # to aid in stopping the server -# We want to close our connection to the controlling terminal -# to avoid accedentially spamming the use. And causing interactive processes -# to be SIGSTOP'ed. I do this with a Null Device class. -# You could just as easily do some sort of logging thing. -sys.stdin.close() -sys.stdout = NullDevice() -sys.stderr = NullDevice() + print(os.getpid()) # to aid in stopping the server + # We want to close our connection to the controlling terminal + # to avoid accedentially spamming the use. And causing interactive processes + # to be SIGSTOP'ed. I do this with a Null Device class. + # You could just as easily do some sort of logging thing. + sys.stdin.close() + sys.stdout = NullDevice() + sys.stderr = NullDevice() -# The last thing we do before handing things off to the daemon's main -# function is set up the daemon's signal table how we want it -# fork, may have initialized it with the default handlers -# depending on implementation -signal.signal(signal.SIGHUP, hup_handle) -signal.signal(signal.SIGTERM, hup_handle) -daemon_main() + # The last thing we do before handing things off to the daemon's main + # function is set up the daemon's signal table how we want it + # fork, may have initialized it with the default handlers + # depending on implementation + signal.signal(signal.SIGHUP, hup_handle) + signal.signal(signal.SIGTERM, hup_handle) + daemon_main() # References diff --git a/lstnr.c b/lstnr.c new file mode 100644 index 0000000..023e4ec --- /dev/null +++ b/lstnr.c @@ -0,0 +1,171 @@ +/* +** server.c -- a stream socket server demo +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORT "3490" // the port users will be connecting to + +#define BACKLOG 10 // how many pending connections queue will hold +char kthanksbye[2] = {EOF, '\0'}; +int proclaunch(char **args, int sockfd) +{ + pid_t pid; + int status; + + pid = fork(); + if (pid == 0) { + dup2(sockfd, 0); + dup2(sockfd, 1); + dup2(sockfd, 2); + + // Child process + if (execvp(args[0], args) == -1) { + perror("EXECERROR"); + } + exit(EXIT_FAILURE); + } else if (pid < 0) { + // Error forking + perror("fork error"); + } else { + // Parent process + do { + waitpid(pid, &status, WUNTRACED); + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + } + + return 1; +} + +void sigchld_handler(int s) +{ + // waitpid() might overwrite errno, so we save and restore it: + int saved_errno = errno; + + while(waitpid(-1, NULL, WNOHANG) > 0); + + errno = saved_errno; +} + + +// get sockaddr, IPv4 or IPv6: +void *get_in_addr(struct sockaddr *sa) +{ + if (sa->sa_family == AF_INET) { + return &(((struct sockaddr_in*)sa)->sin_addr); + } + + return &(((struct sockaddr_in6*)sa)->sin6_addr); +} + +int main(int argc, char *argv[]) +{ + if (argc < 3) { + exit(1); + } + int sockfd, new_fd; // listen on sock_fd, new connection on new_fd + struct addrinfo hints, *servinfo, *p; + struct sockaddr_storage their_addr; // connector's address information + socklen_t sin_size; + struct sigaction sa; + int yes=1; + char s[INET6_ADDRSTRLEN]; + int rv; + + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; // use my IP + + if ((rv = getaddrinfo(NULL, argv[1], &hints, &servinfo)) != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); + return 1; + } + + // loop through all the results and bind to the first we can + for (p = servinfo; p != NULL; p = p->ai_next) { + if ((sockfd = socket(p->ai_family, p->ai_socktype, + p->ai_protocol)) == -1) { + perror("server: socket"); + continue; + } + + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, + sizeof(int)) == -1) { + perror("setsockopt"); + exit(1); + } + + if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { + close(sockfd); + perror("server: bind"); + continue; + } + + break; + } + + freeaddrinfo(servinfo); // all done with this structure + + if (p == NULL) { + fprintf(stderr, "server: failed to bind\n"); + exit(1); + } + + if (listen(sockfd, BACKLOG) == -1) { + perror("listen"); + exit(1); + } + + sa.sa_handler = sigchld_handler; // reap all dead processes + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + if (sigaction(SIGCHLD, &sa, NULL) == -1) { + perror("sigaction"); + exit(1); + } + + printf("lstnr: Using %s\n", argv[2]); + + while(1) { // main accept() loop + sin_size = sizeof their_addr; + new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); + if (new_fd == -1) { + perror("accept"); + continue; + } + + inet_ntop(their_addr.ss_family, + get_in_addr((struct sockaddr *)&their_addr), + s, sizeof s); + printf("server: got connection from %s\n", s); + + if (!fork()) { // this is the child process + close(sockfd); // child doesn't need the listener + proclaunch(&argv[2], new_fd); + ssize_t w; + w = write(new_fd, &kthanksbye, 2); + if (w == -1) { + if (errno != EINTR) perror("HUPFAIL"); + close(new_fd); + exit(1); + } + close(new_fd); + exit(0); + } + close(new_fd); // parent doesn't need this + } + + return 0; +}