lnwire: add new compatibility parsing more for onion error chan updates

In this commit, we add a compatibility mode for older version of
clightning to ensure that we're able to properly parse all their channel
updates. An older version of c-lightning would send out encapsulated
onion error message with an additional type byte. This would throw off
our parsing as we didn't expect the type byte, and so we always 2 bytes
off. In order to ensure that we're able to parse these messages and make
adjustments to our path finding, we'll first check to see if the type
byte is there, if so, then we'll snip off two bytes from the front and
continue with parsing. if the bytes aren't found, then we can proceed as
normal and parse the request.
This commit is contained in:
Olaoluwa Osuntokun 2018-08-31 17:24:20 -07:00
parent 2f1b024679
commit 31e92c4ff2
No known key found for this signature in database
GPG Key ID: 964EA263DD637C21
2 changed files with 117 additions and 9 deletions

@ -1,6 +1,7 @@
package lnwire
import (
"bufio"
"crypto/sha256"
"encoding/binary"
"fmt"
@ -478,6 +479,44 @@ func (f FailInvalidOnionKey) Error() string {
return fmt.Sprintf("InvalidOnionKey(onion_sha=%x)", f.OnionSHA256[:])
}
// parseChannelUpdateCompatabilityMode will attempt to parse a channel updated
// encoded into an onion error payload in two ways. First, we'll try the
// compatibility oriented version wherein we'll _skip_ the length prefixing on
// the channel update message. Older versions of c-lighting do this so we'll
// attempt to parse these messages in order to retain compatibility. If we're
// unable to pull out a fully valid version, then we'll fall back to the
// regular parsing mechanism which includes the length prefix an NO type byte.
func parseChannelUpdateCompatabilityMode(r *bufio.Reader,
chanUpdate *ChannelUpdate, pver uint32) error {
// We'll peek out two bytes from the buffer without advancing the
// buffer so we can decide how to parse the remainder of it.
maybeTypeBytes, err := r.Peek(2)
if err != nil {
return err
}
// Some nodes well prefix an additional set of bytes in front of their
// channel updates. These bytes will _almost_ always be 258 or the type
// of the ChannelUpdate message.
typeInt := binary.BigEndian.Uint16(maybeTypeBytes)
if typeInt == MsgChannelUpdate {
// At this point it's likely the case that this is a channel
// update message with its type prefixed, so we'll snip off the
// first two bytes and parse it as normal.
var throwAwayTypeBytes [2]byte
_, err := r.Read(throwAwayTypeBytes[:])
if err != nil {
return err
}
}
// At this pint, we've either decided to keep the entire thing, or snip
// off the first two bytes. In either case, we can just read it as
// normal.
return chanUpdate.Decode(r, pver)
}
// FailTemporaryChannelFailure is if an otherwise unspecified transient error
// occurs for the outgoing channel (eg. channel capacity reached, too many
// in-flight htlcs)
@ -491,9 +530,6 @@ type FailTemporaryChannelFailure struct {
Update *ChannelUpdate
}
// TODO(roasbeef): any error with an Update needs to include the edge+vertex of
// failure
// NewTemporaryChannelFailure creates new instance of the FailTemporaryChannelFailure.
func NewTemporaryChannelFailure(update *ChannelUpdate) *FailTemporaryChannelFailure {
return &FailTemporaryChannelFailure{Update: update}
@ -530,7 +566,9 @@ func (f *FailTemporaryChannelFailure) Decode(r io.Reader, pver uint32) error {
if length != 0 {
f.Update = &ChannelUpdate{}
return f.Update.Decode(r, pver)
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), f.Update, pver,
)
}
return nil
@ -610,7 +648,9 @@ func (f *FailAmountBelowMinimum) Decode(r io.Reader, pver uint32) error {
}
f.Update = ChannelUpdate{}
return f.Update.Decode(r, pver)
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
@ -681,7 +721,9 @@ func (f *FailFeeInsufficient) Decode(r io.Reader, pver uint32) error {
}
f.Update = ChannelUpdate{}
return f.Update.Decode(r, pver)
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
@ -752,7 +794,9 @@ func (f *FailIncorrectCltvExpiry) Decode(r io.Reader, pver uint32) error {
}
f.Update = ChannelUpdate{}
return f.Update.Decode(r, pver)
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
@ -812,7 +856,9 @@ func (f *FailExpiryTooSoon) Decode(r io.Reader, pver uint32) error {
}
f.Update = ChannelUpdate{}
return f.Update.Decode(r, pver)
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.
@ -879,7 +925,9 @@ func (f *FailChannelDisabled) Decode(r io.Reader, pver uint32) error {
}
f.Update = ChannelUpdate{}
return f.Update.Decode(r, pver)
return parseChannelUpdateCompatabilityMode(
bufio.NewReader(r), &f.Update, pver,
)
}
// Encode writes the failure in bytes stream.

@ -1,7 +1,9 @@
package lnwire
import (
"bufio"
"bytes"
"encoding/binary"
"reflect"
"testing"
)
@ -69,3 +71,61 @@ func TestEncodeDecodeCode(t *testing.T) {
}
}
}
// TestChannelUpdateCompatabilityParsing tests that we're able to properly read
// out channel update messages encoded in an onion error payload that was
// written in the legacy (type prefixed) format.
func TestChannelUpdateCompatabilityParsing(t *testing.T) {
t.Parallel()
// We'll start by taking out test channel update, and encoding it into
// a set of raw bytes.
var b bytes.Buffer
if err := testChannelUpdate.Encode(&b, 0); err != nil {
t.Fatalf("unable to encode chan update: %v", err)
}
// Now that we have the set of bytes encoded, we'll ensure that we're
// able to decode it using our compatibility method, as it's a regular
// encoded channel update message.
var newChanUpdate ChannelUpdate
err := parseChannelUpdateCompatabilityMode(
bufio.NewReader(&b), &newChanUpdate, 0,
)
if err != nil {
t.Fatalf("unable to parse channel update: %v", err)
}
// At this point, we'll ensure that we get the exact same failure out
// on the other side.
if !reflect.DeepEqual(testChannelUpdate, newChanUpdate) {
t.Fatalf("mismatched channel updates: %v", err)
}
// We'll now reset then re-encoded the same channel update to try it in
// the proper compatible mode.
b.Reset()
// Before we encode the update itself, we'll also write out the 2-byte
// type in order to simulate the compat mode.
var tByte [2]byte
binary.BigEndian.PutUint16(tByte[:], MsgChannelUpdate)
b.Write(tByte[:])
if err := testChannelUpdate.Encode(&b, 0); err != nil {
t.Fatalf("unable to encode chan update: %v", err)
}
// We should be able to properly parse the encoded channel update
// message even with the extra two bytes.
var newChanUpdate2 ChannelUpdate
err = parseChannelUpdateCompatabilityMode(
bufio.NewReader(&b), &newChanUpdate2, 0,
)
if err != nil {
t.Fatalf("unable to parse channel update: %v", err)
}
if !reflect.DeepEqual(newChanUpdate2, newChanUpdate) {
t.Fatalf("mismatched channel updates: %v", err)
}
}