github.com/decred/dcrlnd@v0.7.6/lntest/itest/lnd_network_test.go (about) 1 package itest 2 3 import ( 4 "context" 5 "fmt" 6 stdnet "net" 7 "strings" 8 "time" 9 10 "github.com/decred/dcrlnd" 11 "github.com/decred/dcrlnd/lnrpc" 12 "github.com/decred/dcrlnd/lntest" 13 "github.com/stretchr/testify/require" 14 ) 15 16 // testNetworkConnectionTimeout checks that the connectiontimeout is taking 17 // effect. It creates a node with a small connection timeout value, and connects 18 // it to a non-routable IP address. 19 func testNetworkConnectionTimeout(net *lntest.NetworkHarness, t *harnessTest) { 20 // Bind to a random port on localhost but never actually accept any 21 // connections. This makes any connection attempts timeout. 22 l, err := stdnet.Listen("tcp", "127.0.0.1:0") 23 require.NoError(t.t, err) 24 defer l.Close() 25 26 var ( 27 ctxt, _ = context.WithTimeout( 28 context.Background(), defaultTimeout, 29 ) 30 // testPub is a random public key for testing only. 31 testPub = "0332bda7da70fefe4b6ab92f53b3c4f4ee7999" + 32 "f312284a8e89c8670bb3f67dbee2" 33 34 // testHost is the previously bound addr that will not answer 35 // to any conn attempts. 36 testHost = l.Addr().String() 37 ) 38 39 // First, test the global timeout settings. 40 // Create Carol with a connection timeout of 1 millisecond. 41 carol := net.NewNode(t.t, "Carol", []string{"--connectiontimeout=1ms"}) 42 defer shutdownAndAssert(net, t, carol) 43 44 // Try to connect Carol to a non-routable IP address, which should give 45 // us a timeout error. 46 req := &lnrpc.ConnectPeerRequest{ 47 Addr: &lnrpc.LightningAddress{ 48 Pubkey: testPub, 49 Host: testHost, 50 }, 51 } 52 assertTimeoutError(ctxt, t, carol, req) 53 54 // Second, test timeout on the connect peer request. 55 // Create Dave with the default timeout setting. 56 dave := net.NewNode(t.t, "Dave", nil) 57 defer shutdownAndAssert(net, t, dave) 58 59 // Try to connect Dave to a non-routable IP address, using a timeout 60 // value of 1ms, which should give us a timeout error immediately. 61 req = &lnrpc.ConnectPeerRequest{ 62 Addr: &lnrpc.LightningAddress{ 63 Pubkey: testPub, 64 Host: testHost, 65 }, 66 Timeout: 1, 67 } 68 assertTimeoutError(ctxt, t, dave, req) 69 } 70 71 // testReconnectAfterIPChange verifies that if a persistent inbound node changes 72 // its listening address then it's peer will still be able to reconnect to it. 73 func testReconnectAfterIPChange(net *lntest.NetworkHarness, t *harnessTest) { 74 // In this test, the following network will be set up. A single 75 // dash line represents a peer connection and a double dash line 76 // represents a channel. 77 // Charlie will create a connection to Dave so that Dave is the inbound 78 // peer. This will be made a persistent connection for Charlie so that 79 // Charlie will attempt to reconnect to Dave if Dave restarts. 80 // A channel will be opened between Dave and Alice to ensure that any 81 // NodeAnnouncements that Dave sends will reach Alice. 82 // The connection between Alice and Charlie ensures that Charlie 83 // receives all of Dave's NodeAnnouncements. 84 // The desired behaviour is that if Dave changes his P2P IP address then 85 // Charlie should still be able to reconnect to him. 86 // 87 // /------- Charlie <-----\ 88 // | | 89 // v | 90 // Dave <===============> Alice 91 92 // The first thing we will test is the case where Dave advertises two 93 // external IP addresses and then switches from the first one listed 94 // to the second one listed. The desired behaviour is that Charlie will 95 // attempt both of Dave's advertised addresses when attempting to 96 // reconnect. 97 98 // Create a new node, Charlie. 99 charlie := net.NewNode(t.t, "Charlie", nil) 100 defer shutdownAndAssert(net, t, charlie) 101 102 // We derive two ports for Dave, and we initialise his node with 103 // these ports advertised as `--externalip` arguments. 104 ip1 := lntest.NextAvailablePort() 105 ip2 := lntest.NextAvailablePort() 106 107 advertisedAddrs := []string{ 108 fmt.Sprintf("127.0.0.1:%d", ip1), 109 fmt.Sprintf("127.0.0.1:%d", ip2), 110 } 111 112 var daveArgs []string 113 for _, addr := range advertisedAddrs { 114 daveArgs = append(daveArgs, "--externalip="+addr) 115 } 116 117 // withP2PPort is a helper closure used to set the P2P port that a node 118 // should use. 119 var withP2PPort = func(port int) lntest.NodeOption { 120 return func(cfg *lntest.BaseNodeConfig) { 121 cfg.P2PPort = port 122 } 123 } 124 125 // Create a new node, Dave, and ensure that his initial P2P port is 126 // ip1 derived above. 127 dave := net.NewNode(t.t, "Dave", daveArgs, withP2PPort(ip1)) 128 defer shutdownAndAssert(net, t, dave) 129 130 // Subscribe to graph notifications from Charlie so that we can tell 131 // when he receives Dave's NodeAnnouncements. 132 ctxb := context.Background() 133 charlieSub := subscribeGraphNotifications(ctxb, t, charlie) 134 defer close(charlieSub.quit) 135 136 // Connect Alice to Dave and Charlie. 137 net.ConnectNodes(t.t, net.Alice, dave) 138 net.ConnectNodes(t.t, net.Alice, charlie) 139 140 // We'll then go ahead and open a channel between Alice and Dave. This 141 // ensures that Charlie receives the node announcement from Alice as 142 // part of the announcement broadcast. 143 chanPoint := openChannelAndAssert( 144 t, net, net.Alice, dave, lntest.OpenChannelParams{ 145 Amt: 1000000, 146 }, 147 ) 148 defer closeChannelAndAssert(t, net, net.Alice, chanPoint, false) 149 150 // waitForNodeAnnouncement is a closure used to wait on the given graph 151 // subscription for a node announcement from a node with the given 152 // public key. It also waits for the node announcement that advertises 153 // a particular set of addresses. 154 waitForNodeAnnouncement := func(graphSub graphSubscription, 155 nodePubKey string, addrs []string) { 156 157 for { 158 select { 159 case graphUpdate := <-graphSub.updateChan: 160 nextUpdate: 161 for _, update := range graphUpdate.NodeUpdates { 162 if update.IdentityKey != nodePubKey { 163 continue 164 } 165 166 addrMap := make(map[string]bool) 167 for _, addr := range update.NodeAddresses { 168 addrMap[addr.GetAddr()] = true 169 } 170 171 for _, addr := range addrs { 172 if !addrMap[addr] { 173 continue nextUpdate 174 } 175 } 176 177 return 178 } 179 180 case err := <-graphSub.errChan: 181 t.Fatalf("unable to recv graph update: %v", err) 182 183 case <-time.After(defaultTimeout): 184 t.Fatalf("did not receive node ann update") 185 } 186 } 187 } 188 189 // Wait for Charlie to receive Dave's initial NodeAnnouncement. 190 waitForNodeAnnouncement(charlieSub, dave.PubKeyStr, advertisedAddrs) 191 192 // Now create a persistent connection between Charlie and Bob with no 193 // channels. Charlie is the outbound node and Bob is the inbound node. 194 net.ConnectNodesPerm(t.t, charlie, dave) 195 196 // Assert that Dave and Charlie are connected 197 assertConnected(t, dave, charlie) 198 199 // Change Dave's P2P port to the second IP address that he advertised 200 // and restart his node. 201 dave.Cfg.P2PPort = ip2 202 err := net.RestartNode(dave, nil) 203 require.NoError(t.t, err) 204 205 // assert that Dave and Charlie reconnect successfully after Dave 206 // changes to his second advertised address. 207 assertConnected(t, dave, charlie) 208 209 // Next we test the case where Dave changes his listening address to one 210 // that was not listed in his original advertised addresses. The desired 211 // behaviour is that Charlie will update his connection requests to Dave 212 // when he receives the Node Announcement from Dave with his updated 213 // address. 214 215 // Change Dave's listening port and restart. 216 dave.Cfg.P2PPort = lntest.NextAvailablePort() 217 dave.Cfg.ExtraArgs = []string{ 218 fmt.Sprintf( 219 "--externalip=127.0.0.1:%d", dave.Cfg.P2PPort, 220 ), 221 } 222 err = net.RestartNode(dave, nil) 223 require.NoError(t.t, err) 224 225 // Show that Charlie does receive Dave's new listening address in 226 // a Node Announcement. 227 waitForNodeAnnouncement( 228 charlieSub, dave.PubKeyStr, 229 []string{fmt.Sprintf("127.0.0.1:%d", dave.Cfg.P2PPort)}, 230 ) 231 232 // assert that Dave and Charlie do reconnect after Dave changes his P2P 233 // address to one not listed in Dave's original advertised list of 234 // addresses. 235 assertConnected(t, dave, charlie) 236 } 237 238 // assertTimeoutError asserts that a connection timeout error is raised. A 239 // context with a default timeout is used to make the request. If our customized 240 // connection timeout is less than the default, we won't see the request context 241 // times out, instead a network connection timeout will be returned. 242 func assertTimeoutError(ctxt context.Context, t *harnessTest, 243 node *lntest.HarnessNode, req *lnrpc.ConnectPeerRequest) { 244 245 t.t.Helper() 246 247 err := connect(ctxt, node, req) 248 249 // a DeadlineExceeded error will appear in the context if the above 250 // ctxtTimeout value is reached. 251 require.NoError(t.t, ctxt.Err(), "context time out") 252 253 // Check that the network returns a timeout error. 254 require.Containsf( 255 t.t, err.Error(), "i/o timeout", 256 "expected to get a timeout error, instead got: %v", err, 257 ) 258 } 259 260 func connect(ctxt context.Context, node *lntest.HarnessNode, 261 req *lnrpc.ConnectPeerRequest) error { 262 263 syncTimeout := time.After(15 * time.Second) 264 ticker := time.NewTicker(time.Millisecond * 100) 265 defer ticker.Stop() 266 267 for { 268 select { 269 case <-ticker.C: 270 _, err := node.ConnectPeer(ctxt, req) 271 // If there's no error, return nil 272 if err == nil { 273 return err 274 } 275 // If the error is no ErrServerNotActive, return it. 276 // Otherwise, we will retry until timeout. 277 if !strings.Contains(err.Error(), 278 dcrlnd.ErrServerNotActive.Error()) { 279 280 return err 281 } 282 case <-syncTimeout: 283 return fmt.Errorf("chain backend did not " + 284 "finish syncing") 285 } 286 } 287 return nil 288 }