Skip to content

Instantly share code, notes, and snippets.

@l1x
Last active March 1, 2023 08:14
Show Gist options
  • Save l1x/b0a7f844b283ac08e3125d1ba6e81eeb to your computer and use it in GitHub Desktop.
Save l1x/b0a7f844b283ac08e3125d1ba6e81eeb to your computer and use it in GitHub Desktop.
NTP client in Erlang
-module(ntp).
-export([get_time/1, get_time/0]).
-define(NTP_PORT, 123). % udp
-define(SERVER_TIMEOUT, 5000). % ms
-define(EPOCH, 2208988800). % offset yr 1900 to unix epoch
ntp_servers() ->
[ "0.europe.pool.ntp.org",
"1.europe.pool.ntp.org",
"2.europe.pool.ntp.org" ].
get_time() ->
Random_server = lists:nth(rand:uniform(length(ntp_servers())), ntp_servers()),
get_time(Random_server).
get_time(Host) ->
Resp = ntp_request(Host, create_ntp_request()),
process_ntp_response(Resp) .
ntp_request(Host, Binary) ->
{ok, Socket} = gen_udp:open(0, [binary, {active, false}]),
gen_udp:send(Socket, Host, ?NTP_PORT, Binary),
{ok, {_Address, _Port, Resp}} = gen_udp:recv(Socket, 0, 500),
gen_udp:close(Socket),
Resp.
process_ntp_response(Ntp_response) ->
<< LI:2, Version:3, Mode:3, Stratum:8, Poll:8/signed, Precision:8/signed,
RootDel:32, RootDisp:32, R1:8, R2:8, R3:8, R4:8, RtsI:32, RtsF:32,
OtsI:32, OtsF:32, RcvI:32, RcvF:32, XmtI:32, XmtF:32 >> = Ntp_response,
{NowMS, NowS, NowUS} = erlang:timestamp(),
NowTimestamp = NowMS * 1.0e6 + NowS + NowUS/1000,
TransmitTimestamp = XmtI - ?EPOCH + binfrac(XmtF),
{ {li, LI}, {vn, Version}, {mode, Mode}, {stratum, Stratum}, {poll, Poll}, {precision, Precision},
{rootDelay, RootDel}, {rootDispersion, RootDisp}, {referenceId, R1, R2, R3, R4},
{referenceTimestamp, RtsI - ?EPOCH + binfrac(RtsF)},
{originateTimestamp, OtsI - ?EPOCH + binfrac(OtsF)},
{receiveTimestamp, RcvI - ?EPOCH + binfrac(RcvF)},
{transmitTimestamp, TransmitTimestamp},
{clientReceiveTimestamp, NowTimestamp},
{offset, TransmitTimestamp - NowTimestamp} }.
create_ntp_request() ->
<< 0:2, 4:3, 3:3, 0:(3*8 + 3*32 + 4*64) >>.
binfrac(Bin) ->
binfrac(Bin, 2, 0).
binfrac(0, _, Frac) ->
Frac;
binfrac(Bin, N, Frac) ->
binfrac(Bin bsr 1, N*2, Frac + (Bin band 1)/N).
defmodule Der do
require Record
require Logger
@ntp_port 123
@client_timeout 500
def ntp_servers() do
['0.europe.pool.ntp.org', '1.europe.pool.ntp.org', '2.europe.pool.ntp.org']
end
def get_time() do
random_domain = Enum.random(ntp_servers())
Logger.info("Selected NTP server name: #{inspect(random_domain)}")
{:ok, {_, _, _, _, _, ips}} = :inet_res.getbyname(Enum.random(ntp_servers()), :a)
Logger.info("Server IPs: #{inspect(ips)}")
ip = Enum.random(ips)
Logger.info("Selected server IP: #{inspect(ip)}")
get_time(ip)
end
def get_time(ip) do
ntp_request = create_ntp_request()
{:ok, ntp_response} = send_ntp_request(ip, ntp_request)
end
def create_ntp_request do
<<0::integer-size(2), 4::integer-size(3), 3::integer-size(3), 0::integer-size(376)>>
end
def send_ntp_request(ip, ntp_request) do
{:ok, socket} = :gen_udp.open(0, [:binary, {:active, false}])
Logger.info("Local socket: #{inspect(socket)}")
:gen_udp.send(socket, ip, @ntp_port, ntp_request)
{:ok, {_address, _port, response}} = :gen_udp.recv(socket, 0, @client_timeout)
:gen_udp.close(socket)
Logger.info("NTP server response: #{inspect(response)}")
response
end
defp process_ntp_response(<<li::integer-size(2), version::integer-size(3), mode::integer-size(3), _rest::integer-size(376)>>) do
{li, version, mode}
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment