Created
February 6, 2026 19:24
-
-
Save aloktiagi/73913cdfd038016fb1197998244b2801 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * 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\">▶</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 -> Network</div>';h+='<div class=\"fbox\"><div class=\"fbox-title\">Container</div></div><div class=\"farrow egress\">↓</div>';if(vlans>0)h+='<div class=\"fbox\"><div class=\"fbox-title\">VLANs (x'+vlans+')</div></div><div class=\"farrow egress\">↓</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\">↓</div><div class=\"fbox\"><div class=\"fbox-title\">Network</div></div>';h+='<div class=\"fheader ingress\">INGRESS: Network -> Container</div>';h+='<div class=\"fbox\"><div class=\"fbox-title\">Network</div></div><div class=\"farrow ingress\">↓</div><div class=\"fbox\"><div class=\"fbox-title\">eth1 (receive)</div></div><div class=\"farrow ingress\">↓</div>';if(ifb){h+='<div class=\"fredirect\"><div class=\"fredirect-title\">clsact REDIRECT to IFB</div></div><div class=\"farrow ingress\">↓</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\">↓</div>';}if(vlans>0)h+='<div class=\"fbox\"><div class=\"fbox-title\">VLANs (x'+vlans+')</div></div><div class=\"farrow ingress\">↓</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