From 10584ccdde047080d0910f8526566cbc0d1d102a Mon Sep 17 00:00:00 2001 From: Frans Kaashoek Date: Mon, 7 Oct 2024 15:05:55 -0400 Subject: [PATCH] net lab --- .gitignore | 12 +- Makefile | 229 +++++++++++- conf/lab.mk | 1 + grade-lab-net | 62 ++++ gradelib.py | 628 ++++++++++++++++++++++++++++++++ kernel/defs.h | 50 +++ kernel/e1000.c | 130 +++++++ kernel/e1000_dev.h | 125 +++++++ kernel/file.h | 1 + kernel/main.c | 11 +- kernel/memlayout.h | 14 +- kernel/net.c | 259 +++++++++++++ kernel/net.h | 127 +++++++ kernel/pci.c | 61 ++++ kernel/plic.c | 12 + kernel/spinlock.c | 111 +++++- kernel/syscall.c | 23 ++ kernel/syscall.h | 15 + kernel/trap.c | 11 +- kernel/vm.c | 77 +++- nettest.py | 169 +++++++++ user/nettest.c | 878 +++++++++++++++++++++++++++++++++++++++++++++ user/pingpong.c | 52 +++ user/user.h | 18 + user/usys.pl | 6 + 25 files changed, 3048 insertions(+), 34 deletions(-) create mode 100644 conf/lab.mk create mode 100755 grade-lab-net create mode 100644 gradelib.py create mode 100644 kernel/e1000.c create mode 100644 kernel/e1000_dev.h create mode 100644 kernel/net.c create mode 100644 kernel/net.h create mode 100644 kernel/pci.c create mode 100755 nettest.py create mode 100644 user/nettest.c create mode 100644 user/pingpong.c diff --git a/.gitignore b/.gitignore index 07216f3..11d0677 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,17 @@ entryother initcode initcode.out kernelmemfs -mkfs +mkfs/mkfs kernel/kernel user/usys.S .gdbinit +*.zip +xv6.out* +.vagrant/ +submissions/ +ph +barrier +/lab-*.json +.DS_Store +*.dSYM +*.pcap diff --git a/Makefile b/Makefile index f8c820e..74e454f 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,16 @@ + +# To compile and run with a lab solution, set the lab name in conf/lab.mk +# (e.g., LAB=util). Run make grade to test solution with the lab's +# grade script (e.g., grade-lab-util). + +-include conf/lab.mk + K=kernel U=user OBJS = \ $K/entry.o \ - $K/start.o \ - $K/console.o \ - $K/printf.o \ - $K/uart.o \ $K/kalloc.o \ - $K/spinlock.o \ $K/string.o \ $K/main.o \ $K/vm.o \ @@ -30,6 +32,33 @@ OBJS = \ $K/plic.o \ $K/virtio_disk.o +OBJS_KCSAN = \ + $K/start.o \ + $K/console.o \ + $K/printf.o \ + $K/uart.o \ + $K/spinlock.o + +ifdef KCSAN +OBJS_KCSAN += \ + $K/kcsan.o +endif + +ifeq ($(LAB),lock) +OBJS += \ + $K/stats.o\ + $K/sprintf.o +endif + + +ifeq ($(LAB),net) +OBJS += \ + $K/e1000.o \ + $K/net.o \ + $K/pci.o +endif + + # riscv64-unknown-elf- or riscv64-linux-gnu- # perhaps in /opt/riscv/bin #TOOLPREFIX = @@ -57,6 +86,13 @@ OBJCOPY = $(TOOLPREFIX)objcopy OBJDUMP = $(TOOLPREFIX)objdump CFLAGS = -Wall -Werror -O -fno-omit-frame-pointer -ggdb -gdwarf-2 + +ifdef LAB +LABUPPER = $(shell echo $(LAB) | tr a-z A-Z) +XCFLAGS += -DSOL_$(LABUPPER) -DLAB_$(LABUPPER) +endif + +CFLAGS += $(XCFLAGS) CFLAGS += -MD CFLAGS += -mcmodel=medany # CFLAGS += -ffreestanding -fno-common -nostdlib -mno-relax @@ -70,6 +106,15 @@ CFLAGS += -fno-builtin-printf -fno-builtin-fprintf -fno-builtin-vprintf CFLAGS += -I. CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) +ifeq ($(LAB),net) +CFLAGS += -DNET_TESTS_PORT=$(SERVERPORT) +endif + +ifdef KCSAN +CFLAGS += -DKCSAN +KCSANFLAG = -fsanitize=thread -fno-inline +endif + # Disable PIE when possible (for Ubuntu 16.10 toolchain) ifneq ($(shell $(CC) -dumpspecs 2>/dev/null | grep -e '[^f]no-pie'),) CFLAGS += -fno-pie -no-pie @@ -80,11 +125,17 @@ endif LDFLAGS = -z max-page-size=4096 -$K/kernel: $(OBJS) $K/kernel.ld $U/initcode - $(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS) +$K/kernel: $(OBJS) $(OBJS_KCSAN) $K/kernel.ld $U/initcode + $(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS) $(OBJS_KCSAN) $(OBJDUMP) -S $K/kernel > $K/kernel.asm $(OBJDUMP) -t $K/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $K/kernel.sym +$(OBJS): EXTRAFLAG := $(KCSANFLAG) + +$K/%.o: $K/%.c + $(CC) $(CFLAGS) $(EXTRAFLAG) -c -o $@ $< + + $U/initcode: $U/initcode.S $(CC) $(CFLAGS) -march=rv64g -nostdinc -I. -Ikernel -c $U/initcode.S -o $U/initcode.o $(LD) $(LDFLAGS) -N -e start -Ttext 0 -o $U/initcode.out $U/initcode.o @@ -96,6 +147,10 @@ tags: $(OBJS) _init ULIB = $U/ulib.o $U/usys.o $U/printf.o $U/umalloc.o +ifeq ($(LAB),lock) +ULIB += $U/statistics.o +endif + _%: %.o $(ULIB) $(LD) $(LDFLAGS) -T $U/user.ld -o $@ $^ $(OBJDUMP) -S $@ > $*.asm @@ -114,7 +169,7 @@ $U/_forktest: $U/forktest.o $(ULIB) $(OBJDUMP) -S $U/_forktest > $U/forktest.asm mkfs/mkfs: mkfs/mkfs.c $K/fs.h $K/param.h - gcc -Werror -Wall -I. -o mkfs/mkfs mkfs/mkfs.c + gcc $(XCFLAGS) -Werror -Wall -I. -o mkfs/mkfs mkfs/mkfs.c # Prevent deletion of intermediate files, e.g. cat.o, after first build, so # that disk image changes after first build are persistent until clean. More @@ -140,18 +195,96 @@ UPROGS=\ $U/_wc\ $U/_zombie\ -fs.img: mkfs/mkfs README $(UPROGS) - mkfs/mkfs fs.img README $(UPROGS) + + + +ifeq ($(LAB),syscall) +UPROGS += \ + $U/_attack\ + $U/_attacktest\ + $U/_secret +endif + +ifeq ($(LAB),lock) +UPROGS += \ + $U/_stats +endif + +ifeq ($(LAB),traps) +UPROGS += \ + $U/_call\ + $U/_bttest +endif + +ifeq ($(LAB),lazy) +UPROGS += \ + $U/_lazytests +endif + +ifeq ($(LAB),cow) +UPROGS += \ + $U/_cowtest +endif + +ifeq ($(LAB),thread) +UPROGS += \ + $U/_uthread + +$U/uthread_switch.o : $U/uthread_switch.S + $(CC) $(CFLAGS) -c -o $U/uthread_switch.o $U/uthread_switch.S + +$U/_uthread: $U/uthread.o $U/uthread_switch.o $(ULIB) + $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $U/_uthread $U/uthread.o $U/uthread_switch.o $(ULIB) + $(OBJDUMP) -S $U/_uthread > $U/uthread.asm + +ph: notxv6/ph.c + gcc -o ph -g -O2 $(XCFLAGS) notxv6/ph.c -pthread + +barrier: notxv6/barrier.c + gcc -o barrier -g -O2 $(XCFLAGS) notxv6/barrier.c -pthread +endif + +ifeq ($(LAB),pgtbl) +UPROGS += \ + $U/_pgtbltest +endif + +ifeq ($(LAB),lock) +UPROGS += \ + $U/_kalloctest\ + $U/_bcachetest +endif + +ifeq ($(LAB),fs) +UPROGS += \ + $U/_bigfile +endif + + + +ifeq ($(LAB),net) +UPROGS += \ + $U/_nettest +endif + +UEXTRA= +ifeq ($(LAB),util) + UEXTRA += user/xargstest.sh +endif + + +fs.img: mkfs/mkfs README $(UEXTRA) $(UPROGS) + mkfs/mkfs fs.img README $(UEXTRA) $(UPROGS) -include kernel/*.d user/*.d -clean: - rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg \ +clean: + rm -rf *.tex *.dvi *.idx *.aux *.log *.ind *.ilg *.dSYM *.zip *.pcap \ */*.o */*.d */*.asm */*.sym \ - $U/initcode $U/initcode.out $K/kernel fs.img \ - mkfs/mkfs .gdbinit \ - $U/usys.S \ - $(UPROGS) + $U/initcode $U/initcode.out $U/usys.S $U/_* \ + $K/kernel \ + mkfs/mkfs fs.img .gdbinit __pycache__ xv6.out* \ + ph barrier # try to generate a unique GDB port GDBPORT = $(shell expr `id -u` % 5000 + 25000) @@ -162,12 +295,23 @@ QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \ ifndef CPUS CPUS := 3 endif +ifeq ($(LAB),fs) +CPUS := 1 +endif + +FWDPORT1 = $(shell expr `id -u` % 5000 + 25999) +FWDPORT2 = $(shell expr `id -u` % 5000 + 30999) QEMUOPTS = -machine virt -bios none -kernel $K/kernel -m 128M -smp $(CPUS) -nographic QEMUOPTS += -global virtio-mmio.force-legacy=false QEMUOPTS += -drive file=fs.img,if=none,format=raw,id=x0 QEMUOPTS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 +ifeq ($(LAB),net) +QEMUOPTS += -netdev user,id=net0,hostfwd=udp::$(FWDPORT1)-:2000,hostfwd=udp::$(FWDPORT2)-:2001 -object filter-dump,id=net0,netdev=net0,file=packets.pcap +QEMUOPTS += -device e1000,netdev=net0,bus=pcie.0 +endif + qemu: $K/kernel fs.img $(QEMU) $(QEMUOPTS) @@ -178,3 +322,56 @@ qemu-gdb: $K/kernel .gdbinit fs.img @echo "*** Now run 'gdb' in another window." 1>&2 $(QEMU) $(QEMUOPTS) -S $(QEMUGDB) +ifeq ($(LAB),net) +# try to generate a unique port for the echo server +SERVERPORT = $(shell expr `id -u` % 5000 + 25099) + +endif + +## +## FOR testing lab grading script +## + +ifneq ($(V),@) +GRADEFLAGS += -v +endif + +print-gdbport: + @echo $(GDBPORT) + +grade: + @echo $(MAKE) clean + @$(MAKE) clean || \ + (echo "'make clean' failed. HINT: Do you have another running instance of xv6?" && exit 1) + ./grade-lab-$(LAB) $(GRADEFLAGS) + +## +## FOR submissions +## + +submit-check: + @if ! test -d .git; then \ + echo No .git directory, is this a git repository?; \ + false; \ + fi + @if test "$$(git symbolic-ref HEAD)" != refs/heads/$(LAB); then \ + git branch; \ + read -p "You are not on the $(LAB) branch. Hand-in the current branch? [y/N] " r; \ + test "$$r" = y; \ + fi + @if ! git diff-files --quiet || ! git diff-index --quiet --cached HEAD; then \ + git status -s; \ + echo; \ + echo "You have uncomitted changes. Please commit or stash them."; \ + false; \ + fi + @if test -n "`git status -s`"; then \ + git status -s; \ + read -p "Untracked files will not be handed in. Continue? [y/N] " r; \ + test "$$r" = y; \ + fi + +zipball: clean submit-check + git archive --verbose --format zip --output lab.zip HEAD + +.PHONY: zipball clean grade submit-check diff --git a/conf/lab.mk b/conf/lab.mk new file mode 100644 index 0000000..26dcd75 --- /dev/null +++ b/conf/lab.mk @@ -0,0 +1 @@ +LAB=net diff --git a/grade-lab-net b/grade-lab-net new file mode 100755 index 0000000..3b2707b --- /dev/null +++ b/grade-lab-net @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 + +import re +import subprocess +from gradelib import * + +r = Runner(save("xv6.out")) + +serverout = None + +@test(0, "running nettest") +def test_nettest(): + global serverout + server = subprocess.Popen(["python3", "./nettest.py", "grade"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + r.run_qemu(shell_script([ + 'nettest grade' + ]), timeout=30) + server.terminate() + serverout = server.communicate()[0].decode() + +@test(20, "nettest: txone", parent=test_nettest) +def test_nettest_(): + assert_lines_match(serverout, 'txone: OK') + +@test(20, "nettest: arp_rx", parent=test_nettest) +def test_nettest_(): + r.match('arp_rx: received an ARP packet') + +@test(20, "nettest: ip_rx", parent=test_nettest) +def test_nettest_(): + r.match('ip_rx: received an IP packet') + +@test(20, "nettest: ping0", parent=test_nettest) +def test_nettest_(): + r.match('^ping0: OK$') + +@test(20, "nettest: ping1", parent=test_nettest) +def test_nettest_(): + r.match('^ping1: OK$') + +@test(20, "nettest: ping2", parent=test_nettest) +def test_nettest_(): + r.match('^ping2: OK$') + +@test(30, "nettest: ping3", parent=test_nettest) +def test_nettest_(): + r.match('^ping3: OK$') + +@test(10, "nettest: dns", parent=test_nettest) +def test_nettest_(): + r.match('^dns: OK$') + +#@test(10, "answers-net.txt") +#def test_answers(): +# # just a simple sanity check, will be graded manually +# check_answers("answers-net.txt") + +@test(1, "time") +def test_time(): + check_time() + +run_tests() diff --git a/gradelib.py b/gradelib.py new file mode 100644 index 0000000..f0d4934 --- /dev/null +++ b/gradelib.py @@ -0,0 +1,628 @@ +from __future__ import print_function + +import sys, os, re, time, socket, select, subprocess, errno, shutil, random, string, json +from subprocess import check_call, Popen +from optparse import OptionParser + +__all__ = [] + +################################################################## +# Test structure +# + +__all__ += ["test", "end_part", "run_tests", "get_current_test"] + +TESTS = [] +TOTAL = POSSIBLE = 0 +PART_TOTAL = PART_POSSIBLE = 0 +CURRENT_TEST = None +GRADES = {} + +def test(points, title=None, parent=None): + """Decorator for declaring test functions. If title is None, the + title of the test will be derived from the function name by + stripping the leading "test_" and replacing underscores with + spaces.""" + + def register_test(fn, title=title): + if not title: + assert fn.__name__.startswith("test_") + title = fn.__name__[5:].replace("_", " ") + if parent: + title = " " + title + + def run_test(): + global TOTAL, POSSIBLE, CURRENT_TEST, GRADES + + # Handle test dependencies + if run_test.complete: + return run_test.ok + run_test.complete = True + parent_failed = False + if parent: + parent_failed = not parent() + + # Run the test + fail = None + start = time.time() + CURRENT_TEST = run_test + sys.stdout.write("== Test %s == " % title) + if parent: + sys.stdout.write("\n") + sys.stdout.flush() + try: + if parent_failed: + raise AssertionError('Parent failed: %s' % parent.__name__) + fn() + except AssertionError as e: + fail = str(e) + + # Display and handle test result + POSSIBLE += points + if points: + print("%s: %s" % (title, \ + (color("red", "FAIL") if fail else color("green", "OK"))), end=' ') + if time.time() - start > 0.1: + print("(%.1fs)" % (time.time() - start), end=' ') + print() + if fail: + print(" %s" % fail.replace("\n", "\n ")) + else: + TOTAL += points + if points: + GRADES[title] = 0 if fail else points + + for callback in run_test.on_finish: + callback(fail) + CURRENT_TEST = None + + run_test.ok = not fail + return run_test.ok + + # Record test metadata on the test wrapper function + run_test.__name__ = fn.__name__ + run_test.title = title + run_test.complete = False + run_test.ok = False + run_test.on_finish = [] + TESTS.append(run_test) + return run_test + return register_test + +def end_part(name): + def show_part(): + global PART_TOTAL, PART_POSSIBLE + print("Part %s score: %d/%d" % \ + (name, TOTAL - PART_TOTAL, POSSIBLE - PART_POSSIBLE)) + print() + PART_TOTAL, PART_POSSIBLE = TOTAL, POSSIBLE + show_part.title = "" + TESTS.append(show_part) + +def write_results(): + global options + if not options.results: + return + try: + with open(options.results, "w") as f: + f.write(json.dumps(GRADES)) + except OSError as e: + print("Provided a bad results path. Error:", e) + +def run_tests(): + """Set up for testing and run the registered test functions.""" + + # Handle command line + global options + parser = OptionParser(usage="usage: %prog [-v] [filters...]") + parser.add_option("-v", "--verbose", action="store_true", + help="print commands") + parser.add_option("--color", choices=["never", "always", "auto"], + default="auto", help="never, always, or auto") + parser.add_option("--results", help="results file path") + (options, args) = parser.parse_args() + + # Start with a full build to catch build errors + make() + + # Clean the file system if there is one + reset_fs() + + # Run tests + limit = list(map(str.lower, args)) + try: + for test in TESTS: + if not limit or any(l in test.title.lower() for l in limit): + test() + if not limit: + write_results() + print("Score: %d/%d" % (TOTAL, POSSIBLE)) + except KeyboardInterrupt: + pass + if TOTAL < POSSIBLE: + sys.exit(1) + +def get_current_test(): + if not CURRENT_TEST: + raise RuntimeError("No test is running") + return CURRENT_TEST + +################################################################## +# Assertions +# + +__all__ += ["assert_equal", "assert_lines_match"] + +def assert_equal(got, expect, msg=""): + if got == expect: + return + if msg: + msg += "\n" + raise AssertionError("%sgot:\n %s\nexpected:\n %s" % + (msg, str(got).replace("\n", "\n "), + str(expect).replace("\n", "\n "))) + +def assert_lines_match(text, *regexps, **kw): + """Assert that all of regexps match some line in text. If a 'no' + keyword argument is given, it must be a list of regexps that must + *not* match any line in text.""" + + def assert_lines_match_kw(no=[]): + return no + no = assert_lines_match_kw(**kw) + + # Check text against regexps + lines = text.splitlines() + good = set() + bad = set() + for i, line in enumerate(lines): + if any(re.match(r, line) for r in regexps): + good.add(i) + regexps = [r for r in regexps if not re.match(r, line)] + if any(re.match(r, line) for r in no): + bad.add(i) + + if not regexps and not bad: + return + + # We failed; construct an informative failure message + show = set() + for lineno in good.union(bad): + for offset in range(-2, 3): + show.add(lineno + offset) + if regexps: + show.update(n for n in range(len(lines) - 5, len(lines))) + + msg = [] + last = -1 + for lineno in sorted(show): + if 0 <= lineno < len(lines): + if lineno != last + 1: + msg.append("...") + last = lineno + msg.append("%s %s" % (color("red", "BAD ") if lineno in bad else + color("green", "GOOD") if lineno in good + else " ", + lines[lineno])) + if last != len(lines) - 1: + msg.append("...") + if bad: + msg.append("unexpected lines in output") + for r in regexps: + msg.append(color("red", "MISSING") + " '%s'" % r) + raise AssertionError("\n".join(msg)) + +################################################################## +# Utilities +# + +__all__ += ["make", "maybe_unlink", "reset_fs", "color", "random_str", "check_time", "check_answers"] + +MAKE_TIMESTAMP = 0 + +def pre_make(): + """Delay prior to running make to ensure file mtimes change.""" + while int(time.time()) == MAKE_TIMESTAMP: + time.sleep(0.1) + +def post_make(): + """Record the time after make completes so that the next run of + make can be delayed if needed.""" + global MAKE_TIMESTAMP + MAKE_TIMESTAMP = int(time.time()) + +def make(*target): + pre_make() + if Popen(("make",) + target).wait(): + sys.exit(1) + post_make() + +def show_command(cmd): + from pipes import quote + print("\n$", " ".join(map(quote, cmd))) + +def maybe_unlink(*paths): + for path in paths: + try: + os.unlink(path) + except EnvironmentError as e: + if e.errno != errno.ENOENT: + raise + +COLORS = {"default": "\033[0m", "red": "\033[31m", "green": "\033[32m"} + +def color(name, text): + if options.color == "always" or (options.color == "auto" and os.isatty(1)): + return COLORS[name] + text + COLORS["default"] + return text + +def reset_fs(): + if os.path.exists("obj/fs/clean-fs.img"): + shutil.copyfile("obj/fs/clean-fs.img", "obj/fs/fs.img") + +def random_str(n=8): + letters = string.ascii_letters + string.digits + return ''.join(random.choice(letters) for _ in range(n)) + +def check_time(): + try: + print("") + with open('time.txt') as f: + d = f.read().strip() + if not re.match(r'^\d+$', d): + raise AssertionError('time.txt does not contain a single integer (number of hours spent on the lab)') + except IOError: + raise AssertionError('Cannot read time.txt') + +def check_answers(file, n=10): + try: + print("") + with open(file) as f: + d = f.read().strip() + if len(d) < n: + raise AssertionError('%s does not seem to contain enough text' % file) + except IOError: + raise AssertionError('Cannot read %s' % file) + + +################################################################## +# Controllers +# + +__all__ += ["QEMU", "GDBClient"] + +class QEMU(object): + _GDBPORT = None + + def __init__(self, *make_args): + # Check that QEMU is not currently running + try: + GDBClient(self.get_gdb_port(), timeout=0).close() + except socket.error: + pass + else: + print("""\ +GDB stub found on port %d. +QEMU appears to already be running. Please exit it if possible or use +'killall qemu' or 'killall qemu.real'.""" % self.get_gdb_port(), file=sys.stderr) + sys.exit(1) + + if options.verbose: + show_command(("make",) + make_args) + cmd = ("make", "-s", "--no-print-directory") + make_args + self.proc = Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE) + # Accumulated output as a string + self.output = "" + # Accumulated output as a bytearray + self.outbytes = bytearray() + self.on_output = [] + + @staticmethod + def get_gdb_port(): + if QEMU._GDBPORT is None: + p = Popen(["make", "-s", "--no-print-directory", "print-gdbport"], + stdout=subprocess.PIPE) + (out, _) = p.communicate() + if p.returncode: + raise RuntimeError( + "Failed to get gdbport: make exited with %d" % + p.returncode) + QEMU._GDBPORT = int(out) + return QEMU._GDBPORT + + def fileno(self): + if self.proc: + return self.proc.stdout.fileno() + + def handle_read(self): + buf = os.read(self.proc.stdout.fileno(), 4096) + self.outbytes.extend(buf) + self.output = self.outbytes.decode("utf-8", "replace") + for callback in self.on_output: + callback(buf) + if buf == b"": + self.wait() + return + + def write(self, buf): + if isinstance(buf, str): + buf = buf.encode('utf-8') + self.proc.stdin.write(buf) + self.proc.stdin.flush() + + def wait(self): + if self.proc: + self.proc.wait() + self.proc = None + + def kill(self): + if self.proc: + self.proc.terminate() + +class GDBClient(object): + def __init__(self, port, timeout=15): + start = time.time() + while True: + self.sock = socket.socket() + try: + self.sock.settimeout(1) + self.sock.connect(("localhost", port)) + break + except socket.error: + if time.time() >= start + timeout: + raise + self.__buf = "" + + def fileno(self): + if self.sock: + return self.sock.fileno() + + def handle_read(self): + try: + data = self.sock.recv(4096).decode("ascii", "replace") + except socket.error: + data = "" + if data == "": + self.sock.close() + self.sock = None + return + self.__buf += data + + while True: + m = re.search(r"\$([^#]*)#[0-9a-zA-Z]{2}", self.__buf) + if not m: + break + pkt = m.group(1) + self.__buf = self.__buf[m.end():] + + if pkt.startswith("T05"): + # Breakpoint + raise TerminateTest + + def __send(self, cmd): + packet = "$%s#%02x" % (cmd, sum(map(ord, cmd)) % 256) + self.sock.sendall(packet.encode("ascii")) + + def __send_break(self): + self.sock.sendall(b"\x03") + + def close(self): + if self.sock: + self.sock.close() + self.sock = None + + def cont(self): + self.__send("c") + + def breakpoint(self, addr): + self.__send("Z1,%x,1" % addr) + + +################################################################## +# QEMU test runner +# + +__all__ += ["TerminateTest", "Runner"] + +class TerminateTest(Exception): + pass + +class Runner(): + def __init__(self, *default_monitors): + self.__default_monitors = default_monitors + + def run_qemu(self, *monitors, **kw): + """Run a QEMU-based test. monitors should functions that will + be called with this Runner instance once QEMU and GDB are + started. Typically, they should register callbacks that throw + TerminateTest when stop events occur. The target_base + argument gives the make target to run. The make_args argument + should be a list of additional arguments to pass to make. The + timeout argument bounds how long to run before returning.""" + + def run_qemu_kw(target_base="qemu", make_args=[], timeout=30): + return target_base, make_args, timeout + target_base, make_args, timeout = run_qemu_kw(**kw) + + # Start QEMU + pre_make() + self.qemu = QEMU(target_base + "-gdb", *make_args) + self.gdb = None + + try: + # Wait for QEMU to start or make to fail. This will set + # self.gdb if QEMU starts. + self.qemu.on_output = [self.__monitor_start] + self.__react([self.qemu], timeout=90) + self.qemu.on_output = [] + if self.gdb is None: + print("Failed to connect to QEMU; output:") + print(self.qemu.output) + sys.exit(1) + post_make() + + # QEMU and GDB are up + self.reactors = [self.qemu, self.gdb] + + # Start monitoring + for m in self.__default_monitors + monitors: + m(self) + + # Run and react + self.gdb.cont() + self.__react(self.reactors, timeout) + finally: + # Shutdown QEMU + try: + if self.gdb is None: + sys.exit(1) + self.qemu.kill() + self.__react(self.reactors, 5) + self.gdb.close() + self.qemu.wait() + except: + print("""\ +Failed to shutdown QEMU. You might need to 'killall qemu' or +'killall qemu.real'. +""") + raise + + def __monitor_start(self, output): + if b"\n" in output: + try: + self.gdb = GDBClient(self.qemu.get_gdb_port(), timeout=2) + raise TerminateTest + except socket.error: + pass + if not len(output): + raise TerminateTest + + def __react(self, reactors, timeout): + deadline = time.time() + timeout + try: + while True: + timeleft = deadline - time.time() + if timeleft < 0: + sys.stdout.write("Timeout! ") + sys.stdout.flush() + return + + rset = [r for r in reactors if r.fileno() is not None] + if not rset: + return + + rset, _, _ = select.select(rset, [], [], timeleft) + for reactor in rset: + reactor.handle_read() + except TerminateTest: + pass + + def user_test(self, binary, *monitors, **kw): + """Run a user test using the specified binary. Monitors and + keyword arguments are as for run_qemu. This runs on a disk + snapshot unless the keyword argument 'snapshot' is False.""" + + maybe_unlink("obj/kern/init.o", "obj/kern/kernel") + if kw.pop("snapshot", True): + kw.setdefault("make_args", []).append("QEMUEXTRA+=-snapshot") + self.run_qemu(target_base="run-%s" % binary, *monitors, **kw) + + def match(self, *args, **kwargs): + """Shortcut to call assert_lines_match on the most recent QEMU + output.""" + + assert_lines_match(self.qemu.output, *args, **kwargs) + +################################################################## +# Monitors +# + +__all__ += ["save", "stop_breakpoint", "call_on_line", "stop_on_line", "shell_script"] + +def save(path): + """Return a monitor that writes QEMU's output to path. If the + test fails, copy the output to path.test-name.""" + + def setup_save(runner): + f.seek(0) + f.truncate() + runner.qemu.on_output.append(f.write) + get_current_test().on_finish.append(save_on_finish) + + def save_on_finish(fail): + f.flush() + save_path = path + "." + get_current_test().__name__[5:] + if fail: + shutil.copyfile(path, save_path) + print(" QEMU output saved to %s" % save_path) + elif os.path.exists(save_path): + os.unlink(save_path) + print(" (Old %s failure log removed)" % save_path) + + f = open(path, "wb") + return setup_save + +def stop_breakpoint(addr): + """Returns a monitor that stops when addr is reached. addr may be + a number or the name of a symbol.""" + + def setup_breakpoint(runner): + if isinstance(addr, str): + addrs = [int(sym[:16], 16) for sym in open("kernel/kernel.sym") + if sym[17:].strip() == addr] + assert len(addrs), "Symbol %s not found" % addr + runner.gdb.breakpoint(addrs[0]) + else: + runner.gdb.breakpoint(addr) + return setup_breakpoint + +def call_on_line(regexp, callback): + """Returns a monitor that calls 'callback' when QEMU prints a line + matching 'regexp'.""" + + def setup_call_on_line(runner): + buf = bytearray() + def handle_output(output): + buf.extend(output) + while b"\n" in buf: + line, buf[:] = buf.split(b"\n", 1) + line = line.decode("utf-8", "replace") + if re.match(regexp, line): + callback(line) + runner.qemu.on_output.append(handle_output) + return setup_call_on_line + +def stop_on_line(regexp): + """Returns a monitor that stops when QEMU prints a line matching + 'regexp'.""" + + def stop(line): + raise TerminateTest + return call_on_line(regexp, stop) + +def shell_script(script, terminate_match=None): + """Returns a monitor that plays the script, and stops when the script is + done executing.""" + + def setup_call_on_line(runner): + class context: + n = 0 + buf = bytearray() + def handle_output(output): + context.buf.extend(output) + if terminate_match is not None: + if re.match(terminate_match, context.buf.decode('utf-8', 'replace')): + raise TerminateTest + if b'$ ' in context.buf: + context.buf = bytearray() + if context.n < len(script): + runner.qemu.write(script[context.n]) + runner.qemu.write('\n') + context.n += 1 + else: + if terminate_match is None: + raise TerminateTest + runner.qemu.on_output.append(handle_output) + return setup_call_on_line diff --git a/kernel/defs.h b/kernel/defs.h index d1b6bb9..d133cd3 100644 --- a/kernel/defs.h +++ b/kernel/defs.h @@ -1,3 +1,7 @@ +#ifdef LAB_MMAP +typedef unsigned long size_t; +typedef long int off_t; +#endif struct buf; struct context; struct file; @@ -117,6 +121,10 @@ void initlock(struct spinlock*, char*); void release(struct spinlock*); void push_off(void); void pop_off(void); +int atomic_read4(int *addr); +#ifdef LAB_LOCK +void freelock(struct spinlock*); +#endif // sleeplock.c void acquiresleep(struct sleeplock*); @@ -173,6 +181,12 @@ uint64 walkaddr(pagetable_t, uint64); int copyout(pagetable_t, uint64, char *, uint64); int copyin(pagetable_t, char *, uint64, uint64); int copyinstr(pagetable_t, char *, uint64, uint64); +#if defined(LAB_PGTBL) || defined(SOL_MMAP) +void vmprint(pagetable_t); +#endif +#ifdef LAB_PGTBL +pte_t* pgpte(pagetable_t, uint64); +#endif // plic.c void plicinit(void); @@ -187,3 +201,39 @@ void virtio_disk_intr(void); // number of elements in fixed-size array #define NELEM(x) (sizeof(x)/sizeof((x)[0])) + + + +#ifdef LAB_PGTBL +// vmcopyin.c +int copyin_new(pagetable_t, char *, uint64, uint64); +int copyinstr_new(pagetable_t, char *, uint64, uint64); +#endif + +#ifdef LAB_LOCK +// stats.c +void statsinit(void); +void statsinc(void); + +// sprintf.c +int snprintf(char*, unsigned long, const char*, ...); +#endif + +#ifdef KCSAN +void kcsaninit(); +#endif + +#ifdef LAB_NET +// pci.c +void pci_init(); + +// e1000.c +void e1000_init(uint32 *); +void e1000_intr(void); +int e1000_transmit(char *, int); + +// net.c +void netinit(void); +void net_rx(char *buf, int len); + +#endif diff --git a/kernel/e1000.c b/kernel/e1000.c new file mode 100644 index 0000000..009e977 --- /dev/null +++ b/kernel/e1000.c @@ -0,0 +1,130 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" +#include "e1000_dev.h" + +#define TX_RING_SIZE 16 +static struct tx_desc tx_ring[TX_RING_SIZE] __attribute__((aligned(16))); +static char *tx_bufs[TX_RING_SIZE]; + +#define RX_RING_SIZE 16 +static struct rx_desc rx_ring[RX_RING_SIZE] __attribute__((aligned(16))); +static char *rx_bufs[RX_RING_SIZE]; + +// remember where the e1000's registers live. +static volatile uint32 *regs; + +struct spinlock e1000_lock; + +// called by pci_init(). +// xregs is the memory address at which the +// e1000's registers are mapped. +void +e1000_init(uint32 *xregs) +{ + int i; + + initlock(&e1000_lock, "e1000"); + + regs = xregs; + + // Reset the device + regs[E1000_IMS] = 0; // disable interrupts + regs[E1000_CTL] |= E1000_CTL_RST; + regs[E1000_IMS] = 0; // redisable interrupts + __sync_synchronize(); + + // [E1000 14.5] Transmit initialization + memset(tx_ring, 0, sizeof(tx_ring)); + for (i = 0; i < TX_RING_SIZE; i++) { + tx_ring[i].status = E1000_TXD_STAT_DD; + tx_bufs[i] = 0; + } + regs[E1000_TDBAL] = (uint64) tx_ring; + if(sizeof(tx_ring) % 128 != 0) + panic("e1000"); + regs[E1000_TDLEN] = sizeof(tx_ring); + regs[E1000_TDH] = regs[E1000_TDT] = 0; + + // [E1000 14.4] Receive initialization + memset(rx_ring, 0, sizeof(rx_ring)); + for (i = 0; i < RX_RING_SIZE; i++) { + rx_bufs[i] = kalloc(); + if (!rx_bufs[i]) + panic("e1000"); + rx_ring[i].addr = (uint64) rx_bufs[i]; + } + regs[E1000_RDBAL] = (uint64) rx_ring; + if(sizeof(rx_ring) % 128 != 0) + panic("e1000"); + regs[E1000_RDH] = 0; + regs[E1000_RDT] = RX_RING_SIZE - 1; + regs[E1000_RDLEN] = sizeof(rx_ring); + + // filter by qemu's MAC address, 52:54:00:12:34:56 + regs[E1000_RA] = 0x12005452; + regs[E1000_RA+1] = 0x5634 | (1<<31); + // multicast table + for (int i = 0; i < 4096/32; i++) + regs[E1000_MTA + i] = 0; + + // transmitter control bits. + regs[E1000_TCTL] = E1000_TCTL_EN | // enable + E1000_TCTL_PSP | // pad short packets + (0x10 << E1000_TCTL_CT_SHIFT) | // collision stuff + (0x40 << E1000_TCTL_COLD_SHIFT); + regs[E1000_TIPG] = 10 | (8<<10) | (6<<20); // inter-pkt gap + + // receiver control bits. + regs[E1000_RCTL] = E1000_RCTL_EN | // enable receiver + E1000_RCTL_BAM | // enable broadcast + E1000_RCTL_SZ_2048 | // 2048-byte rx buffers + E1000_RCTL_SECRC; // strip CRC + + // ask e1000 for receive interrupts. + regs[E1000_RDTR] = 0; // interrupt after every received packet (no timer) + regs[E1000_RADV] = 0; // interrupt after every packet (no timer) + regs[E1000_IMS] = (1 << 7); // RXDW -- Receiver Descriptor Write Back +} + +int +e1000_transmit(char *buf, int len) +{ + // + // Your code here. + // + // buf contains an ethernet frame; program it into + // the TX descriptor ring so that the e1000 sends it. Stash + // a pointer so that it can be freed after send completes. + // + + + return 0; +} + +static void +e1000_recv(void) +{ + // + // Your code here. + // + // Check for packets that have arrived from the e1000 + // Create and deliver a buf for each packet (using net_rx()). + // + +} + +void +e1000_intr(void) +{ + // tell the e1000 we've seen this interrupt; + // without this the e1000 won't raise any + // further interrupts. + regs[E1000_ICR] = 0xffffffff; + + e1000_recv(); +} diff --git a/kernel/e1000_dev.h b/kernel/e1000_dev.h new file mode 100644 index 0000000..8173fdb --- /dev/null +++ b/kernel/e1000_dev.h @@ -0,0 +1,125 @@ +// +// E1000 hardware definitions: registers and DMA ring format. +// from the Intel 82540EP/EM &c manual. +// + +/* Registers */ +#define E1000_CTL (0x00000/4) /* Device Control Register - RW */ +#define E1000_ICR (0x000C0/4) /* Interrupt Cause Read - R */ +#define E1000_IMS (0x000D0/4) /* Interrupt Mask Set - RW */ +#define E1000_RCTL (0x00100/4) /* RX Control - RW */ +#define E1000_TCTL (0x00400/4) /* TX Control - RW */ +#define E1000_TIPG (0x00410/4) /* TX Inter-packet gap -RW */ +#define E1000_RDBAL (0x02800/4) /* RX Descriptor Base Address Low - RW */ +#define E1000_RDTR (0x02820/4) /* RX Delay Timer */ +#define E1000_RADV (0x0282C/4) /* RX Interrupt Absolute Delay Timer */ +#define E1000_RDH (0x02810/4) /* RX Descriptor Head - RW */ +#define E1000_RDT (0x02818/4) /* RX Descriptor Tail - RW */ +#define E1000_RDLEN (0x02808/4) /* RX Descriptor Length - RW */ +#define E1000_RSRPD (0x02C00/4) /* RX Small Packet Detect Interrupt */ +#define E1000_TDBAL (0x03800/4) /* TX Descriptor Base Address Low - RW */ +#define E1000_TDLEN (0x03808/4) /* TX Descriptor Length - RW */ +#define E1000_TDH (0x03810/4) /* TX Descriptor Head - RW */ +#define E1000_TDT (0x03818/4) /* TX Descriptor Tail - RW */ +#define E1000_MTA (0x05200/4) /* Multicast Table Array - RW Array */ +#define E1000_RA (0x05400/4) /* Receive Address - RW Array */ + +/* Device Control */ +#define E1000_CTL_SLU 0x00000040 /* set link up */ +#define E1000_CTL_FRCSPD 0x00000800 /* force speed */ +#define E1000_CTL_FRCDPLX 0x00001000 /* force duplex */ +#define E1000_CTL_RST 0x04000000 /* full reset */ + +/* Transmit Control */ +#define E1000_TCTL_RST 0x00000001 /* software reset */ +#define E1000_TCTL_EN 0x00000002 /* enable tx */ +#define E1000_TCTL_BCE 0x00000004 /* busy check enable */ +#define E1000_TCTL_PSP 0x00000008 /* pad short packets */ +#define E1000_TCTL_CT 0x00000ff0 /* collision threshold */ +#define E1000_TCTL_CT_SHIFT 4 +#define E1000_TCTL_COLD 0x003ff000 /* collision distance */ +#define E1000_TCTL_COLD_SHIFT 12 +#define E1000_TCTL_SWXOFF 0x00400000 /* SW Xoff transmission */ +#define E1000_TCTL_PBE 0x00800000 /* Packet Burst Enable */ +#define E1000_TCTL_RTLC 0x01000000 /* Re-transmit on late collision */ +#define E1000_TCTL_NRTU 0x02000000 /* No Re-transmit on underrun */ +#define E1000_TCTL_MULR 0x10000000 /* Multiple request support */ + +/* Receive Control */ +#define E1000_RCTL_RST 0x00000001 /* Software reset */ +#define E1000_RCTL_EN 0x00000002 /* enable */ +#define E1000_RCTL_SBP 0x00000004 /* store bad packet */ +#define E1000_RCTL_UPE 0x00000008 /* unicast promiscuous enable */ +#define E1000_RCTL_MPE 0x00000010 /* multicast promiscuous enab */ +#define E1000_RCTL_LPE 0x00000020 /* long packet enable */ +#define E1000_RCTL_LBM_NO 0x00000000 /* no loopback mode */ +#define E1000_RCTL_LBM_MAC 0x00000040 /* MAC loopback mode */ +#define E1000_RCTL_LBM_SLP 0x00000080 /* serial link loopback mode */ +#define E1000_RCTL_LBM_TCVR 0x000000C0 /* tcvr loopback mode */ +#define E1000_RCTL_DTYP_MASK 0x00000C00 /* Descriptor type mask */ +#define E1000_RCTL_DTYP_PS 0x00000400 /* Packet Split descriptor */ +#define E1000_RCTL_RDMTS_HALF 0x00000000 /* rx desc min threshold size */ +#define E1000_RCTL_RDMTS_QUAT 0x00000100 /* rx desc min threshold size */ +#define E1000_RCTL_RDMTS_EIGTH 0x00000200 /* rx desc min threshold size */ +#define E1000_RCTL_MO_SHIFT 12 /* multicast offset shift */ +#define E1000_RCTL_MO_0 0x00000000 /* multicast offset 11:0 */ +#define E1000_RCTL_MO_1 0x00001000 /* multicast offset 12:1 */ +#define E1000_RCTL_MO_2 0x00002000 /* multicast offset 13:2 */ +#define E1000_RCTL_MO_3 0x00003000 /* multicast offset 15:4 */ +#define E1000_RCTL_MDR 0x00004000 /* multicast desc ring 0 */ +#define E1000_RCTL_BAM 0x00008000 /* broadcast enable */ +/* these buffer sizes are valid if E1000_RCTL_BSEX is 0 */ +#define E1000_RCTL_SZ_2048 0x00000000 /* rx buffer size 2048 */ +#define E1000_RCTL_SZ_1024 0x00010000 /* rx buffer size 1024 */ +#define E1000_RCTL_SZ_512 0x00020000 /* rx buffer size 512 */ +#define E1000_RCTL_SZ_256 0x00030000 /* rx buffer size 256 */ +/* these buffer sizes are valid if E1000_RCTL_BSEX is 1 */ +#define E1000_RCTL_SZ_16384 0x00010000 /* rx buffer size 16384 */ +#define E1000_RCTL_SZ_8192 0x00020000 /* rx buffer size 8192 */ +#define E1000_RCTL_SZ_4096 0x00030000 /* rx buffer size 4096 */ +#define E1000_RCTL_VFE 0x00040000 /* vlan filter enable */ +#define E1000_RCTL_CFIEN 0x00080000 /* canonical form enable */ +#define E1000_RCTL_CFI 0x00100000 /* canonical form indicator */ +#define E1000_RCTL_DPF 0x00400000 /* discard pause frames */ +#define E1000_RCTL_PMCF 0x00800000 /* pass MAC control frames */ +#define E1000_RCTL_BSEX 0x02000000 /* Buffer size extension */ +#define E1000_RCTL_SECRC 0x04000000 /* Strip Ethernet CRC */ +#define E1000_RCTL_FLXBUF_MASK 0x78000000 /* Flexible buffer size */ +#define E1000_RCTL_FLXBUF_SHIFT 27 /* Flexible buffer shift */ + +#define DATA_MAX 1518 + +/* Transmit Descriptor command definitions [E1000 3.3.3.1] */ +#define E1000_TXD_CMD_EOP 0x01 /* End of Packet */ +#define E1000_TXD_CMD_RS 0x08 /* Report Status */ + +/* Transmit Descriptor status definitions [E1000 3.3.3.2] */ +#define E1000_TXD_STAT_DD 0x00000001 /* Descriptor Done */ + +// [E1000 3.3.3] +struct tx_desc +{ + uint64 addr; + uint16 length; + uint8 cso; + uint8 cmd; + uint8 status; + uint8 css; + uint16 special; +}; + +/* Receive Descriptor bit definitions [E1000 3.2.3.1] */ +#define E1000_RXD_STAT_DD 0x01 /* Descriptor Done */ +#define E1000_RXD_STAT_EOP 0x02 /* End of Packet */ + +// [E1000 3.2.3] +struct rx_desc +{ + uint64 addr; /* Address of the descriptor's data buffer */ + uint16 length; /* Length of data DMAed into data buffer */ + uint16 csum; /* Packet checksum */ + uint8 status; /* Descriptor status */ + uint8 errors; /* Descriptor Errors */ + uint16 special; +}; + diff --git a/kernel/file.h b/kernel/file.h index b076d1d..bd42a0e 100644 --- a/kernel/file.h +++ b/kernel/file.h @@ -38,3 +38,4 @@ struct devsw { extern struct devsw devsw[]; #define CONSOLE 1 +#define STATS 2 diff --git a/kernel/main.c b/kernel/main.c index f0d3171..61e7675 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -12,6 +12,9 @@ main() { if(cpuid() == 0){ consoleinit(); +#if defined(LAB_LOCK) + statsinit(); +#endif printfinit(); printf("\n"); printf("xv6 kernel is booting\n"); @@ -28,11 +31,17 @@ main() iinit(); // inode table fileinit(); // file table virtio_disk_init(); // emulated hard disk +#ifdef LAB_NET + pci_init(); +#endif userinit(); // first user process +#ifdef KCSAN + kcsaninit(); +#endif __sync_synchronize(); started = 1; } else { - while(started == 0) + while(atomic_read4((int *) &started) == 0) ; __sync_synchronize(); printf("hart %d starting\n", cpuid()); diff --git a/kernel/memlayout.h b/kernel/memlayout.h index 3ab2ace..0a5679b 100644 --- a/kernel/memlayout.h +++ b/kernel/memlayout.h @@ -25,6 +25,10 @@ #define VIRTIO0 0x10001000 #define VIRTIO0_IRQ 1 +#ifdef LAB_NET +#define E1000_IRQ 33 +#endif + // qemu puts platform-level interrupt controller (PLIC) here. #define PLIC 0x0c000000L #define PLIC_PRIORITY (PLIC + 0x0) @@ -45,7 +49,7 @@ // map kernel stacks beneath the trampoline, // each surrounded by invalid guard pages. -#define KSTACK(p) (TRAMPOLINE - ((p)+1)* 2*PGSIZE) +#define KSTACK(p) (TRAMPOLINE - (p)*2*PGSIZE - 3*PGSIZE) // User memory layout. // Address zero first: @@ -54,6 +58,14 @@ // fixed-size stack // expandable heap // ... +// USYSCALL (shared with kernel) // TRAPFRAME (p->trapframe, used by the trampoline) // TRAMPOLINE (the same page as in the kernel) #define TRAPFRAME (TRAMPOLINE - PGSIZE) +#ifdef LAB_PGTBL +#define USYSCALL (TRAPFRAME - PGSIZE) + +struct usyscall { + int pid; // Process ID +}; +#endif diff --git a/kernel/net.c b/kernel/net.c new file mode 100644 index 0000000..f4f9a45 --- /dev/null +++ b/kernel/net.c @@ -0,0 +1,259 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" +#include "fs.h" +#include "sleeplock.h" +#include "file.h" +#include "net.h" + +// xv6's ethernet and IP addresses +static uint8 local_mac[ETHADDR_LEN] = { 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 }; +static uint32 local_ip = MAKE_IP_ADDR(10, 0, 2, 15); + +// qemu host's ethernet address. +static uint8 host_mac[ETHADDR_LEN] = { 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02 }; + +static struct spinlock netlock; + +void +netinit(void) +{ + initlock(&netlock, "netlock"); +} + + +// +// bind(int port) +// prepare to receive UDP packets address to the port, +// i.e. allocate any queues &c needed. +// +uint64 +sys_bind(void) +{ + // + // Your code here. + // + + return -1; +} + +// +// unbind(int port) +// release any resources previously created by bind(port); +// from now on UDP packets addressed to port should be dropped. +// +uint64 +sys_unbind(void) +{ + // + // Optional: Your code here. + // + + return 0; +} + +// +// recv(int dport, int *src, short *sport, char *buf, int maxlen) +// if there's a received UDP packet already queued that was +// addressed to dport, then return it. +// otherwise wait for such a packet. +// +// sets *src to the IP source address. +// sets *sport to the UDP source port. +// copies up to maxlen bytes of UDP payload to buf. +// returns the number of bytes copied, +// and -1 if there was an error. +// +// dport, *src, and *sport are host byte order. +// bind(dport) must previously have been called. +// +uint64 +sys_recv(void) +{ + // + // Your code here. + // + return -1; +} + +// This code is lifted from FreeBSD's ping.c, and is copyright by the Regents +// of the University of California. +static unsigned short +in_cksum(const unsigned char *addr, int len) +{ + int nleft = len; + const unsigned short *w = (const unsigned short *)addr; + unsigned int sum = 0; + unsigned short answer = 0; + + /* + * Our algorithm is simple, using a 32 bit accumulator (sum), we add + * sequential 16 bit words to it, and at the end, fold back all the + * carry bits from the top 16 bits into the lower 16 bits. + */ + while (nleft > 1) { + sum += *w++; + nleft -= 2; + } + + /* mop up an odd byte, if necessary */ + if (nleft == 1) { + *(unsigned char *)(&answer) = *(const unsigned char *)w; + sum += answer; + } + + /* add back carry outs from top 16 bits to low 16 bits */ + sum = (sum & 0xffff) + (sum >> 16); + sum += (sum >> 16); + /* guaranteed now that the lower 16 bits of sum are correct */ + + answer = ~sum; /* truncate to 16 bits */ + return answer; +} + +// +// send(int sport, int dst, int dport, char *buf, int len) +// +uint64 +sys_send(void) +{ + struct proc *p = myproc(); + int sport; + int dst; + int dport; + uint64 bufaddr; + int len; + + argint(0, &sport); + argint(1, &dst); + argint(2, &dport); + argaddr(3, &bufaddr); + argint(4, &len); + + int total = len + sizeof(struct eth) + sizeof(struct ip) + sizeof(struct udp); + if(total > PGSIZE) + return -1; + + char *buf = kalloc(); + if(buf == 0){ + printf("sys_send: kalloc failed\n"); + return -1; + } + memset(buf, 0, PGSIZE); + + struct eth *eth = (struct eth *) buf; + memmove(eth->dhost, host_mac, ETHADDR_LEN); + memmove(eth->shost, local_mac, ETHADDR_LEN); + eth->type = htons(ETHTYPE_IP); + + struct ip *ip = (struct ip *)(eth + 1); + ip->ip_vhl = 0x45; // version 4, header length 4*5 + ip->ip_tos = 0; + ip->ip_len = htons(sizeof(struct ip) + sizeof(struct udp) + len); + ip->ip_id = 0; + ip->ip_off = 0; + ip->ip_ttl = 100; + ip->ip_p = IPPROTO_UDP; + ip->ip_src = htonl(local_ip); + ip->ip_dst = htonl(dst); + ip->ip_sum = in_cksum((unsigned char *)ip, sizeof(*ip)); + + struct udp *udp = (struct udp *)(ip + 1); + udp->sport = htons(sport); + udp->dport = htons(dport); + udp->ulen = htons(len + sizeof(struct udp)); + + char *payload = (char *)(udp + 1); + if(copyin(p->pagetable, payload, bufaddr, len) < 0){ + kfree(buf); + printf("send: copyin failed\n"); + return -1; + } + + e1000_transmit(buf, total); + + return 0; +} + +void +ip_rx(char *buf, int len) +{ + // don't delete this printf; make grade depends on it. + static int seen_ip = 0; + if(seen_ip == 0) + printf("ip_rx: received an IP packet\n"); + seen_ip = 1; + + // + // Your code here. + // + +} + +// +// send an ARP reply packet to tell qemu to map +// xv6's ip address to its ethernet address. +// this is the bare minimum needed to persuade +// qemu to send IP packets to xv6; the real ARP +// protocol is more complex. +// +void +arp_rx(char *inbuf) +{ + static int seen_arp = 0; + + if(seen_arp){ + kfree(inbuf); + return; + } + printf("arp_rx: received an ARP packet\n"); + seen_arp = 1; + + struct eth *ineth = (struct eth *) inbuf; + struct arp *inarp = (struct arp *) (ineth + 1); + + char *buf = kalloc(); + if(buf == 0) + panic("send_arp_reply"); + + struct eth *eth = (struct eth *) buf; + memmove(eth->dhost, ineth->shost, ETHADDR_LEN); // ethernet destination = query source + memmove(eth->shost, local_mac, ETHADDR_LEN); // ethernet source = xv6's ethernet address + eth->type = htons(ETHTYPE_ARP); + + struct arp *arp = (struct arp *)(eth + 1); + arp->hrd = htons(ARP_HRD_ETHER); + arp->pro = htons(ETHTYPE_IP); + arp->hln = ETHADDR_LEN; + arp->pln = sizeof(uint32); + arp->op = htons(ARP_OP_REPLY); + + memmove(arp->sha, local_mac, ETHADDR_LEN); + arp->sip = htonl(local_ip); + memmove(arp->tha, ineth->shost, ETHADDR_LEN); + arp->tip = inarp->sip; + + e1000_transmit(buf, sizeof(*eth) + sizeof(*arp)); + + kfree(inbuf); +} + +void +net_rx(char *buf, int len) +{ + struct eth *eth = (struct eth *) buf; + + if(len >= sizeof(struct eth) + sizeof(struct arp) && + ntohs(eth->type) == ETHTYPE_ARP){ + arp_rx(buf); + } else if(len >= sizeof(struct eth) + sizeof(struct ip) && + ntohs(eth->type) == ETHTYPE_IP){ + ip_rx(buf, len); + } else { + kfree(buf); + } +} diff --git a/kernel/net.h b/kernel/net.h new file mode 100644 index 0000000..42c0971 --- /dev/null +++ b/kernel/net.h @@ -0,0 +1,127 @@ +// +// endianness support +// + +static inline uint16 bswaps(uint16 val) +{ + return (((val & 0x00ffU) << 8) | + ((val & 0xff00U) >> 8)); +} + +static inline uint32 bswapl(uint32 val) +{ + return (((val & 0x000000ffUL) << 24) | + ((val & 0x0000ff00UL) << 8) | + ((val & 0x00ff0000UL) >> 8) | + ((val & 0xff000000UL) >> 24)); +} + +// Use these macros to convert network bytes to the native byte order. +// Note that Risc-V uses little endian while network order is big endian. +#define ntohs bswaps +#define ntohl bswapl +#define htons bswaps +#define htonl bswapl + + +// +// useful networking headers +// + +#define ETHADDR_LEN 6 + +// an Ethernet packet header (start of the packet). +struct eth { + uint8 dhost[ETHADDR_LEN]; + uint8 shost[ETHADDR_LEN]; + uint16 type; +} __attribute__((packed)); + +#define ETHTYPE_IP 0x0800 // Internet protocol +#define ETHTYPE_ARP 0x0806 // Address resolution protocol + +// an IP packet header (comes after an Ethernet header). +struct ip { + uint8 ip_vhl; // version << 4 | header length >> 2 + uint8 ip_tos; // type of service + uint16 ip_len; // total length, including this IP header + uint16 ip_id; // identification + uint16 ip_off; // fragment offset field + uint8 ip_ttl; // time to live + uint8 ip_p; // protocol + uint16 ip_sum; // checksum, covers just IP header + uint32 ip_src, ip_dst; +}; + +#define IPPROTO_ICMP 1 // Control message protocol +#define IPPROTO_TCP 6 // Transmission control protocol +#define IPPROTO_UDP 17 // User datagram protocol + +#define MAKE_IP_ADDR(a, b, c, d) \ + (((uint32)a << 24) | ((uint32)b << 16) | \ + ((uint32)c << 8) | (uint32)d) + +// a UDP packet header (comes after an IP header). +struct udp { + uint16 sport; // source port + uint16 dport; // destination port + uint16 ulen; // length, including udp header, not including IP header + uint16 sum; // checksum +}; + +// an ARP packet (comes after an Ethernet header). +struct arp { + uint16 hrd; // format of hardware address + uint16 pro; // format of protocol address + uint8 hln; // length of hardware address + uint8 pln; // length of protocol address + uint16 op; // operation + + char sha[ETHADDR_LEN]; // sender hardware address + uint32 sip; // sender IP address + char tha[ETHADDR_LEN]; // target hardware address + uint32 tip; // target IP address +} __attribute__((packed)); + +#define ARP_HRD_ETHER 1 // Ethernet + +enum { + ARP_OP_REQUEST = 1, // requests hw addr given protocol addr + ARP_OP_REPLY = 2, // replies with the hw addr of the protocol addr +}; + +// an DNS packet (comes after an UDP header). +struct dns { + uint16 id; // request ID + + uint8 rd: 1; // recursion desired + uint8 tc: 1; // truncated + uint8 aa: 1; // authoritive + uint8 opcode: 4; + uint8 qr: 1; // query/response + uint8 rcode: 4; // response code + uint8 cd: 1; // checking disabled + uint8 ad: 1; // authenticated data + uint8 z: 1; + uint8 ra: 1; // recursion available + + uint16 qdcount; // number of question entries + uint16 ancount; // number of resource records in answer section + uint16 nscount; // number of NS resource records in authority section + uint16 arcount; // number of resource records in additional records +} __attribute__((packed)); + +struct dns_question { + uint16 qtype; + uint16 qclass; +} __attribute__((packed)); + +#define ARECORD (0x0001) +#define QCLASS (0x0001) + +struct dns_data { + uint16 type; + uint16 class; + uint32 ttl; + uint16 len; +} __attribute__((packed)); diff --git a/kernel/pci.c b/kernel/pci.c new file mode 100644 index 0000000..5cd2102 --- /dev/null +++ b/kernel/pci.c @@ -0,0 +1,61 @@ +// +// simple PCI-Express initialization, only +// works for qemu and its e1000 card. +// + +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" + +void +pci_init() +{ + // we'll place the e1000 registers at this address. + // vm.c maps this range. + uint64 e1000_regs = 0x40000000L; + + // qemu -machine virt puts PCIe config space here. + // vm.c maps this range. + uint32 *ecam = (uint32 *) 0x30000000L; + + // look at each possible PCI device on bus 0. + for(int dev = 0; dev < 32; dev++){ + int bus = 0; + int func = 0; + int offset = 0; + uint32 off = (bus << 16) | (dev << 11) | (func << 8) | (offset); + volatile uint32 *base = ecam + off; + uint32 id = base[0]; + + // 100e:8086 is an e1000 + if(id == 0x100e8086){ + // command and status register. + // bit 0 : I/O access enable + // bit 1 : memory access enable + // bit 2 : enable mastering + base[1] = 7; + __sync_synchronize(); + + for(int i = 0; i < 6; i++){ + uint32 old = base[4+i]; + + // writing all 1's to the BAR causes it to be + // replaced with its size. + base[4+i] = 0xffffffff; + __sync_synchronize(); + + base[4+i] = old; + } + + // tell the e1000 to reveal its registers at + // physical address 0x40000000. + base[4+0] = e1000_regs; + + e1000_init((uint32*)e1000_regs); + } + } +} diff --git a/kernel/plic.c b/kernel/plic.c index 4175db9..5c9d96a 100644 --- a/kernel/plic.c +++ b/kernel/plic.c @@ -14,6 +14,13 @@ plicinit(void) // set desired IRQ priorities non-zero (otherwise disabled). *(uint32*)(PLIC + UART0_IRQ*4) = 1; *(uint32*)(PLIC + VIRTIO0_IRQ*4) = 1; + +#ifdef LAB_NET + // PCIE IRQs are 32 to 35 + for(int irq = 1; irq < 0x35; irq++){ + *(uint32*)(PLIC + irq*4) = 1; + } +#endif } void @@ -25,6 +32,11 @@ plicinithart(void) // for the uart and virtio disk. *(uint32*)PLIC_SENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO0_IRQ); +#ifdef LAB_NET + // hack to get at next 32 IRQs for e1000 + *(uint32*)(PLIC_SENABLE(hart)+4) = 0xffffffff; +#endif + // set this hart's S-mode priority threshold to 0. *(uint32*)PLIC_SPRIORITY(hart) = 0; } diff --git a/kernel/spinlock.c b/kernel/spinlock.c index 9840302..266a698 100644 --- a/kernel/spinlock.c +++ b/kernel/spinlock.c @@ -8,12 +8,52 @@ #include "proc.h" #include "defs.h" +#ifdef LAB_LOCK +#define NLOCK 500 + +static struct spinlock *locks[NLOCK]; +struct spinlock lock_locks; + +void +freelock(struct spinlock *lk) +{ + acquire(&lock_locks); + int i; + for (i = 0; i < NLOCK; i++) { + if(locks[i] == lk) { + locks[i] = 0; + break; + } + } + release(&lock_locks); +} + +static void +findslot(struct spinlock *lk) { + acquire(&lock_locks); + int i; + for (i = 0; i < NLOCK; i++) { + if(locks[i] == 0) { + locks[i] = lk; + release(&lock_locks); + return; + } + } + panic("findslot"); +} +#endif + void initlock(struct spinlock *lk, char *name) { lk->name = name; lk->locked = 0; lk->cpu = 0; +#ifdef LAB_LOCK + lk->nts = 0; + lk->n = 0; + findslot(lk); +#endif } // Acquire the lock. @@ -25,12 +65,21 @@ acquire(struct spinlock *lk) if(holding(lk)) panic("acquire"); +#ifdef LAB_LOCK + __sync_fetch_and_add(&(lk->n), 1); +#endif + // On RISC-V, sync_lock_test_and_set turns into an atomic swap: // a5 = 1 // s1 = &lk->locked // amoswap.w.aq a5, a5, (s1) - while(__sync_lock_test_and_set(&lk->locked, 1) != 0) - ; + while(__sync_lock_test_and_set(&lk->locked, 1) != 0) { +#ifdef LAB_LOCK + __sync_fetch_and_add(&(lk->nts), 1); +#else + ; +#endif + } // Tell the C compiler and the processor to not move loads or stores // past this point, to ensure that the critical section's memory @@ -108,3 +157,61 @@ pop_off(void) if(c->noff == 0 && c->intena) intr_on(); } + +// Read a shared 32-bit value without holding a lock +int +atomic_read4(int *addr) { + uint32 val; + __atomic_load(addr, &val, __ATOMIC_SEQ_CST); + return val; +} + +#ifdef LAB_LOCK +int +snprint_lock(char *buf, int sz, struct spinlock *lk) +{ + int n = 0; + if(lk->n > 0) { + n = snprintf(buf, sz, "lock: %s: #test-and-set %d #acquire() %d\n", + lk->name, lk->nts, lk->n); + } + return n; +} + +int +statslock(char *buf, int sz) { + int n; + int tot = 0; + + acquire(&lock_locks); + n = snprintf(buf, sz, "--- lock kmem/bcache stats\n"); + for(int i = 0; i < NLOCK; i++) { + if(locks[i] == 0) + break; + if(strncmp(locks[i]->name, "bcache", strlen("bcache")) == 0 || + strncmp(locks[i]->name, "kmem", strlen("kmem")) == 0) { + tot += locks[i]->nts; + n += snprint_lock(buf +n, sz-n, locks[i]); + } + } + + n += snprintf(buf+n, sz-n, "--- top 5 contended locks:\n"); + int last = 100000000; + // stupid way to compute top 5 contended locks + for(int t = 0; t < 5; t++) { + int top = 0; + for(int i = 0; i < NLOCK; i++) { + if(locks[i] == 0) + break; + if(locks[i]->nts > locks[top]->nts && locks[i]->nts < last) { + top = i; + } + } + n += snprint_lock(buf+n, sz-n, locks[top]); + last = locks[top]->nts; + } + n += snprintf(buf+n, sz-n, "tot= %d\n", tot); + release(&lock_locks); + return n; +} +#endif diff --git a/kernel/syscall.c b/kernel/syscall.c index ed65409..4aea542 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -102,6 +102,17 @@ extern uint64 sys_link(void); extern uint64 sys_mkdir(void); extern uint64 sys_close(void); +#ifdef LAB_NET +extern uint64 sys_bind(void); +extern uint64 sys_unbind(void); +extern uint64 sys_send(void); +extern uint64 sys_recv(void); +#endif +#ifdef LAB_PGTBL +extern uint64 sys_pgpte(void); +extern uint64 sys_kpgtbl(void); +#endif + // An array mapping syscall numbers from syscall.h // to the function that handles the system call. static uint64 (*syscalls[])(void) = { @@ -126,8 +137,20 @@ static uint64 (*syscalls[])(void) = { [SYS_link] sys_link, [SYS_mkdir] sys_mkdir, [SYS_close] sys_close, +#ifdef LAB_NET +[SYS_bind] sys_bind, +[SYS_unbind] sys_unbind, +[SYS_send] sys_send, +[SYS_recv] sys_recv, +#endif +#ifdef LAB_PGTBL +[SYS_pgpte] sys_pgpte, +[SYS_kpgtbl] sys_kpgtbl, +#endif }; + + void syscall(void) { diff --git a/kernel/syscall.h b/kernel/syscall.h index bc5f356..a122272 100644 --- a/kernel/syscall.h +++ b/kernel/syscall.h @@ -20,3 +20,18 @@ #define SYS_link 19 #define SYS_mkdir 20 #define SYS_close 21 + +// System calls for labs +#define SYS_trace 22 +#define SYS_sysinfo 23 +#define SYS_sigalarm 24 +#define SYS_sigreturn 25 +#define SYS_symlink 26 +#define SYS_mmap 27 +#define SYS_munmap 28 +#define SYS_bind 29 +#define SYS_unbind 30 +#define SYS_send 31 +#define SYS_recv 32 +#define SYS_pgpte 33 +#define SYS_kpgtbl 34 diff --git a/kernel/trap.c b/kernel/trap.c index d454a7d..e617a0b 100644 --- a/kernel/trap.c +++ b/kernel/trap.c @@ -68,6 +68,8 @@ usertrap(void) } else if((which_dev = devintr()) != 0){ // ok } else { + + printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid); printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval()); setkilled(p); @@ -75,6 +77,7 @@ usertrap(void) if(killed(p)) exit(-1); + // give up the CPU if this is a timer interrupt. if(which_dev == 2) @@ -196,7 +199,13 @@ devintr() uartintr(); } else if(irq == VIRTIO0_IRQ){ virtio_disk_intr(); - } else if(irq){ + } +#ifdef LAB_NET + else if(irq == E1000_IRQ){ + e1000_intr(); + } +#endif + else if(irq){ printf("unexpected interrupt irq=%d\n", irq); } diff --git a/kernel/vm.c b/kernel/vm.c index 62421a2..7f388fe 100644 --- a/kernel/vm.c +++ b/kernel/vm.c @@ -4,6 +4,8 @@ #include "elf.h" #include "riscv.h" #include "defs.h" +#include "spinlock.h" +#include "proc.h" #include "fs.h" /* @@ -30,6 +32,14 @@ kvmmake(void) // virtio mmio disk interface kvmmap(kpgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W); +#ifdef LAB_NET + // PCI-E ECAM (configuration space), for pci.c + kvmmap(kpgtbl, 0x30000000L, 0x30000000L, 0x10000000, PTE_R | PTE_W); + + // pci.c maps the e1000's registers here. + kvmmap(kpgtbl, 0x40000000L, 0x40000000L, 0x20000, PTE_R | PTE_W); +#endif + // PLIC kvmmap(kpgtbl, PLIC, PLIC, 0x4000000, PTE_R | PTE_W); @@ -92,6 +102,11 @@ walk(pagetable_t pagetable, uint64 va, int alloc) pte_t *pte = &pagetable[PX(level, va)]; if(*pte & PTE_V) { pagetable = (pagetable_t)PTE2PA(*pte); +#ifdef LAB_PGTBL + if(PTE_LEAF(*pte)) { + return pte; + } +#endif } else { if(!alloc || (pagetable = (pde_t*)kalloc()) == 0) return 0; @@ -125,6 +140,7 @@ walkaddr(pagetable_t pagetable, uint64 va) return pa; } + // add a mapping to the kernel page table. // only used when booting. // does not flush TLB or enable paging. @@ -179,15 +195,19 @@ uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free) { uint64 a; pte_t *pte; + int sz; if((va % PGSIZE) != 0) panic("uvmunmap: not aligned"); - for(a = va; a < va + npages*PGSIZE; a += PGSIZE){ + for(a = va; a < va + npages*PGSIZE; a += sz){ + sz = PGSIZE; if((pte = walk(pagetable, a, 0)) == 0) panic("uvmunmap: walk"); - if((*pte & PTE_V) == 0) + if((*pte & PTE_V) == 0) { + printf("va=%ld pte=%ld\n", a, *pte); panic("uvmunmap: not mapped"); + } if(PTE_FLAGS(*pte) == PTE_V) panic("uvmunmap: not a leaf"); if(do_free){ @@ -227,6 +247,7 @@ uvmfirst(pagetable_t pagetable, uchar *src, uint sz) memmove(mem, src, sz); } + // Allocate PTEs and physical memory to grow process from oldsz to // newsz, which need not be page aligned. Returns new size or 0 on error. uint64 @@ -234,19 +255,23 @@ uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz, int xperm) { char *mem; uint64 a; + int sz; if(newsz < oldsz) return oldsz; oldsz = PGROUNDUP(oldsz); - for(a = oldsz; a < newsz; a += PGSIZE){ + for(a = oldsz; a < newsz; a += sz){ + sz = PGSIZE; mem = kalloc(); if(mem == 0){ uvmdealloc(pagetable, a, oldsz); return 0; } - memset(mem, 0, PGSIZE); - if(mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_R|PTE_U|xperm) != 0){ +#ifndef LAB_SYSCALL + memset(mem, 0, sz); +#endif + if(mappages(pagetable, a, sz, (uint64)mem, PTE_R|PTE_U|xperm) != 0){ kfree(mem); uvmdealloc(pagetable, a, oldsz); return 0; @@ -316,8 +341,11 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) uint64 pa, i; uint flags; char *mem; + int szinc; - for(i = 0; i < sz; i += PGSIZE){ + for(i = 0; i < sz; i += szinc){ + szinc = PGSIZE; + szinc = PGSIZE; if((pte = walk(old, i, 0)) == 0) panic("uvmcopy: pte should exist"); if((*pte & PTE_V) == 0) @@ -363,13 +391,21 @@ copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) while(len > 0){ va0 = PGROUNDDOWN(dstva); - if(va0 >= MAXVA) + if (va0 >= MAXVA) return -1; - pte = walk(pagetable, va0, 0); - if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0 || - (*pte & PTE_W) == 0) + if((pte = walk(pagetable, va0, 0)) == 0) { + // printf("copyout: pte should exist 0x%x %d\n", dstva, len); + return -1; + } + + + // forbid copyout over read-only user text pages. + if((*pte & PTE_W) == 0) + return -1; + + pa0 = walkaddr(pagetable, va0); + if(pa0 == 0) return -1; - pa0 = PTE2PA(*pte); n = PGSIZE - (dstva - va0); if(n > len) n = len; @@ -389,7 +425,7 @@ int copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len) { uint64 n, va0, pa0; - + while(len > 0){ va0 = PGROUNDDOWN(srcva); pa0 = walkaddr(pagetable, va0); @@ -449,3 +485,20 @@ copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max) return -1; } } + + +#ifdef LAB_PGTBL +void +vmprint(pagetable_t pagetable) { + // your code here +} +#endif + + + +#ifdef LAB_PGTBL +pte_t* +pgpte(pagetable_t pagetable, uint64 va) { + return walk(pagetable, va, 0); +} +#endif diff --git a/nettest.py b/nettest.py new file mode 100755 index 0000000..b0357fa --- /dev/null +++ b/nettest.py @@ -0,0 +1,169 @@ +#!/usr/bin/env python3 + +# +# net tests +# to be used with user/nettest.c +# + +import socket +import sys +import time +import os + +# qemu listens for packets sent to FWDPORT, +# and re-writes them so they arrive in +# xv6 with destination port 2000. +FWDPORT1 = (os.getuid() % 5000) + 25999 +FWDPORT2 = (os.getuid() % 5000) + 30999 + +# xv6's nettest.c tx sends to SERVERPORT. +SERVERPORT = (os.getuid() % 5000) + 25099 + +def usage(): + sys.stderr.write("Usage: nettest.py txone\n") + sys.stderr.write(" nettest.py rxone\n") + sys.stderr.write(" nettest.py rx\n") + sys.stderr.write(" nettest.py rx2\n") + sys.stderr.write(" nettest.py rxburst\n") + sys.stderr.write(" nettest.py tx\n") + sys.stderr.write(" nettest.py ping\n") + sys.stderr.write(" nettest.py grade\n") + sys.exit(1) + +if len(sys.argv) != 2: + usage() + +if sys.argv[1] == "txone": + # + # listen for a single UDP packet sent by xv6's nettest txone. + # nettest.py must be started before xv6's nettest txone. + # + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.bind(('127.0.0.1', SERVERPORT)) + print("tx: listening for a UDP packet") + buf0, raddr0 = sock.recvfrom(4096) + if buf0 == b'txone': + print("txone: OK") + else: + print("txone: unexpected payload %s" % (buf0)) +elif sys.argv[1] == "rxone": + # + # send a single UDP packet to xv6 to test e1000_recv(). + # should result in arp_rx() printing + # arp_rx: received an ARP packet + # and ip_rx() printing + # ip_rx: received an IP packet + # + print("txone: sending one UDP packet") + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.sendto(b'xyz', ("127.0.0.1", FWDPORT1)) +elif sys.argv[1] == "rx": + # + # test the xv6 receive path by sending a slow + # stream of UDP packets, which should appear + # on port 2000. + # + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + i = 0 + while True: + txt = "packet %d" % (i) + sys.stderr.write("%s\n" % txt) + buf = txt.encode('ascii', 'ignore') + sock.sendto(buf, ("127.0.0.1", FWDPORT1)) + time.sleep(1) + i += 1 +elif sys.argv[1] == "rx2": + # + # send to two different UDP ports, to see + # if xv6 keeps them separate. + # + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + i = 0 + while True: + txt = "one %d" % (i) + sys.stderr.write("%s\n" % txt) + buf = txt.encode('ascii', 'ignore') + sock.sendto(buf, ("127.0.0.1", FWDPORT1)) + + txt = "two %d" % (i) + sys.stderr.write("%s\n" % txt) + buf = txt.encode('ascii', 'ignore') + sock.sendto(buf, ("127.0.0.1", FWDPORT2)) + + time.sleep(1) + i += 1 +elif sys.argv[1] == "rxburst": + # + # send a big burst of packets to 2001, then + # a packet to 2000. + # + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + i = 0 + while True: + + for ii in range(0, 32): + txt = "packet %d" % (i) + # sys.stderr.write("%s\n" % txt) + buf = txt.encode('ascii', 'ignore') + sock.sendto(buf, ("127.0.0.1", FWDPORT2)) + + txt = "packet %d" % (i) + sys.stderr.write("%s\n" % txt) + buf = txt.encode('ascii', 'ignore') + sock.sendto(buf, ("127.0.0.1", FWDPORT1)) + + time.sleep(1) + i += 1 +elif sys.argv[1] == "tx": + # + # listen for UDP packets sent by xv6's nettest tx. + # nettest.py must be started before xv6's nettest tx. + # + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.bind(('127.0.0.1', SERVERPORT)) + print("tx: listening for UDP packets") + buf0, raddr0 = sock.recvfrom(4096) + buf1, raddr1 = sock.recvfrom(4096) + if buf0 == b't 0' and buf1 == b't 1': + print("tx: OK") + else: + print("tx: unexpected packets %s and %s" % (buf0, buf1)) +elif sys.argv[1] == "ping": + # + # listen for UDP packets sent by xv6's nettest ping, + # and send them back. + # nettest.py must be started before xv6's nettest. + # + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.bind(('127.0.0.1', SERVERPORT)) + print("ping: listening for UDP packets") + while True: + buf, raddr = sock.recvfrom(4096) + sock.sendto(buf, raddr) +elif sys.argv[1] == "grade": + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.bind(('127.0.0.1', SERVERPORT)) + + # first, listen for a single UDP packet sent by xv6, + # in order to test only e1000_transmit(), in a situation + # where perhaps e1000_recv() has not yet been implemented. + buf, raddr = sock.recvfrom(4096) + if buf == b'txone': + print("txone: OK") + else: + print("txone: received incorrect payload %s" % (buf)) + sys.stdout.flush() + sys.stderr.flush() + + # second, send a single UDP packet, to test + # e1000_recv() -- received by user/nettest.c's rxone(). + print("rxone: sending one UDP packet") + sock1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock1.sendto(b'rxone', ("127.0.0.1", FWDPORT2)) + + # third, act as a ping reflector. + while True: + buf, raddr = sock.recvfrom(4096) + sock.sendto(buf, raddr) +else: + usage() diff --git a/user/nettest.c b/user/nettest.c new file mode 100644 index 0000000..2f4272f --- /dev/null +++ b/user/nettest.c @@ -0,0 +1,878 @@ +// +// network tests +// to be used with nettest.py (run outside of qemu) +// + +#include "kernel/types.h" +#include "kernel/net.h" +#include "kernel/stat.h" +#include "user/user.h" + +// +// send a single UDP packet (but don't recv() the reply). +// python3 nettest.py txone can be used to wait for +// this packet, and you can also see what +// happened with tcpdump -XXnr packets.pcap +// +void +txone() +{ + printf("txone: sending one packet\n"); + uint32 dst = 0x0A000202; // 10.0.2.2 + int dport = NET_TESTS_PORT; + char buf[5]; + buf[0] = 't'; + buf[1] = 'x'; + buf[2] = 'o'; + buf[3] = 'n'; + buf[4] = 'e'; + if(send(2003, dst, dport, buf, 5) < 0){ + printf("txone: send() failed\n"); + } +} + +// +// test just receive. +// outside of qemu, run +// ./nettest.py rx +// +int +rx(char *name) +{ + bind(2000); + + int lastseq = -1; + int ok = 0; + + while(ok < 4){ + char ibuf[128]; + uint32 src; + uint16 sport; + int cc = recv(2000, &src, &sport, ibuf, sizeof(ibuf)-1); + if(cc < 0){ + fprintf(2, "nettest %s: recv() failed\n", name); + return 0; + } + + if(src != 0x0A000202){ // 10.0.2.2 + printf("wrong ip src %x\n", src); + return 0; + } + + if(cc < strlen("packet 1")){ + printf("len %d too short\n", cc); + return 0; + } + + if(cc > strlen("packet xxxxxx")){ + printf("len %d too long\n", cc); + return 0; + } + + if(memcmp(ibuf, "packet ", strlen("packet ")) != 0){ + printf("packet doesn't start with packet\n"); + return 0; + } + + ibuf[cc] = '\0'; +#define isdigit(x) ((x) >= '0' && (x) <= '9') + if(!isdigit(ibuf[7])){ + printf("packet doesn't contain a number\n"); + return 0; + } + for(int i = 7; i < cc; i++){ + if(!isdigit(ibuf[i])){ + printf("packet contains non-digits in the number\n"); + return 0; + } + } + int seq = ibuf[7] - '0'; + if(isdigit(ibuf[8])){ + seq *= 10; + seq += ibuf[8] - '0'; + if(isdigit(ibuf[9])){ + seq *= 10; + seq += ibuf[9] - '0'; + } + } + + if(lastseq != -1){ + if(seq != lastseq + 1){ + printf("got seq %d, expecting %d\n", seq, lastseq + 1); + return 0; + } + } + + lastseq = seq; + + ok += 1; + } + + printf("%s: OK\n", name); + + return 1; +} + +// +// test receive on two different ports, interleaved. +// outside of qemu, run +// ./nettest.py rx2 +// +int +rx2() +{ + bind(2000); + bind(2001); + + for(int i = 0; i < 3; i++){ + char ibuf[128]; + uint32 src; + uint16 sport; + int cc = recv(2000, &src, &sport, ibuf, sizeof(ibuf)-1); + if(cc < 0){ + fprintf(2, "nettest rx2: recv() failed\n"); + return 0; + } + + if(src != 0x0A000202){ // 10.0.2.2 + printf("wrong ip src %x\n", src); + return 0; + } + + if(cc < strlen("one 1")){ + printf("len %d too short\n", cc); + return 0; + } + + if(cc > strlen("one xxxxxx")){ + printf("len %d too long\n", cc); + return 0; + } + + if(memcmp(ibuf, "one ", strlen("one ")) != 0){ + printf("packet doesn't start with one\n"); + return 0; + } + } + + for(int i = 0; i < 3; i++){ + char ibuf[128]; + uint32 src; + uint16 sport; + int cc = recv(2001, &src, &sport, ibuf, sizeof(ibuf)-1); + if(cc < 0){ + fprintf(2, "nettest rx2: recv() failed\n"); + return 0; + } + + if(src != 0x0A000202){ // 10.0.2.2 + printf("wrong ip src %x\n", src); + return 0; + } + + if(cc < strlen("one 1")){ + printf("len %d too short\n", cc); + return 0; + } + + if(cc > strlen("one xxxxxx")){ + printf("len %d too long\n", cc); + return 0; + } + + if(memcmp(ibuf, "two ", strlen("two ")) != 0){ + printf("packet doesn't start with two\n"); + return 0; + } + } + + for(int i = 0; i < 3; i++){ + char ibuf[128]; + uint32 src; + uint16 sport; + int cc = recv(2000, &src, &sport, ibuf, sizeof(ibuf)-1); + if(cc < 0){ + fprintf(2, "nettest rx2: recv() failed\n"); + return 0; + } + + if(src != 0x0A000202){ // 10.0.2.2 + printf("wrong ip src %x\n", src); + return 0; + } + + if(cc < strlen("one 1")){ + printf("len %d too short\n", cc); + return 0; + } + + if(cc > strlen("one xxxxxx")){ + printf("len %d too long\n", cc); + return 0; + } + + if(memcmp(ibuf, "one ", strlen("one ")) != 0){ + printf("packet doesn't start with one\n"); + return 0; + } + } + + printf("rx2: OK\n"); + + return 1; +} + +// +// send some UDP packets to nettest.py tx. +// +int +tx() +{ + for(int ii = 0; ii < 5; ii++){ + uint32 dst = 0x0A000202; // 10.0.2.2 + int dport = NET_TESTS_PORT; + char buf[3]; + buf[0] = 't'; + buf[1] = ' '; + buf[2] = '0' + ii; + if(send(2000, dst, dport, buf, 3) < 0){ + printf("send() failed\n"); + return 0; + } + sleep(10); + } + + // can't actually tell if the packets arrived. + return 1; +} + +// +// send just one UDP packets to nettest.py ping, +// expect a reply. +// nettest.py ping must be started first. +// +int +ping0() +{ + printf("ping0: starting\n"); + + bind(2004); + + uint32 dst = 0x0A000202; // 10.0.2.2 + int dport = NET_TESTS_PORT; + char buf[5]; + memcpy(buf, "ping0", sizeof(buf)); + if(send(2004, dst, dport, buf, sizeof(buf)) < 0){ + printf("ping0: send() failed\n"); + return 0; + } + + char ibuf[128]; + uint32 src = 0; + uint16 sport = 0; + memset(ibuf, 0, sizeof(ibuf)); + int cc = recv(2004, &src, &sport, ibuf, sizeof(ibuf)-1); + if(cc < 0){ + fprintf(2, "ping0: recv() failed\n"); + return 0; + } + + if(src != 0x0A000202){ // 10.0.2.2 + printf("ping0: wrong ip src %x, expecting %x\n", src, 0x0A000202); + return 0; + } + + if(sport != NET_TESTS_PORT){ + printf("ping0: wrong sport %d, expecting %d\n", sport, NET_TESTS_PORT); + return 0; + } + + if(memcmp(buf, ibuf, sizeof(buf)) != 0){ + printf("ping0: wrong content\n"); + return 0; + } + + if(cc != sizeof(buf)){ + printf("ping0: wrong length %d, expecting %ld\n", cc, sizeof(buf)); + return 0; + } + + printf("ping0: OK\n"); + + return 1; +} + +// +// send many UDP packets to nettest.py ping, +// expect a reply to each. +// nettest.py ping must be started first. +// +int +ping1() +{ + printf("ping1: starting\n"); + + bind(2005); + + for(int ii = 0; ii < 20; ii++){ + uint32 dst = 0x0A000202; // 10.0.2.2 + int dport = NET_TESTS_PORT; + char buf[3]; + buf[0] = 'p'; + buf[1] = ' '; + buf[2] = '0' + ii; + if(send(2005, dst, dport, buf, 3) < 0){ + printf("ping1: send() failed\n"); + return 0; + } + + char ibuf[128]; + uint32 src = 0; + uint16 sport = 0; + memset(ibuf, 0, sizeof(ibuf)); + int cc = recv(2005, &src, &sport, ibuf, sizeof(ibuf)-1); + if(cc < 0){ + fprintf(2, "ping1: recv() failed\n"); + return 0; + } + + if(src != 0x0A000202){ // 10.0.2.2 + printf("ping1: wrong ip src %x, expecting %x\n", src, 0x0A000202); + return 0; + } + + if(sport != NET_TESTS_PORT){ + printf("ping1: wrong sport %d, expecting %d\n", sport, NET_TESTS_PORT); + return 0; + } + + if(memcmp(buf, ibuf, 3) != 0){ + printf("ping1: wrong content\n"); + return 0; + } + + if(cc != 3){ + printf("ping1: wrong length %d, expecting 3\n", cc); + return 0; + } + } + + printf("ping1: OK\n"); + + return 1; +} + +// +// send UDP packets from two different ports to nettest.py ping, +// expect a reply to each to appear on the correct port. +// nettest.py ping must be started first. +// +int +ping2() +{ + printf("ping2: starting\n"); + + bind(2006); + bind(2007); + + for(int ii = 0; ii < 5; ii++){ + for(int port = 2006; port <= 2007; port++){ + uint32 dst = 0x0A000202; // 10.0.2.2 + int dport = NET_TESTS_PORT; + char buf[4]; + buf[0] = 'p'; + buf[1] = ' '; + buf[2] = (port == 2006 ? 'a' : 'A') + ii; + buf[3] = '!'; + if(send(port, dst, dport, buf, 4) < 0){ + printf("ping2: send() failed\n"); + return 0; + } + } + } + + for(int port = 2006; port <= 2007; port++){ + for(int ii = 0; ii < 5; ii++){ + char ibuf[128]; + uint32 src = 0; + uint16 sport = 0; + memset(ibuf, 0, sizeof(ibuf)); + int cc = recv(port, &src, &sport, ibuf, sizeof(ibuf)-1); + if(cc < 0){ + fprintf(2, "ping2: recv() failed\n"); + return 0; + } + + if(src != 0x0A000202){ // 10.0.2.2 + printf("ping2: wrong ip src %x\n", src); + return 0; + } + + if(sport != NET_TESTS_PORT){ + printf("ping2: wrong sport %d\n", sport); + return 0; + } + + if(cc != 4){ + printf("ping2: wrong length %d\n", cc); + return 0; + } + + // printf("port=%d ii=%d: %c%c%c\n", port, ii, ibuf[0], ibuf[1], ibuf[2]); + + char buf[4]; + buf[0] = 'p'; + buf[1] = ' '; + buf[2] = (port == 2006 ? 'a' : 'A') + ii; + buf[3] = '!'; + + if(memcmp(buf, ibuf, 3) != 0){ + // possibly recv() sees packets out of order. + printf("ping2: wrong content\n"); + return 0; + } + } + } + + printf("ping2: OK\n"); + + return 1; +} + +// +// send a big burst of packets from ports 2008 and 2010, +// causing drops, +// bracketed by two packets from port 2009. +// check that the two packets can be recv()'d on port 2009. +// check that port 2008 had a finite queue length (dropped some). +// nettest.py ping must be started first. +// +int +ping3() +{ + printf("ping3: starting\n"); + + bind(2008); + bind(2009); + + // + // send one packet on 2009. + // + { + uint32 dst = 0x0A000202; // 10.0.2.2 + int dport = NET_TESTS_PORT; + char buf[4]; + buf[0] = 'p'; + buf[1] = ' '; + buf[2] = 'A'; + buf[3] = '!'; + if(send(2009, dst, dport, buf, 4) < 0){ + printf("ping3: send() failed\n"); + return 0; + } + } + sleep(1); + + // + // send so many packets from 2008 and 2010 that some of the + // replies must be dropped due to the requirement + // for finite maximum queueing. + // + for(int ii = 0; ii < 257; ii++){ + uint32 dst = 0x0A000202; // 10.0.2.2 + int dport = NET_TESTS_PORT; + char buf[4]; + buf[0] = 'p'; + buf[1] = ' '; + buf[2] = 'a' + ii; + buf[3] = '!'; + int port = 2008 + (ii % 2) * 2; + if(send(port, dst, dport, buf, 4) < 0){ + printf("ping3: send() failed\n"); + return 0; + } + } + sleep(1); + + // + // send another packet from 2009. + // + { + uint32 dst = 0x0A000202; // 10.0.2.2 + int dport = NET_TESTS_PORT; + char buf[4]; + buf[0] = 'p'; + buf[1] = ' '; + buf[2] = 'B'; + buf[3] = '!'; + if(send(2009, dst, dport, buf, 4) < 0){ + printf("ping3: send() failed\n"); + return 0; + } + } + + // + // did both reply packets for 2009 arrive? + // + for(int ii = 0; ii < 2; ii++){ + char ibuf[128]; + uint32 src = 0; + uint16 sport = 0; + memset(ibuf, 0, sizeof(ibuf)); + int cc = recv(2009, &src, &sport, ibuf, sizeof(ibuf)-1); + if(cc < 0){ + fprintf(2, "ping3: recv() failed\n"); + return 0; + } + + if(src != 0x0A000202){ // 10.0.2.2 + printf("ping3: wrong ip src %x\n", src); + return 0; + } + + if(sport != NET_TESTS_PORT){ + printf("ping3: wrong sport %d\n", sport); + return 0; + } + + if(cc != 4){ + printf("ping3: wrong length %d\n", cc); + return 0; + } + + // printf("port=%d ii=%d: %c%c%c\n", port, ii, ibuf[0], ibuf[1], ibuf[2]); + + char buf[4]; + buf[0] = 'p'; + buf[1] = ' '; + buf[2] = 'A' + ii; + buf[3] = '!'; + + if(memcmp(buf, ibuf, 3) != 0){ + // possibly recv() sees packets out of order. + // possibly the burst on 2008 caused 2009's + // packets to be dropped. + printf("ping3: wrong content\n"); + return 0; + } + } + + // + // now count how many replies were queued for 2008. + // + int fds[2]; + pipe(fds); + int pid = fork(); + if(pid == 0){ + close(fds[0]); + write(fds[1], ":", 1); // ensure parent's read() doesn't block + while(1){ + char ibuf[128]; + uint32 src = 0; + uint16 sport = 0; + memset(ibuf, 0, sizeof(ibuf)); + int cc = recv(2008, &src, &sport, ibuf, sizeof(ibuf)-1); + if(cc < 0){ + printf("ping3: recv failed\n"); + break; + } + write(fds[1], "x", 1); + } + exit(0); + } + close(fds[1]); + + sleep(5); + static char nbuf[512]; + int n = read(fds[0], nbuf, sizeof(nbuf)); + close(fds[0]); + kill(pid); + + n -= 1; // the ":" + if(n > 16){ + printf("ping3: too many packets (%d) were queued on a UDP port\n", n); + return 0; + } + + printf("ping3: OK\n"); + + return 1; +} + +// Encode a DNS name +void +encode_qname(char *qn, char *host) +{ + char *l = host; + + for(char *c = host; c < host+strlen(host)+1; c++) { + if(*c == '.') { + *qn++ = (char) (c-l); + for(char *d = l; d < c; d++) { + *qn++ = *d; + } + l = c+1; // skip . + } + } + *qn = '\0'; +} + +// Decode a DNS name +void +decode_qname(char *qn, int max) +{ + char *qnMax = qn + max; + while(1){ + if(qn >= qnMax){ + printf("invalid DNS reply\n"); + exit(1); + } + int l = *qn; + if(l == 0) + break; + for(int i = 0; i < l; i++) { + *qn = *(qn+1); + qn++; + } + *qn++ = '.'; + } +} + +// Make a DNS request +int +dns_req(uint8 *obuf) +{ + int len = 0; + + struct dns *hdr = (struct dns *) obuf; + hdr->id = htons(6828); + hdr->rd = 1; + hdr->qdcount = htons(1); + + len += sizeof(struct dns); + + // qname part of question + char *qname = (char *) (obuf + sizeof(struct dns)); + char *s = "pdos.csail.mit.edu."; + encode_qname(qname, s); + len += strlen(qname) + 1; + + // constants part of question + struct dns_question *h = (struct dns_question *) (qname+strlen(qname)+1); + h->qtype = htons(0x1); + h->qclass = htons(0x1); + + len += sizeof(struct dns_question); + return len; +} + +// Process DNS response +int +dns_rep(uint8 *ibuf, int cc) +{ + struct dns *hdr = (struct dns *) ibuf; + int len; + char *qname = 0; + int record = 0; + + if(cc < sizeof(struct dns)){ + printf("DNS reply too short\n"); + return 0; + } + + if(!hdr->qr) { + printf("Not a DNS reply for %d\n", ntohs(hdr->id)); + return 0; + } + + if(hdr->id != htons(6828)){ + printf("DNS wrong id: %d\n", ntohs(hdr->id)); + return 0; + } + + if(hdr->rcode != 0) { + printf("DNS rcode error: %x\n", hdr->rcode); + return 0; + } + + //printf("qdcount: %x\n", ntohs(hdr->qdcount)); + //printf("ancount: %x\n", ntohs(hdr->ancount)); + //printf("nscount: %x\n", ntohs(hdr->nscount)); + //printf("arcount: %x\n", ntohs(hdr->arcount)); + + len = sizeof(struct dns); + + for(int i =0; i < ntohs(hdr->qdcount); i++) { + char *qn = (char *) (ibuf+len); + qname = qn; + decode_qname(qn, cc - len); + len += strlen(qn)+1; + len += sizeof(struct dns_question); + } + + for(int i = 0; i < ntohs(hdr->ancount); i++) { + if(len >= cc){ + printf("dns: invalid DNS reply\n"); + return 0; + } + + char *qn = (char *) (ibuf+len); + + if((int) qn[0] > 63) { // compression? + qn = (char *)(ibuf+qn[1]); + len += 2; + } else { + decode_qname(qn, cc - len); + len += strlen(qn)+1; + } + + struct dns_data *d = (struct dns_data *) (ibuf+len); + len += sizeof(struct dns_data); + //printf("type %d ttl %d len %d\n", ntohs(d->type), ntohl(d->ttl), ntohs(d->len)); + if(ntohs(d->type) == ARECORD && ntohs(d->len) == 4) { + record = 1; + printf("DNS arecord for %s is ", qname ? qname : "" ); + uint8 *ip = (ibuf+len); + printf("%d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]); + if(ip[0] != 128 || ip[1] != 52 || ip[2] != 129 || ip[3] != 126) { + printf("dns: wrong ip address"); + return 0; + } + len += 4; + } + } + + // needed for DNS servers with EDNS support + for(int i = 0; i < ntohs(hdr->arcount); i++) { + char *qn = (char *) (ibuf+len); + if(*qn != 0) { + printf("dns: invalid name for EDNS\n"); + return 0; + } + len += 1; + + struct dns_data *d = (struct dns_data *) (ibuf+len); + len += sizeof(struct dns_data); + if(ntohs(d->type) != 41) { + printf("dns: invalid type for EDNS\n"); + return 0; + } + len += ntohs(d->len); + } + + if(len != cc) { + printf("dns: processed %d data bytes but received %d\n", len, cc); + return 0; + } + if(!record) { + printf("dns: didn't receive an arecord\n"); + return 0; + } + + return 1; +} + +int +dns() +{ + #define N 1000 + uint8 obuf[N]; + uint8 ibuf[N]; + uint32 dst; + int len; + + printf("dns: starting\n"); + + memset(obuf, 0, N); + memset(ibuf, 0, N); + + // 8.8.8.8: google's name server + dst = (8 << 24) | (8 << 16) | (8 << 8) | (8 << 0); + + len = dns_req(obuf); + + bind(10000); + + if(send(10000, dst, 53, (char*)obuf, len) < 0){ + fprintf(2, "dns: send() failed\n"); + return 0; + } + + uint32 src; + uint16 sport; + int cc = recv(10000, &src, &sport, (char*)ibuf, sizeof(ibuf)); + if(cc < 0){ + fprintf(2, "dns: recv() failed\n"); + return 0; + } + + if(dns_rep(ibuf, cc)){ + printf("dns: OK\n"); + return 1; + } else { + return 0; + } +} + +void +usage() +{ + printf("Usage: nettest txone\n"); + printf(" nettest tx\n"); + printf(" nettest rx\n"); + printf(" nettest rx2\n"); + printf(" nettest rxburst\n"); + printf(" nettest ping1\n"); + printf(" nettest ping2\n"); + printf(" nettest ping3\n"); + printf(" nettest dns\n"); + printf(" nettest grade\n"); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + if(argc != 2) + usage(); + + if(strcmp(argv[1], "txone") == 0){ + txone(); + } else if(strcmp(argv[1], "rx") == 0 || strcmp(argv[1], "rxburst") == 0){ + rx(argv[1]); + } else if(strcmp(argv[1], "rx2") == 0){ + rx2(); + } else if(strcmp(argv[1], "tx") == 0){ + tx(); + } else if(strcmp(argv[1], "ping0") == 0){ + ping0(); + } else if(strcmp(argv[1], "ping1") == 0){ + ping1(); + } else if(strcmp(argv[1], "ping2") == 0){ + ping2(); + } else if(strcmp(argv[1], "ping3") == 0){ + ping3(); + } else if(strcmp(argv[1], "grade") == 0){ + // + // "python3 nettest.py grade" must already be running... + // + txone(); + sleep(2); + ping0(); + sleep(2); + ping1(); + sleep(2); + ping2(); + sleep(2); + ping3(); + sleep(2); + dns(); + sleep(2); + } else if(strcmp(argv[1], "dns") == 0){ + dns(); + } else { + usage(); + } + + exit(0); +} diff --git a/user/pingpong.c b/user/pingpong.c new file mode 100644 index 0000000..6ed12e7 --- /dev/null +++ b/user/pingpong.c @@ -0,0 +1,52 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +#define N 5 +char buf[N]; + +void +pong(int *parent_to_child, int *child_to_parent) { + if (read(parent_to_child[0], buf, N) < 0) { + printf("read failed\n"); + } + printf("%d: received %s\n", getpid(), buf); + if (write(child_to_parent[1], "pong", 4) != 4) { + printf("write failed\n"); + } +} + +void +ping(int *parent_to_child, int *child_to_parent) { + + if (write(parent_to_child[1], "ping", 4) != 4) { + printf("write failed\n"); + } + if (read(child_to_parent[0], buf, N) < 0) { + printf("read failed\n"); + } + printf("%d: received %s\n", getpid(), buf); +} + +int +main(int argc, char *argv[]) +{ + int parent_to_child[2]; + int child_to_parent[2]; + + int pid; + + if (pipe(parent_to_child) < 0 || pipe(child_to_parent) < 0) { + printf("pipe failed\n"); + } + if ((pid = fork()) < 0) { + printf("fork failed\n"); + } + if (pid == 0) { + pong(parent_to_child, child_to_parent); + } else { + ping(parent_to_child, child_to_parent); + } + + exit(0); +} diff --git a/user/user.h b/user/user.h index f16fe27..0e9bd4a 100644 --- a/user/user.h +++ b/user/user.h @@ -1,3 +1,7 @@ +#ifdef LAB_MMAP +typedef unsigned long size_t; +typedef long int off_t; +#endif struct stat; // system calls @@ -22,6 +26,17 @@ int getpid(void); char* sbrk(int); int sleep(int); int uptime(void); +#ifdef LAB_NET +int bind(uint16); +int unbind(uint16); +int send(uint16, uint32, uint16, char *, uint32); +int recv(uint16, uint32*, uint16*, char *, uint32); +#endif +#ifdef LAB_PGTBL +int ugetpid(void); +uint64 pgpte(void*); +void kpgtbl(void); +#endif // ulib.c int stat(const char*, struct stat*); @@ -37,6 +52,9 @@ void* memset(void*, int, uint); int atoi(const char*); int memcmp(const void *, const void *, uint); void *memcpy(void *, const void *, uint); +#ifdef LAB_LOCK +int statistics(void*, int); +#endif // umalloc.c void* malloc(uint); diff --git a/user/usys.pl b/user/usys.pl index 01e426e..2c19fa6 100755 --- a/user/usys.pl +++ b/user/usys.pl @@ -36,3 +36,9 @@ entry("getpid"); entry("sbrk"); entry("sleep"); entry("uptime"); +entry("bind"); +entry("unbind"); +entry("send"); +entry("recv"); +entry("pgpte"); +entry("kpgtbl");