Ethernet 7/7

Checksum

Il checksum è un metodo per controllare l'integrità di un pacchetto, ovvero permette di stabilire se questo contiene errori (ed è quindi inutilizzabile).
Di per sé, la parola checksum non identifica un particolare algoritmo, ma quello utilizzato per IP ed altri proptocolli è
il complemento a 1 della somma dei complementi a 1 delle parole a 16bit dei dati su cui effettuare il calcolo; ovvero, si sommano tutte le parole a 16bit, sommando anche l'eventuale overflow ed infine si esegue il complemento a 1.

Prima è stato illustrato come il checksum, in fase di invio, venga calcolato e poi scritto nel pacchetto; ciò che non è ancora stato illustrato è il controllo del checksum all'arrivo di un pacchetto. Per verificare che il pacchetto sia integro, bisognerebbe salvare temporaneamente il campo checksum del pacchetto e mettere zero al suo posto, poi si esegue il calcolo e si confronta il risultato con quello salvato; se sono uguali, ovviamente, il pacchetto è corretto.

In realtà , per come è fatto l'algoritmo, la verifica è molto più semplice: infatti basta calcolare il checksum sul pacchetto senza modificarlo in alcun modo; se il risultato è zero, allora il pacchetto è OK, altrimenti è da scartare.

L'ENC28J60 integra, nel modulo DMA, una funzione abbastanza rapida per il calcolo del checksum, ed è ciò che utilizzeremo. Sfortunatamente, pero, nella revisione B5 del chip, tale funzione da dei grossi problemi alla ricezione dei pacchetti, quindi in questo caso il calcolo verrà eseguito via software.

Innanzitutto vediamo il prototipo della funzione DMAChecksum:

u16 DMAChecksum(u16 start, u16 len, BOOL rx)

il primo parametro indica l'indirizzo al quale iniziano i dati, len la lunghezza in byte di questi dati, infine rx serve ad indicare se l'indirizzo si riferisce alla memoria RX o TX. (L'indirizzo è un offset relativo, rispetto all'inizio del campo dati del pacchetto MAC)

Come dicevo prima, questo metodo ha due diverse implementazioni per le revisioni B1-B4 e B5, vediamo la prima:

u16 DMAChecksum(u16 start, u16 len, BOOL rx){
	// calcola il checksum utilizzando l'apposita funzione del chip
	u16 tmp;
	u8 L,H;
	if (rx) {
		tmp = TX_BUF_START + 1 + sizeof(MAC_Header) + start;
	} else {
		tmp = packetStart + 6 + sizeof(MAC_Header) + start;
		if (tmp > RX_BUF_END)
			tmp = tmp - RX_BUF_END + RX_BUF_START - 1;
	}

	setBank(0);
	writeReg(EDMASTL, LOW(tmp));
	writeReg(EDMASTH, HIGH(tmp));
	
	tmp = tmp+len-1; 			// fine pacchetto
	if (!rx && tmp > RX_BUF_END)
		tmp = tmp - RX_BUF_END + RX_BUF_START - 1;
	writeReg(EDMANDL, LOW(tmp));
	writeReg(EDMANDH, HIGH(tmp));
	
	BFSReg(ECON1, 0b00110000);      	// inizio calcolo
	while(readETH(ECON1) & 0b00100000);	// attende fine calcolo
	tmp = (u16)readETH(EDMACSL) << 8;
	tmp = tmp | readETH(EDMACSH);
	return tmp;				// ritorna il checksum calcolato
}

Prima di tutto, vengono calcolati e scritti negli appositi registri gli indirizzi assoluti di inizio e fine
dei dati su cui effettuare l'operazione. Poi viene avviato il calcono e se ne attende il termine.

Nel secondo caso, si esegue il calcolo via software, il che rende l'operazione molto più lenta:

u16 DMAChecksum(u16 start, u16 len, BOOL rx){
		// calcola il checksum via software
	u16	tmp;
	u16 reg[2];
	u32 sum;
	u16 len2;
	int i;

	if (rx) {
    	tmp = TX_BUF_START + 1 + sizeof(MAC_Header) + start;
	} else {
		tmp = packetStart + 6 + sizeof(MAC_Header) + start;
		if (tmp > RX_BUF_END)
			tmp = tmp - RX_BUF_END + RX_BUF_START - 1;
	}

	// salva ERDPT
	setBank(0);
	reg[0] = readETH(ERDPTL);
	reg[1] = readETH(ERDPTH);

	writeReg(ERDPTL,LOW(tmp));
	writeReg(ERDPTH,HIGH(tmp));

	sum = 0;
	len2 = len & 0xFE;
	CS = 0;
	spiWrite(RBM);
	for (i=0; i<len2; i=i+2){
		tmp = ((u16)spiRead()) << 8 | spiRead();
		sum = sum + (u32) tmp;	
	}
	if (len2!=len) sum += ((u32)spiRead()) << 8; 		// se il pacchetto ha lunghezza dispari
	CS = 1;
	
	while (sum >> 16)
	  sum = (sum & 0xFFFF) + (sum >>  16);

	tmp = ~sum;

	// ripristina ERDPT;
	setBank(0);
	writeReg(ERDPTL,reg[0]);
	writeReg(ERDPTH,reg[1]);

	return htons(tmp);					// ritorna il checksum calcolato
}

Prima di effettuare l'operazione, viene salvato il registro ERDPT (ripristinato alla fine), perché¨ verranno letti tutti i byte necessari al calcolo.

Vediamo anche il metodo putChecksum utilizzato in precedenza:

void putChecksum(u16 offset, u16 sum){
	// scrive il checksum "sum" all'indirizzo "offset" all'interno del buffer di scrittura
	u16 tmp;
	u16 addr[2];
	setBank(0);		
	addr[0] = readETH(EWRPTL);			// salva temporaneamente il puntatore
	addr[1] = readETH(EWRPTH);			// di scrittura
	tmp = 1+sizeof(MAC_Header)+offset;		// nuovo indirizzo
	writeReg(EWRPTL,LOW(TX_BUF_START+tmp));
	writeReg(EWRPTH,HIGH(TX_BUF_START+tmp));	// carica il puntatore
	encPut(LOW(sum));
	encPut(HIGH(sum));      			// scrive il checksum
	writeReg(EWRPTL,addr[0]);
	writeReg(EWRPTH,addr[1]);			// ripristina il vecchio puntatore	
}

ICMP
L'ICMP è un protocollo di servizio (descritto dalla RFC 792), utilizzato per effettuare controlli e segnalazioni all'interno
di una rete. Un pacchetto ICMP può trasportare diversi tipi di messaggi, ma quello più conosciuto e l'unico trattato qui, è la richiesta di Echo, conosciuto anche come Ping.
Nel modello ISO/OSI questo protocollo viene solitamente inserito al terzo livello, non essendo propriamente un protocollo di trasporto,
ma si trova comunque al di sopra del protocollo IP all'interno del quale viene incapsulato.

Struttura del pacchetto
La struttura di un pacchetto ICMP varia a seconda del messaggio trasportato, salvo un'intestazione comune a tutti i tipi.
Vediamo il caso particolare di una richiesta di eco:

ICMP

- Type: assume il valore 8 per la richiesta, mentre nel pacchetto di risposta vale 0.
- Code: inutilizzato.
- Checksum: L'ormai noto campo di controllo. L'algoritmo utilizzato è lo stesso del protocollo IP.
- Identifier e Sequence Number: vengono generati dal richiedente per riconoscere la risposta. Vengono lasciati inalterati da chi risponde.
- Data: contiene un numero variabile di dati che vengono restituiti tali e quali nella risposta.

Il codice
Prima di tutto vediamo il contenuto del file icmp.h il quale contiene alcune define, ma soprattutto
la definizione del pacchetto ICMP.

#define ICMP_ECHO		8
#define ICMP_ECHO_REPLY		0
#define MAX_ICMP_DATA 		32  	// dimensione massima del campo dati

typedef struct {
	u8	type;
	u8	code;
	u16	checksum;
	u16	id;
	u16	sn;
	u8	data[MAX_ICMP_DATA];
} ICMPPacket;

void processICMP(IP_Header ipHeader);

Come si può vedere sono presenti i vari campi descritti nel paragrafo precedente.

Il file icmp.c contiene il codice che permette di rispondere ad un ping.

#include "stack.h"

#define ICMP_Offset	sizeof(IP_Header)

void processICMP(IP_Header ipHeader){
	ICMPPacket	packet;
	u8 size;

	size = ipHeader.totalLength - (ipHeader.verlen & 0x0F)*4;
	if (size > sizeof(packet)) size = sizeof(packet);
	encGetArray((u8*)&packet, size);
	
	if (packet.type == ICMP_ECHO){
		packet.type = ICMP_ECHO_REPLY;
		packet.checksum = 0;
		IPPutHeader(ipHeader.sourceIP, IPPROTO_ICMP, (u8*)&packet, size, size);
		putChecksum(ICMP_Offset+2,DMAChecksum(ICMP_Offset,size,TRUE));
		MACSend();
	}	
}

Attraverso il metodo encGetArray il pacchetto viene letto e salvato nella variabile packet. Poi, una volta
esaminato il campo type, se questo risulta essere una richiesta di eco, viene costruito il pacchetto di risposta:
il campo type viene settato a 0 (Echo Reply), il checksum azzerato e ricalcolato una volta che il pacchetto
viene scritto nel buffer; infine si invia il tutto con MACSend.

ProcessPacket
Perchè tutto funzioni, bisogna implementare un metodo che esamini i pacchetti in arrivo
e li mandi ai relativi gestori (IP e ARP per il terzo livello).
Questo metodo è processPacket ed è richiamato all'interno di un loop infinito nel metodo main:

void main() {
	encInit();
	
	while (1)
		processPacket();	
}

Vediamo il file stack.c

#include "stack.h"

MACAddr	remoteAddr;

void processPacket(){
	MAC_Header header;
	setBank(1);
	while (readETH(EPKTCNT)) { // se c'è almeno un pacchetto

		MACGetHeader(&header);
		if (header.type == TYPE_IP){
			remoteAddr = header.sourceMAC;
			processIP();
		} else
		if (header.type == TYPE_ARP)
			processARP();

		freeRxSpace();  // libera lo spazio nel buffer RX
	}
}

Leggendo il registro EPKCNT verifichiamo che ci sia almeno un pacchetto in attesa di essere letto, poi con MACGetHeader viene
letta l'intestazione MAC ed attraverso il campo type viene scelto se chiamare processIP o proccessARP (o anche nessuno dei due).
Questi metodi sono stati già trattati nelle pagine precedenti.

Scarica subito una copia gratis
Tags:

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend