fbertone / lib32100

Library implementing port 32100 UDP Cloud protocol
MIT License
45 stars 6 forks source link

It can simulate the protocol used by this camera - RT5350 ? #1

Closed ricardojlrufino closed 7 years ago

ricardojlrufino commented 7 years ago

I bought a Chinese camera and it works with this application. https://play.google.com/store/apps/details?id=object.aisaidezx.client

I got access via telnet, and found this configuration Upnp

Platform=RT5350

<manufacturer>Ralink Linux UPnP IGD Project</manufacturer>
<manufacturerURL>http://www.ralink.com.tw</manufacturerURL>
<modelDescription>RT2880 802.11 SoC Solution</modelDescription>
<modelName>RT2880 IGD Version 1.00</modelName>
<modelNumber>RT2880 C</modelNumber>
<serialNumber>1.00</serialNumber>

By looking at the internal executable strings, I found these addresses www.ipupnp.hk open.kaicong.info www.365home.org

Have any tips on how I can use a LIB to communicate with this camera ?

ricardojlrufino commented 7 years ago

cat /proc/version

Linux version 2.6.21 (root@mailzxh-desktop) (gcc version 3.4.2) #636 Fri Nov 16 10:03:21 CST 2012

cat /proc/cpuinfo

system type : Ralink SoC processor : 0 cpu model : MIPS 24K V4.12 BogoMIPS : 239.10 wait instruction : yes microsecond timers : yes tlb_entries : 32 extra interrupt vector : yes hardware watchpoint : yes ASEs implemented : mips16 dsp VCED exceptions : not available VCEI exceptions : not available

fbertone commented 7 years ago

Does it have an ID composed by 4 letters, 6 numbers and 5 letters? Something like XXXX123456ABCDE?

ricardojlrufino commented 7 years ago

My is 3 letters - 6 numbers and 5 letters

fbertone commented 7 years ago

It may be compatible, but I can't tell for sure, you will have to check it. Do you know how to use wireshark?

ricardojlrufino commented 7 years ago

I do not know very well, I tried to use but did not find much relevant information, or could not read ...

What I found was some requests for addresses: Destination: 174.129.229.15 Destination: 174.129.229.102 Destination: 174,129,228,173

I do not know very well, I tried to use but did not find much relevant information, or could not read ...

What I found was some requests for addresses:

Destination: 174.129.229.15 Destination: 174.129.229.102 Destination: 174,129,228,173

others: Brodcast UDP

Address: Broadcast (ff: ff: ff: ff: ff: ff) 255.255.255.255
User Datagram Protocol, Src Port: 6808 (6808), Dst Port: 6809 (6809)
Text: Hello, I'm online!

And nmap

ec2-174-129-228-173.compute-1.amazonaws.com (174.129.228.173) Host is up (0.19s latency). Not shown: 989 closed ports PORT STATE SERVICE 21/tcp filtered ftp 22/tcp open ssh 25/tcp filtered smtp 135/tcp filtered msrpc 139/tcp filtered netbios-ssn 445/tcp filtered microsoft-ds ->554/tcp filtered rtsp 1434/tcp filtered ms-sql-m ->51723/tcp filtered pptp 2008/tcp filtered conf 6667/tcp filtered irc

ricardojlrufino commented 7 years ago

As I would to test, which server could I use. ? Would I have to do any modification on the camera?

fbertone commented 7 years ago

Good news, the things you posted sound familiar to me. The requests to the three servers have as destination port 32100?

ricardojlrufino commented 7 years ago

I did not find this request ..

But using nmap I saw that the port is open.

I had only TCP scan

ricardojlrufino commented 7 years ago

I'll do the test later, I'm without the camera at the moment.. Thanks

This is required ?

Add camera address client.addCamAddress({host: "192.168.0.100", port: 10088})

fbertone commented 7 years ago

The last one (174.129.228.173) seems to be working! But you have to help me understand how the ID is encoded in the packets. You should run wireshark to look at you client's traffic, to do that you can create a wifi access point on your pc, let your smartphone connect to the ap and dump the traffic. You can use as filter this string: ip.addr == 174.129.228.173 and udp.port == 32100 You should see some packets going from the client to the server that have a len=24 (i.e. 24 bytes of data). Those packets should contain the ID of the camera, if you click on them you should recognize the characters from you id. Do not post the data, just tell me if you can see that.

fbertone commented 7 years ago

if you don't see packets of 24 bytes just look into each packet and search for the strings, then let me know if you find it.

ricardojlrufino commented 7 years ago

Yes a found BEGIN and FINAL of ID, in this package

image

fbertone commented 7 years ago

Nice! Let me guess, the data should be something like this: f1 20 00 14, then the first three characters, then the number converted in hexadecimal (translate it from base 16 to base 10 to confirm), (over how many bytes?), then the last 5 characters and some 00 until the end. Am I correct? Let me know exactly how many bytes compose each part of the packet so that I can adapt my code. Thanks!

fbertone commented 7 years ago

you can post a screenshot of the packet censoring some details of the id if you want

ricardojlrufino commented 7 years ago

DATA(24): f1670014455354000000000000010014XXXXXXXXXX000000

fbertone commented 7 years ago

Yeah, the structure is definitely the same, but the fact that it begins with f1 67 and not f1 20 makes me think that it could be a slightly different variant of the protocol. I'll need your help to verify if there are more differences, but we can do it, the general protocol is the same! Let me know what you would like to do with this library so I can give you more info.

ricardojlrufino commented 7 years ago

I need to develop a web application (in Java) that can access the images of the camera, but I did not find any information about the operation of this protocol.

I was able to access using the "Http API" but only on local network. Using DDNS I could not because the router of the internet carrier I do not have access.

The hope was to understand how this protocol works and get access to the camera in the same way as the App

ricardojlrufino commented 7 years ago

Hi, how are you.

I've done some tests now with the camera, and your library.

Some of the communication is working, but I'm getting an unidentified packet.

client.on('stun', (e) => console.log("stun:"+JSON.stringify(e))); client.on('lookup', (e) => console.log("lookup:"+JSON.stringify(e)));

client.on('unknownMsg', (e) => console.log("unknownMsg:"+JSON.stringify(e))); client.on('pingpong', (e) => console.log("pingpong:"+JSON.stringify(e))); client.on('close', (e) => console.log("close:"+JSON.stringify(e))); client.on('confirmed', (e) => console.log("confirmed:"+JSON.stringify(e))); client.on('http', (e) => console.log("http:"+JSON.stringify(e)));

Result:

stun:{"address":{"host":"189.81.122.20X","port":35740}} stun:{"address":{"host":"189.81.122.20X","port":35740}} unknownMsg:{"header":"lookup"}

Values from unknownMsg (Uint8Array[8]): 241,33,0,4,253,0,0,0

fbertone commented 7 years ago

OK, so you can use the library to help testing the protocol and implement you version in Java. What you want to do is quite simple, it will not take a lot of work. I can also help you with the knowledge I already have on the protocol, maybe I'll put all the description on the wiki. I will write in a few hours. I have to modify the code of the library to support the format of your ID, because now it only supports IDs starting with 4 chars while yours starts with 3 chars.

ricardojlrufino commented 7 years ago

Hi, I would greatly appreciate it. Can you describe how the protocol flow works would be interesting. And how can I access the images from the camera, whether it opens an HTTP port or rtsp

ricardojlrufino commented 7 years ago

Maybe i use your library directly from Java ...

I found this interesting project: https://github.com/apigee/trireme

In my application there is already a proposal to run JavaScript scripts within Java, But using: http://openjdk.java.net/projects/nashorn/

fbertone commented 7 years ago

Very briefly, the protocol works this way:

workflow

1) the camera register itself on the servers communicating its LAN address, and continuously send packets to keep the connection alive (basically an UDP hole punching). 2) the client requests the location of the camera using its ID 3) the server responds with all known camera addresses (LAN + external) or with an OFFLINE message. 4) the client try to contact the camera using the addresses and sending the camera ID 5) two possibilities here: a) if the camera get the message, it responds with its ID and starts sending keepalive messages to keep the connection opened. The client must respond to the keepalive messages otherwise the session get closed by the camera. b) if the client can not contact the camera directly, it tries other ways with the help of the server (I still have to study these functionalities, I think there are 2 options: 1) the camera can try to contact directly the client, if this method fails: 2) the connection is established using relay servers) 6) once the connection is opened, the client can send GET requests very similar to HTTP requests but using a different protocol 7) the responses are divided in various packets (they have a sequence number) and have to be reassembled by the client 8) the client have to ACK the received packages otherwise the session get closed by the camera

packets format

fbertone commented 7 years ago

I started the wiki, I'll put more info there later. I pushed a new branch 'develop' which should add support for your ID and a few more improvements. I will publish a reference client in the future, for now you can clone this repo, checkout the develop branch and create a test client using something like this:

const lib32100 = require('../lib32100')

const client1 = lib32100.client()
client1.on('stun', (e) => console.log(JSON.stringify(e)))
client1.on('http', (e) => console.log(JSON.stringify(e)))
client1.addServer({host: "174.129.228.173", port: 32100})
client1.setUid('YUOURID')
client1.setCamCredentials({user: 'admin', pass: 'password'})

client1.on('lookup', (e) => {
  console.log(JSON.stringify(e))
  client1.addCamAddress(e.address)
  if(e.address.host.startsWith('192')) { // put the first part of your LAN ip so that you connect directly
    console.log('OK')
    client1.openDirectCamSession(e.address)
  }
})
let cnt = 0
client1.on('confirmed', (e) => {
  console.log('CAM confirmed ID!')
  cnt++
  if(cnt == 4) {
    client1.checkCredentials()
    client1.getSnapshot()
    setTimeout(() => { // closing the connection after 10 seconds
      client1.closeSession()
    }, 10000)
  }
})

if everything worked fine you should see some packages flowing! At the moment I still have to implement the code reassemble the packages so you can not automatically save the image, but should be able to strip 16 bytes of header from the first packet (of the image!) and 8 bytes from the following and reassemble the JPEG image bu hand. Let me know if you try this and it works or not! (or if you need help) I will add more functionalities in the next days

ricardojlrufino commented 7 years ago

Wow, great explanation.

I did some remote testing (the camera is in my house), and the communication to be ok. Its returning the IP from my local home network. Calbacks fired: 2x stun, 2x lookup.

5.b) if the client can not contact the camera directly , this is my case now ... =]

5.b.2) the connection is established using relay servers Do you think it would be possible to use a private server? Or take the images through it ?

fbertone commented 7 years ago

Calbacks fired: 2x stun, 2x lookup.

Just that? The session with the camera is not opened? (you can check that also with wireshark)

Do you think it would be possible to use a private server? Or take the images through it ?

In my opinion YES (and should be done, I don't really trust my credentials and images passing in clear through random chinese servers ;) ) In fact, one of the goals of this project is to implement also the server-side and give instructions on how to do it. This would also allow to add end-to-end encryption for example. But it will require some work, because to do that you have to intercept the packets and change them on the fly, while still keeping compatibility with the protocol.

ricardojlrufino commented 7 years ago

Just that? The session with the camera is not opened? (you can check that also with wireshark)

I am accessing remotely, I'll test at home later.

fbertone commented 7 years ago

You can try to connect remotely if you want, just tweak the code to use the external address instead of the internal one (for example change the '192' with the first part of the remote ip) If there is no firewall blocking the udp traffic it should work

ricardojlrufino commented 7 years ago

I tested but it did not work. The home network looks like this: Router of my friend> my router> camera

fbertone commented 7 years ago

Actually I discovered that I added support for your ID only for the lookup phase but not for the connection phase, it would have never worked. My bad! Now it should be fixed, pull the changes and try again.

ricardojlrufino commented 7 years ago

Wow .... CAM confirmed ID! Its working , put I think the connection is not open

I can see the 'get' request but no response from cam: My cam follow the same URL, ex.: get_camera_params.cgi?loginuse=admin&loginpas=pass But it is running on port http/81

Netstat in mycam

tcp 0 0 0.0.0.0:81 0.0.0.0: LISTEN 184/encoder tcp 0 0 0.0.0.0:23 0.0.0.0: LISTEN 31/telnetd tcp 0 0 0.0.0.0:8600 0.0.0.0: LISTEN 32/daemon.v5.10 tcp 0 157 192.168.3.110:23 192.168.3.106:45418 ESTABLISHED 31/telnetd udp 236 0 127.0.0.1:8832 0.0.0.0: 184/encoder udp 0 0 127.0.0.1:6666 0.0.0.0: 184/encoder udp 0 0 0.0.0.0:3089 0.0.0.0: 184/encoder udp 0 0 0.0.0.0:3090 0.0.0.0: 184/encoder udp 0 0 0.0.0.0:8600 0.0.0.0: 32/daemon.v5.10 udp 0 0 127.0.0.1:9123 0.0.0.0: 32/daemon.v5.10 udp 0 0 127.0.0.1:9124 0.0.0.0: 184/encoder udp 0 0 0.0.0.0:32108 0.0.0.0: 184/encoder udp 0 0 127.0.0.1:8813 0.0.0.0: 184/encoder udp 0 0 127.0.0.1:8822 0.0.0.0: 184/encoder udp 0 0 127.0.0.1:8831 0.0.0.0: 33/cmd_thread

ricardojlrufino commented 7 years ago

Package from APP............Data: f1d00059d1000003010a00.... Package from NODEJS....Data: f1d00059d1000000010a00

From NODEJS: image I noticed you are not sending the ACKs

fbertone commented 7 years ago

Wow .... CAM confirmed ID!

Cool! We are on the right path then =D

But it is running on port http/81

Yeah, they run an interface on port 81 for local access but the protocol implements a similar (it's not exactly the same) interface on the UDP socket.

With wireshark can you see small 4-byte packets flowing? You should also see them if you put a listener on 'pingpong' events.

Can you check with wireshark if the cam responds something to the GETs (it is also passible that it responds with something like 'not implemented'

fbertone commented 7 years ago

Package from APP............Data: f1d00059d1000003010a00.... Package from NODEJS....Data: f1d00059d1000000010a00

Oh probably it's a problem with sequence numbers or something like that, I'll look at that ASAP

ricardojlrufino commented 7 years ago

Pingpong is sent automatically or I have to send manually ? 'pingpong' listener is not fired !

With wireshark can you see small 4-byte packets flowing?

Not

fbertone commented 7 years ago

the cam should send 'pings' (f1e00000) after the confirmation of the uid and the library should respond automatically with 'pongs' (f1e10000). But maybe we are sending the request too early and the cam did not yet open the session? You can try delaying the requests

ricardojlrufino commented 7 years ago

Not working, The camera is not pinging !

let cnt = 0
client1.on('confirmed', (e) => {
  console.log('CAM confirmed ID!')
  cnt++
  if(cnt == 1) {

    setTimeout(() => { 
      console.log("sendGET");
      client1.checkCredentials()
      // client1.getSnapshot()
    }, 5000)

    setTimeout(() => { // closing the connection after 10 seconds
      client1.closeCamSession()
    }, 20000)
  }
})
fbertone commented 7 years ago

Uh ok, I guess in this case we have to start sending the pings ourselves. I pushed a new version which exposes the sendPing method, try calling it after the confirmed (maybe try also to put it in a setInterval to send it each 0.1 seconds or so)

ricardojlrufino commented 7 years ago

Wow... this working remote !!!

I'm getting the answers now:

Many:

pingpong:{"subtype":"pong","raw":"f1e10000"} pingpong:{"subtype":"ping","raw":"f1e00000"} pingpong:{"subtype":"pong","raw":"f1e10000"} pingpong:{"subtype":"pong","raw":"f1e10000"} pingpong:{"subtype":"ping","raw":"f1e00000"} pingpong:{"subtype":"pong","raw":"f1e10000"} pingpong:{"subtype":"pong","raw":"f1e10000"} pingpong:{"subtype":"ping","raw":"f1e00000"} pingpong:{"subtype":"pong","raw":"f1e10000"} pingpong:{"subtype":"ping","raw":"f1e00000"} pingpong:{"subtype":"pong","raw":"f1e10000"}

And some:

ack:{"raw":"f1d10006d10000010000"} http:{"seq":0,"raw":"f1d00017d100........"} http:{"seq":0,"raw":"f1d00017d100........"} http:{"seq":0,"raw":"f1d00017d100........"} http:{"seq":0,"raw":"f1d00017d100........"}

The http always comes with seq = 0

ricardojlrufino commented 7 years ago

Now i try snapshot....

let cnt = 0;
var startPing;
client1.on('confirmed', (e) => {
  console.log('CAM confirmed ID!')
  cnt++
  if(cnt == 1) {

    startPing = setInterval(() => { 
      client1.sendPing()
    }, 200);

    setTimeout(() => { 
      clearInterval(startPing);
      client1.checkCredentials()
    }, 2000);

    setTimeout(() => { 
      console.log("getSnapshot");
      client1.getSnapshot()
    }, 5000)

    setTimeout(() => { // closing the connection after 10 seconds
      client1.closeCamSession()
    }, 20000)
  }
})

I'm getting the picture, incredible ...

But a receiving multiple times:

http:{"seq":0,"raw":"f1d00017d10000000 http:{"seq":1,"raw":"f1d00404d100000..... BIGGGCONTENT.......

I think I have to send an ack to the camera, right?

ricardojlrufino commented 7 years ago

I have copied some returns and the data is the same (for: http:{"seq":1,"raw" ...) **md5sum lib32100/resp/***

4d2bdf2bfc43f511a4f6647434bb8955 lib32100/resp/1.txt 4d2bdf2bfc43f511a4f6647434bb8955 lib32100/resp/2.txt 4d2bdf2bfc43f511a4f6647434bb8955 lib32100/resp/3.txt 4d2bdf2bfc43f511a4f6647434bb8955 lib32100/resp/4.txt

Converted to binary: screen.bin: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 320x240, frames 3

fbertone commented 7 years ago

Wow... this working remote !!!

Great! I will integrate the pings directly into the library then.

I think I have to send an ack to the camera, right?

Yeah, I have to do it in the library, I think next week.

screen.bin: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 320x240, frames 3

Seems working, right?

ricardojlrufino commented 7 years ago

Enough progress thanks to your help.

I just do not understand how the final image is made ... I took the captures of Wireshark in the APP, and I observed the sequence of each.

image

Are in sequence: f1d001a8d1000001 f1d001a8d1000002 f1d001a8d1000003 f1d001a8d1000004 f1d001a8d1000005 f1d001a8d1000006

But when I join the bytes of the image (position 16)

dd skip=16 if=1.bin of=1.jpg bs=1 cat *.jpg > out.jpg

This is the result. image

Original image

fbertone commented 7 years ago

You have to skip 16 bytes in the first packet and just 8 on the following! That's because the first packet contains an additional 8-bytes header that tells how many packets compose the file (that is how many packets you have to join together to get the complete file). The library should do this work but it's not implemented yet.

ricardojlrufino commented 7 years ago

additional 8-bytes header

Have you been able to identify offset that represent the quantity? I already hit my head and I did not find a match ...

Have you been able to identify the format of the ack? I looked at the package (F1 D1) of various sizes: 10, 22, 28, 38 from app > cam

fbertone commented 7 years ago

Have you been able to identify offset that represent the quantity?

0000 f1 d0 04 04 d1 00 00 00 01 0a 15 60 90 53 00 01

0x15 == 21 in decimal

Have you been able to identify the format of the ack?

0000 f1 d1 00 06 d1 00 00 01 00 00

00 06 -> 6-bytes payload d1 00 -> ACK 00 01 -> ACKing 1 packet 00 00 -> sequence number of packet

You can ack multiple packets by increasing 00 01 to the number of packets and adding all the sequence numbers to ACK

example:

00 08 -> 8 byte payload d1 00 -> ACK 00 02 -> ACKing 2 packet 00 00 00 01 -> sequence number of packets

ricardojlrufino commented 7 years ago

See my capture: image

The image is composed 12 pcks (I was able to mount the image manually following your tips) Then the ACK is sent, and some packets of the image are repeated (from pck 8 - 12).

Index 11 = 0x15 = 21 in decimal, does not seem to be correct in my case

fbertone commented 7 years ago

what about index 14-13 as the size? 90 53 -> 53 90 -> 21392 bytes

ricardojlrufino commented 7 years ago

Yes: 4c:2c - > 2c4c = 11340

-rwxr-xr-x 1 ricardo root 11340 Abr 9 08:47 out.jpg

ricardojlrufino commented 7 years ago

Could I consider "f1: d0: 00: 58" as the end of the image?

86      img f1:d0:04:04:d1:00:00:01:01:0a:15:60:4c:2c:00:01:ff:d8:ff:e0:00:10:4a:46:
87          f1:d0:04:04:d1:00:00:02:0d:c3:b7:15:85:68:c4:1c:7e:15:ba:74:a9:35:a5:1a:
88          f1:d0:04:04:d1:00:00:03:14:80:17:84:9c:fb:f5:ae:88:0e:5b:9a:f2:e5:62:23:
89          f1:d0:04:04:d1:00:00:04:a6:32:b0:40:10:80:45:3b:a0:c9:3d:05:56:73:46:2a:
90          f1:d0:04:04:d1:00:00:05:b1:15:9e:9a:3e:9e:b2:79:9e:46:e3:e8:c7:23:f2:ab:
91          f1:d0:04:04:d1:00:00:06:24:5f:dd:6c:8f:c6:b3:53:cd:7a:13:8c:c6:0d:6a:ca:
92          f1:d0:04:04:d1:00:00:07:62:f8:2c:29:e3:91:5b:7a:eb:ec:d3:df:9c:64:81:5c:
93          f1:d0:04:04:d1:00:00:08:f3:59:49:e1:3b:30:e0:97:62:3d:2b:5a:38:ad:ac:d0:
94          f1:d0:04:04:d1:00:00:09:1b:19:04:fb:1c:d3:94:30:f9:ba:11:ea:28:a2:bb:6f:
95          f1:d0:04:04:d1:00:00:0a:6a:f5:b4:48:8a:24:9b:f2:a6:5c:5d:4b:29:f2:e1:f9:
96          f1:d0:04:04:d1:00:00:0b:ad:f7:8d:44:b2:15:6c:8e:2b:7b:4f:d7:1a:10:12:43:
97      end f1:d0:00:58:d1:00:00:0c:54:25:b7:52:ae:07:bd:60:cd:9a:d4:0a:90:36:3e:94:
98          f1:d1:00:12:d1:00:00:07:00:01:00:02:00:03:00:04:00:05:00:06:00:07
99      rep f1:d0:04:04:d1:00:00:08:f3:59:49:e1:3b:30:e0:97:62:3d:2b:5a:38:ad
100         f1:d0:04:04:d1:00:00:09:1b:19:04:fb:1c:d3:94:30:f9:ba:11:ea:28:a2
101         f1:d0:04:04:d1:00:00:0a:6a:f5:b4:48:8a:24:9b:f2:a6:5c:5d:4b:29:f2
102         f1:d0:04:04:d1:00:00:0b:ad:f7:8d:44:b2:15:6c:8e:2b:7b:4f:d7:1a:10
103     end f1:d0:00:58:d1:00:00:0c:54:25:b7:52:ae:07:bd:60:cd:9a:d4:0a:90:36
104         f1:d1:00:18:d1:00:00:0a:00:08:00:09:00:0a:00:0b:00:0c:00:08:00:09:00:0a:
105         f1:e0:00:00
106         f1:e1:00:00
107         f1:e0:00:00
108         f1:e1:00:00
109         f1:e0:00:00

Another thing I just noticed, the APP made two requests in the same package ... MGET /check_user.cgi?loginuse=admin&loginpas=XXXX&user=admin&pwd=XXXXX& KGET /snapshot.cgi?loginuse=admin&loginpas=XXXX&user=admin&pwd=XXXX&

fbertone commented 7 years ago

Could I consider "f1: d0: 00: 58" as the end of the image?

No, 00 58 should be just the size of the last piece of image (88 bytes)

Another thing I just noticed, the APP made two requests in the same package

Yes, you can join multiple requests together, but the camera will respond with multiple packets This functionality is already implemented (sendMultipleGet)