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 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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. "time"
  8. "github.com/oragono/oragono/irc/caps"
  9. )
  10. const (
  11. // RegisterTimeout is how long clients have to register before we disconnect them
  12. RegisterTimeout = time.Minute
  13. // IdleTimeout is how long without traffic before a registered client is considered idle.
  14. IdleTimeout = time.Minute + time.Second*30
  15. // IdleTimeoutWithResumeCap is how long without traffic before a registered client is considered idle, when they have the resume capability.
  16. IdleTimeoutWithResumeCap = time.Minute*2 + time.Second*30
  17. // QuitTimeout is how long without traffic before an idle client is disconnected
  18. QuitTimeout = time.Minute
  19. )
  20. // client idleness state machine
  21. type TimerState uint
  22. const (
  23. TimerUnregistered TimerState = iota // client is unregistered
  24. TimerActive // client is actively sending commands
  25. TimerIdle // client is idle, we sent PING and are waiting for PONG
  26. TimerDead // client was terminated
  27. )
  28. type IdleTimer struct {
  29. sync.Mutex // tier 1
  30. // immutable after construction
  31. registerTimeout time.Duration
  32. quitTimeout time.Duration
  33. client *Client
  34. // mutable
  35. idleTimeout time.Duration
  36. state TimerState
  37. timer *time.Timer
  38. }
  39. // NewIdleTimer sets up a new IdleTimer using constant timeouts.
  40. func NewIdleTimer(client *Client) *IdleTimer {
  41. it := IdleTimer{
  42. registerTimeout: RegisterTimeout,
  43. idleTimeout: IdleTimeout,
  44. quitTimeout: QuitTimeout,
  45. client: client,
  46. }
  47. return &it
  48. }
  49. // updateIdleDuration updates the idle duration, given the client's caps.
  50. func (it *IdleTimer) updateIdleDuration() {
  51. newIdleTime := IdleTimeout
  52. // if they have the resume cap, wait longer before pinging them out
  53. // to give them a chance to resume their connection
  54. if it.client.capabilities.Has(caps.Resume) {
  55. newIdleTime = IdleTimeoutWithResumeCap
  56. }
  57. it.Lock()
  58. defer it.Unlock()
  59. it.idleTimeout = newIdleTime
  60. }
  61. // Start starts counting idle time; if there is no activity from the client,
  62. // it will eventually be stopped.
  63. func (it *IdleTimer) Start() {
  64. it.Lock()
  65. defer it.Unlock()
  66. it.state = TimerUnregistered
  67. it.resetTimeout()
  68. }
  69. func (it *IdleTimer) Touch() {
  70. // ignore touches from unregistered clients
  71. if !it.client.Registered() {
  72. return
  73. }
  74. it.updateIdleDuration()
  75. it.Lock()
  76. defer it.Unlock()
  77. // a touch transitions TimerUnregistered or TimerIdle into TimerActive
  78. if it.state != TimerDead {
  79. it.state = TimerActive
  80. it.resetTimeout()
  81. }
  82. }
  83. func (it *IdleTimer) processTimeout() {
  84. it.updateIdleDuration()
  85. var previousState TimerState
  86. func() {
  87. it.Lock()
  88. defer it.Unlock()
  89. previousState = it.state
  90. // TimerActive transitions to TimerIdle, all others to TimerDead
  91. if it.state == TimerActive {
  92. // send them a ping, give them time to respond
  93. it.state = TimerIdle
  94. it.resetTimeout()
  95. } else {
  96. it.state = TimerDead
  97. }
  98. }()
  99. if previousState == TimerActive {
  100. it.client.Ping()
  101. } else {
  102. it.client.Quit(it.quitMessage(previousState))
  103. it.client.destroy(false)
  104. }
  105. }
  106. // Stop stops counting idle time.
  107. func (it *IdleTimer) Stop() {
  108. if it == nil {
  109. return
  110. }
  111. it.Lock()
  112. defer it.Unlock()
  113. it.state = TimerDead
  114. it.resetTimeout()
  115. }
  116. func (it *IdleTimer) resetTimeout() {
  117. if it.timer != nil {
  118. it.timer.Stop()
  119. }
  120. var nextTimeout time.Duration
  121. switch it.state {
  122. case TimerUnregistered:
  123. nextTimeout = it.registerTimeout
  124. case TimerActive:
  125. nextTimeout = it.idleTimeout
  126. case TimerIdle:
  127. nextTimeout = it.quitTimeout
  128. case TimerDead:
  129. return
  130. }
  131. it.timer = time.AfterFunc(nextTimeout, it.processTimeout)
  132. }
  133. func (it *IdleTimer) quitMessage(state TimerState) string {
  134. switch state {
  135. case TimerUnregistered:
  136. return fmt.Sprintf("Registration timeout: %v", it.registerTimeout)
  137. case TimerIdle:
  138. // how many seconds before registered clients are timed out (IdleTimeout plus QuitTimeout).
  139. it.Lock()
  140. defer it.Unlock()
  141. return fmt.Sprintf("Ping timeout: %v", (it.idleTimeout + it.quitTimeout))
  142. default:
  143. // shouldn't happen
  144. return ""
  145. }
  146. }
  147. // NickTimer manages timing out of clients who are squatting reserved nicks
  148. type NickTimer struct {
  149. sync.Mutex // tier 1
  150. // immutable after construction
  151. timeout time.Duration
  152. client *Client
  153. // mutable
  154. stopped bool
  155. nick string
  156. accountForNick string
  157. account string
  158. timer *time.Timer
  159. }
  160. // NewNickTimer sets up a new nick timer (returning nil if timeout enforcement is not enabled)
  161. func NewNickTimer(client *Client) *NickTimer {
  162. config := client.server.AccountConfig().NickReservation
  163. if !(config.Enabled && config.Method == NickReservationWithTimeout) {
  164. return nil
  165. }
  166. nt := NickTimer{
  167. client: client,
  168. timeout: config.RenameTimeout,
  169. }
  170. return &nt
  171. }
  172. // Touch records a nick change and updates the timer as necessary
  173. func (nt *NickTimer) Touch() {
  174. if nt == nil {
  175. return
  176. }
  177. nick := nt.client.NickCasefolded()
  178. account := nt.client.Account()
  179. accountForNick := nt.client.server.accounts.NickToAccount(nick)
  180. var shouldWarn bool
  181. func() {
  182. nt.Lock()
  183. defer nt.Unlock()
  184. if nt.stopped {
  185. return
  186. }
  187. // the timer will not reset as long as the squatter is targeting the same account
  188. accountChanged := accountForNick != nt.accountForNick
  189. // change state
  190. nt.nick = nick
  191. nt.account = account
  192. nt.accountForNick = accountForNick
  193. delinquent := accountForNick != "" && accountForNick != account
  194. if nt.timer != nil && (!delinquent || accountChanged) {
  195. nt.timer.Stop()
  196. nt.timer = nil
  197. }
  198. if delinquent && accountChanged {
  199. nt.timer = time.AfterFunc(nt.timeout, nt.processTimeout)
  200. shouldWarn = true
  201. }
  202. }()
  203. if shouldWarn {
  204. nt.sendWarning()
  205. }
  206. }
  207. // Stop stops counting time and cleans up the timer
  208. func (nt *NickTimer) Stop() {
  209. if nt == nil {
  210. return
  211. }
  212. nt.Lock()
  213. defer nt.Unlock()
  214. if nt.timer != nil {
  215. nt.timer.Stop()
  216. nt.timer = nil
  217. }
  218. nt.stopped = true
  219. }
  220. func (nt *NickTimer) sendWarning() {
  221. baseNotice := "Nickname is reserved; you must change it or authenticate to NickServ within %v"
  222. nt.client.Notice(fmt.Sprintf(nt.client.t(baseNotice), nt.timeout))
  223. }
  224. func (nt *NickTimer) processTimeout() {
  225. baseMsg := "Nick is reserved and authentication timeout expired: %v"
  226. nt.client.Notice(fmt.Sprintf(nt.client.t(baseMsg), nt.timeout))
  227. nt.client.server.RandomlyRename(nt.client)
  228. }