Comments (6)
First we define a few buffers to maximize performance using batch send/recv:
internal var wireguardReadBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
internal var wireguardWriteBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
internal var wireguardTimerBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: maxPacketSize)
// Preallocated data arrays
internal var wireguardReadDatagrams = [Data]()
internal var wireguardReadPackets = [Data]()
internal var wireguardReadPacketProtocols = [NSNumber]()
internal var wireguardWriteDatagrams = [Data]()
The encapsulate method looks like this, it is called by packetFlow.readPacketObjects
in an infinite loop, unless there is a connection error.
/**
Handle packets from the virual interface. Encapsulate and send to server.
- parameter packets: The raw IP packets that will be encapsulated.
*/
internal func handleOutboundWarpPackets(_ packets: [NEPacket]) {
var offset = 0
for packet in packets {
if offset + maxPacketSize > bufferSize {
// Flush if might not have enough space
sendDatagrams(datagrams: warp.wireguardWriteDatagrams)
warp.wireguardWriteDatagrams.removeAll(keepingCapacity: true)
offset = 0
}
// We encapsulate as many packets as we can into the wireguardWriteBuffer
let writeBuff = warp.wireguardWriteBuffer.advanced(by: offset)
let res = packet.data.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> wireguard_result in
let dataPtr = ptr.baseAddress?.bindMemory(to: UInt8.self, capacity: packet.data.count)
return wireguard_write(self.warp.wireguardClient, dataPtr, UInt32(packet.data.count), writeBuff, UInt32(maxPacketSize))
}
switch res.op {
case WRITE_TO_NETWORK:
self.setClientId(buffer: writeBuff)
warp.wireguardWriteDatagrams.append(Data(bytesNoCopy: writeBuff, count: Int(res.size), deallocator: .none))
offset += res.size
case WIREGUARD_DONE:
break
case WIREGUARD_ERROR:
Log.info("Warp: WIREGUARD_ERROR in wireguard_write: \(res.size)")
default:
Log.info("Warp: Unexpected return type: \(res.op)")
}
}
if warp.wireguardWriteDatagrams.count > 0 {
// Send
self.sendDatagrams(datagrams: warp.wireguardWriteDatagrams)
warp.wireguardWriteDatagrams.removeAll(keepingCapacity: true)
}
}
The decapsulate method (which we pass in setReadHandler
) is this:
/**
The function the connection calls when datagrams arrive
*/
// swiftlint:disable function_body_length
internal func handleInboundWarpPackets(datagrams: [Data]?, err: Error?) {
guard let datagrams = datagrams else {
if err != nil && !self.reconnecting.value {
Log.error(err, message: "Warp: error while reading datagrams")
return self.handleUDPError(err: err!)
}
return
}
var offset = 0
var flushQueue = false
// This is the main loop
for var wireguardDatagram in datagrams {
// Check if we ran out of space and need to flush
if offset + maxPacketSize >= bufferSize {
flushReadStructs()
offset = 0
}
let readBuff = warp.wireguardReadBuffer.advanced(by: offset)
let res = wireguardDatagram.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> wireguard_result in
let dataPtr = ptr.baseAddress?.bindMemory(to: UInt8.self, capacity: maxPacketSize)
return wireguard_read(self.warp.wireguardClient, dataPtr, UInt32(wireguardDatagram.count), readBuff, UInt32(maxPacketSize))
}
switch res.op {
case WIREGUARD_DONE:
break
case WRITE_TO_NETWORK:
self.setClientId(buffer: readBuff)
warp.wireguardReadDatagrams.append(Data(bytesNoCopy: readBuff, count: Int(res.size), deallocator: .none))
offset += res.size
flushQueue = true
case WRITE_TO_TUNNEL_IPV4, WRITE_TO_TUNNEL_IPV6:
let family = (res.op == WRITE_TO_TUNNEL_IPV4) ? afInet : afInet6
warp.wireguardReadPackets.append(Data(bytesNoCopy: readBuff, count: Int(res.size), deallocator: .none))
warp.wireguardReadPacketProtocols.append(family)
offset += res.size
case WIREGUARD_ERROR:
Log.info("Warp: WIREGUARD_ERROR in wireguard_read: \(res.size)")
default:
Log.info("Warp: Unexpected return type: \(res.op)")
}
}
// If wireguard library wants to send prior pending packets to network, handle that case
// and repeat calling wireguard_read() until WIREGUARD_DONE or error is returned.
if flushQueue {
if offset + maxPacketSize >= bufferSize {
flushReadStructs()
offset = 0
}
let readBuff = warp.wireguardReadBuffer.advanced(by: offset)
flushLoop: while true {
let res = wireguard_read(self.warp.wireguardClient, nil, 0, readBuff, UInt32(maxPacketSize))
switch res.op {
case WRITE_TO_NETWORK:
self.setClientId(buffer: readBuff)
warp.wireguardReadDatagrams.append(Data(bytesNoCopy: readBuff, count: Int(res.size), deallocator: .none))
offset += res.size
case WIREGUARD_DONE:
break flushLoop
case WIREGUARD_ERROR:
Log.info("Warp: WIREGUARD_ERROR in repeat wireguard_read: \(res.size)")
break flushLoop
default:
Log.info("Warp: Unexpected return type: \(res.op)")
break flushLoop
}
}
}
flushReadStructs()
}
func flushReadStructs() {
if warp.wireguardReadPackets.count > 0 {
self.packetFlow.writePackets(warp.wireguardReadPackets, withProtocols: warp.wireguardReadPacketProtocols)
}
if warp.wireguardReadDatagrams.count > 0 {
self.sendDatagrams(datagrams: warp.wireguardReadDatagrams)
}
warp.wireguardReadPackets.removeAll(keepingCapacity: true)
warp.wireguardReadDatagrams.removeAll(keepingCapacity: true)
warp.wireguardReadPacketProtocols.removeAll(keepingCapacity: true)
}
For roaming, you usually get a UDP error, on which we immediately reconnect.
When you are on cellular and connect to wifi, we used to monitor the path with a callback, but it was not very reliable in iOS. Now instead we check the hasBetterPath property of the current UDP connection every 500ms.
from boringtun.
It is functional enough, after all we use it just for that.
Idiomatic means that for example on iOS if you must use NetworkExtensions and NEPacketTunnelProvider.
You also use the platform dependent routing and UDP connection to a server.
On packetFlow.readPackets you call wireguard_write from the ffi code, and send the encapsulated packet over the UDP connection.
On each packet you receive from UDP you call wireguard_read and depending on result either write back to UDP or use packetFlow.writePackets to inject the decapsulated packets.
In addition you need a timed function to call wireguard_tick.
from boringtun.
On iOS you will definitely need to use NetworkExtensions and NEPacketTunnelProvider as on Android VpnService but UDP connection could be managed from Rust side since you can pass created file descriptor to Rust.
@vkrasnov Could you also explain a bit about wireguard_tick fn and why it's needed. To me it looks that this should be internal implementation detail.
from boringtun.
Hi @anjmao
You could definitely manage the UDP connection from Rust, but iOS provides much more powerful (and faster) UDP connection API in the form of createUDPSession.
It has multiple benefits over managing your own BSD socket (which Rust provides only minimal abstraction over), including better roaming support, better battery life etc.
If you really wanted to use Rust for that you can also technically extract the filedescrtiptor of the tunnel created by NetworkExtensions and pass it to Rust too.
For WARP we use the idiomatic flow readPackets -> Batch encapsulate with boringtun -> writeMultipleDatagrams
And in the other direction we use the callback in setReadHandler to batch decapsulate multiple datagrams with boringtun followed by writePacketObjects.
from boringtun.
is there a code example of this iOS idiomatic loop flow? do you also need to listen for network changes to re-handshake on roaming?
from boringtun.
Thanks a lot for the code example. I've been playing a bit with this and got a successful tunnel up.
However I noticed some issues with that NWUDPSession.writeMultipleDatagrams
.
First thing was that I also ended up with a timer since the observe KVO which docs recommend seems broken.
Then to conform with "Callers should wait until the completionHandler is executed before issuing another write." I'm using a DispatchSemaphore. But it seems like the completion handler isn't always called, causing a dead lock. Easy to reproduce by toggling flight mode while doing speed tests.
Did you experience this as well? A bit off topic from boringtun so I understand if you have other things to do :)
from boringtun.
Related Issues (20)
- Failed to initialize tunnel HOT 1
- Slow work during testing on VM HOT 17
- The wireguard_write encryption result is different from the official one HOT 5
- libboringtun.so shared lib
- Decapsulate error InvalidCounter (not a huge problem, just curious why it happens and whether I should worry) HOT 2
- Tag 0.6.0?
- Intermittent connection loss with HANDSHAKE(REKEY_TIMEOUT) errors HOT 9
- Add support for mips(el) with ring 0.17.0
- Failed to initialize tunnel, error: Socket kind: NotFound
- any benchmarks against Wireguard? HOT 5
- Apple Silicon Support? HOT 2
- i found it's hard to compile this project to shared lib for android, lets share it here HOT 1
- Bad latest handshake timestamp and keepalives not being sent
- Fails to create API socket when /var/lib/wireguard does not exist
- Appetite for refactoring to be SANS-IO (including time?)
- centos7 host based debian docker image failed to implement boringtun in usersapce
- creation of "connected socket" returns unsupported HOT 1
- Undefined symbol: _new_tunnel HOT 1
- `daemonize` dependency is out of date and has code that will be rejected by a future version of Rust
- Is this project dead/abandoned? HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from boringtun.