La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

Lez. 101 Reti di Calcolatori RPC - Remote Procedure Call Vedi: W.R. Stevens, Unix Network Programming, Prentice Hall, sezz. 18.1- 18-3, pagg. 692-709.

Presentazioni simili


Presentazione sul tema: "Lez. 101 Reti di Calcolatori RPC - Remote Procedure Call Vedi: W.R. Stevens, Unix Network Programming, Prentice Hall, sezz. 18.1- 18-3, pagg. 692-709."— Transcript della presentazione:

1 Lez. 101 Reti di Calcolatori RPC - Remote Procedure Call Vedi: W.R. Stevens, Unix Network Programming, Prentice Hall, sezz , pagg SunSoft, Network Interfaces Programmer's Guide, parte 2 (solo alcune parti) e app. C. RFC 1057, RPC: Remote procedure Call protocol Specification – Version 2, Copyright © by Claudio Salati. Alma Mater Studiorum - Universita' di Bologna Sede di Cesena

2 2 Fino dall’inizio dell’informatica la tecnica principale per la scomposizione di un programma in piu’ moduli e’ consistita nella definizione di sotto-programmi (subroutine, procedure, funzioni,...) e nella possibilita’ da parte di un sotto-programma (chiamante) di attivare l’esecuzione di (chiamare) un altro sotto-programma (chiamato) Tutti i processori HW supportano direttamente la nozione di trasferimento di controllo collegato ad una chiamata di procedura mettendo a disposizione una istruzione di JumpToSubroutine (o Call, o BranchAndLink, o …) l’istruzione duale di Return (o Ret, o …) Associata alla nozione di chiamata di procedura c’e’ anche quella di trasferimento di informazioni, cioe’ del passaggio di argomenti di ingresso e di risultati di ritorno tra la procedura chiamante e quella chiamata  I meccanismi per il passaggio dei parametri non sono legati al processore HW ma al sistema di programmazione Il collegamento tra la procedura chiamante e quella chiamata, in un ambiente di programmazione locale, e’ di norma realizzato dal linker (o dal compilatore) che costruisce il programma eseguibile complessivo assemblando tutti i moduli che lo compongono Local Procedure Call

3 3 Di norma un modulo non e’ costituito da una singola procedura, ma da un insieme di procedure correlate, ad esempio quelle che definiscono un particolare tipo di dato astratto (ADT / classe). Nella libreria standard C sono ad esempio presenti i moduli stdio, che definisce l’ADT FILE, e che comprende le procedure fopen(), fclose(), fread(), fwrite(), fgetc(), fputc(), fprintf(), fscanf(),... string, che definisce l’ADT stringa di caratteri, e che comprende le procedure strcpy(), strcat(), strcmp(), strtok(),... Il linker non collega tra loro singole procedure ma interi moduli, quindi, se collega un modulo, collega tutte le procedure definite nel modulo. Il servizio offerto da un modulo e’ descritto da una interfaccia che, prendendo come esempio il linguaggio C, e’ descritta in uno header file (.h) dedicato, uno per ciascun modulo. Moduli e procedure

4 4 The caller places arguments to a procedure in some well-specified location (such as a register window). It then blocks and transfers control to the procedure.  tramite istruzione Call The callee performs its job, puts its results in some well-specified location, and transfers control back to the caller procedure.  tramite istruzione Return The caller regains control (all’istruzione successiva alla Call ). At that point, the results of the procedure are extracted from the well-specified location, and the caller continues execution. Local Procedure Call model

5 5 Procedura chiamante Call ProceduraChiamata(); Procedura chiamata Return; Input parameters passing Result parameters passing Result parameters extraction

6 6  N.B.: c’e’ una assunzione implicita: la rappresentazione concreta dell'informazione e’ la stessa per chiamante e chiamato. Il chiamato sa interpretare la rappresentazione concreta dell’informazione dei parametri d’ingresso (ricevuta dal chiamante). Il chiamante sa interpretare la rappresentazione concreta dell’informazione dei parametri di ritorno (ricevuta dal chiamato). Quando, nella programmazione locale, si devono collegare moduli scritti in diversi linguaggi, occorre gestire esplicitamente l’adattamento delle rappresentazioni concrete dei dati, se queste non sono omogenee. Ad esempio, nel caso della rappresentazione concreta delle matrici a piu’ dimensioni, che in Fortran e’ per colonne, e in C e in Pascal e’ per righe. Local Procedure Call model

7 7 In una chiamata di procedura remota (RPC) un processo su un sistema invoca una procedura che e’ eseguita su un sistema remoto. La procedura remota potrebbe anche essere eseguita sullo stesso sistema su cui e’ eseguito il processo chiamante, ma sarebbe comunque eseguita in un contesto di esecuzione diverso (da un diverso processo). La ragione per cui una interazione di questo genere e’ denominata chiamata di procedura e’ che sia dal punto di vista della procedura chiamata, sia, soprattutto, dal punto di vista della procedura chiamante, vogliamo che l'interazione sia quanto piu’ simile possibile ad una chiamata di procedura locale. In realta’ l’essere remota rende l’interazione sostanzialmente diversa da una chiamata di procedura locale. 1.Sistemi di elaborazione (HW e sistemi operativi) eterogenei 2.Linguaggi e sistemi di programmazione eterogenei 3.Problemi di comunicazione 4.Problemi di sicurezza Remote Procedure Call

8 8 The RPC model is similar to the local procedure call model. One thread of control logically winds through two processes: the caller’s process, and a server’s process. The caller process first sends a call message to the server process and waits (blocks) for a reply message. The call message includes the procedure’s parameters, and the reply message includes the procedure’s results.  Ma in realta’ abbiamo visto che la definizione del PDU rpc_msg del protocollo Sun RPC v2 non prevede, se non in termini di place holders, la presenza dei parametri di ingresso e di ritorno! Once the reply message is received, the results of the procedure are extracted, and caller’s execution is resumed. On the server side, a process is dormant awaiting the arrival of a call message. When a call message arrives, the server process extracts the procedure’s parameters, computes the results, sends a reply message, and then awaits the next call message. In this model, only one of the two processes is active at any given time. Remote Procedure Call model

9 SendCallMessage( ProceduraChiamata, InputParams); ReceiveReplyMessage( OutputParams); 9 Remote Procedure Call model Procedura chiamante Procedura chiamata Return; Input parameters passing Result parameters passing Result parameters extraction ReceiveCallMessage( ProceduraChiamata, InputParams); Processo chiamato SendReplyMessage( OutputParams); Call ProceduraChiamata(); call message reply message Local Procedure Call Call ProceduraChiamata();

10 10 Schema dell'interazione RPC Client stub Client RPC protocol entity Transport Service Client routine Server stub Server RPC protocol entity Transport Service Server routine RPC middleware Call Return RPC protocol

11 11 Comunicazioni reali: Comunicazioni virtuali: 1.Il cliente (chiamante) chiama una procedura locale, detta client stub. Dal punto di vista del chiamante essa rappresenta la procedura remota che lui vuole chiamare. 2.Il client stub encodifica (serializza) i parametri di input della chiamata (di cui conosce il tipo) e li passa, insieme a un identificatore della procedura chiamata, all’entita’ di protocollo RPC. L’operazione di serializzazione dei parametri e’ chiamata marshaling. 3.L’entita’ di protocollo RPC crea il PDU di chiamata e lo comunica al suo pari remoto utilizzando il Servizio di Trasporto (4, 5). 6.L'entita’ di protocollo RPC remota, che era in attesa di PDU di chiamata, riceve il PDU e ne estrae l’identificatore della procedura chiamata e i parametri di ingresso (questi in forma ancora serializzata). In base al valore dell’identificatore l’entita’ chiama il server stub della procedura chiamata passandogli i parametri di ingresso, ancora in forma serializzata. Schema dell'interazione RPC: legenda.1

12 12 7.Il server stub deserializza (decodifica o unmarshaling) i parametri di ingresso (di cui conosce il tipo) e chiama la procedura remota tramite una normale chiamata di procedura locale. 8.La procedura chiamata inizia la propria esecuzione. Quando la procedura (remota) termina la propria esecuzione ritorna il controllo al suo chiamante, il server stub, e gli passa i valori di ritorno previsti. 9.Il server stub effettua il marshaling (encodifica, serializzazione) dei valori di ritorno e ritorna il controllo all’entita’ di protocollo RPC. 10.L’entita’ di protocollo RPC crea il PDU di risposta e lo comunica al suo pari utilizzando il Servizio di Trasporto (11, 12). 13.L’entita’ di protocollo RPC chiamante, che era in attesa del PDU RPC di risposta, riceve il PDU, ne estrae i parametri di ritorno (ancora in forma serializzata), e li passa al client stub. 14.Il client stub deserializza (decodifica o unmarshaling) i parametri di ritorno (di cui conosce il tipo), e ritorna il controllo alla procedura chiamante passandole i risultati e terminando la chiamata. Schema dell'interazione RPC: legenda.2

13 13 1.Il Layer 7.1 si occupa di realizzare in rete le istruzioni Call e Return, che nella programmazione concentrata sono implementate direttamente dal processore HW. 2.Il Layer 7.2 si occupa del passaggio dei parametri, cosi’ come nel contesto della programmazione concentrata se ne occupa il sistema di programmazione. 3.Il Layer 7.2 si occupa anche di fornire al programma utente (modulo chiamante/client routine e modulo chiamato/server routine) una interfaccia reale (operazioni 1, 14, 7, 8) quanto piu’ simile possibile all’interfaccia virtuale (RPC Call e Return ). La procedura chiamante invoca la procedura remota tramite una normale chiamata di procedura locale, chiamata a una procedura locale (client stub) che rappresenta e ha la stessa signature di quella remota. La procedura remota chiamata e’ attivata tramite una normale chiamata di procedura locale. Schema dell'interazione RPC: riassumendo

14 14 Schema dell'interazione RPC: moduli vs. procedure Client stub... Client RPC protocol entity Transport Service Modulo chiamante Server stub... Server RPC protocol entity Transport Service Modulo chiamato RPC middleware Call Return RPC protocol RPC cs1 RPC csn RPC cs2 RPC ss1 RPC ss2 RPC ssn RPC 1 RPC 2 RPC n

15 15 Il dialogo reale chiamante-chiamato visto dai moduli utente chiamata: operazioni 1 e 7 ritorno: operazioni 8 e 14 deve essere quanto piu’ simile possibile al dialogo virtuale corrispondente chiamata: operazione Call ritorno: operazione Return Notare che l’operazione virtuale Call (anche la Return !) e’ magica: lato chiamante, la corrispondente operazione reale (1) e’ in accordo al sistema di elaborazione e di programmazione del chiamante lato chiamato, la corrispondente operazione reale (7) e’ in accordo al sistema di elaborazione e di programmazione del chiamato Obbiettivo di RPC

16 // client stub (Layer 7.2) retParams remoteProc(paramsT params, handleT handle) { encPar = encode(params); // sa come si fa encRes = RPCCall(remoteProcId, encPar, handle); // interazioni 2 e 13 results = decode(encRes); // sa come si fa return results; } // client routine. Comprende la funzione main() handleT handle; // link dinamico all’ handle = linkTo(RPCServer); // interfaccia remota while (notDone) {... retArgs = remoteProc(args, handle); // interazioni 1 e } exit(); 16 Schema di programma RPC: lato client

17 // RPC protocol entity lato chiamante (Layer 7.1) handleT linkTo(serviceT RPCServer) { // link dinamico all’interfaccia remota // set-up delle risorse di comunicazione... } opaque RPCCall(procIdT remoteProc, opaque encPar, handleT handle) { rpc_pdu = compileEncode(CALL, remoteProc, encPar); sendTo(rpc_pdu, handle); // di CALL // interazione 3 receiveFrom(&rpc_pdu, handle); // di REPLY // interazione 12 encRes = checkDecodeClient(rpc_pdu); return encRes; // ancora in forma codificata } 17 Schema di programma RPC: lato client

18 // server stub (Layer 7.2) opaque serverStub(procIdT proc, opaque encPars) { switch (proc) {... case remoteProcId : { args = decode(encPars); // sa come si fa results = remoteProc(args); // interazioni 7 e 8 encRes = encode(results); // sa come si fa return encRes; // gia’ codificati }... } // server routine (procedura passiva) retParamsT remoteProc(paramsT params);... return retArgs; } 18 Schema di programma RPC: lato server

19 // RPC protocol entity lato chiamato (Layer 7.1) handleT RPCSetUp(serviceT RPCServer, … serverStub) { // registrazione dell’interfaccia remota // set-up delle risorse di comunicazione... } void RPCWaitCall(handleT handle) { receiveFrom(&rpc_pdu, handle); // di CALL // interazione 5 {procId, encArgs} = checkDecodeServer(rpc_pdu); // encArgs e encRes in forma codificata encRes = handle.serverStub(procId, encArgs); // interazioni 6 e 9 rpc_pdu = compileEncode(RETURN, procId, encRes); sendTo(rpc_pdu, handle); // di REPLY // interazione 10 } 19 Schema di programma RPC: lato server

20 // il server non e’ solo un insieme di procedure // passive: deve aspettare attivamente l’arrivo di // messaggi di chiamata dalla rete, quindi deve essere // implementato come un processo. Quindi: // il server deve avere anche la sua funzione main() // server process skeleton (Layer 7.2) int main(...) { handle = RPCHandleSetUp(RPCServer, serverStub); // potrei voler registrare diverse interfacce while (1) { RPCWaitCall(handle); } 20 Schema di programma RPC: lato server

21 21 Gli stub sono legati al particolare servizio RPC. Per questo: Sono in grado di riconoscere e gestire in modo appropriato la particolare chiamata di RPC (la particolare procedura chiamata). Conoscendo la particolare procedura RPC chiamata conoscono i tipi dei parametri di chiamata e di ritorno e sono quindi in grado di de/codificarli. La protocol entity RPC e’ indipendente dal particolare servizio RPC. Il lato server, a livello di applicazione utente, e’ passivo: dal punto di vista del modulo utente esso e’ costituito solo da un insieme di procedure. Ma per aspettare messaggi di Call dalla rete bisogna essere attivi! Ogni processo ha bisogno di una funzione main() ! Il process skeleton lato server fornisce questa funzione main(). Lo skeleton lato server, facendo da collante, e’ legato al particolare servizio RPC e alla particolare interfaccia (API utente) che deve essere esportata per renderlo disponibile. Lo skeleton lato server e’ parte dell’infrastruttura RPC. Lato client la funzione main() e’ una normale funzione utente. Schema di programma RPC: note

22 22 Schema di programma : lato server Process skeleton Server stub... Server RPC protocol entity Modulo chiamato RPC middleware RPC ss1 RPC ss2 RPC ssn RPC 1 RPC 2 RPC n

23 23 Nella costruzione di applicazioni distribuite il meccanismo dell’RPC nasconde la complessita’ dell'interfacciamento alla rete: Le procedure utente client e server non si devono preoccupare di interagire con socket e Layer di Trasporto:  Ci pensa la protocol entity RPC (Layer 7.1). Le procedure utente client e server non si devono preoccupare delle diverse modalita’ di rappresentazione locale dell’informazione e dell’un/marshaling (dell’interazione con il Layer di Presentazione):  Ci pensano gli stub (Layer 7.2). Scrivere un’applicazione distribuita dovrebbe risultare altrettanto facile che scrivere un’applicazione concentrata. I principi per la scomposizione in moduli di un’applicazione distribuita sono identici a quelli utilizzati nella programmazione concentrata (in linea di principio: quanto costa una chiamata RPC?). I meccanismi di interazione tra moduli che si utilizzano nella programmazione distribuita sono gli stessi utilizzati nella programmazione concentrata (ma con un costo ben diverso!). Perche’ RPC

24 24 Secondo il modello OSI, RPC e’ un servizio dell’Application Layer. RPC utilizza i servizi del Presentation Layer per due scopi: 1.Per descrivere e comunicare i PDU del protocollo RPC stesso.  Protocollo del Layer 7.1 (e.g. protocollo Sun RPC v2). 2.Per descrivere e comunicare i parametri di ingresso e di uscita delle chiamate.  Protocollo del Layer 7.2. I sistemi di programmazione RPC sono composti di: Un compilatore per generare gli stub cliente e servitore (e il server skeleton: Layer 7.2) a partire dai prototipi delle procedure remote. Le entita’ di protocollo RPC (client/chiamante e server/chiamato: Layer 7.1. Costituiscono la parte run-time del sistema di programmazione RPC). I sistemi di programmazione RPC si basano sull’utilizzo dei sistemi di programmazione del Layer di Presentazione (ASN.1 o XDR). Il servizio RPC nel modello di riferimento OSI

25 25 Passaggio dei parametri e dei risultati e rappresentazione dei dati. Binding.  N.B.: sfortunatamente il significato della parola binding e’ ambiguo. Nel contesto RPC il suo significato e’ lo stesso che le si attribuisce nel contesto dei linguaggi di programmazione: collegamento tra un riferimento e una definizione di una funzione.  Riportato in terminologia di reti di calcolatori il significato del termine binding di RPC e’ quindi piu’ simile a quello di parole come collegamento o connessione. Semantica dell’RPC e modalita’ d’esecuzione. Servizio di trasporto utilizzato. Exception handling. Security. Problemi

26 26 Tutti i sistemi di programmazione RPC si basano su tre principi: La definizione di una sintassi di trasferimento canonica. La definizione di un linguaggio per definire la sintassi astratta. La selezione di un insieme di linguaggi di programmazione target per i quali supportare il mapping tra la sintassi di trasferimento e la sintassi locale di rappresentazione dell’informazione. La sintassi locale di rappresentazione dell’informazione e’ normalmente definita non dal programmatore ma dal sistema di programmazione RPC. Il problema del passaggio dei parametri tra chiamante e chiamato e’ risolto in linea di principio attraverso l’utilizzo dei servizi del Layer di Presentazione e attraverso l’utilizzo di un type checking dinamico. Questo risolve il problema costituito dal fatto che diversi sistemi di programmazione per diversi linguaggi, su diverse macchine, rappresentano lo stesso tipo di informazione in modi diversi. Passaggio dei parametri e rappresentazione dei dati

27 27 Notare che attraverso meccanismi del Layer di Presentazione quali i dati opzionali di XDR e’ possibile passare tra chiamante e chiamato anche strutture dati complesse (e basate sull’uso di puntatori!) come liste e alberi.  Quello che si passa e’ pero’ una copia della lista o dell'albero (passaggio per valore), non un riferimento ad esso. Rimangono differenze significative rispetto alla chiamata di procedura locale: Non e’ possibile passare parametri per riferimento. Non e’ possibile condividere variabili globali. Questi problemi sono stati poi risolti in un contesto di programmazione Object-Oriented in cui: E’ (deve essere) possibile passare come parametro il riferimento ad un oggetto: ovviamente, non per modificarlo da parte del chiamato ma solo per poterne invocare i metodi. L’utilizzo di variabili globali e’ comunque deprecato. Passaggio dei parametri e rappresentazione dei dati

28 28 Nei sistemi di programmazione locale il collegamento corretto tra una invocazione di una particolare procedura e la procedura stessa e’ basato sul match dei nomi e sul type checking, ed e’ responsabilita’ del linker (quando chiamante e chiamato non appartengono ad uno stesso modulo, altrimenti esso e’ realizzato direttamente dal compilatore). Nel caso di un programma distribuito chiamante e chiamato non possono essere collegati insieme staticamente. Come puo’ il chiamante localizzare correttamente la procedura chiamata (dato il suo nome)? Localizzare la macchina su cui essa e’ allocata. Localizzare la porta di protocollo di trasporto tramite la quale sono raggiungibili i servizi offerti sulla rete dal modulo remoto. Identificare la particolare interfaccia / procedura remota su quella macchina / porta. N.B.: ricordare sempre: una procedura fa parte di un modulo (e’ una delle n procedure del modulo) e, in prima battuta, e’ il modulo nel suo complesso che viene linkato. Binding

29 29 Tre soluzioni possibili: Un name server globale, con un indirizzo ben noto (vedi ORB, Object Request Broker, di CORBA) Un name server locale sulla macchina remota, su una porta ben nota Il cliente deve conoscere l’indirizzo di rete della macchina (come?), ma il name server risolve il nome della RPC Un indirizzo di rete e una porta ben noti per la procedura (interfaccia) remota Vantaggi di un name server globale: Consentirebbe la riallocazione dinamica del server su macchine diverse in modo trasparente al cliente. Svantaggi di un name server globale: complessita’ Binding

30 30 Stevens: "When we call a local procedure, there is never any question as how many times the procedure executed. If it returns to the caller we know that it executed exactly once. With a remote procedure, however, if we don't get a response after a certain interval, we don't know how many times the remote procedure was executed." Perche' non c'e' stata risposta? La richiesta del chiamante non e' mai arrivata a destinazione. La richiesta del chiamante e' arrivata a destinazione, ma la procedura remota non e' stata eseguita a causa di un crash. La procedura remota e' stata eseguita, ma la risposta non e' stata inviata a causa di un crash. La procedura remota e' stata eseguita e la risposta e' stata inviata, ma non e' arrivata a destinazione. E se a subire il crash fosse il chiamante, dopo avere inviato la richiesta? Cosa dovrebbe succedere al chiamato rimasto orfano? E se il chiamante, se non riceve risposta, ripete la richiesta? Semantica dell'RPC

31 31 Sono definite canonicamente tre diverse, possibili semantiche per l'RPC: 1.At least once. 2.Exactly once. 3.At most once, o all-or-nothing, o transazionale o atomica o ACID: Atomic Consistent Isolated Durable Semantica dell'RPC

32 32 Quando il chiamante riceve la risposta il chiamato e' stato eseguito almeno una volta. Gli schemi di chiamante e chiamato sono i seguenti: Questa semantica e' utilizzabile quando la procedura remota e' idempotente (puo' essere ripetuta fornendo sempre il medesimo risultato): e.g. leggi/scrivi un blocco di disco, leggi l'estratto conto. Esempi di procedure non idempotenti: appendi un blocco ad un file, preleva 1.000$ dal conto corrente. Semantica dell'RPC: at least once chiamantechiamato do { inviaRichiesta(...); attendiRisposta(...); } while (non riceve risposta); while (TRUE) { riceviRichiesta(...); processa(...); inviaRisposta(...); }

33 33 Se il chiamante riceve la risposta il chiamato e' stato eseguito esattamente una volta, se no puo' essere stato eseguito 0 o 1 volta. Gli schemi di chiamante e chiamato sono i seguenti: Questa semantica affida al programma chiamante il trattamento delle situazioni anomale. Se non riceve risposta il chiamante, prima di fare qualunque cosa, deve interrogare il chiamato per verificarne lo stato. Semantica dell'RPC: exactly once chiamantechiamato inviaRichiesta(...); attendiRisposta(...); if (nessuna risposta) { riallinea(...); } while (TRUE) { riceviRichiesta(...); processa(...); inviaRisposta(...); }

34 34 Se il chiamante riceve la risposta il chiamato e' stato eseguito esattamente una volta, se no il chiamato non e' stato eseguito (nemmeno parzialmente!) e il suo stato e lo stato generale del sistema non sono stati modificati. E' la semantica delle transazioni distribuite, quando occorre coordinare l'attivita' di diversi server, garantendo che, o tutti eseguono il loro lavoro fino in fondo, o nessuno fa niente. Esempi: ritiro 100$ al bancomat CityBank, e CityBank addebita a WellsFargo Bank 100$ piu' le spese, e WellsFargo Bank addebita sul mio conto 100$ piu' le spese. Voglio prenotare l'aereo per Parigi e un albergo: se l'uno o l'altro non e' disponibile, non voglio prenotare niente. Semantica dell'RPC: at most once

35 35 Server concorrente vs. Server sequenziale. RPC confermate vs. RPC non confermate  e in particolare, RPC non confermate broadcast RPC confermata sincrona (bloccante) vs. RPC confermata asincrona (non bloccante). Possibilita' di effettuare piu' chiamate RPC senza attendere la risposta vs. Possibilita' di effettuare una sola chiamata RPC per volta. N.B.: "non/confermato" non ha niente a che fare con l'affidabilita' della comunicazione. La nozione e' legata al modello di interazione: c'e' o non c'e' una risposta esplicita dal chiamato al chiamante. RPC: modello d'esecuzione

36 36 Unix: modello d'esecuzione confermata sincrona chiamante / padre fork(); wait(); chiamato / figlio exit(); exec();

37 37 Unix: modello d'esecuzione non confermata chiamante / padre fork(); chiamato / figlio exit(); exec(); fork(); chiamato / figlio exit(); exec(); fork(); chiamato / figlio exit(); exec();

38 38 Unix: modello d'esecuzione confermata asincrona chiamante / padre fork(); chiamato / figlio exit(); exec(); fork(); chiamato / figlio exit(); exec(); wait(); // quale figlio // e’ terminato?

39 39 I sistemi di programmazione RPC tendono a proclamarsi indipendenti dal Servizio di Trasporto utilizzato. Puo’ essere persino ammesso che un server offra il proprio servizio contemporaneamente su diversi Servizi di Trasporto. Naturalmente la semantica dell’RPC puo’ essere influenzata dalle caratteristiche del Servizio di Trasporto. La semantica exactly-once, quella piu’ normale e diffusa, e’ facile da implementare su COTS, piu’ difficile (in forma affidabile) su CLTS. Anche l’interfaccia offerta da una procedura remota puo’ essere vincolata dal Servizio di Trasporto utilizzato, ad esempio perche’ i PDU RPC di chiamata e di ritorno possono dover essere contenuti in un singolo T- SDU (come nel caso caso dell’RPC Sun su UDP). A parita’ di semantica RPC (e livello di affidabilita’) il supporto RPC deve operare diversamente a seconda che si utilizzi CLTS o COTS. Nel caso operi su CLTS puo’ volersi occupare di error detection e recovery (e.g. di ritrasmissione di una chiamata in caso non sia ricevuto un PDU di risposta, e del corrispondente scarto lato server di chiamate duplicate). Servizio di trasporto

40 40 La possibilita’ del verificarsi di situazioni abnormi aumenta moltissimo quando si va ad operare in ambiente distribuito: Problemi di comunicazione. Restart/crash di sistemi. Abort di thread remote (lato chiamante o chiamato). In caso di errore occorre poter distinguere tra: Errore rivelato dal provider del servizio RPC (e.g. binding della RPC fallito, problemi di type checking, problemi di comunicazione): eccezione di sistema. Errore durante l’esecuzione della RPC (e.g. il file cercato non esiste): eccezione utente. Possibilita’ per il client di richiedere il kill del server. Gestione di server orfani (il cui chiamante e’ abortito). Exception handling

41 41 Al di la' di eventuali problemi di riservatezza, i problemi fondamentali dell'RPC sono: autenticazione dell'identita' del chiamante, e conseguente verifica dell'autorizzazione del chiamante a richiedere l'operazione chiamata. Sono gli stessi problemi che si incontrano in applicazioni di terminale remoto (rlogin, TELNET) o di esecuzione remota di comandi (rexec, rcmd). "Allowing a remote program to call a procedure on your system is similar to someone executing a command on your system." (Stevens) Il problema fondamentale e' quindi l'autenticazione del chiamante (client) da parte del chiamato (server). Ovviamente esiste anche il problema di autenticare il chiamato (server) rispetto al chiamante (client). Security

42 42 Open Network Computing (ONC) Remote Procedure Call (RPC) is a widely deployed remote procedure call system. ONC was originally developed by Sun Microsystems as part of their Network File System project, and is sometimes referred to as Sun ONC or Sun RPC. ONC is considered "lean and mean”. ONC is based on calling conventions used in Unix and the C programming language. It serializes data using the XDR. ONC then delivers the XDR payload using either UDP or TCP. Access to RPC services on a machine are provided via a port mapper that listens for queries on a well-known port (number 111) over UDP and TCP. Implementations of ONC RPC exist in most Unix-like systems. Microsoft supplies an implementation for Windows in their Microsoft Windows Services for UNIX product. In addition, a number of third-party implementation of ONC RPC for Windows exist, including versions for C/C++, Java, and.NET. ONC RPC was described in RFC 1831, published in RFC 5531, published in 2009, is the current version. Sun RPC: Open Network Computing Remote Procedure Call

43 43 A network service is a collection of one or more remote programs. For example, a network file service may be composed of two programs. One program may deal with high-level applications such as file system access control and locking. The other may deal with low-level file input and output and have procedures like "read" and "write". A remote program implements one or more remote procedures. un remote program rappresenta un modulo del programma distribuito e il servizio da esso fornito implementa l’interfaccia del modulo. The procedures, their parameters and results are documented in the specific program’s protocol specification (specifica dell’interfaccia, in linguaggio RPC, una estensione di XDR). A server may support more than one version of a remote program in order to be compatible with changing protocols. N.B. un server puo’ implementare anche piu’ di una interfaccia! Sun RPC (RFC 1057)

44 44 Viene utilizzato XDR, che fornisce sia una sintassi di trasferimento che un linguaggio per la definizione di sintassi astratte. Il linguaggio di programmazione target per il sistema di programmazione XDR/RPC e' il C (N.B.: l’unico che consideriamo qui). Non e' un vincolo di architettura, e' solo che e' stato scelto il C come primo linguaggio target, poi non ci sono piu' state tante altre implementazioni (e' un limite del supporto XDR/RPC). In realta' esiste un supporto XDR/RPC anche per Java e.NET. La XDR library e il compilatore XDR/RPC (rpcgen) definiscono la sintassi concreta di rappresentazione locale dei dati. Diverse versioni del supporto RPC hanno supportato diversi template (formati del prototipo) per definire procedure remote. All'inizio era ammesso un unico parametro di ingresso, che doveva essere passato per riferimento. Versioni piu' recenti ammettono anche piu' parametri che possono essere passati anche per valore, come si fa normalmente in C. Sun RPC (RFC 1057): rappresentazione dei dati

45 45 XDR, in quanto sintassi di trasferimento, si basa sulla tipizzazione implicita dell'informazione: Nessuna informazione di tipo viene trasportata insieme al valore. Che conseguenze ha cio' per RPC? In base alla conoscenza di quale procedura remota e' chiamata o sta ritornando il risultato, e al prototipo della procedura stessa, i tipi del parametro di ingresso e di quello di ritorno sono noti, non c'e' bisogno di trasportarli esplicitamente nel PDU di call o return. Dal punto di vista di chiamante e chiamato, la presenza di una informazione di tipo esplicita consentirebbe solo (solo?!) un type check migliore. La presenza di informazioni esplicite di tipo renderebbe invece possibile la realizzazione di protocol analyzer, che cosi' sono invece impossibili da realizzare in assenza di informazioni sulla sintassi astratta della RPC. Sun RPC (RFC 1057): rappresentazione dei dati

46 46  E’ evidente che anche se un argomento e’ passato per riferimento la procedura chiamata non puo’ modificarne (direttamente) il valore! Quello che la procedura chiamata ha in mano e’ il riferimento ad una copia locale dell’argomento attuale remoto.  Logicamente il passaggio di parametri, in input e in output, e’ sempre per valore. Esercizio: date le regole di de/codifica di XDR (fare riferimento all’uso di XDR stream in memoria) come puo’ avvenire la generalizzazione da un solo parametro per riferimento a N parametri, per valore o per riferimento? Esercizio: in che cosa consiste il type checking effettuato dal chiamato? Come, nel contesto di una operazione di decodifica XDR, ci si puo’ accorgere di una incongruenza tra il valore ricevuto e il suo tipo atteso? Sun RPC (RFC 1057): rappresentazione dei dati

47 47 Because of transport independence, the RPC protocol does not attach specific semantics to the remote procedures or their execution requirements. Semantics can be inferred from (but should be explicitly specified by) the underlying transport protocol. Sun RPC protocol does not try to implement any kind of reliability.  In ogni caso, in base alla classificazione che abbiamo dato, la semantica della RPC Sun e’ exactly once, in quanto non e’ prevista, da parte del chiamante, la ritrasmissione del PDU di CALL in caso di mancata ricezione del PDU di REPLY. The application may need to be aware of the type of transport protocol underneath RPC. If it is running on top of an unreliable transport such as UDP (e se vuole comunque avere un livello elevato di affidabilita’ nelle comunicazioni), it must implement its own time-out, retransmission, and duplicate detection policies as the RPC layer does not provide these services. Sun RPC (RFC 1057): semantica

48 48 Consider RPC running on top of an unreliable transport such as UDP. If an application retransmits RPC call messages after time-outs, and does not receive a reply, it cannot infer anything about the number of times the procedure was executed. If it does receive a reply, then it can infer that the procedure was executed at least once. A server may wish to remember previously granted requests from a client and not regrant them in order to insure some degree of exactly-once semantics. A server can do this by taking advantage of the transaction ID (campo xid ) that is packaged with every RPC message (PDU rpc_msg ). The main use of this transaction ID is by the client RPC layer in matching replies to calls. However, a client application may choose to reuse its previous transaction ID when retransmitting a call. The server may choose to remember this ID after executing a call and not execute calls with the same ID. Il server deve ricordare anche il valore di ritorno di ogni chiamata eseguita, in modo da poterlo inviare al cliente nel PDU di REPLY in caso riceva di nuovo un PDU di CALL con lo stesso ID. RPC: semantica (non di Sun RPC!)

49 49 The intended use of the RPC protocol is for calling remote procedures. Normally, each call message is matched with a reply message. However, the RPC protocol itself is a message-passing protocol with which other (non-procedure call) protocols can be implemented.  N.B.: qui per protocollo intende solo i PDU scambiati tra i pari. Sun currently uses, or perhaps abuses, the RPC message protocol for the batching (or pipelining) of procedure calls. In the case of batching, the client never waits for a reply from the server and the server does not send replies to batch calls. A sequence of batch calls is usually terminated by a legitimate RPC operation in order to flush the pipeline and get a positive acknowledgement. broadcast remote procedure calls. In broadcast protocols, the client sends a broadcast call to the network and waits for numerous replies. This requires the use of packet-based protocols (like UDP) as its transport protocol. Sun RPC (RFC 1057): modello di esecuzione

50 50 Poiche’ TCP e’ basato su connessioni punto-punto non supporta l’idea di messaggi broadcast. IP supporta indirizzi broadcast e multicast, sia a livello di sottorete che di internetwork. E’ quindi possibile indirizzare un datagram UDP contemporaneamente a molteplici destinatari allocati su macchine diverse, ma che si affacciano alla rete con lo stesso numero di porta. Dal punto di vista dell’RPC Sun, in cui i server RPC offrono il loro servizio su porte effimere, e’ praticamente impossibile che lo stesso servizio sia offerto su macchine diverse tramite la stessa porta! Come e’ possibile allora effettuare chiamate broadcast di RPC?  Un supporto ad hoc e’ fornito dal Port Mapper (vedi seguito). Broadcast RPC

51 51 The Sun RPC protocol makes no restrictions on the concurrency model implemented and, beside the synchronous local-like model, others are possible. For example, an implementation may choose to have RPC calls be asynchronous, so that the client may do useful work while waiting for the reply from the server. Another possibility is to have the server create a separate task/thread to process an incoming call, so that the original server can be free to receive other requests. Server concorrente vs. Server sequenziale. Entrambi i modelli sono supportati dal sistema di programmazione Sun RPC. Sun RPC (RFC 1057): modello di concorrenza

52 52 Secondo lo schema normale di costruzione di programmi distribuiti basati sul supporto Sun RPC: Un server RPC offre il proprio servizio tramite una porta di trasporto effimera. Il cliente deve sapere a priori su quale macchina il servizio e’ offerto. Il cliente deve sapere a priori se un servizio e’ offerto tramite TCP o UDP (o entrambi). Quello che il cliente non puo’ comunque sapere a priori e’ su quale porta il servizio e’ offerto: lo stesso server puo’ saperlo solo dopo che la porta effimera e’ stata effettivamente allocata ( bind -ata)! La porta utilizzata dal servizio deve quindi essere acquisita dal cliente dinamicamente! Perche’ questo sia possibile occorre che ci sia un servizio di pagine gialle (name service) che su ogni macchina effettui il mapping servizio basato su RPC  porta su cui il servizio e’ offerto. Notare che questo name service, nell’architettura Sun RPC, e’ locale: fornisce informazioni solo relativamente a servizi basati su RPC che sono allocati sulla sua stessa macchina. Ogni RPC server, durante la sua fase di inizializzazione, deve registrare sul name service la porta effimera su cui offre il servizio. Sun RPC (RFC 1057): binding

53 53 The act of binding (collegare) a particular client to a particular service and transport parameters is not part of the Sun RPC protocol specification. Binding is left up to some higher-level software. Implementors could think of the RPC protocol as the Jump-To- Subroutine instruction ( JSR o Call ) of a network. La funzione di binding di un programma remoto (interfaccia remota) e' supportata da servizi applicativi come il Port Mapper (vedi seguito). Il Port Mapper a sua volta si basa sull'utilizzo di RPC, sull'uso di un numero di porta well-known, e sull'offerta dei propri servizi sia su UDP che su TCP. Per una descrizione del Port Mapper vedi il seguito della lezione. Versioni piu' recenti del supporto RPC (su Sun Solaris) hanno sostituito il Port Mapper con un servizio analogo chiamato rpcbind. Il binding di singole procedure remote all'interno di un programma (interfaccia) e' demandato al supporto RPC del server stub. Sun RPC (RFC 1057): binding

54 54 The RPC protocol can be implemented on several different transport protocols. The RPC protocol does not care how a message is passed from one process to another, but only with specification and interpretation of messages. On the other hand, the application may wish to obtain information about (and perhaps control over) the transport layer. The transport protocol may impose a restriction on the maximum size of RPC messages, like UDP, or it may be stream-oriented like TCP with no size limit. If RPC is running on top of an unreliable transport such as UDP, the service must devise its own retransmission and time-out policy. Sun RPC does not provide this service. Client and server must agree on their transport protocol choices, e.g. through a mechanism such as the one supported by the Port Mapper (vedi). Un programma remoto puo' offrire i suoi servizi simultaneamente su TCP e su UDP. Sun RPC (RFC 1057): servizio di trasporto

55 55 Sono previste diverse condizioni di eccezione di sistema: Se non riceve una risposta entro lo scadere di un timer (configurabile) il client stub genera una eccezione. Eccezioni vengono generate anche se si rilevano condizioni anomale nel protocollo RPC, nel binding o nell’un/marshaling. Nel caso il Servizio di Trasporto utilizzato sia COTS (TCP) una eccezione viene sollevata anche in caso di caduta della connessione. La condizione di eccezione viene segnalata esplicitamente dal client stub al modulo chiamante. Il chiamante, utilizzando funzioni del middleware RPC, Puo’ interrogare il supporto RPC per avere informazioni di dettaglio sulla condizione di errore ( clnt_geterr() ), e/o Puo’ tracciare l’errore su standard error ( clnt_perror() ). Sun RPC (RFC 1057): Exception handling.1

56 56 Non e' invece prevista la possibilita' di definire condizioni di eccezione utente sull'interfaccia.  Sono i valori dei normali parametri di ritorno che devono essere in grado di descrivere anche condizioni di eccezione utente. Il chiamante non ha alcuna possibilita' di interrompere l'esecuzione della procedura remota. Non c'e' alcun supporto che consenta la gestione della condizione di orfano. Sun RPC (RFC 1057): Exception handling.2

57 57 Il sistema Sun RPC supporta diverse modalita' di autenticazione del client da parte del server (ed e' aperto ad ulteriori estensioni). Nessuna autenticazione ( AUTH_NULL, modalita' di default). Autenticazione à la Unix ( AUTH_SYS, prima detta AUTH_UNIX ). (vedi seguito) Autenticazione basata sulla tecnica di encrittazione DES ( AUTH_DES ). Autenticazione secondo la modalita' Kerberos (anch'essa basata su encrittazione DES) ( AUTH_KERB ). Un campo del PDU RPC indica la modalita' di autenticazione utilizzata: (struct call_body).cred.flavor Tutti gli esempi delle dispense non implementano alcuna forma di autenticazione: questa e' la modalita' di default. (struct call_body).cred.flavor == AUTH_NULL Sun RPC: Security

58 58 The rpcgen tool generates remote program interface modules (client e server stub + server skeleton). It compiles source code written in the XDR/RPC language rpcgen produces one or more C language source modules, which are then compiled by a C compiler The default output of rpcgen is: A header file of definitions (e dichiarazioni C) common to the server and the client (l’interfaccia implementativa client-server) A set of XDR routines that translate (de/codifica: le funzioni filtro XDR) each data type defined in the header file A stub program for the server (compreso il process skeleton) A stub program for the client rpcgen can optionally generate: A time-out for servers Server stubs that are not main programs An RPC dispatch table that checks authorizations rpcgen

59 59 Stralcio di un modulo che realizza un servizio locale di message log: # include “messagelog.h” /* Prints a message to the the log file. * Returns a boolean: success (true)/ failure(false) */ int printmessage(msg m) { FILE *f; if ((f = fopen("my.log", "a")) == NULL) { return (0); } if (fprintf(f, "%s\n", m) != strlen(m)+1) { fclose(f); return (0); } fclose(f); return(1); } // continua alla pagina seguente rpcgen: esempio 1.1

60 60 // continua dalla pagina precedente /* Reset the log file. Return a boolean * indicating whether the operation succeded. */ int resetlog(void) { FILE *f; if ((f = fopen("my.log", "w")) == NULL) { return (0); } fclose(f); return(1); } rpcgen: esempio 1.1’

61 61 Quando si definisce un servizio, per prima cosa bisogna definire l’API tramite cui esso e’ accessibile ai clienti. Header file C che descrive l’API di accesso al servizio di message log, cioe’ il protocollo chiamante-chiamato: // messagelog.h – message log service API #ifndef _MESSAGELOG #define _MESSAGELOG typedef char *msg; int printmessage(msg); int resetlog(void); #endif  Vorremmo trasformare il servizio di message log in un servizio di rete. rpcgen: esempio 1.2

62 62 Prima cosa: definire l’interfaccia del servizio in linguaggio XDR/RPC. /* messagelog.x: Remote message log protocol */ const MAXLINE = 256; typedef string msg ; program MESSAGELOG { version MESSAGELOGVERS { int PRINTMESSAGE(msg) = 1; int RESETLOG(void) = 2; } = 1; } = 0x ; Remote procedures are always declared as part of remote programs (interfacce remote versionate). rpcgen: esempio 1.3

63 63 Procedure PRINTMESSAGE is declared to be procedure 1 in version 1 of the remote program MESSAGEPROG, with the program number 0x Version numbers are incremented when functionality is changed in the remote program.  More than one version of a remote program can be defined. Una procedura remota e’ identificata da una tripla di numeri: 1.il numero del programma cui appartiene; 2.il numero della versione di programma cui e’ relativa; 3.il numero assegnato alla funzione all’interno della versione. In realta’ ci sono altre 3 informazioni necessarie per indirizzare una istanza del servizio: La macchina su cui viene offerto il servizio (viene implementata l’interfaccia): indirizzo IP. La porta e il protocollo di trasporto su cui viene offerto il servizio (viene implementata l’interfaccia). Identificazione di una procedura remota

64 64 Il numero di versione e quello della funzione sono assegnati dal programmatore, e sono significativi solo all’interno del programma. I numeri dei programmi sono invece soggetti a regolamentazione: Da 0x a 0x1fffffff sono assegnati dalla Sun. Da 0x a 0x3fffffff sono assegnati liberamente per uso locale. Da 0x a 0x5fffffff sono per applicazioni che utilizzano program numbers generati dinamicamente. Da 0x a 0xffffffff sono riservati. Solo i numeri di programma assegnati da Sun hanno garanzia di univocita’ universale. Numeri assegnati localmente sono utilizzabili solo localmente (per evitare conflitti d’uso). Identificazione di una interfaccia remota

65 65 Il numero di una funzione e’ significativo solo nel contesto di una interfaccia versionata. Una interfaccia versionata identifica un servizio di rete RPC-based. Una interfaccia versionata e’ identificata da una coppia di numeri: Numero (identificatore) del programma (cioe’ dell’interfaccia). Versione del programma (cioe’ dell’interfaccia). Il numero di versione e’ significativo solo nel contesto di una interfaccia (programma). Il Port Mapper si occupa quindi solo del binding di interfacce versionate (locali), cioe’ del mappaggio interfaccia versionata  porta su cui viene offerto il servizio in questo momento, su questa macchina. Identificazione di una interfaccia versionata remota

66 66 Header file C prodotto dalla compilazione dell'interfaccia remota con rpcgen: #ifndef _MESSAGELOG_H_RPCGEN #define _MESSAGELOG_H_RPCGEN #include #define MAXLINE 256 typedef char *msg; #define MESSAGELOG 0x #define MESSAGELOGVERS 1 #define PRINTMESSAGE 1 int *printmessage_1(msg *, CLIENT *); int *printmessage_1_svc(msg *, struct svc_req *); #define RESETLOG 2 int *resetlog_1(void *, CLIENT *); int *resetlog_1_svc(void *, struct svc_req *); int messagelog_1_freeresult(SVCXPRT *, xdrproc_t, caddr_t); bool_t xdr_msg(XDR *, msg *); #endif rpcgen: esempio 1.4

67 67 rpcgen : esempio 1.4’ Client stub: int *printmessage_1(…) {... } Client routine... printmessage_1(…);... Server stub:... printmessage_1_svc(…);... Server routine int *printmessage_1_svc(…) {... } messagelog.x messagelog.h program MESSAGELOG { version MESSAGELOGVERS { int PRINTMESSAGE(msg) = 1; int RESETLOG(void) = 2; } = 1; } = 0x ; 7.2

68 68 Server RPC: /* msg_proc.c: implementation of the remote * module "message log" */ #include #include "messagelog.h" /* generated by rpcgen */ int *printmessage_1_svc(msg *m, struct svc_req *req) { static int result; /* must be static! Perche’? */ FILE *f; if ((f = fopen("my.log", "a")) == NULL) { result = 0; return (&result); } if (fprintf(f, "%s\n", *m) != strlen(*m)+1) { fclose(f); result = 0; return (&result); } fclose(f); result = 1; return(&result); } rpcgen: esempio 1.5

69 69 Server RPC (continua): int *resetlog_1_svc(void *dummy, struct svc_req *req) { // N.B.: nota che una procedura senza parametri // funzionali e’ stata trasformata in una procedura // con un parametro funzionale di tipo (void*), che // poi viene ignorato! static int result; /* must be static! */ FILE *f; if ((f = fopen("my.log", "w")) == NULL) { result = 0; return (&result); } fclose(f); result = 1; return (&result); } rpcgen: esempio 1.6

70 70 Server RPC (continua). Notare che: Il nome delle procedure e’ correlato al nome utilizzato nella definizione dell’interfaccia (conversione caratteri maiuscoli in minuscoli, e suffisso legato alla versione). I parametri funzionali di ingresso e anche il risultato sono passati (implementativamente) per riferimento e non per valore. Sono ammessi un solo parametro funzionale di ingresso e uno di uscita (e questi ci sono sempre!).  Nel caso sia necessario scambiarsi piu’ informazioni, queste devono (e possono sempre!) essere impaccate in una singola struttura dati.  N.B.: in realta’ il sistema di programmazione RPC adesso supporta anche interfacce con piu’ parametri di ingresso e il passaggio per valore dei parametri.  L’esempio e’ un po’ un ibrido, perche’ si basa sulla vecchia disciplina ma e’ in C ANSI: l’uso del C ANSI anziche’ del C tradizionale come linguaggio target e’ un’opzione diventata possibile in rilasci successivi di rpcgen. rpcgen: esempio 1.7

71 71 Server RPC (continua). Notare che: I parametri di ingresso e il risultato sono rappresentati localmente in accordo alla rappresentazione concreta locale prevista dal sistema di programmazione XDR. Poiche’ il risultato (valore di ritorno della funzione chiamata) e’ passato per riferimento, esso deve essere contenuto in una variabile statica (perche’?). Nelle procedure e’ presente un parametro addizionale ( struct svc_req *req ). Esso contiene informazioni aggiuntive sul contesto di chiamata, ed in particolare: informazioni correlate ai parametri di security ( cred e verf del PDU RPC di call); il numero della procedura remota che e’ stata chiamata. (questo numero e’ utilizzato da un dispacciatore che e’ parte del server stub per selezionare quale delle procedure che fanno parte del programma deve essere chiamata) rpcgen: esempio 1.8

72 72 Client RPC: #include #include "messagelog.h" /* generated by rpcgen */ main(int argc, char *argv[]) { CLIENT *clnt; int *result; char *server; char sendLine[MAXLINE+1]; msg message = sendLine; if (argc != 2) { fprintf(stderr, "usage: %s host missing\n", argv[0]); exit(1); } server = argv[1]; /* Create client handle used for calling MESSAGELOG * on the server designated on the command line. */ clnt = clnt_create(server, MESSAGELOG, MESSAGELOGVERS, "udp"); rpcgen: esempio 1.9

73 73 Client RPC (continua): if (clnt == NULL) { /* Couldn’t establish connection with server. */ clnt_pcreateerror(server); exit(1); } result = resetlog_1(NULL, clnt); if (result == NULL) { // eccezione di sistema /* An error occurred while calling the server. */ clnt_perror(clnt, server); exit(1); } /* we successfully called the remote procedure. */ if (*result == 0) { // eccezione utente /* Server was unable to reset log file. */ fprintf(stderr, "%s: could not reset log.\n", argv[0]); exit(1); } rpcgen: esempio 1.10

74 74 Client RPC (continua): while (fgets(sendLine, MAXLINE, stdin) != NULL) { /* Call remote procedure printmessage. */ result = printmessage_1(&message, clnt); if (result == NULL) { // eccezione di sistema clnt_perror(clnt, server); exit(1); } /* we successfully called the remote procedure. */ if (*result == 0) { // eccezione utente fprintf(stderr, "%s: couldn't print message\n", argv[0]); exit(1); } printf("Message delivered to %s\n", server); } clnt_destroy(clnt); exit(0); } rpcgen: esempio 1.11

75 75 Client RPC (continua). Notare che: Il nome delle procedure (chiamate, e quindi delle procedure-stub lato chiamante) e' correlato al nome utilizzato nella definizione dell'interfaccia. I parametri funzionali di ingresso e anche il risultato sono passati (implementativamente) per riferimento e non per valore. Si applicano le stesse considerazioni gia' fatte per il lato server. Il fatto che il valore di ritorno sia passato per riferimento assume particolare rilevanza lato client dato che viene adottata la seguente convenzione:  Una eccezione di sistema relativa alla realizzazione della chiamata remota viene segnalata al modulo chiamante ritornando, anziche' un puntatore al valore di ritorno, un puntatore NULL. rpcgen: esempio 1.12

76 76 CLIENT *clnt_create(const char *host, const rpcprog_t prognum, const rpcvers_t versnum, const char *nettype); Client creation routine for program prognum and version versnum.  prognum e versnum identificano l’interfaccia remota ai cui servizi il client vuole accedere. host identifies the name of the remote host where the server is located.  Ma quale e’ il suo indirizzo IP? Vedi DNS!  E quale e’ la porta? Vedi Port Mapper!  Nota che il client deve sapere a priori su quale macchina il servizio remoto e’ allocato. nettype indicates the class of transport protocol to use.  Il protocollo di trasporto utilizzato dal servizio remoto per comunicare sulla rete (che deve essere noto a priori al client). Binding del server e client-handle.1

77 77 Una client handle (inizializzata) consente al client di invocare sulla macchina server indicata tutte le procedure dichiarate nella interfaccia di cui ha effettuato il binding (collegamento, “connessione”) eseguendo la funzione clnt_create(). Per completare il binding la funzione clnt_create() deve: Risolvere il nome della macchina server host nel relativo indirizzo IP (vedi DNS). Identificare su quale porta del protocollo di trasporto nettype e’ implementata l’interfaccia del servizio definito dall’interfaccia versionata [ prognum, versnum ](vedi Port Mapper). Allocare le risorse di comunicazione necessarie per supportare il dialogo con il server (e.g. socket e risorse necessarie per il Layer di Presentazione). Effettuare la connessione di trasporto al server remoto che implementa l’interfaccia (puramente logica se il server offre il servizio tramite UDP, effettiva se il server offre il servizio tramite TCP). Binding del server e client-handle.2

78 78 void clnt_destroy(CLIENT *clnt); Destroys the client’s RPC handle. Destruction usually involves deallocation of private data structures, including clnt itself. Use of clnt is undefined after calling clnt_destroy(). La funzione clnt_destroy() termina l’accesso del client che la invoca ai servizi offerti dall’interfaccia remota acceduta tramite la handle clnt. Disconnette il server (nel caso di un server TCP chiude la connessione di trasporto). Disalloca le risorse locali di comunicazione (socket, …). Binding del server e client-handle

79 79 “... On the server side, a process is dormant awaiting the arrival of a call message. When one arrives...” Mentre per il lato client rpcgen produce effettivamente solo il client stub (procedurale/passivo), per il lato server esso produce (puo’ produrre) l’intero process skeleton (attivo) del programma server.  Lo skeleton contiene la funzione main().  Lo skeleton garantisce l’esecuzione in background del processo server. Per prima cosa il processo server si registra con il Port Mapper (utilizzando una funzione offerta dalla protocol entity RPC). Poi apre le comunicazioni sulla rete e si mette in attesa di richieste. E’ possibile specificare a rpcgen su quale/i protocolli di trasporto (UDP, TCP) il processo server deve offrire i suoi servizi. L’I/O dalla rete puo’ essere gestito in modo tale da consentire una attivazione del processo server anche da un port monitor come inetd. Server skeleton

80 80 Input files: Protocol.x Definizione della sintassi astratta del dialogo RPC-based. Output files: Protocol.h Prototipi C per l’interazione con gli stub chiamante e chiamato e tipi di dato per la rappresentazione concreta locale dei parametri. E’ la definizione locale (sia lato client che lato server), language specific, dell’API definita dalla sintassi astratta. Protocol_xdr.c Funzioni filtro XDR per la de/serializzazione dei parametri di ingresso/uscita delle procedure remote. Protocol_clnt.c Implementazione degli stub lato chiamante. Protocol_svc.c Implementazione degli stub (e dello skeleton) lato chiamato. rpcgen

81 81 rpcgen e run time system RPC (lato chiamante) Protocol.x (sintassi astratta RPC / XDR) rpcgen Protocol_xdr.c (funzioni di de/codifica per i tipi definiti nella sintassi astratta) Protocol.h (rappresentazione concreta locale di valori della sintassi astratta e prototipi di RPC) XDR Library chiama produce User program (C) chiama ( xdr_free() ) include chiama Protocol_clnt.c (RPC stub lato chiamante) RPC Protocol Entity chiama chiama ( clnt_create() e clnt_destroy() )

82 82 RPC Protocol Entity RPC_protocol.x (sintassi astratta XDR del protocollo Sun RPC v2) rpcgen RPC_protocol_xdr.c (funzioni di de/codifica per rpc_msg e tipi riferiti) RPC_protocol.h (rappresentazione concreta locale di PDU del protocollo Sun RPC v2) XDR Library chiama produce Sun RPC v2 Protocol Entity (C) chiama include chiama

83 83 rpcgen e run time system RPC (lato chiamato) Protocol.x (sintassi astratta RPC / XDR) rpcgen Protocol_xdr.c (funzioni di de/codifica per i tipi definiti nella sintassi astratta) Protocol.h (rappresentazione concreta locale di valori della sintassi astratta e prototipi di RPC) XDR Library chiama produce Implementazione delle procedure remote esportate (C) Chiama ( xdr_free() ) include chiama Protocol_svc.c (RPC stub e skeleton lato chiamato) RPC Protocol Entity chiama

84 84 rpcgen Client stub: Interfaccia_clnt.c Interfaccia_xdr.c (pres. layer) Client RPC protocol entity Transport Service Client routine Server stub: Interfaccia_svc.c Interfaccia_xdr.c (pres. layer) Server RPC protocol entity Transport Service Server routine RPC middleware Interfaccia.x Interfaccia.h rpc_msg

85 85 -a Generate all the files including sample code for client and server side. -I Generate a server side that can be started from inetd. -M Use the newstyle of rpcgen (piu’ di un parametro di ingresso, parametri per valore). -s nettype Compile into server side stubs for all the transports belonging to the class nettype (tra le classi che ci interessano, “tcp” e “udp”). Lo stesso risultato si puo’ ottenere con lo switch –n netid dove, per i valori che ci interessano, netid ha gli stessi valori di nettype. N.B.: queste opzioni si possono utilizzare ripetutamente per far si’ che il server utilizzi contemporaneamente piu’ servizi di trasporto. rpcgen options

86 86 Quando dalla rete arriva una richiesta, il supporto RPC attiva una procedura di dispacciamento che e’ parte del server stub. Questa: Riconosce quale procedura server e’ stata chiamata; Ne decodifica il parametro di ingresso; Chiama la procedura server corrispondente definita dall’utente; Al termine dell’esecuzione della procedura server: Codifica il parametro di ritorno; Genera la risposta verso il chiamante utilizzando i servizi della protocol entity RPC; Libera la memoria allocata per contenere la rappresentazione locale del parametro di ingresso. Esiste la possibilita’ di farsi produrre da rpcgen: Un server skeleton sequenziale o un server skeleton concorrente (basato sull’uso del supporto multi-thread). Un server stub passivo, senza l’intero process skeleton, per consentire ad un unico processo server di supportare piu’ interfacce remote. Server skeleton / stub

87 87 Per ogni chiamata alla funzione printmessage() il server stub (tramite la procedura di decodifica) alloca memoria dinamica per contenere il valore del parametro di ingresso (una stringa). Per ogni chiamata a qualunque delle due procedure remote il client stub potrebbe dover allocare memoria dinamica per contenere il valore del risultato (nel caso particolare, essendo il risultato un intero, non viene allocata memoria dinamica). Chi e' responsabile di liberare questa memoria? Il client stub: Non e' responsabile di liberare la memoria dinamica utilizzata ne' dai parametri di ingresso ne' dai valori di ritorno di una chiamata remota. Queste responsabilita' sono del programma cliente. Il server stub: E' responsabile di liberare la memoria utilizzata per la rappresentazione locale degli argomenti di input della chiamata (quando questa e' terminata, e gli viene restituito il controllo). Non e' responsabile di liberare la memoria utilizzata per la rappresentazione del valore di ritorno della funzione. Gestione della memoria

88 88 Per tenere conto della disciplina di de/allocazione definita dal supporto RPC il client potrebbe dover essere modificato nel modo seguente (ad esempio, per la chiamata a printmessage() : nel caso particolare questo non e' effettivamente necessario): result = printmessage_1(&message, clnt); if (result == NULL) { clnt_perror(clnt, server); exit(1); } if (*result == 0) { fprintf(stderr, "%s: couldn't print message\n", argv[0]); exit(1); } /* libera la memoria che contiene il valore di * ritorno (N.B.: in questo caso, niente): */ xdr_free(xdr_int, (char*)result); Gestione della memoria lato chiamante.1

89 89 In realta’ ci sarebbe anche il problema di liberare la memoria relativa alla rappresentazione locale del parametro di ingresso. Perche’ abbiamo aggiunto xdr_free(xdr_int, (char*)result); ma non xdr_free(xdr_msg, (char*)&message); ? Come avremmo dovuto modificare il programma chiamante perche’, dopo la chiamata della funzione remota printmessage_1(), fosse corretta la liberazione sia della memoria del parametro di ingresso che di quella del valore di ritorno, secondo lo schema seguente? result = printmessage_1(&message, clnt);... xdr_free(xdr_int, (char*)result); xdr_free(xdr_msg, (char*)&message); Gestione della memoria lato chiamante: esercizio

90 90 Per tenere conto della disciplina di de/allocazione definita dal supporto RPC la procedura remota (server, chiamata) dovrebbe farsi carico di liberare la memoria contenente il suo valore di ritorno. N.B.: nel nostro caso questo non e’ necessario perche’ il valore di ritorno e' contenuto in una semplice variabile statica. Esso potrebbe pero’ essere un valore piu’ complesso, che richiede per la sua rappresentazione l’utilizzo di memoria dinamica. Come e’ possibile che la procedura remota faccia qualcosa (si occupi di liberare la memoria) dopo essere terminata? E’ evidentemente impossibile che durante una attivazione la funzione liberi la memoria contenente il valore di ritorno di quella attivazione. Durante una attivazione pero’ una procedura remota puo’ liberare la memoria che conteneva il risultato dell'attivazione precedente!  Questa e’ la disciplina di programmazione prevista dal supporto Sun RPC. Gestione della memoria lato chiamato.1

91 91 L’alternativa sarebbe stata di attribuire al server stub la responsabilita’ della liberazione della memoria allocata per la rappresentazione concreta locale del valore di ritorno. Ma questo avrebbe implicato assumere che questa memoria, che e’ allocata dalla procedura chiamata (dall’utente), fosse stata allocata dinamicamente. Chi lo puo’ garantire? E se lo si ponesse come vincolo al codice utente, sarebbe un vincolo appropriato? Gestione della memoria lato chiamato.2

92 92 Servizio di remote directory list (dir.x): const MAXNAMELENGTH = 255; /* max length of dir entry */ typedef string nametype ; /* entry */ /* A node in the directory listing */ struct namenode { nametype name; /* name of dir entry */ struct namenode *next; /* next entry */ }; typedef namenode *namelist; /* link in the listing */ /* continua alla pagina seguente */ Esempio 2: remote directory.1

93 93 Servizio di remote directory list (dir.x, continua): /* The result of a READDIR operation. Error codes * rely upon passing UNIX errno’s back. */ union readdir_res switch (int errno) { case 0: namelist list; /* no error: return directory listing */ default: void; /* error occurred: nothing to return */ }; program DIRPROG { version DIRVERS { readdir_res READDIR(nametype) = 1; } = 1; } = 0x ; Esempio 2: remote directory.2

94 94 Header file C prodotto dalla compilazione di dir.x con rpcgen (dir.h): #ifndef _DIR_H_RPCGEN #define _DIR_H_RPCGEN #include #define MAXNAMELENGTH 255 typedef char *nametype; struct namenode { nametype name; struct namenode *next; }; typedef struct namenode namenode; typedef namenode *namelist; struct readdir_res { int errno; union { namelist list; } readdir_res_u; }; typedef struct readdir_res readdir_res; Esempio 2: remote directory.3

95 95 Header file C dir.h, continua: #define DIRPROG 0x #define DIRVERS 1 #define READDIR 1 readdir_res *readdir_1(nametype *, CLIENT *); readdir_res *readdir_1_svc(nametype *, struct svc_req *); int dirprog_1_freeresult(SVCXPRT *, xdrproc_t, caddr_t); /* the xdr functions */ bool_t xdr_nametype(XDR *, nametype*); bool_t xdr_namenode(XDR *, namenode*); bool_t xdr_namelist(XDR *, namelist*); bool_t xdr_readdir_res(XDR *, readdir_res*); #endif /* !_DIR_H_RPCGEN */ Esempio 2: remote directory.4

96 96 Funzioni filtro XDR prodotte dalla compilazione di dir.x con rpcgen (dir_xdr.c): #include "dir.h" bool_t xdr_nametype(XDR *xdrs, nametype *objp) { if (!xdr_string(xdrs, objp, MAXNAMELENGTH)) return (FALSE); return (TRUE); } bool_t xdr_namenode(XDR *xdrs, namenode *objp) { if (!xdr_nametype(xdrs, &objp->name)) return (FALSE); if (!xdr_pointer(xdrs, (char **)&objp->next, sizeof(namenode), (xdrproc_t)xdr_namenode)) return (FALSE); return (TRUE); } Esempio 2: remote directory.5

97 97 Funzioni filtro XDR (dir_xdr.c), continua: bool_t xdr_namelist(XDR *xdrs, namelist *objp) { if (!xdr_pointer(xdrs, (char **)objp, sizeof(namenode), (xdrproc_t)xdr_namenode)) return (FALSE); return (TRUE); } bool_t xdr_readdir_res(XDR *xdrs, readdir_res *objp) { if (!xdr_int(xdrs, &objp->errno)) return (FALSE); switch (objp->errno) { case 0: if (!xdr_namelist(xdrs, &objp->readdir_res_u.list)) return (FALSE); break; } return (TRUE); } Esempio 2: remote directory.6

98 98 Client stub prodotto dalla compilazione di dir.x con rpcgen (dir_clnt.c): #include /* for memset */ #include "dir.h" /* Default timeout can be changed using clnt_control() */ static struct timeval TIMEOUT = { 25, 0 }; readdir_res *readdir_1(nametype *argp, CLIENT *clnt) { static readdir_res clnt_res; memset((char *)&clnt_res, 0, sizeof (clnt_res)); if (clnt_call(clnt, READDIR, (xdrproc_t)xdr_nametype, (caddr_t)argp, (xdrproc_t)xdr_readdir_res, (caddr_t)&clnt_res, TIMEOUT) /* RPC: utilizza la API RPC di livello simple */ != RPC_SUCCESS) { return (NULL); } return (&clnt_res); }  N.B.: azzera clnt_res per utilizzarlo nella decodifica del valore di ritorno! Esempio 2: remote directory.7

99 99 Procedura remota server: /* dir_proc.c: remote readdir implementation */ #include #include "dir.h" /* Created by rpcgen */ readdir_res * readdir_1_svc(nametype *dirname, struct svc_req *req) { DIR *dirp; struct dirent *d; namelist nl; namelist *nlp; static readdir_res res = {0, {NULL}}; /* must be static! */ /* Free previous result */ xdr_free(xdr_readdir_res, (char*)&res); Esempio 2: remote directory.8

100 100 Quando si esegue la xdr_free() di una variabile locale e’ necessario che: Tutte le strutture dati secondarie linkate ad essa siano state allocate dinamicamente. La variabile abbia un valore corretto (legittimo). Una variabile i cui byte siano tutti nulli non ha necessariamente un valore legittimo.  Basta pensare al caso di un campo tag di una union che non preveda il valore 0 tra i suoi valori possibili. Per questo, o e’ possibile inizializzare staticamente il valore del parametro di ritorno in modo corretto, oppure bisogna evitare che la prima attivazione di una procedura remota effettui la xdr_free() della rappresentazione locale di questo parametro. Inizializzazione della rappresentazione locale

101 101 Procedura remota server (continua): /* Open directory */ if ((dirp = opendir(*dirname)) == NULL) { res.errno = errno; return (&res); } /* Collect directory entries. Memory allocated here is freed * by xdr_free the next time readdir_1 is called */ nlp = &res.readdir_res_u.list; while (d = readdir(dirp)) { nl = *nlp = (namenode *) malloc(sizeof(namenode)); if (nl == NULL) { closedir(dirp); res.errno = EAGAIN; return(&res); } nl->name = strdup(d->d_name); nlp = &nl->next; } *nlp = NULL; /* Return the result */ closedir(dirp); res.errno = 0; return (&res); } Esempio 2: remote directory.9

102 102 Client: #include #include "dir.h" /* generated by rpcgen */ main(int argc, char *argv[]) { CLIENT *clnt; char *server; char *dir; readdir_res *result; namelist nl; if (argc != 3) { fprintf(stderr, "usage: %s host directory\n",argv[0]); exit(1); } server = argv[1]; // server domain name dir = argv[2]; // path to dir we want to read Esempio 2: remote directory.10

103 103 Client (continua): /* Create client "handle" used for calling DIRPROG * on the server designated on the command line. */ clnt = clnt_create(server, DIRPROG, DIRVERS, "tcp"); if (clnt == (CLIENT *)NULL) { clnt_pcreateerror(server); exit(1); } /* call remote server procedure */ result = readdir_1(&dir, clnt); if (result == (readdir_res *)NULL) { clnt_perror(clnt, server); exit(1); } /* Okay, we successfully called the remote procedure. */ if (result->errno != 0) { /* Remote system error. Print error message and die. */ errno = result->errno; perror(dir); exit(1); } Esempio 2: remote directory.11

104 104 Client (continua): /* Successfully got a directory listing. Print it. */ for (nl = result->readdir_res_u.list; nl != NULL; nl = nl->next) { printf("%s\n", nl->name); } /* free dynamic memory of result value */ xdr_free(xdr_readdir_res, (char*)result); clnt_destroy(clnt); exit(0); }  N.B. In questo caso particolare non e’ necessario deallocare il parametro di ingresso. Esempio 2: remote directory.12

105 105 La funzione clnt_call() e' chiamata dal client stub generato da rpcgen. enum clnt_stat clnt_call(CLIENT *clnt, const rpcproc_t procnum, const xdrproc_t inproc, const caddr_t in, const xdrproc_t outproc, caddr_t out, const struct timeval tout); Calls the remote procedure procnum associated with the client handle clnt, which is obtained with an RPC client creation routine such as clnt_create(). inproc is the XDR function used to encode the procedure’s parameters. outproc is the XDR function used to decode the procedure’s results. Continua -> RPC API: call remota.1

106 106 clnt_call() (continua): in is the address of the procedure’s argument(s). out is the address of where to place the result(s). tout is the time allowed for results to be returned, which is overridden by a time-out set explicitly through clnt_control(). If the remote call succeeds, the status returned is RPC_SUCCESS. Otherwise, an appropriate status is returned.  N.B.: Il lavoro di encodifica del parametro di ingresso e di decodifica del parametro di ritorno e’ fatto concretamente dalla funzione clnt_call() ma le funzioni filtro che devono essere utilizzate sono indicate dal client stub. Quindi il vero responsabile di queste attivita’ non e’ la funzione clnt_call() (cioe’ la protocol entity RPC) ma il client stub. RPC API: call remota.2

107 107 A fronte della dichiarazione di una procedura remota senza parametri di ingresso, e.g. int RESETLOG(void) = 2; rpcgen genera un corrispondente client stub che prevede comunque un parametro di ingresso di tipo *void : int *resetlog_1(void *, CLIENT *); e il modulo chiamante deve comunque passare un valore (dummy) NULL in corrispondenza a questo pseudo-parametro: result = resetlog_1(NULL, clnt); Cosa succede a fronte di una dichiarazione di procedura remota senza parametri di ritorno? La questione e' rilevante perche' si e' visto che per convenzione il client stub ritorna NULL per indicare una eccezione di sistema. Come puo' sfruttare questa convenzione in caso di mancanza di un parametro di ritorno? Procedure senza parametri di ingresso / ritorno

108 108 A fronte della dichiarazione di una procedura remota senza parametri di ritorno, e.g. void PING(void) = 2; rpcgen genera un corrispondente client stub che prevede comunque un parametro di ritorno di tipo void* : void *ping_1(void *, CLIENT *); Il valore ritornato dal client stub e' NULL per indicare una eccezione di sistema. != NULL per indicare una esecuzione con successo della procedura remota.  Ovviamente, in questo caso, la cosa riferita dal puntatore ritornato e' non significativa (non ha senso, ed e' impossibile, dereferenziare il puntatore in quanto e' un (void*) ). Procedure senza parametri di ritorno

109 109 Stub cliente della procedura remota: void PING(void) = 2; static struct timeval TIMEOUT = { 25, 0 }; void *ping_1(void *argp, CLIENT *clnt) { static char clnt_res; memset((char *)&clnt_res, 0, sizeof (clnt_res)); if (clnt_call(clnt, PING, (xdrproc_t)xdr_void, (caddr_t)argp, (xdrproc_t)xdr_void, (caddr_t)&clnt_res, TIMEOUT) != RPC_SUCCESS) { return (NULL); } return ((*void)&clnt_res); } Procedure senza parametri di ritorno

110 110 Sia il lato client che il lato server utilizzano la libreria di supporto RPC (che implementa la protocol entity RPC). clnt_create() clnt_call() clnt_destroy() svc_create() svc_run() svc_getargs() svc_sendreply() svc_freeargs() Alcune di queste funzioni sono chiamate direttamente dal programma utente ( clnt_create(), clnt_destroy() ), ma la maggior parte sono chiamate indirettamente tramite gli stub generati dal compilatore rpcgen (tutte le altre, e.g. clnt_call() ). Volendo, si potrebbe fare anche a meno di rpcgen e codificare direttamente le funzioni fornite dagli stub. RPC API Protocol entity RPC lato chiamante Protocol entity RPC lato chiamato

111 111 RPC: stub e protocol entity Client stub: Interfaccia_clnt.c Interfaccia_xdr.c Client RPC protocol ent.: clnt_create() clnt_call() clnt_destroy() Transport Service Client routine Server stub: Interfaccia_svc.c Interfaccia_xdr.c Server RPC protocol ent.: svc_create() svc_run() svc_getargs() svc_sendreply() svc_freeargs() Transport Service Server routine RPC middleware Interfaccia.x Interfaccia.h rpc_msg

112 112 Il sistema RPC offre una API che consente un accesso a 5 diversi livelli (tramite diversi insiemi di funzioni). Il livello di accesso utilizzato dal compilatore rpcgen e' quello chiamato simple: Richiede/consente al cliente di creare (e, eventualmente, distruggere) esplicitamente la handle RPC lato client. Lato client supporta solo chiamate confermate sincrone (bloccanti). Diversi livelli di accesso presentano diversi livelli di complessita' d'uso; consentono un controllo piu' o meno forte sulle operazioni (e.g. gestione degli errori e delle ritrasmissioni); consentono o meno l'accesso ad alcune funzionalita'. Ad esempio, solo i livelli di accesso avanzati consentono di invocare procedure remote in modo non confermato e in modo broadcast. RPC API

113 113 int svc_create (void (*dispatch)(const struct svc_req *, const SVCXPRT *), rpcprog_t prognum, rpcvers_t versnum, char *nettype); svc_create() creates server handles for all the transports belonging to the class nettype. nettype defines a class of transports which can be used for a particular application. svc_create() registers itself with the rpcbind (Port Mapper) service. dispatch() is called when there is a remote procedure call for the given prognum and versnum ; this requires calling svc_run(). If svc_create() succeeds, it returns the number of server handles it created, otherwise it returns 0 and an error message is logged. RPC API: svc_create()

114 114 svc_create() crea tutte le risorse necessarie per supportare l’interazione sulla rete con i clienti, e.g.: Socket, associato (per quanto detto) ad una porta effimera. Risorse per il Layer di Presentazione (e.g. stream XDR). Conoscendo l’indirizzo di trasporto su cui sara’ offerto il servizio RPC prognum/versnum, puo’ registrare questa informazione sul Port Mapper. dispatch() e’ il server stub associato all’interfaccia RPC prognum/versnum. Il server stub dispatch() supporta tutte le RPC definite come parte dell’interfaccia RPC prognum/versnum. Il server skeleton chiamera’ tante volte svc_create(), una per ciascuna interfaccia RPC (coppia prognum/versnum ) supportata dal server. svc_create()

115 115 void svc_run(void); This routine never returns. (server RPC sequenziale) In single threaded mode, it waits for RPC requests to arrive, and calls the appropriate service procedure (per il tramite della funzione di dispacciamento dispatch registrata per un certo programma/versione tramite chiamata a svc_create() ). (server RPC concorrente) Applications executing in the Automatic or User MT (MT = Multi Thread) modes should invoke this function exactly once. In the Automatic MT mode, it will create threads to service client requests. In the User MT mode, it will provide a framework for service developers to create and manage their own threads for servicing client requests. RPC API: svc_run()

116 116 Attende l’arrivo di PDU rpc_msg di CALL su tutte le server handle definite dal server skeleton tramite svc_create(). Attende cioe’ l’arrivo di PDU rpc_msg di CALL su tutti i socket registrati ognuno in una delle server handle del processo server. Quando arriva un PDU rpc_msg di CALL su uno di questi socket (lo decodifica e) cerca a quale handle questo socket e’ associato e passa la chiamata al server stub associato a questa server handle. Al momento dell’attivazione del server stub nella server handle e’ rimasto registrato il parametro di ingresso della call (in forma codificata).  Infatti un PDU RPC di call e’ effettivamente composto di due parti: un valore (PDU) di tipo rpc_msg di tipo CALL (l’istruzione di call su rete vera e propria) e, di seguito, cioe’ in fondo al PDU rpc_msg, come indicato nel testo XDR della sua definizione, l’encodifica del parametro di ingresso. svc_run()

117 117 bool_t svc_getargs(const SVCXPRT *xprt, const xdrproc_t inproc, caddr_t in); svc_getargs() decodes the arguments of an RPC request associated with the RPC service transport handle xprt. The parameter in is the address where the arguments will be placed. The parameter inproc is the XDR routine used to decode the arguments. The parameter xprt is the request’s associated transport handle. This routine returns TRUE if decoding succeeds, and FALSE otherwise. RPC API: svc_getargs()

118 118 bool_t svc_freeargs(const SVCXPRT *xprt, const xdrproc_t inproc, caddr_t in); svc_freeargs() frees any data allocated by the RPC/XDR system when it decoded the arguments to a service procedure using svc_getargs(). The parameter xprt is the request’s associated transport handle. This routine returns TRUE if it succeeds, and FALSE otherwise. RPC API: svc_freeargs()

119 119 bool_t svc_sendreply(const SVCXPRT *xprt, const xdrproc_t outproc, const caddr_t out); Called by an RPC service’s dispatch routine (server stub) to send the results of a remote procedure call. The parameter xprt is the request’s associated transport handle. The parameter outproc is the XDR routine which is used to encode the results. The parameter out is the address of the results. This routine returns TRUE if it succeeds, FALSE otherwise. RPC API: svc_sendreply()

120 120 rpcgen produce la procedura di dispacciamento del programma remoto/versione (del modulo RPC). Questa procedura implementa il server stub per tutte le procedure remote definite nel programma (modulo RPC). La procedura di dispacciamento: riconosce in base al contenuto della struct svc_rec in input quale e’ la procedura remota chiamata; (di conseguenza) ne deserializza il parametro di input (if any) chiamando svc_getargs() ; quindi attiva la procedura stessa; al termine dell’esecuzione della procedura (cioe’ quando questa esegue l’istruzione C return ), e tenendo conto dell’eventuale parametro di ritorno, genera la risposta remota invocando la system call svc_sendreply(). Deve anche liberare la memoria allocata per contenere la rappresentazione locale del parametro di ingresso (tramite svc_freeargs() ). RPC API server-side e rpcgen

121 121 rpcgen genera anche il process skeleton (funzione main() ) del programma remoto (modulo server RPC). Per prima cosa il process skeleton, chiamando la funzione svc_create(), crea la RPC server handle (per poter offrire il servizio in rete), vi registra la procedura di dispacciamento (relativa, quella generata dal compilatore rpcgen per l’interfaccia versionata), collegandola al numero del programma/versione, e registra sul Port Mapper le coordinate del servizio. Quindi, supponendo uno skeleton sequenziale, questo entra in un loop eterno chiamando la funzione svc_run() (il loop infinito e’ contenuto all’interno della funzione svc_run() ). La funzione svc_run(), a fronte della ricezione di un PDU RPC di call (che, come detto, contiene sia un valore di tipo rpc_msg che, di seguito, l’encodifica del parametro di ingresso), chiama la funzione di dispacciamento relativa al programma remoto/versione riferito nel PDU passandole (nel parametro struct svc_rec ) numero della procedura remota chiamata e parametro di input (if any) ancora serializzato. RPC API server-side e rpcgen

122 122 Consideriamo il programma di esempio program ESPROG { version ESVERS {... outT ESPROC(inT) =...;... } = 1; } =...; Quale sarebbe la struttura dello scheletro e dello stub server prodotti da rpcgen? Assumiamo che il protocollo di trasporto utilizzato sia UDP. Trascuriamo i casi di errore. RPC API server-side e rpcgen: esempio.1

123 123 Server skeleton int main(int argc, char *argv[]) { svc_create(serverStub, ESPROG, ESVERS, "udp"); // rpcgen chiamato con lo switch –s udp svc_run(); // waits for call PDUs from sockfd // and dispatches them to serverstub. // forever (never returns) // in realta’ svc_run() opera su tutte le server // handle definite dal server skeleton tramite // chiamate a svc_create() } RPC API server-side e rpcgen: esempio.2

124 124 Esempio semplificato di svc_create() struct SVCXPRT { int prog; int vers; void (*dispatch)(const struct svc_req *, const SVCXPRT *); int sock; XDR decStream; XDR encStream; XDR freeStream;... }; struct SVCPRT serverHandle; Un server RPC puo’ supportare diverse interfacce remote. All’arrivo di un PDU rpc_msg di call le scandisce tutte per identificare quale di queste e’ quella riferita (se nessuna, allora e’ un errore di binding!). RPC API server-side e rpcgen: esempio.3a

125 125 Esempio semplificato di svc_create() int svc_create (void (*dispatch)(const struct svc_req *, const SVCXPRT *), rpcprog_t prognum, rpcvers_t versnum, char *nettype) { // assume nettype == "udp" int sockfd; struct sockaddr_in serv_addr; sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(0); // int svc_create: continua RPC API server-side e rpcgen: esempio.3b

126 126 Esempio semplificato di svc_create() // int svc_create: continua tmp = bind(sockfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)); getsockname(sockfd, &serv_addr, &tmp); mapping m = {prognum, versnum, IPPROTO_UDP, ntohs(serv_addr.sin_port)} portMapper.PMAPPROC_SET(m); // portMapper locale serverHandle.prog = prognum; serverHandle.vers = versnum; serverHandle.sock = sockfd; serverHandle.stub = dispatch;... } RPC API server-side e rpcgen: esempio.3c

127 127 Esempio semplificato di svc_run() int svc_run () { // assume nettype == "udp" e una sola server handle. // per esercizio. while (1) { Si mette in attesa di un PDU di tipo rpc_msg sul (sui) socket registrato nella (nelle) server handle. Quando questo arriva lo decodifica e verifica che esso riferisca programma/versione registrati nella (in una delle) server handle. Verifica anche che l’operazione sia autorizzata. Compila in base al PDU di chiamata il parametro struct svc_req. Chiama la procedura di dispacciamento (server stub) registrata nelle server handle. } RPC API server-side e rpcgen: esempio.4

128 128 Server stub int serverStub(const struct svc_req *req, const SVCXPRT *svc) { inT in; outT *out; // N.B.: solo un puntatore! switch (req->procnum) {... case ESPROC : { // qui c’e’ un errore (di omissione)! svc_getargs(svc, xdr_inT, (caddr_t) &in); out = esproc_1_svc(&in, req); svc_sendreply(svc, xdr_outT, (caddr_t) out); svc_freeargs(svc, xdr_inT, (caddr_t) &in); }... } RPC API server-side e rpcgen: esempio.5

129 129 Il chiamante trasmette al chiamato, con ogni richiesta: un time stamp; il nome del proprio host; i propri userId e groupId effettivi, e la lista dei propri altri groupId. Il chiamato, in base a queste informazioni, decide se soddisfare o meno la richiesta. The bytes of the credential's opaque body encode the following structure: struct auth_unix { unsigned int stamp; string machinename ; unsigned int uid; unsigned int gid; unsigned int gids ; }; The verifier accompanying the credentials should be AUTH_NULL (cioe', non c'e' verifier e quindi possibilita' di autenticazione). Note that these credentials are only unique within a particular domain of machine names, uids, and gids. Sun RPC: Security, modalita' Unix

130 130 Per utilizzare questa modalita’ il chiamante, dopo avere creato la RPC client handle clnt, deve settare su di questa la modalita’ Unix. Per farlo deve eseguire l’assegnamento: clnt->cl_auth = authsys_create_default(); In questo modo inizializza nella handle anche le informazioni di machine name, uid e gids (dell’utente che esegue il programma chiamante) che saranno poi utilizzate in tutte le successive chiamate. In realta’ il chiamante puo’ modificare queste informazioni nella handle a suo piacimento, per cui puo’ simulare una identita’ diversa da quella reale senza possibilita’ di effettiva autenticazione da parte del chiamato. In modalita’ Unix non e’ prevista alcuna autenticazione del chiamato nei riguardi del chiamante.  L’idea e’ che l’autenticazione dell’utente sia avvenuta sulla macchina client, e che il server si fidi di questa autenticazione. La verifica delle credenziali del chiamante non e’ operata dal supporto RPC, che al riguardo e’ trasparente, ma e’ lasciata alla procedura chiamata. Sun RPC: Security, modalita' Unix

131 131 Client: #include #include "dir.h" /* generated by rpcgen */ main(int argc, char *argv[]) { CLIENT *clnt; char *server; char *dir; readdir_res *result; namelist nl; if (argc != 3) { fprintf(stderr, "usage: %s host directory\n",argv[0]); exit(1); } server = argv[1]; dir = argv[2]; Security, modalita' Unix (system): esempio.1

132 132 Client: (continua): /* Create client "handle" used for calling DIRPROG * on the server designated on the command line. */ clnt = clnt_create(server, DIRPROG, DIRVERS, "tcp"); if (clnt == (CLIENT *)NULL) { clnt_pcreateerror(server); exit(1); } /* set unix security with my userid and groupid */ clnt->cl_auth = authsys_create_default(); /* call remote server procedure */ result = readdir_1(&dir, clnt); if (result == (readdir_res *)NULL) { clnt_perror(clnt, server); exit(1); } Security, modalita' Unix (system): esempio.2

133 133 Client: (continua): /* Okay, we successfully called the remote procedure. */ if (result->errno != 0) { /* Remote system error. Print error message and die. */ errno = result->errno; perror(dir); exit(1); } /* Successfully got a directory listing. Print it. */ for (nl = result->readdir_res_u.list; nl != NULL; nl = nl->next) { printf("%s\n", nl->name); } /* free dynamic memory of result value */ xdr_free(xdr_readdir_res, (char*)result); clnt_destroy(clnt); exit(0); } Security, modalita' Unix (system): esempio.3

134 134 Remote server procedures: #include #include "dir.h" /* Created by rpcgen */ readdir_res * readdir_1_svc(nametype *dirname, struct svc_req *req) { DIR *dirp; struct dirent *d; namelist nl; namelist *nlp; static readdir_res res = {0, {NULL}}; /* must be static! */ /* Free previous result */ xdr_free(xdr_readdir_res, (char*)&res); Security, modalita' Unix (system): esempio.4

135 135 Remote server procedures (continua): /* verifica che il cliente utilizzi security Unix */ switch(req->rq_cred.oa_flavor) { case AUTH_SYS: { struct authsys_parms *sys_cred; struct stat buf; mode_t st_mode; if (stat(*dirname, &buf)) { res.errno = errno; return (&res); } st_mode = buf.st_mode; sys_cred = (struct authsys_parms *) req->rq_clntcred; /* verifica l'autorizzazione del cliente in base ai * suoi valori di userId e groupId, relativamente ai * valori di userId e groupId dell'owner del file, e * rispetto ai diritti d'accesso previsti per il file */ Security, modalita' Unix (system): esempio.5

136 136 Remote server procedures (continua): if (sys_cred->aup_uid == buf.st_uid) { if (!(st_mode & S_IREAD)) { res.errno = EACCES; return (&res); } } else if (sys_cred->aup_gid == buf.st_gid) { if (!(st_mode & S_IRGRP)) { res.errno = EACCES; return (&res); } } else {/*other including superuser*/ if (!(st_mode & S_IROTH)) { res.errno = EACCES; return (&res); } break; } default: /* il cliente non utilizza security Unix */ res.errno = EACCES; return (&res); } Security, modalita' Unix (system): esempio.6

137 137 Remote server procedures (continua): if ((dirp = opendir(*dirname)) == NULL) { res.errno = errno; return (&res); } nlp = &res.readdir_res_u.list; while (d = readdir(dirp)) { nl = *nlp = (namenode *) malloc(sizeof(namenode)); if (nl == NULL) { closedir(dirp); res.errno = EAGAIN; return(&res); } nl->name = strdup(d->d_name); nlp = &nl->next; } *nlp = NULL; closedir(dirp); res.errno = 0; return (&res); } Security, modalita' Unix (system): esempio.7

138 138 The Port Mapper program maps RPC program and version numbers to transport-specific port numbers. Il mappaggio e’ locale all’host su cui il Port Mapper e’ in esecuzione. Il Port Mapper e’ un name service locale! This program makes dynamic binding of remote programs possible. By running only the Port Mapper on a reserved (well known) port, the port numbers of other remote programs can be ascertained by querying the Port Mapper.  Il Port Mapper offre il suo servizio sulla porta well known 111, sia su TCP che su UDP. Il servizio di Port Mapper e’ implementato tramite RPC, ed e’ quindi descritto da una interfaccia RPC.  Ma noi non lo possiamo accedere via RPC perche’ non sappiamo come effettuare il binding a un server RPC che opera su porta well known (anziche’ su porta effimera)! Port Mapper

139 139 Il cliente conosce il nome DNS (o l’indirizzo IP) della macchina su cui e’ offerto il servizio RPC che gli interessa.  Rivolgendosi al resolver DNS gethostbyname() il cliente ottiene la traduzione del nome DNS della macchina nel relativo indirizzo IP. Il cliente (a) conosce i numeri di programma e di versione dell’interfaccia remota (l’identificatore dell’interfaccia versionata) del servizio RPC che gli interessa, e (b) conosce anche il servizio di Trasporto su cui il servizio e’ offerto.  Rivolgendosi al Port Mapper residente all’indirizzo IP identificato al passo precedente e indirizzabile sulla porta well known 111 (sia su TCP che su UDP), il cliente ottiene la porta sui cui e’ offerto in questo momento sulla macchina su cui e’ in esecuzione il Port Mapper a cui si e’ rivolto il servizio RPC che gli interessa. RPC binding

140 140 Poiche' la funzione normale del name service DNS e' quella di tradurre il nome di una macchina nel suo indirizzo IP, esiste una funzione (un resolver DNS) dedicata a questo scopo. Fornendo alla funzione un nome di una macchina essa ne ritorna la descrizione completa secondo la struttura hostent. #include struct hostent { char *h_name; /* canonical name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses */ }; struct hostent *gethostbyname(const char *name); gethostbyname().1

141 141 Nel nostro caso h_addrtype==AF_INET e la lista di indirizzi e' una lista di struct in_addr. Se la funzione non trova alcun host con il nome (domain name) indicato ritorna NULL. Entrambi i campi che rappresentano liste sono implementati tramite un puntatore ad un vettore che contiene i puntatori agli elementi della lista. I vettori sono terminati da un puntatore di valore NULL. DNS opera solo su domain name completi. Di norma e' pero' sufficiente fornire a gethostbyname() solo il nome locale, senza il suffisso di dominio, perche' e' la funzione stessa che concatena il nome indicato con una lista configurabile di suffissi. E' ovvio che se il nodo che cerchiamo non appartiene ad uno dei domini configurati e' necessario invocare gethostbyname() passando il domain name completo. N.B.: questa funzione e' considerata obsoleta e di uso pericoloso perche' ritorna il suo risultato su memoria allocata staticamente. gethostbyname().2

142 142 The Port Mapper also aids in broadcast RPC. A given RPC program will usually have different port number bindings on different machines, so there is no way to directly broadcast to all of these programs. The Port Mapper, however, does have a fixed port number. So, to broadcast to a given program, the client actually sends its message to the Port Mappers located at the broadcast address. Each Port Mapper that picks up the broadcast then calls the local service specified by the client. When the Port Mapper gets the reply from the local service, it sends the reply on back to the client. Si riferisce ad un possibile utilizzo della operazione del Port Mapper PMAPPROC_CALLIT(), che consente al chiamante di chiamare una operazione remota di un server RPC senza doversi prima collegare a questo, effettuando la chiamata indirettamente con la mediazione del Port Mapper. Port Mapper: PMAPPROC_CALLIT() e multicast call

143 143 const PMAP_PORT = 111; /* well known * portmapper port number */ /* A mapping of (program, version, protocol) to port * number: */ struct mapping { unsigned int prog; unsigned int vers; unsigned int prot; unsigned int port; }; /* Supported values for the "prot" field: */ const IPPROTO_TCP = 6; /* prot number for TCP */ const IPPROTO_UDP = 17; /* prot number for UDP */ Port Mapper: service definition.1

144 144 /* A list of mappings: */ struct *pmaplist { mapping map; pmaplist next; }; /* Arguments to callit: */ struct call_args { unsigned int prog; unsigned int vers; unsigned int proc; opaque args<>; }; /* Results of callit: */ struct call_result { unsigned int port; opaque res<>; }; Port Mapper: service definition.2

145 145 program PMAP_PROG { version PMAP_VERS { void PMAPPROC_NULL(void) = 0; bool PMAPPROC_SET(mapping) = 1; bool PMAPPROC_UNSET(mapping) = 2; unsigned int PMAPPROC_GETPORT(mapping) = 3; pmaplist PMAPPROC_DUMP(void) = 4; call_result PMAPPROC_CALLIT(call_args) = 5; } = 2; } = ; Port Mapper: service definition.3

146 146 The Port Mapper program currently supports two transport protocols (UDP and TCP). The Port Mapper is contacted by talking to it on assigned port number 111 (Sun RPC) on either of these protocols. PMAPPROC_NULL : this procedure does no work. By convention, procedure zero of any protocol takes no parameters and returns no results. Serve per fare il ping di un programma remoto! PMAPPROC_SET : when a program first becomes available on a machine, it registers itself with the Port Mapper program on the same machine. The program (server RPC) passes its program number prog, version number vers, transport protocol number prot, and the port port on which it awaits service request. The procedure returns a boolean reply whose value is TRUE if the procedure successfully set up the mapping and FALSE otherwise. The procedure refuses to establish a mapping if one already exists for the tuple ( prog, vers, prot ). Notare che un server RPC puo' registrarsi piu' volte: per diverse versioni o perche' supporta diversi protocolli o diverse interfacce. Port Mapper: operazioni.1

147 147 PMAPPROC_UNSET : when a program becomes unavailable, it should unregister itself with the Port Mapper program on the same machine. The parameters and results have meanings identical to those of PMAPPROC_SET. The protocol and port number fields of the argument are ignored. Quindi la deregistrazione per una coppia ( prog, vers ) e’ globale, da tutti i mapping, tramite una singola invocazione dell’operazione PMAPPROC_UNSET. PMAPPROC_GETPORT : given a program number prog, version number vers, and transport protocol number prot, this procedure returns the port number on which the program is awaiting call requests. A port value of zero as the return parameter means the program has not been registered. The port field of the input argument is ignored. PMAPPROC_DUMP : this procedure enumerates all entries in the Port Mapper's database. Port Mapper: operazioni.2

148 148 PMAPPROC_CALLIT : this procedure allows a client to call another remote procedure on the same machine (del Port Mapper) without knowing the remote procedure's port number. It is intended for supporting broadcasts to arbitrary remote programs via the well-known port mapper's port. Parameters prog, vers, proc, and the bytes of args are the program, version and procedure numbers, and parameters of the remote procedure that is to be called. Esercizio: implementare le operazioni PMAPPROC_GETPORT e PMAPPROC_SET, e PMAPPROC_CALLIT. Nell’operazione PMAPPROC_CALLIT la chiamata effettiva dell’operazione target deve essere effettuata utilizzando direttamente i servizi di Trasporto e di Presentazione. Port Mapper: operazioni.3

149 149 Definire una API per il sistema RPC composta di una singola operazione che sfruttando la procedura PMAPPROC_CALLIT() del Port Mapper, consenta di invocare qualunque operazione remota di qualunque server RPC presente sulla stessa macchina del Port Mapper stesso. Dare il prototipo di questa procedura di interfaccia al sistema RPC e poi implementare la procedura utilizzando direttamente i servizi di Trasporto e di Presentazione per interagire con il Port Mapper.  Domanda: il servizio di Trasporto utilizzato per l’implementazione di questa operazione dipende da quello sul quale e’ offerto il servizio RPC che si vuole accedere? Esercizio 1

150 150 A.Implementare le seguenti funzioni della protocol entity RPC lato server: svc_run() svc_getargs() svc_sendreply() Si assuma che il server RPC implementi un’unica interfaccia remota e che il protocollo di trasporto utilizzato sia UDP. Utilizzare direttamente i servizi di Trasporto e di Presentazione per gestire il dialogo con il chiamante. B.Implementare le funzioni clnt_create() e clnt_call() della protocol entity RPC lato client utilizzando direttamente i servizi di Trasporto e di Presentazione per interloquire rispettivamente con il Port Mapper e con il server RPC.  In entrambi gli esercizi, inventare in modo ragionevole e giustificabile il contenuto e la gestione delle strutture dati rilevanti (e.g. la client handle CLIENT ). Esercizio 2


Scaricare ppt "Lez. 101 Reti di Calcolatori RPC - Remote Procedure Call Vedi: W.R. Stevens, Unix Network Programming, Prentice Hall, sezz. 18.1- 18-3, pagg. 692-709."

Presentazioni simili


Annunci Google