test: the network is checked by the suite now, and /proc/netif says what it did
The TCP/IP stack landed with its end-to-end test performed by hand: start a listener, boot, type nettest, read the output. That is evidence once and nothing afterwards, and it is exactly the kind of check that quietly stops being run. bootcheck -Net does the whole exchange. A listener runs on this host on a port picked by binding one and letting go, the guest is told to connect out to 10.0.2.2 at that port, and both ends are asserted: the guest reports what it received, the listener reports what arrived. One side alone proves much less than it looks -- a stack that completes a handshake and then drops every byte satisfies any check made from inside the guest. The listener is a background job, a separate process, because this harness has one hard rule: nothing may block the writer feeding QEMU's stdin. A socket accept is precisely the wait that deadlocked two earlier attempts at reading QEMU's output. It answers the protocol nettest speaks and then closes -- the close is load-bearing, since nettest reads until end of file and a listener that stayed open would hang the guest rather than fail it. The NIC is spelled out (-netdev user + -device e1000) rather than left to QEMU's default. Other runs can take the default and still answer "did a driver bind"; a run that asserts on an address should be reading its own configuration. /proc/netif is new, and the address is why. Until now the only report was a line on the console at boot, which userland cannot read and a test can only find by scraping the transcript. It gives interface, MAC, state, address, netmask, gateway, MTU and frame counts. Named netif rather than net on purpose: /proc/net is a *directory* on Linux, and a plain file there would give ENOTDIR to anything reaching for /proc/net/dev, which is worse than not having it. The counters are kept at the lwIP seam rather than in the driver, because a frame the driver received and the netif dropped never reached the stack. Verified: 38 [ok], no failures. Eleven of them are new -- lwIP up, DHCP obtaining 10.0.2.15 from SLIRP (a number nothing in the guest knows in advance, so it is the exchange completing rather than a guess), the four /proc/netif fields, frames counted in both directions, the handshake, the reply read back, and the listener confirming HELLO-FROM-SCHOS arrived. One bug found by writing it: $netPort was chosen after the command list was built, so the guest ran `nettest 10.0.2.2` with no port and printed its usage. An unset PowerShell variable interpolates to nothing rather than failing, which is worth remembering next time a command in this file looks right and behaves oddly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c0806d1b64
commit
f46fb41966
6 changed files with 268 additions and 1 deletions
|
|
@ -531,6 +531,7 @@ int e1000_present(void);
|
||||||
// net_is_up reports whether an address has been obtained.
|
// net_is_up reports whether an address has been obtained.
|
||||||
void netinit(void);
|
void netinit(void);
|
||||||
int net_is_up(void);
|
int net_is_up(void);
|
||||||
|
int net_info(char *buf, int cap);
|
||||||
|
|
||||||
// net/lwip/ -- the port layer and the interface, called only from net.c.
|
// net/lwip/ -- the port layer and the interface, called only from net.c.
|
||||||
void lwip_sys_init(void);
|
void lwip_sys_init(void);
|
||||||
|
|
@ -538,6 +539,7 @@ void lwip_sys_lock(void);
|
||||||
void lwip_sys_unlock(void);
|
void lwip_sys_unlock(void);
|
||||||
int net_e1000_attach(void);
|
int net_e1000_attach(void);
|
||||||
int net_e1000_poll(void);
|
int net_e1000_poll(void);
|
||||||
|
void net_e1000_counts(uint64 *rx, uint64 *tx);
|
||||||
struct netif *net_e1000_netif(void);
|
struct netif *net_e1000_netif(void);
|
||||||
void e1000_selftest(void);
|
void e1000_selftest(void);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -364,6 +364,7 @@ gen_filesystems(struct sbuf *sb)
|
||||||
#define PROC_INTERRUPTS 12
|
#define PROC_INTERRUPTS 12
|
||||||
#define PROC_DEVICES 13
|
#define PROC_DEVICES 13
|
||||||
#define PROC_FSSTAT 14
|
#define PROC_FSSTAT 14
|
||||||
|
#define PROC_NETIF 15
|
||||||
|
|
||||||
// Render global file `id` (a PROC_* constant) into `buf`. Returns its length.
|
// Render global file `id` (a PROC_* constant) into `buf`. Returns its length.
|
||||||
//
|
//
|
||||||
|
|
@ -393,6 +394,10 @@ proc_gen_global(int id, char *buf, int cap)
|
||||||
case PROC_PCI: sb.len = pci_info(buf, PROC_BUFSZ); break;
|
case PROC_PCI: sb.len = pci_info(buf, PROC_BUFSZ); break;
|
||||||
// Likewise irq.c: it owns the table, so it renders it.
|
// Likewise irq.c: it owns the table, so it renders it.
|
||||||
case PROC_INTERRUPTS: sb.len = irq_info(buf, PROC_BUFSZ); break;
|
case PROC_INTERRUPTS: sb.len = irq_info(buf, PROC_BUFSZ); break;
|
||||||
|
// And likewise net.c, which is the only file here allowed to include lwIP's
|
||||||
|
// headers -- reaching into the stack from this one would put them on every
|
||||||
|
// kernel file's include path.
|
||||||
|
case PROC_NETIF: sb.len = net_info(buf, PROC_BUFSZ); break;
|
||||||
case PROC_DEVICES: gen_devices(&sb); break;
|
case PROC_DEVICES: gen_devices(&sb); break;
|
||||||
default: return 0;
|
default: return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ static const struct global_file globals[] = {
|
||||||
{ "interrupts", 12 },
|
{ "interrupts", 12 },
|
||||||
{ "devices", 13 },
|
{ "devices", 13 },
|
||||||
{ "fsstat", 14 },
|
{ "fsstat", 14 },
|
||||||
|
{ "netif", 15 },
|
||||||
};
|
};
|
||||||
#define NGLOBALS ((int)(sizeof(globals) / sizeof(globals[0])))
|
#define NGLOBALS ((int)(sizeof(globals) / sizeof(globals[0])))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,19 @@
|
||||||
|
|
||||||
static struct netif e1000_netif;
|
static struct netif e1000_netif;
|
||||||
|
|
||||||
|
// Frames in and out, for /proc/netif. Counted here rather than in the driver
|
||||||
|
// because this is the seam lwIP actually uses: a frame the driver received and
|
||||||
|
// this file dropped never reached the stack, and a counter kept one layer down
|
||||||
|
// would say otherwise. Only the network thread touches them, so no lock.
|
||||||
|
static uint64 rx_frames, tx_frames;
|
||||||
|
|
||||||
|
void
|
||||||
|
net_e1000_counts(uint64 *rx, uint64 *tx)
|
||||||
|
{
|
||||||
|
*rx = rx_frames;
|
||||||
|
*tx = tx_frames;
|
||||||
|
}
|
||||||
|
|
||||||
// One staging buffer, not one per call: a frame is 1518 bytes and the kernel
|
// One staging buffer, not one per call: a frame is 1518 bytes and the kernel
|
||||||
// stack is 4096 in total, so a local of this size on a path that is several
|
// stack is 4096 in total, so a local of this size on a path that is several
|
||||||
// frames deep is exactly the overflow the kstack canary exists to catch.
|
// frames deep is exactly the overflow the kstack canary exists to catch.
|
||||||
|
|
@ -56,6 +69,7 @@ e1000_linkoutput(struct netif *netif, struct pbuf *p)
|
||||||
if (e1000_transmit(txframe, off) != 0)
|
if (e1000_transmit(txframe, off) != 0)
|
||||||
return ERR_WOULDBLOCK;
|
return ERR_WOULDBLOCK;
|
||||||
|
|
||||||
|
tx_frames++;
|
||||||
return ERR_OK;
|
return ERR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,6 +145,7 @@ net_e1000_poll(void)
|
||||||
if (e1000_netif.input(p, &e1000_netif) != ERR_OK)
|
if (e1000_netif.input(p, &e1000_netif) != ERR_OK)
|
||||||
pbuf_free(p);
|
pbuf_free(p);
|
||||||
|
|
||||||
|
rx_frames++;
|
||||||
moved++;
|
moved++;
|
||||||
}
|
}
|
||||||
return moved;
|
return moved;
|
||||||
|
|
|
||||||
133
kernel/net/net.c
133
kernel/net/net.c
|
|
@ -113,6 +113,139 @@ net_thread(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
/* /proc/netif */
|
||||||
|
/* ------------------------------------------------------------------ */
|
||||||
|
//
|
||||||
|
// Named netif rather than net, and that is deliberate: on Linux /proc/net is a
|
||||||
|
// *directory* of a dozen files, and putting a plain file there would mean
|
||||||
|
// anything reaching for the familiar /proc/net/dev got ENOTDIR from a path that
|
||||||
|
// looks like it exists. This is one interface's state, so it is named for what
|
||||||
|
// it holds and leaves the Linux name free for the day there is a directory to
|
||||||
|
// put under it.
|
||||||
|
//
|
||||||
|
// No snprintf in the kernel, so this appends by hand in the same style as
|
||||||
|
// kernel/dev/pci.c.
|
||||||
|
|
||||||
|
static int
|
||||||
|
put_str(char *out, const char *s)
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
while (s[n]) { out[n] = s[n]; n++; }
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
put_u(char *out, uint64 v)
|
||||||
|
{
|
||||||
|
char tmp[24];
|
||||||
|
int i = 0, n = 0;
|
||||||
|
|
||||||
|
if (v == 0) { out[0] = '0'; return 1; }
|
||||||
|
while (v && i < (int)sizeof(tmp)) { tmp[i++] = '0' + (char)(v % 10); v /= 10; }
|
||||||
|
while (i--) out[n++] = tmp[i];
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
put_ip4(char *out, const ip4_addr_t *a)
|
||||||
|
{
|
||||||
|
int n = 0;
|
||||||
|
n += put_u(out + n, ip4_addr1(a)); out[n++] = '.';
|
||||||
|
n += put_u(out + n, ip4_addr2(a)); out[n++] = '.';
|
||||||
|
n += put_u(out + n, ip4_addr3(a)); out[n++] = '.';
|
||||||
|
n += put_u(out + n, ip4_addr4(a));
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
put_mac(char *out, const unsigned char *m)
|
||||||
|
{
|
||||||
|
static const char d[] = "0123456789abcdef";
|
||||||
|
int n = 0;
|
||||||
|
for (int i = 0; i < 6; i++) {
|
||||||
|
if (i) out[n++] = ':';
|
||||||
|
out[n++] = d[m[i] >> 4];
|
||||||
|
out[n++] = d[m[i] & 0xF];
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// One "name value" pair per line, which is the shape /proc/schfs and
|
||||||
|
// /proc/fsstat already use here -- a reader that can scan one can scan all of
|
||||||
|
// them.
|
||||||
|
static int
|
||||||
|
put_field(char *out, const char *name)
|
||||||
|
{
|
||||||
|
int n = put_str(out, name);
|
||||||
|
while (n < 12)
|
||||||
|
out[n++] = ' ';
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
net_info(char *buf, int cap)
|
||||||
|
{
|
||||||
|
int len = 0;
|
||||||
|
|
||||||
|
if (cap < 512)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!net_started) {
|
||||||
|
len += put_field(buf + len, "state");
|
||||||
|
len += put_str(buf + len, e1000_present() ? "down\n" : "no interface\n");
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct netif *nif = net_e1000_netif();
|
||||||
|
|
||||||
|
lwip_sys_lock();
|
||||||
|
|
||||||
|
len += put_field(buf + len, "interface");
|
||||||
|
buf[len++] = nif->name[0];
|
||||||
|
buf[len++] = nif->name[1];
|
||||||
|
len += put_u(buf + len, nif->num);
|
||||||
|
buf[len++] = '\n';
|
||||||
|
|
||||||
|
len += put_field(buf + len, "mac");
|
||||||
|
len += put_mac(buf + len, nif->hwaddr);
|
||||||
|
buf[len++] = '\n';
|
||||||
|
|
||||||
|
len += put_field(buf + len, "state");
|
||||||
|
len += put_str(buf + len, net_bound ? "up\n" : "no address\n");
|
||||||
|
|
||||||
|
len += put_field(buf + len, "address");
|
||||||
|
len += put_ip4(buf + len, netif_ip4_addr(nif));
|
||||||
|
buf[len++] = '\n';
|
||||||
|
|
||||||
|
len += put_field(buf + len, "netmask");
|
||||||
|
len += put_ip4(buf + len, netif_ip4_netmask(nif));
|
||||||
|
buf[len++] = '\n';
|
||||||
|
|
||||||
|
len += put_field(buf + len, "gateway");
|
||||||
|
len += put_ip4(buf + len, netif_ip4_gw(nif));
|
||||||
|
buf[len++] = '\n';
|
||||||
|
|
||||||
|
len += put_field(buf + len, "mtu");
|
||||||
|
len += put_u(buf + len, nif->mtu);
|
||||||
|
buf[len++] = '\n';
|
||||||
|
|
||||||
|
lwip_sys_unlock();
|
||||||
|
|
||||||
|
uint64 rx, tx;
|
||||||
|
net_e1000_counts(&rx, &tx);
|
||||||
|
|
||||||
|
len += put_field(buf + len, "rx_frames");
|
||||||
|
len += put_u(buf + len, rx);
|
||||||
|
buf[len++] = '\n';
|
||||||
|
|
||||||
|
len += put_field(buf + len, "tx_frames");
|
||||||
|
len += put_u(buf + len, tx);
|
||||||
|
buf[len++] = '\n';
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
// Start the stack. Called from main() once processes exist.
|
// Start the stack. Called from main() once processes exist.
|
||||||
void
|
void
|
||||||
netinit(void)
|
netinit(void)
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,15 @@ param(
|
||||||
# it exists for -- one directory per process -- is exactly what nothing
|
# it exists for -- one directory per process -- is exactly what nothing
|
||||||
# else in this suite touches.
|
# else in this suite touches.
|
||||||
[switch]$Ps,
|
[switch]$Ps,
|
||||||
|
# TCP/IP. A listener is started on this host and the guest is told to
|
||||||
|
# connect out to it, which is the only arrangement that checks both
|
||||||
|
# directions from both ends: the guest reports what it received, and the
|
||||||
|
# listener reports what arrived. A stack that completed a handshake and
|
||||||
|
# then dropped every byte would pass any check made from one side alone.
|
||||||
|
#
|
||||||
|
# Under QEMU's user-mode networking the host is 10.0.2.2 seen from inside,
|
||||||
|
# and SLIRP connects to the host's loopback -- so the listener binds there.
|
||||||
|
[switch]$Net,
|
||||||
[int]$BootSeconds = 20,
|
[int]$BootSeconds = 20,
|
||||||
[string]$Qemu
|
[string]$Qemu
|
||||||
)
|
)
|
||||||
|
|
@ -230,6 +239,21 @@ if ($Usb) {
|
||||||
if ($Sock) {
|
if ($Sock) {
|
||||||
$commands += 'socktest'
|
$commands += 'socktest'
|
||||||
}
|
}
|
||||||
|
if ($Net) {
|
||||||
|
# A free loopback port, found by binding one and letting go, so two runs on
|
||||||
|
# one machine do not collide. It has to be chosen here rather than with the
|
||||||
|
# rest of the network setup below: the command line that uses it is built
|
||||||
|
# in this block, and an unset variable interpolates to nothing -- which is
|
||||||
|
# how nettest first ran with no port at all and reported its usage.
|
||||||
|
$l = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0)
|
||||||
|
$l.Start(); $script:netPort = $l.LocalEndpoint.Port; $l.Stop()
|
||||||
|
|
||||||
|
# The interface first, so a failure below reads as "the address never
|
||||||
|
# arrived" rather than "TCP is broken" -- two very different bugs that look
|
||||||
|
# identical from a failed connect.
|
||||||
|
$commands += 'cat /proc/netif'
|
||||||
|
$commands += "nettest 10.0.2.2 $netPort"
|
||||||
|
}
|
||||||
if ($Ps) {
|
if ($Ps) {
|
||||||
$commands += 'ls /proc'
|
$commands += 'ls /proc'
|
||||||
$commands += 'cat /proc/1/stat'
|
$commands += 'cat /proc/1/stat'
|
||||||
|
|
@ -297,7 +321,56 @@ $monPort = if ($Usb -or $Evdev) {
|
||||||
|
|
||||||
$monitor = if ($Usb -or $Evdev) { @("-monitor tcp:127.0.0.1:$monPort,server,nowait") } else { @() }
|
$monitor = if ($Usb -or $Evdev) { @("-monitor tcp:127.0.0.1:$monPort,server,nowait") } else { @() }
|
||||||
|
|
||||||
$psi.Arguments = ($drives + $usbDevices + $monitor + @(
|
# The network run: an explicit NIC, and a listener on this host for the guest
|
||||||
|
# to talk to.
|
||||||
|
#
|
||||||
|
# The NIC is spelled out rather than left to QEMU's default. Every other run
|
||||||
|
# gets the default one and that is fine for "did a driver bind", but a check
|
||||||
|
# that asserts on an address wants to be reading its own configuration rather
|
||||||
|
# than whatever the machine type happens to supply this year.
|
||||||
|
$netDevices = if ($Net) {
|
||||||
|
@('-netdev user,id=net0', '-device e1000,netdev=net0')
|
||||||
|
} else { @() }
|
||||||
|
|
||||||
|
# The listener runs in a background job -- a separate process -- rather than on
|
||||||
|
# a thread here. This script's one hard rule is that nothing may block the
|
||||||
|
# writer feeding QEMU's stdin, and a socket accept is exactly the kind of wait
|
||||||
|
# that has deadlocked this harness before.
|
||||||
|
#
|
||||||
|
# It answers the protocol cmd/nettest speaks: read what the guest sends, send a
|
||||||
|
# line back, close. The close matters -- nettest reads until end of file, so a
|
||||||
|
# listener that stayed open would hang the guest rather than fail it.
|
||||||
|
$netJob = if ($Net) {
|
||||||
|
Start-Job -ArgumentList $netPort -ScriptBlock {
|
||||||
|
param($port)
|
||||||
|
$l = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, $port)
|
||||||
|
$l.Start()
|
||||||
|
# Generous, because it has to cover the whole boot and every command
|
||||||
|
# before nettest, under TCG. Nothing waits on this deadline: the job is
|
||||||
|
# torn down by the caller either way.
|
||||||
|
$deadline = (Get-Date).AddSeconds(900)
|
||||||
|
while (-not $l.Pending()) {
|
||||||
|
if ((Get-Date) -gt $deadline) { $l.Stop(); return '' }
|
||||||
|
Start-Sleep -Milliseconds 200
|
||||||
|
}
|
||||||
|
$c = $l.AcceptTcpClient()
|
||||||
|
$s = $c.GetStream()
|
||||||
|
$s.ReadTimeout = 30000
|
||||||
|
$buf = New-Object byte[] 512
|
||||||
|
$got = ''
|
||||||
|
try {
|
||||||
|
$n = $s.Read($buf, 0, $buf.Length)
|
||||||
|
if ($n -gt 0) { $got = [Text.Encoding]::ASCII.GetString($buf, 0, $n) }
|
||||||
|
} catch { }
|
||||||
|
$reply = [Text.Encoding]::ASCII.GetBytes("HELLO-FROM-HOST`n")
|
||||||
|
try { $s.Write($reply, 0, $reply.Length); $s.Flush() } catch { }
|
||||||
|
$c.Close()
|
||||||
|
$l.Stop()
|
||||||
|
return $got
|
||||||
|
}
|
||||||
|
} else { $null }
|
||||||
|
|
||||||
|
$psi.Arguments = ($drives + $usbDevices + $netDevices + $monitor + @(
|
||||||
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
'-m 1G -smp 1 -cpu qemu64,+rdtscp -machine pc-i440fx-9.2'
|
||||||
'-display none'
|
'-display none'
|
||||||
"-chardev stdio,id=c0,logfile=`"$log`""
|
"-chardev stdio,id=c0,logfile=`"$log`""
|
||||||
|
|
@ -493,6 +566,17 @@ try {
|
||||||
Remove-Item -Force $pidfile -ErrorAction SilentlyContinue
|
Remove-Item -Force $pidfile -ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# What the listener on this host received, if there was one. Collected after
|
||||||
|
# QEMU is gone: the job either finished when the guest closed the connection or
|
||||||
|
# is still sitting in accept, and the second case is a failure to report rather
|
||||||
|
# than one to wait out.
|
||||||
|
$netFromGuest = ''
|
||||||
|
if ($Net -and $netJob) {
|
||||||
|
Wait-Job $netJob -Timeout 20 | Out-Null
|
||||||
|
$netFromGuest = (Receive-Job $netJob) -join ''
|
||||||
|
Remove-Job $netJob -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
if (-not (Test-Path $log)) { throw "no serial log produced at $log" }
|
if (-not (Test-Path $log)) { throw "no serial log produced at $log" }
|
||||||
$text = Get-Content $log -Raw
|
$text = Get-Content $log -Raw
|
||||||
if ($null -eq $text) { $text = '' }
|
if ($null -eq $text) { $text = '' }
|
||||||
|
|
@ -651,6 +735,33 @@ if ($Sock) {
|
||||||
Expect 'socktest: ALL CHECKS PASSED' 'every socket check passed'
|
Expect 'socktest: ALL CHECKS PASSED' 'every socket check passed'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($Net) {
|
||||||
|
# The stack came up at all. Cheap, and it separates "lwIP never started"
|
||||||
|
# from every other network failure below.
|
||||||
|
Expect 'net: lwIP \d+\.\d+\.\d+ up' 'the TCP/IP stack started'
|
||||||
|
# 10.0.2.15 is the address QEMU's user-mode networking hands out, so this
|
||||||
|
# is DHCP genuinely completing -- discover, offer, request, ack -- and not
|
||||||
|
# a hardcoded guess: nothing in the guest knows that number in advance.
|
||||||
|
Expect 'net: 10\.0\.2\.15/' 'DHCP obtained an address from SLIRP'
|
||||||
|
Expect '(?m)^state\s+up' '/proc/netif reports the interface up'
|
||||||
|
Expect '(?m)^address\s+10\.0\.2\.15' '/proc/netif reports the address it was given'
|
||||||
|
Expect '(?m)^gateway\s+10\.0\.2\.2' '/proc/netif reports the gateway'
|
||||||
|
# Frames in both directions. DHCP alone guarantees these are non-zero, so a
|
||||||
|
# zero here means the counters are wrong rather than the network.
|
||||||
|
Expect '(?m)^rx_frames\s+[1-9]' 'frames arrived and reached the stack'
|
||||||
|
Expect '(?m)^tx_frames\s+[1-9]' 'frames were transmitted'
|
||||||
|
|
||||||
|
# And now real traffic. Each of these is a different part of the exchange,
|
||||||
|
# so a failure says which one: the handshake, the guest's receive path, and
|
||||||
|
# the guest's send path as seen from outside.
|
||||||
|
Expect 'nettest: connected' 'a TCP connection out of the guest completed'
|
||||||
|
Expect 'nettest: OK' 'the guest read a reply back off that connection'
|
||||||
|
Expect 'HELLO-FROM-HOST' 'and the reply is the host''s bytes'
|
||||||
|
ExpectThat ($netFromGuest -match 'HELLO-FROM-SCHOS') `
|
||||||
|
'the listener on this host received what the guest sent' `
|
||||||
|
"the listener read '$($netFromGuest -replace '\s+', ' ')'"
|
||||||
|
}
|
||||||
|
|
||||||
if ($Ps) {
|
if ($Ps) {
|
||||||
# /proc is synthetic, so a per-process directory is the thing to prove.
|
# /proc is synthetic, so a per-process directory is the thing to prove.
|
||||||
# `ls /proc` prints in columns, so pid 1 is a token on a line rather than a
|
# `ls /proc` prints in columns, so pid 1 is a token on a line rather than a
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue