In a standard WiFi infrastructure network with one access point (AP) and multiple clients, all client-to-client communication must go through the AP. This is fundamentally different from wired Ethernet networks where devices can communicate directly. The IEEE 802.11 protocol specifies this routing behavior to maintain network coordination and prevent collisions.
When Client 1 sends data to Client 2:
1. Client 1 → AP: [Frame with DA=Client2, RA=AP] 2. AP → Client 2: [Frame with DA=Client2, RA=Client2]
The frame passes through the AP's MAC layer, which handles the retransmission. This two-hop process happens even if both clients can physically hear each other's transmissions.
Consider this Python simulation of throughput impact:
import math
def calculate_effective_throughput(direct_rate, ap_rate, overhead=0.2):
"""Calculate effective throughput when routing through AP"""
return min(direct_rate, ap_rate) * (1 - overhead)
# Example with clients close but AP distant
client_link_rate = 54 # Mbps (802.11g)
ap_link_rate = 6 # Mbps (due to range)
print(f"Effective throughput: {calculate_effective_throughput(client_link_rate, ap_link_rate):.1f} Mbps")
For scenarios requiring direct client communication:
- WiFi Direct: Bypasses the AP completely
- Ad-hoc mode: Creates a peer-to-peer network
- TDLS (Tunneled Direct Link Setup): Allows direct client communication after AP-assisted setup
Use Wireshark filters to observe the traffic flow:
wlan.da == client2_mac && wlan.sa == client1_mac # Should show empty wlan.da == ap_mac && wlan.sa == client1_mac # Shows frames from Client1 to AP wlan.da == client2_mac && wlan.sa == ap_mac # Shows frames from AP to Client2
For developers working with WiFi hardware:
// Example of enabling TDLS in Linux
#include <linux/wireless.h>
int enable_tdls(int sockfd, const char *ifname, const uint8_t *peer_mac) {
struct iwreq wrq;
memset(&wrq, 0, sizeof(wrq));
strncpy(wrq.ifr_name, ifname, IFNAMSIZ);
wrq.u.data.pointer = (void *)peer_mac;
wrq.u.data.length = ETH_ALEN;
return ioctl(sockfd, SIOCSIWTDLSCREATE, &wrq);
}
In standard 802.11 infrastructure mode networks, all frame transmissions between wireless clients must pass through the access point (AP). This includes both unicast and broadcast traffic between STAs (stations). The AP acts as both a bridge to the wired network and a central hub for wireless communication.
// Simplified frame flow diagram (client to client via AP)
Client1 → AP → Client2
↓ ↓ ↓
MAC:SA1 MAC:RA1 → MAC:SA2
MAC:SA2 → MAC:RA2
Even when two clients are physically close to each other but associated with the same distant AP, their communication still follows this path. The 802.11 protocol doesn't implement direct client-to-client frame forwarding at the MAC layer in infrastructure mode.
Example of frame address fields changing during transit:
Client1 to AP:
Frame Control | Duration | Address1 (AP) | Address2 (Client1) | Address3 (Client2) | Sequence | Frame Body
AP to Client2:
Frame Control | Duration | Address1 (Client2) | Address2 (AP) | Address3 (Client1) | Sequence | Frame Body
This architecture has significant impacts on network performance:
- Effectively halves available bandwidth for client-to-client communication
- Increases latency with distant APs (RTT includes two wireless hops)
- Creates a single point of failure for local communication
You can verify this behavior using Wireshark with the following display filter:
wlan.fc.type_subtype == 0x0020 || // QoS Data
(wlan.addr == AP_MAC && wlan.ta != wlan.ra) // Transit frames
For direct client communication, consider these technical solutions:
// Linux iw command to enable Wi-Fi Direct (P2P)
$ iw dev wlan0 set type managed
$ iw dev wlan0 connect SSID
// Or create an ad-hoc network
$ iw dev wlan0 set type ibss
$ iw dev wlan0 ibss join SSID freq
Modern protocols like Wi-Fi Direct (P2P) or 802.11z (Tunneled Direct Link Setup) enable direct client communication while maintaining AP association.
This Python script demonstrates the throughput difference between AP-routed and direct communication:
import speedtest
def test_throughput(interface, peer_ip):
st = speedtest.Speedtest(source_address=peer_ip)
st.get_best_server()
return st.download(), st.upload()
# Compare AP-routed vs direct (when possible)
ap_routed = test_throughput('wlan0', '192.168.1.100')
direct = test_throughput('p2p-wlan0-0', '192.168.49.1')
print(f"AP-routed: {ap_routed} vs Direct: {direct}")