mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-25 16:52:29 +00:00
feat(net,gui): real internet access -- redirects, HTTPS guard, netcheck, and a live ONLINE indicator
The stack could already reach the real internet once the UDP-checksum and
scheduler fixes landed; this makes it work on real-world URLs and gives the
desktop a first-class "is the internet up?" answer.
HTTP client
- Follows 3xx redirects (Location header, absolute or relative) up to five
hops, so a bare host that 301s to a path or a canonical host resolves to
actual content. Verified: `webget google.com/` follows the 301 to
www.google.com and returns its HTTP 200 homepage.
- Refuses https:// targets -- at the initial URL and on redirect -- with a
clear message instead of speaking plaintext to port 443 and timing out.
- Split the URL parser out so both the initial request and redirect targets
go through the same path; added a case-insensitive header finder.
netcheck (CLI)
One command that walks the whole chain -- link, address, gateway ping, DNS,
and a real HTTP fetch -- and reports each PASS/FAIL, ending in ONLINE or
DEGRADED. The one-shot answer to "is the internet working?".
Network Monitor (GUI)
A live Internet: ONLINE/OFFLINE badge with the resolved IP, HTTP status and
latency. It auto-probes once on open and re-checks on the Check button, all
through the async HTTP worker so the window never blocks.
Verified against the real internet through QEMU's NAT: DNS resolved
example.com to a live Cloudflare address; `netcheck` passed all five layers
(Internet: ONLINE); the GUI badge showed "ONLINE 104.20.23.154 HTTP 200 240
ms"; redirect following and the HTTPS refusal both behaved. Builds clean on
x86_64, aarch64 and armv8i.
Known limit: no TLS, so HTTPS URLs are reported rather than fetched -- plain
HTTP only.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
This commit is contained in:
1 parent
dd4922767a
commit
f6e4511047
4 files changed
+308
-33
No files matched your search
+78
-2
@@ -26,6 +26,7 @@
|
||||
#include <net/tcp.h>
|
||||
#include <net/udp.h>
|
||||
#include <net/socket.h>
|
||||
#include <net/http_client.h>
|
||||
#include <drivers/e1000.h>
|
||||
|
||||
#define NETMON_HISTORY 64
|
||||
@@ -46,8 +47,57 @@ typedef struct {
|
||||
bool primed; // a baseline reading has been taken
|
||||
|
||||
int scroll; // first connection row shown
|
||||
|
||||
// Internet reachability probe (async, via the HTTP worker).
|
||||
http_fetch_t* probe;
|
||||
int inet_state; // 0 unknown, 1 checking, 2 online, 3 offline
|
||||
char inet_detail[72];
|
||||
bool auto_checked;
|
||||
} netmon_data_t;
|
||||
|
||||
#define INET_UNKNOWN 0
|
||||
#define INET_CHECKING 1
|
||||
#define INET_ONLINE 2
|
||||
#define INET_OFFLINE 3
|
||||
|
||||
#define NETMON_PROBE_HOST "example.com/" // a plain-HTTP host to reach for
|
||||
|
||||
// Advance the reachability probe: collect a finished result, or note that one
|
||||
// is still in flight. Runs every paint; the fetch itself is on the worker.
|
||||
static void netmon_poll_probe(netmon_data_t* nd) {
|
||||
if (!nd->probe) return;
|
||||
|
||||
int st = nd->probe->state;
|
||||
if (st == HTTP_STATE_RUNNING) { nd->inet_state = INET_CHECKING; return; }
|
||||
|
||||
if (st == HTTP_STATE_DONE && nd->probe->status_code > 0) {
|
||||
char ipx[16];
|
||||
ip_to_str(nd->probe->ip, ipx);
|
||||
nd->inet_state = INET_ONLINE;
|
||||
snprintf(nd->inet_detail, sizeof(nd->inet_detail), "%s HTTP %d %u ms",
|
||||
ipx, nd->probe->status_code, nd->probe->elapsed_ms);
|
||||
} else {
|
||||
nd->inet_state = INET_OFFLINE;
|
||||
snprintf(nd->inet_detail, sizeof(nd->inet_detail), "%s",
|
||||
st == HTTP_STATE_ERROR ? nd->probe->err : "no response");
|
||||
}
|
||||
|
||||
http_fetch_release(nd->probe);
|
||||
nd->probe = NULL;
|
||||
}
|
||||
|
||||
static void netmon_start_probe(netmon_data_t* nd) {
|
||||
if (nd->probe) return; // one in flight already
|
||||
nd->probe = http_fetch_start(NETMON_PROBE_HOST, 4096);
|
||||
if (nd->probe) {
|
||||
nd->inet_state = INET_CHECKING;
|
||||
} else {
|
||||
// The single worker slot is busy (e.g. the Web app is fetching).
|
||||
nd->inet_state = INET_UNKNOWN;
|
||||
snprintf(nd->inet_detail, sizeof(nd->inet_detail), "worker busy, try again");
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Sampling
|
||||
// ===========================================================================
|
||||
@@ -217,8 +267,32 @@ static void netmon_paint(gui_window_t* win) {
|
||||
(unsigned)tcp_get_connections_count(), tcp_get_listener_count());
|
||||
gui_label_aligned(0, row + 12, w - 12, buf, GUI_THEME_TEXT_MUTED, GUI_ALIGN_RIGHT);
|
||||
|
||||
// --- internet reachability -------------------------------------------
|
||||
// Kick off one probe automatically the first time the window paints, then
|
||||
// let the user re-check on demand. The fetch runs on the worker thread, so
|
||||
// this stays responsive while a check is in flight.
|
||||
if (!nd->auto_checked) { nd->auto_checked = true; netmon_start_probe(nd); }
|
||||
netmon_poll_probe(nd);
|
||||
|
||||
int irow = row + 28;
|
||||
static const char* const inet_labels[] = { "UNKNOWN", "checking...", "ONLINE", "OFFLINE" };
|
||||
uint32_t inet_col = (nd->inet_state == INET_ONLINE) ? GUI_THEME_SUCCESS
|
||||
: (nd->inet_state == INET_OFFLINE) ? GUI_THEME_DANGER
|
||||
: (nd->inet_state == INET_CHECKING)? GUI_THEME_WARNING
|
||||
: GUI_THEME_TEXT_DIM;
|
||||
gui_label(10, irow, "Internet:", GUI_THEME_TEXT_MUTED);
|
||||
gui_badge(84, irow - 1, inet_labels[nd->inet_state],
|
||||
inet_col, GUI_COLOR_BLACK);
|
||||
if (nd->inet_detail[0]) {
|
||||
gui_label(150, irow, nd->inet_detail, GUI_THEME_TEXT_DIM);
|
||||
}
|
||||
if (gui_button(5, w - 82, irow - 3, 72, 18,
|
||||
nd->probe ? "checking" : "Check")) {
|
||||
netmon_start_probe(nd);
|
||||
}
|
||||
|
||||
// --- connection table -------------------------------------------------
|
||||
int tbl_y = row + 30;
|
||||
int tbl_y = row + 46;
|
||||
gui_separator(10, tbl_y - 4, w - 20);
|
||||
gui_label_bold(10, tbl_y, "PROTO LOCAL", GUI_THEME_TEXT_DIM);
|
||||
gui_label(w / 2 + 6, tbl_y, "REMOTE", GUI_THEME_TEXT_DIM);
|
||||
@@ -306,7 +380,9 @@ static void netmon_event(gui_window_t* win, const gui_event_t* ev) {
|
||||
gui_widget_feed_event(win, ev);
|
||||
|
||||
if (ev->type == GUI_EVENT_CLOSE && win->user_data) {
|
||||
kfree(win->user_data);
|
||||
netmon_data_t* nd = (netmon_data_t*)win->user_data;
|
||||
if (nd->probe) http_fetch_release(nd->probe); // safe mid-fetch
|
||||
kfree(nd);
|
||||
win->user_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ typedef struct http_fetch {
|
||||
char host[128];
|
||||
char path[256];
|
||||
uint16_t port;
|
||||
bool tls; // the URL asked for https, which we cannot speak
|
||||
|
||||
// Result. `state` is the only field the requester may read while the fetch
|
||||
// is in flight; everything else is valid once state != RUNNING.
|
||||
@@ -41,10 +42,11 @@ typedef struct http_fetch {
|
||||
int len; // bytes stored
|
||||
int cap; // buffer capacity
|
||||
|
||||
uint32_t ip; // resolved peer address
|
||||
uint32_t ip; // resolved peer address (of the final hop)
|
||||
int status_code; // parsed HTTP status, 0 if not found
|
||||
uint32_t elapsed_ms; // wall time of the fetch
|
||||
char err[64]; // human-readable failure reason
|
||||
uint32_t elapsed_ms; // wall time of the whole fetch
|
||||
int redirects; // number of 3xx hops followed
|
||||
char err[96]; // human-readable failure reason
|
||||
} http_fetch_t;
|
||||
|
||||
// Start the worker thread. Call once at boot, after the TCP stack is up.
|
||||
|
||||
+143
-28
@@ -37,41 +37,119 @@ static http_fetch_t* volatile mailbox = NULL; // job awaiting the worker
|
||||
// Anything it cannot make sense of leaves the job unusable and is reported as
|
||||
// an error rather than guessed at.
|
||||
// ===========================================================================
|
||||
static void parse_url(const char* url, http_fetch_t* f) {
|
||||
f->host[0] = '\0';
|
||||
strcpy(f->path, "/");
|
||||
f->port = 80;
|
||||
// Parse an absolute URL into its parts. Returns false only when there is no
|
||||
// host to be found. `is_https` reports the scheme so the caller can refuse a
|
||||
// TLS target cleanly rather than speaking plaintext to port 443.
|
||||
static bool split_url(const char* url, char* host, size_t host_cap,
|
||||
char* path, size_t path_cap, uint16_t* port, bool* is_https) {
|
||||
host[0] = '\0';
|
||||
strcpy(path, "/");
|
||||
*port = 80;
|
||||
*is_https = false;
|
||||
|
||||
if (!url) return;
|
||||
if (strncmp(url, "http://", 7) == 0) url += 7;
|
||||
else if (strncmp(url, "https://", 8) == 0) url += 8; // no TLS; try plain
|
||||
if (!url) return false;
|
||||
if (strncmp(url, "http://", 7) == 0) {
|
||||
url += 7;
|
||||
} else if (strncmp(url, "https://", 8) == 0) {
|
||||
url += 8;
|
||||
*is_https = true;
|
||||
*port = 443;
|
||||
}
|
||||
|
||||
// Host (and optional :port) up to the first '/'.
|
||||
int i = 0;
|
||||
char hostport[192];
|
||||
int i = 0;
|
||||
while (url[i] && url[i] != '/' && i < (int)sizeof(hostport) - 1) {
|
||||
hostport[i] = url[i];
|
||||
i++;
|
||||
}
|
||||
hostport[i] = '\0';
|
||||
|
||||
const char* rest = url + i; // "" or "/path..."
|
||||
|
||||
// Split host:port.
|
||||
char* colon = strchr(hostport, ':');
|
||||
if (colon) {
|
||||
*colon = '\0';
|
||||
int p = 0;
|
||||
for (const char* c = colon + 1; *c >= '0' && *c <= '9'; c++) p = p * 10 + (*c - '0');
|
||||
if (p > 0 && p < 65536) f->port = (uint16_t)p;
|
||||
if (p > 0 && p < 65536) *port = (uint16_t)p;
|
||||
}
|
||||
strncpy(f->host, hostport, sizeof(f->host) - 1);
|
||||
f->host[sizeof(f->host) - 1] = '\0';
|
||||
strncpy(host, hostport, host_cap - 1);
|
||||
host[host_cap - 1] = '\0';
|
||||
|
||||
if (rest[0] == '/') {
|
||||
strncpy(f->path, rest, sizeof(f->path) - 1);
|
||||
f->path[sizeof(f->path) - 1] = '\0';
|
||||
strncpy(path, rest, path_cap - 1);
|
||||
path[path_cap - 1] = '\0';
|
||||
}
|
||||
return host[0] != '\0';
|
||||
}
|
||||
|
||||
static void parse_url(const char* url, http_fetch_t* f) {
|
||||
split_url(url, f->host, sizeof(f->host), f->path, sizeof(f->path), &f->port, &f->tls);
|
||||
}
|
||||
|
||||
// Find a header's value in the response, case-insensitively. Copies the value
|
||||
// (trimmed, up to the CRLF) into out. Returns false if the header is absent.
|
||||
static bool find_header(const char* buf, int len, const char* name,
|
||||
char* out, size_t out_cap) {
|
||||
size_t nlen = strlen(name);
|
||||
// Scan line by line; the status line is skipped naturally (no match).
|
||||
for (int i = 0; i + (int)nlen < len; i++) {
|
||||
if (i != 0 && buf[i - 1] != '\n') continue; // only at a line start
|
||||
|
||||
// Case-insensitive compare of the header name.
|
||||
size_t k = 0;
|
||||
while (k < nlen) {
|
||||
char a = buf[i + k], b = name[k];
|
||||
if (a >= 'A' && a <= 'Z') a = (char)(a - 'A' + 'a');
|
||||
if (b >= 'A' && b <= 'Z') b = (char)(b - 'A' + 'a');
|
||||
if (a != b) break;
|
||||
k++;
|
||||
}
|
||||
if (k != nlen || buf[i + k] != ':') continue;
|
||||
|
||||
const char* v = buf + i + nlen + 1;
|
||||
const char* end = buf + len;
|
||||
while (v < end && (*v == ' ' || *v == '\t')) v++;
|
||||
size_t o = 0;
|
||||
while (v < end && *v != '\r' && *v != '\n' && o < out_cap - 1) out[o++] = *v++;
|
||||
out[o] = '\0';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Resolve a Location value against the current request. Absolute URLs replace
|
||||
// host/port/path; a path-only Location keeps the host. Returns false and sets
|
||||
// f->err when the target is HTTPS, which this TLS-less client cannot follow.
|
||||
static bool apply_redirect(http_fetch_t* f, const char* location) {
|
||||
if (location[0] == '/' || location[0] == '\0') {
|
||||
// Same host, new path.
|
||||
strncpy(f->path, location[0] ? location : "/", sizeof(f->path) - 1);
|
||||
f->path[sizeof(f->path) - 1] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strncmp(location, "http://", 7) != 0 && strncmp(location, "https://", 8) != 0) {
|
||||
// A bare relative reference; treat it as a rooted path.
|
||||
snprintf(f->path, sizeof(f->path), "/%s", location);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool https;
|
||||
char host[128], path[256];
|
||||
uint16_t port;
|
||||
if (!split_url(location, host, sizeof(host), path, sizeof(path), &port, &https)) {
|
||||
snprintf(f->err, sizeof(f->err), "redirect to a URL we cannot parse");
|
||||
return false;
|
||||
}
|
||||
if (https) {
|
||||
snprintf(f->err, sizeof(f->err), "redirect to HTTPS (TLS unsupported): %s", host);
|
||||
return false;
|
||||
}
|
||||
strcpy(f->host, host);
|
||||
strcpy(f->path, path);
|
||||
f->port = port;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse the numeric status out of "HTTP/1.x NNN ...", or 0 if absent.
|
||||
@@ -96,22 +174,23 @@ static int parse_status(const char* buf, int len) {
|
||||
// finalize would then read freed memory. Returning the state instead keeps the
|
||||
// job private to the worker until the atomic hand-off.
|
||||
// ===========================================================================
|
||||
static int do_fetch(http_fetch_t* f) {
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
#define HTTP_MAX_REDIRECTS 5
|
||||
|
||||
// Resolve: a dotted quad is taken as-is, otherwise the DNS resolver runs
|
||||
// (itself bounded, so an unreachable server times out rather than hangs).
|
||||
// Perform one request/response against the current host/path/port. Returns the
|
||||
// HTTP status code, or a negative error code; the raw response lands in f->buf.
|
||||
static int do_one_request(http_fetch_t* f) {
|
||||
f->ip = ip_parse(f->host);
|
||||
if (f->ip == 0) f->ip = dns_resolve(f->host);
|
||||
if (f->ip == 0) {
|
||||
snprintf(f->err, sizeof(f->err), "cannot resolve %s", f->host);
|
||||
return HTTP_STATE_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
tcp_conn_t* c = tcp_connect(f->ip, f->port, HTTP_CONNECT_TIMEOUT_MS);
|
||||
if (!c) {
|
||||
snprintf(f->err, sizeof(f->err), "connect to :%u refused/timed out", f->port);
|
||||
return HTTP_STATE_ERROR;
|
||||
snprintf(f->err, sizeof(f->err), "connect to %s:%u refused/timed out",
|
||||
f->host, f->port);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char req[512];
|
||||
@@ -121,10 +200,9 @@ static int do_fetch(http_fetch_t* f) {
|
||||
if (tcp_send(c, req, (uint16_t)rl) < 0) {
|
||||
snprintf(f->err, sizeof(f->err), "send failed");
|
||||
tcp_close(c);
|
||||
return HTTP_STATE_ERROR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Drain the response until the peer closes or the buffer fills.
|
||||
f->len = 0;
|
||||
while (f->len < f->cap - 1) {
|
||||
int n = tcp_recv(c, f->buf + f->len, (uint16_t)(f->cap - 1 - f->len),
|
||||
@@ -135,14 +213,51 @@ static int do_fetch(http_fetch_t* f) {
|
||||
f->buf[f->len] = '\0';
|
||||
tcp_close(c);
|
||||
|
||||
f->status_code = parse_status(f->buf, f->len);
|
||||
f->elapsed_ms = (uint32_t)((pit_get_ticks() - t0) * 10);
|
||||
|
||||
if (f->len == 0) {
|
||||
snprintf(f->err, sizeof(f->err), "no data received");
|
||||
return -1;
|
||||
}
|
||||
return parse_status(f->buf, f->len);
|
||||
}
|
||||
|
||||
// Fills in the result fields but deliberately does NOT touch f->state:
|
||||
// publishing the terminal state is finalize's job, done under interrupts-off.
|
||||
// Returning the state keeps the job private to the worker until that hand-off.
|
||||
//
|
||||
// A 3xx response with a Location header is followed, up to a bounded number of
|
||||
// hops, so a real-world URL that redirects (a bare host to a path, http to a
|
||||
// canonical host) resolves to actual content. An https target is reported
|
||||
// rather than followed, since this client has no TLS.
|
||||
static int do_fetch(http_fetch_t* f) {
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
f->redirects = 0;
|
||||
|
||||
// The client has no TLS, so an https URL cannot be served. Say so plainly
|
||||
// rather than sending a plaintext request to port 443 and reporting a
|
||||
// confusing timeout.
|
||||
if (f->tls) {
|
||||
snprintf(f->err, sizeof(f->err), "%s is HTTPS; this client has no TLS", f->host);
|
||||
return HTTP_STATE_ERROR;
|
||||
}
|
||||
return HTTP_STATE_DONE;
|
||||
|
||||
for (;;) {
|
||||
int status = do_one_request(f);
|
||||
if (status < 0) return HTTP_STATE_ERROR; // f->err already set
|
||||
|
||||
bool is_redirect = (status == 301 || status == 302 || status == 303 ||
|
||||
status == 307 || status == 308);
|
||||
char location[512];
|
||||
if (is_redirect && f->redirects < HTTP_MAX_REDIRECTS &&
|
||||
find_header(f->buf, f->len, "Location", location, sizeof(location))) {
|
||||
if (!apply_redirect(f, location)) return HTTP_STATE_ERROR; // e.g. HTTPS
|
||||
f->redirects++;
|
||||
continue;
|
||||
}
|
||||
|
||||
f->status_code = status;
|
||||
f->elapsed_ms = (uint32_t)((pit_get_ticks() - t0) * 10);
|
||||
return HTTP_STATE_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
static void free_job(http_fetch_t* f) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <net/udp.h>
|
||||
#include <net/tcp.h>
|
||||
#include <net/http_client.h>
|
||||
#include <kernel/sched.h>
|
||||
#include <net/socket.h>
|
||||
#include <drivers/e1000.h>
|
||||
#include <drivers/ata.h>
|
||||
@@ -2488,6 +2489,86 @@ static int applet_webget(int argc, char** argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// End-to-end connectivity check: link, address, gateway, DNS, and a real HTTP
|
||||
// fetch, each reported PASS/FAIL. This is the one-command answer to "is the
|
||||
// internet working?" -- it exercises every layer the stack depends on.
|
||||
static int applet_netcheck(int argc, char** argv) {
|
||||
const char* probe = (argc >= 2) ? argv[1] : "example.com";
|
||||
|
||||
printk(ANSI_BRIGHT_CYAN "=== SUB-OS Connectivity Check ===\n" ANSI_RESET);
|
||||
|
||||
#define OKMARK ANSI_BRIGHT_GREEN "[ ok ]" ANSI_RESET
|
||||
#define FAILMARK ANSI_BRIGHT_RED "[fail]" ANSI_RESET
|
||||
|
||||
int failures = 0;
|
||||
|
||||
// 1. Physical link.
|
||||
bool link = e1000_is_link_up();
|
||||
printk(" %s link eth0 %s\n", link ? OKMARK : FAILMARK,
|
||||
link ? "UP" : "DOWN");
|
||||
if (!link) failures++;
|
||||
|
||||
// 2. Interface address.
|
||||
net_if_t* nif = net_get_primary_if();
|
||||
bool has_ip = nif && nif->ip != 0;
|
||||
char ips[16], gws[16], dnss[16];
|
||||
ip_to_str(nif ? nif->ip : 0, ips);
|
||||
ip_to_str(nif ? nif->gateway : 0, gws);
|
||||
ip_to_str(nif ? nif->dns : 0, dnss);
|
||||
printk(" %s address %s gw %s dns %s\n", has_ip ? OKMARK : FAILMARK,
|
||||
ips, gws, dnss);
|
||||
if (!has_ip) failures++;
|
||||
|
||||
// 3. Gateway reachability (one quiet ping).
|
||||
int recv = (nif && nif->gateway) ? net_ping(nif->gateway, 1, 1000) : 0;
|
||||
printk(" %s gateway %s %s\n", recv > 0 ? OKMARK : FAILMARK, gws,
|
||||
recv > 0 ? "reachable" : "no reply");
|
||||
if (recv <= 0) failures++;
|
||||
|
||||
// 4. DNS resolution.
|
||||
uint32_t rip = dns_resolve(probe);
|
||||
char rips[16];
|
||||
ip_to_str(rip, rips);
|
||||
printk(" %s dns %s -> %s\n", rip ? OKMARK : FAILMARK, probe,
|
||||
rip ? rips : "no answer");
|
||||
if (!rip) failures++;
|
||||
|
||||
// 5. A real HTTP fetch, on the async worker, polled to completion.
|
||||
char url[160];
|
||||
snprintf(url, sizeof(url), "%s/", probe);
|
||||
http_fetch_t* f = http_fetch_start(url, 8192);
|
||||
if (!f) {
|
||||
printk(" %s http could not start fetch\n", FAILMARK);
|
||||
failures++;
|
||||
} else {
|
||||
uint64_t t0 = pit_get_ticks();
|
||||
while (f->state == HTTP_STATE_RUNNING && (pit_get_ticks() - t0) < 1500) {
|
||||
sched_yield();
|
||||
}
|
||||
if (f->state == HTTP_STATE_DONE && f->status_code > 0) {
|
||||
printk(" %s http GET %s -> %d, %d bytes, %u ms%s\n", OKMARK,
|
||||
probe, f->status_code, f->len, f->elapsed_ms,
|
||||
f->redirects ? " (redirected)" : "");
|
||||
} else {
|
||||
printk(" %s http %s\n", FAILMARK,
|
||||
f->state == HTTP_STATE_ERROR ? f->err : "timed out");
|
||||
failures++;
|
||||
}
|
||||
http_fetch_release(f);
|
||||
}
|
||||
|
||||
if (failures == 0) {
|
||||
printk(ANSI_BRIGHT_GREEN "\n Internet: ONLINE -- all checks passed.\n" ANSI_RESET);
|
||||
} else {
|
||||
printk(ANSI_YELLOW "\n Internet: DEGRADED -- %d check(s) failed.\n" ANSI_RESET,
|
||||
failures);
|
||||
}
|
||||
|
||||
#undef OKMARK
|
||||
#undef FAILMARK
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
// A minimal HTTP/1.0 client over the active-open path.
|
||||
static int applet_httpget(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
@@ -2860,6 +2941,7 @@ static const lazybox_applet_t applets[] = {
|
||||
{"tcpconnect", applet_tcpconnect, "tcpconnect <host> <port> [text]", "Open an outbound TCP stream", "Network"},
|
||||
{"httpget", applet_httpget, "httpget <host> [path] [port]", "Fetch a URL over TCP", "Network"},
|
||||
{"webget", applet_webget, "webget <url>", "Fetch a URL via the async worker", "Network"},
|
||||
{"netcheck", applet_netcheck, "netcheck [host]", "End-to-end internet connectivity test", "Network"},
|
||||
{"tcpserve", applet_tcpserve, "tcpserve <port>", "Accept one inbound TCP stream", "Network"},
|
||||
|
||||
// Storage & Devices
|
||||
|
||||
Reference in new issue
Block a user