• Non ci sono risultati.

Conclusioni............................................................................................61 Installazione...........................................................................................55 Librerie esterne..........................

N/A
N/A
Protected

Academic year: 2021

Condividi "Conclusioni............................................................................................61 Installazione...........................................................................................55 Librerie esterne.........................."

Copied!
64
0
0

Testo completo

(1)

Indice

Introduzione ... 1 Architettura ... 4 Piattaforma Hardware ... 7 Piattaforma Software... 9 OpenWRT ... 10 CoMo... 31 Librerie esterne... 52 Installazione ... 55 Conclusioni ... 61

(2)

Introduzione

Il monitoraggio continuo dei dati – in campo informatico – serve a soddisfare l’esigenza di tenere traccia, in tempo reale, di tutti i dati generati da un insieme di fonti digitali, e offrire la possibilità di esaminarli e trarne statistiche.

Implementare un meccanismo di questo tipo su un sistema embedded rende disponibile una lunga serie di funzionalità, soprattutto in scenari come le reti di sensori, in cui i dati immagazzinati e gestiti possono fornire importanti informazioni sullo stato e sull’evoluzione del sistema controllato e dell’ambiente esterno.

A tal fine, il sistema di monitoraggio deve essere quanto più possibile indipendente dal numero dei dispositivi presenti sulla rete (scalabilità), e supportare tutti i sistemi e protocolli di comunicazione tra di essi allo scopo di osservarne il traffico e le informazioni scambiate.

Per lo stesso scopo, è importante che il meccanismo non richieda modifiche alle sorgenti dei dati, e che quindi quest’ultime possano

(3)

continuare ad operare nel modo consueto senza curarsi della presenza del sistema di monitoraggio (trasparenza).

È evidente che un’applicazione di questo tipo richiede alla base diversi sniffer (uno per ogni mezzo fisico di comunicazione disponibile all’interno della rete), che forniscano i dati al sistema, e meccanismi per la presentazione dei dati raccolti, in forma diversamente aggregata.

Con questo lavoro ci proponiamo di realizzare una piattaforma capace di gestire quanto precedentemente esposto, ma soprattutto che sia basata su hardware e software facilmente reperibili, non eccessivamente costosi, e che lascino ampi margini per modifiche e adattamenti, in modo che la soluzione non risulti troppo vincolata ad un particolare scenario; nello specifico, la piattaforma dovrà mostrare potenzialità di crescita e miglioramento, per essere utilizzata in reti e sistemi diversi e più complessi da quelli qui esaminati.

L’architettura che intendiamo realizzare consisterà dunque di un dispositivo embedded, da connettere alla rete, capace di raccogliere e memorizzare tutte le informazioni generate dalle varie sorgenti su più mezzi di comunicazione, e consentire in ogni momento di eseguire analisi e statistiche su questi dati; inoltre, il software che realizzi questa funzionalità sul dispositivo dovrà avere una struttura fortemente modulare, per

(4)

consentire di modificarne il comportamento “a caldo” semplicemente aggiungendo o modificando alcuni file, per supportare nuove sorgenti o nuove interrogazioni sui dati memorizzati.

(5)

Architettura

Intendiamo realizzare un sistema di monitoraggio su un dispositivo embedded connesso alla rete, senza ulteriori modifiche ai dispositivi connessi alla rete stessa.

Ad esempio, assumiamo lo scenario di una rete di tipo medico, in cui i dati sono inviati da sensori wireless, postazioni di lavoro o macchinari di analisi connessi attraverso rete fissa, seriale, bluetooth o infrarossi (se non direttamente attraverso a un PC configurato in modo tale da inoltrare i dati). Vogliamo che il sistema di monitoraggio possa ricevere i messaggi inviati da tutte le sorgenti, ne memorizzi il contenuto, e quando richiesto da personale medico autorizzato, ne illustri contenuto e statistiche, per permettere una rapida analisi e diagnosi.

Il dispositivo di rete che meglio si presta a tale scopo è senza dubbio un router, in quanto questo dispone di un buon numero di interfacce di rete e riceve il traffico proveniente da tutte le sottoreti; inoltre è comunemente

(6)

posto alla frontiera della rete, e dunque facilmente raggiungibile dall’esterno.

La struttura del programma di monitoraggio, come si è detto, dovrà essere di tipo modulare; in particolare, dovremo avere degli sniffer, dei filtri, una base di dati, un sistema di presentazione e un sistema di comunicazione.

Il ruolo dei moduli sniffer è quello di acquisire dati; attivati su una o più interfacce di rete, osservano i messaggi prodotti dalle sorgenti e ne estraggono i dati, da passare ai filtri affinché ne ricavino le informazioni utili. Nel caso di una connessione fissa o wireless, questi moduli coincidono con gli sniffer classici, mentre su altri mezzi di comunicazione possono gestire una connessione diretta; ad esempio nel caso del bluetooth, che richiede un’associazione, o nel caso di comunicazione attraverso la porta seriale.

I filtri implementano le interrogazioni che l’utente può effettuare sui dati raccolti; le informazioni di cui l’utente può aver bisogno possono essere molteplici e comprendere vari livelli di aggregazione, perciò alcuni di questi moduli mantengono memoria di alcune dei dati precedentemente osservati per combinarli in risultati aggregati. I messaggi prelevati dagli sniffer vengono così scomposti ed esaminati, e le informazioni ottenute

(7)

passate alla base di dati per la memorizzazione; ma soprattutto i filtri vengono utilizzati dal sistema di presentazione per recuperare quanto registrato e presentarlo nella forma richiesta dall’utente (poiché tale forma può variare da filtro a filtro, tale compito non può essere svolto autonomamente dal sistema di presentazione).

La base di dati è probabilmente il punto più delicato del sistema, poiché ne rappresenta il collo di bottiglia; i dati filtrati vengono memorizzati su disco per essere resi disponibili al modulo di presentazione. La scrittura deve essere rapida, e la lettura supportare diversi accessi concorrenziali. Nel sistema che implementeremo useremo un file diverso per ogni filtro, e velocizzeremo il processo attraverso l’uso delle mappature di memoria, ovvero utilizzeremo la funzione mmap() di Linux per mantenere in memoria le porzioni utili di questi file.

Il sistema di presentazione si occupa di mostrare le informazioni richieste all’utente finale; nella nostra implementazione, sarà un server web.

Il sistema di comunicazione, banalmente, si occupa di passare i dati tra i moduli, e coincide con il sistema che coordina il loro funzionamento.

(8)

Piattaforma Hardware

La piattaforma hardware prescelta è il router wireless ASUS WL-500g Premium; basato su hardware non particolarmente dedicato; è compatibile con sistemi operativi diversi da quello di default, e attraverso le porte seriali e USB è possibile connetterlo ad altri mezzi di comunicazione oltre a quelli presenti di default (10/100 BaseT Fast Ethernet LAN e IEEE 802.11b/g wireless LAN).

Inoltre, sempre la presenza di porte USB consente di collegare dispositivi di memoria esterna in cui immagazzinare la mole di dati raccolti. Il processore Broadcom BCM4704, che ha una frequenza nominale di 300Mhz ma nel router viene costretto a operare a 264MHz, è un MIPS a 32 bit; la memoria complessiva è composta da 32MB di RAM (in due chips DDR SDRAM) e 8MB di memoria flash nella quale viene memorizzato il firmware.

(9)

Fotografia del dispositivo (ASUS WL-500g Premium). Dimensioni: 215 x 160 x 42 mm

Il sistema operativo di default è basato su Linux, ma per le modifiche necessarie (in particolare l’installazione di nuovo software), sarà necessario sostituirlo; se, quando si collega l’alimentazione, si tiene premuto il tasto RESET per qualche secondo, il router entra in modalità diag, ed accetta l’invio di una nuova immagine via TFTP.

(10)

Piattaforma Software

Come piattaforma software intendiamo l’insieme di sistema operativo, librerie e applicazioni che andremo ad installare sulla piattaforma hardware prescelta, oltre alla loro configurazione per realizzare l’obiettivo desiderato.

La piattaforma hardware non è, ovviamente, in grado di compilare autonomamente tali programmi, perciò questa fase deve essere svolta su una piattaforma esterna in crosscompilazione.

Di seguito, supporremo di utilizzare una piattaforma FreeBSD, sulla quale non disponiamo dell’account di root; inoltre, utilizzeremo la Bourne shell (sh) per illustrare i comandi da eseguire.

(11)

OpenWRT

Il sistema operativo che si è deciso di adottare è OpenWRT (http://openwrt.org/), una distribuzione Linux per sistemi embedded, nella versione trunk svn (https://svn.openwrt.org/openwrt/trunk/).

Rispetto al sistema operativo di default del router, OpenWRT ha il vantaggio di essere facilmente modificabile e configurabile, sia in fase di compilazione che dopo l’installazione; in particolare supporta l’installazione di nuovo software, compatibilmente con le poche risorse disponibili, sia attraverso un sistema autonomo – ipkg, basato sul sistema di gestione pacchetti di Debian – sia attraverso la copia di nuovi files sulla flash, gestita attraverso un filesystem JFFS – Journalling Flash File System.

Per di più, a differenza del sistema originale che permetteva solo un accesso tramite web, OpenWRT consente anche un accesso più diretto attraverso telnet o, una volta definita una password, ssh.

I sorgenti possono essere agevolmente compilati anche su una piattaforma BSD, ma richiedono diversi prerequisiti, alcuni di questi nella

(12)

versione GNU, che è usualmente quella predefinita su piattaforme Linux ma non su BSD.

I pacchetti richiesti come dipendenza sono g++, ncurses, zlib, bison, flex, autoconf, wget, GNU awk (gawk), GNU tar (gtar), GNU patch (gpatch) e le GNU findutils; inoltre occorre ovviamente subversion per poter effettuare il check-out dei sorgenti.

Il Makefile di OpenWRT controlla automaticamente la presenza di queste dipendenze, con l’eccezione del terminfo compiler; tuttavia non verifica che la versione di patch in uso sia quella GNU, e quella in uso su BSD provoca un errore di patch malformata.

In ogni caso, è abbastanza semplice assicurarsi che la versione corretta sia la prima nel PATH dell’ambiente.

Per installare le dipendenze attraverso il sistema dei ports di BSD, occorre definire alcune variabili di sistema affinché lo scaricamento, la compilazione e l’installazione avvengano in una directory sulla quale l’utente che esegue l’operazione abbia sufficienti diritti.

Scegliamo per l’occasione una directory “apps” nella home dell’utente, ed eseguiamo i seguenti comandi:

(13)

export PREFIX=${HOME}/apps export WRKDIRPREFIX=${PREFIX}/build export DISTDIR=${PREFIX}/dist export INSTALL_AS_USER= export NO_PKG_REGISTER= export NO_MTREE=

Con queste definizioni indichiamo ai ports di scaricare in ~/apps/dist, compilare in ~/apps/build, non passare all’account di root per le operazioni che in linea di principio potrebbero richiederlo e non eseguire le fasi (registrazione dell’installazione e aggiornamento della directory delle specifiche di sistema) che senza i diritti di root fallirebbero.

Per installare le dipendenze, è sufficiente ora eseguire invocare make sui ports necessari:

for i in \

lang/gawk ftp/wget \

archivers/gtar misc/findutils \

devel/patch devel/cmake ; do

( cd /usr/ports/$i && make install ) done

(14)

Notiamo che alla lista mancano alcuni pacchetti, in quanto comunemente presenti in un’installazione di default.

In particolare manca il pacchetto devel/ncurses, perché questo effettua operazioni che richiedono i diritti di root, e non supporta quindi l’installazione da parte di un utente semplice; una possibilità resta quella di installare il pacchetto al di fuori del sistema dei ports. Normalmente, ncurses è presente nell’installazione di default, ma il terminfo compiler viene installato nel sottosistema di compatibilità con Linux (/usr/compat/linux/): per renderlo disponibile ad OpenWRT, va incluso nel PATH, o più semplicemente linkato nella directory contenente i nostri altri prerequisiti:

export BINDIR=${PREFIX}/bin

[ -h ${BINDIR}/tic ] || \

ln -s /usr/compat/linux/usr/bin/tic ${BINDIR}/tic

Inoltre, qualora nel sistema sia già presente patch, la nuova versione da noi compilata non viene automaticamente linkata a tale nome, perciò sarà necessario creare anche questo collegamento.

(15)

[ -h ${BINDIR}/patch ] || \ ln -s ${BINDIR}/gpatch ${BINDIR}/patch

Una volta scaricati i sorgenti, con un semplice check-out da subversion, e installate le dovute dipendenze, dobbiamo configurare i sorgenti secondo le nostre necessità.

Digiteremo allora “make menuconfig” nella directory principale di OpenWRT, per entrare nella configurazione a menu basati su ncurses.

Rispetto alle opzioni di default, solo poche modifiche sono necessarie. Come profilo selezioneremo “ASUS WL-500g Premium” che corrisponde alla nostra piattaforma hardware:

(16)

Dovremo inoltre attivare nella configurazione le librerie necessarie per i programmi che intendiamo compilare;

In particolare sceglieremo libopenssl, per avere funzionalità di crittografia, e libpcap, necessario per funzionalità di sniffing.

(17)

Ci occorrerà inoltre collegare dispositivi di memoria esterni su USB, perciò attiviamo le mount-utils:

(18)

Dobbiamo ora configurare il kernel di Linux per supportare i dispositivi che intendiamo collegare:

(19)

Come abbiamo detto, vorremo utilizzare dispositivi di memoria esterna su USB; dobbiamo perciò selezionare, nella sezione relativa all’USB, “usb-storage” e il driver per l’USB2.

(20)
(21)

Per poter pienamente utilizzare il dispositivo esterno, però, occorre che il sistema ne supporti il filesystem.

Tipicamente questi dispositivi sono formattati in FAT32, supportato dal modulo “vfat” in Linux.

(22)

Per utilizzare il sistema su altre piattaforme, possiamo ad esempio pensare di collegare alla porta USB del router un adattatore bluetooth; affinché questo sia supportato dal kernel di Linux, è necessario attivare la relativa opzione.

(23)
(24)

Rimane solo da salvare la configurazione prodotta perché sia utilizzata nella compilazione.

Torniamo al menu principale, selezioniamo “exit” and diamo la conferma per il salvataggio delle modifiche.

(25)

La configurazione prodotta è memorizzata nel file “.config”, ed è possibile copiarla per successive installazioni.

Una semplice invocazione di make senza argomenti a questo punto sarà sufficiente a compilare tutta la catena per mips-linux, il kernel e le librerie.

Qualora make sia invocato senza argomenti ma il file “.config” risulti assente, il Makefile istruisce il programma ad eseguire preventivamente la fase di configurazione precedentemente vista.

Una volta completata l’installazione, la directory bin/ conterrà diverse immagini; noi scegliamo openwrt-brcm-2.4-squashfs.trx, che riflette il nostro sistema target (Broadcom BCM947xx/953xx) e utilizza il kernel Linux 2.4.

(26)

Questa sarà l’immagine che installeremo sul nostro router.

Per compilare nuovi programmi per la piattaforma OpenWRT, però, è indispensabile rendere disponibile la catena di crosscompilazione utilizzata per costruire l’immagine e le librerie.

Il modo più semplice per ottenere questo risultato è scrivere uno script che agisca da wrapper, invocando il crosscompilatore con le dovute opzioni per individuare gli header da includere e le librerie da collegare. Un esempio di script è quello che segue:

#!/bin/sh #

# Wrapper to the crosscompiler built by OpenWRT

# OpenWRT installation directory STAGING_DIR=${TO_SET} # Useful directories MIPS_BASE_DIR="${STAGING_DIR}/toolchain-mipsel_gcc3.4.6/mipsel-linux-uclibc" MIPS_BIN_DIR="${MIPS_BASE_DIR}/bin" MIPS_INC_DIR="${MIPS_BASE_DIR}/lib/gcc/mipsel-linux-uclibc/3.4.6/include" MIPS_SYS_DIR="${MIPS_BASE_DIR}/sys-include"

(27)

MIPS_TOOL_INC_DIR="${STAGING_DIR}/mipsel/usr/inclu de"

MIPS_LIB_DIR_LOCAL="${STAGING_DIR}/mipsel/usr/lib"

MIPS_LIB_DIR="${STAGING_DIR}/toolchain-mipsel_gcc3.4.6/lib"

# Runs the crosscompiler

${MIPS_BIN_DIR}/gcc -fno-builtin –nostdinc \

-I${MIPS_INC_DIR} \ -I${MIPS_SYS_DIR} \ -I${MIPS_TOOL_INC_DIR} \

-L${MIPS_LIB_DIR} $@

Come si evidenzia è necessario trascrivere la directory di installazione di OpenWRT nella variabile $STAGING_DIR; tale directory è, appunto, la directory staging_dir/ all’interno del percorso di compilazione.

I programmi che andremo a compilare, tuttavia, molto probabilmente useranno altri strumenti tra quelli generalmente presenti, quali ad esempio ar, ranlib, ld, nm, e strip.

La soluzione più semplice è renderli disponibili nella stessa directory in cui abbiamo posto il nostro wrapper attraverso link simbolici:

(28)

for i in ar ranlib ld nm strip ; do

ln -s ${MIPS_BIN_DIR}/$i \

${TOOLCHAINDIR}/mips-linux-$i done

Se la directory $TOOLCHAINDIR si trova all’interno della variabile d’ambiente $PATH, tutto ciò che resta da fare per compilare un programma esterno è cambiare il nome degli strumenti della toolchain, in modo che corrispondano a quelli del wrapper e dei collegamenti.

Effettuare questa operazione è però ancora una complicazione inutile; un espediente più pratico è quello di implementare ancora un altro wrapper che imposti automaticamente le variabili di ambiente per eseguire i comandi indicati, utilizzando il percorso della toolchain per reperire gli strumenti di compilazione.

Un approccio di questo tipo non richiede neanche che la directory della toolchain sia presente nel path, ma questa va specificata al suo interno.

Una possibile implementazione ad esempio è la seguente:

#!/bin/sh #

# Sets the environment variables to point # to the mipsel toolchain,

(29)

# and executes the remaining arguments. # Example Usage:

#

# with_mipsel make

# Directory containing the toolchain binaries # (or links to them)

# The install command will replace # the line ^BINDIR... with the

# proper path to the toolchain commands BINDIR=${TO_SET}

# Setting environment variables to define # the toolchain names

# PATH - we add the ${BINDIR} to the list export PATH=${PATH}:${BINDIR}

# host_alias - suggests 'configure' # we're crosscompiling

export host_alias=mips-linux

# gcc - GNU project C and C++ compiler export CC=${host_alias}-gcc

# ar - create, modify, and extract from archives export AR=${host_alias}-ar

# ranlib - generate index to archive export RANLIB=${host_alias}-ranlib

(30)

# ld - links the object files export LD=${host_alias}-ld

# nm - list symbols from object files export NM=${host_alias}-nm

# strip - discard symbols from object files export STRIP=${host_alias}-strip

# Reads the command to execute CMD=$1

shift

# Runs the command echo running $CMD $@ sleep 1

$CMD $@

Come si nota, il wrapper non fa altro che eseguire nella shell i propri parametri, premurandosi di impostare preventivamente alcune variabili.

La variabile “host_alias” è una variabile utilizzata da autoconf per lo stesso scopo del parametro --host; indica che si sta eseguendo la configurazione per un sistema diverso da quello che ‘configure’ rileva, e quindi i controlli che richiedono l’esecuzione del programma generato dal compilatore non possono essere effettuati.

(31)

Specificare questa variabile nel wrapper ci consente di non specificare l’opzione --host quando si invoca configure, e rende la crosscompilazione alquanto trasparente.

Salvo la necessità di specificare preventivamente il nome del wrapper (che abbiamo chiamato “with_mipsel”), i comandi da dare per configurare, compilare e installare un programma per la piattaforma mips-linux sono gli stessi che si darebbero normalmente sul sistema ospite.

(32)

CoMo

Per raccogliere e presentare i dati raccolti dai sensori utilizziamo CoMo (http://como.sourceforge.net/) nella versione 1.5 da svn (http://como.svn.sourceforge.net/svnroot/como/src/trunk).

Si tratta di un’applicazione di monitoraggio del traffico di rete per sistemi Unix e Unix-like facilmente configurabile e soprattutto adattabile ai nostri scopi, grazie alla sua struttura modulare.

CoMo supporta inoltre la crosscompilazione per ARM, una piattaforma i cui requisiti di allineamento non sono dissimili da quelli del processore MIPS della piattaforma hardware.

Tuttavia, nonostante le porzioni di codice dedicate al supporto per questa famiglia di processori i sorgenti producono diversi errori e warning quando li si compila con GCC, e alcune modifiche si rendono necessarie per garantire il corretto allineamento delle strutture dati.

Da notare che per compilare CoMo non possiamo usare semplicemente il wrapper “with_mipsel”, perché CoMo non è basato su autoconf, ma su CMake, un build system simile ma che mira ad essere molto più

(33)

configurabile, cross-piattaforma e con una sintassi relativamente semplice; a differenza di autoconf, CMake ignora le variabili di sistema, e per modificare i comandi dati in fase di compilazione è necessario modificare direttamente i suoi file di configurazione.

Innanzitutto, dobbiamo aggiungere lo switch BUILD_FOR_MIPS per fornire a CMake le informazioni sulla catena di compilazione da utilizzare. In CMakeLists.txt aggiungeremo quindi:

IF(BUILD_FOR_MIPS)

INCLUDE(${COMO_SOURCE_DIR}/cmake/mips.cmake) COMO_BUILD_FOR_MIPS()

ENDIF(BUILD_FOR_MIPS)

Con questi comandi chiediamo a CMake, qualora sia specificato lo

switch BUILD_FOR_MIPS, di invocare la macro

COMO_BUILD_FOR_MIPS() contenuta nel file cmake/mips.cmake. In questo file dobbiamo inserire i nomi degli strumenti di compilazione, in modo che individuino quelli della catena prodotta da OpenWRT, e possiamo inoltre impostare o modificare altre variabili che siano specifiche al caso di una compilazione per MIPS:

(34)

#

# Enable cross compilation for MIPS architecture #

# Note: this is designed for a crosscompilation # using OpenWRT's toolchain

# so it's very application specific

MACRO(COMO_BUILD_FOR_MIPS)

MESSAGE(STATUS "Cross compiling for MIPS-Linux") # Compiling with a Linux toolchain

# even if we're on FreeBSD SET(FREEBSD FALSE) SET(LINUX TRUE) SET(CMAKE_SYSTEM_NAME "Linux") # Compiler name SET(CMAKE_C_COMPILER "$ENV{CC}") SET(CMAKE_COMPILER_IS_GNUCC 1) SET(CMAKE_AR "$ENV{AR}") SET(CMAKE_RANLIB "$ENV{RANLIB}") # Macro ADD_DEFINITIONS(-DNEED_ALIGNED) ENDMACRO(COMO_BUILD_FOR_MIPS)

Il comandi MACRO() e ENDMACRO() individuano il codice relativo alla macro COMO_BUILD_FOR_MIPS, al cui interno poniamo le istruzioni per permettere a CMake di invocare il compilatore corretto.

(35)

In particolare, il primo gruppo di istruzioni SET() modifica le variabili di sistema che nel caso di crosscompilazione da BSD puntano a questo, mentre OpenWRT è una piattaforma Linux; il secondo gruppo specifica invece che come nome dei programmi gcc, ar e ranlib occorre utilizzare il contenuto delle variabili di ambiente CC, AR e RANLIB, recuperando sostanzialmente la funzionalità di autoconf di ricevere tali informazioni attraverso l’ambiente. Questo riabilita inoltre il wrapper che avevamo prodotto al termine dell’installazione di OpenWRT.

La macro “NEED_ALIGNED” indicherà nel codice del programma l’esigenza di tener conto dei problemi di allineamento del processore MIPS; poiché i problemi sono gli stessi di quelli del processore ARM, unifichiamo i due casi sotto questo stesso nome, cambiando ogni occorrenza di “BUILD_FOR_ARM” con “NEED_ALIGNED”:

sed -i '' 's|BUILD_FOR_ARM|NEED_ALIGNED|' */*.[ch]

A questo punto, occorre specificare a CMake di non utilizzare le directory di sistema per cercare header e librerie; questo passo può essere realizzato utilizzando variabili di sistema:

(36)

export P=/home/openwrt/staging_dir

export CMAKE_LIBRARY_PATH=$P/mipsel/usr/lib

export CMAKE_INCLUDE_PATH=$P/mipsel/usr/include

Possiamo facilmente includere queste definizioni nel wrapper with_mipsel in modo da non dover ripetere questo passaggio in successive compilazioni; inoltre, eventuali altri programmi che fanno uso di CMake avranno molto probabilmente bisogno delle stesse definizioni.

Perdiamo però la separazione tra i wrapper, ovvero è necessario registrare in with_mipsel la posizione dell’installazione di OpenWRT, precedentemente nota solo in mips-linux-gcc.

> with_mipsel cmake -DBUILD_FOR_MIPS=1 ../como1.5/ running cmake -DBUILD_FOR_MIPS=1 ../como1.5/

-- Check for working C compiler: /home/toolchain-bin/mips-linux-gcc

-- Check for working C compiler: /home/toolchain-bin/mips-linux-gcc -- works

-- Check size of void*

-- Check size of void* - done

-- Building from SVN revision 1236. -- Cross compiling for MIPS-Linux -- Found BISON: /usr/local/bin/bison -- Found FLEX: /usr/bin/flex

(37)

-- Found PCAP:

/home/openwrt/staging_dir/mipsel/usr/lib/libpcap.s o

-- Found SSLLIB:

/home/openwrt/staging_dir/mipsel/usr/lib/libssl.so -- CoMo build type is `debug`.

-- CoMo will be installed in `/usr/local`. Type `make` to build CoMo, `make install` to install CoMo.

Tip: you can get verbose output with `VERBOSE=1 make`.

-- Configuring done -- Generating done

-- Build files have been written to: /home/build_como

Invocando make notiamo subito una serie di warning relativi a problemi di allineamento.

> make

Scanning dependencies of target apps [ 1%] Building C object

modules/CMakeFiles/apps.dir/apps.o

/home/como1.5/modules/apps.c: In function `init':

(38)

/home/como1.5/modules/apps.c:170: warning: cast increases required alignment of target type

La riga incriminata è:

N16(TCP(src_port)) = 0xffff;

La funzione TCP() è definita in include/stdpkt.h:

#define TCP(field) \

((typeof(((struct _como_tcphdr *)NULL)->field) \ get_field((char *)&(((struct _como_tcphdr *) \ (pkt->payload + pkt->l4ofs))->field), \

sizeof(typeof(((struct _como_tcphdr *) \ NULL)->field))))

Mentre la funzione N16() in include/nettypes.h:

#define N16(x) ((x).__x16)

Questa funzione di accesso si limita a selezionare un particolare campo della struttura:

(39)

struct _n16_t { uint16_t __x16; };

Questa struttura, come è evidente, incapsula un valore 16 bit in formato di rete; attraverso l’incapsulamento è più semplice individuare accessi non mediati a questi, e prevenire confusione tra il formato nativo della macchina e quello di rete.

Il problema segnalato dal compilatore allude alla conversione:

(struct _como_tcphdr *)(pkt->payload + pkt->l4ofs)

che è indubbiamente insicura dal punto di vista dell’allineamento, in quanto l’allineamento di un array di char è sempre unitario, mentre quello di una struttura può variare.

Ma anche eludendo questo problema, inserendo banalmente una conversione a (void *) nel mezzo, c’è un problema più grave con la funzione:

__inline__ static uint64_t

get_field (char *ptr, size_t size);

(40)

Questa funzione restituisce un valore destro, che non può quindi essere oggetto di assegnamento. Possiamo quindi riformulare l’assegnamento in termini della funzione:

__inline__ static void

set_field (char *ptr, size_t size,

uint64_t value);

Ma queste funzioni appaiono alquanto complesse, e anche le macro con cui le si invoca sono notevolmente involute e ripetitive; lo stesso risultato si può più semplicemente ottenere affidandosi alle funzioni di libreria, che si possono considerare sicure relativamente alla piattaforma per cui sono progettate.

Possiamo quindi creare un header include/casts.h al cui interno definire una serie di macro per facilitare conversioni di questo tipo:

#ifndef _COMO_CASTS_H #define _COMO_CASTS_H

(41)

/* Dereference a pointer in a safe way (alignmentwise) */

#define DEREFERENCE(type, pointer) \

({ type __a, *__b = (type *)(void *)(pointer); \ bcopy(__b, &__a, sizeof(__a)); \

__a; })

/* Access fields of structs in a safe way (alignmentwise) */

#define GET_FIELD(type, pointer, field) \

({ type __c, *__d = (type *)(void *)(pointer); \ bcopy(&__d->field, &__c.field, \

sizeof(__c.field)); \ __c.field; })

#define SET_FIELD(type, pointer, field, value) \ ({ type __e, *__f = (type *)(void *)(pointer); \ __e.field = value; \

bcopy(&__e.field, &__f->field, \ sizeof(__f->field)); })

#define INC_FIELD(type, pointer, field, value) \ ({ type __g, *__h = (type *)(void *)(pointer); \ bcopy(&__h->field, &__g.field, \

sizeof(__g.field)); \ __g.field += value; \

bcopy(&__g.field, &__h->field, \

(42)

sizeof(__h->field)); })

/* Necessary to access net types (n16_t, n32_t) */ #define SET_FIELD16(type, pointer, field, value) \ ({ type __e, *__f = (type *)(void *)(pointer); \ N16(__e.field) = value; \

bcopy(&__e.field, &__f->field, \ sizeof(__f->field)); })

#define SET_FIELD32(type, pointer, field, value) \ ({ type __e, *__f = (type *)(void *)(pointer); \ N32(__e.field) = value; \

bcopy(&__e.field, &__f->field, \ sizeof(__f->field)); })

#else

#define DEREFERENCE(type, pointer) \ (*((type *)(pointer)))

#define GET_FIELD(type, pointer, field) \ (((type *)(pointer))->field)

#define SET_FIELD(type, pointer, field, value) \ (((type *)(pointer))->field = value)

#define INC_FIELD(type, pointer, field, value) \ (((type *)(pointer))->field += value)

#define SET_FIELD16(type, pointer, field, value) \ (N16(((type *)(pointer))->field) = value)

(43)

#define SET_FIELD32(type, pointer, field, value) \ (N32(((type *)(pointer))->field) = value)

#endif

#endif /* _COMO_CASTS_H */

Come si vede nulla cambia per le piattaforme non allineate (che non definiscono quindi la macro NEED_ALIGNED), mentre le piattaforme allineate possono usare queste macro basate su bcopy(), che essendo una funzione di sistema non presenterà problemi di allineamento.

A questo punto l’assegnamento in questione:

N16(TCP(src_port)) = 0xffff;

Può essere riscritto come:

TCPX16(pkt, src_port, 0xffff);

Laddove TCPX16() è una macro che incapsula le funzionalità di N16() e TCPX(), ed è definita come:

(44)

#define TCPX16(pkt, field, value) \ (SET_FIELD16(struct _como_tcphdr, \

pkt->payload + pkt->l4ofs, field, value))

Questa soluzione è più semplice, più facile da interpretare, ed è meno “oscura” della precedente, nella quale il parametro pkt era implicito e nascosto. Possiamo riscrivere in questa forma tutte le funzioni di accesso alle strutture.

Qualora, invece, il campo fosse un array, si pone un problema di prestazioni, in quanto l’operatore sizeof() applicato ad un array ne restituisce la dimensione, e la funzione bcopy() copierebbe l’intero array. In alcuni casi questo può essere un comportamento voluto, ma nel codice si accede all’intero array solo quando è necessario confrontarlo con un altro, e quando ciò accade tutto ciò che serve è il puntatore al primo elemento, che andrà poi passato a funzioni di libreria come memcmp() o bcopy(); poiché possiamo considerare queste funzioni sicure, passare preventivamente da una copia rappresenterebbe un degrado inutile delle prestazioni.

/*Warning: we're returning an unaligned pointer */ #define ETH_ARRAY(pkt, field) \

(45)

((typeof(((struct _como_eth *)NULL)->field[0]) *)\ ((struct _como_eth *)(void *) \

(pkt->payload + pkt->l2ofs))->field)

La conversione a puntatore al primo elemento è certamente superflua nel caso il campo da accedere sia realmente un array, ma aiuta ad individuare accessi non allineati ad altri campi.

Il commento che precede la definizione ci ricorda semplicemente di non utilizzare questa macro allorché ci sia bisogno di un puntatore allineato.

Un caso più delicato si ha quando il campo da accedere sia un bit-field: in tal caso non è dato sapere, anche se facilmente prevedibile, su quanti e quali bit venga memorizzato il dato, e l’uso dell’operatore sizeof() su questi campi genera errori.

Se la struttura è composta solo o quasi da bit-field, la soluzione è semplice: ad esempio, la struttura (definita così nel caso big endian)

struct _ieee80211_frame_control { uint16_t subtype:4; uint16_t type:2; uint16_t version:2; uint16_t order:1; 44

(46)

uint16_t wep:1; uint16_t more_data:1; uint16_t power_management:1; uint16_t retry:1; uint16_t more_fragments:1; uint16_t from_ds:1; uint16_t to_ds:1; };

occupa solo due byte, e può essere trasferita interamente come un campo a 16 bit.

Naturalmente un assegnamento sarebbe più complesso, in quanto richiederebbe di allocare una copia della struttura, modificare il bit in questione, e poi invocare la macro SET_FIELD.

I campi ihl e version dell’header IPv4, invece, non sono isolati all’interno di una struttura dati, ma coesistono insieme ad altri campi nella struttura _como_iphdr, e una soluzione come quella appena vista, ovvero trasferire l’intera struttura _como_iphdr, comporterebbe un consumo di memoria eccessivo. È tuttavia possibile isolare questi campi in una sottostruttura che possa essere trasferita con la stessa tecnica:

(47)

struct _como_iphdr { struct ip_misc { #ifdef COMO_LITTLE_ENDIAN uint8_t ihl:4; uint8_t version:4; #else uint8_t version:4; uint8_t ihl:4; #endif } flags; […]

Lo stesso si può fare per i flags dell’header TCP:

struct _como_tcphdr { n16_t src_port; n16_t dst_port; n32_t seq; n32_t ack_seq; struct misc { #ifdef COMO_LITTLE_ENDIAN uint16_t res1:4; uint16_t hlen:4; uint16_t fin:1; uint16_t syn:1; uint16_t rst:1; 46

(48)

uint16_t psh:1; uint16_t ack:1; uint16_t urg:1; uint16_t ece:1; uint16_t cwr:1; #else uint16_t hlen:4; uint16_t res1:4; uint16_t cwr:1; uint16_t ece:1; uint16_t urg:1; uint16_t ack:1; uint16_t psh:1; uint16_t rst:1; uint16_t syn:1; uint16_t fin:1; #endif } flags;

Altre conversioni, come quelle tra puntatori a FLOWDESC/EFLOWDESC e puntatori a char, possono essere invece considerate sicure dal punto di vista dell’allineamento, in quanto gli array di char vengono utilizzati come buffer per la struttura, ma la struttura in essi contenuta resta allineata; per risolvere i warning lanciati dal

(49)

compilatore è sufficiente introdurre preventivamente una conversione in puntatore a void.

#define F(x) \

((FLOWDESC *)(void *)(((char *) x) + \ sizeof(rec_t)))

#define EF(x) \

((EFLOWDESC *)(void *)(((char *) x) + \ sizeof(rec_t)))

Un altro problema, di natura ben differente, si ha nel file lib/bitmap.c, in cui compaiono delle ottimizzazioni assembler che però funzionano solo su processori della famiglia Intel x86; nella funzione set_bit() compare parte del codice senza ottimizzazioni sotto lo switch BUILD_FOR_ARM, seppure con alcuni refusi facilmente risolvibili; la funzione test_and_set_bit(), invece, non prende in considerazione il caso non allineato, ed è necessario introdurre il codice non ottimizzato:

int

test_and_set_bit(bitmap_t *bm, uint32_t key) {

int bit = key & (bm->nbits - 1); int where, what, old;

(50)

where = which_byte(bit); what = which_bit(bit);

#ifndef NEED_ALIGNED /*

* asm test and set operation */ asm volatile("btsl %2, %1\n\tsbbl %0, %0\n\t" : "=r" (old), "=m" (bm->map[where]) : "r" (what)); if (old == 0) bm->zeros--; #else

old=bm->map[where] & (1 << what); if (0 == old) { bm->zeros--; bm->map[where] |= 1 << what; } #endif return old; }

(51)

L’ultimo piccolo problema che viene ancora segnalato dal compilatore è in sniffers/sniffer-radio.c, alla riga:

int mod_chan = (int) rintf(in_chan / 1000000);

Il problema qui è che la funzione "float rintf(float)" non è definita da uClibc, la libreria del C utilizzata da OpenWRT; è però disponibile la funzione "double rint(double)", il cui utilizzo non sembra comportare problemi:

int mod_chan = (int) rint (in_chan / 1000000);

Con queste modifiche, CoMo può essere compilato per piattaforma MIPS; tuttavia, dobbiamo fare in modo che l’installazione avvenga in una directory indipendente, in modo da poter trasferire l’intero albero di directory sul filesystem del router. A tal fine, CMake mette a disposizione la variabile CMAKE_INSTALL_PREFIX, ma il percorso in cui CoMo andrà a cercare per default il file di configurazione e i moduli è comprensivo di questa variabile, perciò è preferibile annullare questa variabile e introdurne un’altra che non venga utilizzata per formare i percorsi di default: definiamo la variabile STAGING_DIR, da utilizzare

(52)

esclusivamente come prefisso per l’installazione. È sufficiente sostituire, nei files CMakeLists.txt, base/CMakeLists.txt, man/CMakeLists.txt, modules/CMakeLists.txt, tutte le linee che iniziano con “INSTALL_FILES(…)” con “INSTALL_FILES(${STAGING_DIR}…)”. Con questa modifica, possiamo procedere a installare CoMo in una nostra directory locale:

with_mipsel cmake -DSTAGING_DIR=${HOME}/croot \ -DCMAKE_INSTALL_PREFIX='' -DBUILD_FOR_MIPS=1 \ ../como

with_mipsel make

with_mipsel make install

La directory prodotta, ~/croot, può essere copiata per intero su un dispositivo di memoria esterno, pronta per essere ricopiata sul filesystem del router.

(53)

Librerie esterne

Per eseguire nuove query sul traffico, è generalmente necessario aggiungere nuovi moduli per CoMo; può talvolta sorgere la necessità di compilare delle librerie esterne per la piattaforma MIPS.

La compilazione di codice sorgente con il crosscompilatore mips-linux-gcc risulta alquanto naturale; l’unica nota di rilievo è che nei sistemi basati su AutoConf, lo script “configure” da questo costruito tenderebbe a controllare che l’eseguibile prodotto vada a termine correttamente, mentre un eseguibile crosscompilato non può essere caricato correttamente sulla piattaforma che lo ha generato. Indicando l’opzione --host=mips-linux lo script si adegua e salta questa verifica; lo stesso risultato si può ottenere definendo la variabile host_alias:

# host_alias –

# suggests 'configure' we're crosscompiling export host_alias=mips-linux

# gcc - GNU project C and C++ compiler export CC=${host_alias}-gcc

(54)

# ar - create, modify, and extract from archives export AR=${host_alias}-ar

# ranlib - generate index to archive export RANLIB=${host_alias}-ranlib # ld - links the object files

export LD=${host_alias}-ld

# nm - list symbols from object files export NM=${host_alias}-nm

# strip - discard symbols from object files export STRIP=${host_alias}-strip

Ora scarichiamo ed estraiamo i sorgenti che intendiamo compilare (nell’esempio libgd):

fetch \

http://www.libgd.org/releases/gd-2.0.35.tar.gz tar zxvf gd-2.0.35.tar.gz

cd gd-2.0.35

A questo punto configuriamo, compiliamo e installiamo; il prefisso ${P} sarà il percorso assoluto di una directory in cui installare i files, poiché anche la libreria, come già visto per CoMo, deve essere trasferita ricopiando l’intero albero di directory:

(55)

P=${HOME}/libroot

with_mipsel ./configure --prefix ${P} && \ with_mipsel make && \

with_mipsel make install

Affinché mips-linux-gcc includa automaticamente gli header e le librerie generate, si può modificare lo script aggiungendo le opportune opzioni –I e –L, oppure copiare gli header e le librerie in uno dei percorsi inclusi dallo script (con lo svantaggio di “sporcare” l’installazione di OpenWRT).

(56)

Installazione

Per installare l’immagine sul dispositivo hardware, occorre disconnetterlo dall’alimentazione e poi riconnetterlo tenendo premuto il tasto RESTORE per alcuni secondi; il router in questo modo entra in modalità diag e assume l’indirizzo 192.168.1.1 o 192.168.1.2 (si può verificare con un ping).

Una volta individuato l’indirizzo corretto, vi si può inviare l’immagine openwrt-brcm-2.4-squashfs.trx attraverso tftp in modalità binaria; al termine del caricamento, occorre aspettare alcuni minuti perché venga installata, dopo i quali, se il router non si riavvia automaticamente, è possibile staccare e ricollegare l’alimentazione.

Al riavvio, possiamo entrare in telnet sulla nuova piattaforma OpenWRT; una volta all’interno, colleghiamo un dispositivo di memoria USB contenente la root directory di CoMo e lo montiamo. Una semplice copia è sufficiente per installarlo.

mount /dev/discs/disc0/part1 /mnt/ cp -Rf /mnt/local_root/* /

(57)

Poiché CoMo si aspetta di trovare una libreria condivisa di nome libpcap.so, dobbiamo creare un collegamento simbolico a questo nome.

ln -s /usr/lib/libpcap.so.0.9.4 /usr/lib/libpcap.so

Dobbiamo ora modificare il file /etc/como/como.conf, facendo sì che la variabile db-path punti a una directory sul filesystem della memoria USB e che memsize sia impostato a 16 bytes.

# Directory where the output data of all modules # reside.

# Default: /share/como-1.0-1236

db-path "/mnt/share"

# Memory (in MBytes) allocated to the CAPTURE # process

# to maintain the state used by all modules. # The more modules, the more memory is needed. # Default: 64

memsize 16

(58)

Se vogliamo che CoMo operi sull’effettivo traffico di rete, dobbiamo commentare la linea relativa allo sniffer pcap e decommentare quella relativa allo sniffer libpcap, specificandovi l’interfaccia di rete opportuna (br-lan, eth0.0 o eth0.1).

# libpcap - Captures live from a device

# using libpcap.

sniffer "libpcap" "br-lan" \ "snaplen=112 promisc=1 timeout=1"

# sniffer "pcap" "/etc/como/example-trace.pcap"

Per testare il bluetooth dobbiamo installare bluez: occorre quindi modificare il percorso su cui reperire i pacchetti in /etc/ipkg.conf.

src snapshots http://downloads.openwrt.org/ kamikaze/packages/mipsel

Ora aggiorniamo la lista dei pacchetti disponibili e installiamo bluez:

ipkg update

(59)

Modifichiamo i file /etc/bluetooth/hcid.conf (occorre specificare qui il nome di rete del dispositivo) e /etc/bluetooth/givepin (indicando il pin di rete), e possiamo effettuare una scansione dei dispositivi con:

hciconfig hci0 up hcitool scan

Al momento, tuttavia, CoMo non dispone di sniffer per intercettare traffico su un’interfaccia bluetooth.

Possiamo verificare il funzionamento del nostro sistema di monitoraggio decommentando un modulo (ad esempio “tuple”) in /etc/como/como.conf e avviando como.

(60)

Possiamo interrogare il sistema di monitoraggio puntando il browser sulla porta 44444 del router e chiedendo i dati di un modulo; per conoscere lo stato e parte della configurazione di CoMo è possibile utilizzare “status” come nome del modulo:

CoMo ci dice, tra le altre cose, che è attivo il modulo “tuple”, che elenca la tupla (protocollo, indirizzo IP e porta sorgente, indirizzo IP e porta destinatario) dei flussi attivi, che supporta i formati di visualizzazione “plain”, “pretty”, “html” e “como”: proviamo ad esempio ad effettuare una query su questo modulo con formato “pretty”.

(61)

È possibile attivare numerosi altri moduli in /etc/como/como.conf, ma è soprattutto relativamente agevole scrivere nuovi filtri per differenti query sui dati raccolti.

(62)

Conclusioni

Abbiamo visto come costruire una piattaforma di monitoraggio continuo del traffico, su un dispositivo di rete embedded, che è possibile collegare a qualunque rete ci interessi tenere sotto controllo; abbiamo utilizzato materiali e software facilmente reperibili sul mercato, individuando una soluzione semplice, che può essere raggiunta a costi contenuti e potenziabile con tempi e risorse minimi. In particolare, la piattaforma software è facilmente adattabile a diverse esigenze, ricompilando il kernel di OpenWRT per gestire hardware aggiuntivo e scrivendo nuovi moduli di sniffing e filtraggio per CoMo.

Successivo lavoro dovrebbe essere orientato ad aumentare il numero di sniffer, in modo da rendere disponibili più mezzi di trasmissione wired e wireless, e filtri, permettendo interrogazioni più complesse e dettagliate sui dati raccolti.

Qualora lo scenario lo richiedesse, un modulo sniffer può essere progettato in modo da agire ben differentemente da uno sniffer tradizionale, ad esempio aprendo una porta sul router per ricevere direttamente i dati. Un

(63)

approccio di questo tipo perde in trasparenza, ma può essere utile quando la presenza di un sistema di controllo e monitoraggio dei dati è una parte fondamentale del sistema.

Ad esempio, in un’applicazione di tipo medico, il sistema può raccogliere informazioni di tipo ambientale (attraverso sensori collegati alla porta seriale o in rete) e individuali (magari attraverso sensori posti sui pazienti o trasmettitori collegati ai consueti macchinari di monitoraggio), da sottoporre al personale medico; in un sistema industriale, i dati raccolti possono essere ancora ambientali (temperatura, umidità, presenza di gas nell’atmosfera), ma anche relativi al funzionamento dei sistemi automatici o alla presenza di personale in determinate aree, per fornire agli addetti strumenti aggiuntivi di vigilanza e prevenzione infortunistica, oltre a permettere un’ottimale pianificazione degli interventi di manutenzione.

In tutti questi casi, in cui il sistema di monitoraggio non sia solo uno spettatore passivo del sistema ma anche un valido strumento di controllo, i moduli di raccolta dei dati possono essere strutturati per interrogare periodicamente le sorgenti delle informazioni necessarie, e i moduli di presentazione per mostrarli in forma diversamente aggregata ai vari soggetti che vi hanno accesso.

(64)

Altri settori nel quale è possibile migliorare il sistema di monitoraggio sono quelli di autenticazione, controllo degli accessi, e crittografia della base di dati.

A causa di normative sulla privacy o altre ragioni di riservatezza, infatti, è probabile che si desideri limitare l’accesso ad alcune delle informazioni contenute nella base di dati; i diversi utenti che utilizzano il sistema saranno autorizzati a visualizzare solo alcune tipologie di dati, o particolari loro livelli di aggregazione (ad esempio, visualizzare alcune statistiche ma non i dati da cui sono state tratte).

In tali scenari sarebbe opportuno identificare il personale autorizzato tramite meccanismi basati su password o certificati, e limitare le interrogazioni che è loro permesso eseguire tramite ACL (Access Control List). Imporre inoltre una crittografia sulla base di dati impedirebbe anche ad eventuali amministratori del sistema di scavalcare i controlli ed estrarre le informazioni riservate direttamente dai file.

Riferimenti

Documenti correlati

■ Quindi il vero argomento della prima funzione non é la seconda funzione, ma un normale valore, che può avere qualsiasi origine (variabile, espressione ecc...), e in particolare

un publisher per comunicare al robot come intendiamo trasformare i comandi provenienti dalla tastiera in comandi di velocità.. comandi

Molto spesso il polo nell’origine, semplice o doppio, viene volutamente introdotto nella funzione di trasferimento del regolatore (posto a monte del sistema controllato), al fine

Secondo questo metodo gli oggetti più recenti si trovano vicini alla superficie del terreno e quelli più antichi sono più in profondità.. Con i fossili di tipo organico, quelli

La pubblicazione può essere statica (i dati estratti dal database diventano una pagina web, HTML, statica, un file statico di tipo snapshot)o dinamica (l’utente vede i dati

Sebbene la maggior parte delle informazioni del dominio (come utenti e gruppi) venga replicata soltanto sul controller all'interno del dominio stesso, Active Directory replica

• Tolta dagli altri rifiuti ne elimina gli odori - se in casa si fa bene la raccolta dell’organico si è fatto più del 50% del lavoro;!. &gt;SE SI FA BENE LA RACCOLTA DELL’ORGANICO

In questo lavoro si vuole approfondire il tema del costo delle materie prime attraverso un approccio basato su due prospettive principali: la prima riguarda l’analisi dei