padding_tamaraw.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. // padding_tamaraw.go - Tamaraw padding implementation.
  2. // Copyright (C) 2016 Yawning Angel.
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as
  6. // published by the Free Software Foundation, either version 3 of the
  7. // License, or (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. package basket2
  17. import (
  18. "bytes"
  19. "io"
  20. "net"
  21. "os"
  22. "sync"
  23. "time"
  24. "git.schwanenlied.me/yawning/basket2.git/framing"
  25. "git.schwanenlied.me/yawning/basket2.git/framing/tentp"
  26. "git.schwanenlied.me/yawning/basket2.git/internal/tcpinfo"
  27. )
  28. const (
  29. // PaddingTamaraw is an implementation of a variant of the Tamaraw
  30. // website fingerprinting defense as specified in "A Systematic
  31. // Approach to Developing and Evaluating Website Fingerprinting
  32. // Defenses", with some ideas taken from CS-BuFLO This method
  33. // should be avoided for "obfuscation" purposes as it is about
  34. // as subtle as going over to the DPI box and smashing it with
  35. // a brick, and guzzles bandwith like no tomorrow.
  36. //
  37. // Parameters are taken from Wang, T., "Website Fingerprinting:
  38. // Attacks and Defenses", and are tuned assuming the client is
  39. // primarily interested in things like web browsing, and that the
  40. // link MTU is 1500 bytes.
  41. PaddingTamaraw PaddingMethod = 0xf0
  42. )
  43. type tamarawPadding struct {
  44. sync.WaitGroup
  45. conn *commonConn
  46. fConn *os.File
  47. sendChan chan []byte
  48. lPpad int
  49. lSeg int
  50. rho int
  51. recvBuf bytes.Buffer
  52. }
  53. func (p *tamarawPadding) writeWorker() {
  54. defer p.Done()
  55. for {
  56. // Blocking channel read, since the write worker is idling (waiting
  57. // on next burst).
  58. b, ok := <-p.sendChan
  59. if !ok {
  60. // The send channel is closed, connection must be being torn down.
  61. break
  62. }
  63. if err := p.workerOnBurst(b); err != nil {
  64. // All errors are fatal, and no further writes are possible.
  65. break
  66. }
  67. }
  68. }
  69. func (p *tamarawPadding) workerOnBurst(b []byte) error {
  70. // CS-BuFLO uses 2 seconds, basket1 uses 250 ms... Not sure what the
  71. // best thing to do here is. Shorter is better for efficiency, but
  72. // I suspect this doesn't matter too much.
  73. const minIdleTime = 50 * time.Millisecond
  74. // Unblocked due to data entering the send channel, indicating the start
  75. // of a burst.
  76. nSegs := 0
  77. nPaddingSegs := 0
  78. canIdleAt := time.Now().Add(minIdleTime)
  79. sleepRho := func() {
  80. // Instead of scheduling packets at absolute intervals, draw from
  81. // the CS-BuFLO design and sample a random value [0, 2*rho), to
  82. // avoid leaking load information.
  83. //
  84. // CS-BuFLO uses an adaptive value for `rho`, which may be a good
  85. // idea...
  86. rho := time.Duration(p.conn.mRNG.Intn(2*p.rho)) * time.Microsecond
  87. time.Sleep(rho)
  88. }
  89. for {
  90. // Check channel capacity, if supported.
  91. //
  92. // Tamaraw will happily choke itself by clogging up the link with
  93. // padding, CS-BuFLO will start to back off when the send socket
  94. // buffer is full. Follow in the footsteps of the original basket
  95. // code and use TCP_INFO (or similar).
  96. if p.fConn != nil {
  97. linkCapacity, err := tcpinfo.EstimatedWriteCapacity(p.fConn)
  98. if err == nil {
  99. const hdrOverhead = 20 + 20 // Assuming IPv4 is prolly ok.
  100. const tentOverhead = tentp.FramingOverhead + tentp.PayloadOverhead
  101. if linkCapacity < hdrOverhead+tentOverhead+p.lPpad {
  102. // Either insufficient buffer space, or the link is
  103. // snd_cwnd bound. Writes now will just block/get
  104. // queued, so there's no point.
  105. sleepRho()
  106. continue
  107. }
  108. }
  109. }
  110. // Send the data (or pure padding), and update accounting.
  111. padLen := p.lPpad - len(b)
  112. if err := p.conn.SendRawRecord(framing.CmdData, b, padLen); err != nil {
  113. return err
  114. }
  115. if b == nil {
  116. nPaddingSegs++
  117. }
  118. nSegs++
  119. // Delay after the send.
  120. sleepRho()
  121. // Obtain further data from the channel.
  122. b = nil
  123. ok := false
  124. select {
  125. case b, ok = <-p.sendChan:
  126. canIdleAt = time.Now().Add(minIdleTime)
  127. case <-time.After(0):
  128. // Channel empty.
  129. if nPaddingSegs > 0 && (nSegs%p.lSeg == 0) && time.Now().After(canIdleAt) {
  130. // We have sent at least 1 segment of padding, are exactly at
  131. // a multiple of Lseg, and the channel has not provided us
  132. // with data to send for minIdleTime ms. Consider the burst
  133. // finished.
  134. return nil
  135. }
  136. ok = true
  137. }
  138. if !ok {
  139. // Channel is closed, return, and allow the caller to clean up.
  140. // XXX: Should I attempt to pad out the final burst?
  141. return io.EOF
  142. }
  143. }
  144. }
  145. func (p *tamarawPadding) Write(b []byte) (n int, err error) {
  146. // The OnClose() hook will close the sendChan, which is a problem
  147. // if we are in the packetization loop. Catch this case and
  148. // gracefully deal with it.
  149. defer func() {
  150. if r := recover(); r != nil {
  151. err = io.ErrShortWrite
  152. }
  153. }()
  154. // Break up the write into lPpad sized chunks.
  155. for toSend := len(b); toSend > 0; {
  156. wrLen := p.lPpad
  157. if wrLen > toSend {
  158. // Short is ok, the worker will pad it out.
  159. wrLen = toSend
  160. }
  161. frame := make([]byte, wrLen)
  162. copy(frame, b[n:n+wrLen])
  163. p.sendChan <- frame
  164. n += wrLen
  165. toSend -= wrLen
  166. }
  167. return
  168. }
  169. func (p *tamarawPadding) Read(b []byte) (int, error) {
  170. return paddingImplGenericRead(p.conn, &p.recvBuf, b)
  171. }
  172. func (p *tamarawPadding) OnClose() {
  173. p.recvBuf.Reset()
  174. // Close the send channel and wait for the worker to finish.
  175. if p.fConn != nil {
  176. p.fConn.Close()
  177. }
  178. close(p.sendChan)
  179. p.Wait()
  180. }
  181. func newTamarawPadding(conn *commonConn, isClient bool) paddingImpl {
  182. p := new(tamarawPadding)
  183. p.conn = conn
  184. p.sendChan = make(chan []byte, 64)
  185. if tConn, ok := (conn.rawConn).(*net.TCPConn); ok {
  186. if fConn, err := tConn.File(); err == nil {
  187. p.fConn = fConn
  188. }
  189. }
  190. p.conn.enforceRecordSize = true
  191. // The thesis that evaluates this suggests:
  192. //
  193. // Client: rho: 20 ms, l ppad: 800 bytes, Lseg: 500 segments
  194. // Server: rho: 5 ms, l ppad: 1500 bytes, Lseg: 500 segments
  195. //
  196. // The l ppad numbers were chosed for a non-tor data set, which is
  197. // a poor value for basket2 given that Tor for the most part uses
  198. // fixed length cells.
  199. //
  200. // Lseg = 100 gives a maximum attacker accuracy of 0.59, while 500
  201. // reduces that to ~0.35.
  202. //
  203. if isClient {
  204. // Tune for "short infrequent bursts".
  205. //
  206. // The CS-BuFLO's early termination feature suggests that the tail
  207. // end of the padding doesn't gain much, so lowering Lseg may be
  208. // acceptable.
  209. p.rho = 20 * 1000 // ms -> usec
  210. p.lPpad = 543 // Tuned for a single Tor cell in a TLS record.
  211. p.lSeg = 100
  212. } else {
  213. // Tune for "bulk data transfer".
  214. p.rho = 5 * 1000 // ms -> usec
  215. p.lPpad = p.conn.maxRecordSize // Could lower it by 2 for PPPoE links.
  216. p.lSeg = 100
  217. // Random read side delivery jitter.
  218. p.conn.enableReadDelay = true
  219. }
  220. p.Add(1)
  221. go p.writeWorker()
  222. return p
  223. }