Convé fer un repàs del treball dut a terme al prototip durant els darrers mesos i enumerar alguns dels problemes sorgits durant aquest període.
El procés d'implementació de les operacions de sockets i I/O general ha sigut bastant senzill. La majoria d'accions que realitzen les operacions en pfinet aquí ja estan implementades per LwIP, això inclou la gestió de l'estat dels sockets i la concurrència, de manera que moltes de les operacions del translator de LwIP l'única cosa que fan és comprovar les credencials de la crida rpc, cridar a la funció pertinent de l'API de sockets de LwIP i retornar l'errno. Un bon exemple és l'operació connect. Com es pot veure, algunes operacions com la recv o la connect mateix necessiten alguna modificació addicional per satisfer els requeriments de la Glibc, però en general els problemes han vingut després.
Un dels problemes més destacables l'he tingut amb l'operació I/O get_openmodes. La implementació en pfinet retorna O_WRITE si l'extrem local encara no ha enviat el missatge FIN, i O_READ si encara no l'ha rebut, també retorna el marcador O_NONBLOCK si està activat al socket. En LwIP, la funció lwip_fcntl() retornava només el darrer dels tres, així que vaig haver de fer uns canvis en aquesta funció per fer-la retornar també els dos primers. Vaig escriure un pegat que em van rebutjar perquè estava basat en alguns malentesos i estava poc polit, però al final ho vaig arreglar i formarà part de la versió 2.0.3 de LwIP.
En una altra ocasió, vaig observar que la pila sempre fallava en intentar descarregar un fitxer amb wget la primera vegada, però els següents intents funcionaven. Després d'investigar-ho, vaig trobar que el problema estava relacionat amb el protocol ARP. El primer cop que la pila intenta enviar un missatge, la taula ARP es troba buida i cal enviar primer una petició ARP per obtindre l'adreça MAC de la destinació. En aquest, cas, aquest primer intent corresponia a una petició DNS que es quedava desada en un búfer intern de la pila mentre esperava l'arribada de la resposta ARP. El problema era que immediatament es generava una segona petició DNS, en aquest cas per obtindre l'adreça IPv6 del mateix nom, i expulsava del búfer la petició anterior ja que només hi havia espai per a un paquet a la cua. Arreglar-ho va ser fàcil perquè només era afegir una línia augmentant la capacitat d'aquest búfer, però trobar el problema em va fer perdre uns dies.
He tingut alguns moments poc gloriosos durant aquests mesos, un d'ells va ser quan vaig perdre un parell de vesprades tractant de saber perquè l'operació I/O select rebia el paràmetre timeout amb un valor estrany i completament distint al que el programa d'usuari havia enviat, i la resposta era... que la Glibc el transformava en Unix time :-/. El diable està en els detalls.
També vaig tindre un problema a l'hora d'enviar les trames a la targeta de xarxa que encara no he acabat d'entendre. A LwIP, les trames generades per la pila estan estructurades en una llista enllaçada simple:
struct pbuf {
struct pbuf *next; // següent pbuf de la cadena
void *payload; // punter a les dades
u16_t tot_len; // longitud total d'aquest búfer i els restants
u16_t len; // longitud d'aquest búfer
u8_t type;
u8_t flags;
u16_t ref; // comptador de refrències
}
D'aquesta manera és fàcil per a la pila muntar la trama afegint capçaleres a cada capa. L'aplicació genera unes dades que es guarden en un pbuf, les capes inferiors generen unes capçaleres en un altre pbuf que apunta al primer, i així successivament. Al final, el mòdul ethernet rep l'adreça del primer pbuf de la cadena i seguint els enllaços obté totes les dades en l'ordre correcte. Per això, el que jo feia era simplement recórrer la cadena i anar enviant la càrrega útil a la targeta de xarxa, però fent açò les dades no apareixien en el cable. L'única manera de fer que les dades apareguen en el cable és concatenar totes les parts en un únic búffer que continga tota la trama sencera abans d'enviar-la al dispositiu. Això em fa pensar que potser en alguna part entre la pila i el dispositiu, potser al controlador, aquestes porcions de trama es consideren trames malformades i es descarten. M'agradaria tindre un d'aquests blocs on l'autor pareix que sap de què parla :P, però la veritat és que encara no sé què està passant aquí.
Amb això arribem al dia d'aviu. De la llista de tasques que vaig incloure a la proposta, les següents són les que encara tinc pendents:
- Afegir suport per a IPv6
- Implementar les operacions d'altres interfícies si escau.
- Implementar el suport per a diverses interfícies de xarxa.
- Rebre la configuració de la pila des de la línia de comandes.
- Rebre la configuració de la pila a través d'fsysopts.
El prototip funciona i es pot emprar per a connectar-se a Internet. però quan es posa la prova seriosament comencen a sorgir errors de tota mena, de manera que encara està lluny de ser estable i queda molt per polir.