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) + } +}