// nettest -- prove the TCP/IP stack carries real traffic. // // Not a ping and not a status readout: both of those can pass on a stack that // cannot actually move bytes. This opens a TCP connection to a host outside the // guest, sends a request, reads the reply and reports what came back -- which // exercises ARP, IP, the TCP handshake, the send and receive paths, the window // and the close, in one exchange. // // nettest // // Under QEMU's user-mode networking the host is 10.0.2.2, so pointing this at a // listener there is the intended use: // // host$ printf 'HELLO-FROM-HOST\n' | nc -l -p 9000 // guest# nettest 10.0.2.2 9000 #include "types.h" #include "user.h" #include "socket.h" static void puts_(const char *s) { int n = 0; while (s[n]) n++; write(1, (void *)s, n); } static void putn_(int v) { char buf[12]; int i = 0; if (v == 0) { write(1, "0", 1); return; } while (v > 0 && i < (int)sizeof(buf)) { buf[i++] = '0' + (v % 10); v /= 10; } while (i--) write(1, &buf[i], 1); } // Host byte order to network. Written out rather than taken from a header // because the userland has no and this is all of what it would // provide here. static ushort hton16(ushort v) { return (ushort)((v << 8) | (v >> 8)); } // "10.0.2.2" -> the 32-bit address, already in network order. Returns 0 on // anything malformed, which doubles as "0.0.0.0 is not a destination". static uint parse_ip(const char *s) { uint out = 0; for (int part = 0; part < 4; part++) { int v = 0, digits = 0; while (*s >= '0' && *s <= '9') { v = v * 10 + (*s++ - '0'); digits++; } if (digits == 0 || v > 255) return 0; // Network order is most-significant byte first, and the first part of // a dotted quad is the most significant. out |= ((uint)v) << (8 * part); if (part < 3) { if (*s != '.') return 0; s++; } } return *s == '\0' ? out : 0; } static int parse_port(const char *s) { int v = 0; while (*s >= '0' && *s <= '9') v = v * 10 + (*s++ - '0'); return (*s == '\0' && v > 0 && v < 65536) ? v : -1; } int main(int argc, char *argv[]) { if (argc != 3) { puts_("usage: nettest \n"); exit(); } uint ip = parse_ip(argv[1]); int port = parse_port(argv[2]); if (ip == 0 || port < 0) { puts_("nettest: bad address or port\n"); exit(); } int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { puts_("nettest: socket failed\n"); exit(); } // Zeroed by hand: this userland has no memset, and sin_zero must be clear // because the kernel compares the whole struct's length against sizeof. struct sockaddr_in sa; for (int i = 0; i < (int)sizeof(sa); i++) ((char *)&sa)[i] = 0; sa.sin_family = AF_INET; sa.sin_port = hton16((ushort)port); sa.sin_addr = ip; puts_("nettest: connecting to "); puts_(argv[1]); puts_(":"); putn_(port); puts_("\n"); if (connect(fd, (const struct sockaddr *)&sa, sizeof(sa)) < 0) { puts_("nettest: connect failed\n"); close(fd); exit(); } puts_("nettest: connected\n"); const char *msg = "HELLO-FROM-SCHOS\n"; int len = 0; while (msg[len]) len++; if (write(fd, (void *)msg, len) != len) { puts_("nettest: short write\n"); close(fd); exit(); } puts_("nettest: sent "); putn_(len); puts_(" bytes\n"); // Read until the peer closes. One read is not enough to claim the receive // path works: TCP is a stream and may well deliver in pieces. char buf[512]; int total = 0; for (;;) { int n = read(fd, buf + total, (int)sizeof(buf) - 1 - total); if (n <= 0) break; total += n; if (total >= (int)sizeof(buf) - 1) break; } buf[total] = '\0'; close(fd); puts_("nettest: received "); putn_(total); puts_(" bytes: "); puts_(buf); if (buf[0] && buf[total ? total - 1 : 0] != '\n') puts_("\n"); if (total == 0) { puts_("nettest: FAILED, nothing came back\n"); exit(); } puts_("nettest: OK\n"); exit(); }