Carimbo de data/hora do pacote
Introdução
Muitas NICs (placas de adaptador de rede ou adaptadores de rede) podem gerar um carimbo de data/hora em seu hardware sempre que um pacote é recebido ou transmitido. O carimbo de data/hora é gerado usando o próprio relógio de hardware da NIC. Esse recurso é usado em particular pelo PROTOCOLOP (Precision Time Protocol), que é um protocolo de sincronização de tempo. O PTP provisiona para usar esses carimbos de data/hora de hardware dentro do próprio protocolo.
Os carimbos de data/hora podem, por exemplo, ser usados para calcular o tempo gasto por um pacote dentro da pilha de rede do computador antes de serem enviados ou recebidos da transmissão. Esses cálculos podem ser usados pelo PTP para melhorar a precisão da sincronização de tempo. O suporte ao carimbo de data/hora do pacote de adaptadores de rede é voltado às vezes especificamente para o protocolo PTP. Em outros casos, um suporte mais geral é fornecido.
As APIs de carimbo de data/hora dão ao Windows a capacidade de dar suporte à funcionalidade de carimbo de data/hora de hardware dos adaptadores de rede para o protocolo PTP versão 2. No geral, os recursos incluem fornecer a capacidade de drivers de adaptadores de rede para dar suporte a carimbos de data/hora e para aplicativos de modo de usuário consumirem carimbos de data/hora associados a pacotes por meio do Windows Sockets (consulte Carimbo de data/hora do Winsock). Além disso, a capacidade de gerar carimbos de data/hora de software também está disponível, o que permite que um driver de rede gere carimbos de data/hora no software. Esses carimbos de data/hora de software são gerados por drivers NIC usando o equivalente do modo kernel de QueryPerformanceCounter (QPC). No entanto, não há suporte para ter carimbos de data/hora de hardware e software habilitados juntos.
Em particular, as APIs de carimbo de data/hora do pacote do Auxiliar de Protocolo ip (Auxiliar de Ip) descritas neste tópico fornecem a capacidade de aplicativos de modo de usuário determinarem a funcionalidade de carimbo de data/hora de um adaptador de rede e consultar carimbos de data/hora do adaptador de rede na forma de carimbos de data/hora cruzados (descritos abaixo).
Suporte ao Protocolo de Tempo de Precisão versão 2
Conforme mencionado, o main objetivo do suporte ao carimbo de data/hora no Windows é dar suporte ao protocolo PTPv2 (Precision Time Protocol versão 2). Dentro de PTPv2, nem todas as mensagens precisam de um carimbo de data/hora. Em particular, as mensagens de evento PTP usam carimbos de data/hora. Atualmente, o suporte está no escopo de PTPv2 no UDP (User Datagram Protocol). Não há suporte para PTP sobre ethernet bruta.
Há suporte para carimbo de data/hora para PTPv2 que opera no modo de duas etapas . 2 etapa refere-se ao modo no qual os carimbos de data/hora reais nos pacotes PTP não são gerados em tempo real no hardware, mas são recuperados do hardware e transmitidos como mensagens separadas (por exemplo, usando uma mensagem de acompanhamento).
Em resumo, você pode usar as APIs de carimbo de data/hora do pacote auxiliar de PROTOCOLO (Auxiliar de IP), juntamente com o suporte de carimbo de data/hora do Winsock, em um aplicativo PTPv2 para melhorar sua precisão de sincronização de tempo.
Recuperando os recursos de carimbo de data/hora de um adaptador de rede
Um aplicativo como um serviço de sincronização de horário PTP precisa determinar a funcionalidade de carimbo de data/hora de um adaptador de rede. Usando os recursos recuperados, o aplicativo pode decidir se deseja ou não usar carimbos de data/hora.
Mesmo que um adaptador de rede dê suporte a carimbos de data/hora, é necessário manter a capacidade desativada por padrão. Um adaptador ativa o carimbo de data/hora quando instruído a fazer isso. O Windows fornece APIs para um aplicativo recuperar a funcionalidade do hardware, bem como quais recursos estão ativados.
Para recuperar os recursos de carimbo de data/hora com suporte de um adaptador de rede, chame a função GetInterfaceSupportedTimestampCapabilities , fornecendo o LUID (identificador exclusivo local) do adaptador de rede e, em troca, recuperando os recursos de carimbo de data/hora com suporte na forma de um objeto INTERFACE_TIMESTAMP_CAPABILITIES .
O código retornado de GetInterfaceSupportedTimestampCapabilities indica se a chamada foi ou não bem-sucedida e se um valor de INTERFACE_TIMESTAMP_CAPABILITIES preenchido foi recuperado ou não.
Para recuperar os recursos de carimbo de data/hora atualmente habilitados de um adaptador de rede, chame a função GetInterfaceActiveTimestampCapabilities , fornecendo o LUID (identificador exclusivo local) do adaptador de rede e, em troca, recuperando os recursos de carimbo de data/hora habilitados na forma de um objeto INTERFACE_TIMESTAMP_CAPABILITIES .
Novamente, o código retornado de GetInterfaceActiveTimestampCapabilities indica êxito ou falha e se um valor de INTERFACE_TIMESTAMP_CAPABILITIES válido foi recuperado ou não.
Os adaptadores de rede podem dar suporte a uma variedade de recursos de carimbo de data/hora. Por exemplo, alguns adaptadores podem carimbo de data/hora de cada pacote durante o envio e recebimento, enquanto outros dão suporte apenas a pacotes PTPv2. A estrutura INTERFACE_TIMESTAMP_CAPABILITIES descreve os recursos exatos aos quais um adaptador de rede dá suporte.
Recuperando carimbos de data/hora cruzados de um adaptador de rede
Ao usar carimbos de data/hora de hardware, um aplicativo PTP precisa estabelecer uma relação (por exemplo, usando técnicas matemáticas apropriadas) entre o relógio de hardware do adaptador de rede e um relógio do sistema. Isso é necessário para que um valor que represente uma hora na unidade de um relógio possa ser convertido na unidade de outro relógio. Carimbos de data/hora cruzados são fornecidos para essa finalidade, e seu aplicativo pode amostrar carimbos de data/hora cruzados periodicamente para estabelecer essa relação.
Para fazer isso, chame a função CaptureInterfaceHardwareCrossTimestamp , fornecendo o LUID (identificador exclusivo local) do adaptador de rede e, em troca, recuperando o carimbo de data/hora do adaptador de rede na forma de um objeto INTERFACE_HARDWARE_CROSSTIMESTAMP .
Notificações de alteração da funcionalidade de carimbo de data/hora
Para ser notificado se os recursos de carimbo de data/hora de um adaptador de rede forem alterados, chame a função RegisterInterfaceTimestampConfigChange , fornecendo um ponteiro para a função de retorno de chamada que você implementou, juntamente com um contexto opcional alocado pelo chamador.
RegisterInterfaceTimestampConfigChange retorna um identificador que você pode passar posteriormente para UnregisterInterfaceTimestampConfigChange para cancelar o registro da função de retorno de chamada.
Exemplo de código 1 – recuperando recursos de carimbo de data/hora e carimbos de data/hora cruzados
// main.cpp in a Console App project.
#include <stdio.h>
#include <winsock2.h>
#include <iphlpapi.h>
#pragma comment(lib, "Iphlpapi")
BOOL
IsPTPv2HardwareTimestampingSupportedForIPv4(PINTERFACE_TIMESTAMP_CAPABILITIES timestampCapabilities)
{
// Supported if both receive and transmit side support is present
if (((timestampCapabilities->HardwareCapabilities.PtpV2OverUdpIPv4EventMessageReceive) ||
(timestampCapabilities->HardwareCapabilities.PtpV2OverUdpIPv4AllMessageReceive) ||
(timestampCapabilities->HardwareCapabilities.AllReceive))
&&
((timestampCapabilities->HardwareCapabilities.PtpV2OverUdpIPv4EventMessageTransmit) ||
(timestampCapabilities->HardwareCapabilities.PtpV2OverUdpIPv4AllMessageTransmit) ||
(timestampCapabilities->HardwareCapabilities.TaggedTransmit) ||
(timestampCapabilities->HardwareCapabilities.AllTransmit)))
{
return TRUE;
}
return FALSE;
}
BOOL
IsPTPv2HardwareTimestampingSupportedForIPv6(PINTERFACE_TIMESTAMP_CAPABILITIES timestampCapabilities)
{
// Supported if both receive and transmit side support is present
if (((timestampCapabilities->HardwareCapabilities.PtpV2OverUdpIPv6EventMessageReceive) ||
(timestampCapabilities->HardwareCapabilities.PtpV2OverUdpIPv6AllMessageReceive) ||
(timestampCapabilities->HardwareCapabilities.AllReceive))
&&
((timestampCapabilities->HardwareCapabilities.PtpV2OverUdpIPv6EventMessageTransmit) ||
(timestampCapabilities->HardwareCapabilities.PtpV2OverUdpIPv6AllMessageTransmit) ||
(timestampCapabilities->HardwareCapabilities.TaggedTransmit) ||
(timestampCapabilities->HardwareCapabilities.AllTransmit)))
{
return TRUE;
}
return FALSE;
}
enum SupportedTimestampType
{
TimestampTypeNone = 0,
TimestampTypeSoftware = 1,
TimestampTypeHardware = 2
};
// This function checks and returns the supported timestamp capabilities for an interface for
// a PTPv2 application
SupportedTimestampType
CheckActiveTimestampCapabilitiesForPtpv2(NET_LUID interfaceLuid)
{
DWORD result = NO_ERROR;
INTERFACE_TIMESTAMP_CAPABILITIES timestampCapabilities;
SupportedTimestampType supportedType = TimestampTypeNone;
result = GetInterfaceActiveTimestampCapabilities(
&interfaceLuid,
×tampCapabilities);
if (result != NO_ERROR)
{
printf("Error retrieving hardware timestamp capabilities: %d\n", result);
goto Exit;
}
if (IsPTPv2HardwareTimestampingSupportedForIPv4(×tampCapabilities) &&
IsPTPv2HardwareTimestampingSupportedForIPv6(×tampCapabilities))
{
supportedType = TimestampTypeHardware;
goto Exit;
}
else
{
if ((timestampCapabilities.SoftwareCapabilities.AllReceive) &&
((timestampCapabilities.SoftwareCapabilities.AllTransmit) ||
(timestampCapabilities.SoftwareCapabilities.TaggedTransmit)))
{
supportedType = TimestampTypeSoftware;
}
}
Exit:
return supportedType;
}
// Helper function which does the correlation between hardware and system clock
// using mathematical techniques
void ComputeCorrelationOfHardwareAndSystemTimestamps(INTERFACE_HARDWARE_CROSSTIMESTAMP *crossTimestamp);
// An application would call this function periodically to gather a set
// of matching timestamps for use in converting hardware timestamps to
// system timestamps
DWORD
RetrieveAndProcessCrossTimestamp(NET_LUID interfaceLuid)
{
DWORD result = NO_ERROR;
INTERFACE_HARDWARE_CROSSTIMESTAMP crossTimestamp;
result = CaptureInterfaceHardwareCrossTimestamp(
&interfaceLuid,
&crossTimestamp);
if (result != NO_ERROR)
{
printf("Error retrieving cross timestamp for the interface: %d\n", result);
goto Exit;
}
// Process crossTimestamp further to create a relation between the hardware clock
// of the NIC and the QPC values using appropriate mathematical techniques
ComputeCorrelationOfHardwareAndSystemTimestamps(&crossTimestamp);
Exit:
return result;
}
int main()
{
}
Exemplo de código 2 – registro para notificações de alteração de funcionalidade de carimbo de data/hora
Este exemplo mostra como seu aplicativo pode usar carimbos de data/hora de ponta a ponta.
// main.cpp in a Console App project.
#include <stdlib.h>
#include <stdio.h>
#include <winsock2.h>
#include <mswsock.h>
#include <iphlpapi.h>
#include <mstcpip.h>
#pragma comment(lib, "Ws2_32")
#pragma comment(lib, "Iphlpapi")
// Globals and function declarations used by the application.
// The sample functions and skeletons demonstrate:
// - Checking timestamp configuration for an interface to determine if timestamping can be used
// - If timestamping is enabled, starts tracking changes in timestamp configuration
// - Performing correlation between hardware and system timestamps using cross timestamps
// on a separate thread depending on the timestamp type configured
// - Receiving a packet and computing the latency between when the timestamp
// was generated on packet reception, and when the packet was received by
// the application through the socket
// The sample tries to demonstrate how an application could use timestamps. It is not thread safe
// and does not do exhaustive error checking.
// Lot of the functions are provided as skeletons, or only declared and invoked
// but are not defined. It is up to
// the application to implement these suitably.
// An application could use the functions below by e.g.
// - Call InitializeTimestampingForInterface for the interface it wants to track for timestamping capability.
// - Call EstimateReceiveLatency to estimate the receive latency of a packet depending on the timestamp
// type configured for the interface.
enum SupportedTimestampType
{
TimestampTypeNone = 0,
TimestampTypeSoftware = 1,
TimestampTypeHardware = 2
};
// interfaceBeingTracked is the interface the PTPv2 application
// intends to use for timestamping purpose.
wchar_t* interfaceBeingTracked;
// The active timestamping type determined for
// interfaceBeingTracked.
SupportedTimestampType timestampTypeEnabledForInterface;
HANDLE correlationThread;
HANDLE threadStopEvent;
HIFTIMESTAMPCHANGE TimestampChangeNotificationHandle = NULL;
// Function from sample above to check if an interface supports timestamping for PTPv2.
SupportedTimestampType CheckActiveTimestampCapabilitiesForPtpv2(NET_LUID interfaceLuid);
// Function from sample above to retrieve cross timestamps and process them further.
DWORD RetrieveAndProcessCrossTimestamp(NET_LUID interfaceLuid);
// Helper function which registers for timestamp configuration changes.
DWORD RegisterTimestampChangeNotifications();
// Callback function which is invoked when timestamp configuration changes
// for some network interface.
INTERFACE_TIMESTAMP_CONFIG_CHANGE_CALLBACK TimestampConfigChangeCallback;
// Function which does the correlation between hardware and system clock
// using mathematical techniques. It is periodically invoked and provided
// a sample of cross timestamp to compute a correlation.
void ComputeCorrelationOfHardwareAndSystemTimestamps(INTERFACE_HARDWARE_CROSSTIMESTAMP *crossTimestamp);
// Helper function which converts a hardware timestamp from the NIC clock
// to system timestamp (QPC) values. It is assumed that this works together
// with the ComputeCorrelationOfHardwareAndSystemTimestamps function
// to derive the correlation.
ULONG64 ConvertHardwareTimestampToQpc(ULONG64 HardwareTimestamp);
// Start function of thread which periodically samples
// cross timestamps to correlate hardware and software timestamps.
DWORD WINAPI CorrelateHardwareAndSystemTimestamps(LPVOID);
// Helper function which starts a new thread at CorrelateHardwareAndSystemTimestamps.
DWORD StartCorrelatingHardwareAndSytemTimestamps();
// Helper function which restarts correlation when some change is detected.
DWORD RestartCorrelatingHardwareAndSystemTimestamps();
// Stops the correlation thread.
DWORD StopCorrelatingHardwareAndSystemTimestamps();
DWORD
FindInterfaceFromFriendlyName(wchar_t* friendlyName, NET_LUID* interfaceLuid)
{
DWORD result = 0;
ULONG flags = 0;
ULONG outBufLen = 0;
PIP_ADAPTER_ADDRESSES pAddresses = NULL;
PIP_ADAPTER_ADDRESSES currentAddresses = NULL;
result = GetAdaptersAddresses(0,
flags,
NULL,
pAddresses,
&outBufLen);
if (result == ERROR_BUFFER_OVERFLOW)
{
pAddresses = (PIP_ADAPTER_ADDRESSES)malloc(outBufLen);
result = GetAdaptersAddresses(0,
flags,
NULL,
pAddresses,
&outBufLen);
if (result != NO_ERROR)
{
goto Done;
}
}
else if (result != NO_ERROR)
{
goto Done;
}
currentAddresses = pAddresses;
while (currentAddresses != NULL)
{
if (wcscmp(friendlyName, currentAddresses->FriendlyName) == 0)
{
result = ConvertInterfaceIndexToLuid(currentAddresses->IfIndex, interfaceLuid);
goto Done;
}
currentAddresses = currentAddresses->Next;
}
result = ERROR_NOT_FOUND;
Done:
if (pAddresses != NULL)
{
free(pAddresses);
}
return result;
}
// This function checks if an interface is suitable for
// timestamping for PTPv2. If so, it registers for timestamp
// configuration changes and initializes some globals.
// If hardware timestamping is enabled it also starts
// correlation thread.
DWORD
InitializeTimestampingForInterface(wchar_t* friendlyName)
{
DWORD error;
SupportedTimestampType supportedType = TimestampTypeNone;
NET_LUID interfaceLuid;
error = FindInterfaceFromFriendlyName(friendlyName, &interfaceLuid);
if (error != 0)
{
return error;
}
supportedType = CheckActiveTimestampCapabilitiesForPtpv2(interfaceLuid);
if (supportedType != TimestampTypeNone)
{
error = RegisterTimestampChangeNotifications();
if (error != NO_ERROR)
{
return error;
}
if (supportedType == TimestampTypeHardware)
{
threadStopEvent = CreateEvent(
NULL,
FALSE,
FALSE,
NULL
);
if (threadStopEvent == NULL)
{
return GetLastError();
}
error = StartCorrelatingHardwareAndSytemTimestamps();
if (error != 0)
{
return error;
}
}
interfaceBeingTracked = friendlyName;
timestampTypeEnabledForInterface = supportedType;
return error;
}
return ERROR_NOT_SUPPORTED;
}
DWORD
RegisterTimestampChangeNotifications()
{
DWORD retcode = NO_ERROR;
// Register with NULL context
retcode = RegisterInterfaceTimestampConfigChange(TimestampConfigChangeCallback, NULL, &TimestampChangeNotificationHandle);
if (retcode != NO_ERROR)
{
printf("Error when calling RegisterIfTimestampConfigChange %d\n", retcode);
}
return retcode;
}
// The callback invoked when change in some interface’s timestamping configuration
// happens. The callback takes appropriate action based on the new capability of the
// interface. The callback assumes that there is only 1 NIC. If multiple NICs are being
// tracked for timestamping then the application would need to check all of them.
VOID
WINAPI
TimestampConfigChangeCallback(
_In_ PVOID /*CallerContext*/
)
{
SupportedTimestampType supportedType;
NET_LUID interfaceLuid;
DWORD error;
error = FindInterfaceFromFriendlyName(interfaceBeingTracked, &interfaceLuid);
if (error != NO_ERROR)
{
if (timestampTypeEnabledForInterface == TimestampTypeHardware)
{
StopCorrelatingHardwareAndSystemTimestamps();
timestampTypeEnabledForInterface = TimestampTypeNone;
}
return;
}
supportedType = CheckActiveTimestampCapabilitiesForPtpv2(interfaceLuid);
if ((supportedType == TimestampTypeHardware) &&
(timestampTypeEnabledForInterface == TimestampTypeHardware))
{
// NIC could have been restarted, restart the correlation between hardware and
// system timestamps.
RestartCorrelatingHardwareAndSystemTimestamps();
}
else if (supportedType == TimestampTypeHardware)
{
// Start thread correlating hardware and software timestamps
StartCorrelatingHardwareAndSytemTimestamps();
}
else if (supportedType != TimestampTypeHardware)
{
// Hardware timestamps are not enabled, stop correlation
StopCorrelatingHardwareAndSystemTimestamps();
}
timestampTypeEnabledForInterface = supportedType;
}
DWORD
StartCorrelatingHardwareAndSytemTimestamps()
{
// Create a new thread which starts at CorrelateHardwareAndSoftwareTimestamps
correlationThread = CreateThread(
NULL,
0,
CorrelateHardwareAndSystemTimestamps,
NULL,
0,
NULL);
if (correlationThread == NULL)
{
return GetLastError();
}
}
// Thread which periodically invokes functions to
// sample cross timestamps and use them to compute
// correlation between hardware and system timestamps.
DWORD WINAPI
CorrelateHardwareAndSystemTimestamps(LPVOID /*lpParameter*/)
{
DWORD error;
NET_LUID interfaceLuid;
DWORD result;
result = FindInterfaceFromFriendlyName(interfaceBeingTracked, &interfaceLuid);
if (result != 0)
{
return result;
}
while (TRUE)
{
error = RetrieveAndProcessCrossTimestamp(interfaceLuid);
// Sleep and repeat till the thread gets a signal to stop
result = WaitForSingleObject(threadStopEvent, 5000);
if (result != WAIT_TIMEOUT)
{
if (result == WAIT_OBJECT_0)
{
return 0;
}
else if (result == WAIT_FAILED)
{
return GetLastError();
}
return result;
}
}
}
DWORD
StopCorrelatingHardwareAndSystemTimestamps()
{
SetEvent(threadStopEvent);
return 0;
}
// Function which receives a packet and estimates the latency between the
// point at which receive timestamp (of appropriate type) was generated
// and when the packet was received in the app through the socket.
// The sample assumes that there is only 1 NIC in the system. This is the NIC which is tracked through
// interfaceBeingTracked for correlation purpose, and through which packets are being
// received by the socket.
// The recvmsg parameter is of type LPFN_WSARECVMSG and an application can
// retrieve it by issuing WSAIoctl
// with SIO_GET_EXTENSION_FUNCTION_POINTER control
// and WSAID_WSARECVMSG. Please refer to msdn.
void EstimateReceiveLatency(SOCKET sock, LPFN_WSARECVMSG recvmsg)
{
DWORD numBytes;
INT error;
CHAR data[512];
CHAR control[WSA_CMSG_SPACE(sizeof(UINT64))] = { 0 };
WSABUF dataBuf;
WSABUF controlBuf;
WSAMSG wsaMsg;
UINT64 socketTimestamp = 0;
ULONG64 appLevelTimestamp;
ULONG64 packetReceivedTimestamp;
dataBuf.buf = data;
dataBuf.len = sizeof(data);
controlBuf.buf = control;
controlBuf.len = sizeof(control);
wsaMsg.name = NULL;
wsaMsg.namelen = 0;
wsaMsg.lpBuffers = &dataBuf;
wsaMsg.dwBufferCount = 1;
wsaMsg.Control = controlBuf;
wsaMsg.dwFlags = 0;
// Configure rx timestamp reception.
TIMESTAMPING_CONFIG config = { 0 };
config.Flags |= TIMESTAMPING_FLAG_RX;
error =
WSAIoctl(
sock,
SIO_TIMESTAMPING,
&config,
sizeof(config),
NULL,
0,
&numBytes,
NULL,
NULL);
if (error == SOCKET_ERROR)
{
printf("WSAIoctl failed %d\n", WSAGetLastError());
return;
}
error =
recvmsg(
sock,
&wsaMsg,
&numBytes,
NULL,
NULL);
if (error == SOCKET_ERROR)
{
printf("recvmsg failed %d\n", WSAGetLastError());
return;
}
if (timestampTypeEnabledForInterface != TimestampTypeNone)
{
// Capture system timestamp (QPC) upon message reception.
LARGE_INTEGER t1;
QueryPerformanceCounter(&t1);
appLevelTimestamp = t1.QuadPart;
printf("received packet\n");
// Look for socket rx timestamp returned via control message.
BOOLEAN retrievedTimestamp = FALSE;
PCMSGHDR cmsg = WSA_CMSG_FIRSTHDR(&wsaMsg);
while (cmsg != NULL)
{
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP)
{
socketTimestamp = *(PUINT64)WSA_CMSG_DATA(cmsg);
retrievedTimestamp = TRUE;
break;
}
cmsg = WSA_CMSG_NXTHDR(&wsaMsg, cmsg);
}
if (retrievedTimestamp)
{
// Compute socket receive path latency.
LARGE_INTEGER clockFrequency;
ULONG64 elapsedMicroseconds;
if (timestampTypeEnabledForInterface == TimestampTypeHardware)
{
packetReceivedTimestamp = ConvertHardwareTimestampToQpc(socketTimestamp);
}
else
{
packetReceivedTimestamp = socketTimestamp;
}
QueryPerformanceFrequency(&clockFrequency);
// Compute socket receive path latency.
elapsedMicroseconds = appLevelTimestamp - packetReceivedTimestamp;
elapsedMicroseconds *= 1000000;
elapsedMicroseconds /= clockFrequency.QuadPart;
printf("RX latency estimation: %lld microseconds\n",
elapsedMicroseconds);
}
else
{
printf("failed to retrieve RX timestamp\n");
}
}
}
int main()
{
}