You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

idletimer.go 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. // Copyright (c) 2017 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package irc
  4. import (
  5. "fmt"
  6. "sync"
  7. "sync/atomic"
  8. "time"
  9. "github.com/goshuirc/irc-go/ircfmt"
  10. "github.com/oragono/oragono/irc/caps"
  11. )
  12. const (
  13. // RegisterTimeout is how long clients have to register before we disconnect them
  14. RegisterTimeout = time.Minute
  15. // DefaultIdleTimeout is how long without traffic before we send the client a PING
  16. DefaultIdleTimeout = time.Minute + 30*time.Second
  17. // For Tor clients, we send a PING at least every 30 seconds, as a workaround for this bug
  18. // (single-onion circuits will close unless the client sends data once every 60 seconds):
  19. // https://bugs.torproject.org/29665
  20. TorIdleTimeout = time.Second * 30
  21. // This is how long a client gets without sending any message, including the PONG to our
  22. // PING, before we disconnect them:
  23. DefaultTotalTimeout = 2*time.Minute + 30*time.Second
  24. // Resumeable clients (clients who have negotiated caps.Resume) get longer:
  25. ResumeableTotalTimeout = 3*time.Minute + 30*time.Second
  26. )
  27. // client idleness state machine
  28. type TimerState uint
  29. const (
  30. TimerUnregistered TimerState = iota // client is unregistered
  31. TimerActive // client is actively sending commands
  32. TimerIdle // client is idle, we sent PING and are waiting for PONG
  33. TimerDead // client was terminated
  34. )
  35. type IdleTimer struct {
  36. sync.Mutex // tier 1
  37. // immutable after construction
  38. registerTimeout time.Duration
  39. session *Session
  40. // mutable
  41. idleTimeout time.Duration
  42. quitTimeout time.Duration
  43. state TimerState
  44. timer *time.Timer
  45. lastTouch time.Time
  46. }
  47. // Initialize sets up an IdleTimer and starts counting idle time;
  48. // if there is no activity from the client, it will eventually be stopped.
  49. func (it *IdleTimer) Initialize(session *Session) {
  50. it.session = session
  51. it.registerTimeout = RegisterTimeout
  52. it.idleTimeout, it.quitTimeout = it.recomputeDurations()
  53. registered := session.client.Registered()
  54. now := time.Now().UTC()
  55. it.Lock()
  56. defer it.Unlock()
  57. it.lastTouch = now
  58. if registered {
  59. it.state = TimerActive
  60. } else {
  61. it.state = TimerUnregistered
  62. }
  63. it.resetTimeout()
  64. }
  65. // recomputeDurations recomputes the idle and quit durations, given the client's caps.
  66. func (it *IdleTimer) recomputeDurations() (idleTimeout, quitTimeout time.Duration) {
  67. totalTimeout := DefaultTotalTimeout
  68. // if they have the resume cap, wait longer before pinging them out
  69. // to give them a chance to resume their connection
  70. if it.session.capabilities.Has(caps.Resume) {
  71. totalTimeout = ResumeableTotalTimeout
  72. }
  73. idleTimeout = DefaultIdleTimeout
  74. if it.session.isTor {
  75. idleTimeout = TorIdleTimeout
  76. }
  77. quitTimeout = totalTimeout - idleTimeout
  78. return
  79. }
  80. func (it *IdleTimer) Touch() {
  81. idleTimeout, quitTimeout := it.recomputeDurations()
  82. now := time.Now().UTC()
  83. it.Lock()
  84. defer it.Unlock()
  85. it.idleTimeout, it.quitTimeout = idleTimeout, quitTimeout
  86. it.lastTouch = now
  87. // a touch transitions TimerUnregistered or TimerIdle into TimerActive
  88. if it.state != TimerDead {
  89. it.state = TimerActive
  90. it.resetTimeout()
  91. }
  92. }
  93. func (it *IdleTimer) LastTouch() (result time.Time) {
  94. it.Lock()
  95. result = it.lastTouch
  96. it.Unlock()
  97. return
  98. }
  99. func (it *IdleTimer) processTimeout() {
  100. idleTimeout, quitTimeout := it.recomputeDurations()
  101. var previousState TimerState
  102. func() {
  103. it.Lock()
  104. defer it.Unlock()
  105. it.idleTimeout, it.quitTimeout = idleTimeout, quitTimeout
  106. previousState = it.state
  107. // TimerActive transitions to TimerIdle, all others to TimerDead
  108. if it.state == TimerActive {
  109. // send them a ping, give them time to respond
  110. it.state = TimerIdle
  111. it.resetTimeout()
  112. } else {
  113. it.state = TimerDead
  114. }
  115. }()
  116. if previousState == TimerActive {
  117. it.session.Ping()
  118. } else {
  119. it.session.client.Quit(it.quitMessage(previousState), it.session)
  120. it.session.client.destroy(it.session)
  121. }
  122. }
  123. // Stop stops counting idle time.
  124. func (it *IdleTimer) Stop() {
  125. if it == nil {
  126. return
  127. }
  128. it.Lock()
  129. defer it.Unlock()
  130. it.state = TimerDead
  131. it.resetTimeout()
  132. }
  133. func (it *IdleTimer) resetTimeout() {
  134. if it.timer != nil {
  135. it.timer.Stop()
  136. }
  137. var nextTimeout time.Duration
  138. switch it.state {
  139. case TimerUnregistered:
  140. nextTimeout = it.registerTimeout
  141. case TimerActive:
  142. nextTimeout = it.idleTimeout
  143. case TimerIdle:
  144. nextTimeout = it.quitTimeout
  145. case TimerDead:
  146. return
  147. }
  148. if it.timer != nil {
  149. it.timer.Reset(nextTimeout)
  150. } else {
  151. it.timer = time.AfterFunc(nextTimeout, it.processTimeout)
  152. }
  153. }
  154. func (it *IdleTimer) quitMessage(state TimerState) string {
  155. switch state {
  156. case TimerUnregistered:
  157. return fmt.Sprintf("Registration timeout: %v", it.registerTimeout)
  158. case TimerIdle:
  159. // how many seconds before registered clients are timed out (IdleTimeout plus QuitTimeout).
  160. it.Lock()
  161. defer it.Unlock()
  162. return fmt.Sprintf("Ping timeout: %v", (it.idleTimeout + it.quitTimeout))
  163. default:
  164. // shouldn't happen
  165. return ""
  166. }
  167. }
  168. // NickTimer manages timing out of clients who are squatting reserved nicks
  169. type NickTimer struct {
  170. sync.Mutex // tier 1
  171. // immutable after construction
  172. client *Client
  173. // mutable
  174. nick string
  175. accountForNick string
  176. account string
  177. timeout time.Duration
  178. timer *time.Timer
  179. enabled uint32
  180. }
  181. // Initialize sets up a NickTimer, based on server config settings.
  182. func (nt *NickTimer) Initialize(client *Client) {
  183. if nt.client == nil {
  184. nt.client = client // placate the race detector
  185. }
  186. config := &client.server.Config().Accounts.NickReservation
  187. enabled := config.Enabled && (config.Method == NickEnforcementWithTimeout || config.AllowCustomEnforcement)
  188. nt.Lock()
  189. defer nt.Unlock()
  190. nt.timeout = config.RenameTimeout
  191. if enabled {
  192. atomic.StoreUint32(&nt.enabled, 1)
  193. } else {
  194. nt.stopInternal()
  195. }
  196. }
  197. func (nt *NickTimer) Enabled() bool {
  198. return atomic.LoadUint32(&nt.enabled) == 1
  199. }
  200. func (nt *NickTimer) Timeout() (timeout time.Duration) {
  201. nt.Lock()
  202. timeout = nt.timeout
  203. nt.Unlock()
  204. return
  205. }
  206. // Touch records a nick change and updates the timer as necessary
  207. func (nt *NickTimer) Touch(rb *ResponseBuffer) {
  208. if !nt.Enabled() {
  209. return
  210. }
  211. var session *Session
  212. if rb != nil {
  213. session = rb.session
  214. }
  215. cfnick, skeleton := nt.client.uniqueIdentifiers()
  216. account := nt.client.Account()
  217. accountForNick, method := nt.client.server.accounts.EnforcementStatus(cfnick, skeleton)
  218. enforceTimeout := method == NickEnforcementWithTimeout
  219. var shouldWarn, shouldRename bool
  220. func() {
  221. nt.Lock()
  222. defer nt.Unlock()
  223. // the timer will not reset as long as the squatter is targeting the same account
  224. accountChanged := accountForNick != nt.accountForNick
  225. // change state
  226. nt.nick = cfnick
  227. nt.account = account
  228. nt.accountForNick = accountForNick
  229. delinquent := accountForNick != "" && accountForNick != account
  230. if nt.timer != nil && (!enforceTimeout || !delinquent || accountChanged) {
  231. nt.timer.Stop()
  232. nt.timer = nil
  233. }
  234. if enforceTimeout && delinquent && (accountChanged || nt.timer == nil) {
  235. nt.timer = time.AfterFunc(nt.timeout, nt.processTimeout)
  236. shouldWarn = true
  237. } else if method == NickEnforcementStrict && delinquent {
  238. shouldRename = true // this can happen if reservation was enabled by rehash
  239. }
  240. }()
  241. if shouldWarn {
  242. tnick := nt.client.Nick()
  243. message := fmt.Sprintf(ircfmt.Unescape(nt.client.t(nsTimeoutNotice)), nt.Timeout())
  244. // #449
  245. for _, mSession := range nt.client.Sessions() {
  246. if mSession == session {
  247. rb.Add(nil, nsPrefix, "NOTICE", tnick, message)
  248. rb.Add(nil, nt.client.server.name, "WARN", "*", "ACCOUNT_REQUIRED", message)
  249. } else {
  250. mSession.Send(nil, nsPrefix, "NOTICE", tnick, message)
  251. mSession.Send(nil, nt.client.server.name, "WARN", "*", "ACCOUNT_REQUIRED", message)
  252. }
  253. }
  254. } else if shouldRename {
  255. nt.client.Notice(nt.client.t("Nickname is reserved by a different account"))
  256. nt.client.server.RandomlyRename(nt.client)
  257. }
  258. }
  259. // Stop stops counting time and cleans up the timer
  260. func (nt *NickTimer) Stop() {
  261. nt.Lock()
  262. defer nt.Unlock()
  263. nt.stopInternal()
  264. }
  265. func (nt *NickTimer) stopInternal() {
  266. if nt.timer != nil {
  267. nt.timer.Stop()
  268. nt.timer = nil
  269. }
  270. atomic.StoreUint32(&nt.enabled, 0)
  271. }
  272. func (nt *NickTimer) processTimeout() {
  273. baseMsg := "Nick is reserved and authentication timeout expired: %v"
  274. nt.client.Notice(fmt.Sprintf(nt.client.t(baseMsg), nt.Timeout()))
  275. nt.client.server.RandomlyRename(nt.client)
  276. }
  277. // BrbTimer is a timer on the client as a whole (not an individual session) for implementing
  278. // the BRB command and related functionality (where a client can remain online without
  279. // having any connected sessions).
  280. type BrbState uint
  281. const (
  282. // BrbDisabled is the default state; the client will be disconnected if it has no sessions
  283. BrbDisabled BrbState = iota
  284. // BrbEnabled allows the client to remain online without sessions; if a timeout is
  285. // reached, it will be removed
  286. BrbEnabled
  287. // BrbDead is the state of a client after its timeout has expired; it will be removed
  288. // and therefore new sessions cannot be attached to it
  289. BrbDead
  290. )
  291. type BrbTimer struct {
  292. // XXX we use client.stateMutex for synchronization, so we can atomically test
  293. // conditions that use both brbTimer.state and client.sessions. This code
  294. // is tightly coupled with the rest of Client.
  295. client *Client
  296. state BrbState
  297. brbAt time.Time
  298. duration time.Duration
  299. timer *time.Timer
  300. }
  301. func (bt *BrbTimer) Initialize(client *Client) {
  302. bt.client = client
  303. }
  304. // attempts to enable BRB for a client, returns whether it succeeded
  305. func (bt *BrbTimer) Enable() (success bool, duration time.Duration) {
  306. // TODO make this configurable
  307. duration = ResumeableTotalTimeout
  308. bt.client.stateMutex.Lock()
  309. defer bt.client.stateMutex.Unlock()
  310. if !bt.client.registered || bt.client.alwaysOn || bt.client.resumeID == "" {
  311. return
  312. }
  313. switch bt.state {
  314. case BrbDisabled, BrbEnabled:
  315. bt.state = BrbEnabled
  316. bt.duration = duration
  317. bt.resetTimeout()
  318. // only track the earliest BRB, if multiple sessions are BRB'ing at once
  319. // TODO(#524) this is inaccurate in case of an auto-BRB
  320. if bt.brbAt.IsZero() {
  321. bt.brbAt = time.Now().UTC()
  322. }
  323. success = true
  324. default:
  325. // BrbDead
  326. success = false
  327. }
  328. return
  329. }
  330. // turns off BRB for a client and stops the timer; used on resume and during
  331. // client teardown
  332. func (bt *BrbTimer) Disable() (brbAt time.Time) {
  333. bt.client.stateMutex.Lock()
  334. defer bt.client.stateMutex.Unlock()
  335. if bt.state == BrbEnabled {
  336. bt.state = BrbDisabled
  337. brbAt = bt.brbAt
  338. bt.brbAt = time.Time{}
  339. }
  340. bt.resetTimeout()
  341. return
  342. }
  343. func (bt *BrbTimer) resetTimeout() {
  344. if bt.timer != nil {
  345. bt.timer.Stop()
  346. }
  347. if bt.state != BrbEnabled {
  348. return
  349. }
  350. if bt.timer == nil {
  351. bt.timer = time.AfterFunc(bt.duration, bt.processTimeout)
  352. } else {
  353. bt.timer.Reset(bt.duration)
  354. }
  355. }
  356. func (bt *BrbTimer) processTimeout() {
  357. dead := false
  358. defer func() {
  359. if dead {
  360. bt.client.Quit(bt.client.AwayMessage(), nil)
  361. bt.client.destroy(nil)
  362. }
  363. }()
  364. bt.client.stateMutex.Lock()
  365. defer bt.client.stateMutex.Unlock()
  366. if bt.client.alwaysOn {
  367. return
  368. }
  369. switch bt.state {
  370. case BrbDisabled, BrbEnabled:
  371. if len(bt.client.sessions) == 0 {
  372. // client never returned, quit them
  373. bt.state = BrbDead
  374. dead = true
  375. } else {
  376. // client resumed, reattached, or has another active session
  377. bt.state = BrbDisabled
  378. bt.brbAt = time.Time{}
  379. }
  380. case BrbDead:
  381. dead = true // shouldn't be possible but whatever
  382. }
  383. bt.resetTimeout()
  384. }