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.

proxy.go 4.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. // Copyright (c) 2020 Shivaram Lingamneni <slingamn@cs.stanford.edu>
  2. // released under the MIT license
  3. package utils
  4. import (
  5. "crypto/tls"
  6. "errors"
  7. "net"
  8. "strings"
  9. "sync"
  10. "time"
  11. )
  12. // TODO: handle PROXY protocol v2 (the binary protocol)
  13. const (
  14. // https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
  15. // "a 108-byte buffer is always enough to store all the line and a trailing zero
  16. // for string processing."
  17. maxProxyLineLen = 107
  18. )
  19. var (
  20. ErrBadProxyLine = errors.New("invalid PROXY line")
  21. // TODO(golang/go#4373): replace this with the stdlib ErrNetClosing
  22. ErrNetClosing = errors.New("use of closed network connection")
  23. )
  24. // ListenerConfig is all the information about how to process
  25. // incoming IRC connections on a listener.
  26. type ListenerConfig struct {
  27. TLSConfig *tls.Config
  28. ProxyDeadline time.Duration
  29. RequireProxy bool
  30. // these are just metadata for easier tracking,
  31. // they are not used by ReloadableListener:
  32. Tor bool
  33. STSOnly bool
  34. WebSocket bool
  35. }
  36. // read a PROXY line one byte at a time, to ensure we don't read anything beyond
  37. // that into a buffer, which would break the TLS handshake
  38. func readRawProxyLine(conn net.Conn, deadline time.Duration) (result string) {
  39. // normally this is covered by ping timeouts, but we're doing this outside
  40. // of the normal client goroutine:
  41. conn.SetDeadline(time.Now().Add(deadline))
  42. defer conn.SetDeadline(time.Time{})
  43. var buf [maxProxyLineLen]byte
  44. oneByte := make([]byte, 1)
  45. i := 0
  46. for i < maxProxyLineLen {
  47. n, err := conn.Read(oneByte)
  48. if err != nil {
  49. return
  50. } else if n == 1 {
  51. buf[i] = oneByte[0]
  52. if buf[i] == '\n' {
  53. candidate := string(buf[0 : i+1])
  54. if strings.HasPrefix(candidate, "PROXY") {
  55. return candidate
  56. } else {
  57. return
  58. }
  59. }
  60. i += 1
  61. }
  62. }
  63. // no \r\n, fail out
  64. return
  65. }
  66. // ParseProxyLine parses a PROXY protocol (v1) line and returns the remote IP.
  67. func ParseProxyLine(line string) (ip net.IP, err error) {
  68. params := strings.Fields(line)
  69. if len(params) != 6 || params[0] != "PROXY" {
  70. return nil, ErrBadProxyLine
  71. }
  72. ip = net.ParseIP(params[2])
  73. if ip == nil {
  74. return nil, ErrBadProxyLine
  75. }
  76. return ip.To16(), nil
  77. }
  78. /// WrappedConn is a net.Conn with some additional data stapled to it;
  79. // the proxied IP, if one was read via the PROXY protocol, and the listener
  80. // configuration.
  81. type WrappedConn struct {
  82. net.Conn
  83. ProxiedIP net.IP
  84. Config ListenerConfig
  85. }
  86. // ReloadableListener is a wrapper for net.Listener that allows reloading
  87. // of config data for postprocessing connections (TLS, PROXY protocol, etc.)
  88. type ReloadableListener struct {
  89. // TODO: make this lock-free
  90. sync.Mutex
  91. realListener net.Listener
  92. config ListenerConfig
  93. isClosed bool
  94. }
  95. func NewReloadableListener(realListener net.Listener, config ListenerConfig) *ReloadableListener {
  96. return &ReloadableListener{
  97. realListener: realListener,
  98. config: config,
  99. }
  100. }
  101. func (rl *ReloadableListener) Reload(config ListenerConfig) {
  102. rl.Lock()
  103. rl.config = config
  104. rl.Unlock()
  105. }
  106. func (rl *ReloadableListener) Accept() (conn net.Conn, err error) {
  107. conn, err = rl.realListener.Accept()
  108. rl.Lock()
  109. config := rl.config
  110. isClosed := rl.isClosed
  111. rl.Unlock()
  112. if isClosed {
  113. if err == nil {
  114. conn.Close()
  115. }
  116. err = ErrNetClosing
  117. }
  118. if err != nil {
  119. return nil, err
  120. }
  121. var proxiedIP net.IP
  122. if config.RequireProxy {
  123. // this will occur synchronously on the goroutine calling Accept(),
  124. // but that's OK because this listener *requires* a PROXY line,
  125. // therefore it must be used with proxies that always send the line
  126. // and we won't get slowloris'ed waiting for the client response
  127. proxyLine := readRawProxyLine(conn, config.ProxyDeadline)
  128. proxiedIP, err = ParseProxyLine(proxyLine)
  129. if err != nil {
  130. conn.Close()
  131. return nil, err
  132. }
  133. }
  134. if config.TLSConfig != nil {
  135. conn = tls.Server(conn, config.TLSConfig)
  136. }
  137. return &WrappedConn{
  138. Conn: conn,
  139. ProxiedIP: proxiedIP,
  140. Config: config,
  141. }, nil
  142. }
  143. func (rl *ReloadableListener) Close() error {
  144. rl.Lock()
  145. rl.isClosed = true
  146. rl.Unlock()
  147. return rl.realListener.Close()
  148. }
  149. func (rl *ReloadableListener) Addr() net.Addr {
  150. return rl.realListener.Addr()
  151. }