buildInjection constructs a fresh IPv4+TCP packet that mirrors the observed ACK closing the handshake but replaces the payload, sequence number, IP ident, and TCP flags per injectSpec. Returns a fully assembled packet. The IP checksum is always valid; the TCP checksum is valid unless spec.corruptCh
(ack pkt, spec injectSpec)
| 122 | // is XOR'd with a fixed nonzero constant so the destination host drops the |
| 123 | // packet while a checksum-agnostic DPI still parses it. |
| 124 | func buildInjection(ack pkt, spec injectSpec) []byte { |
| 125 | seqNum := spec.seqNum |
| 126 | ipIDDelta := spec.ipIDDelta |
| 127 | setPSH := spec.setPSH |
| 128 | payload := spec.payload |
| 129 | // Minimal IPv4 (20 bytes) + minimal TCP (20 bytes) + payload. We |
| 130 | // deliberately drop TCP options from the injected packet — they are not |
| 131 | // required for DPI to parse the SNI and keep the packet small. |
| 132 | const ipHL, tcpHL = 20, 20 |
| 133 | total := ipHL + tcpHL + len(payload) |
| 134 | out := make([]byte, total) |
| 135 | |
| 136 | // IPv4 header. |
| 137 | out[0] = 0x45 // version 4, IHL 5 |
| 138 | out[1] = 0x00 // DSCP/ECN |
| 139 | binary.BigEndian.PutUint16(out[2:4], uint16(total)) |
| 140 | binary.BigEndian.PutUint16(out[4:6], ack.ident+ipIDDelta) |
| 141 | binary.BigEndian.PutUint16(out[6:8], 0x4000) // DF, no fragment |
| 142 | out[8] = 64 // TTL |
| 143 | out[9] = 6 // protocol TCP |
| 144 | // checksum at 10..12 left zero for now |
| 145 | copy(out[12:16], ack.srcIP[:]) |
| 146 | copy(out[16:20], ack.dstIP[:]) |
| 147 | ipCsum := checksum(out[0:ipHL]) |
| 148 | binary.BigEndian.PutUint16(out[10:12], ipCsum) |
| 149 | |
| 150 | // TCP header. |
| 151 | tcp := out[ipHL:] |
| 152 | binary.BigEndian.PutUint16(tcp[0:2], ack.srcPort) |
| 153 | binary.BigEndian.PutUint16(tcp[2:4], ack.dstPort) |
| 154 | binary.BigEndian.PutUint32(tcp[4:8], seqNum) |
| 155 | binary.BigEndian.PutUint32(tcp[8:12], ack.ackNum) |
| 156 | tcp[12] = (tcpHL / 4) << 4 |
| 157 | flags := uint8(flagACK) |
| 158 | if setPSH { |
| 159 | flags |= flagPSH |
| 160 | } |
| 161 | tcp[13] = flags |
| 162 | binary.BigEndian.PutUint16(tcp[14:16], 0x0800) // window — matches common Linux default |
| 163 | // checksum placeholder at 16:18 |
| 164 | // urgent pointer at 18:20 = 0 |
| 165 | copy(tcp[tcpHL:], payload) |
| 166 | |
| 167 | // TCP checksum: pseudo-header + TCP header + payload. |
| 168 | var psh [12]byte |
| 169 | copy(psh[0:4], ack.srcIP[:]) |
| 170 | copy(psh[4:8], ack.dstIP[:]) |
| 171 | psh[8] = 0 |
| 172 | psh[9] = 6 |
| 173 | binary.BigEndian.PutUint16(psh[10:12], uint16(tcpHL+len(payload))) |
| 174 | |
| 175 | tcpCsum := checksumMany(psh[:], tcp[:tcpHL+len(payload)]) |
| 176 | if spec.corruptChecksum { |
| 177 | // Flip several bits so recomputation by an aggressive middlebox can't |
| 178 | // accidentally match. 0xBEEF is distinctive in captures for debugging. |
| 179 | tcpCsum ^= 0xBEEF |
| 180 | if tcpCsum == 0 { |
| 181 | tcpCsum = 0x1234 // 0 means "no checksum"; avoid it. |