#zeromq #public-key #tor #peer #pub-sub #networking

emyzelium

ZeroMQ的Pub-Sub包装器,带有Curve+ZAP,通过Tor。通过公钥、洋葱地址和端口号标识的对等方,将Vec>发布到其他对等方订阅的主题下。

7个版本

0.9.12 2024年6月5日
0.9.10 2024年2月2日
0.9.8 2024年1月8日
0.9.6 2023年11月30日
0.9.4 2023年10月31日

#578 in 网络编程

Download history 8/week @ 2024-05-20 161/week @ 2024-06-03 9/week @ 2024-06-10 13/week @ 2024-06-17 25/week @ 2024-07-01

每月414次下载
用于 aelhometta

GPL-3.0-or-later

51KB
596

logo disk Emyzelium (Rust)

crate

是ZeroMQ的ZeroMQ发布-订阅消息模式的另一个包装器,具有强制性的Curve安全和可选的ZAP身份验证过滤器,通过Tor,通过Tor SOCKS代理,用于分布式人工生命、决策等系统,其中每个对等方,由其公钥、洋葱地址和端口号标识,将数据字节数组向量发布到其他对等方订阅的唯一主题下,并接收相应的数据。

需要Rustlibzmq (更多关于构建信息,例如Linux中的libzmq3-devlibzmq5软件包)和Tor

其他语言版本

警告

在有些任务和规模中,这种模型可能成功(从宠物级项目开始),而在有些任务和规模中,它将失败得很惨(以行业级和特别是关键基础设施级结束),直至国际哀悼日。请谨慎行事。

另请参阅 真菌感染

演示

让我们使用 Emyzelium 将可分配性引入细胞自动机,包括经典的 康威生命游戏 及其变体。 一旦... 更多... 再... 再次...

为了明确起见,假设使用已安装 Rust 和 libzmq5 软件包的 Linux 系统。但演示也应该在其他操作系统上运行。

在单台连接到互联网的计算机上

使用 RAM 中同一台计算机上的对等节点连接时,是否需要 Tor 和公钥加密?当然不需要。然而,在涉及多台计算机之前,可能您想确保这个事情在原则上可以工作。

首先,安装 Tor到您的 Linux,并在 3 个不同的端口上设置 3 个隐藏服务,任意大于 1024 的端口。更准确地说,在您的 /etc/tor/torrc 中添加以下类似的行

HiddenServiceDir /var/lib/tor/p2p_dummysite1/
HiddenServicePort 60847

HiddenServiceDir /var/lib/tor/p2p_dummysite2/
HiddenServicePort 60848

HiddenServiceDir /var/lib/tor/p2p_dummysite3/
HiddenServicePort 60849

并在终端(注意 tor@default 而不是 tor)中

$ sudo systemctl restart tor@default

然后检查是否有任何问题

$ systemctl status tor@default

应显示 ... active(正在运行) ...... 引导 100% (完成): 完成 ...

等待 3 个指定的目录出现,在每个目录中,都有一个名为 hostname 的文件。

现在下载 Emyzelium 文件,例如到 ~/emz-rs/,或者简单地通过 cargo install emyzelium。打开 examples/demo.rs 并在导入之后,将 ALIEN_ONION 的值更改为来自 /var/lib/tor/p2p_dummysite1/hostname 的洋葱地址(不要包含 .onion 后缀)。同时将 ALIEN_PORT 的值更改为 60847 如果它不是这个值的话。

JOHN_ONIONJOHN_PORT(2)和 MARY_ONIONMARY_PORT(3)进行类似的更改。将更改保存到 demo.rs


您还可以检查这些洋葱地址是否已为 Tor 网络所知;如果是的话,例如 netcat 应该可以工作,— 打开 2 个终端并查看是否如此

term1$ nc -v -l 60847
term2$ torsocks nc -v ONION1.onion 60847

来自 ...连接到 ... 表示洋葱是可到达的。如果它们不可达,请等待几分钟。


~/emz-rs/ 构建

$ cargo build --release --example demo

最后,进行 Life 的 emyzelium 化。打开 3 个终端,并从 ~/emz-rs/ 以任意顺序运行以下命令

term1$ cargo run --release --example demo Alien
term2$ cargo run --release --example demo John
term3$ cargo run --release --example demo Mary

(或者,从 ~/emz-rs/target/release/examples/ 运行 ./demo Name。)

然后您应该看到类似以下内容

  • 终端 1(对等节点 Alien)

Demo animation, Alien

  • 终端 2(对等节点 John)

Demo animation, John

  • 终端 3(对等节点 Mary)

Demo animation, Mary

当Alien的、John的、Mary的同伴(efungi)在Tor上建立连接(ehyphae)后,它们的细胞自动机(realms)可以实时地交换细胞区域(etales)。

在建立连接之前,SLUs(自上次更新以来)是“大”的(尚未更新);之后它们保持在0-10秒范围内。按“1”或“2”实际导入其他领域的更新区域。

如果您将导入和发射都设置为自动,例如每8秒从随机其他领域导入,那么整个过程将更加自主。当然,在这样的小环境中,进化潜力有限。

请注意,Alien的CA,B34/S34的出生/生存规则与John和Mary的CA的经典B3/S23不同。换句话说,尽管领域的“局部几何形状”相同(摩尔邻域),但它们的“物理”不同。

“Alien”、“John”、“Mary”这些名字不是必需的,只是为了方便。每个同伴由其公钥、洋葱地址和端口识别。

您可以在任何时候退出这3个实例之一,稍后再重新运行,连接将会恢复。给定同伴发布的最后“快照”被保存在接收过它的每个同伴那里,直到被下一个快照替换。

并且您可以混合不同语言的版本,只要同一时间不会运行每个同伴的多个实例。也就是说,您可以

term1$ cargo run --release --example demo Alien

替换为

term1$ ./demo Alien

C++版的Emyzelium

在连接到互联网的多台PC上

如预期的那样,与“单机”场景的唯一主要区别是隐藏服务在多台PC之间分配。假设有3个,PC1“Alien的”,PC2“John的”,PC3“Mary的”。

这次端口号可以是相同的:在所有3台PC上,/etc/tor/torrc包含

HiddenServiceDir /var/lib/tor/p2p_dummysite/
HiddenServicePort 60847

只有onion地址在hostname文件中不同,并且与之前一样,应在demo.rs-s中指定为ALIEN_ONIONJOHN_ONIONMARY_ONION值;所有_PORT都必须是60847。此外,这次每个PC只需在demo.rs中有一个对应的_SECRETKEY

将相应修改的demo.rs的Emyzelium文件放置在这些PC上,并且成功构建了demo可执行文件后,执行以下操作

pc1$ cargo run --release --example demo Alien
pc2$ cargo run --release --example demo John
pc3$ cargo run --release --example demo Mary

并且应该观察到几乎与上面相同的结果。

安全和密钥

Emyzelium依赖于ZeroMQ的Curve和ZAP加密和身份验证方案,以及各种公钥加密(假定具备基本知识)。因此,emyzelium中的每个“主题”都需要并部分由一个密钥和一个相应的公钥定义。此类密钥有2种编码:原始(32字节,每个字节从0-255范围)和可打印的Z85(40个符号,每个符号来自ASCII的85元素集合)。

Emyzelium的方法期望以Z85编码的&str形式提供密钥。

如何获得这样的密钥对
use std::ffi::{
    c_int,
    c_char
};

#[link(name = "zmq")]
extern "C" {
    fn zmq_curve_keypair(z85_public_key: *mut c_char, z85_secret_key: *mut c_char) -> c_int;
}

const KEY_Z85_LEN: usize = 40;
const KEY_Z85_CSTR_LEN: usize = KEY_Z85_LEN + 1;

fn zmqe_curve_keypair() -> (String, String, i32) {
    let mut publickey_bufn = vec![0u8; KEY_Z85_CSTR_LEN];
    let mut secretkey_bufn = vec![0u8; KEY_Z85_CSTR_LEN];
    let r = unsafe {
        zmq_curve_keypair(
            (&mut publickey_bufn).as_mut_ptr() as *mut c_char,
            (&mut secretkey_bufn).as_mut_ptr() as *mut c_char
        )
    } as i32;
    (String::from_utf8(publickey_bufn[..KEY_Z85_LEN].to_vec()).unwrap(), String::from_utf8(secretkey_bufn[..KEY_Z85_LEN].to_vec()).unwrap(), r)
}

fn main() {
    let (publickey, secretkey, _) = zmqe_curve_keypair();
    println!("Public key: {}", &publickey);
    // Make sure no one is behind your back...
    println!("Secret key: {}", &secretkey);
}

注意Emyzelium在此处未使用;另一方面,此代码位于examples/genkeypair.rs中。


显然,密钥不是长度为40的任意ASCII字符串,不能通过键盘敲击输入。特别是,

如何从密钥派生出公钥
...
#[link(name = "zmq")]
extern "C" {
    fn zmq_curve_public(z85_public_key: *mut c_char, z85_secret_key: *mut c_char) -> c_int;
}
...
    unsafe {
        secretkey.as_ptr().copy_to((&mut secretkey_bufn).as_mut_ptr(), KEY_Z85_LEN);
        zmq_curve_public(
            (&mut publickey_bufn).as_mut_ptr() as *mut c_char,
            (&mut secretkey_bufn).as_mut_ptr() as *mut c_char
        );
    }
    println!("Public key: {}", String::from_utf8(publickey_bufn[..KEY_Z85_LEN].to_vec()).unwrap());

您使用独特的密钥构建对等方,每个对等方一个。除了您和您信任的人,没有人应该知道这些密钥。任何想与您的对等方通信的人都必须知道相应的公钥。相应地,如果您想与别人运行的对等方通信,除了他们的洋葱地址和端口,您还必须知道这些对等方的公钥。基于ZAP的“白名单”功能(见下文),对等方的所有者可以限制那些能够与此对等方通信的人。

现在我们仔细看看这些

实体,它们的角色和用法

简而言之,efunguz发布etales,伸出ehyphae来接触其他efungi并接收它们发布的etales(在这里你应该有似曾相识的感觉,因为这和Synopsis中的“真菌学”隐喻有关)。

这一群blob更好地服务于某种目的——有需要连接以交换数据的程序。我们称这样的程序为“领域”,以强调它们可能属于非常不同的环境,可能用不同的语言编写,不能轻易一次性全部替换。就像森林中的两朵蘑菇,一朵长在洞穴里,另一朵长在河岸上,但它们是同一个菌丝体的一部分,您附加到每个程序上的efungi进行通信,通过efungi,“洞穴程序”和“河岸程序”也能通信。请参阅演示屏幕录制,终端1-3。

遵循发布-订阅模式,Emyzelium模型更倾向于“读取”而不是“写入”:在对方想要读取并对其进行操作之前,您不能将某些数据写入对方的程序内存,除非您想读取这些数据并对其进行操作,没有人可以将他们的数据写入您程序的内存,除非您想读取这些数据并对其进行操作。

此模式的另一个重要属性是多播:数据没有单一的目标接收者。任何允许订阅您的efunguz的etales的人都可以接收它发布的任何etale,如果他们知道其标题。此外,即使他们收到了,您也不知道谁实际上收到了什么etale,直到他们以某种方式将其传达给您。

关于这一点将在下面进一步说明,但就现在而言,请考虑这些限制,并决定它们是否符合您的目标。

如果它们确实如此,并且您的某个程序是用Rust编写的,请继续。否则,请参阅S&B此列表等。

下面的代码片段假定

[dependencies]
emyzelium = "X.Y.Z"

extern crate emyzelium;

use emyzelium::{self as emz, Efunguz};

另请参阅demo.rs。此外,它还包含带有更多参数的方法调用。

因此,EfunguzEhyphaEtale只是对知名概念的时髦名称


Efunguz,又称对等方,是某些“领域”(由您的Rust程序表示)与Tor网络(由托尔SOCKS代理上的ZeroMQ表示)之间的中介。对于前者,它简化了安全、(重)连接和数据流任务。

构建efunguz的最简单方法是

let my_secretkey: &str = "gbMF0ZKztI28i6}ax!&Yw/US<CCA9PLs.Osr3APc";
let mut efunguz = Efunguz::new(my_secretkey, & HashSet::new(), emz::DEF_PUBSUB_PORT, emz::DEF_TOR_PROXY_PORT, emz::DEF_TOR_PROXY_HOST);

更多定制

let whitelist_publickeys = HashSet::from([String::from("WR)%3-d9dw)%3VQ@O37dVe<09FuNzI{vh}Vfi+]0"), String::from("iGxlt)JYh!P9xPCY%BlY4Y]c^<=W)k^$T7GirF[R")]);
let mut efunguz = Efunguz::new(my_secretkey, &whitelist_publickeys, 54321, 9955, emz::DEF_TOR_PROXY_HOST);

现在只有与whitelist_publickeys对应的密钥的所有者才能订阅并接收此efunguz的etales。他们必须连接到端口号54321,而不是“默认”端口号。

默认情况下,白名单为空,这意味着...与您的预期相反:任何人都可以订阅

Efunguz是可变的。您可以

  • 通过Efunguz对象的add_whitelist_publickeys()read_whitelist_publickeys()del_whitelist_publickeys()clear_whitelist_publickeys()方法,添加和删除白名单中的密钥。

  • 通过add_ehypha()del_ehypha()添加和删除(以下定义的)ehyphae

let that_publickey: &str = "WR)%3-d9dw)%3VQ@O37dVe<09FuNzI{vh}Vfi+]0";
let that_onion: &str = "abcde23456abcde23456abcde23456abcde23456abcde23456abcdef";
let that_port: u16 = 12345;
if let Ok(ehypha) = efunguz.add_ehypha(that_publickey, that_onion, that_port) {
    ...
}
  • 通过get_ehypha()get_mut_ehypha()方法,通过公钥获取ehypha的不可变和可变引用。
let that_publickey: &str = "iGxlt)JYh!P9xPCY%BlY4Y]c^<=W)k^$T7GirF[R";
if let Some(ehypha) = efunguz.get_ehypha(that_publickey) {
    ...
}
  • 通过emit_etale()发布/发出etales。
let title: &str = "status2";
let parts: Vec<Vec<u8>> = vec![vec![2, 1], vec![255, 0, 2, 1]];
efunguz.emit_etale(title, &parts);

标题可以是空的,即""。这可能是一种协议,在空标题下发布一些关于“正常”etales的描述,以便其他efungi能够获取可用的etales列表。

efunguz.emit_etale("",
 vec!["status2".as_bytes().to_vec(), "2B humidity, 4B kappa level".as_bytes().to_vec(),
  "advice".as_bytes().to_vec(), "C string with today's advice".as_bytes().to_vec()]);
  • 通过update()方法使用从连接的efungi接收到的数据更新其状态、ehypha及其etales。

调用update()的合适位置是您程序的主循环。例如:

while !quit { // main program loop
    // do something here
    efunguz.update();
    if my_status_updated {
        efunguz.emit_etale("status2", &status_parts);
    }
    if that_etale.t_in() > t_last_etale {
        if (that_etale.parts.len() == 2) && (that_etale.parts[1].len() == 4) { // sanity checks
            let mut buf: [0u8; 4];
            buf.copy_from_slice(& that_etale.parts[1]);
            let kappa_level = i32::from_le_bytes(buf);
            // do something with kappa level
        }
        t_last_etale = that_etale.t_in;
    }
}
  • 通过in_absorbing_num()获取从其他efungi成功认证的传入连接的当前数量(以下用IN1表示)。

  • 通过in_permitted_num()获取成功认证的传入连接的总数IN2)。

  • 通过in_attempted_num()获取尝试的传入连接的总数IN3)。

大多数情况下,IN1IN2,因为一些efungi可能已经断开连接,且IN2IN3,因为一些efungi可能甚至没有通过认证过滤器。

  • 获取最后发生的以下时间点:1) 尝试的传入连接,通过t_last_attempt(),2) 认证的传入连接,通过t_last_permit(),3) 断开连接,通过t_last_disconnect()

请参阅demo.rs中的Realm_CA::run()

在内部,Efunguz拥有ZeroMQ上下文、etales的PUB套接字、ZAP认证的REP套接字和监视PUB的PAIR套接字。


Ehypha,即从一个efunguz到另一个efunguz的连接。通过ehypha,前者从后者接收etales。它是Efunguz的一部分,因此其构造已在上面考虑。

Ehypha是可变的。您可以

  • 通过 add_etale()del_etale() 从目标 efunguz 订阅和取消订阅 etales。
if let Ok(that_etale) = ehypha.add_etale("status3") {
    ...
}

最初,etale 是空的(没有部分)。如果公钥为 WR)%3-d9dw)%3VQ@O37dVe<09FuNzI{vh}Vfi+]0 的 efunguz 在洋葱地址 abcde23456abcde23456abcde23456abcde23456abcde23456abcdef 上,端口 12345,允许您的 efunguz 订阅,并以 status3 为标题发布 etale,那么,经过一段时间,这个 etale 将在您调用 efunguz.update() 后收到,并且只要这些条件成立,就会不断更新。其字段在下面的 Etale 段落中描述。

  • 通过 get_etale() 通过标题获取 etale 的不可变引用。
let title: &str = "status7";
if let Some(etale) = ehypha.get_etale(title) {
    ...
}
  • 通过 pause_etale[s]()resume_etale[s]() 暂停和恢复单个 etale 或所有 etales 的更新。

在内部,Ehypha 拥有用于 etales 的 SUB 套接字。上下文是 Efunguz 的。


Etale,即带有元数据的分区数据块,是 efungi 交换的主要数据单元。它有以下公共方法来访问其只读字段

  • parts() -> & Vec<Vec<u8>> 是最新获取的数据

  • t_out() -> i64 是从 Unix 纪元以来的时间(以微秒为单位),在发送时测量 etale 发布的时间

  • t_in() -> i64 是从 Unix 纪元以来的时间(以微秒为单位),在接收时测量 etale 被获取的时间

Etale 从外部是不可变的,并由构建它的 Ehypha 所拥有。

让“tale”在名称中提醒人们,一个 tale 可能是一个 lie,无论讲述者的意图或听众的期望如何。


主要数据流如下

领域 1 ↔ Efunguz 1 ↔ ZeroMQ ↔ Tor ↔ turtles ↔ Tor ↔ ZeroMQ ↔ Efunguz 2 ↔ 领域 2

这里它是双向的,但也可以是单向的。为此,每个 efunguz 必须知道所有收集 etales 的 efungi 的网络地址,即与其 ehyphae 连接的 efungi 的地址。这就是 Tor 网络的洋葱地址发挥作用的地方...

在 v0.9.0 之前,存在名为 Ecataloguzes 的服务器,也就是名称服务器。Efungi 与它们进行交互,交换它们的(动态)IP 地址,而 Emyzelium 通过基本的 TCP/IP 互联网进行工作……或者更确切地说,如果没有 NAT、防火墙等,它 将会 工作(实际上它是在局域网内工作的)。现在,由于 Tor 解决了这个问题,我们不必在这里再写一个关于那些名称服务器的无聊部分!同时,再见,端口转发、打孔等。

PAQ(可能被问到的问题)

问:与其他类似项目相比,Emyzelium 引入了什么新特性?

答:IOHO,没有。


问:Emyzelium 的可靠性如何?安全性如何?是否有后门?

答:尚未进行“审计”,所以……仔细阅读源代码,它足够小——Rust 版本比这个 README 文件还小。那么,责任就转移到底层——ZeroMQ、Curve、Tor、TCP/IP、BIOS/EFI、硬件等等。抱歉,如果您只信任自己或当前的 Planck 时间单位,没有其他方式。

是的,存在后门。不,不存在后门。

不要在接收 etales 和它们的反序列化过程中省略健全性检查。

不要使用演示中的密钥,生成您自己的唯一密钥对。


问:Emyzelium 是垃圾,我永远不会使用它,但我想与一些 efungi 交换数据。除了他们的洋葱、端口和公钥之外,我还需要什么?

答:这里描述的实体之间流动的数据中没有“Emyzelium 特定的”内容。您在 Efunguz 的“发布者”端口上订阅了一些以 null 结尾的主题吗?——它将向您发送相应的 etale,如果没有白名单或您在其中。编写自己的/使用别人的包装器,围绕您需要的 ZeroMQ、Tor 等部分的代码(参见图 STREAM 套接字)。实际上,Emyzelium 的 实现(或任何传统名称)的 架构 已经在这里。与 Emyzelium 交换数据就是成为它的一部分。如果数据随后流向其他地方,也许您目标是 桥接器。毕竟,您始终可以从头开始重写或改进引起您反感的部分,将其重命名为“Epór”/“Ekinzhitai”/“E...”(基于爱尔兰语/日语中的“mycelium”)并使用它。


问:有些人正在用 emyzelium 做坏事。如何阻止他们?

答:对于此类架构可能没有特殊之处。限制对 Tor 的访问,识别参与网络的设备……进行一些元数据分析……还有其他领域除了菌类学。


问:有些人正在关闭我们用于做好事的 emyzelium。如何阻止他们?

答:对于此类架构可能没有特殊之处。使用 Tor 桥接器,切换参与网络的设备……进行一些元数据混淆……还有其他领域除了菌类学。

S&B

大多是大型...

许可证

此包装器为自由软件:您可以在自由软件基金会发布的 GNU 通用许可证条款下重新分发它和/或修改它,无论是许可证的第 3 版,还是(根据您的选择)许可证的任何后续版本。

此包装器分发时希望它是有用的,但没有任何保证;甚至没有关于其可销售性或适用于特定目的的暗示性保证。有关详细信息,请参阅 GNU 通用许可证。

依赖关系

~215KB