Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
326 lines
11 KiB
C
326 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
//
|
|
// netecho -- a network, in a process.
|
|
//
|
|
// docs/architecture/ldls.md, "The class bridge". `/dev/netdev` lets a process
|
|
// be a network interface; this is the program that proves it, and it is
|
|
// deliberately *not* a driver.
|
|
// It has no hardware, claims none, and answers every frame itself:
|
|
//
|
|
// DHCP so the interface gets an address at all -- lwIP asks the moment
|
|
// the link comes up, and an interface with no address cannot be
|
|
// pinged, so this is not optional
|
|
// ARP so the stack can find us
|
|
// ICMP so `ping 10.9.0.1` answers
|
|
//
|
|
// Why a fake network and not the Linux driver straight away: the two halves
|
|
// fail differently. If `ping` does not work with this, the bridge is wrong.
|
|
// If it works with this and not with a driver, the driver's side is wrong. A
|
|
// test that can only fail in one lump tells you nothing about which half.
|
|
//
|
|
// Everything here is built by hand from the RFCs -- Ethernet II framing,
|
|
// ARP (RFC 826), IPv4 (RFC 791), ICMP (RFC 792), DHCP (RFC 2131). All
|
|
// published, all specifications, which is docs/licensing.md §5.
|
|
|
|
#include "core/types.h"
|
|
#include "uapi/user.h"
|
|
#include "uapi/fcntl.h"
|
|
#include "net/netuser.h"
|
|
|
|
// Us, and the address we hand out. Numbers chosen not to collide with QEMU's
|
|
// 10.0.2.0/24 user network, so both interfaces can be up at once.
|
|
#define MY_IP 0x0A090001u /* 10.9.0.1, the "router" */
|
|
#define PEER_IP 0x0A090002u /* 10.9.0.2, offered to SchOS */
|
|
#define NETMASK 0xFFFFFF00u
|
|
|
|
static const unsigned char my_mac[6] = { 0x02, 0x4c, 0x44, 0x4c, 0x53, 0x01 };
|
|
|
|
// --- output, since lib/ has no printf ---------------------------------------
|
|
|
|
static void
|
|
puts_(const char *s)
|
|
{
|
|
int n = 0;
|
|
while (s[n]) n++;
|
|
write(1, (void *)s, n);
|
|
}
|
|
|
|
static void
|
|
putn(long long v)
|
|
{
|
|
char b[24];
|
|
int i = 0;
|
|
if (v < 0) { write(1, "-", 1); v = -v; }
|
|
if (v == 0) { write(1, "0", 1); return; }
|
|
while (v > 0) { b[i++] = (char)('0' + (v % 10)); v /= 10; }
|
|
while (i-- > 0) write(1, &b[i], 1);
|
|
}
|
|
|
|
static void
|
|
putip(unsigned int ip)
|
|
{
|
|
putn((ip >> 24) & 0xff); puts_(".");
|
|
putn((ip >> 16) & 0xff); puts_(".");
|
|
putn((ip >> 8) & 0xff); puts_(".");
|
|
putn(ip & 0xff);
|
|
}
|
|
|
|
// --- reading and writing the wire -------------------------------------------
|
|
|
|
static unsigned short
|
|
rd16(const unsigned char *p) { return (unsigned short)((p[0] << 8) | p[1]); }
|
|
|
|
static unsigned int
|
|
rd32(const unsigned char *p)
|
|
{
|
|
return ((unsigned int)p[0] << 24) | ((unsigned int)p[1] << 16) |
|
|
((unsigned int)p[2] << 8) | p[3];
|
|
}
|
|
|
|
static void
|
|
wr16(unsigned char *p, unsigned short v) { p[0] = (unsigned char)(v >> 8); p[1] = (unsigned char)v; }
|
|
|
|
static void
|
|
wr32(unsigned char *p, unsigned int v)
|
|
{
|
|
p[0] = (unsigned char)(v >> 24); p[1] = (unsigned char)(v >> 16);
|
|
p[2] = (unsigned char)(v >> 8); p[3] = (unsigned char)v;
|
|
}
|
|
|
|
// The one's-complement sum every IP protocol uses. RFC 1071.
|
|
static unsigned short
|
|
csum(const unsigned char *p, int n, unsigned int seed)
|
|
{
|
|
unsigned int s = seed;
|
|
int i;
|
|
|
|
for (i = 0; i + 1 < n; i += 2)
|
|
s += (unsigned int)((p[i] << 8) | p[i + 1]);
|
|
if (i < n)
|
|
s += (unsigned int)(p[i] << 8);
|
|
while (s >> 16)
|
|
s = (s & 0xffff) + (s >> 16);
|
|
return (unsigned short)(~s & 0xffff);
|
|
}
|
|
|
|
static void
|
|
eth_header(unsigned char *f, const unsigned char *dst, unsigned short type)
|
|
{
|
|
int i;
|
|
for (i = 0; i < 6; i++) f[i] = dst[i];
|
|
for (i = 0; i < 6; i++) f[6 + i] = my_mac[i];
|
|
wr16(f + 12, type);
|
|
}
|
|
|
|
// --- ARP (RFC 826) -----------------------------------------------------------
|
|
|
|
static int
|
|
arp(const unsigned char *in, int len, unsigned char *out)
|
|
{
|
|
const unsigned char *a = in + 14;
|
|
|
|
if (len < 14 + 28) return 0;
|
|
if (rd16(a) != 1 || rd16(a + 2) != 0x0800) return 0; /* ethernet, ipv4 */
|
|
if (rd16(a + 6) != 1) return 0; /* request */
|
|
if (rd32(a + 24) != MY_IP) return 0; /* not for us */
|
|
|
|
eth_header(out, in + 6, 0x0806);
|
|
{
|
|
unsigned char *r = out + 14;
|
|
int i;
|
|
wr16(r, 1); wr16(r + 2, 0x0800);
|
|
r[4] = 6; r[5] = 4;
|
|
wr16(r + 6, 2); /* reply */
|
|
for (i = 0; i < 6; i++) r[8 + i] = my_mac[i];
|
|
wr32(r + 14, MY_IP);
|
|
for (i = 0; i < 6; i++) r[18 + i] = a[8 + i]; /* their mac */
|
|
wr32(r + 24, rd32(a + 14)); /* their ip */
|
|
}
|
|
return 14 + 28;
|
|
}
|
|
|
|
// --- ICMP echo (RFC 792) ------------------------------------------------------
|
|
|
|
static int
|
|
icmp(const unsigned char *in, int len, unsigned char *out)
|
|
{
|
|
const unsigned char *ip = in + 14;
|
|
int ihl, iplen, icmplen;
|
|
|
|
if (len < 14 + 20) return 0;
|
|
if ((ip[0] >> 4) != 4) return 0;
|
|
ihl = (ip[0] & 0xf) * 4;
|
|
if (ihl < 20 || len < 14 + ihl + 8) return 0;
|
|
if (ip[9] != 1) return 0; /* not ICMP */
|
|
if (rd32(ip + 16) != MY_IP) return 0; /* not for us */
|
|
if (ip[ihl] != 8) return 0; /* not an echo */
|
|
|
|
iplen = rd16(ip + 2);
|
|
if (iplen > len - 14 || iplen < ihl + 8) return 0;
|
|
icmplen = iplen - ihl;
|
|
|
|
eth_header(out, in + 6, 0x0800);
|
|
{
|
|
unsigned char *o = out + 14;
|
|
int i;
|
|
|
|
// The reply is the request with the addresses swapped and the type
|
|
// changed, which is what RFC 792 asks for -- the payload has to come
|
|
// back byte for byte so the sender can match it.
|
|
for (i = 0; i < iplen; i++)
|
|
o[i] = ip[i];
|
|
wr32(o + 12, MY_IP);
|
|
wr32(o + 16, rd32(ip + 12));
|
|
o[10] = o[11] = 0;
|
|
wr16(o + 10, csum(o, ihl, 0));
|
|
|
|
o[ihl] = 0; /* echo reply */
|
|
o[ihl + 2] = o[ihl + 3] = 0;
|
|
wr16(o + ihl + 2, csum(o + ihl, icmplen, 0));
|
|
}
|
|
return 14 + iplen;
|
|
}
|
|
|
|
// --- DHCP (RFC 2131) ----------------------------------------------------------
|
|
//
|
|
// Only the two exchanges lwIP needs: DISCOVER answered with OFFER, REQUEST
|
|
// answered with ACK. Everything else about DHCP -- leases expiring, renewal,
|
|
// duplicate detection -- is real and is not what this program is for.
|
|
|
|
static int
|
|
dhcp(const unsigned char *in, int len, unsigned char *out)
|
|
{
|
|
const unsigned char *ip = in + 14;
|
|
int ihl = (len >= 14 + 20) ? (ip[0] & 0xf) * 4 : 0;
|
|
const unsigned char *udp, *bp;
|
|
int msgtype = 0, i, blen;
|
|
unsigned char *o, *b;
|
|
|
|
if (ihl < 20 || len < 14 + ihl + 8) return 0;
|
|
if (ip[9] != 17) return 0; /* not UDP */
|
|
udp = ip + ihl;
|
|
if (rd16(udp) != 68 || rd16(udp + 2) != 67) return 0; /* not to a server */
|
|
|
|
bp = udp + 8;
|
|
if (len < (int)(bp - in) + 240) return 0;
|
|
if (rd32(bp + 236) != 0x63825363u) return 0; /* the magic cookie */
|
|
|
|
// Walk the options for the message type. TLV, terminated by 255.
|
|
for (i = 240; (int)(bp - in) + i < len; ) {
|
|
unsigned char t = bp[i];
|
|
if (t == 255) break;
|
|
if (t == 0) { i++; continue; }
|
|
if ((int)(bp - in) + i + 2 > len) break;
|
|
if (t == 53) msgtype = bp[i + 2];
|
|
i += 2 + bp[i + 1];
|
|
}
|
|
if (msgtype != 1 && msgtype != 3) return 0; /* DISCOVER/REQUEST */
|
|
|
|
// Broadcast the reply. A client with no address yet cannot be reached any
|
|
// other way, which is exactly why DHCP is built on broadcast.
|
|
{
|
|
static const unsigned char bcast[6] =
|
|
{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
|
eth_header(out, bcast, 0x0800);
|
|
}
|
|
o = out + 14;
|
|
b = o + 20 + 8;
|
|
|
|
for (i = 0; i < 240; i++) b[i] = 0;
|
|
b[0] = 2; /* BOOTREPLY */
|
|
b[1] = 1; b[2] = 6;
|
|
for (i = 0; i < 4; i++) b[4 + i] = bp[4 + i]; /* xid, echoed */
|
|
wr32(b + 16, PEER_IP); /* yiaddr */
|
|
wr32(b + 20, MY_IP); /* siaddr */
|
|
for (i = 0; i < 6; i++) b[28 + i] = bp[28 + i]; /* chaddr */
|
|
wr32(b + 236, 0x63825363u);
|
|
|
|
blen = 240;
|
|
b[blen++] = 53; b[blen++] = 1; b[blen++] = (msgtype == 1) ? 2 : 5;
|
|
b[blen++] = 54; b[blen++] = 4; wr32(b + blen, MY_IP); blen += 4;
|
|
b[blen++] = 51; b[blen++] = 4; wr32(b + blen, 86400); blen += 4;
|
|
b[blen++] = 1; b[blen++] = 4; wr32(b + blen, NETMASK); blen += 4;
|
|
b[blen++] = 3; b[blen++] = 4; wr32(b + blen, MY_IP); blen += 4;
|
|
b[blen++] = 255;
|
|
while (blen < 300) b[blen++] = 0; /* pad to a sane size */
|
|
|
|
wr16(o + 20, 67); wr16(o + 20 + 2, 68);
|
|
wr16(o + 20 + 4, (unsigned short)(8 + blen));
|
|
o[20 + 6] = o[20 + 7] = 0; /* UDP checksum: optional over IPv4 */
|
|
|
|
o[0] = 0x45; o[1] = 0;
|
|
wr16(o + 2, (unsigned short)(20 + 8 + blen));
|
|
wr16(o + 4, 0); wr16(o + 6, 0);
|
|
o[8] = 64; o[9] = 17;
|
|
o[10] = o[11] = 0;
|
|
wr32(o + 12, MY_IP);
|
|
wr32(o + 16, 0xFFFFFFFFu);
|
|
wr16(o + 10, csum(o, 20, 0));
|
|
|
|
return 14 + 20 + 8 + blen;
|
|
}
|
|
|
|
// --- main ---------------------------------------------------------------------
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
struct netuser_create c;
|
|
unsigned char in[NETUSER_MTU], out[NETUSER_MTU];
|
|
int fd, i, n;
|
|
long arps = 0, pings = 0, dhcps = 0;
|
|
|
|
fd = open("/dev/netdev", O_RDWR);
|
|
if (fd < 0) { puts_("netecho: cannot open /dev/netdev\n"); exit(); }
|
|
|
|
for (i = 0; i < (int)sizeof(c.name); i++) c.name[i] = 0;
|
|
for (i = 0; i < 6; i++) c.mac[i] = my_mac[i];
|
|
c.pad[0] = c.pad[1] = 0;
|
|
c.name[0] = 'l'; c.name[1] = 'd'; c.name[2] = 'l'; c.name[3] = 's';
|
|
|
|
if (ioctl(fd, NETUSERIOC_CREATE, &c) < 0) {
|
|
puts_("netecho: /dev/netdev is already somebody else's\n");
|
|
exit();
|
|
}
|
|
|
|
puts_("netecho: this process is now an interface. I am ");
|
|
putip(MY_IP);
|
|
puts_(" and will offer ");
|
|
putip(PEER_IP);
|
|
puts_("\n");
|
|
|
|
for (;;) {
|
|
n = read(fd, (char *)in, sizeof(in));
|
|
if (n <= 0) {
|
|
// The kernel took the interface back, which happens when the
|
|
// descriptor is released -- there is nothing left to do.
|
|
puts_("netecho: the interface went away\n");
|
|
exit();
|
|
}
|
|
if (n < 14)
|
|
continue;
|
|
|
|
{
|
|
unsigned short type = rd16(in + 12);
|
|
int r = 0;
|
|
|
|
if (type == 0x0806) {
|
|
r = arp(in, n, out);
|
|
if (r) { arps++;
|
|
if (arps == 1) puts_("netecho: answered an ARP\n"); }
|
|
} else if (type == 0x0800) {
|
|
r = dhcp(in, n, out);
|
|
if (r) { dhcps++;
|
|
puts_("netecho: answered DHCP, offering an address\n"); }
|
|
if (!r) {
|
|
r = icmp(in, n, out);
|
|
if (r) { pings++;
|
|
if (pings == 1) puts_("netecho: answered a ping\n"); }
|
|
}
|
|
}
|
|
|
|
if (r > 0)
|
|
write(fd, (char *)out, r);
|
|
}
|
|
}
|
|
}
|