Skip to content

Instantly share code, notes, and snippets.

@aloktiagi
Created February 6, 2026 19:24
Show Gist options
  • Select an option

  • Save aloktiagi/73913cdfd038016fb1197998244b2801 to your computer and use it in GitHub Desktop.

Select an option

Save aloktiagi/73913cdfd038016fb1197998244b2801 to your computer and use it in GitHub Desktop.
/*
* qdisc-serve.c - HTTP server for traffic control visualization
* Compile: gcc -o qdisc-serve qdisc-serve.c
* Usage: sudo ./qdisc-serve [port]
* Then open: http://localhost:8080
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/pkt_sched.h>
#include <net/if.h>
#include <time.h>
#include <signal.h>
#define BUFFER_SIZE 32768
#define MAX_INTERFACES 512
#define MAX_RESPONSE 1048576 // 1MB for HTTP response
typedef struct {
int index;
char name[IFNAMSIZ];
} InterfaceRef;
static InterfaceRef interfaces[MAX_INTERFACES];
static int num_interfaces = 0;
static int server_fd = -1;
static volatile int running = 1;
void handle_sigint(int sig) {
running = 0;
if (server_fd >= 0) close(server_fd);
}
// Send netlink request
static int send_request(int sock, int type) {
struct {
struct nlmsghdr nlh;
union {
struct ifinfomsg ifm;
struct tcmsg tcm;
};
} req;
memset(&req, 0, sizeof(req));
req.nlh.nlmsg_type = type;
req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.nlh.nlmsg_seq = 1;
req.nlh.nlmsg_pid = getpid();
if (type == RTM_GETLINK) {
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req.ifm.ifi_family = AF_PACKET;
} else {
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
req.tcm.tcm_family = AF_UNSPEC;
}
return send(sock, &req, req.nlh.nlmsg_len, 0) > 0 ? 0 : -1;
}
// Send per-interface request
static int send_request_for_interface(int sock, int type, int ifindex) {
struct {
struct nlmsghdr nlh;
struct tcmsg tcm;
} req;
memset(&req, 0, sizeof(req));
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
req.nlh.nlmsg_type = type;
req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.nlh.nlmsg_seq = ifindex;
req.nlh.nlmsg_pid = getpid();
req.tcm.tcm_family = AF_UNSPEC;
req.tcm.tcm_ifindex = ifindex;
return send(sock, &req, req.nlh.nlmsg_len, 0) > 0 ? 0 : -1;
}
// Format bandwidth
static void format_bw(unsigned long long bps, char *buf, size_t len) {
if (bps == 0) {
buf[0] = '\0';
return;
}
if (bps >= 1000000000) {
snprintf(buf, len, "%.1fGbit", bps / 1000000000.0);
} else if (bps >= 1000000) {
snprintf(buf, len, "%.0fMbit", bps / 1000000.0);
} else if (bps >= 1000) {
snprintf(buf, len, "%.0fKbit", bps / 1000.0);
} else {
snprintf(buf, len, "%llubit", bps);
}
}
// Generate HTML response
static char* generate_html(char *hostname) {
static char response[MAX_RESPONSE];
char *ptr = response;
int remaining = MAX_RESPONSE;
int n;
// Start HTML
n = snprintf(ptr, remaining,
"<!DOCTYPE html>\n"
"<html><head>\n"
"<meta charset=\"UTF-8\">\n"
"<title>qdisc-serve - %s</title>\n"
"<style>\n", hostname);
ptr += n; remaining -= n;
// CSS (same as before but condensed)
n = snprintf(ptr, remaining,
":root{--bg:#0d1117;--surface:#161b22;--border:#30363d;--text:#e6edf3;--muted:#8b949e;--accent:#58a6ff;--green:#3fb950;--orange:#d29922;--red:#f85149;--purple:#bc8cff;--cyan:#39d2c0;--lime:#7ee787}"
"*{margin:0;padding:0;box-sizing:border-box}body{font-family:monospace;background:var(--bg);color:var(--text)}"
".app{display:flex;flex-direction:column;height:100vh}"
"header{padding:12px 24px;border-bottom:1px solid var(--border);background:var(--surface)}"
"header h1{font-size:15px;color:var(--accent);display:inline}header .sub{font-size:11px;color:var(--muted);margin-left:12px}"
".tabs{display:flex;gap:2px;padding:0 24px;background:var(--surface);border-bottom:1px solid var(--border)}"
".tab{padding:10px 20px;font-size:12px;cursor:pointer;border-bottom:2px solid transparent;color:var(--muted)}"
".tab:hover{color:var(--text)}.tab.active{color:var(--accent);border-bottom-color:var(--accent)}"
".viz{flex:1;overflow:auto;padding:24px}.view{display:none}.view.active{display:block}"
".summary{display:flex;gap:18px;margin-bottom:18px;padding:10px 14px;background:var(--surface);border:1px solid var(--border);border-radius:7px}"
".stat{font-size:11px}.stat .l{color:var(--muted)}.stat .v{color:var(--text);font-weight:600;margin-left:3px}"
".icard{background:var(--surface);border:1px solid var(--border);border-radius:7px;margin-bottom:12px}"
".ihdr{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer}.ihdr:hover{background:rgba(255,255,255,0.02)}"
".iname{font-size:13px;font-weight:600}.imeta{font-size:10px;color:var(--muted);display:flex;gap:10px}"
".tag{padding:1px 5px;border-radius:2px;font-size:9px;font-weight:600}"
".tag-up{background:rgba(63,185,80,0.15);color:var(--green)}"
".arrow{color:var(--muted);font-size:9px;margin-left:auto}.ihdr.exp .arrow{transform:rotate(90deg)}"
".qtree{border-top:1px solid var(--border);padding:14px;display:none}.qtree.vis{display:block}"
".qnode{padding:6px 0;font-size:11px}.qtype{color:var(--orange);font-weight:600}.qhandle{color:var(--muted);margin-left:4px}"
".bw{margin-top:4px;font-size:10px}.rate{color:var(--lime);margin-right:8px}.ceil{color:var(--orange)}"
".qkids{margin-left:20px;border-left:2px solid var(--border);padding-left:12px}"
".flow{max-width:800px;margin:0 auto}"
".ftitle{text-align:center;font-size:16px;font-weight:700;color:var(--accent);margin-bottom:24px}"
".fheader{text-align:center;font-size:13px;font-weight:700;padding:10px;border-radius:6px;margin:30px 0 20px}"
".fheader.egress{background:rgba(63,185,80,0.1);color:var(--green)}"
".fheader.ingress{background:rgba(248,81,73,0.1);color:var(--red)}"
".fbox{background:var(--surface);border:2px solid var(--border);border-radius:6px;padding:12px;margin:12px auto;max-width:400px;text-align:center}"
".fbox.egress{border-color:var(--green)}.fbox.ingress{border-color:var(--red)}"
".fbox-title{font-size:12px;font-weight:700}.fbox-sub{font-size:10px;color:var(--muted);margin-top:4px}"
".fbox-content{margin-top:8px;padding-top:8px;border-top:1px solid var(--border)}"
".fclass{font-size:10px;padding:3px 6px;margin:2px;display:inline-block;background:rgba(188,140,255,0.15);color:var(--purple);border-radius:3px}"
".fclass .rate{color:var(--lime);margin-left:4px}.fclass .ceil{color:var(--orange);margin-left:4px}"
".farrow{text-align:center;font-size:20px;margin:8px 0}"
".farrow.egress{color:var(--green)}.farrow.ingress{color:var(--red)}"
".flabel{text-align:center;font-size:10px;color:var(--muted);font-style:italic}"
".fredirect{background:rgba(188,140,255,0.15);border:2px dashed var(--purple);border-radius:6px;padding:10px;margin:12px auto;max-width:350px;text-align:center}"
".fredirect-title{font-size:11px;font-weight:700;color:var(--purple)}"
".fkey{background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:16px;margin-top:30px}"
".fkey-title{font-size:12px;font-weight:700;color:var(--orange);margin-bottom:8px}"
".fkey-content{font-size:10px;color:var(--muted);line-height:1.6}"
"</style>\n</head>\n<body>\n"
"<div class=\"app\">\n"
"<header><h1>qdisc-serve</h1><span class=\"sub\">%s - Live traffic control visualization</span></header>\n"
"<div class=\"tabs\">\n"
"<div class=\"tab active\" onclick=\"switchTab('tree')\">Tree View</div>\n"
"<div class=\"tab\" onclick=\"switchTab('flow')\">Flow Diagram</div>\n"
"</div>\n"
"<div class=\"viz\"><div id=\"tree-view\" class=\"view active\"></div><div id=\"flow-view\" class=\"view\"></div></div>\n"
"</div>\n<script>\n", hostname);
ptr += n; remaining -= n;
// Start JavaScript data
n = snprintf(ptr, remaining, "const DATA={interfaces:[");
ptr += n; remaining -= n;
// Query netlink data
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock < 0) return response;
struct sockaddr_nl sa;
memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;
sa.nl_pid = getpid();
bind(sock, (struct sockaddr *)&sa, sizeof(sa));
// Interfaces
char buf[BUFFER_SIZE];
struct nlmsghdr *nlh;
int len;
num_interfaces = 0;
if (send_request(sock, RTM_GETLINK) == 0) {
int first = 1, done = 0;
while (!done && remaining > 1000) {
len = recv(sock, buf, sizeof(buf), 0);
if (len <= 0) break;
nlh = (struct nlmsghdr *)buf;
while (NLMSG_OK(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_DONE) { done = 1; break; }
if (nlh->nlmsg_type == RTM_NEWLINK) {
struct ifinfomsg *ifi = NLMSG_DATA(nlh);
struct rtattr *rta = IFLA_RTA(ifi);
int rtalen = IFLA_PAYLOAD(nlh);
char name[IFNAMSIZ] = "?", parent[IFNAMSIZ] = "";
int mtu = 0;
while (RTA_OK(rta, rtalen)) {
if (rta->rta_type == IFLA_IFNAME) {
strncpy(name, RTA_DATA(rta), sizeof(name)-1);
if (num_interfaces < MAX_INTERFACES) {
interfaces[num_interfaces].index = ifi->ifi_index;
strncpy(interfaces[num_interfaces].name, name, IFNAMSIZ);
num_interfaces++;
}
}
else if (rta->rta_type == IFLA_MTU) mtu = *(int*)RTA_DATA(rta);
else if (rta->rta_type == IFLA_LINK) {
int parent_idx = *(int*)RTA_DATA(rta);
if_indextoname(parent_idx, parent);
}
rta = RTA_NEXT(rta, rtalen);
}
if (!first) { n = snprintf(ptr, remaining, ","); ptr += n; remaining -= n; }
first = 0;
n = snprintf(ptr, remaining, "{name:'%s',index:%d,state:'%s',mtu:%d",
name, ifi->ifi_index, (ifi->ifi_flags & IFF_UP) ? "UP" : "DOWN", mtu);
ptr += n; remaining -= n;
if (parent[0]) {
n = snprintf(ptr, remaining, ",parent:'%s'", parent);
ptr += n; remaining -= n;
}
n = snprintf(ptr, remaining, "}");
ptr += n; remaining -= n;
}
nlh = NLMSG_NEXT(nlh, len);
}
}
}
n = snprintf(ptr, remaining, "],qdiscs:[");
ptr += n; remaining -= n;
// Qdiscs
if (send_request(sock, RTM_GETQDISC) == 0) {
int first = 1, done = 0;
while (!done && remaining > 1000) {
len = recv(sock, buf, sizeof(buf), 0);
if (len <= 0) break;
nlh = (struct nlmsghdr *)buf;
while (NLMSG_OK(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_DONE) { done = 1; break; }
if (nlh->nlmsg_type == RTM_NEWQDISC) {
struct tcmsg *tcm = NLMSG_DATA(nlh);
struct rtattr *rta = TCA_RTA(tcm);
int rtalen = TCA_PAYLOAD(nlh);
char kind[64] = "?", ifname[IFNAMSIZ];
if_indextoname(tcm->tcm_ifindex, ifname);
while (RTA_OK(rta, rtalen)) {
if (rta->rta_type == TCA_KIND) strncpy(kind, RTA_DATA(rta), sizeof(kind)-1);
rta = RTA_NEXT(rta, rtalen);
}
if (!first) { n = snprintf(ptr, remaining, ","); ptr += n; remaining -= n; }
first = 0;
n = snprintf(ptr, remaining, "{dev:'%s',kind:'%s',handle:'%x:',parent:'%x:%x',isRoot:%s,ifindex:%d}",
ifname, kind, tcm->tcm_handle >> 16,
tcm->tcm_parent >> 16, tcm->tcm_parent & 0xFFFF,
(tcm->tcm_parent == TC_H_ROOT) ? "true" : "false",
tcm->tcm_ifindex);
ptr += n; remaining -= n;
}
nlh = NLMSG_NEXT(nlh, len);
}
}
}
n = snprintf(ptr, remaining, "],classes:[");
ptr += n; remaining -= n;
// Classes (per-interface)
int first_class = 1;
for (int i = 0; i < num_interfaces && remaining > 1000; i++) {
if (send_request_for_interface(sock, RTM_GETTCLASS, interfaces[i].index) == 0) {
int done = 0;
while (!done) {
len = recv(sock, buf, sizeof(buf), 0);
if (len <= 0) break;
nlh = (struct nlmsghdr *)buf;
while (NLMSG_OK(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_DONE) { done = 1; break; }
if (nlh->nlmsg_type == RTM_NEWTCLASS) {
struct tcmsg *tcm = NLMSG_DATA(nlh);
struct rtattr *rta = TCA_RTA(tcm);
int rtalen = TCA_PAYLOAD(nlh);
char kind[64] = "?", ifname[IFNAMSIZ], rate_str[64] = "", ceil_str[64] = "";
unsigned long long rate = 0, ceil = 0;
if_indextoname(tcm->tcm_ifindex, ifname);
while (RTA_OK(rta, rtalen)) {
if (rta->rta_type == TCA_KIND) {
strncpy(kind, RTA_DATA(rta), sizeof(kind)-1);
} else if (rta->rta_type == TCA_OPTIONS && strcmp(kind, "htb") == 0) {
struct rtattr *nested = RTA_DATA(rta);
int nested_len = RTA_PAYLOAD(rta);
while (RTA_OK(nested, nested_len)) {
if (nested->rta_type == TCA_HTB_PARMS) {
struct tc_htb_opt *htb = RTA_DATA(nested);
rate = (unsigned long long)htb->rate.rate * 8;
ceil = (unsigned long long)htb->ceil.rate * 8;
format_bw(rate, rate_str, sizeof(rate_str));
format_bw(ceil, ceil_str, sizeof(ceil_str));
}
nested = RTA_NEXT(nested, nested_len);
}
}
rta = RTA_NEXT(rta, rtalen);
}
if (!first_class) { n = snprintf(ptr, remaining, ","); ptr += n; remaining -= n; }
first_class = 0;
n = snprintf(ptr, remaining, "{dev:'%s',classid:'%x:%x',parent:'%x:%x'",
ifname, tcm->tcm_handle >> 16, tcm->tcm_handle & 0xFFFF,
tcm->tcm_parent >> 16, tcm->tcm_parent & 0xFFFF);
ptr += n; remaining -= n;
if (rate_str[0]) {
n = snprintf(ptr, remaining, ",rate:'%s'", rate_str);
ptr += n; remaining -= n;
}
if (ceil_str[0]) {
n = snprintf(ptr, remaining, ",ceil:'%s'", ceil_str);
ptr += n; remaining -= n;
}
n = snprintf(ptr, remaining, "}");
ptr += n; remaining -= n;
}
nlh = NLMSG_NEXT(nlh, len);
}
}
}
}
close(sock);
n = snprintf(ptr, remaining, "]};\n");
ptr += n; remaining -= n;
// Add JavaScript (simplified versions of buildTree and buildFlow)
n = snprintf(ptr, remaining,
"function switchTab(t){document.querySelectorAll('.tab').forEach(e=>e.classList.remove('active'));document.querySelectorAll('.view').forEach(e=>e.classList.remove('active'));document.querySelector('.tab:nth-child('+(t==='tree'?'1':'2')+')').classList.add('active');document.getElementById(t+'-view').classList.add('active');}\n"
"function buildTree(){let h='<div class=\"summary\">';h+='<div class=\"stat\"><span class=\"l\">Interfaces:</span><span class=\"v\">'+DATA.interfaces.length+'</span></div>';h+='<div class=\"stat\"><span class=\"l\">Qdiscs:</span><span class=\"v\">'+DATA.qdiscs.length+'</span></div>';h+='<div class=\"stat\"><span class=\"l\">Classes:</span><span class=\"v\">'+DATA.classes.length+'</span></div>';h+='<div class=\"stat\"><span class=\"l\">With BW:</span><span class=\"v\">'+DATA.classes.filter(c=>c.rate).length+'</span></div></div>';for(const i of DATA.interfaces){if(i.name==='lo')continue;const qs=DATA.qdiscs.filter(q=>q.dev===i.name&&q.isRoot);const cs=DATA.classes.filter(c=>c.dev===i.name);h+='<div class=\"icard\"><div class=\"ihdr\" onclick=\"togQ(this)\"><div class=\"iname\">'+i.name+(i.parent?' @'+i.parent:'')+'</div><div class=\"imeta\">';if(i.state==='UP')h+='<span class=\"tag tag-up\">UP</span>';h+='<span>MTU '+i.mtu+'</span></div><span class=\"arrow\">&#9654;</span></div><div class=\"qtree\">';for(const q of qs){h+='<div class=\"qnode\"><span class=\"qtype\">'+q.kind+'</span><span class=\"qhandle\">'+q.handle+'</span>';const qcs=cs.filter(c=>parseInt(c.parent.split(':')[0],16)===(parseInt(q.handle)));if(qcs.length){h+='<div class=\"qkids\">';for(const c of qcs){h+='<div class=\"qnode\"><span class=\"qtype\">class '+c.classid+'</span>';if(c.rate||c.ceil){h+='<div class=\"bw\">';if(c.rate)h+='<span class=\"rate\">rate '+c.rate+'</span>';if(c.ceil)h+='<span class=\"ceil\">ceil '+c.ceil+'</span>';h+='</div>';}h+='</div>';}h+='</div>';}h+='</div>';}h+='</div></div>';}document.getElementById('tree-view').innerHTML=h;}\n"
"function buildFlow(){const eth1=DATA.interfaces.find(i=>i.name==='eth1');const ifb=DATA.interfaces.find(i=>i.name.startsWith('ifb-ingress'));const vlans=DATA.interfaces.filter(i=>i.name.startsWith('vlan-')).length;const eth1Cs=DATA.classes.filter(c=>c.dev==='eth1');const ifbCs=ifb?DATA.classes.filter(c=>c.dev===ifb.name):[];let h='<div class=\"flow\"><div class=\"ftitle\">Traffic Control Flow Diagram</div>';h+='<div class=\"fheader egress\">EGRESS: Container -&gt; Network</div>';h+='<div class=\"fbox\"><div class=\"fbox-title\">Container</div></div><div class=\"farrow egress\">&darr;</div>';if(vlans>0)h+='<div class=\"fbox\"><div class=\"fbox-title\">VLANs (x'+vlans+')</div></div><div class=\"farrow egress\">&darr;</div>';h+='<div class=\"fbox egress\"><div class=\"fbox-title\">eth1 (Physical)</div>';if(eth1Cs.length){h+='<div class=\"fbox-content\"><div style=\"max-height:300px;overflow-y:auto\">';for(const c of eth1Cs)h+='<div class=\"fclass\">'+c.classid+(c.rate?' <span class=\"rate\">R:'+c.rate+'</span>':'')+(c.ceil?' <span class=\"ceil\">C:'+c.ceil+'</span>':'')+'</div>';h+='</div></div>';}h+='</div><div class=\"farrow egress\">&darr;</div><div class=\"fbox\"><div class=\"fbox-title\">Network</div></div>';h+='<div class=\"fheader ingress\">INGRESS: Network -&gt; Container</div>';h+='<div class=\"fbox\"><div class=\"fbox-title\">Network</div></div><div class=\"farrow ingress\">&darr;</div><div class=\"fbox\"><div class=\"fbox-title\">eth1 (receive)</div></div><div class=\"farrow ingress\">&darr;</div>';if(ifb){h+='<div class=\"fredirect\"><div class=\"fredirect-title\">clsact REDIRECT to IFB</div></div><div class=\"farrow ingress\">&darr;</div>';h+='<div class=\"fbox ingress\"><div class=\"fbox-title\">'+ifb.name+' ('+ifbCs.length+' classes)</div>';if(ifbCs.length){h+='<div class=\"fbox-content\"><div style=\"max-height:300px;overflow-y:auto\">';for(const c of ifbCs)h+='<div class=\"fclass\">'+c.classid+(c.rate?' <span class=\"rate\">R:'+c.rate+'</span>':'')+(c.ceil?' <span class=\"ceil\">C:'+c.ceil+'</span>':'')+'</div>';h+='</div></div>';}h+='</div><div class=\"farrow ingress\">&darr;</div>';}if(vlans>0)h+='<div class=\"fbox\"><div class=\"fbox-title\">VLANs (x'+vlans+')</div></div><div class=\"farrow ingress\">&darr;</div>';h+='<div class=\"fbox\"><div class=\"fbox-title\">Container</div></div>';h+='<div class=\"fkey\"><div class=\"fkey-title\">Key: IFB Redirect Trick</div><div class=\"fkey-content\">Linux can only shape egress. For ingress: clsact intercepts packets, redirects to IFB virtual device where they appear as egress and can be shaped by HTB.</div></div></div>';document.getElementById('flow-view').innerHTML=h;}\n"
"function togQ(el){el.nextElementSibling.classList.toggle('vis');el.classList.toggle('exp');}\n"
"buildTree();buildFlow();\n"
"</script></body></html>\n");
ptr += n; remaining -= n;
return response;
}
int main(int argc, char *argv[]) {
int port = 8080;
char hostname[256];
if (argc > 1) {
port = atoi(argv[1]);
if (port <= 0 || port > 65535) {
fprintf(stderr, "Invalid port number\n");
return 1;
}
}
if (geteuid() != 0) {
fprintf(stderr, "Error: Root privileges required\n");
fprintf(stderr, "Usage: sudo %s [port]\n", argv[0]);
return 1;
}
if (gethostname(hostname, sizeof(hostname)) != 0) {
strcpy(hostname, "unknown");
}
signal(SIGINT, handle_sigint);
signal(SIGTERM, handle_sigint);
// Create server socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
return 1;
}
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(server_fd);
return 1;
}
if (listen(server_fd, 5) < 0) {
perror("listen");
close(server_fd);
return 1;
}
printf("\n");
printf("╔══════════════════════════════════════════════════════════╗\n");
printf("║ ║\n");
printf("║ qdisc-serve - Traffic Control Visualization Server ║\n");
printf("║ ║\n");
printf("╚══════════════════════════════════════════════════════════╝\n");
printf("\n");
printf(" Server running on: http://localhost:%d\n", port);
printf(" Host: %s\n", hostname);
printf("\n");
printf(" Open in your browser to view live traffic control data\n");
printf(" Press Ctrl+C to stop\n");
printf("\n");
while (running) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
if (running) perror("accept");
continue;
}
// Read request (we don't really care what it is)
char req[1024];
recv(client_fd, req, sizeof(req), 0);
// Generate fresh HTML with current data
char *html = generate_html(hostname);
// Send response
char header[512];
snprintf(header, sizeof(header),
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Content-Length: %ld\r\n"
"Connection: close\r\n"
"\r\n", strlen(html));
send(client_fd, header, strlen(header), 0);
send(client_fd, html, strlen(html), 0);
close(client_fd);
}
close(server_fd);
printf("\nServer stopped\n");
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment