# IPFS ping protocol

**URL:** <https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658>\
**Category:** Help\
**Created:** [January 11, 2019, 8:45am UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658 "2019-01-11T08:45:51Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 11, 2019, 8:45am UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/1 "2019-01-11T08:45:51Z")

</div>

Is there a RFC or similar on how I would for example ping a client, not using any library.

I want to better understand how IPFS works, so I would like to first make a super basic client, that would just connect to a known IP/port, ping it, read the pong and report the time. But I wouldn’t like to use any IPFS related library, just basic sockets and OK, I wouldn’t reimplement hashing/crypto.

Or if you suggest anything simpler (w/o using libraries), I could try that.

---

<div class="post-metadata">

**Author:** ![jamiew](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/jamiew/32/2356_2.png) [@jamiew](https://discuss.ipfs.tech/u/jamiew)\
**Post date:** [January 11, 2019, 2:52pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/2 "2019-01-11T14:52:58Z")

</div>

There is an HTTP API you could use if you’re just trying to ping a known node

Are you trying to actually connect to many peers and ping one of them?

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 11, 2019, 3:09pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/3 "2019-01-11T15:09:10Z")

</div>

No, no, I want to connect to for example my IPFS node, essentially like

`telnet [ip of my IPFS node] [port of my IPFS node]`

and ping it.

Just of course not manually, but write a short program/script to do it, without using an API.

Like for HTTP where I would write to the socket `GET / HTTP/1.1\r\n`…

---

<div class="post-metadata">

**Author:** ![leerspace](https://avatars.discourse-cdn.com/v4/letter/l/94ad74/32.png) [@leerspace](https://discuss.ipfs.tech/u/leerspace)\
**Post date:** [January 11, 2019, 9:51pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/4 "2019-01-11T21:51:25Z")

</div>

Reimplementing `ipfs ping` without any libraries likely isn’t going to be simple. If you want to reference the existing implementations rather than try to reverse engineer it, these are probably good starting points.

> <https://github.com/ipfs/go-ipfs/blob/master/core/commands/ping.go>

> <https://github.com/libp2p/go-libp2p/blob/master/p2p/protocol/ping/ping.go>

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 12, 2019, 7:40am UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/5 "2019-01-12T07:40:06Z")

</div>

A user on freenode#ipfs did it in Lua, exactly that: just ping.

> **[whyrusleeping/lua-libp2p](https://github.com/whyrusleeping/lua-libp2p)**
>
> Hacking together a libp2p implementation in lua, for fun, in my copious free time - whyrusleeping/lua-libp2p

---

<div class="post-metadata">

**Author:** ![leerspace](https://avatars.discourse-cdn.com/v4/letter/l/94ad74/32.png) [@leerspace](https://discuss.ipfs.tech/u/leerspace)\
**Post date:** [January 12, 2019, 12:11pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/6 "2019-01-12T12:11:04Z")

</div>

Good find! That’s not just ping, though; it’s a lip2p implementation (or at least the beginnings of one) in lua. And that’s also not just an enthusiastic IPFS user – that’s one of the core go-ipfs devs. So he probably had at least a little head start in terms of understanding how the go-ipfs implementation works 🙂.

The readme summarizes nicely what you would need to do to reimplement it. However, it sounds like you’d need to implement a little multistream functionality first before you can ping.

> Implement ping

> ```
> Open stream, multistream negotiate /ipfs/ping/1.0.0
> Write a random 32 byte value
> read back that same 32 byte value
> time that process
> repeat as often as needed
> 
> ```

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 12, 2019, 4:44pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/7 "2019-01-12T16:44:53Z")

</div>

Yeah. Well I hope I’ll manage (and find free time) to do it.  
I think the protocol is just not that nicely documented, so I’ll try to also write it down.

I was thinking that my end goal would be a tiny/basic implementation for ARM devices (R.Pi, routers etc.) and maybe even IoT devices, e.g. ESP8266/ESP32. That would be interesting and useful, but that’s fairly ambitious.

If I just redo the ping so that I fully understand it and nicely document it, I think I would be fairly happy. 🙂

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 14, 2019, 6:57pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/8 "2019-01-14T18:57:54Z")

</div>

So my progress insofar …

I’m doing it first in PHP, because it’s simple (to me), then I’ll try to do it in C.

I start the communication, switch to unencrypted, but then I’m stuck with how to start multiplexing. I think I somehow write the wrong headers or something. :’(

The following works:

> <https://github.com/seba1337/php-ipfs-ping/blob/master/test.php>

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 15, 2019, 11:41am UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/9 "2019-01-15T11:41:46Z")

</div>

After “/plaintext/1.0.0” I send: `\0b00001000 \0x01 \x0A`  
Which by my logic would be how to initiate the multiplexing:

- first character (from the right): 000 = new stream, 00001 = stream id
- second character (length of data): 1, i.e. 1 byte
- third character (data): just a new line

I get back: `\0x13/multistream/1.0.0\n`

- first character \0x13 is 19, the length of the data
- next is the data

Anything I send after this with a header `\b00001010` (message initiator, stream id=1) or `\b00001001` (message receiver, stream id=1) or without a header causes to drop the connection.  
For example I tried both headers (or without one) + `\0x13/multistream/1.0.0\n` and it drops. I’ve tried also with `/ipfs/ping/1.0.0`, of course with the length before it.

What am I doing wrong?

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 15, 2019, 3:49pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/10 "2019-01-15T15:49:13Z")

</div>

I ran two IPFS’s with disabled encryption (–disable-transport-encryption), added the nodes to the bootstrap (otherwise it doesn’t work) and captured its traffic with wireshark.

You add to bootstrap like this:  
`ipfs bootstrap add /ip4/[ip where to connect]/tcp/[port]/ipfs/[peer id]`

Example:  
`ipfs bootstrap add /ip4/10.0.0.1/tcp/4001/ipfs/QmaRmsQ2BfJFqRkqgd3MEPTgP3vrvwh5FTWPxf1irF1jtR`

I started sniffing without IPFS’s started, then I started them and ran ping while sniffing, then ended.

I’ve saved the `pcapng` dump on IPFS, [https://ipfs.io/ipfs/QmVYdhiH4XW32fdMWptwFVFhYKjsfn54xUPhXZTNZC8zPY](https://ipfs.io/ipfs/QmVYdhiH4XW32fdMWptwFVFhYKjsfn54xUPhXZTNZC8zPY)

Seems that it’s a bit more complex.

I also don’t understand why `/multistream/1.0.0` gets repeated so often. Seems like a lot of overhead.

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 16, 2019, 3:13pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/11 "2019-01-16T15:13:59Z")

</div>

Ok, according to this [https://github.com/multiformats/multistream-select](https://github.com/multiformats/multistream-select), you can also use `ln\n` at various points to get the available options.

I’ll use bold for received messages, italic for transmitted. Everything has a byte (varint) at the beginning, with the length of the message (including newline) written in binary. Lines are ended with newline i.e. \n.

So it seems that the handshake needs to be in form of:  
**`/multistream/1.0.0`**  
_`/multistream/1.0.0`_

Anything else fails: closes socket.

If we do the before described handshake, we can send _`ls`_. If we have the normal ipfs deamon, with encryption on, then we get the only option **`/secio/1.0.0`** , if not, we get **`/plaintext/1.0.0`** , which is what I got with my unencyrpted tests.

We have to repeat that back.

After we repeat that back, we send _`ls`_ again. We get the only option, which is (again) **`/multistream/1.0.0`**.

We have to repeat that back.

After we repeat that back, we send _`ls`_ again. We get two options, each in its own line, but sent in one piece. After the first byte indicating length (like any other message insofar) we get an another byte indicating the number of entries. Then each entry has its own preceeding byte for length.  
**`/yamux/1.0.0`**  
**`/mplex/6.7.0`**

With added extra bytes (including length, newlines):  
**`\0x1D \0x02 \0x0D /yamux/1.0.0 \0x0A`**  
**`\0x0D /mplex/6.7.0 \0x0A`**

- `\0x0A` is new line
- `\0x1D` is 29 which is the number of characters in the whole response (not counting this byte for length).
- `\0x02` is number of entries, 2 in this case
- `\0x0D` is 13, which is the number of characters of /yamux/1.0.0 including new line.
- `\0x0D` is the same thing just for mplex.

I didn’t notice the same behaviour with the _`ls`_ executions before this one. Which points that possibly if the choice is only one, then number of entries is not given.

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 17, 2019, 1:25pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/12 "2019-01-17T13:25:53Z")

</div>

Today’s research.

I’ll write again bold for received messages, italic for transmitted.  
I’ll omit in the first part the preceeding binary byte of message length for clarity. Also at the end it’s always \n which I’ll also omit for the same reason. I’m using still `--disable-transport-encryption`.

So the handshake insofar goes like this:  
**`/multistream/1.0.0`**  
_`/multistream/1.0.0`_  
_`/plaintext/1.0.0`_  
**`/plaintext/1.0.0`**  
**`/multistream/1.0.0`**  
_`/multistream/1.0.0`_  
**`/mplex/6.7.0`**  
_`/mplex/6.7.0`_

After you execute the last line you get a bunch of data which is in a different format now. The format is found here: [https://github.com/libp2p/specs/tree/master/mplex](https://github.com/libp2p/specs/tree/master/mplex)  
Mplex in short is a protocol for stream multiplexing, which in simple terms is how to group a bunch of requests into a single connection.

First a bit of intro:

The format is such, that first you get an extra header byte, then the rest is like before. Note that there are then two data length bytes one after the other, because of “nesting” of protocols i.e. one in the other. One comes from mplex the other from multistream.

- **1st byte** is the _header byte_.
- **2nd byte** is the _data length byte_.
- The data after that is _data length_ bytes long.

Or in graphical form:  
`[header] [strlen(data) in binary] [data]`

The header is in a special binary format where the 3 righter-most bits are type (or flag) of the header and lefter-most 5 bits are the ID of the stream.

Visually this would be like this, where D stands for ID, T stands for type:  
`D D D D D T T T`

The left part, ID, is easy to understand. The 5 bits just represent a number, which is used to identify a stream. They are used for identifying streams. 5 bits gives us 2^5 = 32 possible streams.

A few real examples, where I’ve replaced D with actual values. T aren’t important for now.  
`0 0 0 0 0 T T T` is for example stream ID 0  
`0 0 0 0 1 T T T` is for example stream ID 1  
`0 0 0 1 0 T T T` is for example stream ID 2

When you get the header byte, the most easy way to extract the stream ID is to use bitwise shifting operators, which shift bits by X places. In this case we need to shift by 3 places, that’s why we’ll use `header >> 3`.

That gives us for the first 3 examples:  
`0 0 0 0 0 0 0 0` for stream ID 0  
`0 0 0 0 0 0 0 1` for stream ID 1  
`0 0 0 0 0 0 1 0` for stream ID 2

Now we’ll focus on the Ts, i.e. type or flag of the header.

There are 3 bits, that gives us 2^3 = 8 possible flags, but just 7 are defined in the protocol.  
`D D D D D 0 0 0` (0) `NewStream` initiating a new stream, that one that sends this becomes the **initiator**  
`D D D D D 0 0 1` (1) `MessageReceiver` data sent by the one who _didn’t initiate_ the stream  
`D D D D D 0 1 0` (2) `MessageInitiator` data sent by the one who **initiated** the stream  
`D D D D D 0 1 1` (3) `CloseReceiver` closing the stream by the one who _didn’t initiate_ the stream  
`D D D D D 1 0 0` (4) `CloseInitiator` closing the stream by the one who **initiated** the stream  
`D D D D D 1 0 1` (5) `ResetReceiver`reseting the stream by the one who _didn’t initiate_ the stream  
`D D D D D 1 1 0` (6) `ResetInitiator` reseting the stream by the one who **did initiate** the stream

We can see a pattern where the rightermost bit indicates `0` for the **initiator** and `1` for the _receiver_. This also manifests in the decimal numbers so that **initiator** has always even numbers and _receiver_ odd ones.

To get the type (flag) from the header we use bitwise and of the number 7, which is 111: `header & 0x07`

**So what did I get?**

IPFS, after doing the `/mplex/6.7.0` handshake, returns 3 streams. This is manifested with getting the following things all at once:

> initiate stream 0  
> initiate stream 1  
> initiate stream 2  
> stream0: /multistream/1.0.0  
> stream1: /multistream/1.0.0  
> stream2: /multistream/1.0.0

I’ll present it here also in raw, but I’ll present it like this:  
**1st** byte in binary (header)  
**2nd** byte in hexadecimal (length)  
**3rd** byte in ASCII character (stream name)

**initiate stream 0** : `00000000 0x01 0`  
**initiate stream 1** : `00001000 0x01 1`  
**initiate stream 2** : `00010000 0x01 2`

Now I’ll do it the same as before, just that the 3rd part will be length byte of the nested protocol + ASCII data (stream data). \n stands for new line character.  
**stream 0** : `00000010 0x14 0x13 /multistream/1.0.0\n`  
**stream 1** : `00001010 0x14 0x13 /multistream/1.0.0\n`  
**stream 2** : `00010010 0x14 0x13 /multistream/1.0.0\n`

As we can see we first got _initiate stream_ type headers, i.e. type 0 with stream IDs 0, 1, 2. Then next we got _message initiator_ type headers, i.e. type 2 with IDs 0, 1, 2.

We see we have first 0x14 which is 20 i.e. strlen([data byte] + /multistream/1.0.0\n) and then the next byte is 0x13 which is 19 i.e. strlen(/multistream/1.0.0\n).

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 18, 2019, 10:29am UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/13 "2019-01-18T10:29:22Z")

</div>

I’ll write again bold for received messages, italic for transmitted.

This time I’ll not omit the length byte nor the new lines (which I’ll mark with \n), because the protocol seems to be more complex regarding the use of that. I’ll write binary bytes in hex form, e.g. 2 bytes one after the other I’ll write as 0x00 0x00.

I’m using still --disable-transport-encryption.

So the handshake insofar goes like this:  
**`0x13 /multistream/1.0.0\n`**  
_`0x13 /multistream/1.0.0\n`_  
_`0x11 /plaintext/1.0.0\n`_  
**`0x11 /plaintext/1.0.0\n`**  
**`0x13 /multistream/1.0.0\n`**  
_`0x13 /multistream/1.0.0\n`_  
**`0x0D /mplex/6.7.0\n`**  
_`0x0D /mplex/6.7.0\n`_  
**`0x00 0x01 0`**  
**`0x08 0x01 1`**  
**`0x10 0x01 2`**  
**`0x02 0x14 0x13 /multistream/1.0.0\n`**  
**`0x0A 0x14 0x13 /multistream/1.0.0\n`**  
**`0x12 0x14 0x13 /multistream/1.0.0\n`**

The explanation what is what is in previous posts.

Now in order to ping we must open a new stream and we can’t use the 3 ones opened by the IPFS on the other side as each has a separate function. We can just ignore those streams in order to just ping, but I didn’t at first and instead I tried to play with them.

In order to communicate with each of the streams opened by the IPFS we have to generate messages with proper headers of type (flag) 1, i.e. `MessageReceiver` and the ID.  
We can generate the header byte in the reverse fashion as we decode it, using bitwise functions that’s `header = (id << 3) | flag`. So we first shift the ID 3 bits to the left and then we bitwise OR the bits on the right with the type (flag).

So each message we send has to have the header byte, but also two different length data bytes, along with the data itself, i.e. `[header] [strlen(data) + 1] [strlen(data)] [data]`.

I sent first the `ls\n` command to see if I’ll get anything useful.  
_`0x01 0x04 0x03 ls\n`_ (for stream 0)  
_`0x09 0x04 0x03 ls\n`_ (for stream 1)  
_`0x11 0x04 0x03 ls\n`_ (for stream 2)

What I got were 2 byte responses with length byte of 0x00 and headers with each stream ID, but types (flags) of always in two cases 6 `ResetInitiator` and in one case 4 `CloseInitiator`. Which stream responded what was random, but always just 1 with close and 2 with reset.

After that I’ve sent to each the standard `/multistream/1.0.0` back (with correct headers).

What I got in reply was the following each from a different stream ID (I’ll omit the headers, data bytes, newlines for clarity).  
**`/libp2p/circuit/relay/0.1.0`**  
**`/ipfs/kad/1.0.0`**  
**`/ipfs/id/1.0.0`**

Which stream ID had which response was purely random.

To each of those I sent back again `ls\n` and same thing happened as before, only now I could identify which of those returns type 4 and which type 6. Type 4 (close) is returned by KAD and type 6 by the other 2.

**Now let’s go to the ping protocol.**  
Again, I have to emphasize, that we don’t have to handshake the other streams. I just did it out of curiosity.

We have to open a new stream with stream ID for example 3, i.e. send a header with ID 3 and type (flag) 0. We get a multistream in response. Now we’re the initiator and IPFS is the receiver.

_`0x18 0x01 3`_  
**`0x19 0x14 0x13 /multistream/1.0.0\n`**

We must respond with multistream back, note that header type is 2 `MessageInitiator` as we’re the initiator now.  
_`0x1A 0x14 0x13 /multistream/1.0.0\n`_

Fun thing is that if we send now `ls\n` we get `ls\n` back, so theoretically we could use this already as ping. If we send anything else ending with `\n` we get back `na`, sending without a new line sends headers of flag 3 (close) and one of 5 (reset).

But let’s do it the proper way.

We send the same way as multistream `/ipfs/ping/1.0.0` and we get the same thing back:  
_`0x1A 0x12 0x11 /ipfs/ping/1.0.0\n`_  
**`0x19 0x12 0x11 /ipfs/ping/1.0.0\n`**

Now we have to send 32 bytes of ping data (anything we want), but with **just one length byte**.  
Before we were sending `[header] [strlen(data) + 1] [strlen(data)] [data]` for negotiation.  
Now that we’re in ping mode we send it as `[header] [strlen(data)] [data]`.

If we send anything less than 32 bytes, even if we mark it correctly in the header lenght, it will just stall. If we send anything more it will just read the first 32 bytes and send them back.

So how does it look like?  
_`0x1A 0x20 12345678901234567890123456789012345678912`_  
**`0x19 0x20 12345678901234567890123456789012345678912`**

We can repeat this last message for multiple pings and of course measure the time in between. 🙂

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 18, 2019, 12:31pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/14 "2019-01-18T12:31:11Z")

</div>

Example code that pings a locally started IPFS daemon with disabled encryption, i.e.`ipfs daemon --disable-transport-encryption`

> <https://github.com/seba1337/php-ipfs-ping/blob/master/ping.php>

A real example output from the script is here: [https://github.com/seba1337/php-ipfs-ping/blob/master/ping-output.txt](https://github.com/seba1337/php-ipfs-ping/blob/master/ping-output.txt)

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 19, 2019, 10:28am UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/15 "2019-01-19T10:28:30Z")

</div>

Now, that it works in plaintext, I’ve decided to tackle the encrypted real version.

Starting the handshake with encryption is at first fairly easy. The handshake begins similarly to the plaintext handshake.

**`/multistream/1.0.0`**  
_`/multistream/1.0.0`_  
_`/secio/1.0.0`_  
**`/secio/1.0.0`**

But then it gets tricky. It switches to a binary format. I’ll write all in hex.

From what I’ve analysed insofar, we get 6 bytes which are always the same, no matter the node or connection attempt.

`00 00 01 7c 0a 10`

Next we get 16 bytes which are always different with each attempt of connection.

`24 bf fc 18 f4 3d 97 01 9c aa 77 80 0c e8 98 ac`

Next are 41 bytes which are always the same, no matter the node or connection attempt:

`12 ab 02 08 00 12 a6 02 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 01 01 00`

Next are 256 bytes which are different from node to node, but same for each connection attempt (probably public key):

`d4 4d ef ff ce a0 0b af c9 df 75 e4 cf 51 31 91 c4 f9 eb 72 57 54 cc 0f ee b7 17 fe 08 c5 d2 79 8d 66 3d 3f da ff 94 24 65 77 ad d4 11 e5 0c 0d bf be e8 bf 33 a8 f0 a2 b8 0a ec 76 96 f6 09 da 13 ab 7c 56 58 08 c0 90 0f 8d 1f 56 7a 7c 3a 81 91 1a 46 95 e7 4f ec 28 f3 0c 47 aa cc 77 78 58 c8 6a 00 48 5a 39 b6 b8 0c 0d ab cd 92 b1 88 fa 53 3a c4 fd f9 6c 9a 30 46 c1 b2 3c c6 8b ed fa a4 0b af d3 27 57 30 d3 a1 19 91 ab 8a f4 be ae 1f 12 d6 a8 30 45 14 42 61 43 71 bf 5d 51 a2 8d 90 f9 6b 3a 64 f0 36 7a 22 75 1b 86 42 57 3f bd 1a 6b 73 42 cb e7 9a a1 f8 3b d4 74 42 c7 e4 67 4a a0 87 b7 f1 45 06 1e d9 2a 65 05 1a da 0e 63 1a 8d 13 3a d0 69 0a c5 5b cd 57 24 f2 9b a8 6e 78 d3 be b4 ea a6 76 da 8f 1b 2c 31 e6 91 9c 51 71 fd 47 ff 61 c1 c3 58 71 fc 2f fa 4e 1a 15 b9`

And at last 65 bytes which are always the same no matter the node or connection attempt:

`02 03 01 00 01 1a 11 50 2d 32 35 36 2c 50 2d 33 38 34 2c 50 2d 35 32 31 22 18 41 45 53 2d 32 35 36 2c 41 45 53 2d 31 32 38 2c 42 6c 6f 77 66 69 73 68 2a 0d 53 48 41 32 35 36 2c 53 48 41 35 31 32`

Thanks to an user on #ipfs@freenode I’ve received a work-in-progress (not yet public?) spec for secio.

The binary format should be protobuf, which is explained here: [Encoding | Protocol Buffers Documentation](https://developers.google.com/protocol-buffers/docs/encoding)

The serialization should be of this format:

> Propose {  
> Rand: 16 secure random bytes,  
> Pubkey: public key bytes,  
> Exchanges: comma separated string of supported key exchanges,  
> Ciphers: comma separated string of supported ciphers,  
> Hashes: comma separated string of supported hashes,  
> }

---

<div class="post-metadata">

**Author:** ![robinmonjo](https://avatars.discourse-cdn.com/v4/letter/r/71e660/32.png) [@robinmonjo](https://discuss.ipfs.tech/u/robinmonjo)\
**Post date:** [January 21, 2019, 10:16pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/16 "2019-01-21T22:16:05Z")

</div>

Hey @sebaseba !

I have started working on the mplex part and your post helped me a lot, thank you for sharing 🙂 ! I can relate to everything you said when “talking” with a local IPFS daemon (with encryption on).

However, when talking to one of the [bootstrap peers](https://github.com/ipfs/go-ipfs-config/blob/master/bootstrap_peers.go) of IPFS I see different results, it opens more than 3 streams and doesn’t send data every time. Here is a sample of what I get with peer `104.236.179.241:4001` :

```
data: <<1>>, flag: 0, flag_label: :new_stream, stream_id: 0
data: nil, flag: 0, flag_label: :new_stream, stream_id: 6
data: <<1>>, flag: 0, flag_label: :new_stream, stream_id: 1
data: nil, flag: 1, flag_label: :message_receiver, stream_id: 6
data: <<1>>, flag: 0, flag_label: :new_stream, stream_id: 2
data: nil, flag: 2, flag_label: :message_initiator, stream_id: 6
data: <<1>>, flag: 0, flag_label: :new_stream, stream_id: 3
data: nil, flag: 3, flag_label: :close_receiver, stream_id: 6

```

It doesn’t look to behave consistently. I’ll try to make sense of all of that later, I’ll keep you posted 🙂

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 22, 2019, 9:35am UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/17 "2019-01-22T09:35:39Z")

</div>

That’s nice. It’s always a pleasure to find out, that somebody appreciated what I’ve shared.

I’m not sure what I’m looking at, but it seems like the remote node opens 4 streams, instead of 3, and you open a new stream (id=6), where you first send something as a receiver (wrong) and then as an initiator and at the end you close it?

It could be also that the data is longer? I simplified it in my code/explanation regarding length that it’s just 1 byte, but it’s a varint. It’s 1 byte up to 254 chars, above it gets more complicated and becomes 2 bytes (or more).

Or maybe it’s just (slightly) different protocol with bootstrap nodes. Who knows.

I mean IPFS is so huge and done by so many devs without a consistent standard/documentation at once place, which causes a bunch of tiny differences or rather inconsistencies that show only when you rewrite the whole thing. Like for example a silly one: how `ls\n` works, when there is less than 2 options.

I will get at it when I’ll analyze and deeply understand secio first. 🙂  
Right now I’m somewhat busy. I’m on IRC: seba- #ipfs@freenode.net.

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 22, 2019, 4:21pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/18 "2019-01-22T16:21:07Z")

</div>

Last time we’ve seen that first 6 bytes are always the same, but what do they mean?  
`00 00 01 7c 0a 10`

I went further today with trying to understand _scio_. So, when we do the initial _secio_ handshake what we get back is first 4 bytes indicating length of data and the rest is protobuf serialized data of _Propose_ type, which is defined here [https://github.com/libp2p/go-libp2p-secio/blob/master/pb/spipe.proto](https://github.com/libp2p/go-libp2p-secio/blob/master/pb/spipe.proto)

So the first 4 bytes (in hex),

`00 00 01 7c`

actually mean 380 which is the length of the data after the first 4 bytes (we receive a total of 384 bytes).

How do we get 380 out of those 4 bytes?

We treat the 4 bytes as a single binary number (bigendian uint32).  
`00 = 00000000 = 0`  
`00 = 00000000 = 0`  
`01 = 00000001 = 124`  
`7c = 01111100 = 1`

`(01 7c) = (00000001 01111100) = 380`

Or put in an another (more math/code) way:  
`len = [byte1] * 256^3 + [byte2] * 256^2 + [byte3] * 256^1 + [byte4] * 256^0`  
`len = [byte1] << 3*8 + [byte2] << 2*8 + [byte3] << 8 + [byte4]`

In our case  
`len = 0 * 256^3 + 0 * 256^2 + 1 * 256^1 + 124 * 256^0`  
`len = 1 * 256 + 124 * 1 = 256 + 124 = 380`

So that gives us the length of data.

We read that much (380 bytes) of data and what we get is [_Propose_](https://github.com/libp2p/go-libp2p-secio/blob/master/pb/spipe.proto) encoded in [protobuf](https://developers.google.com/protocol-buffers/docs/encoding).

We’ll focus on the next two bytes:  
`0a 10`

This is already the beginning of _Propose_ encoded in protobuf.

It has 5 fields, numbered or ID-ed from 1 to 5 (`field_number`). All of them are length-delimited types (strings, bytes) which are marked in protobuf (`wire_type`) as 2.

In protobuf each field starts with a header byte followed by a byte (or more bytes as it’s varint) indicating length of data.

Header is defined similarly to mplex. Righter-most 3 bits are type (wire\_type) and the left part (first 5 bits) is the ID (field\_number) of the field.

So like in _mplex_, if we draw it graphically, where ID is D and type is T:

`D D D D D T T T`

For example type = 2 (string) and ID = 1 would be:

`0 0 0 0 1 0 1 0` , 00001 for ID, 010 for type

Codewise we do it exactly the same as in mplex. If we name the header byte as h.  
`type = h & 0x07`  
`id = h >> 3`

In hex `00001010` is `0a`, which is also what we’ve received.

The next byte is length of this data.

Hex `10` stands for 16 and we did notice last time that after the initial “static” bytes, we get 16 bytes of data, which are always different. In the _Propose_ definition it’s defined as _rand_ 16 bytes, which in crypto lang is usually named as [nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce).

After we read those 16 bytes of _nonce_, we’ve finished with the first field, so the next byte `12` is already the header of the next field. I’ll write this header in binary and I’ll put a space between the type and ID part.

`0x12 = 00010 010`

We can see again that it’s of string type, i.e. 010 = 2, but this time its ID is 2 and not 1. In the definition of _Propose_ it’s the _pubkey_ or public key.

The next **two** bytes are length and not just one.  
`ab 02`

How do we know it’s 2 and not 1? If we write the two bytes in binary:  
`0xab = 1 0 1 0 1 0 1 1`  
`0x02 = 0 0 0 0 0 0 1 0`

If the leftmost bit is 1, it means that the next byte should be taken in account as well. Which means that if the length of data is above 127, then 2 bytes are needed. (Probably it’s the same in mplex and libp2p protocol and not 254 as I wrote before, but I haven’t yet dwelved into.)

So how do we make sense out of this now?  
We can’t just join the numbers as before. It’s a bit more complex.

First we remove the left-most bit in `0xab`.  
`0xab = [1] 0 1 0 1 0 1 1 => 0x2b = 0 1 0 1 0 1 1`

Then we append the next byte 0x02 infront:  
`(0x02 0x2b) = 00000010 0101011 = 299`

We can do in code/math way:  
`length = [byte2] * 128 + ([byte1] & ~128)`  
`length = [byte2] << 7 + ([byte1] & ~128)`

So next 299 bytes are the public key, which I don’t yet understand the format as the key itself is 256 bytes long and 43 bytes are for me unknown.

Next are 3 fields all with a header byte and length byte, but inside they are comma-separate values, which are also ordered in preference.

From now on, I’ll leave out the hex/binary form and just write what we get.

First is _exchanges_.

We get the ID = 3, type = 2, length = 17. The data is:  
`P-256,P-384,P-521`

After that we have the field _ciphers_.

We get the ID = 4, type = 2, length = 24. The data is:  
`AES-256,AES-128,Blowfish`

At last we have the field _hashes_.

We get the ID = 5, type = 2, length = 13. The data is:  
`SHA256,SHA512`

---

<div class="post-metadata">

**Author:** ![whyrusleeping](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/whyrusleeping/32/34_2.png) [@whyrusleeping](https://discuss.ipfs.tech/u/whyrusleeping)\
**Post date:** [January 22, 2019, 4:26pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/19 "2019-01-22T16:26:57Z")

</div>

Great stuff @sebaseba! I would love to have to review the current in progress secio spec: [https://github.com/libp2p/specs/pull/106](https://github.com/libp2p/specs/pull/106)

---

<div class="post-metadata">

**Author:** ![sebaseba](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ipfs.tech/sebaseba/32/1615_2.png) [@sebaseba](https://discuss.ipfs.tech/u/sebaseba)\
**Post date:** [January 23, 2019, 1:10pm UTC](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658/20 "2019-01-23T13:10:55Z")

</div>

The public key data is also serialized using protobuf. How it was serialized, was a bit of a mystery at first as in the secio spec in progress the link points to [https://godoc.org/github.com/libp2p/go-libp2p-crypto#Key](https://godoc.org/github.com/libp2p/go-libp2p-crypto#Key), which doesn’t give us that much info.

Luckily an [another user wrote his own implementation of secio](https://discuss.ipfs.tech/t/implementing-secio/4423/6) and from that we can see the [protobuf definition of the public key serialization](https://github.com/robinmonjo/elixir_ipfs/blob/master/apps/secio/lib/secio/handshake/proto.ex).

I’ll write here the first few bytes in hex of the _pubkey_ field (ID = 2), which was protobuf decoded in the last post.  
`08 00 12 a6 02 30 82 01 22 30`

It has two fields: key type and the actual public key.

The first field _key type_ it’s of type `enum`, which in protobuf has type `0`. It can take 3 different values:

- 0 = RSA
- 1 = Ed25519
- 2 = Secp256k1

As `enum` in protobuf is treated as varint, length byte is not needed. Also our values are less than 127, so the next byte is just directly the value in binary.

In our case the first two bytes were  
`08 00`

If we write those in binary:  
`0x08 = 00001 000` (header, ID = 1, type = 0)  
`0x00 = 00000000` (data, enum value = 0)

So it’s a type 0 key, which, from the enum definition before, means it’s a RSA key.

The next field is the _public key_, it’s a protobuf type = 2 or string/byte array, so it has to have also length. In our case it has a length of 294 bytes. The relevant bytes for this are:  
`12 a6 02`

In binary it would be:  
`0x12 = 00010 010` (header, ID = 2, type = 2)  
`0xa6 = 1 0100110` (length, leftmost bit is 1, we have to read the next byte as well)  
`0x02 = 0 0000010` (length, leftmost bit is 0, this is the end of length)

We convert the last two to the correct length (the exact explanation why/how is in the previous post):  
`(0x02 << 7) + (0xa6 & ~128) = 294`

The next 294 bytes of data is in our case the RSA public key.  
`30 82 01 22 30` and so on.

If we google those bytes we get for [example this post](https://community.oracle.com/thread/1530420), which talks about the first bytes of ASN.1 DER RSAPublicKey.

[Next page](https://discuss.ipfs.tech/t/ipfs-ping-protocol/4658.md?page=2)
