Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 12 additions & 1 deletion docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ presentation/ Axum HTTP API (port: 4001)

| 機能 | 実装 |
|------|------|
| P2Pネットワーク | libp2p(Kademlia DHT / Gossipsub / RequestResponse / mDNS) |
| P2Pネットワーク | libp2p(Kademlia DHT / Gossipsub / RequestResponse / mDNS / AutoNAT v2 / circuit relay v2 / DCUtR) |
| CRDT状態管理 | crsl-lib(CIDネイティブDAG CRDT) |
| コンテンツ配置 | sha256(content_id)によるDHTキー空間への決定論的配置 |
| 認証 | P-256 ECDSA、自己完結型鍵ID |
Expand Down Expand Up @@ -320,8 +320,19 @@ XOR距離によるノード選択には以下の特性がある:
| Gossipsub | イベント伝播(ContentCreated, ContentUpdated等) |
| RequestResponse | ノード間の直接通信(CRDT操作の同期) |
| mDNS | ローカルネットワークでのピア探索 |
| AutoNAT v2 | 自ノードが外から到達可能かを**実測**で判定(自己申告ではない) |
| circuit relay v2 | NAT 配下のノードへの接続を中継。提供側は opt-in(`--relay-service`) |
| DCUtR | relay 経由の接続を hole punching で直接接続へ昇格させる |
| TCP / QUIC / WebRTC | トランスポート |

#### NAT traversal と 2 つの「relay」

この表の **circuit relay v2 は接続層**の仕組みで、本書が[relay先の信頼度](#relay先の信頼度)以下で論じる **Monas relay(application 層のリクエスト転送)とは別物**である。前者は「そもそも TCP 接続を張れない相手にどう繋ぐか」、後者は「コンテンツを持たないノードが受けたリクエストをどう member へ渡すか」を扱う。両者は独立しており、circuit relay を無効にしても Monas relay は動く。

**役割は測定から従属的に決まる。** AutoNAT v2 は自ノードのアドレスを他ノードに dial back させ、**実際に到達できたアドレスについてのみ**肯定を返す。到達不能なノードが自分を到達可能と誤認すれば、提供できない relay サービスを広告してしまうため、ここは自己申告であってはならない。

ただし**中継する側になることは opt-in** である。NAT を越えたいこと(client 側)と、見知らぬ相手のトラフィックを運ぶ用意があること(server 側)は別の判断なので、後者は `--relay-service`(`--external-address` 必須)でのみ有効になる。既定では、NAT traversal が有効でも中継役は担わない。

---

## 10. セキュリティモデル
Expand Down
4 changes: 4 additions & 0 deletions monas-state-node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ features = [
"macros",
"cbor",
"quic",
# NAT traversal: reachability probing, circuit relay v2, and hole punching.
"autonat",
"relay",
"dcutr",
]

# WebRTC transport (alpha - for future browser-to-server communication)
Expand Down
24 changes: 24 additions & 0 deletions monas-state-node/infra/docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ BOOTSTRAP_ADDR="${BOOTSTRAP_ADDR:-}"
BOOTSTRAP_DNS="${BOOTSTRAP_DNS:-}"
BOOTSTRAP_PEER_ID="${BOOTSTRAP_PEER_ID:-}"
DISABLE_MDNS="${DISABLE_MDNS:-}"
DISABLE_NAT_TRAVERSAL="${DISABLE_NAT_TRAVERSAL:-}"
RELAY_SERVICE="${RELAY_SERVICE:-}"
EXTERNAL_ADDR="${EXTERNAL_ADDR:-}"

ARGS=(
--data-dir "$DATA_DIR"
Expand All @@ -26,6 +29,27 @@ case "$DISABLE_MDNS" in
1|true|TRUE|yes|YES) ARGS+=(--disable-mdns) ;;
esac

# Externally reachable addresses to advertise (comma-separated multiaddrs).
if [ -n "$EXTERNAL_ADDR" ]; then
IFS=',' read -ra EXT_ADDRS <<< "$EXTERNAL_ADDR"
for a in "${EXT_ADDRS[@]}"; do
a="$(echo "$a" | tr -d '[:space:]')"
[ -n "$a" ] && ARGS+=(--external-address "$a")
done
fi

# NAT traversal is on by default; this is the switch that turns it off
# without a code change.
if [ -n "$DISABLE_NAT_TRAVERSAL" ]; then
ARGS+=(--disable-nat-traversal)
fi

# Acting as a relay for other peers is opt-in: it carries traffic for nodes
# we know nothing about. Requires EXTERNAL_ADDR.
if [ -n "$RELAY_SERVICE" ]; then
ARGS+=(--relay-service)
fi

# Bootstrap addresses.
#
# BOOTSTRAP_ADDR accepts a comma-separated list of full multiaddrs, so a node
Expand Down
8 changes: 8 additions & 0 deletions monas-state-node/src/application_service/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,8 @@ mod tests {
listen_addrs: vec!["/ip4/127.0.0.1/tcp/0".parse().unwrap()],
bootstrap_nodes: vec![],
enable_mdns: false,
enable_nat_traversal: false,
enable_relay_service: false,
gossipsub_topics: vec!["test".to_string()],
external_addrs: vec![],
},
Expand Down Expand Up @@ -723,6 +725,8 @@ mod tests {
listen_addrs: vec!["/ip4/127.0.0.1/tcp/0".parse().unwrap()],
bootstrap_nodes: vec![],
enable_mdns: false,
enable_nat_traversal: false,
enable_relay_service: false,
gossipsub_topics: vec!["test".to_string()],
external_addrs: vec![],
},
Expand Down Expand Up @@ -753,6 +757,8 @@ mod tests {
listen_addrs: vec!["/ip4/127.0.0.1/tcp/0".parse().unwrap()],
bootstrap_nodes: vec![],
enable_mdns: false,
enable_nat_traversal: false,
enable_relay_service: false,
gossipsub_topics: vec!["test".to_string()],
external_addrs: vec![],
},
Expand Down Expand Up @@ -784,6 +790,8 @@ mod tests {
listen_addrs: vec!["/ip4/127.0.0.1/tcp/0".parse().unwrap()],
bootstrap_nodes: vec![],
enable_mdns: false,
enable_nat_traversal: false,
enable_relay_service: false,
gossipsub_topics: vec!["test".to_string()],
external_addrs: vec![],
},
Expand Down
42 changes: 42 additions & 0 deletions monas-state-node/src/bin/state_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ struct Args {
#[arg(long)]
disable_mdns: bool,

/// Disable NAT traversal (AutoNAT v2, circuit relay v2, DCUtR).
///
/// NAT traversal is what lets a node behind a home router or a NAT gateway
/// join at all. A deployment where every node is publicly reachable does
/// not need it and can turn the machinery off.
#[arg(long)]
disable_nat_traversal: bool,

/// Offer circuit relay service to other nodes.
///
/// A relay carries traffic for peers it knows nothing about, so this is
/// opt-in and separate from `--disable-nat-traversal`: needing a relay
/// oneself is not the same as being willing to be one. Only a node that
/// is actually reachable from outside can serve, so this requires
/// `--external-address`.
#[arg(long)]
relay_service: bool,

/// Log level (trace, debug, info, warn, error).
#[arg(long, default_value = "info")]
log_level: String,
Expand All @@ -83,8 +101,12 @@ async fn main() -> Result<()> {
.parse::<Multiaddr>()
.context("Failed to parse P2P listen address")?],
enable_mdns: !args.disable_mdns,
enable_nat_traversal: !args.disable_nat_traversal,
..Default::default()
};
if args.disable_nat_traversal {
tracing::info!("NAT traversal disabled; this node can only reach directly-dialable peers");
}
if args.disable_mdns {
tracing::info!(
"mDNS disabled; discovery relies on bootstrap peers, Kademlia and the peer store"
Expand Down Expand Up @@ -129,6 +151,26 @@ async fn main() -> Result<()> {
}
}

// Relay service is only meaningful on a node that is actually reachable
// from outside. Refusing here rather than starting a relay nobody can
// reach keeps the "advertise only what you can provide" rule honest.
if args.relay_service {
if network_config.external_addrs.is_empty() {
anyhow::bail!(
"--relay-service requires at least one --external-address: a node that \
cannot be reached from outside cannot relay for anyone"
);
}
if args.disable_nat_traversal {
anyhow::bail!("--relay-service cannot be used with --disable-nat-traversal");
}
network_config.enable_relay_service = true;
tracing::info!(
"Relay service enabled; this node will carry circuits for other peers \
(up to 128 reservations / 32 circuits)"
);
}

let config = StateNodeConfig {
data_dir: args.data_dir,
http_addr: args.listen,
Expand Down
Loading
Loading