Как перенести класс сетевого расширения NEPacketTunnelProvider из Obj-C/Swift в Xamarin C#?

Я пытаюсь понять, как сделать сетевое расширение, чтобы мое приложение для 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 :/


person GI1    schedule 09.02.2019    source источник
comment
Впоследствии я также нашел несколько тем на форуме Xamarin, в которых обсуждается собственное расширение приложения forums.xamarin.com/discussion/83693/ и что если расширение больше 15 МБ, оно не будет работать на iOS... так что это относится к размеру библиотеки привязки или что-то еще, пожалуйста, так как dll, сделанный из вышеупомянутого проекта, больше 20 МБ?   -  person GI1    schedule 09.02.2019
comment
Привет! Я автор Passepartout и TunnelKit. Удалось ли вам в конце концов перенести TunnelKit на Xamarin?   -  person keeshux    schedule 30.05.2019
comment
Кроме того, ограничение в 15 МБ предназначено для использования памяти, а не для двоичного файла.   -  person keeshux    schedule 30.05.2019


Ответы (1)


Должен ли я на самом деле просто использовать пользовательскую библиотеку VPN, чтобы открыть туннель VPN и перейти оттуда, но будет ли приложение тогда допустимо для AppStore?

Для iOS 12+ вам обязательно нужно использовать инфраструктуру сетевых расширений, чтобы иметь право на Магазин.

Задача сборки Xamarin.iOS (ValidateAppBundle) правильно определяет com.apple.networkextension.packet-tunnel как допустимое расширение (.appex), поэтому да, вы можете создать расширение NEPacketTunnelProvider.

Вы правы, в VS нет встроенных шаблонов для Network Provider .appex для туннелей, DNS-прокси, управления фильтром | данных, типов прокси, но это не значит, что вы не можете просто использовать еще один из шаблонов (или создать проект с нуля) и изменить его (я создаю проект Xcode iOS и начинаю добавлять цели расширения и просто отражаю эти изменения в VS).

(К вашему сведению: в вашем примере это код Swift, а не ObjC...)

Теперь из-за ограничений размера .appex (и проблем с производительностью в некоторых случаях) многие расширения очень сложно реализовать через Xamarin.iOS. Большинство разработчиков, которые сталкиваются с этим, используют ObjC/Swift, по крайней мере, для разработки appex...

person SushiHangover    schedule 09.02.2019
comment
Applogies, я имел в виду, что сама библиотека адаптера была Obj-C, это код из файла readme о том, как его использовать, и из того, что я видел, тесты в этом проекте также были Swift ... спасибо за ваш комментарий (моя ошибка). ... Я обновлю с помощью Obj-C / Swift, и я надеюсь, что вы не будете возражать, так как это не сделает ваш ответ менее точным. - person GI1; 09.02.2019
comment
Также добавлено редактирование в конце вопроса, пытающееся сообщить о том, что я сделал, на основе некоторых комментариев, которые вы дали, и я надеюсь, что это действительно прогресс, поскольку я использовал проект NotificationServiceExtension (удалив его [Register( NotificationService)] и изменив Entitlements.plist, добавив имя моего класса расширения)... теперь мне нужно выяснить групповую вещь, которая, по моему мнению, является способом взаимодействия хоста и приложений расширения... а также, поскольку мой класс называется PacketTunnelProvider , я предполагаю, что я просто буду использовать этот класс в качестве своего NEVpnProtocol, и это должно быть так (если класс выше работает)? - person GI1; 11.02.2019
comment
Я застрял Сборка выполняется нормально, но либо я не собираюсь использовать ее так, как раньше, либо она неправильно создает DLL-расширение, даже если это удалось, и у dll есть пространство имен, которого нет у хост-приложения. видя и говоря это: /Users/dev/Desktop/VPNTest/iOS/VPNServiceIOS.cs(7,7): ошибка CS0246: не удалось найти имя типа или пространства имен «OpenVPNTunnel» (вы пропустили директиву using или ссылка на сборку?) (CS0246) (VPNTest.iOS) (VPNServiceIOS — это служба, в которой я пытаюсь использовать расширение OpenVPNTunnel так же, как встроенные NEVpnProtocolIke2 и NEVpnProtocolIPSec... неправильно ?) - person GI1; 11.02.2019