npien 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- npien-1.0.0/LICENSE +21 -0
- npien-1.0.0/PKG-INFO +235 -0
- npien-1.0.0/README.md +211 -0
- npien-1.0.0/npien/__init__.py +148 -0
- npien-1.0.0/npien/core.py +886 -0
- npien-1.0.0/npien/crypto.py +204 -0
- npien-1.0.0/npien/discovery.py +154 -0
- npien-1.0.0/npien/exceptions.py +22 -0
- npien-1.0.0/npien/protocol.py +91 -0
- npien-1.0.0/npien/registry.py +125 -0
- npien-1.0.0/npien/stun.py +272 -0
- npien-1.0.0/npien/transport.py +80 -0
- npien-1.0.0/npien.egg-info/PKG-INFO +235 -0
- npien-1.0.0/npien.egg-info/SOURCES.txt +22 -0
- npien-1.0.0/npien.egg-info/dependency_links.txt +1 -0
- npien-1.0.0/npien.egg-info/requires.txt +1 -0
- npien-1.0.0/npien.egg-info/top_level.txt +1 -0
- npien-1.0.0/pyproject.toml +33 -0
- npien-1.0.0/setup.cfg +4 -0
- npien-1.0.0/tests/test_crypto.py +105 -0
- npien-1.0.0/tests/test_integration.py +191 -0
- npien-1.0.0/tests/test_protocol.py +69 -0
- npien-1.0.0/tests/test_registry.py +41 -0
- npien-1.0.0/tests/test_stun.py +88 -0
npien-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 npien contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
npien-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: npien
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: NAT-piercing NET: serverless P2P UDP hole punching with rooms, STUN and optional end-to-end encryption
|
|
5
|
+
Author: npien contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: p2p,nat,stun,udp,hole-punching,networking,e2ee
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: POSIX
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Internet
|
|
18
|
+
Classifier: Topic :: Security :: Cryptography
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: cryptography>=41
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# npien
|
|
26
|
+
|
|
27
|
+
**NAT-piercing NET** — sunucusuz (serverless) P2P UDP hole punching, oda sistemi,
|
|
28
|
+
Google STUN sunucuları ve isteğe bağlı uçtan uca şifreleme.
|
|
29
|
+
|
|
30
|
+
`npien`, iki Python sürecinin NAT arkasından doğrudan birbirine UDP paketi
|
|
31
|
+
göndermesini sağlar. STUN keşfi, delik açma (hole punching), paket çerçeveleme,
|
|
32
|
+
parçalama (fragmentation), keep-alive ve şifreleme kütüphanenin içinde yapılır.
|
|
33
|
+
|
|
34
|
+
## Özellikler
|
|
35
|
+
|
|
36
|
+
- Google STUN sunucuları ile dış IP/port keşfi (RFC 5389, yerleşik istemci)
|
|
37
|
+
- UDP hole punching: aynı anda iki tarafa HELLO paketleri ile NAT eşlemesi açma
|
|
38
|
+
- Oda (room) sistemi: `npien.connect("492109")`
|
|
39
|
+
- Peer ticket: sunucu olmadan uç nokta paylaşımı (`npn1.<payload>.<hmac>`)
|
|
40
|
+
- Yerel keşif: aynı makinede kayıt defteri (registry), LAN'de UDP broadcast
|
|
41
|
+
- İsteğe bağlı E2EE: X25519 anahtar anlaşması + AES-256-GCM (`npien.advsecure()`)
|
|
42
|
+
- Büyük mesajlar için otomatik parçalama ve birleştirme
|
|
43
|
+
- Keep-alive / zaman aşımı yönetimi ve `on_message` callback'leri
|
|
44
|
+
- IPv4, Python 3.10+
|
|
45
|
+
|
|
46
|
+
## Kurulum
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install -e .
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Bağımlılık: `cryptography` (E2EE için). STUN istemcisi kütüphanenin içindedir;
|
|
53
|
+
`pystun3` gerekmez.
|
|
54
|
+
|
|
55
|
+
## Hızlı başlangıç
|
|
56
|
+
|
|
57
|
+
### Aynı odada iki taraf
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import npien
|
|
61
|
+
|
|
62
|
+
npien.set_appid("testprogram")
|
|
63
|
+
npien.connect("492109")
|
|
64
|
+
npien.send("merhaba")
|
|
65
|
+
veri, adres = npien.receive(timeout=10)
|
|
66
|
+
print(adres, veri.decode())
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`connect("492109")` önce yerel kayıt defterine ve LAN'e bakar. İnternet üzerinden
|
|
70
|
+
iki farklı ağ arasında uç noktaların bir şekilde paylaşılması gerekir; bunun için
|
|
71
|
+
ticket kullanın:
|
|
72
|
+
|
|
73
|
+
### Ticket ile bağlanma
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# B tarafı: dinle ve ticket üret
|
|
77
|
+
import npien
|
|
78
|
+
|
|
79
|
+
npien.set_appid("testprogram")
|
|
80
|
+
npien.listen("492109")
|
|
81
|
+
print("TICKET:", npien.create_ticket("492109"))
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# A tarafı: ticket'ı paylaşılan kanaldan (sohbet, e-posta, QR...) al
|
|
86
|
+
import npien
|
|
87
|
+
|
|
88
|
+
npien.set_appid("testprogram")
|
|
89
|
+
npien.connect("492109", ticket="npn1....")
|
|
90
|
+
npien.send("selam")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Ticket, hedefin dış IP/portu, yerel adresi ve açık anahtarını taşır. Aynı ticket
|
|
94
|
+
ile iki taraf aynı anda `connect(...)` de çağırabilir; delik açma simetriktir.
|
|
95
|
+
|
|
96
|
+
### Uçtan uca şifreleme
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import npien
|
|
100
|
+
|
|
101
|
+
npien.set_appid("testprogram")
|
|
102
|
+
npien.connect("492109", ticket=ticket)
|
|
103
|
+
npien.advsecure() # giden mesajlar AES-256-GCM ile şifrelenir
|
|
104
|
+
npien.send("gizli mesaj")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Anahtarlar X25519 ECDH ile her iki tarafta bağımsız olarak türetilir; mesajlar
|
|
108
|
+
ağda asla açık metin olarak dolaşmaz. Karşı taraf `advsecure()` çağırmasa bile
|
|
109
|
+
şifreli paketleri çözebilir; iki yönlü gizlilik için iki taraf da açmalıdır.
|
|
110
|
+
|
|
111
|
+
### Sınıf tabanlı kullanım (çoklu örnek)
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from npien import Npien
|
|
115
|
+
|
|
116
|
+
sunucu = Npien(appid="testprogram", name="sunucu")
|
|
117
|
+
sunucu.listen("492109")
|
|
118
|
+
ticket = sunucu.create_ticket("492109")
|
|
119
|
+
|
|
120
|
+
istemci = Npien(appid="testprogram", name="istemci")
|
|
121
|
+
istemci.connect("492109", ticket=ticket)
|
|
122
|
+
istemci.advsecure()
|
|
123
|
+
istemci.send(b"merhaba")
|
|
124
|
+
veri, adres = sunucu.receive(timeout=10)
|
|
125
|
+
|
|
126
|
+
istemci.close()
|
|
127
|
+
sunucu.close()
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Callback ile mesaj alma
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
@npien.on_message
|
|
134
|
+
def mesaj_geldi(veri: bytes, adres: tuple[str, int]):
|
|
135
|
+
print(adres, veri.decode(errors="replace"))
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Callback'ler transport thread'inde çalışır; ağır işleri ayrı bir thread/kuyruğa
|
|
139
|
+
devredin.
|
|
140
|
+
|
|
141
|
+
## API
|
|
142
|
+
|
|
143
|
+
### Modül seviyesi (varsayılan örnek)
|
|
144
|
+
|
|
145
|
+
| Fonksiyon | Açıklama |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| `set_appid(appid)` | Uygulama kimliğini ayarlar. Ticket HMAC'i ve oda ad alanı buna bağlıdır. |
|
|
148
|
+
| `set_name(name)` | Görünen ad (HELLO/ticket içinde taşınır). |
|
|
149
|
+
| `listen(room, advertise=True)` | Odayı dinlemeye başlar; `advertise` ise kendi ticket'ını kayıt defterine yazar. |
|
|
150
|
+
| `join_room(room)` | `listen` ile aynı. |
|
|
151
|
+
| `connect(room, peer=None, ticket=None, tickets=None, timeout=8.0, discover=True)` | Odaya bağlanır; bağlı peer'ların listesini döner. |
|
|
152
|
+
| `send(data, to=None)` | Varsayılan olarak odadaki tüm peer'lara gönderir. `to` bir oda adı veya `(ip, port)` olabilir. |
|
|
153
|
+
| `sendto(data, peer)` | Belirli bir peer'a gönderir. |
|
|
154
|
+
| `receive(timeout=None)` | `(bytes, (ip, port))` döner. |
|
|
155
|
+
| `receive_nowait()` | Bloklamadan dener; mesaj yoksa `TimeoutError`. |
|
|
156
|
+
| `advsecure(enabled=True)` | Giden mesajlarda E2EE'yi açar/kapatır. |
|
|
157
|
+
| `create_ticket(room)` | Paylaşılabilir ticket üretir; STUN keşfini tetikler. |
|
|
158
|
+
| `add_peer(ticket)` | Ticket'ı kayıt defterine ekler (odadaki diğer süreçler bulabilir). |
|
|
159
|
+
| `known_peers(room)` | Kayıt defterindeki ticket'ları döner. |
|
|
160
|
+
| `peers(room=None)` | Bağlı/bağlanan peer'ların `PeerInfo` listesi. |
|
|
161
|
+
| `on_message(callback)` | Gelen her mesaj için callback kaydeder. |
|
|
162
|
+
| `get_stun_info()` | Dış IP/port, yerel adres, NAT tipi ve STUN sunucuları. |
|
|
163
|
+
| `connection_info()` | Uygulama, oda, güvenlik ve peer özeti. |
|
|
164
|
+
| `disconnect(target=None)` | Peer/peer'ları ayırır. |
|
|
165
|
+
| `close()` | Kaynakları kapatır ve kayıt defteri girdilerini siler. |
|
|
166
|
+
|
|
167
|
+
### `connect` parametreleri
|
|
168
|
+
|
|
169
|
+
- `peer`: `("1.2.3.4", 5678)` veya `"1.2.3.4:5678"` ya da bunların listesi.
|
|
170
|
+
- `ticket` / `tickets`: `create_ticket` çıktısı; açık anahtar pinlenir.
|
|
171
|
+
- `timeout`: delik açma için toplam süre (saniye).
|
|
172
|
+
- `discover`: `True` ise kayıt defteri + LAN broadcast keşfi yapılır.
|
|
173
|
+
|
|
174
|
+
## Nasıl çalışır?
|
|
175
|
+
|
|
176
|
+
1. **STUN**: Ana UDP soketi ile Google STUN sunucularına Binding Request
|
|
177
|
+
gönderilir; XOR-MAPPED-ADDRESS'ten dış uç nokta öğrenilir. Aynı soket
|
|
178
|
+
kullanıldığı için NAT eşlemesi hole punching ile birebir aynıdır.
|
|
179
|
+
2. **Ticket**: Dış adres, yerel adres, uygulama kimliği, oda ve X25519 açık
|
|
180
|
+
anahtarı HMAC-SHA256 ile imzalanarak tek bir metne paketlenir.
|
|
181
|
+
3. **Hole punching**: Her iki taraf aday adreslere (dış + yerel) 250 ms'de bir
|
|
182
|
+
HELLO gönderir. NAT eşlemesi açılınca HELLO/HELLO_ACK alışverişi oturumu kurar.
|
|
183
|
+
4. **Oturum**: Oturum kimliği iki açık anahtarın sıralı hash'inden türer. Anahtar
|
|
184
|
+
X25519 + HKDF-SHA256 ile türetilir.
|
|
185
|
+
5. **Veri**: `NPN1` başlıklı paketler; 1200 baytlık datagramlara parçalanır.
|
|
186
|
+
Şifreli modda AES-256-GCM (12 baytlık nonce, oturuma bağlı AAD) kullanılır.
|
|
187
|
+
6. **Canlılık**: 10 saniyede bir PING/PONG; 45 saniye sessiz peer düşürülür.
|
|
188
|
+
7. **Keşif**: Aynı makinede `~/.npien/registry` altındaki kilitli JSON kayıtları;
|
|
189
|
+
LAN'de 47777/UDP broadcast ve ticket yanıtları. `NPIEN_HOME` ve
|
|
190
|
+
`NPIEN_DISCOVERY_PORT` ile değiştirilebilir.
|
|
191
|
+
|
|
192
|
+
Paket başlığı (28 bayt):
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
NPN1 | version | type | flags | reserved | session(8) | msg_id(8) | frag_index(2) | frag_count(2)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Sınırlamalar
|
|
199
|
+
|
|
200
|
+
- **Simetrik NAT**: Her hedef için farklı dış port atayan NAT'larda doğrudan
|
|
201
|
+
hole punching çoğu zaman başarısız olur. Bu durumda TURN benzeri bir relay
|
|
202
|
+
gerekir; `npien` şu an relay içermez.
|
|
203
|
+
- **Sinyalleşme**: İnternet üzerinde iki tarafın uç noktalarını öğrenmesi için
|
|
204
|
+
bir paylaşım kanalı (ticket) gerekir. LAN içinde broadcast, aynı makinede
|
|
205
|
+
kayıt defteri otomatik çalışır.
|
|
206
|
+
- **Güvenilirlik**: UDP semantiği geçerlidir; sıralama/teslim garantisi yoktur.
|
|
207
|
+
Uygulama katmanında gerekirse yeniden gönderim ekleyin.
|
|
208
|
+
- **Kimlik doğrulama**: Ticket ile açık anahtar pinlenir. Ticket'ı güvenilir bir
|
|
209
|
+
kanaldan paylaşın; aksi halde TOFU (ilk kullanımda güven) geçerlidir ve aktif
|
|
210
|
+
MITM engellenemez.
|
|
211
|
+
- IPv4 ve tek ağ arayüzü varsayılır.
|
|
212
|
+
|
|
213
|
+
## Test
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
python -m pytest -q
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Testler; paket çerçeveleme, şifreleme/ticket doğrulama, STUN ayrıştırma, kayıt
|
|
220
|
+
defteri ve gerçek UDP loopback üzerinden hole punching + şifreli mesajlaşmayı
|
|
221
|
+
kapsar.
|
|
222
|
+
|
|
223
|
+
## Örnek uygulama
|
|
224
|
+
|
|
225
|
+
`examples/chat.py`:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
python examples/chat.py --room 492109 --name ali --secure
|
|
229
|
+
# başka terminal / makine:
|
|
230
|
+
python examples/chat.py --room 492109 --name ayse --ticket "npn1...." --secure
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Lisans
|
|
234
|
+
|
|
235
|
+
MIT
|
npien-1.0.0/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# npien
|
|
2
|
+
|
|
3
|
+
**NAT-piercing NET** — sunucusuz (serverless) P2P UDP hole punching, oda sistemi,
|
|
4
|
+
Google STUN sunucuları ve isteğe bağlı uçtan uca şifreleme.
|
|
5
|
+
|
|
6
|
+
`npien`, iki Python sürecinin NAT arkasından doğrudan birbirine UDP paketi
|
|
7
|
+
göndermesini sağlar. STUN keşfi, delik açma (hole punching), paket çerçeveleme,
|
|
8
|
+
parçalama (fragmentation), keep-alive ve şifreleme kütüphanenin içinde yapılır.
|
|
9
|
+
|
|
10
|
+
## Özellikler
|
|
11
|
+
|
|
12
|
+
- Google STUN sunucuları ile dış IP/port keşfi (RFC 5389, yerleşik istemci)
|
|
13
|
+
- UDP hole punching: aynı anda iki tarafa HELLO paketleri ile NAT eşlemesi açma
|
|
14
|
+
- Oda (room) sistemi: `npien.connect("492109")`
|
|
15
|
+
- Peer ticket: sunucu olmadan uç nokta paylaşımı (`npn1.<payload>.<hmac>`)
|
|
16
|
+
- Yerel keşif: aynı makinede kayıt defteri (registry), LAN'de UDP broadcast
|
|
17
|
+
- İsteğe bağlı E2EE: X25519 anahtar anlaşması + AES-256-GCM (`npien.advsecure()`)
|
|
18
|
+
- Büyük mesajlar için otomatik parçalama ve birleştirme
|
|
19
|
+
- Keep-alive / zaman aşımı yönetimi ve `on_message` callback'leri
|
|
20
|
+
- IPv4, Python 3.10+
|
|
21
|
+
|
|
22
|
+
## Kurulum
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install -e .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Bağımlılık: `cryptography` (E2EE için). STUN istemcisi kütüphanenin içindedir;
|
|
29
|
+
`pystun3` gerekmez.
|
|
30
|
+
|
|
31
|
+
## Hızlı başlangıç
|
|
32
|
+
|
|
33
|
+
### Aynı odada iki taraf
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import npien
|
|
37
|
+
|
|
38
|
+
npien.set_appid("testprogram")
|
|
39
|
+
npien.connect("492109")
|
|
40
|
+
npien.send("merhaba")
|
|
41
|
+
veri, adres = npien.receive(timeout=10)
|
|
42
|
+
print(adres, veri.decode())
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`connect("492109")` önce yerel kayıt defterine ve LAN'e bakar. İnternet üzerinden
|
|
46
|
+
iki farklı ağ arasında uç noktaların bir şekilde paylaşılması gerekir; bunun için
|
|
47
|
+
ticket kullanın:
|
|
48
|
+
|
|
49
|
+
### Ticket ile bağlanma
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
# B tarafı: dinle ve ticket üret
|
|
53
|
+
import npien
|
|
54
|
+
|
|
55
|
+
npien.set_appid("testprogram")
|
|
56
|
+
npien.listen("492109")
|
|
57
|
+
print("TICKET:", npien.create_ticket("492109"))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
# A tarafı: ticket'ı paylaşılan kanaldan (sohbet, e-posta, QR...) al
|
|
62
|
+
import npien
|
|
63
|
+
|
|
64
|
+
npien.set_appid("testprogram")
|
|
65
|
+
npien.connect("492109", ticket="npn1....")
|
|
66
|
+
npien.send("selam")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Ticket, hedefin dış IP/portu, yerel adresi ve açık anahtarını taşır. Aynı ticket
|
|
70
|
+
ile iki taraf aynı anda `connect(...)` de çağırabilir; delik açma simetriktir.
|
|
71
|
+
|
|
72
|
+
### Uçtan uca şifreleme
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import npien
|
|
76
|
+
|
|
77
|
+
npien.set_appid("testprogram")
|
|
78
|
+
npien.connect("492109", ticket=ticket)
|
|
79
|
+
npien.advsecure() # giden mesajlar AES-256-GCM ile şifrelenir
|
|
80
|
+
npien.send("gizli mesaj")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Anahtarlar X25519 ECDH ile her iki tarafta bağımsız olarak türetilir; mesajlar
|
|
84
|
+
ağda asla açık metin olarak dolaşmaz. Karşı taraf `advsecure()` çağırmasa bile
|
|
85
|
+
şifreli paketleri çözebilir; iki yönlü gizlilik için iki taraf da açmalıdır.
|
|
86
|
+
|
|
87
|
+
### Sınıf tabanlı kullanım (çoklu örnek)
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from npien import Npien
|
|
91
|
+
|
|
92
|
+
sunucu = Npien(appid="testprogram", name="sunucu")
|
|
93
|
+
sunucu.listen("492109")
|
|
94
|
+
ticket = sunucu.create_ticket("492109")
|
|
95
|
+
|
|
96
|
+
istemci = Npien(appid="testprogram", name="istemci")
|
|
97
|
+
istemci.connect("492109", ticket=ticket)
|
|
98
|
+
istemci.advsecure()
|
|
99
|
+
istemci.send(b"merhaba")
|
|
100
|
+
veri, adres = sunucu.receive(timeout=10)
|
|
101
|
+
|
|
102
|
+
istemci.close()
|
|
103
|
+
sunucu.close()
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Callback ile mesaj alma
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
@npien.on_message
|
|
110
|
+
def mesaj_geldi(veri: bytes, adres: tuple[str, int]):
|
|
111
|
+
print(adres, veri.decode(errors="replace"))
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Callback'ler transport thread'inde çalışır; ağır işleri ayrı bir thread/kuyruğa
|
|
115
|
+
devredin.
|
|
116
|
+
|
|
117
|
+
## API
|
|
118
|
+
|
|
119
|
+
### Modül seviyesi (varsayılan örnek)
|
|
120
|
+
|
|
121
|
+
| Fonksiyon | Açıklama |
|
|
122
|
+
| --- | --- |
|
|
123
|
+
| `set_appid(appid)` | Uygulama kimliğini ayarlar. Ticket HMAC'i ve oda ad alanı buna bağlıdır. |
|
|
124
|
+
| `set_name(name)` | Görünen ad (HELLO/ticket içinde taşınır). |
|
|
125
|
+
| `listen(room, advertise=True)` | Odayı dinlemeye başlar; `advertise` ise kendi ticket'ını kayıt defterine yazar. |
|
|
126
|
+
| `join_room(room)` | `listen` ile aynı. |
|
|
127
|
+
| `connect(room, peer=None, ticket=None, tickets=None, timeout=8.0, discover=True)` | Odaya bağlanır; bağlı peer'ların listesini döner. |
|
|
128
|
+
| `send(data, to=None)` | Varsayılan olarak odadaki tüm peer'lara gönderir. `to` bir oda adı veya `(ip, port)` olabilir. |
|
|
129
|
+
| `sendto(data, peer)` | Belirli bir peer'a gönderir. |
|
|
130
|
+
| `receive(timeout=None)` | `(bytes, (ip, port))` döner. |
|
|
131
|
+
| `receive_nowait()` | Bloklamadan dener; mesaj yoksa `TimeoutError`. |
|
|
132
|
+
| `advsecure(enabled=True)` | Giden mesajlarda E2EE'yi açar/kapatır. |
|
|
133
|
+
| `create_ticket(room)` | Paylaşılabilir ticket üretir; STUN keşfini tetikler. |
|
|
134
|
+
| `add_peer(ticket)` | Ticket'ı kayıt defterine ekler (odadaki diğer süreçler bulabilir). |
|
|
135
|
+
| `known_peers(room)` | Kayıt defterindeki ticket'ları döner. |
|
|
136
|
+
| `peers(room=None)` | Bağlı/bağlanan peer'ların `PeerInfo` listesi. |
|
|
137
|
+
| `on_message(callback)` | Gelen her mesaj için callback kaydeder. |
|
|
138
|
+
| `get_stun_info()` | Dış IP/port, yerel adres, NAT tipi ve STUN sunucuları. |
|
|
139
|
+
| `connection_info()` | Uygulama, oda, güvenlik ve peer özeti. |
|
|
140
|
+
| `disconnect(target=None)` | Peer/peer'ları ayırır. |
|
|
141
|
+
| `close()` | Kaynakları kapatır ve kayıt defteri girdilerini siler. |
|
|
142
|
+
|
|
143
|
+
### `connect` parametreleri
|
|
144
|
+
|
|
145
|
+
- `peer`: `("1.2.3.4", 5678)` veya `"1.2.3.4:5678"` ya da bunların listesi.
|
|
146
|
+
- `ticket` / `tickets`: `create_ticket` çıktısı; açık anahtar pinlenir.
|
|
147
|
+
- `timeout`: delik açma için toplam süre (saniye).
|
|
148
|
+
- `discover`: `True` ise kayıt defteri + LAN broadcast keşfi yapılır.
|
|
149
|
+
|
|
150
|
+
## Nasıl çalışır?
|
|
151
|
+
|
|
152
|
+
1. **STUN**: Ana UDP soketi ile Google STUN sunucularına Binding Request
|
|
153
|
+
gönderilir; XOR-MAPPED-ADDRESS'ten dış uç nokta öğrenilir. Aynı soket
|
|
154
|
+
kullanıldığı için NAT eşlemesi hole punching ile birebir aynıdır.
|
|
155
|
+
2. **Ticket**: Dış adres, yerel adres, uygulama kimliği, oda ve X25519 açık
|
|
156
|
+
anahtarı HMAC-SHA256 ile imzalanarak tek bir metne paketlenir.
|
|
157
|
+
3. **Hole punching**: Her iki taraf aday adreslere (dış + yerel) 250 ms'de bir
|
|
158
|
+
HELLO gönderir. NAT eşlemesi açılınca HELLO/HELLO_ACK alışverişi oturumu kurar.
|
|
159
|
+
4. **Oturum**: Oturum kimliği iki açık anahtarın sıralı hash'inden türer. Anahtar
|
|
160
|
+
X25519 + HKDF-SHA256 ile türetilir.
|
|
161
|
+
5. **Veri**: `NPN1` başlıklı paketler; 1200 baytlık datagramlara parçalanır.
|
|
162
|
+
Şifreli modda AES-256-GCM (12 baytlık nonce, oturuma bağlı AAD) kullanılır.
|
|
163
|
+
6. **Canlılık**: 10 saniyede bir PING/PONG; 45 saniye sessiz peer düşürülür.
|
|
164
|
+
7. **Keşif**: Aynı makinede `~/.npien/registry` altındaki kilitli JSON kayıtları;
|
|
165
|
+
LAN'de 47777/UDP broadcast ve ticket yanıtları. `NPIEN_HOME` ve
|
|
166
|
+
`NPIEN_DISCOVERY_PORT` ile değiştirilebilir.
|
|
167
|
+
|
|
168
|
+
Paket başlığı (28 bayt):
|
|
169
|
+
|
|
170
|
+
```text
|
|
171
|
+
NPN1 | version | type | flags | reserved | session(8) | msg_id(8) | frag_index(2) | frag_count(2)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Sınırlamalar
|
|
175
|
+
|
|
176
|
+
- **Simetrik NAT**: Her hedef için farklı dış port atayan NAT'larda doğrudan
|
|
177
|
+
hole punching çoğu zaman başarısız olur. Bu durumda TURN benzeri bir relay
|
|
178
|
+
gerekir; `npien` şu an relay içermez.
|
|
179
|
+
- **Sinyalleşme**: İnternet üzerinde iki tarafın uç noktalarını öğrenmesi için
|
|
180
|
+
bir paylaşım kanalı (ticket) gerekir. LAN içinde broadcast, aynı makinede
|
|
181
|
+
kayıt defteri otomatik çalışır.
|
|
182
|
+
- **Güvenilirlik**: UDP semantiği geçerlidir; sıralama/teslim garantisi yoktur.
|
|
183
|
+
Uygulama katmanında gerekirse yeniden gönderim ekleyin.
|
|
184
|
+
- **Kimlik doğrulama**: Ticket ile açık anahtar pinlenir. Ticket'ı güvenilir bir
|
|
185
|
+
kanaldan paylaşın; aksi halde TOFU (ilk kullanımda güven) geçerlidir ve aktif
|
|
186
|
+
MITM engellenemez.
|
|
187
|
+
- IPv4 ve tek ağ arayüzü varsayılır.
|
|
188
|
+
|
|
189
|
+
## Test
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
python -m pytest -q
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Testler; paket çerçeveleme, şifreleme/ticket doğrulama, STUN ayrıştırma, kayıt
|
|
196
|
+
defteri ve gerçek UDP loopback üzerinden hole punching + şifreli mesajlaşmayı
|
|
197
|
+
kapsar.
|
|
198
|
+
|
|
199
|
+
## Örnek uygulama
|
|
200
|
+
|
|
201
|
+
`examples/chat.py`:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
python examples/chat.py --room 492109 --name ali --secure
|
|
205
|
+
# başka terminal / makine:
|
|
206
|
+
python examples/chat.py --room 492109 --name ayse --ticket "npn1...." --secure
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Lisans
|
|
210
|
+
|
|
211
|
+
MIT
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .core import Npien, PeerInfo
|
|
4
|
+
from .crypto import Ticket
|
|
5
|
+
from .exceptions import (
|
|
6
|
+
ConnectionTimeoutError,
|
|
7
|
+
NotConnectedError,
|
|
8
|
+
NpienError,
|
|
9
|
+
PeerNotFoundError,
|
|
10
|
+
ProtocolError,
|
|
11
|
+
TicketError,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__version__ = "1.0.0"
|
|
15
|
+
|
|
16
|
+
_default: Npien | None = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def default() -> Npien:
|
|
20
|
+
global _default
|
|
21
|
+
if _default is None:
|
|
22
|
+
_default = Npien()
|
|
23
|
+
return _default
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def set_appid(appid: str) -> Npien:
|
|
27
|
+
return default().set_appid(appid)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def set_name(name: str) -> Npien:
|
|
31
|
+
return default().set_name(name)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def connect(room: str, peer=None, ticket: str | None = None, tickets=None,
|
|
35
|
+
timeout: float = 8.0, discover: bool = True):
|
|
36
|
+
return default().connect(
|
|
37
|
+
room,
|
|
38
|
+
peer=peer,
|
|
39
|
+
ticket=ticket,
|
|
40
|
+
tickets=tickets,
|
|
41
|
+
timeout=timeout,
|
|
42
|
+
discover=discover,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def listen(room: str, advertise: bool = True) -> Npien:
|
|
47
|
+
return default().listen(room, advertise=advertise)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def join_room(room: str, advertise: bool = True) -> Npien:
|
|
51
|
+
return default().join_room(room, advertise=advertise)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def send(data, to=None) -> int:
|
|
55
|
+
return default().send(data, to=to)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def sendto(data, peer) -> int:
|
|
59
|
+
return default().sendto(data, peer)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def receive(timeout: float | None = None):
|
|
63
|
+
return default().receive(timeout=timeout)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def receive_nowait():
|
|
67
|
+
return default().receive_nowait()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def advsecure(enabled: bool = True) -> Npien:
|
|
71
|
+
return default().advsecure(enabled)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def disablesecure() -> Npien:
|
|
75
|
+
return default().disablesecure()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def create_ticket(room: str | None = None) -> str:
|
|
79
|
+
return default().create_ticket(room)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def add_peer(ticket: str) -> None:
|
|
83
|
+
return default().add_peer(ticket)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def known_peers(room: str | None = None):
|
|
87
|
+
return default().known_peers(room)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def peers(room: str | None = None):
|
|
91
|
+
return default().peers(room)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def on_message(callback):
|
|
95
|
+
return default().on_message(callback)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_stun_info() -> dict:
|
|
99
|
+
return default().get_stun_info()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def connection_info() -> dict:
|
|
103
|
+
return default().connection_info()
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def disconnect(target=None) -> None:
|
|
107
|
+
return default().disconnect(target)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def close() -> None:
|
|
111
|
+
global _default
|
|
112
|
+
if _default is not None:
|
|
113
|
+
_default.close()
|
|
114
|
+
_default = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
__all__ = [
|
|
118
|
+
"Npien",
|
|
119
|
+
"PeerInfo",
|
|
120
|
+
"Ticket",
|
|
121
|
+
"NpienError",
|
|
122
|
+
"ProtocolError",
|
|
123
|
+
"TicketError",
|
|
124
|
+
"NotConnectedError",
|
|
125
|
+
"PeerNotFoundError",
|
|
126
|
+
"ConnectionTimeoutError",
|
|
127
|
+
"default",
|
|
128
|
+
"set_appid",
|
|
129
|
+
"set_name",
|
|
130
|
+
"connect",
|
|
131
|
+
"listen",
|
|
132
|
+
"join_room",
|
|
133
|
+
"send",
|
|
134
|
+
"sendto",
|
|
135
|
+
"receive",
|
|
136
|
+
"receive_nowait",
|
|
137
|
+
"advsecure",
|
|
138
|
+
"disablesecure",
|
|
139
|
+
"create_ticket",
|
|
140
|
+
"add_peer",
|
|
141
|
+
"known_peers",
|
|
142
|
+
"peers",
|
|
143
|
+
"on_message",
|
|
144
|
+
"get_stun_info",
|
|
145
|
+
"connection_info",
|
|
146
|
+
"disconnect",
|
|
147
|
+
"close",
|
|
148
|
+
]
|