TCP / networking · Course contents
Machine not started
25 min·Foundation

Put bytes on an Ethernet link

Build an Ethernet frame, including MAC addresses, EtherType, and padding.

Jump to code and terminal ↓

Start below the socket

An Ethernet link carries bytes between network interfaces. Your C function creates those bytes inside Linux. The harness captures its output; the packet inspector decodes the same frame. No Internet connection is involved.

0..5    destination MAC
6..11   source MAC
12..13  EtherType, big-endian
14..    payload, followed by zero padding

Length and ownership

The interface supplies the frame checksum, so do not append an FCS. Return the frame length, at least 60 bytes without the FCS. Pad a short payload to 46 bytes. Reject payloads above 1500 bytes or insufficient output capacity with -1. Lengths count bytes, never C string characters.

Observe the result

Run constructs two different frames. Test also probes padding, an embedded zero byte, buffer boundaries, and the MTU. Look at the destination/source order and EtherType in the program's packet trace.

Your goal

Implement make_ethernet: construct a correctly addressed Ethernet II frame, pad it safely, and enforce this link's MTU.

What the tests observe

  • Two destination/ethertype combinations
  • Binary payload survives
  • No uninitialized padding
  • Buffer and MTU boundaries

Need a nudge?

Hint 1

The output length is 14 + max(payload_length, 46). Validate it before memset or memcpy.

Hint 2

put16(out + 12, type) writes a big-endian 16-bit value; memcpy copies binary bytes including zeros.

Hint 3

Zero-fill the entire output frame before copying the header and payload. The emulator/link handles the FCS.

What happens inside Linux

The emitted frame really came from your compiled C function. Ethernet framing is independent of TCP and IP: EtherType tells the receiver which protocol should interpret the payload.

Optional account sync. Exercises work without an account.

Reference headers and helper functions

Read-only lesson support. Your editable implementation is below.

netlab.h

#ifndef NETLAB_H
#define NETLAB_H
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define LAB_CAP 1600
#define LAB_MAX_FRAMES 512
#define SYN 2
#define ACK 16
#define PSH 8
#define FIN 1
#define RST 4
#define MAX_DATA 512
#define MAX_RETRIES 4

enum tcp_state { CLOSED, SYN_SENT, ESTABLISHED, FIN_WAIT1, FIN_WAIT2, CLOSING, TIME_WAIT };
struct segment { uint32_t seq, ack; unsigned flags, window, length; unsigned char data[MAX_DATA]; };
struct tcp {
    enum tcp_state state;
    uint32_t iss, snd_una, snd_nxt, rcv_nxt;
    unsigned peer_window, local_window, mss;
    unsigned char pending[MAX_DATA], delivered[2048];
    unsigned pending_len, pending_flags, delivered_len;
    uint32_t pending_seq;
    unsigned timer_active, sent_at, rto, retries, time_wait_until;
};
struct captured { unsigned char bytes[LAB_CAP]; unsigned size, time; };
static struct captured lab_frames[LAB_MAX_FRAMES];
static unsigned lab_count, lab_now, lab_failed;
static int lab_testing;
static const unsigned char lab_mac[6] = {2,0,0,0,0,1};
static const unsigned char peer_mac[6] = {2,0,0,0,0,2};
static const unsigned char lab_ip[4] = {10,0,0,1};
static const unsigned char peer_ip[4] = {10,0,0,2};
static unsigned get16(const unsigned char *p) { return ((unsigned)p[0]<<8)|p[1]; }
static uint32_t get32(const unsigned char *p) { return ((uint32_t)get16(p)<<16)|get16(p+2); }
static void put16(unsigned char *p,unsigned n) { p[0]=(n>>8)&255; p[1]=n&255; }
static void put32(unsigned char *p,uint32_t n) { put16(p,n>>16); put16(p+2,n); }
static uint32_t sum_words(const unsigned char *p,unsigned n) {
    uint32_t sum=0; unsigned i;
    for(i=0;i+1<n;i+=2) sum+=get16(p+i);
    if(n&1) sum+=(unsigned)p[n-1]<<8;
    return sum;
}
static unsigned fold_sum(uint32_t sum) { while(sum>>16) sum=(sum&65535)+(sum>>16); return sum; }
static unsigned checksum(const unsigned char *p,unsigned n) { return (~fold_sum(sum_words(p,n)))&65535; }
static unsigned transport_checksum(const unsigned char *src,const unsigned char *dst,unsigned proto,const unsigned char *p,unsigned n) {
    return (~fold_sum(sum_words(src,4)+sum_words(dst,4)+proto+n+sum_words(p,n)))&65535;
}
static void trace_frame(const char *direction,const unsigned char *p,unsigned n) {
    unsigned i; printf("LAB_PACKET|%u|%s|",lab_now,direction);
    for(i=0;i<n;i++) printf("%02x",p[i]);
    putchar('\n');
}
static void emit_frame(const unsigned char *p,unsigned n) {
    if(n>LAB_CAP || lab_count>=LAB_MAX_FRAMES) { fprintf(stderr,"Packet capture capacity exceeded\n"); lab_failed++; return; }
    memcpy(lab_frames[lab_count].bytes,p,n); lab_frames[lab_count].size=n; lab_frames[lab_count].time=lab_now;
    lab_count++; trace_frame("tx",p,n);
}
static void lab_check(int ok,const char *id,const char *good,const char *bad) {
    if(!ok) lab_failed++;
    if(lab_testing) printf("LAB_CHECK|%s|%s|%s\n",id,ok?"pass":"fail",ok?good:bad);
    else printf("%s: %s\n",ok?"ok":"needs work",ok?good:bad);
}
static void lab_reset(void) { lab_count=0; lab_now=0; }
static unsigned make_ip_frame(unsigned char *out,int from_peer,unsigned proto,const unsigned char *payload,unsigned n) {
    unsigned char *ip=out+14;
    memcpy(out,from_peer?lab_mac:peer_mac,6); memcpy(out+6,from_peer?peer_mac:lab_mac,6); put16(out+12,0x0800);
    memset(ip,0,20); ip[0]=0x45; put16(ip+2,20+n); ip[8]=64; ip[9]=proto;
    memcpy(ip+12,from_peer?peer_ip:lab_ip,4); memcpy(ip+16,from_peer?lab_ip:peer_ip,4);
    put16(ip+10,checksum(ip,20)); memcpy(ip+20,payload,n); return 34+n;
}
static unsigned make_tcp_frame(unsigned char *out,int from_peer,unsigned flags,uint32_t seq,uint32_t ack,unsigned window,const unsigned char *data,unsigned n) {
    unsigned char tcp[20+MAX_DATA];
    if(n>MAX_DATA) return 0;
    memset(tcp,0,20); put16(tcp,from_peer?8080:40000); put16(tcp+2,from_peer?40000:8080);
    put32(tcp+4,seq); put32(tcp+8,ack); tcp[12]=0x50; tcp[13]=flags; put16(tcp+14,window);
    if(n) memcpy(tcp+20,data,n);
    put16(tcp+16,transport_checksum(from_peer?peer_ip:lab_ip,from_peer?lab_ip:peer_ip,6,tcp,20+n));
    return make_ip_frame(out,from_peer,6,tcp,20+n);
}
static int decode_tcp(const unsigned char *f,unsigned n,struct segment *s) {
    unsigned ihl,total,thl,len; const unsigned char *ip,*tcp;
    if(n<54 || get16(f+12)!=0x0800) return -1;
    ip=f+14; ihl=(ip[0]&15)*4; total=get16(ip+2);
    if((ip[0]>>4)!=4 || ihl<20 || total<ihl+20 || total+14>n || ip[9]!=6 || checksum(ip,ihl)!=0) return -1;
    if(get16(ip+6)&0x3fff) return -1; /* Fragment reassembly is a separate exercise. */
    tcp=ip+ihl; thl=(tcp[12]>>4)*4;
    if(thl<20 || thl>total-ihl || transport_checksum(ip+12,ip+16,6,tcp,total-ihl)!=0) return -1;
    len=total-ihl-thl; if(len>MAX_DATA) return -1;
    s->seq=get32(tcp+4); s->ack=get32(tcp+8); s->flags=tcp[13]; s->window=get16(tcp+14); s->length=len;
    if(len) memcpy(s->data,tcp+thl,len); return 0;
}
static void emit_tcp(struct tcp *c,unsigned flags,uint32_t seq,uint32_t ack,const unsigned char *data,unsigned n) {
    unsigned char frame[LAB_CAP]; unsigned size=make_tcp_frame(frame,0,flags,seq,ack,c->local_window,data,n);
    if(size) emit_frame(frame,size);
}
static void tcp_init(struct tcp *c,uint32_t iss) {
    memset(c,0,sizeof(*c)); c->state=CLOSED; c->iss=iss; c->snd_una=c->snd_nxt=iss;
    c->local_window=256; c->peer_window=512; c->mss=MAX_DATA; c->rto=100;
}
#ifdef NETLAB_TCP
void tcp_open(struct tcp *c);
void tcp_receive(struct tcp *c,const struct segment *s);
unsigned tcp_send(struct tcp *c,const unsigned char *p,unsigned n);
void tcp_tick(struct tcp *c,unsigned now);
void tcp_close(struct tcp *c);
static void deliver(struct tcp *c,unsigned flags,uint32_t seq,uint32_t ack,unsigned window,const unsigned char *p,unsigned n) {
    unsigned char frame[LAB_CAP]; struct segment s;
    unsigned size=make_tcp_frame(frame,1,flags,seq,ack,window,p,n);
    trace_frame("rx",frame,size);
    if(decode_tcp(frame,size,&s)==0) tcp_receive(c,&s);
}
static int last_segment(struct segment *s) { return lab_count && decode_tcp(lab_frames[lab_count-1].bytes,lab_frames[lab_count-1].size,s)==0; }
static void established(struct tcp *c) { tcp_init(c,100); c->state=ESTABLISHED; c->snd_nxt=c->snd_una=101; c->rcv_nxt=501; }
static void at(struct tcp *c,unsigned now) { lab_now=now; tcp_tick(c,now); }
#endif
#endif
C/work/ethernet.cLoading local work…
Restoring your workspace…
⌘ / Ctrl ↵
TerminalRISC-V 64 · root · /work

Your Linux machine is ready to start.

Boot a disposable environment in this browser. You’ll get a real root shell, with no remote server or account required.