Ping & PingReply - Azure Function Tips (TimerTrigger)

Ping & PingReply: Caso d’uso

In questo breve HowTo vi mostrerò come ho risolto una situazione pratica in ambito domestico ricorrendo ad una Azure Function TimerTrigger. Ricordate la data del 3 Giugno 2020? Lo spostamento tra regioni era consentito e di conseguenza mi sarei spostato per andare in Emilia dalla Lombardia. Cosa centra tutto questo con l’articolo?

Ora vi spiego quanto accaduto. La sera del primo giugno mi salta (dopo quasi un anno) la corrente di casa per tre volte in pochissime ore. Il due mattina succede lo stesso. Perfetto, proprio domani che dobbiamo partire. Posso chiedere al vicino se gentilmente mi controlla l’interruttore sperando sia a casa.

A quel punto mi è venuta l’illuminazione:

  • Ho un IP di casa fisso
  • Creo una Azure Function TimerTrigger
  • Ogni 4 ore effettuo un ping del mio router (utilizzando Ping & PingReply)
  • Se non risponde faccio partire una mail di notifica (Mailgun API)

I restanti paragrafi del post mostreranno le classi utlizzate per realizzare il tutto senza perdermi troppo in altre parole.

MailGunDTO

Per l’invio della mail utilizzerò MailGun e nel DTO di configurazione metterò le proprietà minimali per poterlo utilizzare.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public interface IMailGunConfigDTO
{
    string ApiBaseUri { get; set; }
    string ApiKey { get; set; }
    string Domain { get; set; }
    string FromMail { get; set; }
    string FromName { get; set; }
}

public class MailGunConfigDTO : IMailGunConfigDTO
{
    public string ApiBaseUri { get; set; }
    public string ApiKey { get; set; }
    public string Domain { get; set; }
    public string FromMail { get; set; }
    public string FromName { get; set; }
}

PingIPAddress (Azure Function)

Di seguito ecco le poche righe di codice pe potere realizzare la nostra Azure Function TimerTrigger che effettuerà in maniera regolare il Ping su un indirizzo ip e dal quale avremo le risposte tramite PingReply.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class PingIPAddress
{
    private string _mailTo = "... add mail(s) here...";
    private string _ipAddress = "... add server ip here...";
    private MailGunConfigDTO _mailGunConfig;

    public PingIPAddress(
        IMailGunConfigDTO mailGunConfig)
    {
        _mailGunConfig = (MailGunConfigDTO)mailGunConfig;
    }

    [FunctionName("PingIPAddress")]
    public void Run([TimerTrigger("0 0 */4 * * *")]TimerInfo myTimer, ILogger log)

    {
        using (Ping pinger = new Ping())
        {
            PingReply reply = pinger.Send(_ipAddress);

            if (reply.Status == IPStatus.Success)
            {
                // Nothing to do ...
            }
            else
            {
                RestClient client = new RestClient();
                client.BaseUrl = new Uri(_mailGunConfig.ApiBaseUri);
                client.Authenticator = new HttpBasicAuthenticator("api", _mailGunConfig.ApiKey);
                RestRequest request = new RestRequest();
                request.AddParameter("domain", _mailGunConfig.Domain, ParameterType.UrlSegment);
                request.Resource = "{domain}/messages";
                request.AddParameter("from", $"{_mailGunConfig.FromName} <{_mailGunConfig.FromMail}>");

                request.AddParameter("to", _mailTo);
                request.AddParameter("subject", $"IP {reply.Address.ToString()} IP IS UNREACHABLE!");
                request.AddParameter("text", $"IP {reply.Address.ToString()} IP IS UNREACHABLE!");

                request.Method = Method.POST;
                var executeResult = client.Execute(request);
            }
        }
    }
}

La classe PingIPAddress riceverà in ingresso MailGunConfigDTO prevalorizzato nello startup (vedi sotto) della nostra functions. Per quanto riguarda i valori di _mailTo e _ipAddress non sono stati passati tramite dependency injection per una questione di comodità/pigrizia.

Se notate il check dello status IPStatus.Success ho lasciato il ramo morto intenzionalmente. Nel mio caso (codice tolto) verrà tracciata in un log la risposta e l’esito.

1
2
3
4
if (reply.Status == IPStatus.Success)
{
    // Nothing to do ...
}

Startup.cs

Di seguito un breve estratto del file Startup.cs per valorizzare MailGunConfigDTO ed utilizzarlo successivamente tramite dependency injection

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(home_functions.Startup))]

namespace home_functions
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton<IMailGunConfigDTO, MailGunConfigDTO>(s =>
            {
                var mailGunConfig = new MailGunConfigDTO()
                {
                    ApiBaseUri = "https://api.eu.mailgun.net/v3/",
                    ApiKey = " ... ",
                    Domain = " ... ",
                    FromMail = " ... ",
                    FromName = " ... ",
                };

                return mailGunConfig;
            });
        }
    }
}

Conclusione Finale

Per mia fortuna la corrente non è saltata, ma la tranquillità di avere un “guardiano in cloud” non è poco!