conn.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. /*
  2. * Copyright (c) 2015, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. // Package transferstats counts and keeps track of session stats. These are
  20. // per-domain bytes transferred and total bytes transferred.
  21. package transferstats
  22. /*
  23. Assumption: The same connection will not be used to access different hostnames
  24. (even if, say, those hostnames map to the same server). If this does occur, we
  25. will mis-attribute some bytes.
  26. Assumption: Enough of the first HTTP will be present in the first Write() call
  27. for us to a) recognize that it is HTTP, and b) parse the hostname.
  28. - If this turns out to not be generally true we will need to add buffering.
  29. */
  30. import (
  31. "net"
  32. "sync/atomic"
  33. )
  34. // Conn is to be used as an intermediate link in a chain of net.Conn objects.
  35. // It inspects requests and responses and derives stats from them.
  36. type Conn struct {
  37. net.Conn
  38. serverID string
  39. firstWrite int32
  40. hostnameParsed int32
  41. hostname string
  42. regexps *Regexps
  43. }
  44. // NewConn creates a Conn. serverID can be anything that uniquely
  45. // identifies the server; it will be passed to TakeOutStatsForServer() when
  46. // retrieving the accumulated stats.
  47. func NewConn(nextConn net.Conn, serverID string, regexps *Regexps) *Conn {
  48. return &Conn{
  49. Conn: nextConn,
  50. serverID: serverID,
  51. firstWrite: 1,
  52. hostnameParsed: 0,
  53. regexps: regexps,
  54. }
  55. }
  56. func (conn *Conn) isRecordingHostBytes() bool {
  57. // When there are no regexes, no per-host bytes stats will be
  58. // recorded, including no "(OTHER)" category. In this case, it's
  59. // expected that there will be no data to send in any status
  60. // request.
  61. return conn.regexps != nil && len(*conn.regexps) > 0
  62. }
  63. // Write is called when requests are being written out through the tunnel to
  64. // the remote server.
  65. func (conn *Conn) Write(buffer []byte) (n int, err error) {
  66. // First pass the data down the chain.
  67. n, err = conn.Conn.Write(buffer)
  68. // Count stats before we check the error condition. It could happen that the
  69. // buffer was partially written and then an error occurred.
  70. if n > 0 {
  71. // If this is the first request, try to determine the hostname to associate
  72. // with this connection. Skip parsing when not recording per-host bytes, as
  73. // the hostname isn't used in this case.
  74. if conn.isRecordingHostBytes() && atomic.CompareAndSwapInt32(&conn.firstWrite, 1, 0) {
  75. hostname, ok := getHostname(buffer)
  76. if ok {
  77. // Get the hostname value that will be stored in stats by
  78. // regexing the real hostname.
  79. conn.hostname = regexHostname(hostname, conn.regexps)
  80. atomic.StoreInt32(&conn.hostnameParsed, 1)
  81. }
  82. }
  83. recordStat(&statsUpdate{
  84. conn.serverID,
  85. conn.hostname,
  86. int64(n),
  87. 0},
  88. conn.isRecordingHostBytes(),
  89. false)
  90. }
  91. return
  92. }
  93. // Read is called when responses to requests are being read from the remote server.
  94. func (conn *Conn) Read(buffer []byte) (n int, err error) {
  95. n, err = conn.Conn.Read(buffer)
  96. var hostname string
  97. if atomic.LoadInt32(&conn.hostnameParsed) == 1 {
  98. hostname = conn.hostname
  99. } else {
  100. hostname = ""
  101. }
  102. // Count bytes without checking the error condition. It could happen that the
  103. // buffer was partially read and then an error occurred.
  104. recordStat(&statsUpdate{
  105. conn.serverID,
  106. hostname,
  107. 0,
  108. int64(n)},
  109. conn.isRecordingHostBytes(),
  110. false)
  111. return
  112. }