Я пытаюсь понять, как сделать сетевое расширение, чтобы мое приложение для iOS могло программно открывать настраиваемый VPN-туннель на С#, но, глядя на некоторые похожие проекты Obj-C, я не уверен, возможно ли это в Xamarin (поскольку я не вижу проекта расширения сети в Visual Studio) и как портировать то, что я понимаю, является обязательным классом PacketTunnelProvider, который, я думаю, должен присутствовать и сначала указан как расширение в plist.info... Я, в частности, больше всего проблем с тем, как портировать части этого класса, которые появляются в конце как расширение, и некоторые обработчики событий, названные так: -> Void) и func Adapter(adapter: Adapter, handleEvent event: AdapterEvent, message: String?), так как они оба имеют другую сигнатуру, чем обработчик событий в C#, который принимает отправителя и eventArgs (или что-то производное)… если есть e сделал это на С#. Я хотел бы знать, по крайней мере, возможно ли, если нет, как портировать такой класс?
Я нашел этот проект https://github.com/ss-abramchuk/OpenVPNAdapter. (поскольку он, кажется, делает большую часть того, что я хочу), который мне удалось перевести в библиотеку привязки Xamarin, но я не уверен, как и следует ли включать его класс PacketTunnelProvider в Xamarin (поскольку это то, что в файле readme говорится, что вы должны использовать для включения что-то вроде этого адаптера)... Я думаю, сначала нужно добавить в plist.info что-то вроде этого:
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
но куда вы идете, чтобы использовать библиотеку привязок? Это код Obj-C, который говорит и, по-видимому, делает то, что я хочу сделать после добавления этого пользовательского туннеля протокола VPN в приложение с использованием библиотеки:
import NetworkExtension
import OpenVPNAdapter
class PacketTunnelProvider : NEPacketTunnelProvider
{
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}
()
let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void)
{
// There are many ways to provide OpenVPN settings to the tunnel provider. For instance,
// you can use `options` argument of `startTunnel(options:completionHandler:)` method or get
// settings from `protocolConfiguration.providerConfiguration` property of `NEPacketTunnelProvider`
// class. Also you may provide just content of a ovpn file or use key:value pairs
// that may be provided exclusively or in addition to file content.
// In our case we need providerConfiguration dictionary to retrieve content
// of the OpenVPN configuration file. Other options related to the tunnel
// provider also can be stored there.
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else
{
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else
{
fatalError()
}
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
configuration.settings = [
// Additional parameters as key:value pairs may be provided here
]
// Apply OpenVPN configuration
let properties: OpenVPNProperties
do
{
properties = try vpnAdapter.apply(configuration: configuration)
}
catch
{
completionHandler(error)
return
}
// Provide credentials if needed
if !properties.autologin {
// If your VPN configuration requires user credentials you can provide them by
// `protocolConfiguration.username` and `protocolConfiguration.passwordReference`
// properties. It is recommended to use persistent keychain reference to a keychain
// item containing the password.
guard let username: String = protocolConfiguration.username else
{
fatalError()
}
// Retrieve a password from the keychain
guard let password: String = ... {
fatalError()
}
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do
{
try vpnAdapter.provide(credentials: credentials)
}
catch
{
completionHandler(error)
return
}
}
// Checking reachability. In some cases after switching from cellular to
// WiFi the adapter still uses cellular data. Changing reachability forces
// reconnection so the adapter will use actual connection.
vpnReachability.startTracking { [weak self] status in
guard status != .notReachable else { return }
self?.vpnAdapter.reconnect(interval: 5)
}
// Establish connection and wait for .connected event
startHandler = completionHandler
vpnAdapter.connect()
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void)
{
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings, completionHandler: @escaping (OpenVPNAdapterPacketFlow?) -> Void)
{
setTunnelNetworkSettings(settings) {
(error) in
completionHandler(error == nil ? self.packetFlow : nil)
}
}
// Process events returned by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?)
{
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error)
{
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else
{
return
}
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else
{
cancelTunnelWithError(error)
}
}
// Use this method to process any log message returned by OpenVPN library.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String)
{
// Handle log messages
}
}
// Extend NEPacketTunnelFlow to adopt OpenVPNAdapterPacketFlow protocol so that
// `self.packetFlow` could be sent to `completionHandler` callback of OpenVPNAdapterDelegate
// method openVPNAdapter(openVPNAdapter:configureTunnelWithNetworkSettings:completionHandler).
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
Как тогда мне перенести на С# или, может быть, я все делаю неправильно (из-за комментария ниже - привязка dll больше 15 МБ - или это ограничение в отношении использования памяти, которое не связано с размером файла) ? Должен ли я на самом деле просто ссылаться на пользовательскую библиотеку VPN, чтобы открыть туннель VPN напрямую из кода и продолжить оттуда, как обычно (поскольку я также нашел проект/приложение https://github.com/passepartoutvpn, который использует Cocopod TunnelKit, но библиотека этого приложения не будет работать с маркером для создания библиотеки привязки, и если это так, такое приложение вообще допустимо в AppStore)? Спасибо за любую помощь заранее.
По совету @SushiHangover я попытался связать TunnelKit, так как этот проект казался меньшим, и частично преуспел... Мне удалось собрать dll ~ 3 МБ, который кажется намного меньше, чем 21 МБ OpenVPNadapter, и я думаю, что я почти закончил с проектом NetworkExtension...Мне нужно просто выяснить, все ли в порядке с @escaping completeHandler и как получить некоторые групповые константы, которые, как я полагаю, должны быть установлены в Хост-приложение каким-то образом?
public override void StartTunnel(NSDictionary<NSString, NSObject> options, Action<NSError> completionHandler)
{
//appVersion = "\(GroupConstants.App.name) \(GroupConstants.App.versionString)";
//dnsTimeout = GroupConstants.VPN.dnsTimeout;
//logSeparator = GroupConstants.VPN.sessionMarker;
base.StartTunnel(options, completionHandler);
}
На данный момент я закомментировал groupcontants, но, по крайней мере, я надеюсь, что это достаточно хороший перенос Swift3:
override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
appVersion = "\(GroupConstants.App.name) \(GroupConstants.App.versionString)"
dnsTimeout = GroupConstants.VPN.dnsTimeout
logSeparator = GroupConstants.VPN.sessionMarker
super.startTunnel(options: options, completionHandler: completionHandler)
}
Если кто-то еще знает о групповых константах и о том, как их получить, я был бы признателен (но я также должен отметить, что sharpie pod не предоставил/не открыл ни одно из этих полей, которые мне следовало бы назначение. Возможно, я сделал это неправильно, поскольку TunnelKit — это полностью проект Swift3, в отличие от OpenVPNadapter :/