diff --git a/channeld/channeld.c b/channeld/channeld.c index 472c43a09a0a..a6bf219ad205 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -545,10 +545,8 @@ static void handle_peer_splice_locked(struct peer *peer, const u8 *msg) static void send_peer_our_alt_address(struct peer *peer) { struct pubkey node_id; - if (pubkey_from_node_id(&node_id, &peer->id)) { - u8 *msg = towire_peer_alt_address(peer, &node_id, peer->our_alt_addr); - peer_write(peer->pps, take(msg)); - } + if (pubkey_from_node_id(&node_id, &peer->id)) + peer_write(peer->pps, take(towire_peer_alt_address(peer, &node_id, peer->our_alt_addr))); } static void handle_peer_channel_ready(struct peer *peer, const u8 *msg) @@ -5702,11 +5700,27 @@ static void handle_dev_quiesce(struct peer *peer, const u8 *msg) maybe_send_stfu(peer); } +static void handle_channeld_alt_address(struct peer *peer, const u8 *msg) +{ + struct pubkey peer_pk; + u8 *our_alt_addr; + + if (!fromwire_channeld_alt_address(peer, msg, &peer_pk, &our_alt_addr)) { + master_badmsg(WIRE_CHANNELD_ALT_ADDRESS, msg); + } + + if (pubkey_from_node_id(&peer_pk, &peer->id)) + peer_write(peer->pps, take(towire_peer_alt_address(peer, &peer_pk, our_alt_addr))); +} + static void req_in(struct peer *peer, const u8 *msg) { enum channeld_wire t = fromwire_peektype(msg); switch (t) { + case WIRE_CHANNELD_ALT_ADDRESS: + handle_channeld_alt_address(peer, msg); + return; case WIRE_CHANNELD_FUNDING_DEPTH: handle_funding_depth(peer, msg); return; diff --git a/channeld/channeld_wire.csv b/channeld/channeld_wire.csv index 2cec2669dae9..dc7f2f2ae76b 100644 --- a/channeld/channeld_wire.csv +++ b/channeld/channeld_wire.csv @@ -352,3 +352,9 @@ msgdata,channeld_upgraded,new_type,channel_type, # Tell peer about our latest and greatest blockheight. msgtype,channeld_blockheight,1012 msgdata,channeld_blockheight,blockheight,u32, + +# master -> channeld Send peer alternative addresses +msgtype,channeld_alt_address,1014 +msgdata,channeld_alt_address,node_id,point, +msgdata,channeld_alt_address,alt_addr_len,u16, +msgdata,channeld_alt_address,alt_addr,u8,alt_addr_len, diff --git a/connectd/connectd.c b/connectd/connectd.c index 4c9302b77f4b..0575c7e5a785 100644 --- a/connectd/connectd.c +++ b/connectd/connectd.c @@ -520,8 +520,7 @@ void handle_peer_alt_address(struct peer *peer, const u8 *msg) master_badmsg(WIRE_PEER_ALT_ADDRESS, msg); } - msg = towire_connectd_alt_address(NULL, &peer_id, peer_alt_addr); - daemon_conn_send(peer->daemon->master, take(msg)); + daemon_conn_send(peer->daemon->master, take(towire_connectd_alt_address(NULL, &peer_id, peer_alt_addr))); tal_free(peer_alt_addr); } diff --git a/contrib/startup_regtest.sh b/contrib/startup_regtest.sh index 5a965164de92..92fdffaf6afd 100755 --- a/contrib/startup_regtest.sh +++ b/contrib/startup_regtest.sh @@ -184,7 +184,10 @@ start_nodes() { log-level=debug log-file=$LIGHTNING_DIR/l$i/log addr=localhost:$socket - alt-addr=127.21.21.21:$socket + # alt-addr=127.20.20.20:$socket #TODO, Test what happends when both default and RPC are used. + alt-bind-addr=127.21.21.21:$socket + alt-bind-addr=127.22.22.22:$socket + bind-addr=127.29.29.29:$socket allow-deprecated-apis=false developer dev-fast-gossip diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index 67ec8720c6b5..17e5d913f1b6 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -1468,6 +1468,7 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) break; /* And we never get these from channeld. */ case WIRE_CHANNELD_INIT: + case WIRE_CHANNELD_ALT_ADDRESS: case WIRE_CHANNELD_FUNDING_DEPTH: case WIRE_CHANNELD_OFFER_HTLC: case WIRE_CHANNELD_FULFILL_HTLC: @@ -2341,3 +2342,63 @@ static const struct json_command dev_quiesce_command = { .dev_only = true, }; AUTODATA(json_command, &dev_quiesce_command); + +static struct command_result *json_alt_addr(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct node_id *peer_node_id; + struct pubkey p_pk; //maybe take this away again (send node_id struct instead) to clean it up? + struct peer *peer; + struct channel *channel; + const char *our_alt_addr; + bool more_than_one; + + if (!param_check(cmd, buffer, params, + p_req("node_id", param_node_id, &peer_node_id), + p_req("alt_addr", param_string, &our_alt_addr), + NULL)) + return command_param_failed(); + + peer = peer_by_id(cmd->ld, peer_node_id); + if (!peer) { + return command_fail(cmd, JSONRPC2_INVALID_REQUEST, + "No such peer: %s", + fmt_node_id(cmd, peer_node_id)); + } + + channel = peer_any_channel(peer, channel_state_can_add_htlc, &more_than_one); + if (!channel || !channel->owner) + return command_fail(cmd, LIGHTNINGD, "Peer bad state"); + /* This is a dev command: fix the api if you need this! */ + if (more_than_one) + return command_fail(cmd, LIGHTNINGD, "More than one channel"); + + if (command_check_only(cmd)) + return command_check_done(cmd); + + if (pubkey_from_node_id(&p_pk, peer_node_id)) { + //TODO, make 'our_alt_addr into a double pointer, array of arrays, for sending many. + subd_send_msg(channel->owner, take(towire_channeld_alt_address(peer, &p_pk, (u8 *)our_alt_addr))); + } + + //TODO, After adding the 'our_alt_addr' here, + // we need to check against that when accepting the incoming connection. + //ADD our_alt_addr into our db under the peer with the id we specify here to create the whitelist for later confirmation when accepting incomming connection. + wallet_add_alt_addr(cmd->ld->wallet->db, peer_node_id, our_alt_addr, true); + + //TODO, We need to add this to 'listnodes' command too! + //TODO, How can we send the peer msg without having a channel to the peer? + + return command_success(cmd, json_stream_success(cmd)); +} + +static const struct json_command alt_addr_command = { + "alt-addr", + "developer", + json_alt_addr, + "Select an alternative private address for peer-to-peer connections", + .dev_only = true, +}; +AUTODATA(json_command, &alt_addr_command); diff --git a/lightningd/connect_control.c b/lightningd/connect_control.c index 2aa7e686e865..04fb8a05d332 100644 --- a/lightningd/connect_control.c +++ b/lightningd/connect_control.c @@ -333,11 +333,23 @@ static void try_connect(const tal_t *ctx, const struct wireaddr_internal *addrhint, bool dns_fallback) { + const struct wireaddr_internal *peer_alt_addr = NULL; struct delayed_reconnect *d; struct peer *peer; - const struct wireaddr_internal *alt_addr; - alt_addr = wallet_get_peer_alt_addr(ld->wallet, id); + /* This is where we handle the whitelist of alt addr, + TODO: whitelist of addr(s!)*/ + if (ld->alt_bind_addr) { + peer_alt_addr = wallet_get_peer_alt_addr(ld->wallet, id); + /* if (peer_alt_addr) { //TODO: Fix so the whitelist works! It wasn't here... + const char *formatted_peer_addr = fmt_wireaddr_internal(ctx, peer_alt_addr); + if (strcmp((char *)ld->alt_bind_addr, formatted_peer_addr) != 0) { + log_debug(ld->log, "No match for alt_bind_addr and peer_alt_addr: '%s' != '%s'\n", ld->alt_bind_addr, formatted_peer_addr); + tal_free(peer_alt_addr); + peer_alt_addr = NULL; + } + } */ + } /* Don't stack, unless this is an instant reconnect */ d = delayed_reconnect_map_get(ld->delayed_reconnect_map, id); @@ -352,7 +364,7 @@ static void try_connect(const tal_t *ctx, d = tal(ctx, struct delayed_reconnect); d->ld = ld; d->id = *id; - d->addrhint = tal_dup_or_null(d, struct wireaddr_internal, alt_addr ? alt_addr : addrhint); + d->addrhint = tal_dup_or_null(d, struct wireaddr_internal, peer_alt_addr ? peer_alt_addr : addrhint); d->dns_fallback = dns_fallback; delayed_reconnect_map_add(ld->delayed_reconnect_map, d); tal_add_destructor(d, destroy_delayed_reconnect); @@ -566,13 +578,13 @@ static void handle_peer_alt_addr_in(struct lightningd *ld, const u8 *msg) if (!fromwire_connectd_alt_address(tmpctx, msg, &peer_node_id, &peer_alt_addr)) { log_broken(ld->log, "Malformed peer_alt_addr_msg: %s", - tal_hex(tmpctx, msg)); + tal_hex(tmpctx, msg)); return; } struct node_id id; node_id_from_pubkey(&id, &peer_node_id); - wallet_add_peer_alt_addr(ld->wallet->db, &id, (char *)peer_alt_addr); + wallet_add_alt_addr(ld->wallet->db, &id, (char *)peer_alt_addr, false); } static void connectd_start_shutdown_reply(struct subd *connectd, diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 0434990a4b26..7b9225bf0fbe 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -256,6 +256,8 @@ static struct lightningd *new_lightningd(const tal_t *ctx) ld->recover_secret = NULL; ld->db_upgrade_ok = NULL; ld->num_startup_connects = 0; + ld->our_alt_addr = NULL; + ld->alt_bind_addr = NULL; /* --experimental-upgrade-protocol */ ld->experimental_upgrade_protocol = false; diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 7e44b9e1c8e0..a4d884f23b7c 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -194,7 +194,10 @@ struct lightningd { struct wireaddr *announceable; /* Alternative address for peer connections not publicly announced */ - u8 *our_alt_addr; + u8 *our_alt_addr; //TODO, make into double pointer + + /* Alternative binding address for peer connections not publicly announced */ + u8 *alt_bind_addr; //TODO, make into double pointer /* Current node announcement (if any) */ const u8 *node_announcement; diff --git a/lightningd/options.c b/lightningd/options.c index 121043d3ee4a..bf734fda4a91 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -450,6 +450,26 @@ static char *opt_add_alt_addr(const char *arg, struct lightningd *ld) return opt_add_addr_withtype(arg, ld, ADDR_LISTEN); } +static char *opt_add_alt_bind_addr(const char *arg, struct lightningd *ld) +{ + assert(arg != NULL); + + //TODO, Add somewhat a flag here to check later with whitelist. + //TODO, This needs to be a list later I think... + ld->alt_bind_addr = tal_free(ld->alt_bind_addr); + ld->alt_bind_addr = (u8 *)tal_strdup(ld, arg); //tal_arr_expand? + + return opt_add_addr_withtype(arg, ld, ADDR_LISTEN); +} + +static char *opt_add_alt_announce_addr(const char *arg, struct lightningd *ld) +{ + assert(arg != NULL); + //TODO, MAKE THIS FUNCTION. + + return opt_add_addr_withtype(arg, ld, ADDR_LISTEN); +} + static char *opt_subdaemon(const char *arg, struct lightningd *ld) { char *subdaemon; @@ -1610,9 +1630,17 @@ static void register_opts(struct lightningd *ld) opt_set_uintval, opt_show_uintval, &ld->config.ip_discovery_port, "Sets the public TCP port to use for announcing discovered IPs."); + clnopt_witharg("--alt-addr", OPT_MULTI, opt_add_alt_addr, NULL, ld, - "Set an alternative IP address (v4 or v6) to use selectively for private reconnections with established peers."); + "Set an alternative IP address (v4 or v6) to use by default for private reconnections with established peers."); + clnopt_witharg("--alt-bind-addr", OPT_MULTI, opt_add_alt_bind_addr, NULL, + ld, + "Bind an alternative IP address (v4 or v6) for listening, but do not announce or use automatically."); + clnopt_witharg("--alt-announce-addr", OPT_MULTI, opt_add_alt_announce_addr, NULL, + ld, + "Provide a reserved IP address (bound by --alt-bind-addr) to established channel peers."); + opt_register_noarg("--offline", opt_set_offline, ld, "Start in offline-mode (do not automatically reconnect and do not accept incoming connections)"); clnopt_witharg("--autolisten", OPT_SHOWBOOL, @@ -2209,6 +2237,8 @@ bool is_known_opt_cb_arg(char *(*cb_arg)(const char *, void *)) || cb_arg == (void *)opt_add_bind_addr || cb_arg == (void *)opt_add_announce_addr || cb_arg == (void *)opt_add_alt_addr + || cb_arg == (void *)opt_add_alt_bind_addr + || cb_arg == (void *)opt_add_alt_announce_addr || cb_arg == (void *)opt_subdaemon || cb_arg == (void *)opt_set_db_upgrade || cb_arg == (void *)arg_log_to_file diff --git a/tests/test_connection.py b/tests/test_connection.py index 8b90f75551c6..2855052e9ad8 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -131,6 +131,87 @@ def test_connect_with_alt_addr(node_factory, bitcoind): raise +def test_connect_with_alt_addr_rpc(node_factory, bitcoind): + logging.basicConfig(level=logging.INFO) + + # Set up nodes + logging.info("Setting up two nodes with the capability to reconnect") + l1 = node_factory.get_node(may_reconnect=True) + l2 = node_factory.get_node(may_reconnect=True) + + # Initial connection + logging.info(f"Initial connection from l1 to l2 using localhost and port {l2.port}") + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + + # Checking initial connection state + logging.info("Waiting for both nodes to report they are connected...") + wait_for(lambda: only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['connected']) + wait_for(lambda: only_one(l2.rpc.listpeers(l1.info['id'])['peers'])['connected']) + + # Fund channel and log the event + logging.info(f"Funding channel between l1 and l2 with 10**6 satoshis") + l1.fundchannel(l2, 10**6) + + # Send the alt-addr from l2 to l1 + alt_addr = '127.21.21.21' + addr_without_bind = '127.22.22.22' + l2.rpc.alt_addr(l1.info['id'], f'{alt_addr}:{l2.port}') + + # Modifying node configuration to use an alternative address + logging.info(f"Stopping l2 to change its address to {alt_addr}:{l2.port}") + l2.stop() + l2.daemon.opts['alt-bind-addr'] = f'{alt_addr}:{l2.port}' + l2.start() + logging.info("Restarted l2 with bind-addr") + + # Verification of the alternative address setting + logging.info("Verifying bind address setting on l2") + try: + binding = l2.rpc.getinfo()['binding'] + assert len(binding) > 0, "No binding found for l2" + assert any(bind['address'] == alt_addr for bind in binding), f"Expected bind-addr {alt_addr}, found {binding}" + + except Exception as e: + logging.error(f"Bind address not set correctly: {e}") + raise + + # Reconnection using the alternative address + logging.info("Attempting to reconnect using the new alternative address") + try: + if any(peer['connected'] for peer in l1.rpc.listpeers()['peers']): + l1.rpc.disconnect(l2.info['id'], force=True) + l1.rpc.connect(l2.info['id'], alt_addr, l2.port) + except Exception as e: + logging.error(f"Error reconnecting nodes using alternative address: {e}") + raise + + # Verify the connection using the new address + logging.info("Verifying new connection details") + try: + connected_peer = l1.rpc.getpeer(l2.info['id']) + assert connected_peer['connected'], "Peers not connected" + assert connected_peer['netaddr'][0].startswith(alt_addr), f"Connection not using alt-addr: {connected_peer['netaddr'][0]}" + except Exception as e: + logging.error(f"Error verifying connection using alt-addr: {e}") + raise + + # Disconnect and attempt to connect using the addr_without_bind + l2.rpc.alt_addr(l1.info['id'], f'{addr_without_bind}:{l2.port}') + l2.stop() + l2.start() + try: + l1.rpc.connect(l2.info['id'], addr_without_bind, l2.port) + logging.error("Connection should not be successful using addr_without_bind") + assert False, "Connection should fail using addr_without_bind" + except Exception as e: + logging.info(f"Expected failure connecting using addr_without_bind: {e}") + + # Final verification + logging.info("Verifying no connection using addr_without_bind") + connected_peer = l1.rpc.listpeers(l2.info['id'])['peers'] + assert len(connected_peer) == 0 or not connected_peer[0]['connected'], "Peer should not be connected using addr_without_bind" + + def test_remote_addr(node_factory, bitcoind): """Check address discovery (BOLT1 #917) init remote_addr works as designed: diff --git a/wallet/db.c b/wallet/db.c index 79f82c27cce9..d1923e86dd17 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -1021,7 +1021,8 @@ static struct migration dbmigrations[] = { {SQL("ALTER TABLE channels ADD remote_htlc_minimum_msat BIGINT DEFAULT NULL;"), NULL}, {SQL("ALTER TABLE channels ADD last_stable_connection BIGINT DEFAULT 0;"), NULL}, {NULL, migrate_initialize_alias_local}, - {SQL("ALTER TABLE peers ADD COLUMN alt_addr TEXT;"), NULL}, + {SQL("ALTER TABLE peers ADD COLUMN peer_alt_addr TEXT DEFAULT '';"), NULL}, + {SQL("ALTER TABLE peers ADD COLUMN our_alt_addr TEXT DEFAULT '';"), NULL}, }; /** diff --git a/wallet/wallet.c b/wallet/wallet.c index ed90b3685505..834a5116342b 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -1027,16 +1027,21 @@ static struct peer *wallet_peer_load(struct wallet *w, const u64 dbid) return peer; } -void wallet_add_peer_alt_addr(struct db *db, const struct node_id *node_id, const char *alt_addr) +void wallet_add_alt_addr(struct db *db, const struct node_id *node_id, const char *alt_addr, bool is_our_addr) { struct db_stmt *stmt; - if (alt_addr != NULL) { - stmt = db_prepare_v2(db, SQL("UPDATE peers SET alt_addr=? WHERE node_id=?")); - db_bind_text(stmt, alt_addr); + if (!alt_addr) { + return; + } + + if (is_our_addr) { + stmt = db_prepare_v2(db, SQL("UPDATE peers SET our_alt_addr=? WHERE node_id=?")); } else { - stmt = db_prepare_v2(db, SQL("UPDATE peers SET alt_addr=NULL WHERE node_id=?")); + stmt = db_prepare_v2(db, SQL("UPDATE peers SET peer_alt_addr=? WHERE node_id=?")); } + + db_bind_text(stmt, alt_addr); db_bind_node_id(stmt, node_id); db_exec_prepared_v2(take(stmt)); } @@ -1062,9 +1067,9 @@ struct wireaddr_internal *wallet_get_peer_alt_addr(struct wallet *w, const struc transaction_started = true; } - stmt = db_prepare_v2(w->db, SQL("SELECT alt_addr FROM peers WHERE node_id=?")); + stmt = db_prepare_v2(w->db, SQL("SELECT peer_alt_addr FROM peers WHERE node_id = ?")); if (!stmt) { - log_broken(w->log, "Failed to prepare statement for node_id %s", fmt_node_id(tmpctx, node_id)); + log_debug(w->log, "Failed to prepare statement for node_id %s", fmt_node_id(tmpctx, node_id)); return handle_alt_addr_failure(w, stmt, transaction_started); } @@ -1072,40 +1077,32 @@ struct wireaddr_internal *wallet_get_peer_alt_addr(struct wallet *w, const struc db_query_prepared(stmt); if (!db_step(stmt)) { - log_broken(w->log, "No alternative address found for peer %s", fmt_node_id(tmpctx, node_id)); + log_debug(w->log, "No alternative address found for peer %s", fmt_node_id(tmpctx, node_id)); return handle_alt_addr_failure(w, stmt, transaction_started); } - const char *addr_str = db_col_strdup(tmpctx, stmt, "alt_addr"); + const char *addr_str = db_col_strdup(tmpctx, stmt, "peer_alt_addr"); if (!addr_str) { - log_broken(w->log, "No address string retrieved for peer %s", fmt_node_id(tmpctx, node_id)); + log_debug(w->log, "Invalid address string retrieved for peer %s", fmt_node_id(tmpctx, node_id)); return handle_alt_addr_failure(w, stmt, transaction_started); } - if (*addr_str == '\0') { - log_broken(w->log, "Empty address string retrieved for peer %s", fmt_node_id(tmpctx, node_id)); - tal_free(addr_str); + if (*addr_str == '\0') { //TODO: This will make the list empty. Need to implement this. + log_debug(w->log, "Empty address string retrieved for peer %s", fmt_node_id(tmpctx, node_id)); return handle_alt_addr_failure(w, stmt, transaction_started); } alt_address = tal(tmpctx, struct wireaddr_internal); - if (!alt_address) { - log_broken(w->log, "Memory allocation failed for alternative address of peer %s", fmt_node_id(tmpctx, node_id)); - tal_free(addr_str); - return handle_alt_addr_failure(w, stmt, transaction_started); - } - - const char *err = parse_wireaddr_internal(tmpctx, addr_str, 0, false, alt_address); + const char *err = parse_wireaddr_internal(tmpctx, addr_str, chainparams_get_ln_port(chainparams), false, alt_address); if (err) { - log_broken(w->log, "Invalid alternative address %s for peer %s: %s", - addr_str, fmt_node_id(tmpctx, node_id), err); + log_debug(w->log, "Invalid alternative address %s for peer %s: %s", + addr_str, fmt_node_id(tmpctx, node_id), err); tal_free(alt_address); alt_address = NULL; } tal_free(addr_str); tal_free(stmt); - if (transaction_started) db_commit_transaction(w->db); diff --git a/wallet/wallet.h b/wallet/wallet.h index e9021bc01c2a..b5995ffea477 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -395,14 +395,18 @@ struct wallet_transaction { }; /** - * wallet_add_peer_alt_addr - Update the alternative address for a peer + * wallet_add_alt_addr - Update the alternative address for a peer connection * @db: the database * @node_id: the ID of the node - * @alt_addr: the alternative address to set, or NULL to clear the alt_addr + * @alt_addr: the alternative address to set + * @is_our_addr: flag to determine whether to update `our_alt_addr` or `peer_alt_addr` * - * This function updates the `alt_addr` field of a peer in the database. + * This function updates the `our_alt_addr` or `peer_alt_addr` field of a peer + * in the database based on the value of the `is_our_addr` flag. If `is_our_addr` + * is true, it updates the `our_alt_addr` field; otherwise, it updates the + * `peer_alt_addr` field. If `alt_addr` is NULL, it just returns. */ -void wallet_add_peer_alt_addr(struct db *db, const struct node_id *node_id, const char *alt_addr); +void wallet_add_alt_addr(struct db *db, const struct node_id *node_id, const char *alt_addr, bool is_our_addr); /** * wallet_get_peer_alt_addr - Retrieve the alternative connection address for a peer