padding_tamaraw.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. // PaddingTamarawBulk is a variant of PaddingTamaraw with parameters
  43. // that are tuned for bulk transport in both directions.
  44. PaddingTamarawBulk PaddingMethod = 0xf1
  45. )
  46. type tamarawPadding struct {
  47. sync.WaitGroup
  48. conn *commonConn
  49. fConn *os.File
  50. sendChan chan []byte
  51. lPpad int
  52. lSeg int
  53. rho int
  54. recvBuf bytes.Buffer
  55. }
  56. func (p *tamarawPadding) writeWorker() {
  57. defer p.Done()
  58. for {
  59. // Blocking channel read, since the write worker is idling (waiting
  60. // on next burst).
  61. b, ok := <-p.sendChan
  62. if !ok {
  63. // The send channel is closed, connection must be being torn down.
  64. break
  65. }
  66. if err := p.workerOnBurst(b); err != nil {
  67. // All errors are fatal, and no further writes are possible.
  68. break
  69. }
  70. }
  71. }
  72. func (p *tamarawPadding) workerOnBurst(b []byte) error {
  73. // CS-BuFLO uses 2 seconds, basket1 uses 250 ms... Not sure what the
  74. // best thing to do here is. Shorter is better for efficiency, but
  75. // I suspect this doesn't matter too much.
  76. const minIdleTime = 50 * time.Millisecond
  77. // Unblocked due to data entering the send channel, indicating the start
  78. // of a burst.
  79. nSegs := 0
  80. nPaddingSegs := 0
  81. canIdleAt := time.Now().Add(minIdleTime)
  82. sleepRho := func() {
  83. // Instead of scheduling packets at absolute intervals, draw from
  84. // the CS-BuFLO design and sample a random value [0, 2*rho), to
  85. // avoid leaking load information.
  86. //
  87. // CS-BuFLO uses an adaptive value for `rho`, which may be a good
  88. // idea...
  89. rho := time.Duration(p.conn.mRNG.Intn(2*p.rho)) * time.Microsecond
  90. time.Sleep(rho)
  91. }
  92. for {
  93. // Check channel capacity, if supported.
  94. //
  95. // Tamaraw will happily choke itself by clogging up the link with
  96. // padding, CS-BuFLO will start to back off when the send socket
  97. // buffer is full. Follow in the footsteps of the original basket
  98. // code and use TCP_INFO (or similar).
  99. if p.fConn != nil {
  100. linkCapacity, err := tcpinfo.EstimatedWriteCapacity(p.fConn)
  101. if err == nil {
  102. const hdrOverhead = 20 + 20 // Assuming IPv4 is prolly ok.
  103. const tentOverhead = tentp.FramingOverhead + tentp.PayloadOverhead
  104. if linkCapacity < hdrOverhead+tentOverhead+p.lPpad {
  105. // Either insufficient buffer space, or the link is
  106. // snd_cwnd bound. Writes now will just block/get
  107. // queued, so there's no point.
  108. sleepRho()
  109. continue
  110. }
  111. }
  112. }
  113. // Send the data (or pure padding), and update accounting.
  114. padLen := p.lPpad - len(b)
  115. if err := p.conn.SendRawRecord(framing.CmdData, b, padLen); err != nil {
  116. return err
  117. }
  118. if b == nil {
  119. nPaddingSegs++
  120. }
  121. nSegs++
  122. // Delay after the send.
  123. sleepRho()
  124. // Obtain further data from the channel.
  125. b = nil
  126. ok := false
  127. select {
  128. case b, ok = <-p.sendChan:
  129. canIdleAt = time.Now().Add(minIdleTime)
  130. case <-time.After(0):
  131. // Channel empty.
  132. if nPaddingSegs > 0 && (nSegs%p.lSeg == 0) && time.Now().After(canIdleAt) {
  133. // We have sent at least 1 segment of padding, are exactly at
  134. // a multiple of Lseg, and the channel has not provided us
  135. // with data to send for minIdleTime ms. Consider the burst
  136. // finished.
  137. return nil
  138. }
  139. ok = true
  140. }
  141. if !ok {
  142. // Channel is closed, return, and allow the caller to clean up.
  143. // XXX: Should I attempt to pad out the final burst?
  144. return io.EOF
  145. }
  146. }
  147. }
  148. func (p *tamarawPadding) Write(b []byte) (n int, err error) {
  149. // The OnClose() hook will close the sendChan, which is a problem
  150. // if we are in the packetization loop. Catch this case and
  151. // gracefully deal with it.
  152. defer func() {
  153. if r := recover(); r != nil {
  154. err = io.ErrShortWrite
  155. }
  156. }()
  157. // Break up the write into lPpad sized chunks.
  158. for toSend := len(b); toSend > 0; {
  159. wrLen := p.lPpad
  160. if wrLen > toSend {
  161. // Short is ok, the worker will pad it out.
  162. wrLen = toSend
  163. }
  164. frame := make([]byte, wrLen)
  165. copy(frame, b[n:n+wrLen])
  166. p.sendChan <- frame
  167. n += wrLen
  168. toSend -= wrLen
  169. }
  170. return
  171. }
  172. func (p *tamarawPadding) Read(b []byte) (int, error) {
  173. return paddingImplGenericRead(p.conn, &p.recvBuf, b)
  174. }
  175. func (p *tamarawPadding) OnClose() {
  176. p.recvBuf.Reset()
  177. // Close the send channel and wait for the worker to finish.
  178. if p.fConn != nil {
  179. p.fConn.Close()
  180. }
  181. close(p.sendChan)
  182. p.Wait()
  183. }
  184. func newTamarawPadding(conn *commonConn, method PaddingMethod, isClient bool) paddingImpl {
  185. p := new(tamarawPadding)
  186. p.conn = conn
  187. p.sendChan = make(chan []byte, 64)
  188. if tConn, ok := (conn.rawConn).(*net.TCPConn); ok {
  189. if fConn, err := tConn.File(); err == nil {
  190. p.fConn = fConn
  191. }
  192. }
  193. p.conn.enforceRecordSize = true
  194. p.conn.enableReadDelay = true
  195. // The thesis that evaluates this suggests:
  196. //
  197. // Client: rho: 20 ms, l ppad: 800 bytes, Lseg: 500 segments
  198. // Server: rho: 5 ms, l ppad: 1500 bytes, Lseg: 500 segments
  199. //
  200. // The l ppad numbers were chosed for a non-tor data set, which is
  201. // a poor value for basket2 given that Tor for the most part uses
  202. // fixed length cells.
  203. //
  204. // Lseg = 100 gives a maximum attacker accuracy of 0.59, while 500
  205. // reduces that to ~0.35.
  206. if isClient && method != PaddingTamarawBulk {
  207. // Tune for "short infrequent bursts".
  208. //
  209. // The CS-BuFLO's early termination feature suggests that the tail
  210. // end of the padding doesn't gain much, so lowering Lseg may be
  211. // acceptable.
  212. p.rho = 20 * 1000 // ms -> usec
  213. p.lPpad = 543 // Tuned for a single Tor cell in a TLS record.
  214. p.lSeg = 100
  215. } else {
  216. // Tune for "bulk data transfer".
  217. p.rho = 5 * 1000 // ms -> usec
  218. p.lPpad = p.conn.maxRecordSize // Could lower it by 2 for PPPoE links.
  219. p.lSeg = 100
  220. // Server side specific tunables.
  221. if !isClient && method == PaddingTamaraw {
  222. // Clamp acceptable packets to the client side lPpad value.
  223. p.conn.maxRecordSize = 543
  224. }
  225. }
  226. p.Add(1)
  227. go p.writeWorker()
  228. return p
  229. }