Progetto di :
INTRODUZIONE:
Il progetto ,svillupato in java ,vuole implementare un servizio di nomi (name service) ,partendo dalle specifiche date nel documento RFC 1035 relativo al DOMAIN NAME SYSTEM.
DESCRIZIONE DNS:
Il servizio si compone di richieste (queries) fatte da utente e risposte
(responses) fornite dal server.
L'obbiettivo principale è l'attuazione di corrispondenze tra
le coppie nome_logico-indirizzo_IP .
Ogni richiesta viene fatta al servizio di nomi, cioè il riferimento
da mappare viene passato al resolver che
- o recupera la corrispondenza dalla propria cache ;
- o la trova attraverso una richiesta ad un name server;
I domini sono divisi in zone di autorità soggette a diversi
servitori.
Un name server ha solo una visione locale del dominio (requisito di
località nel DNS) , cioè per richieste di corrispondenze
esterne alla sua zona o fornisce un riferimento al richidente circa
un name server gerarchicamente a lui superiore o si incarica lui stesso
di effetuare la richiesta a tale name server.
In genere , ogni zona ha un primary master responsabile per i dati
della zona ,ma in più ci sono una serie di secondary master che
sono copie del primary , con consistenza garantita dal protocollo DNS.
AFFIDABILITA':
Allo startup il secondario chiede i dati al primario e può far
fronte al traffico in caso di guasto;
ad intervalli prefissati , i secondari chiedono le informazioni al
primario (modello PULL)
EFFICIENZA SU LOCALITA':
I server mantengono informazioni sui dati già richiesti in precedenza
PROTOCOLLI COMUNICAZIONE :
Le richieste e le risposte per il name server usano protocollo UDP ,sulla
porta 53;
mentre per il coordinamento si usa il protocollo TCP ,sempre sulla
porta 53.
Un server mantiene una registrazione (record) per ogni risorsa; le richieste
(query) consultano l'insieme dei record;
i record sono composti dai seguenti campi :
Esistono diversi tipi di server :
Dopo questa breve descrizione riguardante il DNS , mi sono imposto
di articolare il progetto in due parti principali:
Configurazione sistema con un solo name server:
Local Host
| Foreign
|
+---------+
+----------+ | +--------+
|
| user queries |
|queries | | |
| User |-------------->|
|---------|->|Foreign | +----------+
| Program |
| Resolver | | |
Name |<----|MasterFile|
|
|<--------------|
|<--------|--| Server | +----------+
|
| user responses|
|responses| | |
+---------+
+----------+ | +--------+
| A
|
cache additions | | references |
V |
|
+----------+ |
| cache |
|
+----------+ |
Configurazione con due name server:
+---------+
local host
| forreign
/
/|
|
+---------+ |
+----------+ | +--------+
|
| |
| primary |responses| |
|
|
| |
| Name |---------|->|Foreign |
| Master |-------------->|
Server | |
|Resolver|
| file |
|
| |<--------|--|
|
|
|/
| | queries |
+--------+
+---------+
+----------+ |
A |maintenance | +---------+
| +------------|->|Foreign |
| responses | |secondary|
|
| | Name |
+------------------|--| Server |
maintenance queries | +---------+
Fasi del progetto:
1°FASE:
Implementazione DnsServer1+DnsLookUp
Il primo passo nella progettazione del Name Server ,è stato quello
di costruire un package di strumenti in grado di rispondere alle specifiche
,definite nella RFC1035 , riguardanti il formato dei messaggi ricevuti
e spediti dai server.
Il package è cosi' composto da:
la classe DnsConnection, implementa il Domain
Name Services come specificato nella RFC-1035. La classe DnsConnection
può interrogare( query) un DNS server usando l' UDP protocol.
Il server e la porta sono configurabili dentro l'oggetto DnsConnection.
Sia DNS queries che responses sono rappresentate dalla classe DnsMessage.
Un messaggio DNS è composto da diverse classi. La prima è il messagio stesso, implementato dalla classe DnsMessage. Essa contiene metodi come getResponseCode, isRecursionAvailable, getAnswerCount, etc, che permettono di estrarre dal messagio suoi particolari settaggi. I messaggi contengono liste di questions, answers, authorities, e additionals. Una Interrogazione( query) generalmente contiene solo una domanda(question). Le Questions sono instanze di DnsQuestion. Sono composte di un domain name, type, e class. Il type è il tipo di Resource Record (RR) che è stato richiesto. Le Answers, authorities, and additionals sono le RRs spedite come risposta ad una interrogazione( query).Ciascun RR (instanza di DnsResource) estende DnsQuestion con il valore di time to live a alcuni dati specifici dipendenti dal tipo di RR. Quando una DnsResource è spacchettata (unpacked), essa analizza la rappresentazione in byte impacchettati del messagio e istanzia una specifica sottoclasse di DnsResource dipendente dal valore di tipo che rappresenta. Tale sottoclasse allora spacchetta (unpacks) il resto del RR.
Le Resource Records implementate come classi sono: A (Address), CNAME (CanonicalName), HINFO (HardwareInfo), MX (MailExchange), NS (NameServer), PTR (Pointer), and SOA (StartOfAuthority).
Ora mostro uno scema delle relazioni tra le classi del package:
Definito il package , ho implementato il Name Server ed il Resolver .
La classe dove il name server è implementato è la
DnsServer1
,come "master file " utilizzo un file del local file system
chiamato "dns.properties" .Il file è organizzato come una lista
di nomi (sarebbero i nomi di dominio) a cui è associato un
indirizzo_IP: es. christian.com=1.2.3.4
private void answerQuery(DnsMessage query, DnsMessage response)
throws Exception
{
DnsQuestion question = (DnsQuestion) query.getQuestions().elementAt(0);
response.addQuestion(question);
String address = null;
address = names.getProperty(question.getName());}
if(address == null)
}
if (flag == true)
{try{
DnsQuestion question
= (DnsQuestion) query.getQuestions().elementAt(0);
dns = new DnsConnection("scm.com");
System.out.println("sono a SCM.COM, DnsServer1 run.");
DnsMessage resp = dns.query(question.getName(),question.getType());
for(int i = 0;i <
resp.getAnswerCount();i++)
response.addAnswer((DnsResource)resp.getAnswers().elementAt(i));
System.out.println("dopo
query DnsServer1 ready.");
if (response.getAnswerCount()
== 0)
printResponse(response);
response.pack();
return response;
}
Il Resolver implementato ha il compito di interfacciarsi tra l'utente
ed il name server , trasformando le query di utente in query
secondo il formato riconosciuto dal name server , ricevere la risposta
e renderla visibile all'utente.
Il Resolver costruito è implementato nella classe DnsLookUp
,naturalmente importa come la classe DnsServer1, il package
che permette l'utilizzo dei messaggi formato name server (import
mylib.net.dns.*;).
Può essere utilizzato in due modalità differenti : una
interattiva ,dove viene avviata una sessione di query; ed una in cui al
ricevimento della risposta l'applicativo termina.
Internamente alla sessione di query l'utente può settare 3 tipi
di parametri:
- il name server a cui collegarsi ;
- il tipo di query che si vuole eseguire ;
- ed il livello di debug (che informazioni visualizzare nella risposta).
Viene implementata una cache dove sono memorizzate le risposte precedenti;
Il collegamento con il name server è impostato con un time-out
, se tale tempo di attesa scade l'applicativo finisce l'esecuzione
.
2°FASE:
Implementazione replicazione servizio DnsServer1+DnsServer2+DnsAdministrator+DnsLookUp
La seconda parte del progetto prevede la replicazione del servizio di
nomi introducendo un secondo Name Server (DnsServer2) , avente le stesse
carratteristiche di servizio e operante con gli stessi dati di DnsServer1
. Questa è una specifica richiesta per la maggiore efficienza ed
affidabilità del sistema .L'ingresso di un secondo Name server nel
sistema ha implicato l'utilizzo di un protocollo di cordinamento
tra i due ,ho inoltre introdotto anche la presenza di un Amministratore
del sistema , gestore del "master file", che dovrà anch'esso coordinare
i suoi rapporti con i server.
Prima di tutto occorre definire i ruoli di ogni componente del sistema:
I rapporti tra Resolvers e Name Servers si svolgono sempre
come descritto nella prima fase , utilizzando una connessione UDP sulla
porta 53 , che anche se non garantisce affidabilità , implica un
basso overhead , e vista la dimensione dei pacchetti di dati scambiati
è accettabile .
Le caratteristiche del collegamento sono tali che se accade un guasto
al lato Resolver il Server non se ne preoccupa ,mentre se si guasta il
lato Server o vi è un problema sulla rete di connessione , il Resolver
inviata la richiesta attende un TIME-OUT e quindi termina l'esecuzione
, che porta all'unico inconveniente della perdita dei dati memorizzati
nella cache di quest'ultimo. L'utente quindi , se vorrà riaprire
una sessione di query potrà farlo rilanciando l'applicativo e riprovare
a collegarsi allo stesso Server o ad un altro.
Per quanto riguarda i 2 Name Server e l'Amministratore la gestione
dei rapporti si svolge utilizzando una connessione TCP sulla porta 53 ,
è preferita una connessione di tale tipo poichè la gestione
di tale protocollo coinvolge la trasmissione di quantità di dati
più rilevanti in termini di importanza e dimensione , rispetto alla
precedente.
Il punto focale di coordinamento è il primary name server
(DnsServer1)
le altre due entità devono relazionarsi a lui per la gestione della
risorsa comune rappresentata dal contenuto del "master file".
Quindi ho deciso di istanziare la gestione dei rapporti per il DnsServer1
in un demone (Demone1) che si occupa di :
- ascoltare le comunicazioni che l'Amministratore inoltra e istanziare
la procedura conseguente;
- ascoltare le richieste che giungono dal secondary name server e istanziare
le procedure conseguenti;
Il Demone serve una richiesta alla volta ,quindi o interagisce
con l'Amministratore oppure con il secondary name server.
In questo modo non si avranno inconsistenze nei dati trasmessi.
L'Amministratore si relaziona con il DnsServer1, dopo l'editazione,da parte sua, di cambiamenti nel "Master File" e si avvale di un applicativo DnsAdministrator per notificare il cambiamento.
Il DnsServer2 ha la necessità di comunicare con
il primary server in due particolari occasioni :
- all'avvio ,lo START-UP, per richiedere i dati con cui servire le
richieste dei Resolvers ;
- durante l'eseguzione del servizio ,periodicamente ,per richiedere
l' eventuale l'aggiornamento dei dati , l'UP-DATE.
Il secondary si avvale per svolgere tali operazione di un demone (Demone2)
, istanziato ed avviato nel main();
Coordinamento DnsServer1---DnsAdministrator
Il coordinamento tra il primary (DnsServer1) ed l'Amministratore
è necessario in quanto è prevista la possibilità di
editare cambiamenti sul "master file" da parte dell'amministratore del
sistema .
DnsServer1 utilizza per tale coordinamento un demone (Demone1) istanziato
e messo in eseguzione all'avvio nel main().Tale demone apre una ServerSocket
sulla porta 53 (connessione TCP) e si mette in ascolto ,in attesa di un
collegamento da parte di DnsAdministrator .
Il demone riconosce la sequenza di due stringhe chiave per identificare
il DnsAdministrator : la prima stringa ricevuta identifica l'Amministratore(uno
username) , la seconda un ulteriore riconoscimento (una password).
Terminato il riconoscimento in modo positivo il demone avvia l'aggiornamento
dei dati ricaricando il master file ,
ed aggiornando il serial number.
Il serial number identifica la versione dei dati caricati , viene incrementato
ad ogni aggiornamento ,vedremo che servirà successivamente.
Durante l'aggiornamento , viene impedito l'accesso ai dati del
thread Service1 che svolge il servizio di name server ,
attraverso il settaggio della variabile permess ,in questo modo Service1
devia la query al secondary server.
Ora mostro la parte di codice che descrive l'interazione:
Per il Demone1:
class Demone1 extends DnsServer1
{..
if( Request.equalsIgnoreCase("ADMINISTRATOR")) // se vera
amministratore connesso
{String mess = "GIVE
ME THE PASSWORD";
sockout.println(mess);
Request = sockin.readLine();
if( Request.equalsIgnoreCase("mypass"))
{
System.out.println("\n Password
esatta = "+ Request);
Per il DnsAdministrator:
public class DnsAdministrator
{
static Socket sock;
static String mess1 = "ADMINISTRATOR";
static String mess2 = "mypass";
public static void main(String[] args)
{
try{
sock = new Socket("localhost",53);
BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
PrintWriter out =new PrintWriter(new BufferedWriter(new
OutputStreamWriter(sock.getOutputStream())),true);
out.println(mess1);
String str = in.readLine();
System.out.println(" " + str);
out.println(mess2);
sock.close();
..
..}
Ora mostro il diagramma delle interazioni:
Coordinamento DnsServer1---DnsServer2
Il coordinamento tra il primary server ed il secondary server si svolge attraverso l'interazione di due demoni (Demone1,Demone2) facenti le veci dei rispettivi Server .Come già preannunciato si utilizza sempre la connessione TCP sulla porta 53 , per entrambi i tipi di interazioni (START-UP,UP-DATE).
La procedura di START-UP viene avviata con l'instanziamento
e la messa in esecuzione del Demone2 ,nel main() del DnsServer2 . L'interazione
del Demone2 con il Demone1 si compone di una prima fase di riconoscimento
con l'invio del messaggio "STARTUP" da parte del Demone2 ,seguita dalla
fase di trasferimento dati(contenuto del master file) dal Demone1 al Demone2
e come ultimo passo vi è l'invio del serial number
associato ai dati ,sempre da Demone1 a Demone2. Se la fase di start-up
dovesse fallire a causa di un guasto del DnsServer1 o della linea ,per
non lasciare il DnsServer2 fuori servizio viene avviata una procedura di
emergenza ,che fa utilizzare come dati di lavoro e serial number gli ultimi
salvati rispettivamente nei file dns1.properties, Soa1.properties
.
Il serial number verrà utilizzato nell'UP-DATE ,per controllare
la versione dei dati.
Ora mostro il diagramma delle interazioni dello START-UP:
La procedura di UP-DATE si compone ach'essa di una prima fase
di riconoscimento del Demone2 che invia il messaggio
"REFRESH" ,a questo punto il Demone1 riconosciuto chi si è collegato
e la procedura richiesta invia il serial number corrente al Demone2 , che
lo confronta con quello in suo possesso.
Se i numeri sono uguali (cioè la versione dei dati posseduti
dal DnsServer2 è aggiornata) la procedura di UP-DATE termina , e
viene ripetuta dopo REFRESH secondi.
Se il numero spedito dal Demone1 è maggiore (cioè il
DnsServer2 ha dati non aggiornati) la procedura di UP-DATE viene completata
, con l' invio da parte del Demone2 della stringa "UPDATE" che viene
interpretata dal Demone1 come richiesta di invio dati .Il Demone2 completa
la procedura salvando ,i dati ed il serial number speditigli, nei
file dns1.properties ,e Soa1.properties , che sono utilizzati nella procedura
d'emergenza , anche in questo caso la procedura di UP-DATE viene ripetuta
dopo REFRESH secondi.
Se la fase di UP-DATE dovesse fallire a causa di un guasto del DnsServer1
o della linea ,per non lasciare il DnsServer2 con dati e serial number
inconsistenti viene avviata una procedura di emergenza ,che fa utilizzare
come dati di lavoro e serial number gli ultimi salvati rispettivamente
nei file dns1.properties, Soa1.properties .
Ora mostro il diagramma di interazione della fase di UP-DATE:
NOTA : la procedura di UP-DATE utilizza 2 parametri quali serial number e REFRESH , impostati rispettivamente uno dal DnsServer1 e l'altro dal DnsServer2. In realtà nella RFC1035 tale procedura è cosi' composta : prima il secondary server invia una query del di tipo SOA verso il primary server, quindi il secondary server controlla il campo serial di tale record inviatogli dal primary e lo utilizza come nel mio progetto per capire se deve eseguire l'aggiornamento ,in caso di aggiornamento deve eseguire una query di tipo AXFR che equivale a chiedere il trasferimento della zona gestita dal primary server .Il record SOA contiene inoltre il parametro REFRESH che deve essere utilizzato per impostare l'intervallo con cui va ripetuta la procedura di UP-DATE.
Nel progetto eseguito come serial number utilizzo la variabile di tipo
long serno impostata con il metodo getTime() della
classe Date che ritorna un valore temporale in millisecondi dal 1/1/1970:
..
Date
now = new Date();
serno = now.getTime();
..
Ho scelto questo tipo di implementazione poichè viene richiesto che il serial number sia incrementato ad ogni caricamento del master file da parte del primary server , anche in sessioni di lavoro diverse.
Per quanto riguarda il REFRESH ho impostato un tempo che mi permettesse
di controllare il corretto funzionamento del sistema.
E' stata necessaria questa implementazione viste le caratteristiche
del master file da me implementato.
Le specifiche di lavoro del sistema , in parte già descritte
nella relazione, sono queste:
Per la funzione di NAME SERVER:
4. Lanciare il DnsAdministrator: "java DnsAdministrator"
Questo applicativo va lanciato dopo aver editato cambiamenti
nel "master file" : dns.properties.
E' impostato per essere lanciato sulla stessa macchina dove
esegue DnsServer1.
Nota:va aggiunta nel classpath la directory con il percorso c:\projava\lib