From 31e92c4ff2a558c2595fdb023832d834219a9012 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Fri, 31 Aug 2018 17:24:20 -0700 Subject: [PATCH] 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. --- lnwire/onion_error.go | 66 ++++++++++++++++++++++++++++++++------ lnwire/onion_error_test.go | 60 ++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 9 deletions(-) diff --git a/lnwire/onion_error.go b/lnwire/onion_error.go index cde35165..43b3c766 100644 --- a/lnwire/onion_error.go +++ b/lnwire/onion_error.go @@ -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. diff --git a/lnwire/onion_error_test.go b/lnwire/onion_error_test.go index 6d68991b..9b2ab101 100644 --- a/lnwire/onion_error_test.go +++ b/lnwire/onion_error_test.go @@ -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) + } +}