NTP-сервер за NAT

#c #nat #ntp

#c #nat #ntp

Вопрос:

У меня есть самостоятельно реализованный NTP-сервер, работающий на компьютере с Ubuntu.

Клиент NTP — это еще одна машина Ubuntu с установленными утилитами ntpdate и ntpd. Сервер имеет адрес 192.168.2.200, а клиент — 192.168.2.74 . Если я запущу:

 ntpdate 192.168.2.200
 

Или

 ntpd -c /root/NTP.conf
 

С помощью NTP.conf

 server 192.168.2.200 minpoll 0 maxpoll 0 prefer
 

Клиентская ОС будет синхронизироваться с моим сервером.
Следующий шаг — поместить сервер за NAT.
Клиент NTP по-прежнему имеет адрес 192.168.2.74, но теперь сервер находится за компьютером с Windows 10 с адресом 192.168.2.75 .

Я установил переадресацию портов, чтобы пересылать UDP-пакеты, направленные на 192.168.2.75:123, на внутренний адрес 192.168.2.200:123, где расположен сервер. По какой-то причине, даже если клиент (2.74) получит тот же ответ от сервера (2.200 соответствует 2.75), он не будет синхронизироваться.

Это то, что я сделал до сих пор:

Определите пользовательскую структуру для сообщения NTP, как определено в rfc5905:

 typedef struct
{
    uint8_t hdrhead[4];

    uint32_t delay;
    uint32_t dispersion;
    uint32_t reference_id;

    uint64_t reference_ts;
    uint64_t origin_ts;
    uint64_t receive_ts;
    uint64_t transmit_ts;
} ntp_msg5905;
 

В основной функции у меня есть простой цикл while:

 // socket initialization...
while (1)
{
    uint8_t recv_msg[NTP_MSGLEN];
    while (recvfrom(s, recv_msg, NTP_MSGLEN, 0, (struct sockaddr*) amp;src_addr, amp;src_addrlen) < NTP_MSGLEN)
    {
        _log_("Arrived wrong NTP request: message too short", stdout);
    }
    _log_("Arrived NTP request", stdout);

    uint64_t recvtime;
    timestamp_64(amp;recvtime);
    ntp_reply(s, amp;src_addr , src_addrlen, *(ntp_msg5905*)recv_msg, recvtime);
}
 

функция timestamp_64() просто создает 64-битную временную метку, полученную с помощью gettimeofday(), ничего особенного или связанного с ntp.

Функция ntp_reply является:

 void ntp_reply(int clisock, struct sockaddr_in* saddr_p, socklen_t saddrlen, ntp_msg5905 request, uint64_t recvtime) 
{
    ntp_msg5905 reply;
    memset(amp;reply, 0x00, NTP_MSGLEN);

    reply.hdrhead[STRATUM] = NTP_STRATUM_PRIMARY_SERVER;
    reply.hdrhead[PREC]    = NTP_HOST_PRECISION;
    reply.delay            = NTP_NODELAY;
    reply.dispersion       = NTP_NODISPERSION;
    reply.reference_id     = NTP_REF_ID_GPS;

    reply.hdrhead[LIVN] = request.hdrhead[LIVN] amp; 0x38   NTP_MODE_SERVER;
    reply.hdrhead[POLL] = request.hdrhead[POLL];
    reply.receive_ts    = recvtime;
    reply.origin_ts     = request.transmit_ts;
    uint64_t* rts = amp;reply.reference_ts;
    uint64_t* tts = amp;reply.transmit_ts;
    timestamp_64(rts);  
    timestamp_64(tts);

    if (sendto(clisock, (char*) amp;reply, NTP_MSGLEN, 0, (struct sockaddr*) saddr_p, saddrlen) < NTP_MSGLEN)
    {
        _log_("Cannot send NTP reply", stdout);
    }
    else
    {
        _log_("Request reply sent back to client", stdout);
    }
}
 

Глобальный определяет ниже:

 /* Global NTP parameters */
#define NTP_UTC                      2208988800U
#define NTP_PORT                     123
#define NTP_MSGLEN                   48

/* NTP header's first 4 bytes */
#define LIVN                         0
#define STRATUM                      1
#define POLL                         2
#define PREC                         3

/* NTP header field values */
#define NTP_MODE_SERVER              4
#define NTP_STRATUM_PRIMARY_SERVER   1
#define NTP_HOST_PRECISION          -6
#define NTP_NODELAY                  0
#define NTP_NODISPERSION             0
#define NTP_REF_ID_GPS               *(uint32_t*)("GPS")
 

Где я ошибаюсь?

Комментарии:

1. Работает ли он без NAT?

2. Да, если я напрямую подключаю клиент и сервер через кабель Ethernet, синхронизация работает

3. Можем ли мы видеть пакеты запросов и ответов, которые видит машина, отправившая запрос? Вы можете использовать какой-нибудь инструмент, который может сбрасывать исходные и целевые IP-адреса и порты. Держу пари, что IP-адрес источника ответа не совпадает с IP-адресом назначения запроса на запрашивающем компьютере. (Потому что NAT неверен. Для этого вам нужен двойной NAT.)