Pages

Κυριακή 23 Ιουνίου 2013

NoIP στο Raspberry Pi και μείωση του Internet Traffic

Εισαγωγή

Πολλές φορές χρειαζόμαστε να στήσουμε κάποιου είδους server έτσι ώστε να μπορούμε να έχουμε πρόσβαση σε αυτόν από τον "έξω κόσμο", δηλαδή από παντού, όπου υπάρχει πρόσβαση στο διαδίκτυο. Το πρόβλημα σε αυτό είναι πως οι περισσότεροι πάροχοι Internet (Internet Service Providers, εν συντομία ISPs) δε δίνουν σταθερή διεύθυνση IP στα router που έχουμε σπίτι μας (εκτός, φυσικά, αν το ζητήσουμε με κάποια παραπάνω χρέωση). Έτσι, θα πρέπει κάθε φορά να γνωρίζουμε την IP διεύθυνση του router μας για να μπορέσουμε να έχουμε πρόσβαση στον server που κάθεται πίσω από αυτό. Αυτόν ακριβώς το σκοπό έχει και το παρόν άρθρο. Τι θα λέγατε όμως, αν το σύστημα που θα μας βοηθούσε είναι ένα Raspberry Pi; Όχι πως αυτά που περιγράφονται εδώ δε μπορούν να εφαρμοστούν και σε άλλο σύστημα υπολογιστή... Αντιθέτως, ένα δυνατό σημείο του ανοικτού λογισμικού, είναι πως η διαχείρισή του γίνεται με τον ίδιο ακριβώς τρόπο, είτε αυτό τρέχει σε ένα μηχάνημα με 64 επεξεργαστές, είτε τρέχει σε μια... τοστιέρα (βλ. NetBSD!).

Τι θα δούμε

Παρότι το άρθρο προορίζεται σε ανθρώπους που ξέρουν να ρυθμίζουν routers, να στήνουν servers, να διαχειρίζονται υπηρεσίες σε λειτουργικά όπως το Linux και να έχουν τη δυνατότητα, ίσως, να ασχοληθούν με ένα gadget του τύπου Raspberry Pi, εν τούτης θα δοθούν πληροφορίες ακόμα και σε πιο "αρχικά" πράγματα. Μέσα σε όλους τους σκοπούς του άρθρου είναι να μπορέσει ακόμα και κάποιος αρχάριος να βρει κάποιες εναρκτήριες πληροφορίες, εισαγωγικές περισσότερο σε όλα αυτά με τα οποία σχετίζεται η πρόσβαση ενός υπολογιστή σε ένα άλλο, μέσω του διαδικτύου. Ακόμα και για την πληρότητα του άρθρου, δε θα μπορούσαν να λείπουν αυτές οι πληροφορίες. Αν είστε κάπως πιο προχωρημένος χρήστης/γνώστης και κάποια παράγραφος σας είναι απλή ή δεν προσφέρει κάποιες παραπάνω γνώσεις σε αυτές που ήδη έχετε, μπορείτε ασφαλώς να την προσπεράσετε και να μπείτε στο "ψητό" της ιστορίας.

Μια μικρή περίληψη όσων θα δούμε μέσα στο παρόν άρθρο:

DNS, Ευρετήριο διευθύνσεων διαδικτύου

Κάθε υπολογιστής ή άλλη συσκευή που συνδέεται στο διαδίκτυο με σκοπό να ανταλλάξει πληροφορίες διαμέσου αυτού, παίρνει μια μοναδική διεύθυνση στον κόσμο. Αυτή είναι και η βασική ιδέα. Αυτή την ονομάζουμε διεύθυνση IP. Φυσικά και υπάρχουν διευθύνσεις που θα τις δούμε να υπάρχουν ταυτόχρονα σε πολλούς υπολογιστές (π.χ. μια διεύθυνση της μορφής 192.168.1.1), αλλά ας μείνουμε στη βασική ιδέα του συστήματος του διαδικτύου και ας μην επεκταθούμε σε τέτοιες λεπτομέρειες. Το router που έχουμε στο σπίτι μας ή στο γραφείο μας (ή οπουδήποτε, τέλος πάντων) είναι και αυτό μια συσκευή επικοινωνίας που συνδέεται με το διαδίκτυο. Κατά συνέπεια και αυτό έχει μια διεύθυνση IP. Γιατί γίνεται αυτό; Αυτή η διεύθυνση είναι ένα κομμάτι της "ταυτοποίησης" του αντικειμένου. Όταν γίνεται μια επικοινωνία = μεταφορά δεδομένων από ένα σύστημα σε ένα άλλο, τότε απλά οι διευθύνσεις είναι αυτές που καθορίζουν τον αποστολέα και τον παραλήπτη. Όπως ακριβώς και η μεταφορά αλληλογραφίας με το κανονικό ταχυδρομείο που χρειάζεται να φέρει ο φάκελος τις διευθύνσεις αποστολέα και παραλήπτη.

Οι διευθύνσεις που δίνονται σε όλα αυτά τα συστήματα επικοινωνίας, είτε είναι υπολογιστές είτε είναι ένας δικτυακός εκτυπωτής κ.λ.π, έχουν τη μορφή 4 αριθμών χωρισμένων από τελείες (.). Ο κάθε αριθμός μπορεί να πάρει τιμές από 0 μέχρι και 255. Υπάρχει και ένας πιο σύγχρονος τρόπος διευθυνσιοδότησης, όπου η κάθε διεύθυνση αποτελείται από 8 τετραψήφιους δεκαεξαδικούς αριθμούς χωρισμένους από άνω-κάτω τελείες (:).

Και γιατί τα αναφέρουμε όλα αυτά; Βλέπετε πουθενά λέξεις, όπως google.com, blogspot.gr ή my_really_really_fancy_site.biz; Όχι, γιατί ΔΕΝ ΥΠΑΡΧΟΥΝ. Αυτό σημαίνει πως για να μπορέσουμε να επισκεφθούμε τη μηχανή αναζήτησης της Google, το site που φιλοξενεί τα περισσότερα blogs ή μια άλλη τοποθεσία με το πιο σημαντικό υλικό που μας ενδιαφέρει (λέμε τώρα!) θα έπρεπε να θυμόμαστε ένα σωρό νούμερα που να δώσουν στον browser μας να καταλάβει ποια τοποθεσία θέλουμε να δούμε. Λίγο δύσκολο ε;... Ναι. Και για να μπορέσουμε να βρούμε τοποθεσίες στο internet θα έπρεπε να έχουμε ένα τεράστιο κατάλογο με αντιστοιχία διευθύνσεων σε ονόματα των site που αντιστοιχούν, ή ιδιότητες των site κ.λ.π. Και κάθε φορά που θα θέλαμε να επισκεφθούμε μια τοποθεσία θα έπρεπε να κατεβάζουμε τον αντίστοιχο τόμο από τη βιβλιοθήκη μας και να τον ψάχνουμε, μέχρι να βρούμε αυτή την τοποθεσία που ψάχνουμε... Ακόμα χειρότερα, κάθε φορά που ένα καινούργιο site θα δημιουργόταν στο διαδίκτυο, θα έπρεπε να ανανεώσουμε το ράφι με αυτούς τους καταλόγους!

"Χμμμ... Μα εγώ δε θυμάμαι να δίνω τέτοια νούμερα που αναφέρεις στον browser μου...". Επειδή ακριβώς το να θυμόμαστε νούμερα είναι πολύ δυσκολότερο από το να θυμόμαστε λέξεις, για να μη μπαίνουμε στον κόπο να ψάχνουμε τις διευθύνσεις που πρέπει να δώσουμε για να μπορέσουμε να προσπελάσουμε τον κάθε ιστότοπο που επιθυμούμε, έχουν δημιουργηθεί κάποιοι servers οι οποίοι κάνουν ακριβώς αυτό· αντικαθιστούν το τεράστιο ράφι της βιβλιοθήκης μας, που λέγαμε πριν... Τους δίνουμε ένα όνομα ιστότοπου και αν αυτό υπάρχει μας επιστρέφουν διεύθυνση IP στην οποία βρίσκεται ο εν λόγω ιστότοπος. Αλλά μπορούν να κάνουν και το αντίθετο, δηλαδή να μας πουν το όνομα του ιστότοπου στον οποίο οδηγεί μια διεύθυνση.

Διαδικασία ονοματοδοσίας

Και το ερώτημα είναι πώς γίνεται μια ονοματοδοσία στον τεράστιο χώρο του internet; Όταν κάποιος θελήσει να στήσει ένα server στο διαδίκτυο, τότε θα πρέπει να κατοχυρώσει ένα όνομα, το οποίο όλοι οι υπόλοιποι θα χρησιμοποιούν για να έχουν πρόσβαση σε αυτή την υπηρεσία που προσφέρει ο εν λόγω εξυπηρετητής. Αυτό το όνομα είναι μοναδικό. Αφού κατοχυρωθεί το όνομα, σε κάποιο server του διαδικτύου, μπορεί να φιλοξενηθεί η ιστοσελίδα ή η υπηρεσία που μας ενδιαφέρει. Ο server αυτός έχει κάποια συγκεκριμένη διεύθυνση IP. Μπορεί να ανήκει σε κάποια εταιρία φιλοξενίας (hosting) ιστοσελίδων, ή ακόμα και στον υπολογιστή που έχουμε στο σπίτι μας! Το τελευταίο βήμα, είναι να "παντρέψουμε" το όνομα που κατοχυρώσαμε με τη διεύθυνση στην οποία βρίσκεται η εν λόγω ιστοσελίδα/υπηρεσία· αυτή, συνήθως, τη φτιάχνουμε εμείς.

Πού βρίσκεται το πρόβλημα; Όταν το μόνο που μας ενδιαφέρει είναι να έχουμε ένα ωραιότατο server για δική μας δουλειά, θα πρέπει να κατοχυρώσουμε ένα όνομα στο διαδίκτυο (=κόστος), να πληρώσουμε σε κάποια εταιρία hosting χώρο που να τον διαμορφώσουμε κατάλληλα ώστε να φιλοξενήσει την υπηρεσία που μας ενδιαφέρει (=κόστος) και τέλος να δηλώσουμε σε κάποιο DNS ότι το όνομα που κατοχυρώσαμε "δείχνει" στον server της εταιρίας hosting (=κόστος) η οποία φιλοξενεί την υπηρεσία μας... Εναλλακτικά, μπορούμε να πληρώσουμε τον ISP μας για να μας δώσει σταθερή IP διεύθυνση, οπότε τη φιλοξενία της υπηρεσίας μας την κάνουμε στον υπολογιστή στο σπίτι μας. Όλα αυτά μας επιβαρύνουν οικονομικά.

Το ερώτημα είναι: «Αφού δεν κάνω επαγγελματική ιστοσελίδα, αλλά με ενδιαφέρει να έχω πρόσβαση στο δίκτυό μου για τη δική μου διευκόλυνση και μόνο. Δε μπορώ να το κάνω χωρίς κόστος;» Αυτό που χρειαζόμαστε είναι μια υπηρεσία που να μπορεί να μας αντιστοιχεί μια δυναμική διεύθυνση σε ένα σταθερό όνομα.

Δυναμικός DNS για δυναμικές IP

Αυτή είναι μια υπηρεσία που πλέον βρίσκεται αρκετά στο διαδίκτυο. Η εγγραφή σε μια υπηρεσία δυναμικού DNS είναι μια απλή λύση στο πρόβλημά μας. Οι περισσότερες δυναμικές DNS υπηρεσίες που προσφέρονται από κάποια sites, θέλουν να έχει κάποιος ένα κατοχυρωμένο όνομα. Άλλες πάλι, αφήνουν τον χρήστη να επιλέξει από μια σειρά ονόματα που έχει διαθέσιμα. Ένα χαρακτηριστικό παράδειγμα είναι το DynDNS.

Το DynDNS είναι ένα site που προσφέρει υπηρεσίες δυναμικού DNS. Ο χρήστης έχει τη δυνατότητα να χρησιμοποιήσει ένα όνομα της αρεσκείας του, αρκεί αυτό να ανήκει σε domains που ανήκουν στην ίδια τη DynDNS. Παράδειγμα, ένας χρήστης θα μπορούσε να χρησιμοποιεί το όνομα eliasfiles.dyndns-at-home.org γιατί ανήκει στο domain της DynDNS (dyndns-at-home.org). Το καλό που έχει το DynDNS είναι πως τα περισσότερα, πλέον, routers έχουν ενσωματωμένη την υποστήριξη για τη συγκεκριμένη υπηρεσία. Όμως, τον τελευταίο καιρό το site άρχισε να προσφέρεις τις υπηρεσίες του με πληρωμή. Πιο συγκεκριμένα, ένας καινούργιος χρήστης είναι υποχρεωμένος να δοκιμάσει την υπηρεσία DynDNSPro ελεύθερα για 14 ημέρες και αν δεν τη διακόψει νωρίτερα τότε χρεώνεται για τη χρήση της. Αυτό του δίνει το δικαίωμα να έχει την υπηρεσία για ένα χρόνο. Αν τη διακόψει πριν τη 14η ημέρα, τότε δε μπορεί να έχει καθόλου υπηρεσίες DynDNS. Για κάποιον ο οποίος θέλει να έχει πρόσβαση σε κάποια αρχεία του, κάποια βάση δεδομένων με δικές του πληροφορίες κ.λ.π. δεν είναι ότι καλύτερο να πρέπει να πληρώσει για κάτι το οποίο δεν του αποφέρει καθόλου κέρδος.

NoIP: Υπηρεσία δυναμικού DNS για όλους

Αρκετό ψάξιμο στο internet μου εμφάνισε σελίδες που προσέφεραν υπηρεσίες δυναμικού DNS, αλλά σε όλες έπρεπε να έχει κάποιος κατοχυρωμένο domain name. Τελικά, μια από όλες μου τράβηξε περισσότερη την προσοχή, η NoIP. Αυτή η σελίδα προσφέρει υπηρεσίες δυναμικού DNS, αλλά και ελεύθερης κατοχύρωσης ονόματος που θα ανήκει στο domain no-ip.biz (και άλλα). Κάτι παρόμοιο, δηλαδή, με το παλιό καλό DynDNS.

Τι άλλο προσφέρει η NoIP:

  • Ελεύθερο (χωρίς χρέωση) Domain Name που ανήκει στο no-ip.biz (ή κάποιο άλλο από τα προκαθορισμένα του)
  • Έναν client που αναλαμβάνει την ενημέρωση της λίστας του DNS με την καινούργια IP. Αυτόν τον ονομάζει DUC - Dynamic Update Client (περισσότερα ακολούθως)
  • Ο DUC είναι ανοιχτού κώδικα
  • Οδηγείες για τη δημιουργία δικού μας client αν δε μας αρέσει ο δικός της

Ο NoIP Client είναι ένα πρόγραμμα που τρέχει στον υπολογιστή μας και ανά τακτά διαστήματα ανιχνεύει τη διεύθυνση που έχουμε από τον ISP και αν δει ότι διαφέρει από την προηγούμενη, ενημερώνει τον DNS Server της NoIP για την καινούργια διεύθυνση. Έτσι, το όνομα το οποίο έχουμε κατοχυρώσει δείχνει στην καινούργια μας διεύθυνση. Το ίδιο ακριβώς σύστημα έχει και το DynDNS με ένα πρόγραμμα client που ονομαζόταν ddclient.

Πλεονεκτήματα της χρήσης ενός client προγράμματος είναι ότι αυτός τρέχει σε έναν υπολογιστή και ενημερώνει τη NoIP για τις αλλαγές της IP διεύθυνσης χωρίς να χρειάζεται να κάνει κάτι ο χρήστης.

Μειονεκτήματα της χρήσης ενός client είναι πως ο client λειτουργεί μόνο όση ώρα είναι σε λειτουργία ο υπολογιστής που τον τρέχει. Συνεπώς, υπάρχει η περίπτωση να πρέπει να έχουμε ένα υπολογιστή ενεργοποιημένο 24 ώρες το 24ωρο. Ο client ελέγχει για αλλαγή διεύθυνσης ανά τακτά διαστήματα. Τέτοια αλλαγή μπορεί να γίνει οποιαδήποτε ώρα και στιγμή από τον provider. Αυτό οδηγεί στο δεύτερο μειονέκτημά, πως από τον χρόνο που θα εκτελέσει ο ISP μια αλλαγή στη διεύθυνσή μας μέχρι τη στιγμή που ο client θα ελέγξει για μια τέτοια κατάσταση, ο DNS θα έχει την παλιά μας διεύθυνση και έτσι δε θα μπορούμε να έχουμε πρόσβαση στον server μας γι' αυτό το χρονικό διάστημα. Τέλος, ο client για να ανιχνεύσει την αλλαγή της διεύθυνσης, στέλνει ένα request στο server της NoIP. Αυτό σημαίνει διακίνηση δεδομένων στο διαδίκτυο, κατά συνέπεια δε μπορούμε να ρυθμίσουμε τον client να κάνει τον έλεγχο σε πολύ τακτά χρονικά διαστήματα (π.χ. 5 δευτερολέπτων). Χρήση τακτικών ελέγχων βοηθάει στο να μειώσουμε το χρόνο της ασυμφωνίας της διεύθυνσης που έχει ο DNS με την πραγματική μας, μετά από αλλαγή από τον ISP. Ταυτόχρονα, όμως, αυξάνει και τη διακίνηση πληροφορίας προς τον NoIP server. Αποτέλεσμα είναι να κινδυνεύουμε να μας κάνει τελικά ban ο server και να μη δέχεται τις πληροφορίες μας, διότι θεωρεί ότι του κάνουμε ηλεκτρονική επίθεση.

Γιατί το Raspberry Pi;

Το Raspberry Pi είναι ένας μικρός υπολογιστής μεγέθους πιστωτικής κάρτας (όχι δε μπορείτε να κάνετε πληρωμές σε καταστήματα με αυτό! :)) βασισμένος σε αρχιτεκτονική επεξεργαστή ARM. Καταναλώνει ελάχιστα και τρέχει οποιαδήποτε διανομή Linux (ή ακόμα και FreeBSD) είναι μεταγλωττισμένη γι' αυτή την αρχιτεκτονική. Είναι πολύ βολικό για τέτοιες καταστάσεις.

Επίσης, δεν υπάρχει λόγος γιατί να πρέπει να λειτουργεί 24/7 ένας υπολογιστής (εκτός και αν είναι απαραίτητο για άλλους σκοπούς). Το Raspberry Pi λόγω της μικρής του κατανάλωσης, αλλά και του μικρού του μεγέθους, μπορεί να λειτουργεί συνεχώς, χωρίς πρόβλημα, ενώ δεν πιάνει χώρο επάνω σε ένα γραφείο, όπως τα PC. Μπορεί να μπει δίπλα από το router μας (κάποια από αυτά διαθέτουν και μια USB θύρα και μπορούν να το τροφοδοτήσουν), ενώ υπάρχουν αρκετά όμορφα κουτάκια τα οποία το κάνουν να δείχνει σοβαρό σε οποιοδήποτε χώρο (μη φωνάζει η γυναίκα πως δεν ταιριάζει με τις κουρτίνες :Ρ).

Όσα περιγράφονται στο παρόν άρθρο έχουν γίνει επάνω στο λειτουργικό Raspbian που είναι βασισμένο στο Debian. Επειδή, όμως, οι διανομές Linux έχουν πάρα πολλά κοινά μεταξύ τους, δεν υπάρχει λόγος γιατί, με έστω λίγες αλλαγές, να μη μπορούν να τρέξουν και σε οποιαδήποτε άλλη διανομή για οποιαδήποτε αρχιτεκτονική.

Ο Dynamic Update Client της NoIP

Η NoIP δίνει έναν client για το update της διεύθυνσης IP στον DNS τους, ο οποίος είναι γραμμένος σε γλώσσα C. Για απλή χρήση είναι αυτό που χρειάζεται κάποιος. Μπορείτε να τον κατεβάσετε και να τον κάνετε compile κανονικά, όπως και κάθε άλλο πρόγραμμα. Στο Raspberry Pi γίνεται κανονικά compile, ενώ τρέχει χωρίς κανένα πρόβλημα.

Η εγκατάσταση είναι απλή. Αρχικά κατεβάζετε τον πηγαίο κώδικα, μιας και είναι opensource, από το site της NoIP. Στη συνέχεια αποσυμπιέζετε το αρχείο:

pi@raspberrypi:~ > tar -xvzf noip-duc-linux.tar.gz
...
pi@raspberrypi:~ > 

Αυτό θα δημιουργήσει ένα κατάλογο με όνομα noip-version. Μέσα σε αυτόν βρίσκεται όλος ο πηγαίος κώδικας, scripts για διάφορες διανομές ώστε να λειτουργέι σαν υπηρεσία κ.λ.π. Μπορείτε ελεύθερα να περιηγηθείτε μέσα στα περιεχόμενα του καταλόγου. Όταν τελικά αποφασίσετε να το εγκαταστήσετε κάνετε τα ακόλουθα: (η έκδοση που βρίσκεται ο DUC τη στιγμή που γράφεται το άρθρο είναι η 2.1.9-1)

pi@raspberrypi:~ > cd ~/noip-2.1.9-1
pi@raspberrypi:~ > make
...
pi@raspberrypi:~ > sudo make install
...
pi@raspberrypi:~ > 

Αυτές οι εντολές θα εγκαταστήσουν το noip2 στον κατάλογο /usr/local/bin. Οι πληροφορίες που δίνονται στα διάφορα αρχεία README.FIRST είναι αρκετές για να το χρησιμοποιήσετε.

Μιας και τα μειονεκτήματα του DUC τα είδαμε πριν, ας προχωρήσουμε σε μια διαφορετική υλοποίηση από αυτή του NoIP DUC που βελτιώνει κάποια χαρακτηριστικά.

Μείωση του Internet Traffic

Η μεγαλύτερη διακίνηση πληροφορίας που γίνεται στο Internet από τον client είναι για την εύρεση της IP διεύθυνσης που μας έχει δώσει ο provider μας. Κάθε φορά που ο DUC θέλει να μάθει την IP μας, υποβάλει ένα ερώτημα στον server της NoIP. Αυτός βλέπει από ποια IP γίνεται το ερώτημα και την επιστρέφει στον DUC. Αν διαπιστωθεί αλλαγή της, τότε ο DUC στέλνει άλλο ένα request που αναφέρει πως θα πρέπει να γίνει η αλλαγή της διεύθυνσης και στον κατάλογο του DNS. (αυτή είναι η γενική ιδέα)

Επειδή η αλλαγή της διεύθυνσης συμβαίνει σε αραιά χρονικά διαστήματα (ανά μερικές ημέρες), η περισσότερη επικοινωνία που συμβαίνει (το ερώτημα της τρέχουσας IP διεύθυνσης) είναι άσκοπη. Ταυτόχρονα, σκοπός μας είναι να μπορέσουμε να υποβάλουμε ερώτημα για τη διεύθυνση IP τόσο συχνά ώστε να μην υπάρχει μεγάλο χρονικό διάστημα όπου ο DNS περιέχει παλαιά διεύθυνση (να είναι ασυγχρόνιστος).

Ο πιο απλός τρόπος είναι να μην υποβάλλεται το ερώτημα στον server της NoIP, αλλά στο router μας. Πολλά router έχουν ενεργοποιημένο telnet server, έτσι ώστε να μπορούμε να συνδεθούμε με αυτό το πρωτόκολλο και να εκτελούμε εντολές. Μια άλλη εναλλακτική υπάρχει αν το router που διαθέτουμε τρέχει ssh server, οπότε μπαίνουμε με ssh στο router και πάλι μπορούμε να εκτελέσουμε εντολές. Κάποια, βέβαια, routers δεν έχουν τίποτα από τα δυο (π.χ. κάτι oxygen)... Εκεί θα πρέπει να κάνουμε ερώτημα μέσω https (συνήθως).

Επειδή δεν είναι σταθερός ο τρόπος με τον οποίο υποβάλλουμε ερώτημα για την IP στο router, σε αυτό το άρθρο θα παρουσιαστεί ένα script που είναι για χρήση σε πρωτόκολλο telnet που είναι και το πιο συχνά εμφανιζόμενο. Συγκεκριμένα έχει δοκιμαστεί σε τρία διαφορετικά routers. Ένα ZTE (που δίνει ο ΟΤΕ), ένα TP-Link και ένα AirTies. Φυσικά, με διαφορετικό configuration. Τα δύο από τα τρία εκτελούν κανονικές εντολές Linux, ενώ το TP-Link έχει δικές του εντολές.

Αρχικά, ας δημιουργήσουμε τις συνθήκες για να κάτσουν τα script. Πρώτο βήμα είναι να φτιαχτεί ο κατάλογος που θα φιλοξενήσει τα scripts:

pi@raspberrypi:~ > sudo mkdir /usr/scripts
pi@raspberrypi:~ > cd /usr/scripts
pi@raspberrypi:/usr/scripts > 

Ας δούμε το script που υποβάλλει ερώτημα στο router για την IP. Το script είναι γραμμένο σε python. Μιας και δεν είμαι γνώστης της python, δεν περιμένω ότι το script αυτό είναι το τελειότερο του κόσμου (λέμε τώρα!). Είναι το πρώτο script που γράφω στη συγκεκριμένη γλώσσα προγραμματισμού οπότε κάποιος έμπειρος python προγραμματιστής πολύ πιθανό να βρει κάποιες ατέλειες. Επίσης, είναι μια πρώτη έκδοση. Σκοπεύω στο μέλλον (κοντινό) να του μπαλώσω μια "τρύπα" που έχει. Αλλά για την ώρα κάνει τη δουλειά του. Μπορείτε να το κάνετε Copy/Paste στον αγαπημένο σας editor από εδώ:

#!/usr/bin/python
# THE SOFTWARE IS LICENSED UNDER GPL2 GENERAL PUBLIC LICENSE v2.
# IN ORDER TO OBTAIN A COPY OF THIS LICENSE PLEASE VISIT THE FOLLOWING
# ADDRESS:
# http://www.gnu.org/licenses/gpl-2.0.html

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#First import the necessary python libraries
import sys
import os
import telnetlib

#Setup the default values, before using the configuration file
ConfFile = "/etc/GetInetIP.conf"
RouterIP = "192.168.1.1"
UserName = "admin"
Password = "admin"
NetComm = "ifconfig ppp0"
AddrPrefix = "inet "
ExpTime = 0.0001

#If the user has entered a CLI argument, use it as the configuration file to be used
InArgs = len(sys.argv)
if InArgs > 2:
        sys.stderr.write("ERROR: Only one input argument expexted!\n")
        sys.stderr.write("Usage: GetInetIP [configuration_filename]\n");
        exit(1)
elif InArgs == 2:
        ConfFile = sys.argv[1]
        #Need to check if the user specified filename exists. If not => Error
        if not(os.path.exists(ConfFile)):
                sys.stderr.write("ERROR: Configuration file" + ConfFile + "not found\n")
                exit(2)

#Check for the existance of the configuration file

#Time to import the configuration file's values. If the user specified a configuration file
# then it really exists. But if not, we must try to load the configuration file from
# the default path. If this file doesnot exist, then we ave to use our hardcoded values
#NOTE:The execfile command should be changed for security reasons... A more secure approach
# is to parse the configuration file, and not execute it... (no time to fixit right now)
if os.path.exists(ConfFile):
        execfile(ConfFile)

#Connect to the specified router using telnet protocol
Conn = ""
try:
        Conn = telnetlib.Telnet(RouterIP)
except:
        sys.stderr.write("ERROR: Cannot communicate with" + RouterIP +"\n")
        exit(3)

#Send the login credentials
RespStr = "First"
FailCount = 3
while (RespStr != "") and (FailCount > 0):
        RespStr = Conn.read_until("\n",ExpTime)
        LoginFlag = RespStr.find("login:")
        if LoginFlag >= 0:
                Conn.write(UserName + "\n")
                RespStr = Conn.read_until("\n",ExpTime)
        PassFlag = RespStr.find("Password:")
        if PassFlag >= 0:
                Conn.write(Password + "\n")
                RespStr = Conn.read_until("\n",ExpTime)
                FailCount -= 1

if FailCount == 0:
        sys.stderr.write("ERROR: Credentials failed 3 times\n")
        exit(4)

#Now the telnet awaits commands! Issue the command that announces the IP needed
Conn.write(NetComm +"\n")
RespStr = Conn.read_until("\n",ExpTime)
InetFlag = False
while (RespStr != "") and not(InetFlag):
        InetStrt = RespStr.find(AddrPrefix)
        if InetStrt >= 0:
                InetFlag = True
                InetStrt += len(AddrPrefix)
                InetEnd = RespStr.find(" ",InetStrt)
        else:
                RespStr = Conn.read_until("\n",ExpTime)
Conn.write("exit\n")
DummyStr = Conn.read_all()
Conn.close()
if not(InetFlag):
        sys.stderr.write("ERROR: IP Address not found\n")
        exit(5)
OutStr = RespStr[InetStrt:InetEnd]
print(OutStr.strip())
exit(0)

Αυτό το script το σώζουμε με το όνομα GetInetIP.py μέσα στον κατάλογο που φτιάξαμε. Για να λειτουργήσει χρειάζεται και ένα configuration αρχείο μέσα στον κατάλογο /etc. Προσοχή χρειάζεται στο ότι στο configuration αρχείο βρίσκονται στοιχεία που δεν πρέπει να φτάσουν στα μάτια τρίτων! Κοινώς, το password του router... Το αρχείο θα πρέπει να ονομάζεται GetInetIP.conf:

Configuration για router TP-Link

RouterIP = "192.168.1.1"
Password = "MyCrazyPassword"
NetComm = "show wan status"
AddrPrefix = "Ip = "
ExpTime = 0.07

Ας δούμε λίγο τα περιεχόμενα του configuration αρχείου:

  • RouterIP: Δηλώνει τη διεύθυνση IP που έχει το router μας
  • UserName: Το όνομα χρήστη για το router. Αν δεν δηλώνεται εδώ χρησιμοποιείται η default τιμή που είναι το admin. Κάποια από τα routers δε ζητάνε το username. Σε αυτή την περίπτωση το script το καταλαβαίνει και δεν το στέλνει.
  • Password: Το password που χρειαζόμαστε για να κάνουμε login στο router
  • NetComm: Η εντολή που εκτελούμε, όταν κάνουμε telnet στο router που μας δείχνει τη διεύθυνση IP που έχει στο WAN. Για το TP-Link είναι show wan status, ενώ για ένα router που τρέχει busybox ή γενικά εκτελεί εντολές Linux είναι κάτι του στυλ ifconfig ppp0
  • AddrPrefix: Όποια και αν είναι η εντολή που μας δίνει την επιθυμητή πληροφορία, δεν δίνει απλά ένα ξερό νούμερο, αλλά ολόκληρο κείμενο. Εδώ περιγράφεται τι περιμένει το script για να βρει ακριβώς πριν τη διεύθυνση IP που μας ενδιαφέρει. Αμέσως μετά από αυτό το κείμενο αναφέρεται η πληροφορία μας.
  • ExpTime: Είναι ο χρόνος μέσα στον οποίο αν το router δεν στείλει κάποιο χαρακτήρα, θεωρείται πώς δεν έχει κάτι άλλο να στείλει. Προσοχή, σε άλλα router είναι μικρότερος και σε άλλα μεγαλύτερος. Γενικά, ο μεγάλος χρόνος κάνει για όλα τα router, αλλά καθυστερεί την εξέλιξη του script.

Αν μπούμε χειροκίνητα σε ένα TP-Link router και δώσουμε την εντολή show wan status τότε θα δούμε τα ακόλουθα:

eliaschr@orion:~> telnet router
Trying 192.168.1.1...
Connected to router.
Escape character is '^]'.

Password: *********************
Copyright (c) 2001 - 2011 TP-LINK TECHNOLOGIES CO., LTD.
TP-LINK> show wan status
PVC-0 
        Status = Up
PVC-1 
        Status = Up
PVC-2 
        Status = Up
PVC-3 
        Status = Up
PVC-4 
        Status = Up
        Ip = 94.71.120.43
PVC-5 
        Status = Up
PVC-6 
        Status = Up
PVC-7 
        Status = Down
TP-LINK> 

Βλέπουμε ότι ακριβώς πριν την IP που μας ενδιαφέρει βρίσκεται το Ip = . Αυτό είναι που βάζουμε στο AddrPrefix.

Στην περίπτωση του ZTE και του AirTies ένα configuration αρχείο σαν αυτό που ακολουθεί κάνει τη δουλειά:

Configuration για router ZTE - AirTies - Linux style commands

RouterIP = "192.168.1.1"
Password = "MyCrazyPassword"
NetComm = "ifconfig ppp0"
AddrPrefix = "inet addr:"
ExpTime = 0.05

Φυσικά, όποιο και να είναι το configuration που θα επιλέξετε, το μόνο σίγουρο είναι πως δεν πρέπει να το διαβάζει κανένας, παρά μόνο ο root!:

pi@raspberrypi:/usr/scripts > chown root:root /etc/GetInetIP.conf
pi@raspberrypi:/usr/scripts > chmod 0600 /etc/GetInetIP.conf
pi@raspberrypi:/usr/scripts > 

Dynamic Updater Client script

Ωραία! Αφού φτιάξαμε το script που μας διαβάζει την IP από το router μας, μένει να φτιάξουμε και το script που θα κάνει τη δουλειά του NoIP DUC. Δημιουργήστε το αρχείο IP-Updater.sh στον κατάλογο /usr/scripts που φτιάξαμε πριν. Το αρχείο αυτό θα πρέπει να το κάνετε Copy/Paste από εδώ:

#!/bin/bash
# THE SOFTWARE IS LICENSED UNDER GPL2 GENERAL PUBLIC LICENSE v2.
# IN ORDER TO OBTAIN A COPY OF THIS LICENSE PLEASE VISIT THE FOLLOWING
# ADDRESS:
# http://www.gnu.org/licenses/gpl-2.0.html

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# The script uses a configuration file that is called /etc/IP-Updater.conf
# Valid lines in the script are:
# NoIPUser=the_username_of_NoIP_login_account
# NoIPPass=the_password_of_the_account
# NoIPHost=the_host_to_which_the_IP_is_going_to_be_updated
# FindIPComm=/path/to/the_command_line_to_be_executed_to_get_the_IP
# FailIPDelay=Delay in seconds before retrying to find out the new
#       IP address, after a failure
# RepeatDelay=Delay in seconds before trying to refresh the IP after a
#       successful update
# FailDelay=Delay in seconds if there is a fatal error in NoIP. (Only if PreventExit=true)
# WGetDelay=Delay in seconds if there is a failure in wget to NoIP
# WGetLongFailDelay=Delay in seconds after Maximum number of failures reached
# WGetMaxFails=Maximum wget sequensial failures that make the daemon exit (unless
#       PreventExit=true
# MaxFailIPCnt=Maximum number of retries to fetch IP address from router
#       while it seems to have connection to the internet
# PreventExit={true/false} If true, then upon fatal error the script never ends
# SuccessCmd=Command to execute after a successful update to No-IP
# FailCmd=Command to execute after a failed NoIP update
# ExitCmd=Command to execute if the program must exit because of Fatal Error

# In ExitCmd, FailCmd and SuccessCmd you can use the following values as input:
#       $NoIPResp: if you want to include the responce from NoIP after the 
#               request of IP Address updating
#       $NewIP: the new IP that found and sent to NoIP
#       $CurrIP: the old IP that the system had before the update
#       $CurDate: the time that the execution of the IP update happened

PIDFILE=/var/run/IP-Updater.pid
PID=$BASHPID
LOGME="logger -ist 'NoIP-Updater'"
TEMPFILE="/tmp/CurrentIP"
TEMPWGET="/tmp/CurrentIP.wget"
CONFFILE="/etc/IP-Updater.conf"
USERAGENT="WGet from Raspberry Pi Linux/1.0 eliaschr@gmail.com"
NoIPUser=""
NoIPPass=""
NoIPHost=""
FindIPComm="echo 'ERROR: No router IP fetch program specified' >&2; exit 4;"
PreventExit=true
FailDelay=1800
MaxFailIPCnt=3
FailIPDelay=15
RepeatDelay=60
WGetDelay=5
WGetLongFailDelay=60
WGetMaxFails=10
SuccessCmd=""
FailCmd=""
ExitCmd=""
WGetErrMess[1]="Generic error code"
WGetErrMess[2]="Parse error"
WGetErrMess[3]="File I/O error"
WGetErrMess[4]="Network failure"
WGetErrMess[5]="SSL verification failure"
WGetErrMess[6]="Username/password authentication failure"
WGetErrMess[7]="Protocol errors"
WGetErrMess[8]="Server issued an error response"

CurrIP="x"

test -r $CONFFILE || ( echo "Failed to load the configuration file $CONFFILE..." >&2;
        exit 1 )

source $CONFFILE

touch $TEMPFILE
echo "" > $TEMPFILE

touch $PIDFILE
echo $PID > $PIDFILE

while true
do
        source $TEMPFILE
        FailCounter=$MaxFailIPCnt
        NextDelay=$RepeatDelay
        NewIP=`$FindIPComm`
        Status=$?

        while (( $Status != 0 ))
        do
                CurDate=`date +%T`
                $LOGME "$CurDate WARNING: Could not read the IP address"
                if (( $FailCounter == 0 ))
                then
                        $LOGME "Too many failures... Abording"
                        rm $PIDFILE
                        exit 2
                fi
                sleep $FailIPDelay
                PingStat=`ping -c1 www.no-ip.com > /dev/null; echo $?`
                if [[ "$PingStat" == "0" ]]
                then
                        NewIP=`$FindIPComm`
                        Status=$?
                        FailCounter=$((FailCounter -1))
                fi
        done

        if [[ "$CurrIP" != "$NewIP" ]]
        then
                CurDate=`date +%T`
                WGetStatus=`wget -qO $TEMPWGET -U "$USERAGENT" --user=$NoIPUser --password=$NoIPPass "http://dynupdate.no-ip.com/nic/update?hostname=${NoIPHost}&myip=$NewIP"; echo $?`
                NoIPResp=`cat $TEMPWGET`
                if (( $WGetStatus == 0 ))
                then
                        WGetFails=$WGetMaxFails
                        case ${NoIPResp/ */} in
                                "good")
                                        FailIPResult=0
                                        echo "CurrIP=$NewIP" > $TEMPFILE
                                        $LOGME "IP Address successfuly updated"
                                        ;;
                                "nochg")
                                        FailIPResult=1
                                        HostIPResp=`host $NoIPHost`
                                        if (( $? == 0 ))
                                        then
                                                HostIP=${HostIPResp/* /}
                                                if [[ "$HostIP" == "$NewIP" ]]
                                                then
                                                        FailIPResult=0
                                                        Comment="successfuly"
                                                        echo "CurrIP=$HostIP" > $TEMPFILE
                                                else
                                                        Comment="but it seems different than expected..."
                                                        NextDelay=$FailIPDelay
                                                fi
                                        else
                                                Comment="but could not resolve $NoIPHost"
                                                NextDelay=$FailIPDelay
                                        fi
                                        $LOGME "$CurDate WARNING: The IP in NoIP was updated by another process $Comment"
                                        ;;
                                "nohost")
                                        FailIPResult=1
                                        $LOGME "$CurDate ERROR: Hostname supplied does not exist under specified account"
                                        ;;
                                "badauth")
                                        FailIPResult=1
                                        $LOGME "$CurDate ERROR: Invalid username password combination"
                                        ;;
                                "badagent")
                                        FailIPResult=2
                                        $LOGME "$CurDate ERROR: Client disabled. Client should exit and not perform any more updates without user intervention"
                                        ;;
                                "\!donator")
                                        $LOGME "$CurDate ERROR: An update request was sent including a feature that is not available to that particular user"
                                        FailIPResult=1
                                        ;;
                                "abuse")
                                        $LOGME "$CurDate ERROR: Username is blocked due to abuse. Either for not following our update specifications or disabled due to violation of the No-IP terms of service"
                                        FailIPResult=1
                                        ;;
                                "911")
                                        $LOGME "$CurDate FATAL ERROR: NoIP.com error such as a database outage. Retry the update no sooner 30 minutes"
                                        FailIPResult=1
                                        NextDelay=1800
                                        ;;
                                *)
                                        $LOGME "$CurDate ERROR: Unknown responce from NoIP.com - $NoIPResp"
                                        FailIPResult=1
                                        ;;
                        esac

                        case $FailIPResult in
                                0)
                                        eval $SuccessCmd
                                        ;;
                                1)
                                        eval $FailCmd
                                        ;;
                                *)
                                        if $PreventExit
                                        then
                                                NextDelay=$FailDelay
                                                eval $FailCmd
                                        else
                                                eval $ExitCmd
                                                rm $PIDFILE
                                                exit 3
                                        fi
                                        ;;
                        esac
                else
                        $LOGME "$CurDate ERROR: Wget exited with error status $WGetStatus: ${WGetErrMess[$WGetStatus]}"
                        WGetFails=$((WGetFails -1))
                        if (( $WGetFails == 0 ))
                        then
                                WGetFails=1
                                NextDelay=$WGetLongFailDelay
                        else
                                NextDelay=$WGetDelay
                        fi
                fi
        fi

        sleep $NextDelay
done

Αυτό το script είναι γραμμένο σε bash shell. Και αυτό για να λειτουργήσει χρειάζεται το configuration αρχείο του, /etc/IP-Updater.conf. Ένα δείγμα του εν λόγω αρχείου φαίνεται ακολούθως:

NoIPUser=noipusermail@myisp.gr
NoIPPass="MyUltraFancyPassword!"
NoIPHost=myhostname.no-ip.biz
FindIPComm="/usr/scripts/GetInetIP.py"
FailIPDelay=15
RepeatDelay=60
FailDelay=1800
MaxFailIPCnt=3
PreventExit=true
SuccessCmd="/usr/bin/ttytter -keyf=/etc/ttytter/Servertty -silent -status=\"\`date\` IP Updated successfully. NoIP Response is: \$NoIPResp\""
FailCmd="/usr/bin/ttytter  -keyf=/etc/ttytter/Servertty -silent -status=\"\`date\` IP Update failed. NoIP Response is: \$NoIPResp\""
ExitCmd="/usr/bin/ttytter  -keyf=/etc/ttytter/Servertty -silent -status=\"\`date\` Fatal Error during IP Update. NoIP response is: \$NoIPResp. Exiting...\""

Ας δουμε μια λίστα με πιθανές παραμέτρους του αρχείου:

  • NoIPUser: Δηλώνει τη διεύθυνση mail του λογαριασμού του χρήστη που έχουμε φτιάξει στο NoIP
  • NoIPPass: Το password που έχουμε στο NoIP λογαριασμό
  • NoIPHost: Το όνομα του domain που έχουμε καταχωρήσει
  • FindIPComm: Δηλώνεται το script που όταν τρέξει μας δίνει τη διεύθυνση IP του router. Κοινώς, το script που φτιάξαμε πριν σε python
  • FailIPDelay: Όταν δεν καταφέρει να διαβάσει την IP από το router, όπως σε περίπτωση που το router δεν έχει πάρει ακόμα IP μετά από διακοπή ρεύματος, ή το router είναι σβηστό, το script περιμένει όσα δευτερόλεπτα ορίζονται σε αυτή την παράμετρο, πριν ξαναπροσπαθήσει
  • RepeatDelay: Όταν διαβάσει σωστά μια IP διεύθυνση, δηλώνει σε πόσο χρόνο θα ξαναπροσπαθήσει να δει αν υπάρχει αλλαγή της
  • FailDelay: Όταν υπάρξει πρόβλημα στην ενημέρωση της καινούργιας IP, όπως σε περίπτωση που το NoIP επιστρέψει "911", το script περιμένει όσο χρόνο δηλώνεται εδώ σε δευτερόλεπτα πριν ξαναπροσπαθήσει την ενημέρωση του NoIP DNS
  • MaxFailIPCnt: Ο μέγιστος αριθμός προσπαθειών στις οποίες δε θα μπορέσει να διαβάσει την IP διεύθυνση από το router, ενώ πιστοποιείται πως υπάρχει σύνδεση με το internet. Αυτή η κατάσταση κανονικά δεν πρέπει να υπάρξει ποτέ.
  • PreventExit:true ή false. Αν είναι true τότε το script δε σταματάει ποτέ, ούτε σε περίπτωση λάθους όπως π.χ. username/password στο NoIP
  • SuccessCmd: Εντολή που θα εκτελείται όταν έχουμε μια σωστή απάντηση από το NoIP όσον αφορά την ανανέωση της IP μας. Στο παράδειγμα φαίνεται να τρέχει το ttytter με τέτοιο τρόπο ώστε να μπορεί να στέλνει tweet την κατάστασή του. Δώστε βάση στον τρόπο με τον οποίο δηλώνονται μεταβλητές και εσωτερικές εκτελέσεις εντολών, όπως η date
  • FailCmd: Το ίδιο, αλλά για την περίπτωση όπου υπάρξει απάντηση σφάλματος από το NoIP
  • ExitCmd: Ομοίως για την περίπτωση όπου υπάρξει τέτοιο σφάλμα όπου θα πρέπει το script να τερματιστεί
  • WGetDelay: Το script χρησιμοποιεί τη wget για να στείλει το request στο NoIP server. Σε περίπτωση που υπάρξει σφάλμα περιμένει όσο χρόνο του ορίζει αυτή η μεταβλητή
  • WGetMaxFails: Όταν υπάρξουν τόσα σφάλματα στη σειρά από τη wget όσα ορίζονται σε αυτή την παράμετρο, τότε ο χρόνος αναμονής μέχρι το επόμενο wget που θα επιχειρηθεί ορίζεται από την WGetLongFailDelay
  • WGetLongFailDelay: Μετά από WGetMaxFails αποτυχίες της wget ο χρόνος που περιμένει πριν ξαναδοκιμάσει να στείλει request ορίζεται από αυτή την παράμετρο

Το script μας δίνει τη δυνατότητα να εκτελέσουμε κάποιες εντολές, ανάλογα με το αν ήταν επιτυχής η αλλαγή της IP, αν υπήρξε κάποιο σφάλμα, ή αν υπήρξε μια ανεπίτρεπτη κατάσταση που οδηγεί το script σε τερματισμό. Οι εντολές αυτές ορίζονται στις παραμέτρους SuccessCmd, FailCmd και ExitCmd. Σε αυτές τις εντολές, μπορεί να θέλουμε να χρησιμοποιήσουμε κάποιες πληροφορίες από το script. Οι δυνατές περιπτώσεις είναι οι:

  • $NoIPResp: Περιέχει την απάντηση από τον server της NoIP
  • $NewIP: Περιέχει την καινούργια IP διεύθυνση
  • $CurrIP: Περιέχει την παλιά IP διεύθυνση
  • $CurDate: Περιέχει την ώρα που ανιχνεύτηκε η αλλαγή της IP

Και φυσικά, μιας και σε αυτό το αρχείο περιέχονται ευαίσθητες πληροφορίες, θα πρέπει να μπορεί να το διαβάσει μόνο ο root:

pi@raspberrypi:/usr/scripts > chown root:root /etc/IP-Updater.conf
pi@raspberrypi:/usr/scripts > chmod 0600 /etc/IP-Updater.conf
pi@raspberrypi:/usr/scripts > 

Τέλος, μιας και αυτά τα δύο scripts είναι βασικά για τη λειτουργία του συστήματός μας, καλό είναι και αυτά να τα κάνουμε μη προσβάσιμα από τους χρήστες (εκτός του root):

pi@raspberrypi:/usr/scripts > sudo chown -R root:root /usr/scripts
pi@raspberrypi:/usr/scripts > sudo chmod -R 0700 /usr/scripts
pi@raspberrypi:/usr/scripts > 

Μετατροπή του καινούργιου client σε υπηρεσία

Το τελικό στάδιο είναι να εκτελείται το script που φτιάξαμε ως υπηρεσία του συστήματος. Κάποιος θα μπορούσε πολύ απλά να το τρέξει μέσα από τον cron. Αυτό όμως δεν θεωρείται και τόσο καλή ιδέα. Είναι σαφώς καλύτερα να λειτουργεί όπως όλες οι υπηρεσίες του συστήματος, να ενεργοποιείται και να απενεργοποιείται ανάλογα με το runlevel κ.λ.π. Για να το πετύχουμε αυτό θα πρέπει να φτιάξουμε ένα ακόμα script. Μια παρόμοια εργασία κάναμε και σε προηγούμενο άρθρο. Θα ακολουθήσουμε, λοιπόν, την ίδια τακτική.

Κάντε Copy/Paste τον ακόλουθο κώδικα στον αγαπημένο σας editor:

#! /bin/sh
### BEGIN INIT INFO
# Provides:          IP-Updater
# Required-Start:    $local_fs $syslog $network
# Required-Stop:     $local_fs $syslog $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: IP Updater for NoIP Dynamic DNS service
# Description:       Start IP Updater to check the IP that the ISP has given to router.
#       When the IP is changed, the updater informs NoIP and updates the chosen domain name
#       to point to the new IP.
### END INIT INFO

# Author: Elias Chrysocheris 
#

# Do NOT "set -e"

# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="IP Updating client service for NoIP (http://www.noip.com/)"
NAME=IP-Updater
DAEMON=/usr/scripts/IP-Updater.sh
SCRIPTNAME=/etc/init.d/$NAME
PIDFILE=/var/run/IP-Updater.pid

# Exit if the package is not installed
[ -e "$DAEMON" ] || { echo "Cannot find IP-Updater executable in $DAEMON. Perhaps it is not installed...";
        if [ "$1" = "stop" ]; then exit 0;
        else exit 5; fi; }

# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME

# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh

# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions

#
# Function that starts the daemon/service
#
do_start()
{
        # Return
        #   0 if daemon has been started
        #   1 if daemon was already running
        #   2 if daemon could not be started
        start-stop-daemon --test --start --quiet --pidfile $PIDFILE --exec $DAEMON > /dev/null \
                || return 1
        start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON &
        RETVAL="$?"
        [ "$RETVAL" > 2 ] && return 2
        # Add code here, if necessary, that waits for the process to be ready
        # to handle requests from services started subsequently which depend
        # on this one.  As a last resort, sleep for some time.
}

#
# Function that stops the daemon/service
#
do_stop()
{
        # Return
        #   0 if daemon has been stopped
        #   1 if daemon was already stopped
        #   2 if daemon could not be stopped
        #   other if a failure occurred
        start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
        RETVAL="$?"
        [ "$RETVAL" = 2 ] && return 2
        # Wait for children to finish too if this is a daemon that forks
        # and if the daemon is only ever run from this initscript.
        # If the above conditions are not satisfied then add some other code
        # that waits for the process to drop all resources that could be
        # needed by services started subsequently.  A last resort is to
        # sleep for some time.
        start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --pidfile $PIDFILE
        [ "$?" = 2 ] && return 2
        rm $PIDFILE
        return "$RETVAL"
}

#
# Function that sends a SIGHUP to the daemon/service
#
do_reload() {
        #
        # If the daemon can reload its configuration without
        # restarting (for example, when it is sent a SIGHUP),
        # then implement that here.
        #
        start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
        return 0
}

case "$1" in
  start)
        [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
        do_start
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  stop)
        [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
        do_stop
        case "$?" in
                0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
                2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
        esac
        ;;
  status)
        status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
        ;;
  #reload|force-reload)
        #
        # If do_reload() is not implemented then leave this commented out
        # and leave 'force-reload' as an alias for 'restart'.
        #
        #log_daemon_msg "Reloading $DESC" "$NAME"
        #do_reload
        #log_end_msg $?
        #;;
  restart|force-reload)
        #
        # If the "reload" option is implemented then remove the
        # 'force-reload' alias
        #
        log_daemon_msg "Restarting $DESC" "$NAME"
        do_stop
        case "$?" in
          0|1)
                do_start
                case "$?" in
                        0) log_end_msg 0 ;;
                        1) log_end_msg 1 ;; # Old process is still running
                        *) log_end_msg 1 ;; # Failed to start
                esac
                ;;
          *)
                # Failed to stop
                log_end_msg 1
                ;;
        esac
        ;;
  *)
        #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
        echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
        exit 3
        ;;
esac

Το script βασίστηκε για άλλη μια φορά στο κλασικό skeleton που υπάρχει στον κατάλογο /etc/init.d/. Εκεί θα το σώσουμε σαν root με το όνομα IP-Updater. Αν δεν έχουμε τη δυνατότητα να το κάνουμε απ' ευθείας, μπορούμε σαν χρήστες να το σώσουμε στον κατάλογό μας και στη συνέχεια να το αντιγράψουμε ως root στην κατάλληλη θέση. Στη συνέχεια αλλάζουμε τα δικαιώματα και το κάνουμε εκτελέσιμο και ενημερώνουμε το σύστημα για την καινούργια υπηρεσία:

pi@raspberrypi:/usr/scripts > cd /etc/init.d/
pi@raspberrypi:/etc/init.d > sudo cp /home/pi/IP-Updater .
pi@raspberrypi:/etc/init.d > sudo chmod 0755 IP-Updater
pi@raspberrypi:/etc/init.d > sudo update-rc.d IP-Updater defaults
pi@raspberrypi:/etc/init.d > 

Μετά από αυτό μπορούμε να εκκινήσουμε την υπηρεσία μας. Όμως, για να δοκιμάσουμε ότι όλα βαίνουν καλώς, καλό είναι να δούμε αν ενεργοποιείται αυτόματα με μια επανεκκίνηση:

pi@raspberrypi:/usr/scripts > sudo shutdown -r now
pi@raspberrypi:/etc/init.d > 

Αποτελέσματα

Εαν όλα έχουν πάει καλά, τότε μπορείτε να παρακολουθήσετε τα μηνύματα στο /var/log/messages και να δείτε τη λειτουργία του script. Αν προσέξατε, στο configuration αρχείο του IP-Updater, χρησιμοποιείται το ttytter που είναι ένας client για το twitter. Μέσω αυτού βλέπουμε on-line τη λειτουργία της αλλαγής διεύθυνσης

Αυτό φυσικά προϋποθέτει να έχετε στήσει το ttytter και να το έχετε ρυθμίσει.

Έπειτα από αρκετό καιρό που λειτουργεί η υπηρεσία του NoIP στο Raspberry Pi δεν έχω πετύχει κατάσταση που να μη μπορώ να έχω πρόσβαση μέσω του δικτύου, απομακρυσμένα. Η απόκριση του συστήματος είναι παραπάνω από ικανοποιητική.


Ηλίας Χρυσοχέρης