What version of gotd are you using?
github.com/gotd/td v0.161.0
Can this issue be reproduced with the latest version?
Yes. mtproto/read.go is unchanged in v0.162.0.
What did you do?
Ran a client on a machine whose clock was about 8 minutes off from real time. It was reported to us by a user of tele, a terminal Telegram client built on gotd (sorokin-vladimir/tele#277). Any client reproduces it: set the system clock more than 300 seconds ahead, or more than 30 seconds behind, and call client.Run.
What did you expect to see?
Either the connection works, because the client corrects for the difference as the time synchronization section of the MTProto docs describes:
Having received such a message or a container holding it, the client first performs a time synchronization (in effect, simply storing the difference between the server's time and its own to be able to compute the "correct" time in the future) and then verifies that the message identifiers for correctness.
Or, at minimum, an error that says the local clock is wrong.
What did you see instead?
The client hangs forever. decryptMessage checks every incoming msg_id against the raw local clock (checkMessageID(c.clock.Now(), ...), mtproto/read.go). Once the local clock is off by more than the window, every server message is rejected, and consumeMessage logs it at Warn and returns nil:
Ignoring rejected message error="bad message id ...: created too far in past: message rejected"
No reply ever reaches the caller, and no error surfaces from Run or from the pending RPC. From the application's side the connection looks alive and simply never answers.
The window is asymmetric, because maxPast and maxFuture are applied to the local clock. A clock that is only 30 seconds behind makes every server message look "created too far in future" and triggers the same hang. That is a much more common state than 5 minutes ahead: a VM resumed after a pause, or a machine without NTP.
Possible direction
The docs describe keeping an offset between server time and local time and checking against the corrected time. A message that decrypts with the session's auth key and carries the right session id is already authenticated, so its msg_id could calibrate the offset before the freshness check runs. The same offset would then also apply to outgoing msg_ids, and bad_msg_notification codes 16/17 would update it, as the docs say they should.
In #883 you mentioned that MTProto-based calibration belongs in the main repo. The NTP clock that closed #883 doesn't cover this for everyone: it needs direct UDP to an NTP server, which applications that route all traffic through a proxy can't use.
Even without calibration, surfacing the condition would help a lot. If every incoming message in a row is rejected for time, return an error, or expose the measured difference, so the application can tell the user to fix their clock instead of hanging.
Happy to send a PR if this direction works for you.
What Go version and environment are you using?
go version go1.26.0 darwin/arm64
The original report is from Gentoo Linux.
What version of gotd are you using?
Can this issue be reproduced with the latest version?
Yes.
mtproto/read.gois unchanged in v0.162.0.What did you do?
Ran a client on a machine whose clock was about 8 minutes off from real time. It was reported to us by a user of tele, a terminal Telegram client built on gotd (sorokin-vladimir/tele#277). Any client reproduces it: set the system clock more than 300 seconds ahead, or more than 30 seconds behind, and call
client.Run.What did you expect to see?
Either the connection works, because the client corrects for the difference as the time synchronization section of the MTProto docs describes:
Or, at minimum, an error that says the local clock is wrong.
What did you see instead?
The client hangs forever.
decryptMessagechecks every incomingmsg_idagainst the raw local clock (checkMessageID(c.clock.Now(), ...),mtproto/read.go). Once the local clock is off by more than the window, every server message is rejected, andconsumeMessagelogs it at Warn and returnsnil:No reply ever reaches the caller, and no error surfaces from
Runor from the pending RPC. From the application's side the connection looks alive and simply never answers.The window is asymmetric, because
maxPastandmaxFutureare applied to the local clock. A clock that is only 30 seconds behind makes every server message look "created too far in future" and triggers the same hang. That is a much more common state than 5 minutes ahead: a VM resumed after a pause, or a machine without NTP.Possible direction
The docs describe keeping an offset between server time and local time and checking against the corrected time. A message that decrypts with the session's auth key and carries the right session id is already authenticated, so its
msg_idcould calibrate the offset before the freshness check runs. The same offset would then also apply to outgoingmsg_ids, andbad_msg_notificationcodes 16/17 would update it, as the docs say they should.In #883 you mentioned that MTProto-based calibration belongs in the main repo. The NTP clock that closed #883 doesn't cover this for everyone: it needs direct UDP to an NTP server, which applications that route all traffic through a proxy can't use.
Even without calibration, surfacing the condition would help a lot. If every incoming message in a row is rejected for time, return an error, or expose the measured difference, so the application can tell the user to fix their clock instead of hanging.
Happy to send a PR if this direction works for you.
What Go version and environment are you using?
The original report is from Gentoo Linux.