使用安全隧道远程访问设备

更新时间:
复制 MD 格式

部分物联网设备基于安全的考虑或因部署于私有网络内部,是无法在外网直接访问设备上的服务的。物联网平台提供设备安全隧道产品功能,助您建立双向安全的通信链路,您可基于该通信链路实现对设备的远程访问、远程诊断和管理等功能。

背景信息

物联网平台的安全隧道功能应用于远程登录设备中已有服务的场景,例如设备原本的上位机软件诊断能力,可通过安全隧道服务的远程连接功能实现设备的远程诊断。

安全隧道的使用场景和使用说明的更多介绍,请参见安全隧道概述。

本文以Linux系统设备为例,为您介绍通过安全隧道实现远程访问设备的方案。

本示例中的安全隧道通过控制台页面手动创建,您可以参考本文示例中安全隧道实现方式,基于实际业务需求,设计开发自己设备中本地服务的远程访问功能。

设备端开发

准备工作

  • 获取设备认证信息。

    本示例以一机一密认证方式为例,获取信息如下:

    {
      "ProductKey": "a1WvA***",
      "DeviceName": "device2",
      "DeviceSecret": "ff01e59d***ba2ca2b17"
    }
  • 获取C Link SDK。

    定制SDK时,在SDK定制页面的高级能力区域,选中远程登录,集成包含安全隧道功能的设备端SDK。

  • 准备开发环境。

    本文示例使用Linux系统开发环境,且安装能在端口号7启动的Echo服务进程Xinetd。

    重要

    本示例中设备与访问端数据通信需依赖Xinetd服务,因此必须完成Xinted服务配置并启动。

启动设备端例程

  1. 解压获取的SDK压缩包,在C Link SDK中的Demo文件LinkSDK./demos/remote_access_basic_demo.c中完成以下配置并保存。设备端C Link SDK开发更多内容,请参见C Link SDK概述。

    • 将设备证书信息替换为已获取的测试设备的证书信息。

    • 将设备接入的mqtt_host值替换为当前设备所属企业版实例的MQTT接入地址。

    • 在服务列表中增加ECHO服务的配置,原_SSH服务不可删除。

    配置信息如下所示。

    /* TODO: 替换为自己设备的三元组 */
    char *product_key    = "a1xxx";
    char *device_name    = "device2";
    char *device_secret  = "xxx7";
    /*
     * ...
     */
    char *mqtt_host = "axxx.iot-as-mqtt.xxx.aliyuncs.com";
    uint16_t    port = 443;    /* 无论设备是否使用TLS连接阿里云平台, 目的端口都是443 */
    /* 位于portfiles/aiot_port文件夹下的系统适配函数集 */
    extern aiot_sysdep_portfile_t g_aiot_sysdep_portfile;
    /* 位于external/ali_ca_cert.c中的服务器证书 */
    extern const char *ali_ca_cert;
    static pthread_t g_ra_process_thread;
    static pthread_t g_mqtt_process_thread;
    static pthread_t g_mqtt_recv_thread;
    static uint8_t g_mqtt_process_thread_running = 0;
    static uint8_t g_mqtt_recv_thread_running = 0;
    aiot_ra_service_t services[] = {
        {
            .type = "_SSH",
            .ip = "127.0.0.1",
            .port = 22,
        },{
            .type = "ECHO",
            .ip = "127.0.0.1",
            .port = 7,
        }
    };
            .type = "ECHO",
            .ip = "127.0.0.1",
            .port = 7,
        },
    };
  2. 登录Linux虚拟机,将上一步骤中已修改完成的LinkSDK文件,上传至Linux虚拟机的开发环境。

  3. 在SDK根目录/LinkSDK下,执行make命令,完成样例程序的编译,然后运行样例文件。

    ./output/remote-access-basic-demo
  4. 查看运行日志,显示如下信息,表示设备正常在线且安全隧道SDK正常启动。运行成功后,终端输出类似如下日志,其中 MQTT connect success、安全隧道主题订阅成功及 remote_proxy_thread_start 表示设备已成功连接并启动安全隧道代理。

    [1646291696.533][LK-1000] establish mbedtls connection with server(host='xxx.mqtt.iothub.aliyuncs.com', port=[443])
    [1646291696.588][LK-1000] success to establish mbedtls connection, (cost 45247 bytes in total, max used 47983 bytes)
    [1646291696.677][LK-0313] MQTT connect success in 153 ms
    AIOT_MQTTEVT_CONNECT
    [1646291696.677][LK-0309] sub: /sys/xxx/device2/secure_tunnel/notify
    [1646291696.677][LK-1C00] remote_proxy_thread_start!
  5. 登录物联网平台控制台,进入企业版实例页面,在左侧导航栏选择设备管理 > 设备,找到目标设备,确认设备状态为在线。

创建安全隧道

按照以下操作,在物联网平台控制台添加目标设备的安全隧道。您也可调用物联网平台云端API CreateDeviceTunnel创建安全隧道,使用说明,请参见云端SDK。

  1. 登录物联网平台控制台。

  2. 在实例概览页面,单击目标企业版实例,进入实例详情页面。

  3. 在左侧导航栏选择监控运维>安全隧道。

  4. 在安全隧道页面,单击创建安全隧道。

  5. 在创建隧道对话框,设置以下参数,单击确认。在 创建隧道 对话框中,选择 所属产品 和 设备,并根据需要填写 推送给设备的自定义信息,然后单击 确认。安全隧道创建后默认24小时有效,超过24小时后状态自动变更为已关闭,且处于关闭状态的安全隧道不能再次打开。

  6. 在弹出的对话框中,单击一键复制,保存安全隧道ID、访问端Token和URL信息。安全隧道创建成功后,对话框显示访问端token和访问端url,请妥善保存,关闭页面后无法再次查看隧道信息。单击复制或一键复制保存相关信息。

    安全隧道创建成功后,设备端会收到建连通知,安全隧道的SDK会使用该建连通知中的信息与物联网平台建立WebSocket连接。消息如下:设备端例程启动后,日志输出示例:

    [1644310040.466][LK-1C00] _switch_topic_handler payload:{"schema":"wss","path":"/tunnel/186axxx34f/dest","token_expire":86399,"tunnel_id":"..."}
    ra_event_cb AIOT_RA_EVT_OPEN_WEBSOCKET 186a5 xxx 34f
    [1644310040.522][LK-1C00] start to create cloud channel
    [1644310040.522][LK-1C00] connect remote service host iot-secure-tunnel-cn-sh.aliyuncs.com, port 443establish tcp connection with server(host='iot-secure-tunnel-cn-sh.aliyuncs.com', port=[443])
    success to establish tcp, fd=4
    local port: 59542
    [1644310040.522][LK-1000] establish mbedtls connection with server(host='iot-secure-tunnel-cn-sh.aliyuncs.com', port=[443])
    [1644310040.577][LK-1000] success to establish mbedtls connection, (cost 55960 bytes in total, max used 58648 bytes)
    [1644310040.599][LK-0F10] open_cloud_proxy_channel success
    [1644310040.599][LK-1C00] websocket 186a5cxxx b53e334f opened
    ra_event_cb AIOT_RA_EVT_CONNECT 186a5 xxx 334f
    [1644310060.633][LK-1C00] websocket 186xxx4f ping
    [1644310060.633][LK-1C00] websocket 18xxx4f pong

    物联网平台控制台的监控运维>安全隧道页面,显示安全隧道已打开,设备端已连接。

访问端开发

准备工作

  • 下载设备ECHO服务的访问端示例代码。详细内容,请参见alibabacloud-iot-java-demo。

  • 准备开发环境。本文使用Java开发环境,具体如下:

启动访问端例程

  1. 解压已下载的示例代码包。

  2. 打开IntelliJ IDEA,导入解压后的示例工程。

  3. 在com.aliyun.iotx.lp.demo.secure.tunnel.echo下EchoServiceDemoTunnelSourceTest.java文件中,配置已保存的安全隧道ID、访问端Token和URL值。

    将 tunnelId、sourceUri、sourceToken 替换为实际设备隧道信息,示例代码如下。

    import ...
    /**
     * this is the example for echo service using the device tunnel of aliyun iot
     */
    public class EchoServiceDemoTunnelSourceTest {
        public static void main(String[] args) throws InterruptedException, JoranException, IOException {
            load(EchoServiceDemoTunnelSourceTest.class.getClassLoader().getResource("logback-spring.xml").getPath());
            String tunnelId = "186axxx...xxxe334f";
            String sourceUri = "wss://iot-secure-tunnel-cn-sh.aliyuncs.com/tunnel/186axxx...xxx4f/source";
            String sourceToken = "f32dxxx...xxxaba2";
            if (StringUtils.isBlank(tunnelId) || StringUtils.isBlank(sourceUri) || StringUtils.isBlank(sourceToken)) {
                throw new IllegalArgumentException("tunnelId, sourceUri and sourceToken should not be blank. please crate device tunnel and assign the corresponding value");
            }
            EchoServiceDemoSourceSimulator sourceSimulator = new EchoServiceDemoSourceSimulator(tunnelId, sourceUri, sourceToken);
            sourceSimulator.connect();
            for (int i = 0; i < 2; i++) {
                sourceSimulator.startEchoSession();
            }
            Thread.sleep(20 * 1000);
            sourceSimulator.disconnect();
            Thread.sleep(3 * 1000);
        }
    }
  4. 运行EchoServiceDemoTunnelSourceTest.java示例代码,启动访问端的主程序后,该程序会使用配置的访问端建连信息与物联网平台建立WebSocket连接,创建ECHO类型的会话(Session)。运行日志如下图。

    重要

    本示例代码中,添加了结束程序的代码(Thread.sleep(20 * 1000);),即程序启动成功,运行20秒后会结束。实际场景中,您可根据需要自行设置运行时间。

    18:40:59,129  |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property
    18:40:59,158  |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting additivity of logger [com.aliyun.iot.secure.tunnel] to false
    18:40:59,158  |-INFO in ch.qos.logback.classic.joran.action.LevelAction - com.aliyun.iot.secure.tunnel level set to DEBUG
    18:40:59,158  |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [console] to Logger[com.aliyun.iot.secure.tunnel]
    18:40:59,158  |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
    18:40:59,158  |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@10bbd20a - Registering current configuration as safe fallback point
    2022-02-10 18:40:59,177 [main] DEBUG com.aliyun.iot.secure.tunnel.demo.echo.EchoServiceDemoSourceSimulator - TunnelSource is connected. tunnelId:xxx
    2022-02-10 18:41:04,595 [main] INFO  com.aliyun.iot.secure.tunnel.protocol.source.TunnelSource - source connect success.
    2022-02-10 18:41:04,724 [tunnel-session-thread-0] INFO  com.aliyun.iot.secure.tunnel.protocol.source.TunnelSource - SourceSimulator sendSessionCreateFrame header:{"service_type":"ECHO","frame_type":"SESSION_CREATE","frame_id":2170}
    2022-02-10 18:41:04,724 [tunnel-session-thread-1] INFO  com.aliyun.iot.secure.tunnel.protocol.source.TunnelSource - SourceSimulator sendSessionCreateFrame header:{"service_type":"ECHO","frame_type":"SESSION_CREATE","frame_id":2171}
    2022-02-10 18:41:04,882 [nioEventLoopGroup-2-1] INFO  com.aliyun.iot.secure.tunnel.protocol.handler.ReceivedTunnelFrameProcessor - receive success response, messageHeader={"service_type":"ECHO","session_id":"1644489532843678","frame_type":"COMMON_RESPONSE","frame_id":2171}; response={code:0,"msg":"new session response","success":true}
    2022-02-10 18:41:04,883 [nioEventLoopGroup-2-1] INFO  com.aliyun.iot.secure.tunnel.protocol.source.TunnelSource - source create session success. sessionId:1644489532843678
    2022-02-10 18:41:04,887 [nioEventLoopGroup-2-1] INFO  com.aliyun.iot.secure.tunnel.protocol.handler.ReceivedTunnelFrameProcessor - receive success response, messageHeader={"service_type":"ECHO","session_id":"1644489532843333","frame_type":"COMMON_RESPONSE","frame_id":2170}; response={code:0,"msg":"new session response","success":true}
    2022-02-10 18:41:04,888 [nioEventLoopGroup-2-1] INFO  com.aliyun.iot.secure.tunnel.protocol.source.TunnelSource - source create session success. sessionId:1644489532843333
    2022-02-10 18:41:07,889 [tunnel-session-thread-1] DEBUG com.aliyun.iot.secure.tunnel.demo.echo.EchoServiceDemoSourceSimulator - complete to send data frame. sessionId:1644489532843678 key:payload_key_10000
    2022-02-10 18:41:07,890 [tunnel-session-thread-0] DEBUG com.aliyun.iot.secure.tunnel.demo.echo.EchoServiceDemoSourceSimulator - complete to send data frame. sessionId:1644489532843333 key:payload_key_10002
    2022-02-10 18:41:08,095 [nioEventLoopGroup-2-1] DEBUG com.aliyun.iot.secure.tunnel.protocol.handler.ReceivedTunnelFrameProcessor - source receive data-trans-frame, tunnelId: tunnelFrameHeader:{"service_type":"_SSH","session_id":"1644489532843333","frame_type":"DATA_TRANSPORT","frame_id":165328508}
    2022-02-10 18:41:08,104 [nioEventLoopGroup-2-1] DEBUG com.aliyun.iot.secure.tunnel.protocol.handler.ReceivedTunnelFrameProcessor - source receive data-trans-frame, tunnelId: tunnelFrameHeader:{"service_type":"_SSH","session_id":"1644489532843678","frame_type":"DATA_TRANSPORT","frame_id":933973143}
    2022-02-10 18:41:08,891 [tunnel-session-thread-0] DEBUG com.aliyun.iot.secure.tunnel.demo.echo.EchoServiceDemoSourceSimulator - complete to send data frame. sessionId:1644489532843333 key:payload_key_10004
    2022-02-10 18:41:08,891 [tunnel-session-thread-1] DEBUG com.aliyun.iot.secure.tunnel.demo.echo.EchoServiceDemoSourceSimulator - complete to send data frame. sessionId:1644489532843678 key:payload_key_10005
    2022-02-10 18:41:09,029 [nioEventLoopGroup-2-1] DEBUG com.aliyun.iot.secure.tunnel.protocol.handler.ReceivedTunnelFrameProcessor - source receive data-trans-frame, tunnelId: tunnelFrameHeader:{"service_type":"_SSH","session_id":"1644489532843333","frame_type":"DATA_TRANSPORT","frame_id":413190196}
    2022-02-10 18:41:09,039 [nioEventLoopGroup-2-1] DEBUG com.aliyun.iot.secure.tunnel.protocol.handler.ReceivedTunnelFrameProcessor - source receive data-trans-frame, tunnelId: tunnelFrameHeader:{"service_type":"_SSH","session_id":"1644489532843678","frame_type":"DATA_TRANSPORT","frame_id":643528316}
    2022-02-10 18:41:09,892 [tunnel-session-thread-0] DEBUG com.aliyun.iot.secure.tunnel.demo.echo.EchoServiceDemoSourceSimulator - complete to send data frame. sessionId:1644489532843333 key:payload_key_10008
    2022-02-10 18:41:09,892 [tunnel-session-thread-1] DEBUG com.aliyun.iot.secure.tunnel.demo.echo.EchoServiceDemoSourceSimulator - complete to send data frame. sessionId:1644489532843678 key:payload_key_10009
    2022-02-10 18:41:10,073 [nioEventLoopGroup-2-1] DEBUG com.aliyun.iot.secure.tunnel.protocol.handler.ReceivedTunnelFrameProcessor - source receive data-trans-frame, tunnelId: tunnelFrameHeader:{"service_type":"_SSH","session_id":"1644489532843333","frame_type":"DATA_TRANSPORT","frame_id":462096631}

    Session创建成功后,即可进行设备ECHO服务的测试。