Move out UDP obfuscator to a separate class
This commit is contained in:
parent
6a55ec778b
commit
6e09191bf5
9 changed files with 157 additions and 74 deletions
|
@ -480,6 +480,9 @@ bool UdpSocketBsd::DoRead() {
|
|||
read_packet->size = r;
|
||||
read_packet->protocol = kPacketProtocolUdp;
|
||||
network_->read_packet_ = NULL;
|
||||
|
||||
if (processor_->dev().packet_obfuscator().enabled())
|
||||
processor_->dev().packet_obfuscator().DeobfuscatePacket(read_packet);
|
||||
processor_->HandleUdpPacket(read_packet, network_->overload_);
|
||||
return true;
|
||||
} else {
|
||||
|
|
|
@ -961,6 +961,13 @@ void PacketProcessorUdpCb::OnQueuedItemDelete(QueuedItem *qi) {
|
|||
FreePacket(static_cast<Packet*>(qi));
|
||||
}
|
||||
|
||||
void PacketProcessorDeobfuscateUdpCb::OnQueuedItemEvent(QueuedItem *qi, uintptr_t extra) {
|
||||
PacketProcessor::QueueContext *context = (PacketProcessor::QueueContext *)extra;
|
||||
Packet *packet = static_cast<Packet*>(qi);
|
||||
context->wg->dev().packet_obfuscator().DeobfuscatePacket(packet);
|
||||
PacketProcessorUdpCb::OnQueuedItemEvent(qi, extra);
|
||||
}
|
||||
|
||||
void PacketProcessor::PostExit(int exit_code) {
|
||||
mutex_.Acquire();
|
||||
// Avoid race condition where mode_tun_failed is set during thread exit.
|
||||
|
|
|
@ -30,6 +30,10 @@ struct PacketProcessorUdpCb : QueuedItemCallback {
|
|||
virtual void OnQueuedItemDelete(QueuedItem *ow) override;
|
||||
};
|
||||
|
||||
struct PacketProcessorDeobfuscateUdpCb : PacketProcessorUdpCb {
|
||||
virtual void OnQueuedItemEvent(QueuedItem *ow, uintptr_t extra) override;
|
||||
};
|
||||
|
||||
class PacketProcessor {
|
||||
public:
|
||||
explicit PacketProcessor();
|
||||
|
@ -41,11 +45,20 @@ public:
|
|||
void PostPackets(Packet *first, Packet **end, int count);
|
||||
void ForcePost(QueuedItem *item);
|
||||
void PostExit(int exit_code);
|
||||
void EnableDeobfuscation() {
|
||||
udp_cb_maybe_deobfuscate_ = &udp_cb_deobfuscate_;
|
||||
}
|
||||
|
||||
const uint32 *posted_exit_code() { return &exit_code_; }
|
||||
|
||||
// Handler for tun packets
|
||||
QueuedItemCallback *tun_queue() { return &tun_cb_; }
|
||||
QueuedItemCallback *udp_queue() { return &udp_cb_; }
|
||||
|
||||
// Handler for udp packets
|
||||
QueuedItemCallback *udp_queue() { return udp_cb_maybe_deobfuscate_; }
|
||||
|
||||
// Incoming queue for tcp packets that do not use deobfuscation
|
||||
QueuedItemCallback *tcp_queue() { return &udp_cb_; }
|
||||
|
||||
struct QueueContext {
|
||||
WireguardProcessor *wg;
|
||||
|
@ -65,8 +78,11 @@ private:
|
|||
uint32 exit_code_;
|
||||
bool timer_interrupt_;
|
||||
|
||||
QueuedItemCallback *udp_cb_maybe_deobfuscate_;
|
||||
|
||||
PacketProcessorTunCb tun_cb_;
|
||||
PacketProcessorUdpCb udp_cb_;
|
||||
PacketProcessorDeobfuscateUdpCb udp_cb_deobfuscate_;
|
||||
};
|
||||
|
||||
class NetworkWin32;
|
||||
|
|
|
@ -773,6 +773,8 @@ void TunsafeBackendBsdImpl::WriteUdpPacket(Packet *packet) {
|
|||
if (packet->protocol & kPacketProtocolTcp) {
|
||||
WriteTcpPacket(packet);
|
||||
} else {
|
||||
if (processor_.dev().packet_obfuscator().enabled())
|
||||
processor_.dev().packet_obfuscator().ObfuscatePacket(packet);
|
||||
udp_.WritePacket(packet);
|
||||
}
|
||||
}
|
||||
|
@ -857,7 +859,6 @@ void TunsafeBackendBsdImpl::CloseOrphanTcpConnections() {
|
|||
for(const auto &it : lookup)
|
||||
delete (TcpSocketBsd *)it.second;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
CommandLineOutput cmd = {0};
|
||||
|
||||
|
|
|
@ -95,10 +95,6 @@ void WireguardProcessor::SetInternetBlocking(InternetBlockState internet_blockin
|
|||
internet_blocking_ = internet_blocking;
|
||||
}
|
||||
|
||||
void WireguardProcessor::SetHeaderObfuscation(const char *key) {
|
||||
dev_.SetHeaderObfuscation(key);
|
||||
}
|
||||
|
||||
const WgProcessorStats &WireguardProcessor::GetStats() {
|
||||
// todo: only supports one peer but i want this in the ui for now.
|
||||
stats_.endpoint.sin.sin_family = 0;
|
||||
|
@ -573,42 +569,10 @@ getout_discard:
|
|||
return kPacketResult_Free;
|
||||
}
|
||||
|
||||
// This scrambles the initial 16 bytes of the packet with the
|
||||
// next 8 bytes of the packet as a seed.
|
||||
static void ScrambleUnscramblePacket(Packet *packet, ScramblerSiphashKeys *keys) {
|
||||
uint8 *data = packet->data;
|
||||
size_t data_size = packet->size;
|
||||
|
||||
if (data_size <= 8)
|
||||
return;
|
||||
|
||||
uint64 last_uint64 = ReadLE64(data_size >= 24 ? data + 16 : data + data_size - 8);
|
||||
uint64 a = siphash_u64_u32(last_uint64, (uint32)data_size, (siphash_key_t*)&keys->keys[0]);
|
||||
uint64 b = siphash_u64_u32(last_uint64, (uint32)data_size, (siphash_key_t*)&keys->keys[2]);
|
||||
a = ToLE64(a);
|
||||
b = ToLE64(b);
|
||||
if (data_size >= 24) {
|
||||
((uint64*)data)[0] ^= a;
|
||||
((uint64*)data)[1] ^= b;
|
||||
} else {
|
||||
union {
|
||||
uint64 d[2];
|
||||
uint8 s[16];
|
||||
} scrambler = {{a,b}};
|
||||
for (size_t i = 0; i < data_size - 8; i++)
|
||||
data[i] ^= scrambler.s[i];
|
||||
}
|
||||
}
|
||||
|
||||
void WireguardProcessor::PrepareOutgoingHandshakePacket(WgPeer *peer, Packet *packet) {
|
||||
assert(dev_.IsMainThread());
|
||||
|
||||
stats_.udp_packets_out++;
|
||||
stats_.udp_bytes_out += packet->size;
|
||||
#if WITH_HEADER_OBFUSCATION
|
||||
if (dev_.header_obfuscation_)
|
||||
ScrambleUnscramblePacket(packet, &dev_.header_obfuscation_key_);
|
||||
#endif // WITH_HEADER_OBFUSCATION
|
||||
}
|
||||
|
||||
void WireguardProcessor::RunAllMainThreadScheduled() {
|
||||
|
@ -694,12 +658,6 @@ WireguardProcessor::PacketResult WireguardProcessor::HandleUdpPacket2(Packet *pa
|
|||
uint32 type;
|
||||
assert(packet->protocol != 0xCD && (uint16)packet->addr.sin.sin_family != 0xCDCD); // catch msvc uninit mem
|
||||
|
||||
// Unscramble incoming packets
|
||||
#if WITH_HEADER_OBFUSCATION
|
||||
if (dev_.header_obfuscation_)
|
||||
ScrambleUnscramblePacket(packet, &dev_.header_obfuscation_key_);
|
||||
#endif // WITH_HEADER_OBFUSCATION
|
||||
|
||||
stats_.udp_bytes_in += packet->size;
|
||||
stats_.udp_packets_in++;
|
||||
|
||||
|
|
|
@ -91,7 +91,6 @@ public:
|
|||
void SetAddRoutesMode(bool mode);
|
||||
void SetDnsBlocking(bool dns_blocking);
|
||||
void SetInternetBlocking(InternetBlockState internet_blocking);
|
||||
void SetHeaderObfuscation(const char *key);
|
||||
|
||||
void HandleTunPacket(Packet *packet);
|
||||
void HandleUdpPacket(Packet *packet, bool overload);
|
||||
|
|
|
@ -170,7 +170,7 @@ bool WgFileParser::ParseFlag(const char *group, const char *key, char *value) {
|
|||
|
||||
wg_->SetInternetBlocking((InternetBlockState)v);
|
||||
} else if (strcmp(key, "HeaderObfuscation") == 0) {
|
||||
wg_->SetHeaderObfuscation(value);
|
||||
wg_->dev().packet_obfuscator().SetKey((uint8*)value, strlen(value));
|
||||
} else if (strcmp(key, "PostUp") == 0) {
|
||||
wg_->prepost().post_up.emplace_back(value);
|
||||
} else if (strcmp(key, "PostDown") == 0) {
|
||||
|
|
|
@ -55,7 +55,6 @@ WgDevice::WgDevice() {
|
|||
peers_ = NULL;
|
||||
last_peer_ptr_ = &peers_;
|
||||
plugin_ = NULL;
|
||||
header_obfuscation_ = false;
|
||||
is_private_key_initialized_ = false;
|
||||
next_rng_slot_ = 0;
|
||||
main_thread_scheduled_ = NULL;
|
||||
|
@ -331,18 +330,6 @@ void WgDevice::UpdateKeypairAddrEntry_Locked(const IpAddr &addr, WgKeypair *keyp
|
|||
keypair->broadcast_short_key = 1;
|
||||
}
|
||||
|
||||
//>> > hashlib.sha256('TunSafe Header Obfuscation Key').hexdigest()
|
||||
//'2444423e33eb5bb875961224c6441f54c5dea95a3a4e1139509ffa6992bdb278'
|
||||
static const uint8 kHeaderObfuscationKey[32] = {36, 68, 66, 62, 51, 235, 91, 184, 117, 150, 18, 36, 198, 68, 31, 84, 197, 222, 169, 90, 58, 78, 17, 57, 80, 159, 250, 105, 146, 189, 178, 120};
|
||||
|
||||
void WgDevice::SetHeaderObfuscation(const char *key) {
|
||||
#if WITH_HEADER_OBFUSCATION
|
||||
header_obfuscation_ = (key != NULL);
|
||||
if (key)
|
||||
blake2s_hmac((uint8*)&header_obfuscation_key_, sizeof(header_obfuscation_key_), (uint8*)key, strlen(key), kHeaderObfuscationKey, sizeof(kHeaderObfuscationKey));
|
||||
#endif // WITH_HEADER_OBFUSCATION
|
||||
}
|
||||
|
||||
WgPeer::WgPeer(WgDevice *dev) {
|
||||
assert(dev->IsMainThread());
|
||||
dev_ = dev;
|
||||
|
@ -1056,7 +1043,7 @@ void WgPeer::WriteMacToPacket(const uint8 *data, MessageMacs *dst) {
|
|||
} else {
|
||||
has_mac2_cookie_ = false;
|
||||
|
||||
if (dev_->header_obfuscation_) {
|
||||
if (dev_->packet_obfuscator().enabled()) {
|
||||
// when obfuscation is enabled just make the top bits random
|
||||
for (size_t i = 0; i < 4; i++)
|
||||
((uint32*)dst->mac2)[i] = dev_->GetRandomNumber();
|
||||
|
@ -1482,10 +1469,104 @@ static RandomSiphashKey random_siphash_key;
|
|||
|
||||
size_t WgAddrEntry::IpPortHasher::operator()(const WgAddrEntry::IpPort &a) const {
|
||||
uint32 xx = Read32(a.bytes + 16);
|
||||
return siphash13_2u64(Read64(a.bytes) + xx, Read64(a.bytes + 8) + xx, &random_siphash_key.key);
|
||||
return (size_t)siphash13_2u64(Read64(a.bytes) + xx, Read64(a.bytes + 8) + xx, &random_siphash_key.key);
|
||||
}
|
||||
|
||||
size_t WgPublicKeyHasher::operator()(const WgPublicKey&a) const {
|
||||
return siphash13_4u64(a.u64[0], a.u64[1], a.u64[2], a.u64[3], &random_siphash_key.key);
|
||||
return (size_t)siphash13_4u64(a.u64[0], a.u64[1], a.u64[2], a.u64[3], &random_siphash_key.key);
|
||||
}
|
||||
|
||||
// This scrambles the initial 16 bytes of the packet with the
|
||||
// last 8 bytes of the packet as a seed.
|
||||
void WgPacketObfuscator::ScrambleUnscramble(uint8 *data, size_t data_size) {
|
||||
uint64 last_uint64 = ReadLE64(data + data_size - 8);
|
||||
uint64 a = siphash_u64_u32(last_uint64, (uint32)data_size, (siphash_key_t*)&key_[0]);
|
||||
uint64 b = siphash_u64_u32(last_uint64, (uint32)data_size, (siphash_key_t*)&key_[2]);
|
||||
a = ToLE64(a);
|
||||
b = ToLE64(b);
|
||||
if (data_size >= 24) {
|
||||
((uint64*)data)[0] ^= a;
|
||||
((uint64*)data)[1] ^= b;
|
||||
} else {
|
||||
uint64 d[2] = { a, b };
|
||||
for (size_t i = 0; i < data_size - 8; i++)
|
||||
data[i] ^= ((uint8*)d)[i];
|
||||
}
|
||||
}
|
||||
|
||||
size_t WgPacketObfuscator::InsertRandomBytesIntoPacket(uint8 *data, size_t data_size) {
|
||||
assert(data_size >= 24);
|
||||
// The bytes at offset 16 are used as a seed to the prng
|
||||
uint64 master_key = siphash_u64_u32(Read64(data + 16), (uint32)data_size, &random_siphash_key.key);
|
||||
uint32 random_bytes = master_key & 0xFF;
|
||||
data[3] = (uint8)random_bytes;
|
||||
for (uint32 i = 0; i < random_bytes; i += 8)
|
||||
*(uint64*)(data + data_size + i) = siphash_u64_u32(master_key + i, i, &random_siphash_key.key);
|
||||
data_size += random_bytes;
|
||||
return data_size;
|
||||
}
|
||||
|
||||
void WgPacketObfuscator::ObfuscatePacket(Packet *packet) {
|
||||
uint8 *data = packet->data;
|
||||
size_t data_size = packet->size;
|
||||
|
||||
// Too short packets can't be obfuscated
|
||||
if (data_size < 8)
|
||||
return;
|
||||
|
||||
// If the packet is type 1, 2 or 3, or a keepalive packet of type 4, add random bytes at
|
||||
// the end. This is to make it harder to detect the protocol. Store the # of added bytes
|
||||
// in the 3:rd byte of the packet.
|
||||
uint32 packet_type = ReadLE32(data);
|
||||
if ((packet_type == 4 && data_size <= 32) || packet_type < 4) {
|
||||
if (packet_type != 4) {
|
||||
// The 39:th and 43:rd bytes often have zero MSB because of curve25519 pubkey,
|
||||
// so xor them with something in the header.
|
||||
assert(data_size >= 44);
|
||||
data[39] ^= data[12];
|
||||
data[43] ^= data[12];
|
||||
}
|
||||
packet->size = data_size = InsertRandomBytesIntoPacket(data, data_size);
|
||||
}
|
||||
|
||||
// Scramble the header bytes of the packet
|
||||
ScrambleUnscramble(data, data_size);
|
||||
}
|
||||
|
||||
void WgPacketObfuscator::DeobfuscatePacket(Packet *packet) {
|
||||
uint8 *data = packet->data;
|
||||
size_t data_size = packet->size;
|
||||
|
||||
// Too short packets can't be obfuscated / deobfuscated
|
||||
if (data_size < 8)
|
||||
return;
|
||||
|
||||
// Unscramble the header bytes of the packet
|
||||
ScrambleUnscramble(data, data_size);
|
||||
|
||||
// Check whether the packet type field says that we have
|
||||
// extra bytes appended at the end.
|
||||
if (data[0] <= 4) {
|
||||
if (data[0] < 4 && data_size >= 44) {
|
||||
// The 39:th and 43:rd bytes often have zero MSB because of curve25519 pubkey,
|
||||
// so xor them with something in the header.
|
||||
data[39] ^= data[12];
|
||||
data[43] ^= data[12];
|
||||
}
|
||||
if (data[3] <= data_size) {
|
||||
packet->size = (uint32)(data_size - data[3]);
|
||||
data[3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//>> > hashlib.sha256('TunSafe Header Obfuscation Key').hexdigest()
|
||||
//'2444423e33eb5bb875961224c6441f54c5dea95a3a4e1139509ffa6992bdb278'
|
||||
static const uint8 kHeaderObfuscationKey[32] = { 36, 68, 66, 62, 51, 235, 91, 184, 117, 150, 18, 36, 198, 68, 31, 84, 197, 222, 169, 90, 58, 78, 17, 57, 80, 159, 250, 105, 146, 189, 178, 120 };
|
||||
|
||||
void WgPacketObfuscator::SetKey(const uint8 *key, size_t len) {
|
||||
enabled_ = (key != NULL);
|
||||
if (key)
|
||||
blake2s((uint8*)&key_, sizeof(key_), key, len, kHeaderObfuscationKey, sizeof(kHeaderObfuscationKey));
|
||||
}
|
||||
|
|
|
@ -272,10 +272,6 @@ struct WgAddrEntry {
|
|||
|
||||
};
|
||||
|
||||
struct ScramblerSiphashKeys {
|
||||
uint64 keys[4];
|
||||
};
|
||||
|
||||
union WgPublicKey {
|
||||
uint8 bytes[WG_PUBLIC_KEY_LEN];
|
||||
uint64 u64[WG_PUBLIC_KEY_LEN / 8];
|
||||
|
@ -341,6 +337,32 @@ public:
|
|||
};
|
||||
|
||||
|
||||
// This class is used for scrambing / unscrambling of wireguard UDP/TCP packets,
|
||||
// including adding random bytes at the end of the non-data packets.
|
||||
class WgPacketObfuscator {
|
||||
public:
|
||||
WgPacketObfuscator() : enabled_(false) {}
|
||||
|
||||
bool enabled() { return enabled_; }
|
||||
void ObfuscatePacket(Packet *packet);
|
||||
void DeobfuscatePacket(Packet *packet);
|
||||
|
||||
void SetKey(const uint8 *key, size_t len);
|
||||
|
||||
const uint8 *key() { return (uint8*)key_; }
|
||||
|
||||
static size_t InsertRandomBytesIntoPacket(uint8 *data, size_t data_size);
|
||||
|
||||
private:
|
||||
void ScrambleUnscramble(uint8 *data, size_t data_size);
|
||||
|
||||
// Whether packet obfuscation is enabled
|
||||
bool enabled_;
|
||||
|
||||
// Siphash keys for packet scrambling
|
||||
uint64 key_[4];
|
||||
};
|
||||
|
||||
class WgDevice {
|
||||
friend class WgPeer;
|
||||
friend class WireguardProcessor;
|
||||
|
@ -359,9 +381,6 @@ public:
|
|||
// Remove all peers
|
||||
void RemoveAllPeers();
|
||||
|
||||
// Setup header obfuscation
|
||||
void SetHeaderObfuscation(const char *key);
|
||||
|
||||
// Check whether Mac1 appears to be valid
|
||||
bool CheckCookieMac1(Packet *packet);
|
||||
|
||||
|
@ -385,6 +404,8 @@ public:
|
|||
|
||||
MultithreadedDelayedDelete *GetDelayedDelete() { return &delayed_delete_; }
|
||||
|
||||
WgPacketObfuscator &packet_obfuscator() { return packet_obfuscator_; }
|
||||
|
||||
private:
|
||||
std::pair<WgPeer*, WgKeypair*> *LookupPeerInKeyIdLookup(uint32 key_id);
|
||||
WgKeypair *LookupKeypairByKeyId(uint32 key_id);
|
||||
|
@ -441,9 +462,6 @@ private:
|
|||
// Counter for generating new indices in |keypair_lookup_|
|
||||
uint8 next_rng_slot_;
|
||||
|
||||
// Whether packet obfuscation is enabled
|
||||
bool header_obfuscation_;
|
||||
|
||||
// Whether a private key has been setup for the device
|
||||
bool is_private_key_initialized_;
|
||||
|
||||
|
@ -456,8 +474,6 @@ private:
|
|||
uint8 s_priv_[WG_PUBLIC_KEY_LEN];
|
||||
uint8 s_pub_[WG_PUBLIC_KEY_LEN];
|
||||
|
||||
// Siphash keys for packet scrambling
|
||||
ScramblerSiphashKeys header_obfuscation_key_;
|
||||
|
||||
uint8 precomputed_cookie_key_[WG_SYMMETRIC_KEY_LEN];
|
||||
uint8 precomputed_mac1_key_[WG_SYMMETRIC_KEY_LEN];
|
||||
|
@ -465,6 +481,8 @@ private:
|
|||
uint64 random_number_input_[WG_HASH_LEN / 8 + 1];
|
||||
uint32 random_number_output_[WG_HASH_LEN / 4];
|
||||
|
||||
WgPacketObfuscator packet_obfuscator_;
|
||||
|
||||
WgRateLimit rate_limiter_;
|
||||
|
||||
// For defering deletes until all worker threads are guaranteed not to use an object.
|
||||
|
|
Loading…
Reference in a new issue