外部程式利用 API 與 RouterOS 溝通

RouterOS 在3.0版以後推出了API可以讓外部程式存取

這樣子功能性真的是延伸了很多

目前API支援以下幾種程式語言

in Perl – forum thread by cheesegrits
in Delphi – forum thread and wiki by rodolfo
in PHP – wiki link by Denis Basta
Java sample methods – forum post
in Python – wiki link by Mikrotik staff
in C# – wiki link by wiki user Gregy

可惜沒有VB.NET的版本。不過只要有C#的版本,一切就好辦事了

以下是轉換過後VB.NET版的API

不過傳送或讀取的值如果是中文的話會變成亂碼,這部份要再研究一下

使用前記得先開啟API的服務

/ip service enable api

或是利用圖型介面開啟 IP->Service

VB.NET版本的RouterOS API程式碼如下

Imports System.Net.Sockets
Imports System.IO
Imports System.Text

Class MK_ROS
    Private connection As Stream
    Private con As TcpClient

    Public Sub New(ByVal ip As String)
        con = New TcpClient()
        con.Connect(ip, 8728)
        connection = DirectCast(con.GetStream(), Stream)
    End Sub
    Public Sub Close()
        connection.Close()
        con.Close()
    End Sub
    Public Function Login(ByVal username As String, ByVal password As String) As Boolean
        Send("/login", True)
        Dim hash As String = Read()(0).Split(New String() {"ret="}, StringSplitOptions.None)(1)
        Send("/login")
        Send("=name=" & username)
        Send("=response=00" & EncodePassword(password, hash), True)
        If Read()(0) = "!done" Then
            Return True
        Else
            Return False
        End If
    End Function
    Public Sub Send(ByVal co As String)
        Dim bajty As Byte() = Encoding.ASCII.GetBytes(co.ToCharArray())
        Dim velikost As Byte() = EncodeLength(bajty.Length)

        connection.Write(velikost, 0, velikost.Length)
        connection.Write(bajty, 0, bajty.Length)
    End Sub
    Public Sub Send(ByVal co As String, ByVal endsentence As Boolean)
        Dim bajty As Byte() = Encoding.ASCII.GetBytes(co.ToCharArray())
        Dim velikost As Byte() = EncodeLength(bajty.Length)
        connection.Write(velikost, 0, velikost.Length)
        connection.Write(bajty, 0, bajty.Length)
        connection.WriteByte(0)
    End Sub
    Public Function Read() As List(Of String)
        Dim output As New List(Of String)()
        Dim o As String = ""
        Dim tmp As Byte() = New Byte(3) {}
        Dim count As Long
        While True
            tmp(3) = CByte(connection.ReadByte())
            'if(tmp[3] == 220) tmp[3] = (byte)connection.ReadByte(); it sometimes happend to me that 
            'mikrotik send 220 as some kind of "bonus" between words, this fixed things, not sure about it though
            If tmp(3) = 0 Then
                output.Add(o)
                If o.Substring(0, 5) = "!done" Then
                    Exit While
                Else
                    o = ""
                    Continue While
                End If
            Else
                If tmp(3) < &H80 Then
                    count = tmp(3)
                Else
                    If tmp(3) < &HC0 Then
                        Dim tmpi As Integer = BitConverter.ToInt32(New Byte() {CByte(connection.ReadByte()), tmp(3), 0, 0}, 0)
                        count = tmpi Xor &H8000
                    Else
                        If tmp(3) < &HE0 Then
                            tmp(2) = CByte(connection.ReadByte())
                            Dim tmpi As Integer = BitConverter.ToInt32(New Byte() {CByte(connection.ReadByte()), tmp(2), tmp(3), 0}, 0)
                            count = tmpi Xor &HC00000
                        Else
                            If tmp(3) < &HF0 Then
                                tmp(2) = CByte(connection.ReadByte())
                                tmp(1) = CByte(connection.ReadByte())
                                Dim tmpi As Integer = BitConverter.ToInt32(New Byte() {CByte(connection.ReadByte()), tmp(1), tmp(2), tmp(3)}, 0)
                                count = tmpi Xor &HE0000000
                            Else
                                If tmp(3) = &HF0 Then
                                    tmp(3) = CByte(connection.ReadByte())
                                    tmp(2) = CByte(connection.ReadByte())
                                    tmp(1) = CByte(connection.ReadByte())
                                    tmp(0) = CByte(connection.ReadByte())
                                    count = BitConverter.ToInt32(tmp, 0)
                                Else
                                    'Error in packet reception, unknown length
                                    Exit While
                                End If
                            End If
                        End If
                    End If
                End If
            End If

            For i As Integer = 0 To count - 1
                o += ChrW(connection.ReadByte())
            Next
        End While
        Return output
    End Function
    Private Function EncodeLength(ByVal delka As Integer) As Byte()
        If delka < &H80 Then
            Dim tmp As Byte() = BitConverter.GetBytes(delka)
            Return New Byte(0) {tmp(0)}
        End If
        If delka < &H4000 Then
            Dim tmp As Byte() = BitConverter.GetBytes(delka Or &H8000)
            Return New Byte(1) {tmp(1), tmp(0)}
        End If
        If delka < &H200000 Then
            Dim tmp As Byte() = BitConverter.GetBytes(delka Or &HC00000)
            Return New Byte(2) {tmp(2), tmp(1), tmp(0)}
        End If
        If delka < &H10000000 Then
            Dim tmp As Byte() = BitConverter.GetBytes(delka Or &HE0000000)
            Return New Byte(3) {tmp(3), tmp(2), tmp(1), tmp(0)}
        Else
            Dim tmp As Byte() = BitConverter.GetBytes(delka)
            Return New Byte(4) {&HF0, tmp(3), tmp(2), tmp(1), tmp(0)}
        End If
    End Function

    Public Function EncodePassword(ByVal Password As String, ByVal hash As String) As String
        Dim hash_byte As Byte() = New Byte(hash.Length / 2 - 1) {}
        For i As Integer = 0 To hash.Length - 2 Step 2
            hash_byte(i / 2) = [Byte].Parse(hash.Substring(i, 2), System.Globalization.NumberStyles.HexNumber)
        Next
        Dim heslo As Byte() = New Byte(1 + Password.Length + (hash_byte.Length - 1)) {}
        heslo(0) = 0
        Encoding.ASCII.GetBytes(Password.ToCharArray()).CopyTo(heslo, 1)
        hash_byte.CopyTo(heslo, 1 + Password.Length)

        Dim hotovo As Byte()
        Dim md5 As System.Security.Cryptography.MD5

        md5 = New System.Security.Cryptography.MD5CryptoServiceProvider()

        hotovo = md5.ComputeHash(heslo)

        'Convert encoded bytes back to a 'readable' string
        Dim navrat As String = ""
        For Each h As Byte In hotovo
            navrat += h.ToString("x2")
        Next
        Return navrat
    End Function
End Class

使用方式如下

Dim mikrotik As New MK_ROS("主機位置(可為IP或Domain)")
If Not mikrotik.Login("帳號", "密碼") Then
    TextBox1.Text &= ("連線失敗")
    mikrotik.Close()
    Return
End If
'要下的指令
mikrotik.Send("/system/identity/print", True)
For Each h As String In mikrotik.Read()
    TextBox1.Text &= h
Next

參考資料:

Mikrotik RouterOS v3.x features

RouterOS API

RouterOS Community Support [Scripting]

3 thoughts on “外部程式利用 API 與 RouterOS 溝通

  1. 你好,看了你的日志很受启发,有个疑问能否帮解答一下?
    程序退出的时候,ros里的active user仍然记录着我的登录信息,该怎么办哈,我已经使用了 close方法

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *