Compare commits
396
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80177c9b3e | ||
|
|
8f13f0ec4d | ||
|
|
5420e414ce | ||
|
|
69112ffde1 | ||
|
|
89d675f15d | ||
|
|
914f252b12 | ||
|
|
a7b20f268f | ||
|
|
c0207ad41d | ||
|
|
8051dfe4e5 | ||
|
|
22c24d135e | ||
|
|
d8c8c1c5b0 | ||
|
|
231081dd2f | ||
|
|
62b3695a77 | ||
|
|
0e2b0e28d8 | ||
|
|
e50ca9978e | ||
|
|
b667d6f352 | ||
|
|
6717c863e7 | ||
|
|
2bbdf8195f | ||
|
|
73f707ef9b | ||
|
|
34683f0500 | ||
|
|
150e9977df | ||
|
|
f913bdf351 | ||
|
|
0dd942be63 | ||
|
|
474bb8bd6b | ||
|
|
3419ff3a85 | ||
|
|
6b2b0f1360 | ||
|
|
20e81e493d | ||
|
|
23732039de | ||
|
|
f883c8da75 | ||
|
|
4211e32f51 | ||
|
|
7c840c8cfd | ||
|
|
1a1e305b6b | ||
|
|
974c6770e0 | ||
|
|
075c4f4583 | ||
|
|
9e133699bf | ||
|
|
81cad40e3b | ||
|
|
baca018387 | ||
|
|
48a4f182aa | ||
|
|
7c456a1652 | ||
|
|
a0ea19acb8 | ||
|
|
1f34629753 | ||
|
|
9a3eb1ce80 | ||
|
|
5982b011b4 | ||
|
|
e55c4ec7ce | ||
|
|
dd1b9f2680 | ||
|
|
52f32da3f1 | ||
|
|
defb70f63a | ||
|
|
40c7a5b487 | ||
|
|
e81299a727 | ||
|
|
3217f744f9 | ||
|
|
24a5de9398 | ||
|
|
a09c647dfd | ||
|
|
11002bcb57 | ||
|
|
7a35f4c386 | ||
|
|
483bf440fb | ||
|
|
6f1916b6b2 | ||
|
|
01dbd76834 | ||
|
|
6ee030ca7c | ||
|
|
3ae4988353 | ||
|
|
57281a975e | ||
|
|
b953a6daf0 | ||
|
|
612b6bf1bf | ||
|
|
58e45e9232 | ||
|
|
f8111f7f03 | ||
|
|
859290c202 | ||
|
|
cfd885de7c | ||
|
|
8d0c40195b | ||
|
|
e765d62e43 | ||
|
|
c56ec0b0ed | ||
|
|
4dfc0ce947 | ||
|
|
b8da0db1ab | ||
|
|
30acde7b18 | ||
|
|
c0b6e5769e | ||
|
|
3c036ecc4c | ||
|
|
cb764a1143 | ||
|
|
0c8de3db84 | ||
|
|
4ba790b921 | ||
|
|
e0378ebf1d | ||
|
|
d784b2bfbe | ||
|
|
0449a5fd9d | ||
|
|
d0029178e7 | ||
|
|
ca7f6c0d01 | ||
|
|
609be50b8f | ||
|
|
e4e5ed71f8 | ||
|
|
4716641dff | ||
|
|
bbf39c091d | ||
|
|
a0da29f1eb | ||
|
|
dc2b394e7a | ||
|
|
04f6b3e040 | ||
|
|
63f019530b | ||
|
|
3533ec3122 | ||
|
|
ac3fe79db8 | ||
|
|
e7eb24537d | ||
|
|
51d5790938 | ||
|
|
2a259191b9 | ||
|
|
331d43c6f7 | ||
|
|
7f5325ac24 | ||
|
|
3a2b427287 | ||
|
|
43baeb2306 | ||
|
|
fceec1aea6 | ||
|
|
c5c4967b69 | ||
|
|
4556f327c2 | ||
|
|
226a5744b5 | ||
|
|
1f4fe37fe5 | ||
|
|
e7a9163011 | ||
|
|
b43de4c1f0 | ||
|
|
60800a7f35 | ||
|
|
f43631a532 | ||
|
|
6d661360b1 | ||
|
|
b2e72db31b | ||
|
|
c5b4ec7a05 | ||
|
|
0adff0253d | ||
|
|
4f0baa37db | ||
|
|
6c79939db3 | ||
|
|
a80ba30b06 | ||
|
|
ed6b4d1046 | ||
|
|
803387f2b4 | ||
|
|
53d3f26e87 | ||
|
|
21cfe2d381 | ||
|
|
ac3cdd7739 | ||
|
|
0f0857c22a | ||
|
|
c4e80e6fd7 | ||
|
|
9d4c5b00f4 | ||
|
|
f691c01b45 | ||
|
|
9a5d8797af | ||
|
|
d1c9ef8377 | ||
|
|
e5d11ef9c1 | ||
|
|
c3eccd7b35 | ||
|
|
616986457f | ||
|
|
10b9c93fcb | ||
|
|
be791ee7b2 | ||
|
|
01254406d8 | ||
|
|
c4b1436919 | ||
|
|
f708b1d095 | ||
|
|
deaeae747a | ||
|
|
4b7a1656fc | ||
|
|
40d35ee2a5 | ||
|
|
80e4e4fd5d | ||
|
|
8de8b5bb41 | ||
|
|
7c668b5d6c | ||
|
|
ba03cab182 | ||
|
|
ca3215ab91 | ||
|
|
f9d5495993 | ||
|
|
4e6d57e371 | ||
|
|
a02c474b94 | ||
|
|
2585ef6f29 | ||
|
|
6521451493 | ||
|
|
2144eea46f | ||
|
|
7c71ef683c | ||
|
|
8994d7a834 | ||
|
|
2d516bf16e | ||
|
|
6c2aeb69a1 | ||
|
|
d6c6ca9eb3 | ||
|
|
5aa9bdbd64 | ||
|
|
194c8398c1 | ||
|
|
4d5afe5ae9 | ||
|
|
06b94d37e8 | ||
|
|
1a4b0a9de6 | ||
|
|
f18037ece2 | ||
|
|
b829ff7001 | ||
|
|
60cebb2535 | ||
|
|
3258a64df4 | ||
|
|
cec736a4b0 | ||
|
|
238fb0eb23 | ||
|
|
f1bd65f450 | ||
|
|
f85f2f20fb | ||
|
|
b1e50c2838 | ||
|
|
76e916f94d | ||
|
|
01cf7f67b6 | ||
|
|
c396b91fde | ||
|
|
889f6d5c3a | ||
|
|
13d469d866 | ||
|
|
67d57c9751 | ||
|
|
02c7153822 | ||
|
|
07540b7ff8 | ||
|
|
d00996243d | ||
|
|
edeb83ce89 | ||
|
|
d3b6057f82 | ||
|
|
d0af315ff6 | ||
|
|
ad8bc0ae00 | ||
|
|
c05e1c9991 | ||
|
|
a6311a17d0 | ||
|
|
9b59415f83 | ||
|
|
17e95cc2a3 | ||
|
|
68f4ae12c8 | ||
|
|
a0e5446790 | ||
|
|
2611a05d81 | ||
|
|
fcb6a59ca9 | ||
|
|
075d97a459 | ||
|
|
c24a6367cc | ||
|
|
e1f051856f | ||
|
|
2565e8d008 | ||
|
|
9869cf66f1 | ||
|
|
5e834b519f | ||
|
|
171e70a810 | ||
|
|
dea3a39598 | ||
|
|
0f8df44b2b | ||
|
|
d0d4199273 | ||
|
|
e7c32d9ac1 | ||
|
|
7d03749522 | ||
|
|
61fee27478 | ||
|
|
edfe665837 | ||
|
|
6c057ca89a | ||
|
|
a356eedc0d | ||
|
|
227a1aafad | ||
|
|
048d1c18b1 | ||
|
|
bbebc74bdb | ||
|
|
973c00e544 | ||
|
|
fb76b4252f | ||
|
|
456d274e12 | ||
|
|
c391d497b4 | ||
|
|
4e040b2891 | ||
|
|
f6ef38aa5d | ||
|
|
9c509d4c86 | ||
|
|
39544e6b2b | ||
|
|
2fadec947b | ||
|
|
595d2b177b | ||
|
|
69b49d424b | ||
|
|
6284a95267 | ||
|
|
8cb05da11c | ||
|
|
aafa0cfcab | ||
|
|
3dd8fa4085 | ||
|
|
ba30f2adc6 | ||
|
|
907ef244c6 | ||
|
|
b7da11c2bf | ||
|
|
a66edb8f5f | ||
|
|
11d511797a | ||
|
|
45a181370f | ||
|
|
27df3355f1 | ||
|
|
e0ce9fefc3 | ||
|
|
6aedf5fe91 | ||
|
|
724a9ff5c7 | ||
|
|
697aee0351 | ||
|
|
30413ebce0 | ||
|
|
20f3ba092f | ||
|
|
e39979bfac | ||
|
|
3625a4e5d5 | ||
|
|
a3063ea47a | ||
|
|
f02603f8ac | ||
|
|
bd749c1484 | ||
|
|
0c748501c8 | ||
|
|
72c6b6f197 | ||
|
|
4b9bffffbd | ||
|
|
d3d95c3c19 | ||
|
|
bcb1c37c87 | ||
|
|
3959ed8afe | ||
|
|
5b6455ccff | ||
|
|
ae1c59f3a9 | ||
|
|
4760baa973 | ||
|
|
33fd5bc123 | ||
|
|
04f02ffead | ||
|
|
07db51382e | ||
|
|
ca42e2fa66 | ||
|
|
7a4e7ca4ae | ||
|
|
8e9db5211a | ||
|
|
57dba26e7c | ||
|
|
5b0a743e02 | ||
|
|
b0faf7d372 | ||
|
|
0984ea749a | ||
|
|
b466d14fe4 | ||
|
|
2e3eb0976a | ||
|
|
385aa066b2 | ||
|
|
9be03ece76 | ||
|
|
7a46817799 | ||
|
|
ceef0d701a | ||
|
|
c72d2afbef | ||
|
|
a44d9c10a1 | ||
|
|
1c501f9690 | ||
|
|
e5e520de21 | ||
|
|
cbde38d52a | ||
|
|
5d95f6db30 | ||
|
|
c136db5666 | ||
|
|
77fd7c19de | ||
|
|
f02e1dccec | ||
|
|
6a971389b6 | ||
|
|
16a2aa959d | ||
|
|
6127409c04 | ||
|
|
dc0d700d5c | ||
|
|
60e845ca94 | ||
|
|
d74ab1c1c6 | ||
|
|
3c0b5f501a | ||
|
|
cee9a04348 | ||
|
|
e596564abd | ||
|
|
cfd1e53ee9 | ||
|
|
4e2b1c0c01 | ||
|
|
c57f94f38d | ||
|
|
09248bdbb6 | ||
|
|
41952703d9 | ||
|
|
f195d1d91e | ||
|
|
7b305e820f | ||
|
|
ea844734de | ||
|
|
912eb8480c | ||
|
|
2558942da1 | ||
|
|
b2ed0317a6 | ||
|
|
0157fe6035 | ||
|
|
53a97d2bab | ||
|
|
ae2b654a67 | ||
|
|
58eea06f60 | ||
|
|
a1dd99c8d4 | ||
|
|
d523c6ef45 | ||
|
|
6cba046409 | ||
|
|
cac786f684 | ||
|
|
356e091b7a | ||
|
|
d278041330 | ||
|
|
70deb254b6 | ||
|
|
fdd4ec02f6 | ||
|
|
9ed88d2418 | ||
|
|
d975696543 | ||
|
|
8bad15b7bb | ||
|
|
1a171d5a24 | ||
|
|
122e619649 | ||
|
|
c36462ce09 | ||
|
|
c1d2d26cae | ||
|
|
cd5d6f6e86 | ||
|
|
09fa26bc2c | ||
|
|
c0c366bd83 | ||
|
|
0a44320245 | ||
|
|
27874ffa22 | ||
|
|
c93b84529e | ||
|
|
ad35bbab15 | ||
|
|
cd63f5a1b3 | ||
|
|
e8e20cad88 | ||
|
|
6de88ec85f | ||
|
|
62a059abdb | ||
|
|
54982f2dfb | ||
|
|
7cdb84c7a2 | ||
|
|
31eb4a5d26 | ||
|
|
b613e3a8e0 | ||
|
|
5dc8ad6784 | ||
|
|
2903b82941 | ||
|
|
dba092d88b | ||
|
|
3b31821316 | ||
|
|
09195b2f61 | ||
|
|
ffbc0da86f | ||
|
|
389124d327 | ||
|
|
05323ded60 | ||
|
|
07306e378b | ||
|
|
20ff5a9ed1 | ||
|
|
8794c24893 | ||
|
|
aaa2a13849 | ||
|
|
9c612486b3 | ||
|
|
85367fbe36 | ||
|
|
0dbefab922 | ||
|
|
cec2ffe2eb | ||
|
|
04d3a0e52e | ||
|
|
3e2bf2be4a | ||
|
|
29e54195ed | ||
|
|
5a36b0d30d | ||
|
|
766fd71296 | ||
|
|
8a27c7ad80 | ||
|
|
692b9054f7 | ||
|
|
da09acd24f | ||
|
|
b4ccf548ed | ||
|
|
f094b9cabb | ||
|
|
4ce48bbd7d | ||
|
|
f873e16a63 | ||
|
|
92673d744a | ||
|
|
23622fc421 | ||
|
|
3904c9540d | ||
|
|
fb386976c5 | ||
|
|
6ddc69fec4 | ||
|
|
ac09cd850f | ||
|
|
d6ce728965 | ||
|
|
1e193b8de0 | ||
|
|
52da3fbfe5 | ||
|
|
1f5e2d75db | ||
|
|
1b0f393c6c | ||
|
|
8b16559230 | ||
|
|
e9ff5fcfdb | ||
|
|
b88dcf4a2c | ||
|
|
e82a95d529 | ||
|
|
c3bfa7a8c9 | ||
|
|
f1b6d90d71 | ||
|
|
7f0f28fd58 | ||
|
|
189a93c71d | ||
|
|
fd815150f6 | ||
|
|
c8bac5d322 | ||
|
|
d733bcbf4d | ||
|
|
feb8024506 | ||
|
|
c76646a39a | ||
|
|
07e4f6f086 | ||
|
|
5050f9a8aa | ||
|
|
e47593cd77 | ||
|
|
5184181695 | ||
|
|
b9f8930d1c | ||
|
|
2d9fbd9911 | ||
|
|
ceb94bee7a | ||
|
|
491f44e05e | ||
|
|
2f97196ffd | ||
|
|
9a9a4ccf3c | ||
|
|
2260e068f0 | ||
|
|
d3c4b5f6e7 | ||
|
|
7ee3193dde | ||
|
|
9628686d63 | ||
|
|
a2e4a4b208 | ||
|
|
6938bfcb6a |
No files matched your search
+43
-3
@@ -1,3 +1,7 @@
|
||||
# Build directory (out-of-tree build)
|
||||
/build/
|
||||
tools/chatgui/build/
|
||||
|
||||
# Build outputs
|
||||
*.o
|
||||
*.lo
|
||||
@@ -6,10 +10,19 @@
|
||||
*.so
|
||||
*.so.*
|
||||
utun
|
||||
test_serialize
|
||||
linux-x64-deb
|
||||
|
||||
# Temp files
|
||||
*.c1
|
||||
*.h2
|
||||
1
|
||||
|
||||
# HTML files
|
||||
index.html
|
||||
|
||||
# binary files
|
||||
*.exe
|
||||
**/*[^.]
|
||||
|
||||
# Autotools
|
||||
Makefile
|
||||
@@ -47,7 +60,7 @@ Makefile.in
|
||||
.cache/
|
||||
|
||||
# Debug outputs
|
||||
core
|
||||
/core
|
||||
vgcore.*
|
||||
callgrind.out.*
|
||||
|
||||
@@ -56,11 +69,20 @@ tests/*.log
|
||||
tests/*.trs
|
||||
tests/logs/
|
||||
|
||||
# Test binaries (files without extension in tests/)
|
||||
# Mass update logs
|
||||
logs/
|
||||
|
||||
# Test binaries (executables without extension), but track source/config files
|
||||
tests/test_*
|
||||
!tests/test_*.c
|
||||
!tests/test_*.conf
|
||||
!tests/test_*.json
|
||||
tests/bench_*
|
||||
!tests/bench_*.c
|
||||
|
||||
# All log files
|
||||
*.log
|
||||
utun.log*
|
||||
|
||||
# Local configs
|
||||
*.cfg
|
||||
@@ -68,8 +90,26 @@ tests/logs/
|
||||
utun.conf
|
||||
!utun.conf.sample
|
||||
|
||||
# IDE/Editor directories
|
||||
.opencode/
|
||||
|
||||
# Tool binaries
|
||||
tools/bping/bping
|
||||
tools/proxy/udp_proxy
|
||||
|
||||
# Reference projects (not part of utun)
|
||||
tools/tdesktop-dev/
|
||||
|
||||
# Debug/temp artifacts
|
||||
*.bak
|
||||
*.debug
|
||||
*.csv
|
||||
__pycache__/
|
||||
|
||||
|
||||
# MSVC temp files
|
||||
cl
|
||||
build_win.log
|
||||
/wintun.dll
|
||||
src/lwip_orig/
|
||||
lib/timeout/
|
||||
@@ -1,102 +0,0 @@
|
||||
# Context7 Skill
|
||||
|
||||
## Purpose
|
||||
|
||||
Fetches **live, version-specific documentation** for external libraries and frameworks using the Context7 API. Ensures you always get current API patterns instead of potentially outdated training data.
|
||||
|
||||
**Golden Rule**: Always fetch live docs for external libraries—training data may be outdated.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Recommended: Use ExternalScout Subagent
|
||||
|
||||
The **ExternalScout** subagent is the recommended way to fetch external documentation. It handles:
|
||||
- Library detection
|
||||
- Query optimization
|
||||
- Documentation filtering and sorting
|
||||
- Formatted results with code examples
|
||||
|
||||
**Invocation**:
|
||||
```
|
||||
Use ExternalScout to fetch documentation for [Library Name]: [your specific question]
|
||||
```
|
||||
|
||||
**Example**:
|
||||
```
|
||||
Use ExternalScout to fetch documentation for Drizzle ORM: How do I set up modular schemas with PostgreSQL?
|
||||
```
|
||||
|
||||
### Alternative: Direct Skill Usage
|
||||
|
||||
You can also invoke the Context7 skill directly via bash:
|
||||
|
||||
```bash
|
||||
# Step 1: Search for library
|
||||
curl -s "https://context7.com/api/v2/libs/search?libraryName=LIBRARY&query=TOPIC" | jq '.results[0]'
|
||||
|
||||
# Step 2: Fetch documentation
|
||||
curl -s "https://context7.com/api/v2/context?libraryId=LIBRARY_ID&query=OPTIMIZED_QUERY&type=txt"
|
||||
```
|
||||
|
||||
See `SKILL.md` for detailed API documentation.
|
||||
|
||||
## Supported Libraries
|
||||
|
||||
See `library-registry.md` for the complete list of supported libraries including:
|
||||
- **Database & ORM**: Drizzle, Prisma
|
||||
- **Authentication**: Better Auth, NextAuth.js, Clerk
|
||||
- **Frontend**: Next.js, React, TanStack Query/Router/Start
|
||||
- **Infrastructure**: Cloudflare Workers, AWS Lambda, Vercel
|
||||
- **UI**: Shadcn/ui, Radix UI, Tailwind CSS
|
||||
- **State**: Zustand, Jotai
|
||||
- **Validation**: Zod, React Hook Form
|
||||
- **Testing**: Vitest, Playwright
|
||||
|
||||
## Workflow
|
||||
|
||||
```
|
||||
User Query
|
||||
↓
|
||||
ContextScout (searches internal context)
|
||||
↓
|
||||
No internal context found
|
||||
↓
|
||||
ContextScout recommends ExternalScout
|
||||
↓
|
||||
ExternalScout invoked
|
||||
├─ Reads library-registry.md
|
||||
├─ Detects library
|
||||
├─ Loads query patterns
|
||||
├─ Fetches from Context7 API
|
||||
├─ Filters & sorts results
|
||||
└─ Returns formatted documentation
|
||||
↓
|
||||
User receives current, actionable docs
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
- **`SKILL.md`** - Context7 API documentation and usage
|
||||
- **`library-registry.md`** - Supported libraries, aliases, and query patterns
|
||||
- **`README.md`** - This file (overview and quick start)
|
||||
|
||||
## Adding New Libraries
|
||||
|
||||
To add a new library to the registry:
|
||||
|
||||
1. Edit `library-registry.md`
|
||||
2. Add entry under appropriate category:
|
||||
```markdown
|
||||
#### Library Name
|
||||
- **Aliases**: `alias1`, `alias2`, `package-name`
|
||||
- **Docs**: https://example.com/docs
|
||||
- **Context7**: `use context7 for library-name`
|
||||
- **Common topics**: topic1, topic2, topic3
|
||||
```
|
||||
3. (Optional) Add query optimization patterns
|
||||
4. ExternalScout will automatically detect the new library
|
||||
|
||||
## Related
|
||||
|
||||
- **ExternalScout**: `.opencode/agent/subagents/core/externalscout.md`
|
||||
- **ContextScout**: `.opencode/agent/subagents/core/contextscout.md`
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
name: context7
|
||||
description: Retrieve up-to-date documentation for software libraries, frameworks, and components via the Context7 API. This skill should be used when looking up documentation for any programming library or framework, finding code examples for specific APIs or features, verifying correct usage of library functions, or obtaining current information about library APIs that may have changed since training.
|
||||
---
|
||||
|
||||
# Context7
|
||||
|
||||
## Overview
|
||||
|
||||
This skill enables retrieval of current documentation for software libraries and components by querying the Context7 API via curl. Use it instead of relying on potentially outdated training data.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Search for the Library
|
||||
|
||||
To find the Context7 library ID, query the search endpoint:
|
||||
|
||||
```bash
|
||||
curl -s "https://context7.com/api/v2/libs/search?libraryName=LIBRARY_NAME&query=TOPIC" | jq '.results[0]'
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `libraryName` (required): The library name to search for (e.g., "react", "nextjs", "fastapi", "axios")
|
||||
- `query` (required): A description of the topic for relevance ranking
|
||||
|
||||
**Response fields:**
|
||||
- `id`: Library identifier for the context endpoint (e.g., `/websites/react_dev_reference`)
|
||||
- `title`: Human-readable library name
|
||||
- `description`: Brief description of the library
|
||||
- `totalSnippets`: Number of documentation snippets available
|
||||
|
||||
### Step 2: Fetch Documentation
|
||||
|
||||
To retrieve documentation, use the library ID from step 1:
|
||||
|
||||
```bash
|
||||
curl -s "https://context7.com/api/v2/context?libraryId=LIBRARY_ID&query=TOPIC&type=txt"
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `libraryId` (required): The library ID from search results
|
||||
- `query` (required): The specific topic to retrieve documentation for
|
||||
- `type` (optional): Response format - `json` (default) or `txt` (plain text, more readable)
|
||||
|
||||
## Examples
|
||||
|
||||
### React hooks documentation
|
||||
|
||||
```bash
|
||||
# Find React library ID
|
||||
curl -s "https://context7.com/api/v2/libs/search?libraryName=react&query=hooks" | jq '.results[0].id'
|
||||
# Returns: "/websites/react_dev_reference"
|
||||
|
||||
# Fetch useState documentation
|
||||
curl -s "https://context7.com/api/v2/context?libraryId=/websites/react_dev_reference&query=useState&type=txt"
|
||||
```
|
||||
|
||||
### Next.js routing documentation
|
||||
|
||||
```bash
|
||||
# Find Next.js library ID
|
||||
curl -s "https://context7.com/api/v2/libs/search?libraryName=nextjs&query=routing" | jq '.results[0].id'
|
||||
|
||||
# Fetch app router documentation
|
||||
curl -s "https://context7.com/api/v2/context?libraryId=/vercel/next.js&query=app+router&type=txt"
|
||||
```
|
||||
|
||||
### FastAPI dependency injection
|
||||
|
||||
```bash
|
||||
# Find FastAPI library ID
|
||||
curl -s "https://context7.com/api/v2/libs/search?libraryName=fastapi&query=dependencies" | jq '.results[0].id'
|
||||
|
||||
# Fetch dependency injection documentation
|
||||
curl -s "https://context7.com/api/v2/context?libraryId=/fastapi/fastapi&query=dependency+injection&type=txt"
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
- Use `type=txt` for more readable output
|
||||
- Use `jq` to filter and format JSON responses
|
||||
- Be specific with the `query` parameter to improve relevance ranking
|
||||
- If the first search result is not correct, check additional results in the array
|
||||
- URL-encode query parameters containing spaces (use `+` or `%20`)
|
||||
- No API key is required for basic usage (rate-limited)
|
||||
@@ -1,290 +0,0 @@
|
||||
# External Library Registry
|
||||
|
||||
## Purpose
|
||||
|
||||
This file lists external libraries/frameworks that should use **ExternalScout** (via Context7) for live documentation instead of relying on potentially outdated training data.
|
||||
|
||||
## When to Use This
|
||||
|
||||
**ContextScout** checks this list when:
|
||||
1. User asks about a library/framework
|
||||
2. No internal context exists in `.opencode/context/development/frameworks/`
|
||||
3. Query matches a library name below
|
||||
|
||||
**Action**: Recommend **ExternalScout** subagent
|
||||
|
||||
---
|
||||
|
||||
## Supported Libraries
|
||||
|
||||
### Database & ORM
|
||||
|
||||
#### Drizzle ORM
|
||||
- **Aliases**: `drizzle`, `drizzle-orm`, `drizzle orm`
|
||||
- **Docs**: https://orm.drizzle.team/
|
||||
- **Context7**: `use context7 for drizzle`
|
||||
- **Common topics**: schema organization, migrations, relational queries, transactions, TypeScript types
|
||||
|
||||
#### Prisma
|
||||
- **Aliases**: `prisma`
|
||||
- **Docs**: https://www.prisma.io/docs
|
||||
- **Context7**: `use context7 for prisma`
|
||||
- **Common topics**: schema, migrations, client, relations, TypeScript
|
||||
|
||||
---
|
||||
|
||||
### Authentication
|
||||
|
||||
#### Better Auth
|
||||
- **Aliases**: `better-auth`, `better auth`, `betterauth`
|
||||
- **Docs**: https://www.better-auth.com/docs
|
||||
- **Context7**: `use context7 for better-auth`
|
||||
- **Common topics**: Next.js integration, Drizzle adapter, social providers, session management, 2FA
|
||||
|
||||
#### NextAuth.js
|
||||
- **Aliases**: `nextauth`, `next-auth`, `nextauth.js`
|
||||
- **Docs**: https://next-auth.js.org/
|
||||
- **Context7**: `use context7 for nextauth`
|
||||
- **Common topics**: providers, callbacks, sessions, JWT
|
||||
|
||||
#### Clerk
|
||||
- **Aliases**: `clerk`
|
||||
- **Docs**: https://clerk.com/docs
|
||||
- **Context7**: `use context7 for clerk`
|
||||
- **Common topics**: authentication, user management, organizations
|
||||
|
||||
---
|
||||
|
||||
### Frontend Frameworks
|
||||
|
||||
#### Next.js
|
||||
- **Aliases**: `nextjs`, `next.js`, `next`
|
||||
- **Docs**: https://nextjs.org/docs
|
||||
- **Context7**: `use context7 for nextjs`
|
||||
- **Common topics**: App Router, Server Actions, Server Components, routing, middleware, API routes
|
||||
|
||||
#### React
|
||||
- **Aliases**: `react`, `reactjs`, `react.js`
|
||||
- **Docs**: https://react.dev/
|
||||
- **Context7**: `use context7 for react`
|
||||
- **Common topics**: hooks, components, state, effects, context
|
||||
|
||||
#### TanStack Query
|
||||
- **Aliases**: `tanstack query`, `react query`, `@tanstack/react-query`
|
||||
- **Docs**: https://tanstack.com/query/latest
|
||||
- **Context7**: `use context7 for tanstack query`
|
||||
- **Common topics**: useQuery, useMutation, prefetching, caching, Server Components
|
||||
|
||||
#### TanStack Router
|
||||
- **Aliases**: `tanstack router`, `@tanstack/react-router`
|
||||
- **Docs**: https://tanstack.com/router/latest
|
||||
- **Context7**: `use context7 for tanstack router`
|
||||
- **Common topics**: routing, type-safe routes, loaders, navigation
|
||||
|
||||
#### TanStack Start
|
||||
- **Aliases**: `tanstack start`, `@tanstack/start`
|
||||
- **Docs**: https://tanstack.com/start/latest
|
||||
- **Context7**: `use context7 for tanstack start`
|
||||
- **Common topics**: full-stack setup, server functions, file routing
|
||||
|
||||
---
|
||||
|
||||
### Infrastructure & Deployment
|
||||
|
||||
#### Cloudflare Workers
|
||||
- **Aliases**: `cloudflare workers`, `cloudflare`, `workers`, `cf workers`
|
||||
- **Docs**: https://developers.cloudflare.com/workers
|
||||
- **Context7**: `use context7 for cloudflare workers`
|
||||
- **Common topics**: routing, KV storage, Durable Objects, bindings, middleware
|
||||
|
||||
#### AWS Lambda
|
||||
- **Aliases**: `aws lambda`, `lambda`, `aws λ`
|
||||
- **Docs**: https://docs.aws.amazon.com/lambda
|
||||
- **Context7**: `use context7 for aws lambda`
|
||||
- **Common topics**: handlers, layers, environment variables, triggers, TypeScript
|
||||
|
||||
#### Vercel
|
||||
- **Aliases**: `vercel`
|
||||
- **Docs**: https://vercel.com/docs
|
||||
- **Context7**: `use context7 for vercel`
|
||||
- **Common topics**: deployment, environment variables, edge functions, serverless
|
||||
|
||||
---
|
||||
|
||||
### UI Libraries & Styling
|
||||
|
||||
#### Shadcn/ui
|
||||
- **Aliases**: `shadcn`, `shadcn/ui`, `shadcn-ui`
|
||||
- **Docs**: https://ui.shadcn.com/
|
||||
- **Context7**: `use context7 for shadcn`
|
||||
- **Common topics**: components, installation, theming, customization
|
||||
|
||||
#### Radix UI
|
||||
- **Aliases**: `radix`, `radix ui`, `radix-ui`, `@radix-ui`
|
||||
- **Docs**: https://www.radix-ui.com/
|
||||
- **Context7**: `use context7 for radix`
|
||||
- **Common topics**: primitives, accessibility, composition
|
||||
|
||||
#### Tailwind CSS
|
||||
- **Aliases**: `tailwind`, `tailwindcss`, `tailwind css`
|
||||
- **Docs**: https://tailwindcss.com/docs
|
||||
- **Context7**: `use context7 for tailwind`
|
||||
- **Common topics**: configuration, utilities, responsive design, dark mode
|
||||
|
||||
---
|
||||
|
||||
### State Management
|
||||
|
||||
#### Zustand
|
||||
- **Aliases**: `zustand`
|
||||
- **Docs**: https://zustand-demo.pmnd.rs/
|
||||
- **Context7**: `use context7 for zustand`
|
||||
- **Common topics**: store creation, selectors, middleware, TypeScript
|
||||
|
||||
#### Jotai
|
||||
- **Aliases**: `jotai`
|
||||
- **Docs**: https://jotai.org/
|
||||
- **Context7**: `use context7 for jotai`
|
||||
- **Common topics**: atoms, async atoms, utilities
|
||||
|
||||
---
|
||||
|
||||
### Validation & Forms
|
||||
|
||||
#### Zod
|
||||
- **Aliases**: `zod`
|
||||
- **Docs**: https://zod.dev/
|
||||
- **Context7**: `use context7 for zod`
|
||||
- **Common topics**: schema validation, TypeScript inference, parsing, refinements
|
||||
|
||||
#### React Hook Form
|
||||
- **Aliases**: `react hook form`, `react-hook-form`, `rhf`
|
||||
- **Docs**: https://react-hook-form.com/
|
||||
- **Context7**: `use context7 for react hook form`
|
||||
- **Common topics**: register, validation, errors, TypeScript
|
||||
|
||||
---
|
||||
|
||||
### Testing
|
||||
|
||||
#### Vitest
|
||||
- **Aliases**: `vitest`
|
||||
- **Docs**: https://vitest.dev/
|
||||
- **Context7**: `use context7 for vitest`
|
||||
- **Common topics**: configuration, testing, mocking, coverage
|
||||
|
||||
#### Playwright
|
||||
- **Aliases**: `playwright`
|
||||
- **Docs**: https://playwright.dev/
|
||||
- **Context7**: `use context7 for playwright`
|
||||
- **Common topics**: browser automation, testing, selectors, assertions
|
||||
|
||||
---
|
||||
|
||||
## Detection Patterns
|
||||
|
||||
ContextScout and ExternalScout should match queries containing:
|
||||
- Library name (case-insensitive)
|
||||
- Common variations (e.g., "next.js" vs "nextjs")
|
||||
- Package names (e.g., "@tanstack/react-query")
|
||||
|
||||
**Examples**:
|
||||
- "How do I use **Drizzle** with PostgreSQL?" → Match: Drizzle ORM
|
||||
- "Show me **Next.js** App Router setup" → Match: Next.js
|
||||
- "**TanStack Query** with Server Components" → Match: TanStack Query
|
||||
- "**Better Auth** integration" → Match: Better Auth
|
||||
|
||||
---
|
||||
|
||||
## Query Optimization Patterns
|
||||
|
||||
### Drizzle ORM
|
||||
|
||||
| User Intent | Optimized Query |
|
||||
|-------------|-----------------|
|
||||
| Setup/Installation | `PostgreSQL+setup+configuration+TypeScript+installation` |
|
||||
| Modular schemas | `modular+schema+organization+domain+driven+design` |
|
||||
| Relations | `relational+queries+one+to+many+joins+with+relations` |
|
||||
| Migrations | `drizzle-kit+migrations+generate+push+PostgreSQL` |
|
||||
| Transactions | `database+transactions+patterns+TypeScript` |
|
||||
| Type safety | `TypeScript+type+inference+schema+types+inferInsert` |
|
||||
|
||||
### Better Auth
|
||||
|
||||
| User Intent | Optimized Query |
|
||||
|-------------|-----------------|
|
||||
| Setup | `setup+configuration+Next.js+TypeScript+installation` |
|
||||
| Next.js integration | `Next.js+App+Router+integration+setup+configuration` |
|
||||
| Drizzle adapter | `Drizzle+adapter+PostgreSQL+schema+generation+configuration` |
|
||||
| Social providers | `social+providers+OAuth+GitHub+Google+setup` |
|
||||
| Email/password | `email+password+authentication+signup+signin` |
|
||||
| Session management | `session+management+cookies+JWT+middleware` |
|
||||
|
||||
### Next.js
|
||||
|
||||
| User Intent | Optimized Query |
|
||||
|-------------|-----------------|
|
||||
| App Router | `App+Router+file+conventions+layouts+pages+routing` |
|
||||
| Server Actions | `Server+Actions+form+mutations+revalidation+TypeScript` |
|
||||
| Server Components | `React+Server+Components+async+data+fetching+patterns` |
|
||||
| Dynamic routes | `dynamic+routes+params+TypeScript+generateStaticParams` |
|
||||
| Middleware | `middleware+authentication+redirects+headers+cookies` |
|
||||
| API routes | `API+routes+route+handlers+TypeScript+POST+GET` |
|
||||
|
||||
### TanStack Query
|
||||
|
||||
| User Intent | Optimized Query |
|
||||
|-------------|-----------------|
|
||||
| Setup | `setup+QueryClient+provider+Next.js+TypeScript` |
|
||||
| Data fetching | `useQuery+data+fetching+TypeScript+patterns+async` |
|
||||
| Mutations | `useMutation+optimistic+updates+invalidation+TypeScript` |
|
||||
| Prefetching | `prefetchQuery+Server+Components+hydration+Next.js` |
|
||||
| Caching | `cache+configuration+staleTime+gcTime+invalidation` |
|
||||
|
||||
### Cloudflare Workers
|
||||
|
||||
| User Intent | Optimized Query |
|
||||
|-------------|-----------------|
|
||||
| Setup | `getting+started+setup+TypeScript+wrangler+configuration` |
|
||||
| Routing | `routing+itty-router+hono+request+handling` |
|
||||
| KV storage | `KV+storage+key+value+bindings+TypeScript` |
|
||||
| Durable Objects | `Durable+Objects+state+WebSockets+coordination` |
|
||||
|
||||
### AWS Lambda
|
||||
|
||||
| User Intent | Optimized Query |
|
||||
|-------------|-----------------|
|
||||
| Setup | `getting+started+setup+TypeScript+handler+configuration` |
|
||||
| Handlers | `handler+function+event+context+TypeScript+patterns` |
|
||||
| Layers | `layers+dependencies+shared+code+deployment` |
|
||||
| Environment variables | `environment+variables+secrets+configuration+SSM` |
|
||||
|
||||
---
|
||||
|
||||
## Adding New Libraries
|
||||
|
||||
To add a new library:
|
||||
1. Add entry under appropriate category
|
||||
2. Include: Name, aliases, docs link, Context7 command, common topics
|
||||
3. (Optional) Add query optimization patterns
|
||||
4. Update ExternalScout if needed (usually automatic)
|
||||
|
||||
**Template**:
|
||||
```markdown
|
||||
#### Library Name
|
||||
- **Aliases**: `alias1`, `alias2`, `package-name`
|
||||
- **Docs**: https://example.com/docs
|
||||
- **Context7**: `use context7 for library-name`
|
||||
- **Common topics**: topic1, topic2, topic3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage by ExternalScout
|
||||
|
||||
ExternalScout uses this file to:
|
||||
1. **Detect** which library the user is asking about
|
||||
2. **Load** query optimization patterns for that library
|
||||
3. **Build** optimized Context7 queries
|
||||
4. **Fetch** live documentation
|
||||
5. **Return** filtered, relevant results
|
||||
@@ -1,51 +0,0 @@
|
||||
# Context7 Skill Navigation
|
||||
|
||||
**Purpose**: Live documentation fetching for external libraries via Context7 API
|
||||
|
||||
---
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
context7/
|
||||
├── navigation.md # This file
|
||||
├── README.md # Quick start and workflow
|
||||
├── SKILL.md # Context7 API documentation
|
||||
└── library-registry.md # Supported libraries and query patterns
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Routes
|
||||
|
||||
| Task | Path |
|
||||
|------|------|
|
||||
| **Quick start** | `README.md` |
|
||||
| **API reference** | `SKILL.md` |
|
||||
| **Supported libraries** | `library-registry.md` (lines 18-181) |
|
||||
| **Query patterns** | `library-registry.md` (lines 199-261) |
|
||||
| **Add new library** | `library-registry.md` (lines 264-279) |
|
||||
| **ExternalScout integration** | `README.md` (lines 9-26) |
|
||||
|
||||
---
|
||||
|
||||
## By Purpose
|
||||
|
||||
**Using Context7**:
|
||||
- Quick start → `README.md`
|
||||
- API details → `SKILL.md`
|
||||
|
||||
**Adding Libraries**:
|
||||
- Template → `library-registry.md` (lines 272-279)
|
||||
- Supported list → `library-registry.md` (lines 18-181)
|
||||
|
||||
**Integration**:
|
||||
- ContextScout workflow → `README.md` (lines 54-73)
|
||||
- ExternalScout subagent → `.opencode/agent/subagents/core/externalscout.md`
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- **ExternalScout**: `.opencode/agent/subagents/core/externalscout.md`
|
||||
- **ContextScout**: `.opencode/agent/subagents/core/contextscout.md`
|
||||
@@ -3,58 +3,80 @@
|
||||
Ты - профессиональный программист высокого уровня.
|
||||
Ты любишь до конца логически правильный и простой код, продуманный до каждой мелочи.
|
||||
Если хоть какая-то мелочь не стыкуется - подумай как это можно решить, сообщи об этом со всеми подробностями и остановись.
|
||||
Если что-то получается нелогично или громоздко - хорошо подкмай как сделать просто и компактно. предложи варианты и остановись.
|
||||
Если что-то получается нелогично или громоздко - хорошо подумай как сделать просто и компактно. предложи варианты и остановись.
|
||||
Имей, загружай когда надо полный код нужных тебе функций/структур. Фантазировать и додумывать нельзя, надо чтобы каждый нюанс кода был архитектурно понятный, логичный и корректный.
|
||||
Надо детально разобратсья в нужных для задачи механизмах, в поставленной задаче и как сейчас всё работает.
|
||||
Старайся одно логичеси завершенное действие размещать на одной строке, если строка не слишком длинная (до 150 символов)
|
||||
Надо детально разобраться в нужных для задачи механизмах, в поставленной задаче и как сейчас всё работает.
|
||||
Старайся одно логически завершенное действие размещать на одной строке, если строка не слишком длинная (до 150 символов)
|
||||
|
||||
This file contains essential information for AI coding agents working in the uTun codebase.
|
||||
|
||||
## 📋 Quick Reference
|
||||
## Quick Reference
|
||||
|
||||
**Repository:** uTun - Secure VPN tunnel with ETCP protocol
|
||||
**Language:** C (C99)
|
||||
**Build System:** GNU Autotools (autoconf/automake)
|
||||
**Cryptography:** TinyCrypt (AES-CCM, ECC)
|
||||
**Repository:** uTun - Secure VPN tunnel with ETCP protocol
|
||||
**Language:** C (C99)
|
||||
**Build System:** GNU Autotools (autoconf/automake)
|
||||
**Cryptography:** OpenSSL (AES-CCM, X25519, SHA256)
|
||||
|
||||
## 🔧 Build Commands
|
||||
## Build Commands
|
||||
|
||||
### Full Build
|
||||
win: powershell build_full.bat (в точности как написано без дополнительных опций powershell, текущий каталог не важен)
|
||||
### Full Build (Linux)
|
||||
```bash
|
||||
./configure # Configure build
|
||||
make # Build everything
|
||||
make install # Install (requires sudo)
|
||||
./build.sh --full -j4 # autoreconf + configure + make
|
||||
```
|
||||
|
||||
### Partial Builds
|
||||
win: powershell -Command ".\build.bat" 2>&1 (аналог make если не изменен makefile.am)
|
||||
логи сборки win: build_win.log
|
||||
### Full Build (Windows/MSYS2)
|
||||
```powershell
|
||||
powershell build_full.bat # запускает bash build.sh --full через MSYS2 UCRT64
|
||||
```
|
||||
|
||||
### Incremental Build
|
||||
```bash
|
||||
cd lib && make # Build only the library
|
||||
cd src && make # Build only the main program
|
||||
cd tests && make # Build only tests
|
||||
./build.sh -j4 # make с авто-конфигурацией если надо
|
||||
```
|
||||
```powershell
|
||||
powershell -Command ".\build.bat" 2>&1 # Windows, логи: build_win.log
|
||||
```
|
||||
|
||||
### Clean Build
|
||||
```bash
|
||||
make clean # Clean object files
|
||||
make distclean # Clean everything including configure files
|
||||
make clean # Clean object files
|
||||
make distclean # Clean everything including configure files
|
||||
./build.sh --clean -j4 # Clean then rebuild
|
||||
```
|
||||
|
||||
## 🧪 Test Commands
|
||||
### Partial Builds
|
||||
```bash
|
||||
cd lib && make # Build only the library (libuasync.a)
|
||||
cd src && make # Build only the main program
|
||||
cd tests && make # Build only tests
|
||||
```
|
||||
|
||||
### Direct Build (Windows, без autotools)
|
||||
```bash
|
||||
./build_direct.sh # Компиляция вручную с x86_64-w64-mingw32-gcc
|
||||
```
|
||||
|
||||
## Test Commands
|
||||
|
||||
### Run All Tests
|
||||
win: powershell check.bat
|
||||
```bash
|
||||
make check # Run all tests via automake
|
||||
make check # Run all tests via automake, логи в tests/logs/
|
||||
```
|
||||
```powershell
|
||||
powershell check.bat # Windows, запускает каждый .exe из tests/
|
||||
```
|
||||
|
||||
### Run Specific Test
|
||||
```bash
|
||||
cd tests/ && ./test_etcp_crypto # ETCP crypto test
|
||||
cd tests/ && ./test_etcp_simple # Simple ETCP test
|
||||
cd tests/ && ./test_ecc_encrypt # ECC encryption test
|
||||
cd tests/
|
||||
./test_etcp_crypto
|
||||
./test_etcp_two_instances
|
||||
./test_etcp_simple_traffic
|
||||
./test_pkt_normalizer_etcp
|
||||
./test_etcp_api
|
||||
./test_ll_queue
|
||||
./test_nat_detection
|
||||
./test_bgp_route_exchange
|
||||
```
|
||||
|
||||
### Run Single Test with Debug Info
|
||||
@@ -65,7 +87,7 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
|
||||
./my_test
|
||||
```
|
||||
|
||||
## 💻 Code Style Guidelines
|
||||
## Code Style Guidelines
|
||||
|
||||
### Naming Conventions
|
||||
- **Functions:** `snake_case` - `etcp_connection_create()`, `sc_encrypt()`
|
||||
@@ -85,7 +107,7 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
|
||||
}
|
||||
```
|
||||
- **Comments:** Primary language is Russian for business logic, English for API docs
|
||||
- **Line Length:** Aim for 80-100 characters
|
||||
- **Line Length:** Aim for 80-100 characters, but can go up to 150 if logically coherent
|
||||
|
||||
### Include Order
|
||||
```c
|
||||
@@ -108,195 +130,374 @@ gcc -I../src -I../lib -I../tinycrypt/lib/include \
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to initialize: %s", err);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Socket created on port %d", port);
|
||||
```
|
||||
- Во всех блоках обработки ошибок/нештатных ситуаций должны быть сообщения DEBUG_ERROR/DEBUG_WARN
|
||||
|
||||
## 🔐 Cryptography Guidelines
|
||||
### Memory Management
|
||||
- Use `u_malloc`/`u_calloc`/`u_realloc`/`u_free`/`u_strdup` from `lib/mem.h` (wrappers with leak tracking)
|
||||
- Memory pools: `memory_pool_alloc()` / `memory_pool_free()` for hot-path allocations
|
||||
- Queue entries: `queue_entry_new_from_pool()` / `queue_entry_free()` / `queue_dgram_free()`
|
||||
|
||||
## Cryptography Guidelines
|
||||
|
||||
### Key Sizes
|
||||
| Constant | Value | Description |
|
||||
|----------|-------|-------------|
|
||||
| `SC_PRIVKEY_SIZE` | 32 | X25519 private key |
|
||||
| `SC_PUBKEY_SIZE` | 32 | X25519 public key |
|
||||
| `SC_NONCE_SIZE` | 13 | CCM nonce (exactly 13 bytes) |
|
||||
| `SC_SESSION_KEY_SIZE` | 16 | AES-128 session key |
|
||||
| `SC_TAG_SIZE` | 16 | CCM auth tag |
|
||||
| `SC_CRC32_SIZE` | 4 | CRC32 checksum |
|
||||
| `SC_PUBKEY_ENC_SALT_SIZE` | 8 | Salt for pubkey obfuscation |
|
||||
| `SC_PUBKEY_ENC_SIZE` | 40 | Total pubkey+salt block sent unencrypted |
|
||||
|
||||
### Using Secure Channel (secure_channel.h)
|
||||
```c
|
||||
// 1. Initialize context
|
||||
struct SC_MYKEYS my_keys;
|
||||
// ... fill keys ...
|
||||
sc_init_local_keys(&my_keys, public_key_hex, private_key_hex);
|
||||
// or sc_generate_keypair(&my_keys);
|
||||
|
||||
sc_context_t ctx;
|
||||
sc_init_ctx(&ctx, &my_keys);
|
||||
|
||||
// 2. Set peer public key (for key exchange)
|
||||
sc_set_peer_public_key(&ctx, peer_public_key, 0); // 0 = binary format
|
||||
sc_set_peer_public_key(&ctx, peer_public_key, SC_PEER_PUBKEY_HEX); // 0=bin, 1=hex
|
||||
|
||||
// 3. Ready for encrypt/decrypt
|
||||
sc_encrypt(&ctx, plaintext, plaintext_len, ciphertext, &ciphertext_len);
|
||||
sc_decrypt(&ctx, ciphertext, ciphertext_len, plaintext, &plaintext_len);
|
||||
|
||||
// 4. Pubkey obfuscation (used in INIT/PING packets):
|
||||
// salt(8) + XOR(SHA256(salt||peer_pubkey) || SHA256(peer_pubkey||salt), my_pubkey)
|
||||
sc_obfuscate_pubkey(salt, peer_pubkey_bin, my_pubkey_bin, obfuscated_output);
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
- **Nonce size:** Must be exactly 13 bytes for CCM mode (`SC_NONCE_SIZE = 13`)
|
||||
- **Session key:** 16 bytes for AES-128 (`SC_SESSION_KEY_SIZE = 16`)
|
||||
- **Tag size:** 8 bytes for authentication (`SC_TAG_SIZE = 8`)
|
||||
- **Error codes:** Check return values, negative = error
|
||||
- **Encryption:** 3-byte header (timestamp uint16_t + flag_up uint8_t) + data_len bytes encrypted
|
||||
- **INIT packets:** Header + data encrypted, pubkey+salt block (SC_PUBKEY_ENC_SIZE bytes) appended unencrypted
|
||||
- **Nonce:** Must be exactly 13 bytes for CCM mode
|
||||
- **Error codes:** Check return values, negative = error (SC_OK=0, SC_ERR_* < 0)
|
||||
|
||||
## 🏗️ Architecture
|
||||
## Debug System
|
||||
|
||||
### Debug Levels (по возрастанию)
|
||||
`none < error < warn < info < debug < trace`
|
||||
|
||||
### Debug Categories (21 категория)
|
||||
```
|
||||
NONE=0, UASYNC=1, LL_QUEUE=2, CONNECTION=3, ETCP=4, CRYPTO=5, MEMORY=6,
|
||||
TIMING=7, CONFIG=8, TUN=9, ROUTING=10, TIMERS=11, NORMALIZER=12, BGP=13,
|
||||
SOCKET=14, CONTROL=15, DUMP=16, TRAFFIC=17, DEBUG=18, GENERAL=19, NAT=20
|
||||
```
|
||||
|
||||
### Настройка отладки
|
||||
- В конфиге: `debug = etcp=trace,config=info` (формат: `категория=уровень,...`)
|
||||
- В коде: глобальный уровень и per-category уровни из `debug_config_t g_debug_config`
|
||||
- Макросы: `DEBUG_ERROR(cat,fmt,...)` `DEBUG_WARN` `DEBUG_INFO` `DEBUG_DEBUG` `DEBUG_TRACE`
|
||||
- `log_dump(prefix, data, len)` — hex dump в лог
|
||||
|
||||
### Dual Output
|
||||
- Консоль и файл настраиваются раздельно (`debug_set_console_level`, `debug_set_file_level`)
|
||||
- `debug_enable_file_output(path, truncate)` / `debug_disable_file_output()`
|
||||
|
||||
## Architecture
|
||||
|
||||
### Directory Structure
|
||||
```
|
||||
├── lib/ # Core libraries
|
||||
├── src/ # Main source code
|
||||
├── tests/ # Test programs
|
||||
├── lib/ # Core libraries (13 .c + 14 .h)
|
||||
├── src/ # Main source code (28 .c + 23 .h)
|
||||
├── tests/ # 31+ test programs
|
||||
├── doc/ # Technical Specifications
|
||||
├── tools/ # Auxiliary tools
|
||||
│ ├── etcpmon/ # GUI монитор ETCP
|
||||
│ ├── chatgui/ # GUI чат (Qt 5, отдельная сборка через CMake)
|
||||
│ ├── tdesktop-dev/ # Референс: Telegram Desktop (только для изучения)
|
||||
│ ├── proxy/ # UDP прокси для тестов
|
||||
│ └── bping/ # BPing (bandwidth ping)
|
||||
├── tinycrypt/ # TinyCrypt crypto library (external)
|
||||
└── net_emulator/ # Network emulator
|
||||
├── net_emulator/ # Network emulator (delays, loss, reordering)
|
||||
└── c2/ # Test instance 2 (конфиг и бинарник для тестов)
|
||||
```
|
||||
|
||||
### File Overview
|
||||
|
||||
**Core (src/)**
|
||||
- `utun.c` - Main program entry point, CLI parsing, daemon mode
|
||||
- `utun_instance.c/h` - Root instance lifecycle management
|
||||
- `tun_if.c/h` - Simplified TUN interface (init/write/close)
|
||||
- `utun_instance.c/h` - Root instance lifecycle, config loading, all submodule init
|
||||
- `tun_if.c/h` - TUN interface API (init/write/close, cross-platform)
|
||||
- `tun_linux.c` `tun_freebsd.c` `tun_windows.c` - Platform-specific TUN implementations
|
||||
- `tun_route.c/h` - TUN routing table sync
|
||||
|
||||
**Network Stack (src/)**
|
||||
- `etcp.c/h` - ETCP protocol implementation (TCP-like with crypto)
|
||||
- `etcp_connections.c/h` - Socket and link management
|
||||
- `etcp_loadbalancer.c/h` - Multi-link load balancing
|
||||
- `pkt_normalizer.c/h` - Packet fragmentation/reassembly
|
||||
- `routing.c/h` - Routing table management
|
||||
- `etcp.c/h` - ETCP protocol implementation (inflight queues, retrans, ACK, RTT/jitter)
|
||||
- `etcp_api.c/h` - ETCP public API (send/recv/bind callbacks)
|
||||
- `etcp_connections.c/h` - Socket and link management, INIT handshake, keepalive, PING/PONG
|
||||
- `etcp_loadbalancer.c/h` - Multi-link load balancing with traffic shaper
|
||||
- `etcp_debug.c/h` - ETCP packet dump/formatting
|
||||
- `pkt_normalizer.c/h` - Packet fragmentation/reassembly (packer/unpacker)
|
||||
- `packet_dump.c/h` - Packet hex dump utility
|
||||
- `firewall.c/h` - Firewall rules (per-interface filtering)
|
||||
- `dummynet.c/h` - Network emulator integrated into utun (for testing)
|
||||
|
||||
**Routing (src/)**
|
||||
- `routing.c/h` - Routing table management (local routes)
|
||||
- `route_lib.c/h` - Routing library utilities
|
||||
- `route_bgp.c/h` - BGP-style route exchange between peers
|
||||
- `route_ping.c/h` - Route ping probing (NAT check, liveness)
|
||||
- `route_node.c/h` - Route node (peer) management
|
||||
|
||||
**Crypto (src/)**
|
||||
- `secure_channel.c/h` - AES-CCM encryption with ECC key exchange
|
||||
- `secure_channel.c/h` - AES-CCM encryption with X25519 key exchange, pubkey obfuscation
|
||||
- `crc32.c/h` - CRC32 checksums
|
||||
|
||||
**NAT (src/)**
|
||||
- `eim_nat.c/h` - Endpoint-Independent Mapping NAT engine
|
||||
- `nat_transport.c/h` - NAT transport layer (packet relay)
|
||||
|
||||
**Config (src/)**
|
||||
- `config_parser.c/h` - INI-style config file parsing
|
||||
- `config_updater.c/h` - Config file modification utilities
|
||||
|
||||
**Control (src/)**
|
||||
- `control_server.c/h` - Control/monitoring server (etcpmon backend API)
|
||||
|
||||
**Libraries (lib/)**
|
||||
- `u_async.c/h` - Async event loop (epoll/poll, timers)
|
||||
- `ll_queue.c/h` - Lock-free linked list queue with callbacks
|
||||
- `memory_pool.c/h` - Fast object pool allocator
|
||||
- `debug_config.c/h` - Debug logging system
|
||||
- `timeout_heap.c/h` - Min-heap for timer management
|
||||
- `sha256.c/h` - SHA256 hashing
|
||||
- `u_async.c/h` - Async event loop (epoll/poll/select, timers via timeout_heap)
|
||||
- `ll_queue.c/h` - Lock-free linked list queue with callbacks, hash index, threshold waiter
|
||||
- `memory_pool.c/h` - Fast object pool allocator (pre-allocated blocks)
|
||||
- `debug_config.c/h` - Debug logging system (levels, categories, dual output, runtime config)
|
||||
- `timeout_heap.c/h` - Min-heap for timer management (used by u_async)
|
||||
- `sha256.c/h` - SHA256 hashing (fallback; OpenSSL preferred via USE_OPENSSL)
|
||||
- `mem.c/h` - Memory wrappers with leak tracking (`u_malloc`/`u_free`/`u_calloc`/`u_strdup`)
|
||||
- `serialize.c/h` - Binary serialization utilities
|
||||
- `swm_min.c/h` - Sliding window minimum (for RTT min tracking)
|
||||
- `getmyip.c/h` - Get local IP / default route detection
|
||||
- `myip.c` - My IP helper (no header)
|
||||
- `socket_compat.c/h` - Cross-platform socket compatibility (Linux/FreeBSD/Windows)
|
||||
- `platform_compat.c/h` - Cross-platform compatibility layer (byte order, time, random)
|
||||
|
||||
**Tests (tests/)**
|
||||
- `test_etcp_*.c` - ETCP protocol tests
|
||||
- `test_crypto.c` - TinyCrypt AES/CCM tests
|
||||
- `test_ecc_encrypt.c` - ECC encryption tests
|
||||
- `test_ll_queue.c` - Queue functionality tests
|
||||
- `bench_*.c` - Performance benchmarks
|
||||
|
||||
**External**
|
||||
- `tinycrypt/` - TinyCrypt library (AES, ECC, SHA256)
|
||||
**Memory Pools in UTUN_INSTANCE:**
|
||||
- `data_pool` — для данных пакетов (payload), используется input_queue/output_queue фрагментами
|
||||
- `pkt_pool` — для struct ETCP_DGRAM (сетевые пакеты на отправку/приём)
|
||||
- `ack_pool` — для struct ACK_PACKET (подтверждения приёма)
|
||||
|
||||
### Key Components
|
||||
- **UASYNC:** Asynchronous event loop for sockets and timers
|
||||
- **LL_QUEUE:** Lock-free lockstep queue for packet handling
|
||||
- **Memory Pool:** Fast allocation for network packets
|
||||
- **ETCP:** Extended TCP protocol with crypto and reliability
|
||||
- **Secure Channel:** AES-CCM encryption with ECC key exchange
|
||||
- **UASYNC:** One per thread. Async event loop (epoll/poll), timers via timeout_heap
|
||||
- **LL_QUEUE:** Lock-free queue with auto-callback, hash index lookup, threshold waiter
|
||||
- **Memory Pool:** Fast allocation for hot-path objects (packets, inflight entries, fragments)
|
||||
- **ETCP:** TCP-like reliable protocol with encryption, multi-link, load balancing
|
||||
- **Secure Channel:** AES-CCM + X25519 key exchange, nonce-based encryption, pubkey obfuscation
|
||||
|
||||
## 🐛 Debugging Tips
|
||||
## Queue Usage Rules
|
||||
|
||||
### Enable Debug Output
|
||||
```c
|
||||
// In source files, define debug before including headers
|
||||
#define DEBUG_CATEGORY_YOUR_MODULE 1
|
||||
## Работа с очередями. Важно!
|
||||
С очередью следуею работать придерживаясь следующих правил:
|
||||
- при сетевом обмене не забиваем очередь. всегда используем Пороговое ожидание (backpressure).
|
||||
- для работы с очередью полностью загрузи ll_queue.h и пойми как работает.
|
||||
|
||||
### Запись в очередь
|
||||
- Очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой.
|
||||
- Используй `queue_wait_threshold` для ожидания освобождения очереди до заданного порога.
|
||||
|
||||
### Чтение из очереди
|
||||
- Используй `queue_set_callback`: при вызове callback обработай один или несколько элементов, потом вызови `queue_resume_callback`.
|
||||
- Внутри коллбэка ОБЯЗАТЕЛЬНО: `queue_data_get(q)` → обработать элемент → `queue_resume_callback(q)`
|
||||
- Без вызова `queue_resume_callback` очередь навсегда застрянет
|
||||
|
||||
### Поиск
|
||||
- `queue_data_put_with_index(q, entry)` — добавляет с индексом для быстрого поиска (offset/size заданы при queue_new)
|
||||
- `queue_find_data_by_index(q, key)` — поиск по индексу через хеш-таблицу (размер ключа из q->index_size)
|
||||
|
||||
## u_async Rules
|
||||
- Нельзя использовать в одном потоке несколько u_async. Один поток = всегда 1 uasync instance
|
||||
- Нельзя использовать sleep/usleep если есть uasync. Нужно использовать `uasync_set_timeout`.
|
||||
- Таймеры: `uasync_set_timeout(ua, timeout_tb, arg, callback, name)` — timebase units (0.1ms)
|
||||
Возвращает `void*` handle, отмена: `uasync_cancel_timeout(ua, handle)`.
|
||||
|
||||
## Config Rules
|
||||
- В серверном конфиге только собственные ключи и нет секций `[client]`
|
||||
- В клиентском конфиге есть собственные ключи и pubkey каждого сервера в секции `[client]`
|
||||
|
||||
## Bugs Debugging Protocol
|
||||
Всегда когда начинаешь диагностику ознакомься со скиллом (используй skills)
|
||||
Диагностика и поиск ошибок - это в первую очередь продумывание отладочных механизмов которые покажут понятную картину и точное представление об ошибке. Рассуждать надо как диагностикой добиться точной картины. И как приавльнее добавить диагностику чтобы не спамила лишними сообщениями и была понятной, логичной и информативной.
|
||||
|
||||
### Прочие правила:
|
||||
- sed для редактирования исходников - запрещено
|
||||
- Проверяй на дублирование кода - не сделано ли это уже в другом месте
|
||||
- Не делай функций-посредников: лучше сразу вызывать target функцию без вложенных вызовов
|
||||
- Нельзя ничего восстанавливать из репозитория не спрашивая
|
||||
- Для отладки не printf а DEBUG_*
|
||||
- Перед сборкой всегда make clean
|
||||
- Все лишнее что менял при отладке - строго вернуть назад в состояние до вмешательства
|
||||
|
||||
## Написание кода
|
||||
- Строго обязательно наличие ERROR сообщений во всех ошибочных ветвях алгоритма
|
||||
- Обязательно наличие диагностических сообщений в ветках инициализации-очистки, с информацией которая позволяет оценить наиболее полно внетренние состояния на момент печати сообщения. Например выявить неправильные состояния, неинициализированные поля итд
|
||||
- Обязательно наличие подробной диагностики в функциях кода которые не сильно спамят (не часто вызываются).
|
||||
- Для каждого отладочного вывода подумай какая из доступной информация будет полезна чтобы можно было наиболее завершенно оценить состояние алгоритма и состояний влияющих на алгоритм.
|
||||
|
||||
|
||||
## chatgui (GUI Chat Client)
|
||||
|
||||
Chatgui — десктопный GUI-чат на Qt 5, отдельный проект внутри репозитория. Не связан с autotools-сборкой utun.
|
||||
|
||||
### Технологии
|
||||
- **Язык:** C++17
|
||||
- **Фреймворк:** Qt 5.15+ (Widgets + Network)
|
||||
- **Сборка:** CMake 3.16+
|
||||
- **БД:** SQLite3 (WAL mode, встроенный `db/sqlite3.c` amalgamation)
|
||||
- **Анимации:** rlottie (C API) + zlib (gzip-декомпрессия TGS)
|
||||
|
||||
### Сборка
|
||||
```bash
|
||||
# Установка зависимостей
|
||||
sudo apt install librlottie-dev zlib1g-dev qtbase5-dev
|
||||
|
||||
# Сборка
|
||||
cd tools/chatgui && mkdir -p build && cd build
|
||||
cmake .. && make -j4
|
||||
./chatgui
|
||||
```
|
||||
|
||||
### Common Build Issues
|
||||
1. **Missing headers:** Check include paths in Makefile.am
|
||||
2. **Undefined references:** Ensure source files are listed in Makefile.am
|
||||
3. **Link errors:** Check function declarations match definitions
|
||||
### Структура файлов (src/)
|
||||
|
||||
## 📦 Dependencies
|
||||
| Файл | Назначение |
|
||||
|------|-----------|
|
||||
| `mainwindow.h/cpp` | Главное окно, QSplitter (ChannelList \| MessageList \| AccountList), трей |
|
||||
| `chatview.h/cpp` | QListView с фоновым изображением, hover-сигнал, drag-to-select |
|
||||
| `messagelist.h/cpp` | QStandardItemModel, stub-данные, инициализация анимаций, hover→activate |
|
||||
| `messagedelegate.h/cpp` | QStyledItemDelegate: бабблы, текст, цитаты, реакции, оверлей анимаций |
|
||||
| `emoji.h/cpp` | EmojiData/EmojiCategory/EmojiType, 4 категории, `g_animatedEmojiMap` |
|
||||
| `emojipanel.h/cpp` | Эмодзи-пикер: QTabWidget + QGridLayout, EmojiButton с hover-анимацией |
|
||||
| `emojitabbar.h/cpp` | Кастомный QTabBar для эмодзи-панели |
|
||||
| `inputbar.h/cpp` | Поле ввода (QTextEdit), вставка эмодзи, отправка по Enter |
|
||||
| `lottieicon.h/cpp` | Загрузка TGS (gzip→zlib), рендер кадров через `lottie_animation_render` |
|
||||
| `animtimer.h/cpp` | Синглтон-таймер 30fps, refcount activate/deactivate, сигнал ticked |
|
||||
| `channellist.h/cpp` `channeldelegate.h/cpp` | Список каналов с аватарами и превью |
|
||||
| `accountlist.h/cpp` | Список аккаунтов/пользователей |
|
||||
|
||||
### Build Dependencies
|
||||
- autoconf, automake, libtool
|
||||
- gcc with C99 support
|
||||
- pthread library
|
||||
- OpenSSL/crypto library (for SHA256)
|
||||
### Другие поддиректории
|
||||
- `db/` — SQLite3 amalgamation (`sqlite3.c/h`), `db_manager.h/cpp` (схема: nodes, channels, messages, keys, addresses)
|
||||
- `transport/` — TCP-клиент для связи с uTun (`msg_client.h/cpp`, бинарный протокол `[size:2][type:1][id:8][options:1][data...]`)
|
||||
- `resources/` — `bg.jpg`, `chatgui.qrc` (встраивает 5 TGS в бинарник), `animations/*.tgs`
|
||||
|
||||
### Runtime Dependencies
|
||||
- TUN/TAP interface support (Linux kernel module)
|
||||
- libcrypto (usually installed by default)
|
||||
- Proper network configuration for testing
|
||||
### Система анимированных эмодзи
|
||||
|
||||
## 📝 Git Conventions
|
||||
#### Формат
|
||||
TGS = gzip-сжатый Lottie JSON. Холст 512×512, 60fps, 90–180 кадров. Декомпрессия через zlib (`inflateInit2` с `16+MAX_WBITS`).
|
||||
|
||||
#### Ключевые компоненты
|
||||
```
|
||||
LottieIcon — загрузка TGS, рендер кадров (rlottie C API)
|
||||
↓
|
||||
AnimTimer (синглтон) — таймер 33ms, refcount activate/deactivate
|
||||
├── в сообщениях: → MessageDelegate::paint()
|
||||
│ drawAnimatedEmojiOverlay() — QTextLayout → позиции эмодзи → drawImage()
|
||||
│ drawStatusBar() — анимированные реакции вместо drawText()
|
||||
│
|
||||
├── в панели: → EmojiButton (наследует QPushButton)
|
||||
│ per-button QTimer, enterEvent/leaveEvent, собственный paintEvent
|
||||
│
|
||||
└── активация: → ChatView::hoveredIndexChanged
|
||||
MessageList проверяет текст/реакции сообщения на g_animatedEmojiMap
|
||||
есть анимация → AnimTimer::activate() / нет → deactivate()
|
||||
```
|
||||
|
||||
#### Инициализация
|
||||
1. `MessageList` конструктор: загружает все `EmojiType::Animation` из `builtinEmojiSet()`, создаёт `LottieIcon`, регистрирует в `AnimTimer`
|
||||
2. **Должно быть ДО создания `EmojiPanel`** — иначе `EmojiButton` получит `nullptr`
|
||||
3. `g_animatedEmojiMap` заполняется при старте (Unicode → animPath)
|
||||
|
||||
#### Текущий маппинг (5 эмодзи)
|
||||
| Unicode | Shortcode | TGS-файл |
|
||||
|---------|-----------|----------|
|
||||
| ✨ `U+2728` | `:sparkles:` | `sparkles_emoji.tgs` |
|
||||
| 🏳️ `U+1F3F3` | `:flag:` | `white_flag_emoji.tgs` |
|
||||
| 👋 `U+1F44B` | `:wave:` | `greeting.tgs` |
|
||||
| ✋ `U+270B` | `:raised_hand:` | `stop.tgs` |
|
||||
| 🤖 `U+1F916` | `:robot:` | `robot.tgs` |
|
||||
|
||||
#### Рендеринг в сообщениях
|
||||
- Текст рисуется через `QPainter::drawText(Qt::TextWordWrap)`
|
||||
- `drawAnimatedEmojiOverlay()` повторяет layout через `QTextLayout` с теми же параметрами
|
||||
- Для каждого эмодзи из `g_animatedEmojiMap` находит пиксельную позицию через `QTextLine::cursorToX()`
|
||||
- Рендерит кадр через `LottieIcon::renderFrame(currentFrame(), size)` и рисует `drawImage()`
|
||||
- Размер эмодзи масштабирован ×1.3 относительно высоты строки
|
||||
- Аналогично для реакций в `drawStatusBar()`
|
||||
|
||||
#### Drag-to-select
|
||||
- Одиночный клик снимает выделение (если было), не стартует новое
|
||||
- Движение ≥5px от точки нажатия стартует выделение (`m_selStart`/`m_selEnd`)
|
||||
- Ctrl+C копирует: для одного сообщения — выделенный фрагмент, для нескольких — `[time] author: text`
|
||||
|
||||
### Важные правила chatgui
|
||||
- `CMAKE_AUTORCC ON` обязателен для компиляции `.qrc`-ресурсов
|
||||
- Иконки должны быть зарегистрированы в `AnimTimer` ДО создания `EmojiPanel`
|
||||
- `EmojiButton` НЕ использует `Q_OBJECT` (в анонимном namespace) — таймер через лямбду, каст через `dynamic_cast`
|
||||
- Координаты `indexAt()`: `viewport()->mapFrom(this, event->pos())`
|
||||
|
||||
---
|
||||
|
||||
## tdesktop-dev (референсный проект Telegram Desktop)
|
||||
|
||||
**Расположение:** `tools/tdesktop-dev/` — полный клон [Telegram Desktop](https://github.com/telegramdesktop/tdesktop).
|
||||
|
||||
**Статус:** референс. Не собирается, не модифицируется, в `.gitignore`.
|
||||
|
||||
**Назначение:**
|
||||
- Источник TGS-анимаций (`Telegram/Resources/animations/` — 99+ файлов)
|
||||
- Образец использования rlottie/lib_lottie API
|
||||
- Архитектурный референс для GUI-компонентов
|
||||
|
||||
**Правила:**
|
||||
- НЕ модифицировать файлы внутри `tdesktop-dev/`
|
||||
- НЕ коммитить изменения из `tdesktop-dev/`
|
||||
- НЕ ссылаться в сборке на код/библиотеки из tdesktop-dev
|
||||
- Можно копировать TGS-файлы в `tools/chatgui/resources/animations/`
|
||||
|
||||
**Полезные поддиректории:**
|
||||
- `Telegram/Resources/animations/` — TGS-анимации (dice, reactions, emoji, UI)
|
||||
- `Telegram/lib_lottie/` — C++ обёртка над rlottie (SinglePlayer, MultiPlayer, Icon, FrameGenerator)
|
||||
- `Telegram/ThirdParty/rlottie/` — submodule rlottie (обычно не выкачан)
|
||||
|
||||
**rlottie C API (ключевые функции):**
|
||||
```c
|
||||
Lottie_Animation *lottie_animation_from_data(const char *data, const char *key, const char *resource_path);
|
||||
void lottie_animation_get_size(const Lottie_Animation *anim, size_t *w, size_t *h);
|
||||
size_t lottie_animation_get_totalframe(const Lottie_Animation *anim);
|
||||
double lottie_animation_get_framerate(const Lottie_Animation *anim);
|
||||
void lottie_animation_render(Lottie_Animation *anim, size_t frame, uint32_t *buf, size_t w, size_t h, size_t stride);
|
||||
void lottie_animation_destroy(Lottie_Animation *anim);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Documentation Files
|
||||
- `/doc/etcp_protocol.txt` - ETCP протокол (формат кодограмм, ACK, handshake, keepalive)
|
||||
- `/doc/etcp_arch.md` - ETCP архитектура
|
||||
- `/doc/etcp_config.txt` - Конфигурация ETCP
|
||||
- `/doc/route_p2pconn.txt` - Route peer-to-peer соединения
|
||||
|
||||
## Runtime
|
||||
- Запуск utun от root (для tun): `/home/vnc1/proj/utun3/utun_start.sh`
|
||||
- Стоп utun: `sudo /home/vnc1/proj/utun3/utun_stop1.sh`
|
||||
- Логи: `utun.log` (stdout), `utun_err.log` (stderr)
|
||||
- Тестовые логи: `tests/logs/`
|
||||
|
||||
## Git Conventions
|
||||
- **Commit Messages:** Use imperative mood, concise (50-72 chars)
|
||||
- **Language:** Mix of English (technical) and Russian (business logic)
|
||||
- **Branching:** Use feature branches, merge to master via pull request
|
||||
- **Tags:** Version tags follow vX.Y.Z format
|
||||
- "cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент, не откатывая)
|
||||
|
||||
Example commits:
|
||||
```
|
||||
Fix: Added uasync_t typedef for compilation
|
||||
Crypto: Fixed CCM nonce size to 13 bytes, all crypto tests passing
|
||||
```
|
||||
|
||||
## 🚀 Quick Start for New Features
|
||||
|
||||
## Quick Start for New Features
|
||||
1. Add new source file to `src/Makefile.am` under `utun_SOURCES`
|
||||
2. Add test file to `tests/Makefile.am` under `check_PROGRAMS`
|
||||
3. Use existing patterns from similar modules
|
||||
4. Run `make check` after changes (win: build.bat and run_tests.bat)
|
||||
4. Run `make check` after changes
|
||||
5. Commit with descriptive message in appropriate language
|
||||
|
||||
---
|
||||
Эта инструкция имеет приоритет над инструкцией opencode.
|
||||
|
||||
"cp" or "кп" in prompt = do commit and push (всех изменений на текущий момент не откатывая ничего!)
|
||||
|
||||
Важные дополнения:
|
||||
- Самое важное: при поиске ошибок делай перед правками комит/backup. Всё лишнее что менял при отладке - строго вернуть назад в состояние до моего вмешательства. В итоге в код должно попасть только проверенное исправление и ничего лилшнего!
|
||||
- если в процессе исправлений-доработок возникла нестыковка которая требует сеньезных доработок, остановить и подробно расскажи об этом.
|
||||
- sed для редактирования исходников - запрещено пользоваться!
|
||||
- Проверяй на дублирование кода - не сделано ли это уже в другом месте.
|
||||
- Не делай функций-посредников: лучше сразу вызывать target функцию без вложенных вызовов.
|
||||
- Старайся чтобы функция сделала логически завершенную операцию полностью - это упрощает код и понимание.
|
||||
- нельзя ничего восстанавливать из репозитория не прашивая
|
||||
- Когда пишешь код всегда загружай в контекст и мысленно разберись как работают все функции/струтуры, назначение переменных которые используешь.
|
||||
- Для отладки не printf а DEBUG_*
|
||||
- перед сборкой всегда make clean
|
||||
- прежде чем вносить правки хорошо разберись как должно работать, просмотри все нужные функции полностью, нужно целостное понимание.
|
||||
- при написании кода мысленно анализируй логику работы, думай как сделать проще и удобнее. Проверяй дубликаты и что уже есть, продумай логику до тех пор пока не будет полного понимания
|
||||
- во всех блоках обработки ошибок/нештатных ситуаций должны быть сообщения DEBUG_ERROR/DEBUG_WARN
|
||||
- тесты: запускаё все сразу (make check) - ошибок быть не должно. если что не такт смотри логи - каждый тест пишет лог.
|
||||
|
||||
Действия при поиске бага:
|
||||
1. создать комит или бэкап всего что меняешь
|
||||
2. когда причина бага найдена и устранена - верни всё остальное что менял в исходное состояние
|
||||
3. проверь что тесты проходят и всё работает. make clean перед сбркой обязательно.
|
||||
4. если баг не устранён - продолжай поиск или если время заканчивается - верни всё в исходное состояние
|
||||
|
||||
Как более точно эффективно проводить диагностику ошибки:
|
||||
0. Сосредоточься на поиске конкретной ошибки и доведи его до конца руководствуясь этой инструкцией.
|
||||
1. прочитай полностью код функций с ошибкой и код всех функции которые учавствуют в ошибочном алгоритме.
|
||||
2. еще раз мысленно выполни предполагаемый сценарий ошибки (если чего-то не хватает до полной картины - обязательно дочитывай все ветви функций по ходу: нельзя додумывать - нужна точность) и убедись что:
|
||||
- ты точно понимаешь как алгоритм прихдит к ошибке
|
||||
- все функции которые учавствуют в сценарии ошибки проанализированы и их поведение проверено и понятно
|
||||
- последовательно отсекай логически законченные блоки которые проверены и точно правильно работают
|
||||
- если ты полностью проанализировал функцию и к ней нет описания или описание неточное (неполное), и функция работает логически корректно - поправь описание. Если функция работает логически неверно - добавь запись об этом в todo.txt
|
||||
3. Если исправление как либо меняет поведение функции:
|
||||
- просмотри где используется эта функция
|
||||
- убедись что изменение поведения не повлияет на остальные места где функция используется. особенно важный пункт для библиотек и модулей коммуникаций
|
||||
|
||||
Работа с очередями.
|
||||
- Запись в очередь: очереди забивать нельзя. Добавляй следующий элемент только когда очередь стала пустой. Пользуйся наблюдателем queue_wait_threshold, он специально сделан для добавления в очередь.
|
||||
- Чтение из очереди: пользуйся queue_set_callback: при вызове callback , обработай один или несколько элементов, потом вызови resume_callback.
|
||||
|
||||
Работа с u_async
|
||||
- нельзя использовать в одном потоке несколько u_async. один поток = всегда 1 uasync instance
|
||||
- нельзя использовать sleep/usleep если есть uasync. Нужно использовать uasync_set_timeout.
|
||||
|
||||
Конфиги:
|
||||
- в серверном только собственные ключи и нет секций [client]
|
||||
- в клиентском есть собственные ключи и pubkey каждого сервера в секции [client]
|
||||
|
||||
тех задания для реализации в каталоге /doc.
|
||||
/doc/etcp_protocol.txt - основной протокол (похож на TCP+QUIC, поддеиживает шифрования, load balancing multi-link, работу а неустойчивых каналах, утилизацию полосы и недопущение перегрузки каналов связи)
|
||||
- реализация в /src/etcp*.c/h
|
||||
|
||||
запуск utun от root (для tun) - /home/vnc1/proj/utun3/utun_start.sh
|
||||
стоп utun: sudo /home/vnc1/proj/utun3/utun_stop1.sh
|
||||
логи - utun.log
|
||||
|
||||
|
||||
*Last updated: 2026-02-13 - c_util fully functional with toc/description/show/edit commands*
|
||||
@@ -0,0 +1,253 @@
|
||||
========================================================================
|
||||
uTun3 AUDIT FINDINGS — Сводный отчёт
|
||||
========================================================================
|
||||
|
||||
Аудит от 14.05.2026 — обход основных модулей
|
||||
(use-after-free, out-of-bounds, утечки, reentrancy)
|
||||
|
||||
========================================================================
|
||||
HIGH (19 проблем)
|
||||
========================================================================
|
||||
|
||||
1. u_async.c:137-165 use-after-free
|
||||
Частичный успех u_realloc в socket_array_add_internal: если часть
|
||||
реаллоков прошла, а часть нет — старые массивы уже освобождены,
|
||||
новые не выделены, sa->sockets/sa->fd_to_index/sa->index_to_fd
|
||||
указывают на освобождённую память.
|
||||
|
||||
2. u_async.c:842,1167 логическая ошибка
|
||||
process_posted_tasks никогда не вызывается на Linux.
|
||||
В epoll-пути: drain_wakeup_pipe не вызывает handle_wakeup/process_posted_tasks.
|
||||
В poll-пути: wakeup-слот (i==0) обрабатывается с drain_wakeup_pipe и continue,
|
||||
минуя зарегистрированный коллбэк.
|
||||
Следствие: uasync_post из другого потока не доставляет задачи — posted_tasks
|
||||
накапливаются бесконечно.
|
||||
|
||||
3. u_async.c:1383,1398,1418 утечка памяти
|
||||
При сбое создания wakeup (pipe/socket) в uasync_create не освобождаются:
|
||||
ua->sockets, ua->timeout_heap, ua->timeout_pool, ua->epoll_fd.
|
||||
|
||||
4. memory_pool.c:51-69 double-free
|
||||
Нет детекта double-free. Повторный free создаёт цикл во freelist,
|
||||
memory_pool_alloc выдаёт один и тот же блок двум потребителям.
|
||||
|
||||
5. etcp.c:679-683 утечка памяти
|
||||
input_queue_cb: при исчерпании inflight_pool вызывается
|
||||
queue_entry_free(in_pkt) без queue_dgram_free(&in_pkt->ll).
|
||||
dgram из data_pool утекает.
|
||||
|
||||
6. etcp_connections.c:87,876-926 use-after-free
|
||||
burst_resp_timer не отменяется в etcp_link_close.
|
||||
Коллбэк burst_resp_timeout_cb срабатывает на освобождённом link.
|
||||
|
||||
7. etcp_connections.c:846-854 use-after-free
|
||||
stats_timer не отменяется при провале insert_link в etcp_link_new.
|
||||
link_stats_timer_cb срабатывает на освобождённом link.
|
||||
|
||||
8. etcp_connections.c:879-886 use-after-free
|
||||
Ветка conn==NULL в etcp_link_close пропускает отмену ВСЕХ таймеров
|
||||
(stats_timer, init_timer, shaper_timer, keepalive_timer, burst_resp_timer).
|
||||
Любой из них сработает на освобождённом link.
|
||||
|
||||
9. etcp_connections.c:1388,1531,1561 out-of-bounds read
|
||||
INIT request парсинг: проверка pkt_len < 12 должна быть pkt_len < 26
|
||||
(23 байта заголовка). Пакет с длиной 12-25 пройдёт проверку,
|
||||
но читает за границей расшифрованных данных.
|
||||
|
||||
10. pkt_normalizer.c:33 null deref
|
||||
if (!pn) pn->alloc_errors++ — разыменование NULL после u_calloc.
|
||||
|
||||
11. pkt_normalizer.c:300 null deref
|
||||
pn_buf_renew в цикле without проверки возврата → memcpy(NULL, ...)
|
||||
при сбое memory_pool_alloc.
|
||||
|
||||
12. eim_nat.c:80-93 логическая ошибка
|
||||
Переполнение uint16_t порта: next_port с 65535→0, затем порты
|
||||
ниже port_start выделяются (включая порт 0). Соединения нерабочие.
|
||||
|
||||
13. eim_nat.c (весь файл) логическая ошибка
|
||||
Нет механизма истечения/удаления NAT-записей. Таблица навсегда
|
||||
исчерпывается при долгой работе, eim_nat_egress начинает отказывать.
|
||||
|
||||
14. route_bgp.c:749,762-766 утечка памяти
|
||||
route_bgp_process_nodeinfo: при пересоздании nodeinfo1 старая
|
||||
очередь paths сохраняется в локальную переменную, но затем
|
||||
перезаписывается новой queue_new() — утечка.
|
||||
|
||||
15. route_bgp.c:904-907 утечка памяти
|
||||
route_bgp_send_nodeinfo: при провале etcp_send не освобождаются
|
||||
p (u_malloc) и e (queue_entry_new).
|
||||
|
||||
16. route_ping.c:126-134 утечка памяти
|
||||
route_ping_cancel_for_conn: nat_check_arg (p->arg, u_calloc) не
|
||||
освобождается. В нормальном и таймаутном путях коллбэк освобождает,
|
||||
но cancel-путь коллбэк не вызывает.
|
||||
|
||||
17. ll_queue.c:321,439 reentrancy
|
||||
Синхронный вызов коллбэка при первом queue_data_put (count==1)
|
||||
без отложенного возобновления через uasync → рекурсивный
|
||||
ack_timeout_check с устаревшим указателем current.
|
||||
|
||||
18. secure_channel.c:379,711 потенциальный OOB write
|
||||
sc_encrypt пишет в caller buffer без параметра capacity.
|
||||
Контракт API опасен: вызывающий должен сам гарантировать размер буфера.
|
||||
|
||||
19. secure_channel.c:432,756 потенциальный OOB write
|
||||
sc_decrypt — аналогично, нет проверки capacity выходного буфера.
|
||||
|
||||
|
||||
========================================================================
|
||||
MEDIUM (18 проблем)
|
||||
========================================================================
|
||||
|
||||
20. u_async.c:591-639 use-after-free
|
||||
Stale handle в uasync_cancel_timeout/uasync_call_soon_cancel.
|
||||
Нет инвалидации (generation counter), пул может переиспользовать память.
|
||||
|
||||
21. memory_pool.c:8,38,61,95 race condition
|
||||
Глобальная g_total_free изменяется без блокировок, общая для всех пулов.
|
||||
|
||||
22. memory_pool.c:51-69 потенциальный OOB write
|
||||
Нет проверки принадлежности блока пулу. Free в чужой пул вызывает
|
||||
memset с чужим object_size — частичная очистка или запись за границу.
|
||||
|
||||
23. timeout_heap.c:43-45 утечка / несоответствие документации
|
||||
Документация обещает free() при free_callback==NULL, код этого не делает.
|
||||
|
||||
24. etcp.c:718-763 reentrancy
|
||||
Вложенный ack_timeout_check (через input_send_q_cb→etcp_request_pkt→
|
||||
wait_ack_cb) обрабатывает не тот entry, т.к. outer loop использует
|
||||
current, который мог быть перемещён/удалён внутренним вызовом.
|
||||
|
||||
25. etcp.c:747,944 утечка / дрейф счётчиков
|
||||
Непроверенный queue_data_put_with_index: при провале счётчики
|
||||
(retransmissions_count, inflight_bytes) не откатываются.
|
||||
|
||||
26. etcp_connections.c:1731,1763 out-of-bounds read
|
||||
INIT_RESPONSE парсинг: нужно >=19 байт, проверяется только >=3.
|
||||
|
||||
27. etcp_connections.c:748-753 use-after-free + бесконечный цикл
|
||||
etcp_socket_remove: при link->conn==NULL remove_link пропускается,
|
||||
num_channels не уменьшается, цикл перечитывает освобождённый conn->links[0].
|
||||
|
||||
28. etcp_connections.c:1736-1738 логическая ошибка
|
||||
link_status перезаписывается значением remote_keepalive, игнорируя
|
||||
локальный recv_keepalive. Временно помечает link UP даже когда
|
||||
локальный приём мёртв.
|
||||
|
||||
29. pkt_normalizer.c:43 integer overflow/underflow
|
||||
frag_size = mtu - ACK_REZERV - UDP_HDR_SIZE - UDP_SC_HDR_SIZE.
|
||||
При mtu < 158 беззнаковое вычитание даёт ~65379 → огромные буферы.
|
||||
|
||||
30. pkt_normalizer.c:240 утечка памяти
|
||||
При провале queue_data_put фрагмент и dgram не освобождаются.
|
||||
|
||||
31. eim_nat.c:97,173 null deref
|
||||
Нет проверки ctx->initialized/ctx->table в egress/ingress.
|
||||
|
||||
32. eim_nat.c:268,293 логическая ошибка
|
||||
Статические пробросы (port forward) вне port range недоступны.
|
||||
|
||||
33. secure_channel.c:66 key hygiene
|
||||
Нет sc_free_ctx — сессионный ключ не зануляется при уничтожении контекста.
|
||||
|
||||
34. secure_channel.c:322,620 key hygiene
|
||||
ECDH shared secret остаётся на стеке незанулённым после
|
||||
sc_derive_session_key.
|
||||
|
||||
35. secure_channel.c:352,680 nonce entropy
|
||||
При постоянном провале random_bytes seed остаётся 0 (static init).
|
||||
Nonce становится полностью предсказуемым (counter+timestamp).
|
||||
|
||||
36. route_bgp.c:1122 утечка памяти
|
||||
route_bgp_send_nat_info: etcp_send без проверки → pkt/e утекают при провале.
|
||||
|
||||
37. route_bgp.c:1157 утечка памяти (аналогично #36)
|
||||
route_bgp_send_nat_check_req: etcp_send без проверки.
|
||||
|
||||
38. config_parser.c:1055-1066 логическая ошибка
|
||||
update_config_keys дописывает [global] секцию в конец файла.
|
||||
При повторном вызове получается дубликат секции, парсер может сбойнуть.
|
||||
|
||||
|
||||
========================================================================
|
||||
LOW (13 проблем)
|
||||
========================================================================
|
||||
|
||||
39. u_async.c:1594-1617 утечка (abort)
|
||||
abort() в destroy до освобождения posted_tasks и мьютекса.
|
||||
|
||||
40. u_async.c:1608 race condition
|
||||
posted_tasks_head читается без posted_lock в destroy.
|
||||
|
||||
41. memory_pool.c:47 truncation
|
||||
size_t → uint32_t в u_calloc_impl (object_size > UINT32_MAX).
|
||||
|
||||
42. memory_pool.h:16 signedness
|
||||
int free_count может обернуться при двойном освобождении.
|
||||
|
||||
43. timeout_heap.c:31 dead code
|
||||
freed_count никогда не обновляется, timeout_heap_get_freed_count не реализован.
|
||||
|
||||
44. timeout_heap.c:66-67 integer overflow
|
||||
capacity*2 и sizeof(TimeoutEntry)*new_cap могут переполниться.
|
||||
|
||||
45. ll_queue.c:106-127 dangling pointer
|
||||
queue_free освобождает hash_table, но не чистит entry->hash_next.
|
||||
|
||||
46. pkt_normalizer.c:374 десинхронизация потока
|
||||
При провале ll_alloc_lldgram 2 байта заголовка уже consumed,
|
||||
но фрагмент сброшен → потеря данных.
|
||||
|
||||
47. eim_nat.c:105,181 unaligned access (ARM)
|
||||
*(uint16_t*)(ip_data + 6) — невыровненный доступ на ARM.
|
||||
|
||||
48. secure_channel.c:108-131 избыточность
|
||||
CRC32 поверх CCM (аутентифицированное шифрование) избыточен.
|
||||
|
||||
49. secure_channel.h:51-52 dead code
|
||||
Неиспользуемые поля send_nonce[13]/recv_nonce[13].
|
||||
|
||||
50. config_parser.c:312 undefined behavior
|
||||
~0U << (32 - cidr) при cidr==0 — сдвиг на ширину типа.
|
||||
|
||||
51. route_ping.c:227 integer overflow
|
||||
uint16_t avg_rtt = timeout_ms * 10 — переполнение при timeout_ms > 6553.
|
||||
|
||||
|
||||
========================================================================
|
||||
Файлы без ошибок
|
||||
========================================================================
|
||||
|
||||
- route_node.c — чисто
|
||||
- tun_if.c — чисто
|
||||
- etcp_api.c — только порядок queue_entry_free/dgram_free (LOW, см. etcp.c Issue #7 старого аудита)
|
||||
- etcp_loadbalancer.c — не аудирован (250 строк)
|
||||
|
||||
|
||||
========================================================================
|
||||
Рекомендуемый порядок исправления
|
||||
========================================================================
|
||||
|
||||
1. КРИТИЧЕСКИ ФУНКЦИОНАЛЬНЫЕ:
|
||||
u_async.c #2 (posted_tasks не работают на Linux)
|
||||
u_async.c #3 (утечки при сбое старта)
|
||||
|
||||
2. USE-AFTER-FREE:
|
||||
etcp_connections.c #6,#7,#8,#5 (висячие таймеры)
|
||||
u_async.c #1 (частичный realloc)
|
||||
ll_queue.c #17 (reentrancy)
|
||||
|
||||
3. УТЕЧКИ ПАМЯТИ:
|
||||
etcp.c #5 (dgram из data_pool)
|
||||
route_bgp.c #14,#15,#36,#37
|
||||
route_ping.c #16
|
||||
|
||||
4. OOB / NULL DEREF:
|
||||
etcp_connections.c #9,#26 (INIT bounds)
|
||||
pkt_normalizer.c #10,#11 (null deref)
|
||||
|
||||
5. ЛОГИКА:
|
||||
eim_nat.c #12,#13 (порты + expiry)
|
||||
memory_pool.c #4 (double-free)
|
||||
config_parser.c #38 (дубликат секции)
|
||||
+1
-19
@@ -1,26 +1,8 @@
|
||||
# Main Makefile.am for utun - root directory configuration
|
||||
|
||||
# This is the root Makefile.am - tests are defined in tests/Makefile.am
|
||||
|
||||
# Basic configuration for root directory
|
||||
# Order matters: lib -> src -> tests (tests depend on src objects)
|
||||
SUBDIRS = lib src tests
|
||||
|
||||
# Build directory for out-of-tree builds
|
||||
BUILD_DIR = build
|
||||
|
||||
# Custom target to run full build in separate directory
|
||||
.PHONY: fullbuild
|
||||
fullbuild:
|
||||
@echo "Building in $(BUILD_DIR)/ directory..."
|
||||
@$(MKDIR_P) $(BUILD_DIR)
|
||||
@if test ! -f $(BUILD_DIR)/Makefile; then \
|
||||
echo "Running configure in $(BUILD_DIR)..."; \
|
||||
cd $(BUILD_DIR) && $(top_srcdir)/configure; \
|
||||
fi
|
||||
@$(MAKE) -C $(BUILD_DIR) all
|
||||
@echo "Build complete. Binaries copied to project root."
|
||||
|
||||
# Clean build directory
|
||||
clean-local:
|
||||
-rm -rf $(BUILD_DIR)
|
||||
-rm -rf build/
|
||||
@@ -69,17 +69,16 @@ fi
|
||||
if [ "$CLEAN" -eq 1 ]; then
|
||||
echo "Cleaning build..."
|
||||
make clean 2>/dev/null || true
|
||||
rm -f config.status config.h
|
||||
fi
|
||||
|
||||
# Full rebuild: clean everything and regenerate
|
||||
# Full rebuild: regenerate build system
|
||||
if [ "$FULL_REBUILD" -eq 1 ]; then
|
||||
echo ""
|
||||
echo "======================================"
|
||||
echo "FULL REBUILD: autoreconf + configure"
|
||||
echo "FULL REBUILD: autoreconf"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
|
||||
# Check for autotools
|
||||
if ! command -v autoreconf &> /dev/null; then
|
||||
echo "Error: autoreconf not found."
|
||||
@@ -90,29 +89,24 @@ if [ "$FULL_REBUILD" -eq 1 ]; then
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
# Clean generated files
|
||||
echo "Cleaning generated files..."
|
||||
make distclean 2>/dev/null || true
|
||||
rm -f config.status config.h Makefile */Makefile
|
||||
|
||||
# Regenerate build system
|
||||
rm -rf build/
|
||||
|
||||
# Regenerate build system (root Makefile is hand-written, not generated)
|
||||
echo ""
|
||||
echo "Running autoreconf -fi..."
|
||||
autoreconf -fi 2>&1 | tee autoreconf.log
|
||||
|
||||
# Configure
|
||||
echo ""
|
||||
echo "Running configure..."
|
||||
./configure --prefix=/usr/local 2>&1 | tee configure.log
|
||||
|
||||
|
||||
echo ""
|
||||
echo "======================================"
|
||||
echo "Build system regenerated successfully"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
else
|
||||
# Quick/incremental build: only configure if needed
|
||||
# Quick/incremental build: generate configure if needed
|
||||
if [ ! -f ./configure ]; then
|
||||
echo "Configure script not found. Generating..."
|
||||
if ! command -v autoreconf &> /dev/null; then
|
||||
@@ -126,21 +120,13 @@ else
|
||||
fi
|
||||
autoreconf -fiv
|
||||
fi
|
||||
|
||||
# Run configure if needed
|
||||
if [ ! -f config.status ] || [ config.status -ot configure ]; then
|
||||
echo "Configuring build..."
|
||||
./configure --prefix=/usr/local 2>&1 | tee configure.log
|
||||
else
|
||||
echo "Using existing configuration."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build
|
||||
# Build (all .o files go to build/ directory, binary copied to project root)
|
||||
echo ""
|
||||
echo "Compiling with $JOBS..."
|
||||
BUILD_SUCCESS=0
|
||||
make $JOBS $MAKE_ARGS 2>&1 | tee build_linux.log
|
||||
make $JOBS $MAKE_ARGS 2>&1 | tee build.log
|
||||
if [ ${PIPESTATUS[0]} -eq 0 ]; then
|
||||
BUILD_SUCCESS=1
|
||||
fi
|
||||
@@ -148,10 +134,10 @@ fi
|
||||
if [ $BUILD_SUCCESS -eq 1 ]; then
|
||||
echo ""
|
||||
echo "Build completed successfully!"
|
||||
echo "Binary: src/utun"
|
||||
echo "Binary: utun"
|
||||
else
|
||||
echo ""
|
||||
echo "ERROR: Build failed!"
|
||||
echo "Check build_linux.log for details"
|
||||
echo "Check build.log for details"
|
||||
exit 1
|
||||
fi
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
pkg install -y autoconf openssl
|
||||
#pkg install -y autoconf openssl
|
||||
|
||||
mkdir -p m4
|
||||
set -e
|
||||
|
||||
+137
-74
@@ -7,120 +7,183 @@
|
||||
- либо wait SACK + wait timeout (если sack очередь полная и нет необслуженных запросов ретрансмиссий). после истечения wait timeout он помечает используя round-robin очередной пакет как "нужна ретрансмиссия".
|
||||
из async ожидания он может вызвать etcp_loadbalancer input_from_etcp и передать пакет на отправку.
|
||||
как формируется пакет:
|
||||
1. берем пакет из списка неподтвержденных пакетов ожидающих отправку
|
||||
1. выбираем канал передачи (etcp_loadbalancer_select_link)
|
||||
2. если есть место в rwin (объём inflight данных) то берем очередной пакет из ETCP input_queue и его отправляем
|
||||
если пакет найден:
|
||||
1. вызываем функцию формирования опциональных секций (ACK, channel timestamp) - они записываются в начало
|
||||
2. в конец добавляем секцию 0x00 с данными пакета
|
||||
3. если пакет найден или есть неподтверждённые ACK:
|
||||
1. вызываем функцию формирования опциональных секций (ACK, channel timestamp) - они записываются в начало
|
||||
2. в конец добавляем секцию 0x00 с данными пакета
|
||||
при отправке пакета помечаем в inflight списке с какого интерфейса он отправлен
|
||||
Функция прикрепления опциональных секций:
|
||||
- запрашивает выбор канала передачи.
|
||||
- добавляет накопившиеся ACK
|
||||
При приёме пакета (от etcp_connections):
|
||||
- последовательно сканируем секции и отдаём их на обработку нужным обработчикам:
|
||||
- ack: помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также и ставим флаг в конце запустить etcp если он в wait timeout, т.е. wait_timeout!=NULL
|
||||
- timestamp: обновляем last RTT, пересчитываем RTT10,RTT100 (плавающим окном за последние 10/100 пакетов), и jitter=max(last 10)-min(last 10 packets)
|
||||
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем).
|
||||
- ack (0x01): помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также снимаем блокировку отправки
|
||||
- timestamp (0x06): обновляем last RTT, пересчитываем RTT avg10 и jitter по каждому линку; обновляем recv_dt_avg для раздельного измерения времени передачи/приёма
|
||||
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем). также добавляем ACK запись в ack_q для отправки.
|
||||
после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть.
|
||||
|
||||
пересканирование inflight:
|
||||
- вызывается по таймеру, таймер заводится при добавлении пакета в пустой inflight.
|
||||
- если время последней отправки > RTT10*k1+jitter*k2 (коэффициенты вынести в настройки utun instance) то пакет заново отправляется
|
||||
и заводим таймер отправки следующей ретрансмиссии (список отсортирован по времени)
|
||||
- если встречаем успешный ack, запоминаем это и его timestamp (назовем lp_ts).
|
||||
- если после этого далее в очереди находим пакет который не имеет переповторов и timestamp отправки раньше чем [ИСПРАВЛЕНО: фраза обрезана в оригинале; предположительно, "раньше чем lp_ts" на основе контекста; если нет, пометить как неполное]
|
||||
- запускается при добавлении пакета в очередь ожидания ACK (wait_ack_cb). устанавливается таймер на первый неистекший пакет.
|
||||
- если время последней отправки > RTT_avg10*K1+jitter*K2 (коэффициенты K1=32, K2=32, деление на 16) то пакет заново отправляется
|
||||
- очередь FIFO (сортирована по timestamp добавления = времени отправки), сканирование прекращается на первом неистекшем пакете
|
||||
- по истечению таймера (ack_timeout_cb) снова вызывается проверка очереди
|
||||
|
||||
inflight - два списка (ll_queue):
|
||||
- список с неподтвержденными пакетами ожидающими ack (при запросе ретрансмиссии пакет перемещается в ожидающие отправку)
|
||||
- список с неподтвержденными пакетами ожидающими отправку (при отправке возвращается в ожидающие ack)
|
||||
доп. свойства пакетов:
|
||||
- указатель на ETC_LINK последней отправки
|
||||
- timestamp последней отправки
|
||||
- число отправок
|
||||
- число запросов переповтора
|
||||
inflight - две очереди (ll_queue):
|
||||
- input_send_q: список пакетов ожидающих отправку
|
||||
- input_wait_ack: список неподтвержденных пакетов ожидающих ack
|
||||
При отправке пакет перемещается из input_send_q в input_wait_ack.
|
||||
При ретрансмиссии пакет перемещается из input_wait_ack обратно в input_send_q.
|
||||
доп. свойства пакетов (struct INFLIGHT_PACKET):
|
||||
- указатель на ETC_LINK последней отправки (last_link)
|
||||
- timestamp последней отправки (last_timestamp)
|
||||
- число отправок (send_count)
|
||||
- send_hist[8] - история через какие линки отправлялся (индекс линка)
|
||||
- inflight_bytes/inflight_packets per-link отслеживается в ETCP_LINK
|
||||
Размер буфера inflight лимитируем как сумму по всем активным линкам optimal_inflight.
|
||||
optimal_inflight расчитывается из RTT и bandwidth линка.
|
||||
|
||||
bandwidth по каждому линку адаптивно подстраиваем следующим алгоритмом:
|
||||
периодически инициируем burst_transmission (напоминаем ID пакета на которой она началась).
|
||||
после передаём пачку пакетов только по этому линку со следующими таймингами:
|
||||
<pkt1> <delay 4x> <pkt2><pkt3><pkt4><pkt5> - т.е. чуть задерживаем канал а потом выплёвываем пачку пакетов.
|
||||
и добавляем опцию [meas_ts][n] - где n - номер пакета в пачке
|
||||
приёмная сторона видя эту секцию должна отправить секцию [meas_resp] по всем пакетам, которые содержат локальный RECV_Timestamp фиксирующий время приёма каждого пакета с [meas_ts].
|
||||
приняв эти response анализируем по разнице между pkt1-pkt2 не забита ли очередь отправки, а по дельте pkt2-3-4-5 оцениваем bandwidth.
|
||||
дальше по алгоритму (который потом оттюним) корректируем transmitter speed limit.
|
||||
bandwidth по каждому линку адаптивно подстраиваем:
|
||||
Периодически в stats_timer_cb собираем статистику окна (rtt_avg10, ретрансмиссии, переданные пакеты).
|
||||
BW рассчитывается как: inflight_lim_bytes * 8 / rtt_min (в секундах) * 8 / 1024 * 1.3 (Kbits/sec).
|
||||
Размер inflight_lim_bytes плавно подстраивается вверх/вниз в зависимости от того, растёт ли RTT.
|
||||
Burst-измерения bandwidth (0x07, 0x08) зарезервированы, но на данный момент не реализованы.
|
||||
|
||||
|
||||
**** Формат кодограмм для etcp.c/h ****
|
||||
Каждая кодограмма состоит из обязательного заголовка и опциональных секций:
|
||||
1. Обязательный заголовок всего пакета (2 байта) добавляется при передаче, есть во всех пакетах:
|
||||
[Timestamp high][Timestamp low][flags]
|
||||
- Timestamp: время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется
|
||||
- flags: bit0 = up/down (recv_keepalive)
|
||||
|
||||
шифруется строго ВСЁ включая ВСЕ заголовки (кроме INIT+publickey). timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ
|
||||
2. Опциональные секции (одна или несколько) добавляются в etcp.c:
|
||||
1. Обязательный заголовок всего пакета (3 байта) добавляется при передаче, есть во всех пакетах:
|
||||
[Timestamp high][Timestamp low][flags/flag_up]
|
||||
- Timestamp: uint16_t, время отправки в единицах 0.1 мс (циклическое). при переповторах время обновляется
|
||||
- flag_up: uint8_t, bit0 = up/down (recv_keepalive) — признак живой приёмной стороны
|
||||
|
||||
шифруется строго ВСЁ включая ВСЕ заголовки (кроме обфусцированного publickey в INIT-пакетах).
|
||||
timestamp вставляется ВСЕГДА ВО ВСЕ КОДОГРАММЫ (скрыто в шифрованном заголовке).
|
||||
Итого шифруется: 3 байта заголовка + (data_len - noencrypt_len) байт данных.
|
||||
2. Опциональные секции (одна или несколько) добавляются в etcp.c (внутри data[], без доп. заголовка):
|
||||
а) Подтверждения (ACK) - заголовок 0x01:
|
||||
[0x01][count][(id_hi,id_lo,ts_hi,ts_lo)×count][last_delivered_hi,lo][last_rx_hi,lo]
|
||||
- count: количество пар ID+timestamp (1-32)
|
||||
- last_delivered_id: последний ID, доставленный получателю
|
||||
- last_rx_id: последний полученный ID (для синхронизации прогресса)
|
||||
в) channel timestamp (добавляется после выбора канала передачи):
|
||||
[0x0F] [RET_Timestamp high][RET_Timestamp low] [RECV_Timestamp high][RECV_Timestamp low]
|
||||
- RET_Timestamp: это timestamp последнего принятого пакета по этому каналу ETCP_LINK плюс разница времени между принятием этого пакета и текущим временем. т.е. приёмная сторона по этому timestamp (и зная своё время) может просчитать время в пути туда + обратно
|
||||
- RECV_Timestamp: это timestamp времени приёма последнего принятого пакета (т.е. отправляем время "когда мы получили этот пакет по локальном урмени") - по нему другая сторона сможет просчитать относительное время задержки в одну сторону (время туда без обратно).
|
||||
если последний пакет по этому каналу принят сильно давно (прошло более 30000 единиц времени) то channel timestamp не добавляем. [СПОРНО: порог 30000 (3 сек) arbitrary; может потребовать настройки или обоснования; также риск clock skew между нодами]
|
||||
[0x01][count][last_delivered_id(4 байта LE)][rx_dup_count(2 байта LE)][[seq(4 LE)][recv_ts(2 LE)][dly(2 LE)] × count]
|
||||
- count: количество ACK записей (1 байт; до 32 ограничено размером пакета)
|
||||
- last_delivered_id: uint32_t — последний ID, доставленный получателю (по этот ID всё собрано подряд)
|
||||
- rx_dup_count: uint16_t — счётчик принятых дубликатов (локальный, для вычисления tx_dup_count на удалённой стороне)
|
||||
- seq: uint32_t — номер подтверждаемого пакета
|
||||
- recv_ts: uint16_t — локальный timestamp приёма пакета (в единицах 0.1 мс)
|
||||
- dly: uint16_t — задержка между приёмом пакета и отправкой ACK (для расчёта RTT)
|
||||
Каждая запись = 8 байт.
|
||||
в) channel timestamp (добавляется после выбора канала передачи, если есть свежие данные):
|
||||
[0x06] [RET_Timestamp(2 байта LE)] [RECV_Rel_Timestamp(2 байта LE)]
|
||||
- RET_Timestamp: uint16_t = last_recv_timestamp + (now - last_recv_local_time)
|
||||
(оценка текущего времени на передающей стороне в момент отправки)
|
||||
- RECV_Rel_Timestamp: uint16_t = last_recv_local_time - last_recv_timestamp
|
||||
(относительное смещение между локальным и удалённым временем)
|
||||
Всего 5 байт. Добавляется только если last_recv_updated=1 (был принят пакет с момента последней отправки)
|
||||
и если прошло не более ~100ms (dt < 1000000 таймбаз).
|
||||
3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя):
|
||||
[0x00] [ID 4 байта] [данные...] - Данные пакета
|
||||
- ID: 16-битный циклический номер пакета. [СПОРНО: 16-bit может привести к быстрому overflow в long-lived сессиях; рассмотреть 32-bit]
|
||||
Пакет может содержать несколько опциональных секций (например, ACK + данные). [СПОРНО: нет явных length полей для секций; parsing implicit, риск ошибок при variable длине; добавить length?]
|
||||
[0x00] [ID 4 байта LE] [данные...]
|
||||
- ID: uint32_t, 32-битный циклический номер пакета
|
||||
Пакет может содержать несколько опциональных секций (например, ACK + channel_timestamp + данные),
|
||||
всегда в порядке: ACK, channel_timestamp, payload; payload всегда последний.
|
||||
Разбор секций — последовательное сканирование с известными размерами (нет явного поля длины секции).
|
||||
|
||||
цель протокола:
|
||||
1. контролировать состояние очередей (не накапливать данные где этого можно избежать), анализировать RTT и inflight - динамически подстраивать размер окна и ретрансмиссии, утилизируя сеть насколько это возможно но не допуская наполнения буферов в сети (что приводит к бестолковому увеличению RTT)
|
||||
|
||||
Установка подключения для канала (реализуется в etcp_connections.c/h):
|
||||
каждое ETCP подключение может содержать несколько линков до endpoint (разные маршруты, каналы связи).
|
||||
При прохождении трафика через этот модуль подсчитываются метрики (для каждого канала отдельно):
|
||||
- RTT последнего пакета и время отправки последнего пакета
|
||||
- пакеты делятся на 3 категории: <300 байт, 300-699 байт, 700+ байт. для каждой категории отдельно считаются метрики:
|
||||
- количество отправленных в пакетах байтах
|
||||
- количество перезапросов (по каждому каналу отдельно) в пакетах и байтах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики.
|
||||
- общее количество отправленных/принятых байт
|
||||
- количество перезапросов (по каждому каналу отдельно) в пакетах. когда etcp модуль принял запрос ретрансмиссии он смотрит с какого интерфейса был отправлен этот пакет (это запоминается при отправке) и увеличивает счетчики.
|
||||
- обновление текущего количества неподтвержденных данных inflight (в пакетах и байтах) - сколько по этому каналу отправленных и неподтвержденных на текущий момент
|
||||
Планировщик/балансир каналов (etcp_loadbalancer.c/h):
|
||||
для каждого канала ограничение полосы с тестированием отклика:
|
||||
- полосу ограничиваем по объему данных - полному числу байт ethernet пакта (добавляем размер заголовков)
|
||||
Ограничение реализуем следующим образом:
|
||||
- в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности (т.е. было ненулевое время передачи одного байта).
|
||||
- в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности.
|
||||
Этим счётчиком контролируется полоса: при передаче к нему добавляется расчётное время передачи текущего ethernet пакета (используя текущий bandwidth считаем время передачи одного байта (float) и умножаем на число байт, переводим в таймбазу nanotime). А если при передаче время меньше current_time - delta_time (delta_time - константа в define, x0.1ms) то он устанавливается как current time-delta_time. (при неактивности обновляем время)
|
||||
счетчик нужен для контроля можно ли передавать следующий пакет ("расчётное время передачи"); [СПОРНО: сложность с float; риск precision issues]
|
||||
счетчик нужен для контроля можно ли передавать следующий пакет ("расчётное время передачи");
|
||||
когда etcp пытается отправить пакет через линк, то если в линке не инициализировано подключение и он клиент - то он отбрасывает этот пакет и запускает процесс установки соединения.
|
||||
также процесс установки соединения инициируется при добавлении канала (в etcp_link_new) если это client.
|
||||
процесс установки соединения:
|
||||
- отправляем init запрос и выставляем таймаут (сохраняем его в struct ETCP_LINK)
|
||||
- по таймауту повторяем init запросы, ведем счетчик запросов init.
|
||||
- когда получаем init подтверждение (отправляется в etcp_connections_read_callback) снимаем таймаут и выставляем флаг struct ETCP_LINK initialized=1.
|
||||
При добавлении нового канала посылаем INIT 0x04 (без сброса), а при первичной инициализации подключения - 0x02 (это нужно, т.к. например перезапуск локального софта должен инициировать перезапуск удаленного соединения).
|
||||
процесс установки соединения (рукопожатие):
|
||||
- клиент отправляет init запрос и выставляет таймаут
|
||||
- по таймауту повторяет init запросы с экспоненциально растущим интервалом (начальный 500ms, макс 50s, +25% каждые 10 попыток), ведется счетчик запросов init.
|
||||
- когда клиент получает init подтверждение — снимает таймаут, выставляет link_state=connected, запускает keepalive.
|
||||
- сервер при получении init запроса: если линк существует и session_id совпал — отвечает без сброса (0x05); если session_id новый — выполняет etcp_conn_reinit и отвечает со сбросом (0x03).
|
||||
При добавлении нового канала: если link_state=1 (handshake) — посылаем INIT со сбросом (0x02); если link_state=2 (reconnect) — без сброса (0x04).
|
||||
link_state: 0=just init, 1=handshake, 2=reconnect, 3=connected.
|
||||
|
||||
**** Формат кодограмм для etcp_connections.c/h ****
|
||||
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда только одна секция). в обязательном заголовке ID не используется, при передаче для порядка =0: [СПОРНО: заголовок в etcp — только 2 байта TS; здесь подразумевается ID? Уточнить единый формат]
|
||||
1) Init запрос - заголовок 0x02 (со сбросом etcp сессии) или 0x04 (без сброса):
|
||||
[0x02/0x04] [my_node_id 64bit] [my mtu high] [my mtu low] [keepalive high] [keepalive low] [recovery high] [recovery low] [my link_id 1 байт] [my public key (64 байта, не шифруется)]
|
||||
- Инициирует новый connection для tcp instance. если tcp instance нет (первое подключение) - создаёт. Между нодами только одно подключение, но можно добавлять каналы.
|
||||
- link_id: локальный идентификатор канала (0-255), назначается отправителем для идентификации канала
|
||||
- Публичный ключ отправляется в конце пакета без шифрования, чтобы получатель мог установить его и расшифровать остальную часть пакета (т.к. инициатор соединения всегда имеет оригинальный peer public key в конфиге - по нему исключаем MITM)
|
||||
2) Init подтверждение - заголовок 0x03/0x05 (ответы соответственно на коды 0x02 и 0x04):
|
||||
[0x03/0x05] [my_node_id 64bit] [my mtu high] [my mtu low] [my link_id 1 байт] [peer ipv4 4 байта] [peer port 2 байта]
|
||||
- Подтверждение инициализации (канал успешно создан, можно начинать обмен)
|
||||
- link_id: локальный идентификатор канала (0-255) отправителя ответа
|
||||
- peer ip.port: возвращаем ip:port с которого пришел init запрос (чтобы клиент узнал свой external ip:port если за nat)
|
||||
Кодограммы с этими секциями обрабатываются в etcp_connections (в этих кодограммах всегда одна секция-тип пакета). В обязательном заголовке (timestamp+flag_up) ID не используется.
|
||||
|
||||
Типы пакетов:
|
||||
0x02 — ETCP_INIT_REQUEST (со сбросом etcp сессии)
|
||||
0x03 — ETCP_INIT_RESPONSE (подтверждение со сбросом)
|
||||
0x04 — ETCP_INIT_REQUEST_NOINIT (без сброса)
|
||||
0x05 — ETCP_INIT_RESPONSE_NOINIT (подтверждение без сброса)
|
||||
0x06 — ETCP_PING (проверка доступности)
|
||||
0x07 — ETCP_PONG (ответ на пинг)
|
||||
|
||||
1) Init запрос (0x02 / 0x04):
|
||||
[code(1)] [my_node_id(8 BE)] [session_id(4 BE)] [mtu_local(2)] [keepalive_interval(2)]
|
||||
[recovery_interval/100(2)] [local_link_id(1)] [sock_id(1)] [only_local(1)] [type(1)]
|
||||
[random_padding...] [salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
|
||||
- code: 0x02 (со сбросом) или 0x04 (без сброса)
|
||||
- node_id: 64 бита, идентификатор узла (big-endian)
|
||||
- session_id: 32 бита, случайный ID сессии генерируется клиентом (big-endian)
|
||||
- mtu_local: uint16_t локальный MTU
|
||||
- keepalive_interval: uint16_t интервал keepalive в мс
|
||||
- recovery_interval: uint16_t = recovery_interval/100 (в таймбазах 0.1мс / 100)
|
||||
- local_link_id: uint8_t (0-255), назначается отправителем
|
||||
- sock_id: uint8_t, уникальный ID сокета отправителя
|
||||
- only_local: uint8_t, флаг "только локальные подключения"
|
||||
- type: uint8_t, тип сокета (CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE)
|
||||
- random_padding: случайные байты до размера handshake (защита от fingerprinting)
|
||||
- salt + obfuscated_pubkey: публичный ключ с обфускацией (не шифруется AES,
|
||||
но скрывается XOR с salt и собственным публичным ключом). Передаётся в конце пакета
|
||||
в noencrypt_len байтах (не попадает под AES-шифрование)
|
||||
Инициирует новый connection для tcp instance. Если tcp instance нет (первое подключение) - создаёт.
|
||||
Между нодами только одно подключение, но можно добавлять каналы.
|
||||
|
||||
2) Init подтверждение (0x03 / 0x05):
|
||||
[code(1)] [my_node_id(8 BE)] [session_id(4 BE)] [mtu_local(2)] [local_link_id(1)]
|
||||
[remote_socket_id(1)] [only_local(1)] [type(1)] [peer_ipv4(4)] [peer_port(2)]
|
||||
[random_padding...]
|
||||
- code: 0x03 (со сбросом) или 0x05 (без сброса)
|
||||
- node_id: 64 бита, big-endian
|
||||
- session_id: 32 бита, big-endian (должен совпасть с запросом клиента; если нет — клиент игнорирует)
|
||||
- mtu_local: uint16_t локальный MTU сервера
|
||||
- local_link_id: uint8_t идентификатор канала сервера
|
||||
- remote_socket_id: uint8_t (копируется из запроса)
|
||||
- only_local: uint8_t флаг сервера
|
||||
- type: uint8_t тип сокета сервера
|
||||
- peer_ipv4: 4 байта (IPv4 адрес клиента как его видит сервер — для NAT traversal)
|
||||
- peer_port: 2 байта (порт клиента как его видит сервер)
|
||||
- random_padding: случайные байты
|
||||
|
||||
3) PING (0x06):
|
||||
[0x06] [node_id(8 BE)] [nonce(8 BE)] [user_data_len(2 BE)] [user_data...]
|
||||
[salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
|
||||
Отправляется с отдельным crypto-контекстом (не связан с ETCP-сессией).
|
||||
Используется для проверки доступности узла и NAT-тестирования.
|
||||
|
||||
4) PONG (0x07):
|
||||
[0x07] [node_id(8 BE)] [nonce(8 BE)] [resp_data_len(2 BE)] [response_data...]
|
||||
[salt(SC_PUBKEY_ENC_SALT_SIZE)] [obfuscated_pubkey(SC_PUBKEY_SIZE)]
|
||||
Ответ на PING. nonce копируется из запроса для сопоставления.
|
||||
|
||||
При получении init получатель пакета должен:
|
||||
- reset ETCP_LINK с этим ip_port если он есть
|
||||
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - вызвать etcp_reset (функция сброса окон неподтвержденных данных и нумерации)
|
||||
- создать новый ETCP_CONN с этим node_id. если уже существует подключение с этим node_id - проверить ключи на совпадение.
|
||||
приём кодограмм из UDP выглядит так:
|
||||
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (etcp.c/h - сам tcp механизм) -> ETCP_CONN output_queue
|
||||
uasync select -> etcp_connection.c etcp_connections_read_callback: memory_pool_alloc, decrypt (or init) -> etcp_conn_input (etcp.c/h - сам tcp механизм) -> ETCP_CONN output_queue
|
||||
|
||||
Keepalive: пакет без секций (только timestamp+flags)
|
||||
Keepalive: пакет без секций (только timestamp+flag_up в шифрованном заголовке, data_len=0).
|
||||
keepalive пакеты шлют и клиент и сервер с заданным интервалом если нет полезного трафика.
|
||||
Сервер прекращает слать keepalive если линк потерян (ждёт keepalive от клиента).
|
||||
flag_up в заголовке устанавливается в значение recv_keepalive (признак что локальная сторона принимает пакеты).
|
||||
|
||||
Клиент: если все линки =down то начинается процедура восстановления связи:
|
||||
- клиент посылает init без сброса
|
||||
- сервер как получает init без сброса отправляет init подтверждение: со сбросом или без сброса в зависимости от состояния init его соединения (инициализировано - то без сброса).
|
||||
|
||||
- клиент посылает init без сброса (0x04), link_state=2
|
||||
- сервер как получает init проверяет session_id:
|
||||
- если session_id совпадает — шлёт подтверждение без сброса (0x05)
|
||||
- если session_id изменился (клиент перезапустился) — шлёт подтверждение со сбросом (0x03)
|
||||
@@ -0,0 +1,324 @@
|
||||
# ETCP Router Architecture
|
||||
|
||||
## Структуры данных
|
||||
|
||||
```
|
||||
ETCP_ROUTER_CONN (один на пару remote_node_id + svc_id)
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ ll_entry (хеш-индекс: remote_node_id[8] + svc_id[1]) │
|
||||
│ remote_node_id: u64 │
|
||||
│ svc_id: u8 │
|
||||
│ last_dgram_ts: u16 (timestamp последней датаграммы) │
|
||||
│ │
|
||||
│ tx_seq: u32 следующий seq для отправки │
|
||||
│ rx_seq: u32 ожидаемый seq для сборки │
|
||||
│ tx_acked: u32 сколько наших пакетов подтвердил remote │
|
||||
│ last_sent_ack_seq: u32 последний отправленный ACK │
|
||||
│ │
|
||||
│ recv_q: ll_queue* очередь reorder (хеш по seq[4]) │
|
||||
│ send_q: ll_queue* очередь при переполнении inflight │
|
||||
│ │
|
||||
│ ack_timer: void* периодический 100ms │
|
||||
│ idle_ack_timer: void* idle таймаут 500ms │
|
||||
│ send_resume_timer: void* retry send_q 50ms │
|
||||
│ send_blocked: u8 inflight полон │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
|
||||
SVC_ROUTE_HDR (22 байта)
|
||||
┌──────┬────────────────┬────────────────┬───────┬────────┬──────────┐
|
||||
│ cmd │ dst_node_id │ src_node_id │ seq │ svc_id │ payload │
|
||||
│ u8 │ u64 │ u64 │ u32 │ u8 │ ... │
|
||||
└──────┴────────────────┴────────────────┴───────┴────────┴──────────┘
|
||||
|
||||
seq в data-пакетах = tx_seq (счетчик отправленных)
|
||||
seq в ACK-пакетах = rx_seq (ожидаемый следующий)
|
||||
CLOSE-флаг = 0x80000000 в seq, RST-флаг = 0x40000000
|
||||
```
|
||||
|
||||
## Send Path (отправка данных)
|
||||
|
||||
```
|
||||
etcp_route_send(inst, dst, entry)
|
||||
etcp_router_conn_send(rconn, data, len)
|
||||
│
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ rconn = find/get │ поиск по (dst, svc_id)
|
||||
│ авто-создание если │ или etcp_router_conn_get()
|
||||
│ ещё нет │
|
||||
└────────┬────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────┐
|
||||
│ inflight < ROUTER_MAX(256)? │
|
||||
│ tx_seq - tx_acked < 256 ? │
|
||||
└──────┬───────────┬───────────┘
|
||||
│ YES │ NO (inflight полон)
|
||||
▼ ▼
|
||||
┌──────────────┐ ┌─────────────────────┐
|
||||
│router_send_one│ │ router_enqueue_send │ → send_q (FIFO)
|
||||
│ (прямая) │ │ (в очередь) │ send_blocked = 1
|
||||
└──────┬────────┘ └─────────┬───────────┘ send_resume_timer (50ms)
|
||||
│ │
|
||||
▼ │
|
||||
┌────────────────────┐ │
|
||||
│ tx_seq++ │ │
|
||||
│ hdr.dst = dst │ │
|
||||
│ hdr.src = node_id │ │
|
||||
│ hdr.seq = tx_seq │ │
|
||||
│ hdr.svc_id = svc │ │
|
||||
│ │ │
|
||||
│ etcp_send(conn, e) │ │
|
||||
└────────────────────┘ │
|
||||
│
|
||||
┌────────────────────────────┘
|
||||
│ (при получении ACK от remote)
|
||||
▼
|
||||
┌────────────────────┐
|
||||
│ router_drain_send_q│ вызывается из ACK handler
|
||||
│ │ или по send_resume_timer
|
||||
│ while inflight < 256│
|
||||
│ e = send_q.pop() │
|
||||
│ router_send_one(e)│
|
||||
│ │
|
||||
│ если send_q пуст: │
|
||||
│ send_blocked = 0 │
|
||||
└────────────────────┘
|
||||
```
|
||||
|
||||
## Receive Path (приём данных)
|
||||
|
||||
```
|
||||
etcp_router_recv_cb(conn, entry)
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ dst == node_id? │
|
||||
└──┬───────────────┬───┘
|
||||
│ YES │ NO (транзит)
|
||||
▼ ▼
|
||||
┌────────────────────┐ ┌──────────────────────────┐
|
||||
│ Мы — целевая нода │ │ Транзит: найти next hop │
|
||||
└────────┬───────────┘ │ route_bgp_find_conn_for_ │
|
||||
│ │ node(bgp, hdr.dst) │
|
||||
▼ │ etcp_send(next, entry) │
|
||||
┌────────────────────┐ └──────────────────────────┘
|
||||
│ pl_len == 0? │
|
||||
└──┬───────────┬─────┘
|
||||
│ YES │ NO (данные)
|
||||
▼ ▼
|
||||
┌─────────┐ ┌─────────────────────────┐
|
||||
│ CLOSE/ │ │ rconn = find/get │
|
||||
│ RST? │ │ (авто-создание если нет) │
|
||||
└──┬──┬───┘ └───────────┬─────────────┘
|
||||
│ │ │
|
||||
│ │ NO (ACK) ▼
|
||||
│ ▼ ┌───────────────────────────────────────┐
|
||||
│ ┌──────────┐│ Проверка границ seq: │
|
||||
│ │ tx_acked ││ d = seq - rx_seq │
|
||||
│ │ = hdr.seq││ |d| < ROUTER_MAX_INFLIGHT? │
|
||||
│ │ (только ││ │
|
||||
│ │ вперёд) ││ Дубликат? │
|
||||
│ │ drain_q ││ rx_seq - seq > 0 ИЛИ │
|
||||
│ └──────────┘│ seq уже в recv_q? │
|
||||
│ └──────┬─────────────┬──────────────────┘
|
||||
│ │ OK │ out-of-bounds / dup
|
||||
│ ▼ ▼
|
||||
│ ┌──────────────┐ ┌─────────┐
|
||||
│ │ recv_q.put() │ │ DROP │
|
||||
│ │ хеш по seq │ └─────────┘
|
||||
│ └──────┬───────┘
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌──────────────────────┐
|
||||
│ │ seq == rx_seq? │
|
||||
│ └──┬───────────────┬───┘
|
||||
│ │ YES │ NO
|
||||
│ ▼ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ try_assembly │ │
|
||||
│ │ while next в │ │
|
||||
│ │ recv_q: │ │
|
||||
│ │ deliver() │ │
|
||||
│ │ rx_seq++ │ │
|
||||
│ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────────────┴──┐
|
||||
│ │ router_deliver() │
|
||||
│ │ вызывает callback │
|
||||
│ │ сервиса (svc_id) │
|
||||
│ │ entry: [svc_id|payload] │
|
||||
│ └──────────────┬──────────┘
|
||||
│ │
|
||||
▼ ▼
|
||||
┌────────────────────────────────────┐
|
||||
│ router_schedule_ack(rconn) │
|
||||
│ запускает ack_timer (100ms) если │
|
||||
│ ещё не запущен │
|
||||
└────────────────────────────────────┘
|
||||
```
|
||||
|
||||
```
|
||||
CLOSE / RST ветка:
|
||||
┌────────────────────────────────────────┐
|
||||
│ pl_len == 0 && seq & CLOSE/RST │
|
||||
│ → router_close_and_notify(rconn) │
|
||||
│ ├─ router_send_close_to_service() │
|
||||
│ │ (callback с NULL conn) │
|
||||
│ ├─ отправить CLOSE удалённой стороне│
|
||||
│ ├─ отмена всех таймеров │
|
||||
│ ├─ очистка recv_q + send_q │
|
||||
│ └─ удаление из inst->router_conns │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## ACK Mechanism
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ router_schedule_ack(rconn) │
|
||||
│ (вызывается при получении данных) │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
┌────────────────▼────────────────────────┐
|
||||
│ если idle_ack_timer активен — отменить │
|
||||
│ если ack_timer не активен — запустить │
|
||||
│ ROUTER_ACK_INTERVAL_TB = 1000 (100ms) │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
▼ (через 100ms)
|
||||
┌─────────────────────────────────────────┐
|
||||
│ router_ack_timer_cb() │
|
||||
│ │
|
||||
│ if rx_seq != last_sent_ack_seq: │
|
||||
│ router_send_ack(rconn) │
|
||||
│ last_sent_ack_seq = rx_seq │
|
||||
│ перезапустить ack_timer │
|
||||
│ else: │
|
||||
│ запустить idle_ack_timer (500ms) │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
▼ (через 500ms)
|
||||
┌─────────────────────────────────────────┐
|
||||
│ router_idle_ack_timer_cb() │
|
||||
│ │
|
||||
│ if rx_seq != last_sent_ack_seq: │
|
||||
│ router_send_ack(rconn) (досылка) │
|
||||
│ last_sent_ack_seq = rx_seq │
|
||||
└─────────────────────────────────────────┘
|
||||
|
||||
router_send_ack(rconn):
|
||||
hdr.seq = rx_seq
|
||||
hdr.svc_id = svc_id
|
||||
pl_len = 0 → это ACK-пакет
|
||||
etcp_send(conn, entry)
|
||||
```
|
||||
|
||||
```
|
||||
Обработка входящего ACK:
|
||||
┌────────────────────────────────────────┐
|
||||
│ pl_len == 0 && нет CLOSE/RST флагов │
|
||||
│ │
|
||||
│ if seq >= tx_acked: // только вперёд │
|
||||
│ tx_acked = seq // обновить │
|
||||
│ else: │
|
||||
│ DEBUG_WARN "stale ACK" │
|
||||
│ │
|
||||
│ if send_blocked: │
|
||||
│ router_drain_send_q(rconn) │
|
||||
└────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Inflight Control
|
||||
|
||||
```
|
||||
ROUTER_MAX_INFLIGHT = 256
|
||||
|
||||
Отправка возможна когда: tx_seq - tx_acked < 256
|
||||
Отправка заблокирована: tx_seq - tx_acked >= 256
|
||||
|
||||
При блокировке:
|
||||
данные → send_q (FIFO, без дропов)
|
||||
send_blocked = 1
|
||||
send_resume_timer = 50ms retry
|
||||
|
||||
Разблокировка:
|
||||
приход ACK от remote → tx_acked обновляется
|
||||
→ router_drain_send_q() разгребает send_q
|
||||
или send_resume_timer (50ms) → router_drain_send_q()
|
||||
|
||||
send_q полностью разобран → send_blocked = 0
|
||||
```
|
||||
|
||||
## Полная схема потоков
|
||||
|
||||
```
|
||||
┌──────────┐
|
||||
│ Сервис │ (routing, proxy, NAT, ...)
|
||||
│ (svc_id)│
|
||||
└────┬─────┘
|
||||
│
|
||||
etcp_route_send() │ callback(svc_id, entry)
|
||||
▼ ▲
|
||||
┌─────────┐ send ┌─────────────────────────────────┐ recv ┌─────────┐
|
||||
│ TUN │─────────▶│ ETCP ROUTER │─────────▶│ TUN │
|
||||
│ (выход) │ │ │ │ (вход) │
|
||||
└─────────┘ │ ┌──────────┐ ┌──────────┐ │ └─────────┘
|
||||
│ │ send_q │ │ recv_q │ │
|
||||
│ │ (FIFO) │ │ (хеш seq)│ │
|
||||
│ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ Inflight / Assembly │ │
|
||||
│ │ tx_seq - tx_acked │ │
|
||||
│ │ rx_seq → deliver │ │
|
||||
│ └───────────┬─────────────┘ │
|
||||
│ │ │
|
||||
└──────────────┼──────────────────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ BGP │
|
||||
│ (нахождение│
|
||||
│ next hop) │
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ ETCP conn │──▶ сеть
|
||||
└─────────────┘
|
||||
|
||||
──▶ send (исходящие данные)
|
||||
──▶ recv (входящие данные, callback сервису)
|
||||
──▶ транзит (через BGP к следующему hop)
|
||||
```
|
||||
|
||||
## Таймеры (один rconn)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ack_timer ──────── 100ms ─────▶ router_ack_timer_cb│
|
||||
│ (периодический) │
|
||||
│ │
|
||||
│ idle_ack_timer ─── 500ms ─────▶ router_idle_ack_cb │
|
||||
│ (досылка последнего ACK) │
|
||||
│ │
|
||||
│ send_resume_timer ─ 50ms ─────▶ router_send_resume │
|
||||
│ (retry когда send_q не пуст) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Флаги seq
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ CLOSE: 0x80000000 нормальное закрытие conn │
|
||||
│ RST: 0x40000000 conn не найден (reset) │
|
||||
│ │
|
||||
│ CLOSE/RST-пакеты: seq=0 | флаг, pl_len=0 │
|
||||
│ Data-пакеты: seq=tx_seq, pl_len>0 │
|
||||
│ ACK-пакеты: seq=rx_seq, pl_len=0 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
@@ -0,0 +1,344 @@
|
||||
Архитектура P2P Direct Connection (установка оптимального подключения между узлами)
|
||||
|
||||
Цель: в большой сети узлов каждый узел умеет находить быстрые подключения
|
||||
к любому другому узлу — либо прямое (direct), либо через минимальное число
|
||||
промежуточных узлов с минимальным суммарным RTT. Оба узла пробуют доступные
|
||||
варианты пингом и выбирают лучший.
|
||||
|
||||
|
||||
=== Что уже есть в коде (используем, не дублируем) ===
|
||||
|
||||
1. NODEINFO уже содержит поле tranzit_nodes + структуру NODEINFO_TRANZIT_NODE
|
||||
(node_id, rtt, link_q) — "лучшие транзитные узлы, выбирается/обновляется узлом",
|
||||
но пока не заполняется и не используется.
|
||||
|
||||
2. Пинг-инфраструктура: route_ping_send_req_addr (запрос удалённого пинга через
|
||||
третий узел), route_ping_handle_resp (приём результата). Также есть
|
||||
etcp_send_ping_to_socket — прямой пинг с конкретного сокета на конкретный адрес
|
||||
с pubkey для шифрования.
|
||||
|
||||
3. NAT-детекция уже работает: проверка типа NAT через третий узел, обмен NAT_INFO.
|
||||
|
||||
4. Path system: NODEINFO_PATH с hop_count, routes → ROUTE_ENTRY → NODEINFO_Q → paths —
|
||||
уже выбирается путь для маршрутизации пакетов.
|
||||
|
||||
5. Константа ROUTE_SUBCMD_CONN_REQ (0x0B) зарезервирована в route_bgp.h, но не реализована.
|
||||
|
||||
6. NODEINFO_Q уже имеет best_socket, last_ping_time, last_rtt.
|
||||
|
||||
|
||||
=== Общий алгоритм (на примере узлов A и B) ===
|
||||
|
||||
A ──(ETCP через релейные узлы)──> B [текущий путь, hop_count > 1]
|
||||
|
||||
1. A замечает большой объём трафика к B → запускает CONN_REQ
|
||||
2. A шлёт B через существующий ETCP-путь: свои адреса-кандидаты + лучших транзитных соседей
|
||||
3. B получает CONN_REQ, сразу начинает пинговать адреса A, собирает свои данные
|
||||
4. B шлёт CONN_RESP: свои адреса + транзитных соседей + результаты пробных пингов
|
||||
5. A получает CONN_RESP, пингует все адреса B
|
||||
6. Оба вычисляют лучший вариант (прямой или релейный с минимальным RTT)
|
||||
7. Оба шлют друг другу CONN_RESULT с выбором
|
||||
8. Оба пытаются создать ETCP link к выбранному адресу (если прямой)
|
||||
9. Лишние/худшие линки закрываются позже — все линки в рамках одного ETCP_CONN
|
||||
|
||||
|
||||
=== Фаза 1: Обмен кандидатами (CONN_REQ → CONN_RESP) ===
|
||||
|
||||
Узел-инициатор A собирает и отправляет:
|
||||
- Свои адреса-кандидаты: из своих ETCP_SOCKET (interface_addr + nat_addr).
|
||||
Включаются только сокеты с NAT_VERIFIED_* / EIM / PUBLIC (type >= NAT_VERIFIED_UNKNOWN).
|
||||
Для каждого: ip, port, type, socket_id.
|
||||
- Лучших транзитных соседей: top-N directly-connected узлов (hop_count == 1)
|
||||
отсортированных по NODEINFO_Q.last_rtt (лучший RTT первым).
|
||||
|
||||
Узел B при получении CONN_REQ:
|
||||
- Сохраняет кандидатов A
|
||||
- Сразу запускает пробные пинги ко всем адресам A (etcp_send_ping_to_socket с pubkey)
|
||||
- Собирает свои адреса-кандидаты
|
||||
- Собирает своих транзитных соседей
|
||||
- Шлёт CONN_RESP со своими данными + уже готовыми результатами проб
|
||||
|
||||
|
||||
=== Фаза 2: Пробы (CONN_RESP → Пинги) ===
|
||||
|
||||
Узел A при получении CONN_RESP:
|
||||
- Запускает пинги ко всем адресам B:
|
||||
каждый адрес B пингуется с каждого своего сокета (N×M проб)
|
||||
- Использует etcp_send_ping_to_socket с pubkey узла B (из NODEINFO)
|
||||
- Пинг идёт напрямую по UDP (не через ETCP) — проверяет реальную достижимость
|
||||
включая NAT traversal
|
||||
- Ждёт все результаты либо таймаут
|
||||
|
||||
Параллельно B тоже завершает свои пробы (запущенные на шаге 3).
|
||||
|
||||
|
||||
=== Фаза 3: Выбор лучшего пути (→ CONN_RESULT) ===
|
||||
|
||||
Каждая сторона независимо вычисляет:
|
||||
|
||||
best = {mode: none, rtt: 65535}
|
||||
|
||||
Прямые варианты:
|
||||
for each (мой_сокет, peer_addr) где probe.ok:
|
||||
if probe.rtt < best.rtt:
|
||||
best = {direct, my_sock_idx, peer_addr_idx, probe.rtt}
|
||||
|
||||
Релейные варианты:
|
||||
for each общий_транзитный_узел R (доступен и A и B):
|
||||
// RTT(A↔R) берём из своих замеров (A знает RTT до своих соседей)
|
||||
// RTT(R↔B) берём из транзитных соседей B (присланы в CONN_RESP)
|
||||
total_rtt = A→R_rtt + R→B_rtt
|
||||
if total_rtt < best.rtt:
|
||||
best = {relay, R, total_rtt}
|
||||
|
||||
Обе стороны приходят к одинаковому выводу (информация симметрична).
|
||||
|
||||
Шлют CONN_RESULT с выбором.
|
||||
Если прямой вариант — оба пытаются создать ETCP link к адресу пира.
|
||||
Если релейный — используют существующий путь (уже работает).
|
||||
|
||||
|
||||
=== Триггер запуска negotiation ===
|
||||
|
||||
В route_pkt (routing.c) при отправке пакета узлу с hop_count > 1:
|
||||
- Накапливаем счётчик байт к этому узлу (per-node counter в bgp)
|
||||
- При превышении порога (например 64KB) и если negotiation ещё не запущен — стартуем
|
||||
- Cooldown: не чаще чем раз в 30 секунд для одной пары узлов
|
||||
- Если negotiation уже в процессе для этой пары — не дублируем
|
||||
|
||||
|
||||
=== Tranzit nodes — автоматическое заполнение ===
|
||||
|
||||
В route_bgp_update_my_nodeinfo (route_node.c) при каждом обновлении NODEINFO:
|
||||
- Сканируем directly-connected соседей (из bgp->nodes, где hop_count == 1)
|
||||
- Сортируем по NODEINFO_Q.last_rtt (лучший RTT первым)
|
||||
- Берём top-N (до 4) и упаковываем в NODEINFO.tranzit_nodes как массив
|
||||
NODEINFO_TRANZIT_NODE (node_id, rtt, link_q)
|
||||
|
||||
Плюс периодический refill (раз в ~10 сек) для свежести RTT замеров.
|
||||
|
||||
|
||||
=== Структуры данных (новые, route_p2pconn.h) ===
|
||||
|
||||
#define P2P_MAX_ADDRS 8
|
||||
#define P2P_MAX_RELAYS 8
|
||||
#define P2P_MAX_PROBES (P2P_MAX_ADDRS * P2P_MAX_ADDRS) // до 64
|
||||
|
||||
#define P2P_PHASE_WAIT_RESP 0 // ждём CONN_RESP от пира
|
||||
#define P2P_PHASE_PROBING 1 // пингуем адреса пира
|
||||
#define P2P_PHASE_SELECTING 2 // выбор лучшего, отправка CONN_RESULT
|
||||
#define P2P_PHASE_DONE 3 // завершено
|
||||
|
||||
// Один адрес-кандидат
|
||||
struct P2P_ADDR {
|
||||
uint32_t ip; // network byte order
|
||||
uint16_t port;
|
||||
uint8_t type; // NAT_VERIFIED_*
|
||||
uint8_t socket_id;
|
||||
};
|
||||
|
||||
// Один транзитный узел
|
||||
struct P2P_RELAY {
|
||||
uint64_t node_id;
|
||||
uint16_t rtt; // x0.1ms
|
||||
uint16_t link_q;
|
||||
};
|
||||
|
||||
// Результат одного пробного пинга
|
||||
struct P2P_PROBE_RESULT {
|
||||
uint8_t my_sock_idx; // индекс в my_addrs[]
|
||||
uint8_t peer_addr_idx; // индекс в peer_addrs[]
|
||||
uint16_t rtt; // x0.1ms, 0 = fail
|
||||
};
|
||||
|
||||
// Состояние одних переговоров (хранится в хеш-таблице bgp->p2p_negotiations)
|
||||
struct P2P_NEGOTIATION {
|
||||
struct ll_entry ll;
|
||||
uint32_t request_id;
|
||||
uint64_t peer_node_id;
|
||||
uint8_t phase;
|
||||
|
||||
// Мои данные
|
||||
struct P2P_ADDR my_addrs[P2P_MAX_ADDRS];
|
||||
uint8_t my_addr_count;
|
||||
struct P2P_RELAY my_relays[P2P_MAX_RELAYS];
|
||||
uint8_t my_relay_count;
|
||||
|
||||
// Данные пира (заполняются из CONN_RESP)
|
||||
struct P2P_ADDR peer_addrs[P2P_MAX_ADDRS];
|
||||
uint8_t peer_addr_count;
|
||||
struct P2P_RELAY peer_relays[P2P_MAX_RELAYS];
|
||||
uint8_t peer_relay_count;
|
||||
|
||||
// Результаты проб
|
||||
struct P2P_PROBE_RESULT probes[P2P_MAX_PROBES];
|
||||
uint8_t probe_total; // сколько всего запланировано
|
||||
uint8_t probe_done; // сколько завершилось (ok + fail)
|
||||
|
||||
// Лучший выбор
|
||||
uint8_t best_mode; // 1=direct, 2=relay
|
||||
uint64_t best_relay; // node_id релея (если mode=relay)
|
||||
uint16_t best_rtt; // x0.1ms
|
||||
uint8_t best_my_sock_idx; // индекс в my_addrs (если direct)
|
||||
uint8_t best_peer_addr_idx;// индекс в peer_addrs (если direct)
|
||||
|
||||
void* timeout_timer; // общий таймаут на всю negotiation
|
||||
};
|
||||
|
||||
// Счётчик трафика per-node (для триггера, хранится в bgp)
|
||||
struct P2P_TRAFFIC_COUNTER {
|
||||
uint64_t node_id;
|
||||
uint64_t bytes_sent; // накоплено байт
|
||||
uint64_t last_negotiation_time; // время последней попытки (0 = не было)
|
||||
};
|
||||
|
||||
|
||||
=== Протокольные пакеты (добавляются в route_bgp.h) ===
|
||||
|
||||
ROUTE_SUBCMD_CONN_REQ 0x0B // запрос прямого подключения (уже зарезервирован)
|
||||
ROUTE_SUBCMD_CONN_RESP 0x0C // ответ с адресами + результаты проб
|
||||
ROUTE_SUBCMD_CONN_RESULT 0x0E // финальный выбор
|
||||
|
||||
// Пакет CONN_REQ (A → B)
|
||||
struct BGP_CONN_REQ {
|
||||
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY
|
||||
uint8_t subcmd; // ROUTE_SUBCMD_CONN_REQ
|
||||
uint32_t request_id; // для корреляции
|
||||
uint8_t addr_count; // число адресов-кандидатов
|
||||
uint8_t relay_count; // число транзитных узлов
|
||||
uint8_t reserved[2];
|
||||
// далее динамически (размер = addr_count*8 + relay_count*12):
|
||||
// [addr_count × {ip[4] port[2] type[1] socket_id[1]}]
|
||||
// [relay_count × {node_id[8] rtt[2] link_q[2]}]
|
||||
};
|
||||
|
||||
// Пакет CONN_RESP (B → A)
|
||||
struct BGP_CONN_RESP {
|
||||
uint8_t cmd; // ETCP_ID_ROUTE_ENTRY
|
||||
uint8_t subcmd; // ROUTE_SUBCMD_CONN_RESP
|
||||
uint32_t request_id;
|
||||
uint8_t addr_count; // адреса B
|
||||
uint8_t relay_count; // транзитные узлы B
|
||||
uint8_t probe_count; // готовые результаты проб B→A
|
||||
uint8_t reserved;
|
||||
// [addr_count × {ip[4] port[2] type[1] socket_id[1]}]
|
||||
// [relay_count × {node_id[8] rtt[2] link_q[2]}]
|
||||
// [probe_count × {peer_addr_idx[1] my_sock_idx[1] rtt[2] ok[1]}]
|
||||
};
|
||||
|
||||
// Пакет CONN_RESULT (A ↔ B, финальный)
|
||||
struct BGP_CONN_RESULT {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd; // ROUTE_SUBCMD_CONN_RESULT
|
||||
uint32_t request_id;
|
||||
uint8_t chosen_mode; // 1=direct, 2=relay
|
||||
uint8_t reserved;
|
||||
uint16_t chosen_rtt; // x0.1ms
|
||||
// Если direct:
|
||||
uint32_t peer_ip; // IP пира к которому подключаемся
|
||||
uint16_t peer_port;
|
||||
uint8_t my_socket_id; // свой сокет для подключения
|
||||
uint8_t peer_socket_id; // сокет пира
|
||||
// Если relay (оверлей тех же байт):
|
||||
// uint64_t relay_node_id;
|
||||
};
|
||||
|
||||
|
||||
=== API модуля route_p2pconn ===
|
||||
|
||||
// Запуск negotiation к узлу peer_node_id.
|
||||
// Вызывается из route_pkt при превышении порога трафика.
|
||||
int p2p_start_negotiation(struct ROUTE_BGP* bgp, uint64_t peer_node_id);
|
||||
|
||||
// Проверка: запущена ли уже negotiation для этой пары
|
||||
int p2p_is_negotiating(struct ROUTE_BGP* bgp, uint64_t peer_node_id);
|
||||
|
||||
// Сбор своих адресов-кандидатов (из etcp_sockets)
|
||||
int p2p_collect_my_addrs(struct ROUTE_BGP* bgp, struct P2P_ADDR* out, uint8_t max);
|
||||
|
||||
// Сбор лучших транзитных соседей (из directly-connected nodes)
|
||||
int p2p_collect_my_relays(struct ROUTE_BGP* bgp, struct P2P_RELAY* out, uint8_t max);
|
||||
|
||||
// Обработчики входящих пакетов (вызываются из route_bgp_receive_cbk):
|
||||
void p2p_handle_conn_req(struct ROUTE_BGP* bgp, struct ETCP_CONN* from,
|
||||
const uint8_t* data, size_t len);
|
||||
void p2p_handle_conn_resp(struct ROUTE_BGP* bgp, struct ETCP_CONN* from,
|
||||
const uint8_t* data, size_t len);
|
||||
void p2p_handle_conn_result(struct ROUTE_BGP* bgp, struct ETCP_CONN* from,
|
||||
const uint8_t* data, size_t len);
|
||||
|
||||
// Отмена negotiation при удалении conn
|
||||
void p2p_cancel_for_conn(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn);
|
||||
|
||||
// Очистка всех negotiation (при destroy bgp)
|
||||
void p2p_destroy_all(struct ROUTE_BGP* bgp);
|
||||
|
||||
|
||||
=== Схема состояний negotiation ===
|
||||
|
||||
IDLE ──(трафик > порог)──→ WAIT_RESP ──(CONN_RESP получен)──→ PROBING
|
||||
↑ ↑ │
|
||||
│ (таймаут) (все пробы готовы)
|
||||
│ ↓ │
|
||||
└──────────────────────────── DONE ←──(CONN_RESULT отправлен)── SELECTING
|
||||
|
||||
|
||||
=== Интеграция (какие файлы меняются) ===
|
||||
|
||||
Новые файлы:
|
||||
src/route_p2pconn.h — структуры, константы, прототипы
|
||||
src/route_p2pconn.c — вся логика negotiation
|
||||
|
||||
Изменения в существующих:
|
||||
src/route_bgp.h — добавить ROUTE_SUBCMD_CONN_RESP 0x0C,
|
||||
ROUTE_SUBCMD_CONN_RESULT 0x0E,
|
||||
структуры пакетов BGP_CONN_REQ/RESP/RESULT
|
||||
src/route_bgp.c — в route_bgp_receive_cbk добавить обработку
|
||||
новых subcmd. В struct ROUTE_BGP добавить
|
||||
поле p2p_negotiations (очередь/хеш negotiation)
|
||||
и p2p_traffic_counters (per-node counters)
|
||||
src/route_node.c — в route_bgp_update_my_nodeinfo заполнять
|
||||
tranzit_nodes из RTT directly-connected соседей
|
||||
src/routing.c — в route_pkt добавить накопление счётчика трафика
|
||||
и вызов p2p_start_negotiation при превышении порога
|
||||
src/Makefile.am — добавить route_p2pconn.c в сборку
|
||||
|
||||
|
||||
=== Внутренняя логика route_p2pconn.c ===
|
||||
|
||||
p2p_start_negotiation(bgp, peer_node_id):
|
||||
1. Проверить что нет активной negotiation для этой пары
|
||||
2. Проверить cooldown (30 сек)
|
||||
3. Создать struct P2P_NEGOTIATION, заполнить my_addrs, my_relays
|
||||
4. Сформировать BGP_CONN_REQ и отправить через существующий путь к пиру
|
||||
5. Поставить таймаут на всю negotiation (например 5 сек)
|
||||
|
||||
p2p_handle_conn_req(bgp, from_conn, data, len):
|
||||
1. Распарсить BGP_CONN_REQ
|
||||
2. Сохранить адреса и релеи инициатора в P2P_NEGOTIATION
|
||||
3. Собрать свои my_addrs, my_relays
|
||||
4. Запустить пробные пинги к адресам инициатора (p2p_start_probes)
|
||||
5. Когда пробы готовы — отправить CONN_RESP с результатами
|
||||
|
||||
p2p_start_probes(neg):
|
||||
1. Для каждой пары (мой_сокет, peer_addr) запланировать пинг
|
||||
2. Вызвать etcp_send_ping_to_socket с pubkey пира
|
||||
3. В коллбэке сохранить результат в probes[], инкрементировать probe_done
|
||||
4. Когда probe_done == probe_total → вызвать p2p_select_best
|
||||
|
||||
p2p_select_best(neg):
|
||||
1. Прямые: найти пару (my_sock, peer_addr) с минимальным rtt > 0
|
||||
2. Релейные: для каждого общего транзитного узла посчитать суммарный rtt,
|
||||
найти минимум
|
||||
3. Сравнить прямой vs релейный лучший rtt
|
||||
4. Записать выбор в neg->best_*
|
||||
5. Отправить CONN_RESULT пиру
|
||||
6. Вызвать p2p_establish_link(neg)
|
||||
|
||||
p2p_establish_link(neg):
|
||||
1. Если best_mode == direct:
|
||||
- Найти/создать ETCP_CONN к пиру (по node_id)
|
||||
- Вызвать etcp_link_new с chosen address
|
||||
2. Если best_mode == relay:
|
||||
- Ничего не делаем, существующий путь уже работает
|
||||
3. Пометить negotiation как DONE
|
||||
+17
-2
@@ -17,15 +17,30 @@ libuasync_a_SOURCES = \
|
||||
socket_compat.h \
|
||||
platform_compat.c \
|
||||
platform_compat.h \
|
||||
getmyip.c \
|
||||
getmyip.h \
|
||||
swm_min.c \
|
||||
swm_min.h \
|
||||
mem.c \
|
||||
mem.h
|
||||
mem.h \
|
||||
radix.c \
|
||||
radix.h \
|
||||
serialize.c \
|
||||
serialize.h \
|
||||
tcp_io.c \
|
||||
tcp_io.h \
|
||||
liblmdb/mdb.c \
|
||||
liblmdb/midl.c \
|
||||
liblmdb/lmdb.h \
|
||||
liblmdb/midl.h
|
||||
|
||||
libuasync_a_CFLAGS = \
|
||||
-D_ISOC99_SOURCE \
|
||||
-DDEBUG_OUTPUT_STDERR \
|
||||
-g \
|
||||
-I$(top_srcdir)/src \
|
||||
-I$(top_srcdir)/lib
|
||||
-I$(top_srcdir)/lib \
|
||||
-I$(srcdir)/liblmdb
|
||||
|
||||
# Clean build directory
|
||||
clean-local:
|
||||
|
||||
+77
-38
@@ -13,30 +13,29 @@
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
|
||||
void log_dump(const char* prefix, const uint8_t* data, size_t len) {
|
||||
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len) {
|
||||
if (!debug_should_output(level, category)) return;
|
||||
|
||||
char hex_buf[513] = {'\0'};
|
||||
size_t hex_len = 0;
|
||||
size_t show_len = (len > 128) ? 128 : len;
|
||||
|
||||
|
||||
for (size_t i = 0; i < show_len && hex_len < 512 - 3; i++) {
|
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02x", data[i]);
|
||||
if (i < show_len - 1 && (i + 1) % 32 == 0) { // Add space every 32 bytes
|
||||
if (i < show_len - 1 && (i + 1) % 32 == 0)
|
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, " ");
|
||||
}
|
||||
}
|
||||
|
||||
if (len > 128) {
|
||||
|
||||
if (len > 128)
|
||||
hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "...");
|
||||
|
||||
switch (level) {
|
||||
case DEBUG_LEVEL_ERROR: DEBUG_ERROR(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_WARN: DEBUG_WARN(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_INFO: DEBUG_INFO(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_DEBUG: DEBUG_DEBUG(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
case DEBUG_LEVEL_TRACE: DEBUG_TRACE(category, "%s: len=%zu hex=%s", prefix, len, hex_buf); break;
|
||||
}
|
||||
|
||||
// Single-line debug output with packet info
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DUMP, "%s: len=%zu hex=%s", prefix, len, hex_buf);
|
||||
|
||||
// Additional debug info for first few bytes
|
||||
// if (len >= 2) {
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "%s: first_bytes=%02x%02x last_bytes=%02x%02x",
|
||||
// prefix, data[0], data[1], data[len-2], data[len-1]);
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +56,49 @@ ip_str_t ip_to_str(const void *addr, int family) {
|
||||
return result;
|
||||
}
|
||||
|
||||
ip_str_t sockaddr_storage_to_str(const struct sockaddr_storage *addr) {
|
||||
ip_str_t result;
|
||||
result.str[0] = '\0';
|
||||
|
||||
if (!addr) {
|
||||
return result;
|
||||
}
|
||||
|
||||
char ip_buf[INET6_ADDRSTRLEN];
|
||||
ip_buf[0] = '\0';
|
||||
|
||||
uint16_t port = 0;
|
||||
void *src = NULL;
|
||||
|
||||
if (addr->ss_family == AF_INET) {
|
||||
const struct sockaddr_in *ipv4 = (const struct sockaddr_in *)addr;
|
||||
src = (void *)&ipv4->sin_addr;
|
||||
port = ntohs(ipv4->sin_port);
|
||||
}
|
||||
else if (addr->ss_family == AF_INET6) {
|
||||
const struct sockaddr_in6 *ipv6 = (const struct sockaddr_in6 *)addr;
|
||||
src = (void *)&ipv6->sin6_addr;
|
||||
port = ntohs(ipv6->sin6_port);
|
||||
}
|
||||
else {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!inet_ntop(addr->ss_family, src, ip_buf, sizeof(ip_buf))) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (addr->ss_family == AF_INET6) {
|
||||
// IPv6 обязательно в []
|
||||
snprintf(result.str, sizeof(result.str), "[%s]:%u", ip_buf, port);
|
||||
} else {
|
||||
snprintf(result.str, sizeof(result.str), "%s:%u", ip_buf, port);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const struct {
|
||||
const char* name;
|
||||
@@ -82,6 +124,10 @@ static const struct {
|
||||
{"traffic", DEBUG_CATEGORY_TRAFFIC},
|
||||
{"debug", DEBUG_CATEGORY_DEBUG},
|
||||
{"general", DEBUG_CATEGORY_GENERAL},
|
||||
{"nat", DEBUG_CATEGORY_NAT},
|
||||
{"keepalive", DEBUG_CATEGORY_KEEPALIVE},
|
||||
{"etcp_route", DEBUG_CATEGORY_ETCPROUTE},
|
||||
{"bbr", DEBUG_CATEGORY_BBR},
|
||||
{"all", DEBUG_CATEGORY_ALL},
|
||||
{NULL, DEBUG_CATEGORY_NONE}
|
||||
};
|
||||
@@ -153,29 +199,9 @@ void debug_apply_global_level(const char* level_name) {
|
||||
|
||||
/* Get category name for logging */
|
||||
const char* debug_get_category_name(debug_category_t category_idx) {
|
||||
switch (category_idx) {
|
||||
case DEBUG_CATEGORY_UASYNC: return "UASYNC";
|
||||
case DEBUG_CATEGORY_LL_QUEUE: return "LL_QUEUE";
|
||||
case DEBUG_CATEGORY_CONNECTION: return "CONNECTION";
|
||||
case DEBUG_CATEGORY_ETCP: return "ETCP";
|
||||
case DEBUG_CATEGORY_CRYPTO: return "CRYPTO";
|
||||
case DEBUG_CATEGORY_MEMORY: return "MEMORY";
|
||||
case DEBUG_CATEGORY_TIMING: return "TIMING";
|
||||
case DEBUG_CATEGORY_CONFIG: return "CONFIG";
|
||||
case DEBUG_CATEGORY_TUN: return "TUN";
|
||||
case DEBUG_CATEGORY_ROUTING: return "ROUTING";
|
||||
case DEBUG_CATEGORY_TIMERS: return "TIMERS";
|
||||
case DEBUG_CATEGORY_NORMALIZER: return "NORMALIZER";
|
||||
case DEBUG_CATEGORY_BGP: return "BGP";
|
||||
case DEBUG_CATEGORY_SOCKET: return "SOCKET";
|
||||
case DEBUG_CATEGORY_CONTROL: return "CONTROL";
|
||||
case DEBUG_CATEGORY_DUMP: return "DUMP";
|
||||
case DEBUG_CATEGORY_TRAFFIC: return "TRAFFIC";
|
||||
case DEBUG_CATEGORY_DEBUG: return "DEBUG";
|
||||
case DEBUG_CATEGORY_GENERAL: return "GENERAL";
|
||||
case DEBUG_CATEGORY_NONE: return "NONE";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
for (int i = 0; g_debug_categories[i].name; i++)
|
||||
if (g_debug_categories[i].category == category_idx) return g_debug_categories[i].name;
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
/* Global debug configuration */
|
||||
@@ -297,6 +323,19 @@ void debug_disable_file_output(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Reopen log file (append mode) for SIGHUP. Complete operation with full error logging. */
|
||||
int debug_reopen_log(void) {
|
||||
if (!g_debug_config.output_file) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "No log path for reopen");
|
||||
return -1;
|
||||
}
|
||||
const char* path = g_debug_config.output_file;
|
||||
debug_disable_file_output();
|
||||
debug_enable_file_output(path, 0); // append
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Log reopened: %s", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Check if debug output should be shown for given level and category */
|
||||
int debug_should_output(debug_level_t level, debug_category_t category_idx) {
|
||||
if (category_idx < 0 || category_idx >= DEBUG_CATEGORY_COUNT) {
|
||||
|
||||
+19
-10
@@ -6,9 +6,11 @@
|
||||
#ifndef DEBUG_CONFIG_H
|
||||
#define DEBUG_CONFIG_H
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#include "platform_compat.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <sys/socket.h> // struct sockaddr_storage
|
||||
#endif
|
||||
@@ -55,7 +57,11 @@ typedef int debug_category_t;
|
||||
#define DEBUG_CATEGORY_TRAFFIC 17 // Traffic flow (src/dst node IDs)
|
||||
#define DEBUG_CATEGORY_DEBUG 18 // Current debugging
|
||||
#define DEBUG_CATEGORY_GENERAL 19 // Messages for user (common log)
|
||||
#define DEBUG_CATEGORY_COUNT 20 // Total number of categories
|
||||
#define DEBUG_CATEGORY_NAT 20 // EIM NAT module
|
||||
#define DEBUG_CATEGORY_KEEPALIVE 21 // Link keepalive logic
|
||||
#define DEBUG_CATEGORY_ETCPROUTE 22 // ETCP routing
|
||||
#define DEBUG_CATEGORY_BBR 23 // BBR congestion control
|
||||
#define DEBUG_CATEGORY_COUNT 24 // Total number of categories
|
||||
#define DEBUG_CATEGORY_ALL (-1) // special value for all categories
|
||||
|
||||
/* Debug configuration structure */
|
||||
@@ -103,19 +109,22 @@ void debug_set_output_file(const char* file_path);
|
||||
/* Dual output configuration */
|
||||
void debug_set_console_level(debug_level_t level);
|
||||
void debug_set_file_level(debug_level_t level);
|
||||
void debug_enable_file_output(const char* file_path, int truncate);
|
||||
void debug_disable_file_output(void);
|
||||
void debug_enable_console(int enable);
|
||||
void debug_enable_file_output(const char* file_path, int truncate);
|
||||
void debug_disable_file_output(void);
|
||||
int debug_reopen_log(void);
|
||||
void debug_enable_console(int enable);
|
||||
|
||||
|
||||
// IP address to string (static buffer, single-threaded)
|
||||
typedef struct {
|
||||
char str[48]; // INET6_ADDRSTRLEN(45) + \0
|
||||
char str[54]; // INET6_ADDRSTRLEN(45) + 6chars (:port) + \0
|
||||
} ip_str_t;
|
||||
|
||||
ip_str_t ip_to_str(const void *addr, int family);
|
||||
ip_str_t ip_to_str(const void *addr, int family);// big endian addr
|
||||
ip_str_t sockaddr_storage_to_str(const struct sockaddr_storage *addr);// автоопределение v4 или v6
|
||||
|
||||
// hex dump в лог
|
||||
void log_dump(const char* prefix, const uint8_t* data, size_t len);
|
||||
void log_dump(int level, int category, const char* prefix, const uint8_t* data, size_t len);
|
||||
|
||||
/* Check if debug output should be shown for given level and category */
|
||||
int debug_should_output(debug_level_t level, debug_category_t category_idx);
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* getmyip.c - Определение локального IP-адреса для исходящих соединений
|
||||
*
|
||||
* Использует временный UDP-сокет с connect() для определения
|
||||
* локального IP, который ОС выберет для заданного маршрута.
|
||||
*/
|
||||
|
||||
#include "getmyip.h"
|
||||
#include "socket_compat.h"
|
||||
#include "debug_config.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
|
||||
const struct sockaddr_storage *remote_addr,
|
||||
struct sockaddr_storage *result)
|
||||
{
|
||||
if (!local_addr || !remote_addr || !result) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: NULL argument");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int family = local_addr->ss_family;
|
||||
if (family != AF_INET && family != AF_INET6) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: unsupported family %d", family);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (remote_addr->ss_family != family) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: family mismatch");
|
||||
return -1;
|
||||
}
|
||||
|
||||
socket_t sock = socket_create_udp(family);
|
||||
if (sock == SOCKET_INVALID) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: socket_create_udp failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Bind на local_addr, но порт ставим 0 чтобы не конфликтовать с основным сокетом
|
||||
struct sockaddr_storage bind_addr;
|
||||
memcpy(&bind_addr, local_addr, sizeof(bind_addr));
|
||||
if (family == AF_INET) {
|
||||
((struct sockaddr_in *)&bind_addr)->sin_port = 0;
|
||||
} else {
|
||||
((struct sockaddr_in6 *)&bind_addr)->sin6_port = 0;
|
||||
}
|
||||
|
||||
socklen_t bind_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
|
||||
if (bind(sock, (struct sockaddr *)&bind_addr, bind_len) != 0) {
|
||||
int err = socket_get_error();
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: bind failed: %s", socket_strerror(err));
|
||||
socket_close_wrapper(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Connect к remote_addr — ядро вычислит маршрут и выберет source IP
|
||||
socklen_t remote_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
|
||||
if (connect(sock, (struct sockaddr *)remote_addr, remote_len) != 0) {
|
||||
int err = socket_get_error();
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: connect failed: %s", socket_strerror(err));
|
||||
socket_close_wrapper(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Получаем выбранный локальный адрес
|
||||
struct sockaddr_storage local;
|
||||
socklen_t local_len = sizeof(local);
|
||||
if (getsockname(sock, (struct sockaddr *)&local, &local_len) != 0) {
|
||||
int err = socket_get_error();
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONNECTION, "get_outgoing_local_ip: getsockname failed: %s", socket_strerror(err));
|
||||
socket_close_wrapper(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
socket_close_wrapper(sock);
|
||||
|
||||
// Копируем только IP (без порта)
|
||||
memset(result, 0, sizeof(*result));
|
||||
if (family == AF_INET) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)result;
|
||||
struct sockaddr_in *src = (struct sockaddr_in *)&local;
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_addr = src->sin_addr;
|
||||
sin->sin_port = 0;
|
||||
} else {
|
||||
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)result;
|
||||
struct sockaddr_in6 *src6 = (struct sockaddr_in6 *)&local;
|
||||
sin6->sin6_family = AF_INET6;
|
||||
sin6->sin6_addr = src6->sin6_addr;
|
||||
sin6->sin6_port = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* getmyip.h - Определение локального IP-адреса для исходящих соединений
|
||||
*
|
||||
* Определяет локальный IP, который ОС выберет для отправки
|
||||
* на заданный удаленный адрес через заданный локальный адрес.
|
||||
*/
|
||||
|
||||
#ifndef GETMYIP_H
|
||||
#define GETMYIP_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Определяет локальный IP-адрес, который ОС выберет для отправки
|
||||
* на заданный удаленный адрес через заданный локальный адрес.
|
||||
*
|
||||
* Создает временный UDP-сокет, выполняет bind на local_addr,
|
||||
* connect к remote_addr и получает выбранный локальный IP через getsockname.
|
||||
*
|
||||
* @param local_addr Локальный адрес (0.0.0.0 / :: для автоопределения)
|
||||
* @param remote_addr Удаленный адрес (для определения маршрута)
|
||||
* @param result Заполняемый локальный IP (порт = 0)
|
||||
* @return 0 при успехе, -1 при ошибке
|
||||
*/
|
||||
int get_outgoing_local_ip(const struct sockaddr_storage *local_addr,
|
||||
const struct sockaddr_storage *remote_addr,
|
||||
struct sockaddr_storage *result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
mtest
|
||||
mtest[23456]
|
||||
testdb
|
||||
mdb_copy
|
||||
mdb_stat
|
||||
mdb_dump
|
||||
mdb_load
|
||||
*.lo
|
||||
*.[ao]
|
||||
*.so
|
||||
*.exe
|
||||
*[~#]
|
||||
*.bak
|
||||
*.orig
|
||||
*.rej
|
||||
*.gcov
|
||||
*.gcda
|
||||
*.gcno
|
||||
core
|
||||
core.*
|
||||
valgrind.*
|
||||
man/
|
||||
html/
|
||||
@@ -0,0 +1,317 @@
|
||||
LMDB 0.9 Change Log
|
||||
|
||||
LMDB 0.9.36 Engineering
|
||||
ITS#7772 - limit sub-page growth when re-adding existing data
|
||||
ITS#8335 - more input validation for put(MDB_MULTIPLE)
|
||||
ITS#8386 - be a bit more precise that mdb_get retrieves data in intro.doc
|
||||
ITS#8590 - suppress SIGPIPE on Mac OSX for mdb_copy/compact
|
||||
ITS#8739 - don't use fdatasync on FreeBSD 11.0 and older
|
||||
ITS#8824 - mdb_dump: cleanup check for MDB_SUCCESS
|
||||
ITS#9388 - fix mdb_stat page counts for dupsort DBs
|
||||
ITS#10054 - fix write of 2GB values
|
||||
ITS#10108 - mdb_dump: fix comment
|
||||
ITS#10454 - fix metapage sync on Mac OSX
|
||||
ITS#10504 - silence linker waarning on Mac OSX
|
||||
ITS#10522 - reject 0-length values in DUPSORT DBs
|
||||
|
||||
LMDB 0.9.35 Release (2026/01/27)
|
||||
ITS#10434 - Fix typo affecting Mac OSX
|
||||
|
||||
LMDB 0.9.34 Release (2026/01/26)
|
||||
ITS#9564 - fix race condition freeing spilled pages at end of transaction
|
||||
ITS#10222 - Update mdb_dump(1) and mdb_load(1) man pages for append (-a) option
|
||||
ITS#10275 - mdb_load: add -Q option to use NOSYNC
|
||||
ITS#10296 - fix fdatasync on MacOS
|
||||
ITS#10342 - fix memleak in mdb_txn_begin for nested txns
|
||||
ITS#10346 - fix mdb_env_copy2 with values > (2GB-16)
|
||||
ITS#10355 - fix mplay build on musl
|
||||
ITS#10396 - fix mdb_cursor_del0 with multiple DUPSORT cursors
|
||||
ITS#10419 - add support for NetBSD
|
||||
ITS#10420 - add support for Haiku
|
||||
ITS#10421 - mdb_load: check for malicious input
|
||||
|
||||
LMDB 0.9.33 Release (2024/05/21)
|
||||
ITS#9037 mdb_page_search: fix error code when DBI record is missing
|
||||
ITS#10198 For win32, stop passing ignored parameter
|
||||
ITS#10212 Fix meta page usage by read only tools
|
||||
|
||||
LMDB 0.9.32 Release (2024/01/29)
|
||||
ITS#9378 - Add ability to replay log and replay log tool
|
||||
ITS#10095 - partial revert of ITS#9278. The patch was incorrect and introduced numerous race conditions.
|
||||
ITS#10125 - mdb_load: fix cursor reinit in Append mode
|
||||
ITS#10137 - Allow users to define MDB_IDL_LOGN
|
||||
|
||||
LMDB 0.9.31 Release (2023/07/10)
|
||||
ITS#8447 - Fix cursor_put(MDB_CURRENT) on DUPSORT DB with different sized data
|
||||
|
||||
LMDB 0.9.30 Release (2023/02/08)
|
||||
ITS#9806 - LMDB page_split: key threshold depends on page size
|
||||
ITS#9916 - avoid gcc optimization bug on sparc64 linux
|
||||
ITS#9919 - Mark infrequently used functions as cold
|
||||
ITS#9723 - clear C_EOF on cursor with MDB_FIRST_DUP
|
||||
ITS#9030 - Use sys/cachectl.h rather than asm/cachectl.h on mips
|
||||
|
||||
LMDB 0.9.29 Release (2021/03/16)
|
||||
ITS#9461 refix ITS#9376
|
||||
ITS#9500 fix regression from ITS#8662
|
||||
|
||||
LMDB 0.9.28 Release (2021/02/04)
|
||||
ITS#8662 add -a append option to mdb_load
|
||||
|
||||
LMDB 0.9.27 Release (2020/10/26)
|
||||
ITS#9376 fix repeated DUPSORT cursor deletes
|
||||
|
||||
LMDB 0.9.26 Release (2020/08/11)
|
||||
ITS#9278 fix robust mutex cleanup for FreeBSD
|
||||
|
||||
LMDB 0.9.25 Release (2020/01/30)
|
||||
ITS#9068 fix mdb_dump/load backslashes in printable content
|
||||
ITS#9118 add MAP_NOSYNC for FreeBSD
|
||||
ITS#9155 free mt_spill_pgs in non-nested txn on end
|
||||
|
||||
LMDB 0.9.24 Release (2019/07/24)
|
||||
ITS#8969 Tweak mdb_page_split
|
||||
ITS#8975 WIN32 fix writemap set_mapsize crash
|
||||
ITS#9007 Fix loose pages in WRITEMAP
|
||||
|
||||
LMDB 0.9.23 Release (2018/12/19)
|
||||
ITS#8756 Fix loose pages in dirty list
|
||||
ITS#8831 Fix mdb_load flag init
|
||||
ITS#8844 Fix mdb_env_close in forked process
|
||||
Documentation
|
||||
ITS#8857 mdb_cursor_del doesn't invalidate cursor
|
||||
ITS#8908 GET_MULTIPLE etc don't change passed in key
|
||||
|
||||
LMDB 0.9.22 Release (2018/03/22)
|
||||
Fix MDB_DUPSORT alignment bug (ITS#8819)
|
||||
Fix regression with new db from 0.9.19 (ITS#8760)
|
||||
Fix liblmdb to build on Solaris (ITS#8612)
|
||||
Fix delete behavior with DUPSORT DB (ITS#8622)
|
||||
Fix mdb_cursor_get/mdb_cursor_del behavior (ITS#8722)
|
||||
|
||||
LMDB 0.9.21 Release (2017/06/01)
|
||||
Fix xcursor after cursor_del (ITS#8622)
|
||||
|
||||
LMDB 0.9.20 (Withdrawn)
|
||||
Fix mdb_load with escaped plaintext (ITS#8558)
|
||||
Fix mdb_cursor_last / mdb_put interaction (ITS#8557)
|
||||
|
||||
LMDB 0.9.19 Release (2016/12/28)
|
||||
Fix mdb_env_cwalk cursor init (ITS#8424)
|
||||
Fix robust mutexes on Solaris 10/11 (ITS#8339)
|
||||
Tweak Win32 error message buffer
|
||||
Fix MDB_GET_BOTH on non-dup record (ITS#8393)
|
||||
Optimize mdb_drop
|
||||
Fix xcursors after mdb_cursor_del (ITS#8406)
|
||||
Fix MDB_NEXT_DUP after mdb_cursor_del (ITS#8412)
|
||||
Fix mdb_cursor_put resetting C_EOF (ITS#8489)
|
||||
Fix mdb_env_copyfd2 to return EPIPE on SIGPIPE (ITS#8504)
|
||||
Fix mdb_env_copy with empty DB (ITS#8209)
|
||||
Fix behaviors with fork (ITS#8505)
|
||||
Fix mdb_dbi_open with mainDB cursors (ITS#8542)
|
||||
Fix robust mutexes on kFreeBSD (ITS#8554)
|
||||
Fix utf8_to_utf16 error checks (ITS#7992)
|
||||
Fix F_NOCACHE on MacOS, error is non-fatal (ITS#7682)
|
||||
Build
|
||||
Make shared lib suffix overridable (ITS#8481)
|
||||
Documentation
|
||||
Cleanup doxygen nits
|
||||
Note reserved vs actual mem/disk usage
|
||||
|
||||
|
||||
LMDB 0.9.18 Release (2016/02/05)
|
||||
Fix robust mutex detection on glibc 2.10-11 (ITS#8330)
|
||||
Fix page_search_root assert on FreeDB (ITS#8336)
|
||||
Fix MDB_APPENDDUP vs. rewrite(single item) (ITS#8334)
|
||||
Fix mdb_copy of large files on Windows
|
||||
Fix subcursor move after delete (ITS#8355)
|
||||
Fix mdb_midl_shirnk off-by-one (ITS#8363)
|
||||
Check for utf8_to_utf16 failures (ITS#7992)
|
||||
Catch strdup failure in mdb_dbi_open
|
||||
Build
|
||||
Additional makefile var tweaks (ITS#8169)
|
||||
Documentation
|
||||
Add Getting Started page
|
||||
Update WRITEMAP description
|
||||
|
||||
|
||||
LMDB 0.9.17 Release (2015/11/30)
|
||||
Fix ITS#7377 catch calloc failure
|
||||
Fix ITS#8237 regression from ITS#7589
|
||||
Fix ITS#8238 page_split for DUPFIXED pages
|
||||
Fix ITS#8221 MDB_PAGE_FULL on delete/rebalance
|
||||
Fix ITS#8258 rebalance/split assert
|
||||
Fix ITS#8263 cursor_put cursor tracking
|
||||
Fix ITS#8264 cursor_del cursor tracking
|
||||
Fix ITS#8310 cursor_del cursor tracking
|
||||
Fix ITS#8299 mdb_del cursor tracking
|
||||
Fix ITS#8300 mdb_del cursor tracking
|
||||
Fix ITS#8304 mdb_del cursor tracking
|
||||
Fix ITS#7771 fakepage cursor tracking
|
||||
Fix ITS#7789 ensure mapsize >= pages in use
|
||||
Fix ITS#7971 mdb_txn_renew0() new reader slots
|
||||
Fix ITS#7969 use __sync_synchronize on non-x86
|
||||
Fix ITS#8311 page_split from update_key
|
||||
Fix ITS#8312 loose pages in nested txn
|
||||
Fix ITS#8313 mdb_rebalance dummy cursor
|
||||
Fix ITS#8315 dirty_room in nested txn
|
||||
Fix ITS#8323 dirty_list in nested txn
|
||||
Fix ITS#8316 page_merge cursor tracking
|
||||
Fix ITS#8321 cursor tracking
|
||||
Fix ITS#8319 mdb_load error messages
|
||||
Fix ITS#8320 mdb_load plaintext input
|
||||
Added mdb_txn_id() (ITS#7994)
|
||||
Added robust mutex support
|
||||
Miscellaneous cleanup/simplification
|
||||
Build
|
||||
Create install dirs if needed (ITS#8256)
|
||||
Fix ThreadProc decl on Win32/MSVC (ITS#8270)
|
||||
Added ssize_t typedef for MSVC (ITS#8067)
|
||||
Use ANSI apis on Windows (ITS#8069)
|
||||
Use O_SYNC if O_DSYNC,MDB_DSYNC are not defined (ITS#7209)
|
||||
Allow passing AR to make (ITS#8168)
|
||||
Allow passing mandir to make install (ITS#8169)
|
||||
|
||||
LMDB 0.9.16 Release (2015/08/14)
|
||||
Fix cursor EOF bug (ITS#8190)
|
||||
Fix handling of subDB records (ITS#8181)
|
||||
Fix mdb_midl_shrink() usage (ITS#8200)
|
||||
|
||||
LMDB 0.9.15 Release (2015/06/19)
|
||||
Fix txn init (ITS#7961,#7987)
|
||||
Fix MDB_PREV_DUP (ITS#7955,#7671)
|
||||
Fix compact of empty env (ITS#7956)
|
||||
Fix mdb_copy file mode
|
||||
Fix mdb_env_close() after failed mdb_env_open()
|
||||
Fix mdb_rebalance collapsing root (ITS#8062)
|
||||
Fix mdb_load with large values (ITS#8066)
|
||||
Fix to retry writes on EINTR (ITS#8106)
|
||||
Fix mdb_cursor_del on empty DB (ITS#8109)
|
||||
Fix MDB_INTEGERDUP key compare (ITS#8117)
|
||||
Fix error handling (ITS#7959,#8157,etc.)
|
||||
Fix race conditions (ITS#7969,7970)
|
||||
Added workaround for fdatasync bug in ext3fs
|
||||
Build
|
||||
Don't use -fPIC for static lib
|
||||
Update .gitignore (ITS#7952,#7953)
|
||||
Cleanup for "make test" (ITS#7841), "make clean", mtest*.c
|
||||
Misc. Android/Windows cleanup
|
||||
Documentation
|
||||
Fix MDB_APPEND doc
|
||||
Fix MDB_MAXKEYSIZE doc (ITS#8156)
|
||||
Fix mdb_cursor_put,mdb_cursor_del EACCES description
|
||||
Fix mdb_env_sync(MDB_RDONLY env) doc (ITS#8021)
|
||||
Clarify MDB_WRITEMAP doc (ITS#8021)
|
||||
Clarify mdb_env_open doc
|
||||
Clarify mdb_dbi_open doc
|
||||
|
||||
LMDB 0.9.14 Release (2014/09/20)
|
||||
Fix to support 64K page size (ITS#7713)
|
||||
Fix to persist decreased as well as increased mapsizes (ITS#7789)
|
||||
Fix cursor bug when deleting last node of a DUPSORT key
|
||||
Fix mdb_env_info to return FIXEDMAP address
|
||||
Fix ambiguous error code from writing to closed DBI (ITS#7825)
|
||||
Fix mdb_copy copying past end of file (ITS#7886)
|
||||
Fix cursor bugs from page_merge/rebalance
|
||||
Fix to dirty fewer pages in deletes (mdb_page_loose())
|
||||
Fix mdb_dbi_open creating subDBs (ITS#7917)
|
||||
Fix mdb_cursor_get(_DUP) with single value (ITS#7913)
|
||||
Fix Windows compat issues in mtests (ITS#7879)
|
||||
Add compacting variant of mdb_copy
|
||||
Add BigEndian integer key compare code
|
||||
Add mdb_dump/mdb_load utilities
|
||||
|
||||
LMDB 0.9.13 Release (2014/06/18)
|
||||
Fix mdb_page_alloc unlimited overflow page search
|
||||
Documentation
|
||||
Re-fix MDB_CURRENT doc (ITS#7793)
|
||||
Fix MDB_GET_MULTIPLE/MDB_NEXT_MULTIPLE doc
|
||||
|
||||
LMDB 0.9.12 Release (2014/06/13)
|
||||
Fix MDB_GET_BOTH regression (ITS#7875,#7681)
|
||||
Fix MDB_MULTIPLE writing multiple keys (ITS#7834)
|
||||
Fix mdb_rebalance (ITS#7829)
|
||||
Fix mdb_page_split (ITS#7815)
|
||||
Fix md_entries count (ITS#7861,#7828,#7793)
|
||||
Fix MDB_CURRENT (ITS#7793)
|
||||
Fix possible crash on Windows DLL detach
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
mdb_cursor_put: cursor moves on error (ITS#7771)
|
||||
|
||||
|
||||
LMDB 0.9.11 Release (2014/01/15)
|
||||
Add mdb_env_set_assert() (ITS#7775)
|
||||
Fix: invalidate txn on page allocation errors (ITS#7377)
|
||||
Fix xcursor tracking in mdb_cursor_del0() (ITS#7771)
|
||||
Fix corruption from deletes (ITS#7756)
|
||||
Fix Windows/MSVC build issues
|
||||
Raise safe limit of max MDB_MAXKEYSIZE
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
Remove spurious note about non-overlapping flags (ITS#7665)
|
||||
|
||||
LMDB 0.9.10 Release (2013/11/12)
|
||||
Add MDB_NOMEMINIT option
|
||||
Fix mdb_page_split() again (ITS#7589)
|
||||
Fix MDB_NORDAHEAD definition (ITS#7734)
|
||||
Fix mdb_cursor_del() positioning (ITS#7733)
|
||||
Partial fix for larger page sizes (ITS#7713)
|
||||
Fix Windows64/MSVC build issues
|
||||
|
||||
LMDB 0.9.9 Release (2013/10/24)
|
||||
Add mdb_env_get_fd()
|
||||
Add MDB_NORDAHEAD option
|
||||
Add MDB_NOLOCK option
|
||||
Avoid wasting space in mdb_page_split() (ITS#7589)
|
||||
Fix mdb_page_merge() cursor fixup (ITS#7722)
|
||||
Fix mdb_cursor_del() on last delete (ITS#7718)
|
||||
Fix adding WRITEMAP on existing env (ITS#7715)
|
||||
Fix nested txns (ITS#7515)
|
||||
Fix mdb_env_copy() O_DIRECT bug (ITS#7682)
|
||||
Fix mdb_cursor_set(SET_RANGE) return code (ITS#7681)
|
||||
Fix mdb_rebalance() cursor fixup (ITS#7701)
|
||||
Misc code cleanup
|
||||
Documentation
|
||||
Note that by default, readers need write access
|
||||
|
||||
|
||||
LMDB 0.9.8 Release (2013/09/09)
|
||||
Allow mdb_env_set_mapsize() on an open environment
|
||||
Fix mdb_dbi_flags() (ITS#7672)
|
||||
Fix mdb_page_unspill() in nested txns
|
||||
Fix mdb_cursor_get(CURRENT|NEXT) after a delete
|
||||
Fix mdb_cursor_get(DUP) to always return key (ITS#7671)
|
||||
Fix mdb_cursor_del() to always advance to next item (ITS#7670)
|
||||
Fix mdb_cursor_set(SET_RANGE) for tree with single page (ITS#7681)
|
||||
Fix mdb_env_copy() retry open if O_DIRECT fails (ITS#7682)
|
||||
Tweak mdb_page_spill() to be less aggressive
|
||||
Documentation
|
||||
Update caveats since mdb_reader_check() added in 0.9.7
|
||||
|
||||
LMDB 0.9.7 Release (2013/08/17)
|
||||
Don't leave stale lockfile on failed RDONLY open (ITS#7664)
|
||||
Fix mdb_page_split() ref beyond cursor depth
|
||||
Fix read txn data race (ITS#7635)
|
||||
Fix mdb_rebalance (ITS#7536, #7538)
|
||||
Fix mdb_drop() (ITS#7561)
|
||||
Misc DEBUG macro fixes
|
||||
Add MDB_NOTLS envflag
|
||||
Add mdb_env_copyfd()
|
||||
Add mdb_txn_env() (ITS#7660)
|
||||
Add mdb_dbi_flags() (ITS#7661)
|
||||
Add mdb_env_get_maxkeysize()
|
||||
Add mdb_env_reader_list()/mdb_env_reader_check()
|
||||
Add mdb_page_spill/unspill, remove hard txn size limit
|
||||
Use shorter names for semaphores (ITS#7615)
|
||||
Build
|
||||
Fix install target (ITS#7656)
|
||||
Documentation
|
||||
Misc updates for cursors, DB handles, data lifetime
|
||||
|
||||
LMDB 0.9.6 Release (2013/02/25)
|
||||
Many fixes/enhancements
|
||||
|
||||
LMDB 0.9.5 Release (2012/11/30)
|
||||
Renamed from libmdb to liblmdb
|
||||
Many fixes/enhancements
|
||||
@@ -0,0 +1,20 @@
|
||||
Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted only as authorized by the OpenLDAP
|
||||
Public License.
|
||||
|
||||
A copy of this license is available in the file LICENSE in the
|
||||
top-level directory of the distribution or, alternatively, at
|
||||
<http://www.OpenLDAP.org/license.html>.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
|
||||
Individual files and/or contributed packages may be copyright by
|
||||
other parties and/or subject to additional restrictions.
|
||||
|
||||
This work also contains materials derived from public sources.
|
||||
|
||||
Additional information about OpenLDAP can be obtained at
|
||||
<http://www.openldap.org/>.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,47 @@
|
||||
The OpenLDAP Public License
|
||||
Version 2.8, 17 August 2003
|
||||
|
||||
Redistribution and use of this software and associated documentation
|
||||
("Software"), with or without modification, are permitted provided
|
||||
that the following conditions are met:
|
||||
|
||||
1. Redistributions in source form must retain copyright statements
|
||||
and notices,
|
||||
|
||||
2. Redistributions in binary form must reproduce applicable copyright
|
||||
statements and notices, this list of conditions, and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution, and
|
||||
|
||||
3. Redistributions must contain a verbatim copy of this document.
|
||||
|
||||
The OpenLDAP Foundation may revise this license from time to time.
|
||||
Each revision is distinguished by a version number. You may use
|
||||
this Software under terms of this license revision or under the
|
||||
terms of any subsequent revision of the license.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
|
||||
CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
|
||||
OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The names of the authors and copyright holders must not be used in
|
||||
advertising or otherwise to promote the sale, use or other dealing
|
||||
in this Software without specific, written prior permission. Title
|
||||
to copyright in this Software shall at all times remain with copyright
|
||||
holders.
|
||||
|
||||
OpenLDAP is a registered trademark of the OpenLDAP Foundation.
|
||||
|
||||
Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
|
||||
California, USA. All Rights Reserved. Permission to copy and
|
||||
distribute verbatim copies of this document is granted.
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright 2015-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
/** @page starting Getting Started
|
||||
|
||||
LMDB is compact, fast, powerful, and robust and implements a simplified
|
||||
variant of the BerkeleyDB (BDB) API. (BDB is also very powerful, and verbosely
|
||||
documented in its own right.) After reading this page, the main
|
||||
\ref mdb documentation should make sense. Thanks to Bert Hubert
|
||||
for creating the
|
||||
<a href="https://github.com/ahupowerdns/ahutils/blob/master/lmdb-semantics.md">
|
||||
initial version</a> of this writeup.
|
||||
|
||||
Everything starts with an environment, created by #mdb_env_create().
|
||||
Once created, this environment must also be opened with #mdb_env_open().
|
||||
|
||||
#mdb_env_open() gets passed a name which is interpreted as a directory
|
||||
path. Note that this directory must exist already, it is not created
|
||||
for you. Within that directory, a lock file and a storage file will be
|
||||
generated. If you don't want to use a directory, you can pass the
|
||||
#MDB_NOSUBDIR option, in which case the path you provided is used
|
||||
directly as the data file, and another file with a "-lock" suffix
|
||||
added will be used for the lock file.
|
||||
|
||||
Once the environment is open, a transaction can be created within it
|
||||
using #mdb_txn_begin(). Transactions may be read-write or read-only,
|
||||
and read-write transactions may be nested. A transaction must only
|
||||
be used by one thread at a time. Transactions are always required,
|
||||
even for read-only access. The transaction provides a consistent
|
||||
view of the data.
|
||||
|
||||
Once a transaction has been created, a database can be opened within it
|
||||
using #mdb_dbi_open(). If only one database will ever be used in the
|
||||
environment, a NULL can be passed as the database name. For named
|
||||
databases, the #MDB_CREATE flag must be used to create the database
|
||||
if it doesn't already exist. Also, #mdb_env_set_maxdbs() must be
|
||||
called after #mdb_env_create() and before #mdb_env_open() to set the
|
||||
maximum number of named databases you want to support.
|
||||
|
||||
Note: a single transaction can open multiple databases. Generally
|
||||
databases should only be opened once, by the first transaction in
|
||||
the process. After the first transaction completes, the database
|
||||
handles can freely be used by all subsequent transactions.
|
||||
|
||||
Within a transaction, #mdb_get() can retrieve and #mdb_put() can store single
|
||||
key/value pairs if that is all you need to do (but see \ref Cursors
|
||||
below if you want to do more).
|
||||
|
||||
A key/value pair is expressed as two #MDB_val structures. This struct
|
||||
has two fields, \c mv_size and \c mv_data. The data is a \c void pointer to
|
||||
an array of \c mv_size bytes.
|
||||
|
||||
Because LMDB is very efficient (and usually zero-copy), the data returned
|
||||
in an #MDB_val structure may be memory-mapped straight from disk. In
|
||||
other words <b>look but do not touch</b> (or free() for that matter).
|
||||
Once a transaction is closed, the values can no longer be used, so
|
||||
make a copy if you need to keep them after that.
|
||||
|
||||
@section Cursors Cursors
|
||||
|
||||
To do more powerful things, we must use a cursor.
|
||||
|
||||
Within the transaction, a cursor can be created with #mdb_cursor_open().
|
||||
With this cursor we can store/retrieve/delete (multiple) values using
|
||||
#mdb_cursor_get(), #mdb_cursor_put(), and #mdb_cursor_del().
|
||||
|
||||
#mdb_cursor_get() positions itself depending on the cursor operation
|
||||
requested, and for some operations, on the supplied key. For example,
|
||||
to list all key/value pairs in a database, use operation #MDB_FIRST for
|
||||
the first call to #mdb_cursor_get(), and #MDB_NEXT on subsequent calls,
|
||||
until the end is hit.
|
||||
|
||||
To retrieve all keys starting from a specified key value, use #MDB_SET.
|
||||
For more cursor operations, see the \ref mdb docs.
|
||||
|
||||
When using #mdb_cursor_put(), either the function will position the
|
||||
cursor for you based on the \b key, or you can use operation
|
||||
#MDB_CURRENT to use the current position of the cursor. Note that
|
||||
\b key must then match the current position's key.
|
||||
|
||||
@subsection summary Summarizing the Opening
|
||||
|
||||
So we have a cursor in a transaction which opened a database in an
|
||||
environment which is opened from a filesystem after it was
|
||||
separately created.
|
||||
|
||||
Or, we create an environment, open it from a filesystem, create a
|
||||
transaction within it, open a database within that transaction,
|
||||
and create a cursor within all of the above.
|
||||
|
||||
Got it?
|
||||
|
||||
@section thrproc Threads and Processes
|
||||
|
||||
LMDB uses POSIX locks on files, and these locks have issues if one
|
||||
process opens a file multiple times. Because of this, do not
|
||||
#mdb_env_open() a file multiple times from a single process. Instead,
|
||||
share the LMDB environment that has opened the file across all threads.
|
||||
Otherwise, if a single process opens the same environment multiple times,
|
||||
closing it once will remove all the locks held on it, and the other
|
||||
instances will be vulnerable to corruption from other processes.
|
||||
|
||||
Also note that a transaction is tied to one thread by default using
|
||||
Thread Local Storage. If you want to pass read-only transactions across
|
||||
threads, you can use the #MDB_NOTLS option on the environment.
|
||||
|
||||
@section txns Transactions, Rollbacks, etc.
|
||||
|
||||
To actually get anything done, a transaction must be committed using
|
||||
#mdb_txn_commit(). Alternatively, all of a transaction's operations
|
||||
can be discarded using #mdb_txn_abort(). In a read-only transaction,
|
||||
any cursors will \b not automatically be freed. In a read-write
|
||||
transaction, all cursors will be freed and must not be used again.
|
||||
|
||||
For read-only transactions, obviously there is nothing to commit to
|
||||
storage. The transaction still must eventually be aborted to close
|
||||
any database handle(s) opened in it, or committed to keep the
|
||||
database handles around for reuse in new transactions.
|
||||
|
||||
In addition, as long as a transaction is open, a consistent view of
|
||||
the database is kept alive, which requires storage. A read-only
|
||||
transaction that no longer requires this consistent view should
|
||||
be terminated (committed or aborted) when the view is no longer
|
||||
needed (but see below for an optimization).
|
||||
|
||||
There can be multiple simultaneously active read-only transactions
|
||||
but only one that can write. Once a single read-write transaction
|
||||
is opened, all further attempts to begin one will block until the
|
||||
first one is committed or aborted. This has no effect on read-only
|
||||
transactions, however, and they may continue to be opened at any time.
|
||||
|
||||
@section dupkeys Duplicate Keys
|
||||
|
||||
#mdb_get() and #mdb_put() respectively have no and only some support
|
||||
for multiple key/value pairs with identical keys. If there are multiple
|
||||
values for a key, #mdb_get() will only return the first value.
|
||||
|
||||
When multiple values for one key are required, pass the #MDB_DUPSORT
|
||||
flag to #mdb_dbi_open(). In an #MDB_DUPSORT database, by default
|
||||
#mdb_put() will not replace the value for a key if the key existed
|
||||
already. Instead it will add the new value to the key. In addition,
|
||||
#mdb_del() will pay attention to the value field too, allowing for
|
||||
specific values of a key to be deleted.
|
||||
|
||||
Finally, additional cursor operations become available for
|
||||
traversing through and retrieving duplicate values.
|
||||
|
||||
@section optim Some Optimization
|
||||
|
||||
If you frequently begin and abort read-only transactions, as an
|
||||
optimization, it is possible to only reset and renew a transaction.
|
||||
|
||||
#mdb_txn_reset() releases any old copies of data kept around for
|
||||
a read-only transaction. To reuse this reset transaction, call
|
||||
#mdb_txn_renew() on it. Any cursors in this transaction must also
|
||||
be renewed using #mdb_cursor_renew().
|
||||
|
||||
Note that #mdb_txn_reset() is similar to #mdb_txn_abort() and will
|
||||
close any databases you opened within the transaction.
|
||||
|
||||
To permanently free a transaction, reset or not, use #mdb_txn_abort().
|
||||
|
||||
@section cleanup Cleaning Up
|
||||
|
||||
For read-only transactions, any cursors created within it must
|
||||
be closed using #mdb_cursor_close().
|
||||
|
||||
It is very rarely necessary to close a database handle, and in
|
||||
general they should just be left open.
|
||||
|
||||
@section onward The Full API
|
||||
|
||||
The full \ref mdb documentation lists further details, like how to:
|
||||
|
||||
\li size a database (the default limits are intentionally small)
|
||||
\li drop and clean a database
|
||||
\li detect and report errors
|
||||
\li optimize (bulk) loading speed
|
||||
\li (temporarily) reduce robustness to gain even more speed
|
||||
\li gather statistics about the database
|
||||
\li define custom sort orders
|
||||
|
||||
*/
|
||||
+1608
File diff suppressed because it is too large.
Load diff
+10504
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,55 @@
|
||||
.TH MDB_COPY 1 "2014/07/01" "LMDB 0.9.14"
|
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_copy \- LMDB environment copy tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_copy
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-c ]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
.B srcpath
|
||||
[\c
|
||||
.BR dstpath ]
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_copy
|
||||
utility copies an LMDB environment. The environment can
|
||||
be copied regardless of whether it is currently in use.
|
||||
No lockfile is created, since it gets recreated at need.
|
||||
|
||||
If
|
||||
.I dstpath
|
||||
is specified it must be the path of an empty directory
|
||||
for storing the backup. Otherwise, the backup will be
|
||||
written to stdout.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-c
|
||||
Compact while copying. Only current data pages will be copied; freed
|
||||
or unused pages will be omitted from the copy. This option will
|
||||
slow down the backup process as it is more CPU-intensive.
|
||||
Currently it fails if the environment has suffered a page leak.
|
||||
.TP
|
||||
.BR \-n
|
||||
Open LDMB environment(s) which do not use subdirectories.
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH CAVEATS
|
||||
This utility can trigger significant file size growth if run
|
||||
in parallel with write transactions, because pages which they
|
||||
free during copying cannot be reused until the copy is done.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_stat (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,82 @@
|
||||
/* mdb_copy.c - memory-mapped database backup tool */
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#define MDB_STDOUT GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
#else
|
||||
#define MDB_STDOUT 1
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
static void
|
||||
sighandle(int sig)
|
||||
{
|
||||
}
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
MDB_env *env;
|
||||
const char *progname = argv[0], *act;
|
||||
unsigned flags = MDB_RDONLY;
|
||||
unsigned cpflags = 0;
|
||||
|
||||
for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) {
|
||||
if (argv[1][1] == 'n' && argv[1][2] == '\0')
|
||||
flags |= MDB_NOSUBDIR;
|
||||
else if (argv[1][1] == 'c' && argv[1][2] == '\0')
|
||||
cpflags |= MDB_CP_COMPACT;
|
||||
else if (argv[1][1] == 'V' && argv[1][2] == '\0') {
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
} else
|
||||
argc = 0;
|
||||
}
|
||||
|
||||
if (argc<2 || argc>3) {
|
||||
fprintf(stderr, "usage: %s [-V] [-c] [-n] srcpath [dstpath]\n", progname);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
#ifdef SIGPIPE
|
||||
signal(SIGPIPE, sighandle);
|
||||
#endif
|
||||
#ifdef SIGHUP
|
||||
signal(SIGHUP, sighandle);
|
||||
#endif
|
||||
signal(SIGINT, sighandle);
|
||||
signal(SIGTERM, sighandle);
|
||||
|
||||
act = "opening environment";
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc == MDB_SUCCESS) {
|
||||
rc = mdb_env_open(env, argv[1], flags, 0600);
|
||||
}
|
||||
if (rc == MDB_SUCCESS) {
|
||||
act = "copying";
|
||||
if (argc == 2)
|
||||
rc = mdb_env_copyfd2(env, MDB_STDOUT, cpflags);
|
||||
else
|
||||
rc = mdb_env_copy2(env, argv[2], cpflags);
|
||||
}
|
||||
if (rc)
|
||||
fprintf(stderr, "%s: %s failed, error %d (%s)\n",
|
||||
progname, act, rc, mdb_strerror(rc));
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
.TH MDB_DUMP 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_dump \- LMDB environment export tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_dump
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BI \-f \ file\fR]
|
||||
[\c
|
||||
.BR \-l ]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BR \-p ]
|
||||
[\c
|
||||
.BR \-a \ |
|
||||
.BI \-s \ subdb\fR]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_dump
|
||||
utility reads a database and writes its contents to the
|
||||
standard output using a portable flat-text format
|
||||
understood by the
|
||||
.BR mdb_load (1)
|
||||
utility.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-f \ file
|
||||
Write to the specified file instead of to the standard output.
|
||||
.TP
|
||||
.BR \-l
|
||||
List the databases stored in the environment. Just the
|
||||
names will be listed, no data will be output.
|
||||
.TP
|
||||
.BR \-n
|
||||
Dump an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-p
|
||||
If characters in either the key or data items are printing characters (as
|
||||
defined by isprint(3)), output them directly. This option permits users to
|
||||
use standard text editors and tools to modify the contents of databases.
|
||||
|
||||
Note: different systems may have different notions about what characters
|
||||
are considered printing characters, and databases dumped in this manner may
|
||||
be less portable to external systems.
|
||||
.TP
|
||||
.BR \-a
|
||||
Dump all of the subdatabases in the environment.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Dump a specific subdatabase. If no database is specified, only the main database is dumped.
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
|
||||
Dumping databases that use user-defined comparison functions will output
|
||||
records with the ordering imposed by those comparison functions. If
|
||||
.B mdb_load
|
||||
is invoked without including the
|
||||
.B -a
|
||||
option when reloading those records, the new databases will likely be
|
||||
damaged beyond repair, permitting neither record storage nor retrieval.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_load (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,319 @@
|
||||
/* mdb_dump.c - memory-mapped database dump tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
#define PRINT 1
|
||||
static int mode;
|
||||
|
||||
typedef struct flagbit {
|
||||
int bit;
|
||||
char *name;
|
||||
} flagbit;
|
||||
|
||||
flagbit dbflags[] = {
|
||||
{ MDB_REVERSEKEY, "reversekey" },
|
||||
{ MDB_DUPSORT, "dupsort" },
|
||||
{ MDB_INTEGERKEY, "integerkey" },
|
||||
{ MDB_DUPFIXED, "dupfixed" },
|
||||
{ MDB_INTEGERDUP, "integerdup" },
|
||||
{ MDB_REVERSEDUP, "reversedup" },
|
||||
{ 0, NULL }
|
||||
};
|
||||
|
||||
static volatile sig_atomic_t gotsig;
|
||||
|
||||
static void dumpsig( int sig )
|
||||
{
|
||||
gotsig=1;
|
||||
}
|
||||
|
||||
static const char hexc[] = "0123456789abcdef";
|
||||
|
||||
static void hex(unsigned char c)
|
||||
{
|
||||
putchar(hexc[c >> 4]);
|
||||
putchar(hexc[c & 0xf]);
|
||||
}
|
||||
|
||||
static void text(MDB_val *v)
|
||||
{
|
||||
unsigned char *c, *end;
|
||||
|
||||
putchar(' ');
|
||||
c = v->mv_data;
|
||||
end = c + v->mv_size;
|
||||
while (c < end) {
|
||||
if (isprint(*c)) {
|
||||
if (*c == '\\')
|
||||
putchar('\\');
|
||||
putchar(*c);
|
||||
} else {
|
||||
putchar('\\');
|
||||
hex(*c);
|
||||
}
|
||||
c++;
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
static void byte(MDB_val *v)
|
||||
{
|
||||
unsigned char *c, *end;
|
||||
|
||||
putchar(' ');
|
||||
c = v->mv_data;
|
||||
end = c + v->mv_size;
|
||||
while (c < end) {
|
||||
hex(*c++);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
/* Dump in BDB-compatible format */
|
||||
static int dumpit(MDB_txn *txn, MDB_dbi dbi, char *name)
|
||||
{
|
||||
MDB_cursor *mc;
|
||||
MDB_stat ms;
|
||||
MDB_val key, data;
|
||||
MDB_envinfo info;
|
||||
unsigned int flags;
|
||||
int rc, i;
|
||||
|
||||
rc = mdb_dbi_flags(txn, dbi, &flags);
|
||||
if (rc) return rc;
|
||||
|
||||
rc = mdb_stat(txn, dbi, &ms);
|
||||
if (rc) return rc;
|
||||
|
||||
rc = mdb_env_info(mdb_txn_env(txn), &info);
|
||||
if (rc) return rc;
|
||||
|
||||
printf("VERSION=3\n");
|
||||
printf("format=%s\n", mode & PRINT ? "print" : "bytevalue");
|
||||
if (name)
|
||||
printf("database=%s\n", name);
|
||||
printf("type=btree\n");
|
||||
printf("mapsize=%" Z "u\n", info.me_mapsize);
|
||||
if (info.me_mapaddr)
|
||||
printf("mapaddr=%p\n", info.me_mapaddr);
|
||||
printf("maxreaders=%u\n", info.me_maxreaders);
|
||||
|
||||
if (flags & MDB_DUPSORT)
|
||||
printf("duplicates=1\n");
|
||||
|
||||
for (i=0; dbflags[i].bit; i++)
|
||||
if (flags & dbflags[i].bit)
|
||||
printf("%s=1\n", dbflags[i].name);
|
||||
|
||||
printf("db_pagesize=%d\n", ms.ms_psize);
|
||||
printf("HEADER=END\n");
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) return rc;
|
||||
|
||||
while ((rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT)) == MDB_SUCCESS) {
|
||||
if (gotsig) {
|
||||
rc = EINTR;
|
||||
break;
|
||||
}
|
||||
if (mode & PRINT) {
|
||||
text(&key);
|
||||
text(&data);
|
||||
} else {
|
||||
byte(&key);
|
||||
byte(&data);
|
||||
}
|
||||
}
|
||||
printf("DATA=END\n");
|
||||
if (rc == MDB_NOTFOUND)
|
||||
rc = MDB_SUCCESS;
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void usage(char *prog)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
char *prog = argv[0];
|
||||
char *envname;
|
||||
char *subname = NULL;
|
||||
int alldbs = 0, envflags = 0, list = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
usage(prog);
|
||||
}
|
||||
|
||||
/* -a: dump all subDBs
|
||||
* -s: dump only the named subDB
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -p: use printable characters
|
||||
* -f: write to file instead of stdout
|
||||
* -V: print version and exit
|
||||
* (default) dump only the main DB
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'l':
|
||||
list = 1;
|
||||
/*FALLTHROUGH*/
|
||||
case 'a':
|
||||
if (subname)
|
||||
usage(prog);
|
||||
alldbs++;
|
||||
break;
|
||||
case 'f':
|
||||
if (freopen(optarg, "w", stdout) == NULL) {
|
||||
fprintf(stderr, "%s: %s: reopen: %s\n",
|
||||
prog, optarg, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 'p':
|
||||
mode |= PRINT;
|
||||
break;
|
||||
case 's':
|
||||
if (alldbs)
|
||||
usage(prog);
|
||||
subname = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(prog);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage(prog);
|
||||
|
||||
#ifdef SIGPIPE
|
||||
signal(SIGPIPE, dumpsig);
|
||||
#endif
|
||||
#ifdef SIGHUP
|
||||
signal(SIGHUP, dumpsig);
|
||||
#endif
|
||||
signal(SIGINT, dumpsig);
|
||||
signal(SIGTERM, dumpsig);
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (alldbs || subname) {
|
||||
mdb_env_set_maxdbs(env, 2);
|
||||
}
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
if (alldbs) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key;
|
||||
int count = 0;
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
|
||||
char *str;
|
||||
MDB_dbi db2;
|
||||
if (memchr(key.mv_data, '\0', key.mv_size))
|
||||
continue;
|
||||
count++;
|
||||
str = malloc(key.mv_size+1);
|
||||
memcpy(str, key.mv_data, key.mv_size);
|
||||
str[key.mv_size] = '\0';
|
||||
rc = mdb_open(txn, str, 0, &db2);
|
||||
if (rc == MDB_SUCCESS) {
|
||||
if (list) {
|
||||
printf("%s\n", str);
|
||||
list++;
|
||||
} else {
|
||||
rc = dumpit(txn, db2, str);
|
||||
if (rc)
|
||||
break;
|
||||
}
|
||||
mdb_close(env, db2);
|
||||
}
|
||||
free(str);
|
||||
if (rc) continue;
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
if (!count) {
|
||||
fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, envname);
|
||||
rc = MDB_NOTFOUND;
|
||||
} else if (rc == MDB_NOTFOUND) {
|
||||
rc = MDB_SUCCESS;
|
||||
}
|
||||
} else {
|
||||
rc = dumpit(txn, dbi, subname);
|
||||
}
|
||||
if (rc && rc != MDB_NOTFOUND)
|
||||
fprintf(stderr, "%s: %s: %s\n", prog, envname, mdb_strerror(rc));
|
||||
|
||||
mdb_close(env, dbi);
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
.TH MDB_LOAD 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2014-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_load \- LMDB environment import tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_load
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-a ]
|
||||
[\c
|
||||
.BI \-f \ file\fR]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BI \-s \ subdb\fR]
|
||||
[\c
|
||||
.BR \-N ]
|
||||
[\c
|
||||
.BR \-Q ]
|
||||
[\c
|
||||
.BR \-T ]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_load
|
||||
utility reads from the standard input and loads it into the
|
||||
LMDB environment
|
||||
.BR envpath .
|
||||
|
||||
The input to
|
||||
.B mdb_load
|
||||
must be in the output format specified by the
|
||||
.BR mdb_dump (1)
|
||||
utility or as specified by the
|
||||
.B -T
|
||||
option below.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-a
|
||||
Append all records in the order they appear in the input. The input is assumed to already be
|
||||
in correctly sorted order and no sorting or checking for redundant values will be performed.
|
||||
This option must be used to reload data that was produced by running
|
||||
.B mdb_dump
|
||||
on a database that uses custom compare functions.
|
||||
.TP
|
||||
.BR \-f \ file
|
||||
Read from the specified file instead of from the standard input.
|
||||
.TP
|
||||
.BR \-n
|
||||
Load an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Load a specific subdatabase. If no database is specified, data is loaded into the main database.
|
||||
.TP
|
||||
.BR \-N
|
||||
Don't overwrite existing records when loading into an already existing database; just skip them.
|
||||
.TP
|
||||
.BR \-Q
|
||||
Quick mode, uses MDB_NOSYNC for faster loading. Forces sync with mdb_env_sync() before exiting.
|
||||
.TP
|
||||
.BR \-T
|
||||
Load data from simple text files. The input must be paired lines of text, where the first
|
||||
line of the pair is the key item, and the second line of the pair is its corresponding
|
||||
data item.
|
||||
|
||||
A simple escape mechanism, where newline and backslash (\\) characters are special, is
|
||||
applied to the text input. Newline characters are interpreted as record separators.
|
||||
Backslash characters in the text will be interpreted in one of two ways: If the backslash
|
||||
character precedes another backslash character, the pair will be interpreted as a literal
|
||||
backslash. If the backslash character precedes any other character, the two characters
|
||||
following the backslash will be interpreted as a hexadecimal specification of a single
|
||||
character; for example, \\0a is a newline character in the ASCII character set.
|
||||
|
||||
For this reason, any backslash or newline characters that naturally occur in the text
|
||||
input must be escaped to avoid misinterpretation by
|
||||
.BR mdb_load .
|
||||
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_dump (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,515 @@
|
||||
/* mdb_load.c - memory-mapped database load tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define PRINT 1
|
||||
#define NOHDR 2
|
||||
static int mode;
|
||||
|
||||
static char *subname = NULL;
|
||||
|
||||
static size_t lineno;
|
||||
static int version;
|
||||
|
||||
static int flags;
|
||||
|
||||
static char *prog;
|
||||
|
||||
static int Eof;
|
||||
|
||||
static MDB_envinfo info;
|
||||
|
||||
static MDB_val kbuf, dbuf;
|
||||
static MDB_val k0buf;
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
#define STRLENOF(s) (sizeof(s)-1)
|
||||
|
||||
typedef struct flagbit {
|
||||
int bit;
|
||||
char *name;
|
||||
int len;
|
||||
} flagbit;
|
||||
|
||||
#define S(s) s, STRLENOF(s)
|
||||
|
||||
flagbit dbflags[] = {
|
||||
{ MDB_REVERSEKEY, S("reversekey") },
|
||||
{ MDB_DUPSORT, S("dupsort") },
|
||||
{ MDB_INTEGERKEY, S("integerkey") },
|
||||
{ MDB_DUPFIXED, S("dupfixed") },
|
||||
{ MDB_INTEGERDUP, S("integerdup") },
|
||||
{ MDB_REVERSEDUP, S("reversedup") },
|
||||
{ 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static void readhdr(void)
|
||||
{
|
||||
char *ptr;
|
||||
|
||||
flags = 0;
|
||||
while (fgets(dbuf.mv_data, dbuf.mv_size, stdin) != NULL) {
|
||||
lineno++;
|
||||
if (!strncmp(dbuf.mv_data, "VERSION=", STRLENOF("VERSION="))) {
|
||||
version=atoi((char *)dbuf.mv_data+STRLENOF("VERSION="));
|
||||
if (version > 3) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported VERSION %d\n",
|
||||
prog, lineno, version);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "HEADER=END", STRLENOF("HEADER=END"))) {
|
||||
break;
|
||||
} else if (!strncmp(dbuf.mv_data, "format=", STRLENOF("format="))) {
|
||||
if (!strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "print", STRLENOF("print")))
|
||||
mode |= PRINT;
|
||||
else if (strncmp((char *)dbuf.mv_data+STRLENOF("FORMAT="), "bytevalue", STRLENOF("bytevalue"))) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported FORMAT %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("FORMAT="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "database=", STRLENOF("database="))) {
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
if (subname) free(subname);
|
||||
subname = strdup((char *)dbuf.mv_data+STRLENOF("database="));
|
||||
} else if (!strncmp(dbuf.mv_data, "type=", STRLENOF("type="))) {
|
||||
if (strncmp((char *)dbuf.mv_data+STRLENOF("type="), "btree", STRLENOF("btree"))) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unsupported type %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("type="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "mapaddr=", STRLENOF("mapaddr="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapaddr="), "%p", &info.me_mapaddr);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapaddr %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapaddr="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "mapsize=", STRLENOF("mapsize="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("mapsize="), "%" Z "u", &info.me_mapsize);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid mapsize %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("mapsize="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (!strncmp(dbuf.mv_data, "maxreaders=", STRLENOF("maxreaders="))) {
|
||||
int i;
|
||||
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
|
||||
if (ptr) *ptr = '\0';
|
||||
i = sscanf((char *)dbuf.mv_data+STRLENOF("maxreaders="), "%u", &info.me_maxreaders);
|
||||
if (i != 1) {
|
||||
fprintf(stderr, "%s: line %" Z "d: invalid maxreaders %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data+STRLENOF("maxreaders="));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
int i;
|
||||
for (i=0; dbflags[i].bit; i++) {
|
||||
if (!strncmp(dbuf.mv_data, dbflags[i].name, dbflags[i].len) &&
|
||||
((char *)dbuf.mv_data)[dbflags[i].len] == '=') {
|
||||
flags |= dbflags[i].bit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dbflags[i].bit) {
|
||||
ptr = memchr(dbuf.mv_data, '=', dbuf.mv_size);
|
||||
if (!ptr) {
|
||||
fprintf(stderr, "%s: line %" Z "d: unexpected format\n",
|
||||
prog, lineno);
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
*ptr = '\0';
|
||||
fprintf(stderr, "%s: line %" Z "d: unrecognized keyword ignored: %s\n",
|
||||
prog, lineno, (char *)dbuf.mv_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void badend(void)
|
||||
{
|
||||
fprintf(stderr, "%s: line %" Z "d: unexpected end of input\n",
|
||||
prog, lineno);
|
||||
}
|
||||
|
||||
static int unhex(unsigned char *c2)
|
||||
{
|
||||
int x, c;
|
||||
x = *c2++ & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c = x << 4;
|
||||
x = *c2 & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c |= x;
|
||||
return c;
|
||||
}
|
||||
|
||||
static int readline(MDB_val *out, MDB_val *buf)
|
||||
{
|
||||
unsigned char *c1, *c2, *end;
|
||||
size_t len, l2;
|
||||
int c;
|
||||
|
||||
if (!(mode & NOHDR)) {
|
||||
c = fgetc(stdin);
|
||||
if (c == EOF) {
|
||||
Eof = 1;
|
||||
return EOF;
|
||||
}
|
||||
if (c != ' ') {
|
||||
lineno++;
|
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
|
||||
badend:
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
if (c == 'D' && !strncmp(buf->mv_data, "ATA=END", STRLENOF("ATA=END")))
|
||||
return EOF;
|
||||
goto badend;
|
||||
}
|
||||
}
|
||||
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
|
||||
Eof = 1;
|
||||
return EOF;
|
||||
}
|
||||
lineno++;
|
||||
|
||||
c1 = buf->mv_data;
|
||||
len = strlen((char *)c1);
|
||||
if (!len) {
|
||||
/* This can only happen with an intentionally invalid input
|
||||
* with a NUL byte after the leading SPACE
|
||||
*/
|
||||
goto badend;
|
||||
}
|
||||
l2 = len;
|
||||
|
||||
/* Is buffer too short? */
|
||||
while (c1[len-1] != '\n') {
|
||||
buf->mv_data = realloc(buf->mv_data, buf->mv_size*2);
|
||||
if (!buf->mv_data) {
|
||||
Eof = 1;
|
||||
fprintf(stderr, "%s: line %" Z "d: out of memory, line too long\n",
|
||||
prog, lineno);
|
||||
return EOF;
|
||||
}
|
||||
c1 = buf->mv_data;
|
||||
c1 += l2;
|
||||
if (fgets((char *)c1, buf->mv_size+1, stdin) == NULL) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
buf->mv_size *= 2;
|
||||
len = strlen((char *)c1);
|
||||
l2 += len;
|
||||
}
|
||||
c1 = c2 = buf->mv_data;
|
||||
len = l2;
|
||||
c1[--len] = '\0';
|
||||
end = c1 + len;
|
||||
|
||||
if (mode & PRINT) {
|
||||
while (c2 < end) {
|
||||
if (*c2 == '\\') {
|
||||
if (c2[1] == '\\') {
|
||||
*c1++ = *c2;
|
||||
} else {
|
||||
if (c2+3 > end || !isxdigit(c2[1]) || !isxdigit(c2[2])) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
*c1++ = unhex(++c2);
|
||||
}
|
||||
c2 += 2;
|
||||
} else {
|
||||
/* copies are redundant when no escapes were used */
|
||||
*c1++ = *c2++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* odd length not allowed */
|
||||
if (len & 1) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
while (c2 < end) {
|
||||
if (!isxdigit(*c2) || !isxdigit(c2[1])) {
|
||||
Eof = 1;
|
||||
badend();
|
||||
return EOF;
|
||||
}
|
||||
*c1++ = unhex(c2);
|
||||
c2 += 2;
|
||||
}
|
||||
}
|
||||
c2 = out->mv_data = buf->mv_data;
|
||||
out->mv_size = c1 - c2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void usage(void)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-a] [-f input] [-n] [-s name] [-N] [-T] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static int greater(const MDB_val *a, const MDB_val *b)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_cursor *mc;
|
||||
MDB_dbi dbi;
|
||||
char *envname;
|
||||
int envflags = MDB_NOSYNC, putflags = 0;
|
||||
int dohdr = 0, append = 0;
|
||||
MDB_val prevk;
|
||||
|
||||
prog = argv[0];
|
||||
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
}
|
||||
|
||||
/* -a: append records in input order
|
||||
* -f: load file instead of stdin
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -s: load into named subDB
|
||||
* -N: use NOOVERWRITE on puts
|
||||
* -Q: quick mode using NOSYNC
|
||||
* -T: read plaintext
|
||||
* -V: print version and exit
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "af:ns:NQTV")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'a':
|
||||
append = 1;
|
||||
break;
|
||||
case 'f':
|
||||
if (freopen(optarg, "r", stdin) == NULL) {
|
||||
fprintf(stderr, "%s: %s: reopen: %s\n",
|
||||
prog, optarg, strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 's':
|
||||
subname = strdup(optarg);
|
||||
break;
|
||||
case 'N':
|
||||
putflags = MDB_NOOVERWRITE|MDB_NODUPDATA;
|
||||
break;
|
||||
case 'Q':
|
||||
envflags |= MDB_NOSYNC;
|
||||
break;
|
||||
case 'T':
|
||||
mode |= NOHDR | PRINT;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage();
|
||||
|
||||
dbuf.mv_size = 4096;
|
||||
dbuf.mv_data = malloc(dbuf.mv_size);
|
||||
|
||||
if (!(mode & NOHDR))
|
||||
readhdr();
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
mdb_env_set_maxdbs(env, 2);
|
||||
|
||||
if (info.me_maxreaders)
|
||||
mdb_env_set_maxreaders(env, info.me_maxreaders);
|
||||
|
||||
if (info.me_mapsize)
|
||||
mdb_env_set_mapsize(env, info.me_mapsize);
|
||||
|
||||
if (info.me_mapaddr)
|
||||
envflags |= MDB_FIXEDMAP;
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
kbuf.mv_size = mdb_env_get_maxkeysize(env) * 2 + 2;
|
||||
kbuf.mv_data = malloc(kbuf.mv_size * 2);
|
||||
k0buf.mv_size = kbuf.mv_size;
|
||||
k0buf.mv_data = (char *)kbuf.mv_data + kbuf.mv_size;
|
||||
prevk.mv_data = k0buf.mv_data;
|
||||
|
||||
while(!Eof) {
|
||||
MDB_val key, data;
|
||||
int batch = 0;
|
||||
flags = 0;
|
||||
int appflag;
|
||||
|
||||
if (!dohdr) {
|
||||
dohdr = 1;
|
||||
} else if (!(mode & NOHDR))
|
||||
readhdr();
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_dbi_open(txn, subname, flags|MDB_CREATE, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_dbi_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prevk.mv_size = 0;
|
||||
if (append) {
|
||||
mdb_set_compare(txn, dbi, greater);
|
||||
if (flags & MDB_DUPSORT)
|
||||
mdb_set_dupsort(txn, dbi, greater);
|
||||
}
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
while(1) {
|
||||
rc = readline(&key, &kbuf);
|
||||
if (rc) /* rc == EOF */
|
||||
break;
|
||||
|
||||
rc = readline(&data, &dbuf);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: failed to read key value\n", prog, lineno);
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
if (append) {
|
||||
appflag = MDB_APPEND;
|
||||
if (flags & MDB_DUPSORT) {
|
||||
if (prevk.mv_size == key.mv_size && !memcmp(prevk.mv_data, key.mv_data, key.mv_size))
|
||||
appflag = MDB_CURRENT|MDB_APPENDDUP;
|
||||
else {
|
||||
memcpy(prevk.mv_data, key.mv_data, key.mv_size);
|
||||
prevk.mv_size = key.mv_size;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
appflag = 0;
|
||||
}
|
||||
rc = mdb_cursor_put(mc, &key, &data, putflags|appflag);
|
||||
if (rc == MDB_KEYEXIST && putflags)
|
||||
continue;
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: mdb_cursor_put failed, error %d %s\n", prog, lineno, rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
batch++;
|
||||
if (batch == 100) {
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
|
||||
prog, lineno, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
rc = mdb_cursor_open(txn, dbi, &mc);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
if (append) {
|
||||
MDB_val k, d;
|
||||
mdb_cursor_get(mc, &k, &d, MDB_LAST);
|
||||
memcpy(prevk.mv_data, k.mv_data, k.mv_size);
|
||||
prevk.mv_size = k.mv_size;
|
||||
}
|
||||
batch = 0;
|
||||
}
|
||||
}
|
||||
rc = mdb_txn_commit(txn);
|
||||
txn = NULL;
|
||||
if (rc) {
|
||||
fprintf(stderr, "%s: line %" Z "d: txn_commit: %s\n",
|
||||
prog, lineno, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
if (envflags & MDB_NOSYNC) {
|
||||
rc = mdb_env_sync(env, 1);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_sync failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
}
|
||||
mdb_dbi_close(env, dbi);
|
||||
}
|
||||
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
.TH MDB_STAT 1 "2015/09/30" "LMDB 0.9.17"
|
||||
.\" Copyright 2012-2021 Howard Chu, Symas Corp. All Rights Reserved.
|
||||
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
|
||||
.SH NAME
|
||||
mdb_stat \- LMDB environment status tool
|
||||
.SH SYNOPSIS
|
||||
.B mdb_stat
|
||||
[\c
|
||||
.BR \-V ]
|
||||
[\c
|
||||
.BR \-e ]
|
||||
[\c
|
||||
.BR \-f [ f [ f ]]]
|
||||
[\c
|
||||
.BR \-n ]
|
||||
[\c
|
||||
.BR \-r [ r ]]
|
||||
[\c
|
||||
.BR \-a \ |
|
||||
.BI \-s \ subdb\fR]
|
||||
.BR \ envpath
|
||||
.SH DESCRIPTION
|
||||
The
|
||||
.B mdb_stat
|
||||
utility displays the status of an LMDB environment.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-V
|
||||
Write the library version number to the standard output, and exit.
|
||||
.TP
|
||||
.BR \-e
|
||||
Display information about the database environment.
|
||||
.TP
|
||||
.BR \-f
|
||||
Display information about the environment freelist.
|
||||
If \fB\-ff\fP is given, summarize each freelist entry.
|
||||
If \fB\-fff\fP is given, display the full list of page IDs in the freelist.
|
||||
.TP
|
||||
.BR \-n
|
||||
Display the status of an LMDB database which does not use subdirectories.
|
||||
.TP
|
||||
.BR \-r
|
||||
Display information about the environment reader table.
|
||||
Shows the process ID, thread ID, and transaction ID for each active
|
||||
reader slot. The process ID and transaction ID are in decimal, the
|
||||
thread ID is in hexadecimal. The transaction ID is displayed as "-"
|
||||
if the reader does not currently have a read transaction open.
|
||||
If \fB\-rr\fP is given, check for stale entries in the reader
|
||||
table and clear them. The reader table will be printed again
|
||||
after the check is performed.
|
||||
.TP
|
||||
.BR \-a
|
||||
Display the status of all of the subdatabases in the environment.
|
||||
.TP
|
||||
.BR \-s \ subdb
|
||||
Display the status of a specific subdatabase.
|
||||
.SH DIAGNOSTICS
|
||||
Exit status is zero if no errors occur.
|
||||
Errors result in a non-zero exit status and
|
||||
a diagnostic message being written to standard error.
|
||||
.SH "SEE ALSO"
|
||||
.BR mdb_copy (1)
|
||||
.SH AUTHOR
|
||||
Howard Chu of Symas Corporation <http://www.symas.com>
|
||||
@@ -0,0 +1,263 @@
|
||||
/* mdb_stat.c - memory-mapped database status tool */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define Z "I"
|
||||
#else
|
||||
#define Z "z"
|
||||
#endif
|
||||
|
||||
static void prstat(MDB_stat *ms)
|
||||
{
|
||||
#if 0
|
||||
printf(" Page size: %u\n", ms->ms_psize);
|
||||
#endif
|
||||
printf(" Tree depth: %u\n", ms->ms_depth);
|
||||
printf(" Branch pages: %"Z"u\n", ms->ms_branch_pages);
|
||||
printf(" Leaf pages: %"Z"u\n", ms->ms_leaf_pages);
|
||||
printf(" Overflow pages: %"Z"u\n", ms->ms_overflow_pages);
|
||||
printf(" Entries: %"Z"u\n", ms->ms_entries);
|
||||
}
|
||||
|
||||
static void usage(char *prog)
|
||||
{
|
||||
fprintf(stderr, "usage: %s [-V] [-n] [-e] [-r[r]] [-f[f[f]]] [-a|-s subdb] dbpath\n", prog);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int i, rc;
|
||||
MDB_env *env;
|
||||
MDB_txn *txn;
|
||||
MDB_dbi dbi;
|
||||
MDB_stat mst;
|
||||
MDB_envinfo mei;
|
||||
char *prog = argv[0];
|
||||
char *envname;
|
||||
char *subname = NULL;
|
||||
int alldbs = 0, envinfo = 0, envflags = 0, freinfo = 0, rdrinfo = 0;
|
||||
|
||||
if (argc < 2) {
|
||||
usage(prog);
|
||||
}
|
||||
|
||||
/* -a: print stat of main DB and all subDBs
|
||||
* -s: print stat of only the named subDB
|
||||
* -e: print env info
|
||||
* -f: print freelist info
|
||||
* -r: print reader info
|
||||
* -n: use NOSUBDIR flag on env_open
|
||||
* -V: print version and exit
|
||||
* (default) print stat of only the main DB
|
||||
*/
|
||||
while ((i = getopt(argc, argv, "Vaefnrs:")) != EOF) {
|
||||
switch(i) {
|
||||
case 'V':
|
||||
printf("%s\n", MDB_VERSION_STRING);
|
||||
exit(0);
|
||||
break;
|
||||
case 'a':
|
||||
if (subname)
|
||||
usage(prog);
|
||||
alldbs++;
|
||||
break;
|
||||
case 'e':
|
||||
envinfo++;
|
||||
break;
|
||||
case 'f':
|
||||
freinfo++;
|
||||
break;
|
||||
case 'n':
|
||||
envflags |= MDB_NOSUBDIR;
|
||||
break;
|
||||
case 'r':
|
||||
rdrinfo++;
|
||||
break;
|
||||
case 's':
|
||||
if (alldbs)
|
||||
usage(prog);
|
||||
subname = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(prog);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind != argc - 1)
|
||||
usage(prog);
|
||||
|
||||
envname = argv[optind];
|
||||
rc = mdb_env_create(&env);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (alldbs || subname) {
|
||||
mdb_env_set_maxdbs(env, 4);
|
||||
}
|
||||
|
||||
rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
if (envinfo) {
|
||||
(void)mdb_env_stat(env, &mst);
|
||||
(void)mdb_env_info(env, &mei);
|
||||
printf("Environment Info\n");
|
||||
printf(" Map address: %p\n", mei.me_mapaddr);
|
||||
printf(" Map size: %"Z"u\n", mei.me_mapsize);
|
||||
printf(" Page size: %u\n", mst.ms_psize);
|
||||
printf(" Max pages: %"Z"u\n", mei.me_mapsize / mst.ms_psize);
|
||||
printf(" Number of pages used: %"Z"u\n", mei.me_last_pgno+1);
|
||||
printf(" Last transaction ID: %"Z"u\n", mei.me_last_txnid);
|
||||
printf(" Max readers: %u\n", mei.me_maxreaders);
|
||||
printf(" Number of readers used: %u\n", mei.me_numreaders);
|
||||
}
|
||||
|
||||
if (rdrinfo) {
|
||||
printf("Reader Table Status\n");
|
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
|
||||
if (rdrinfo > 1) {
|
||||
int dead;
|
||||
mdb_reader_check(env, &dead);
|
||||
printf(" %d stale readers cleared.\n", dead);
|
||||
rc = mdb_reader_list(env, (MDB_msg_func *)fputs, stdout);
|
||||
}
|
||||
if (!(subname || alldbs || freinfo))
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto env_close;
|
||||
}
|
||||
|
||||
if (freinfo) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key, data;
|
||||
size_t pages = 0, *iptr;
|
||||
|
||||
printf("Freelist Status\n");
|
||||
dbi = 0;
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
rc = mdb_stat(txn, dbi, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prstat(&mst);
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
iptr = data.mv_data;
|
||||
pages += *iptr;
|
||||
if (freinfo > 1) {
|
||||
char *bad = "";
|
||||
size_t pg, prev;
|
||||
ssize_t i, j, span = 0;
|
||||
j = *iptr++;
|
||||
for (i = j, prev = 1; --i >= 0; ) {
|
||||
pg = iptr[i];
|
||||
if (pg <= prev)
|
||||
bad = " [bad sequence]";
|
||||
prev = pg;
|
||||
pg += span;
|
||||
for (; i >= span && iptr[i-span] == pg; span++, pg++) ;
|
||||
}
|
||||
printf(" Transaction %"Z"u, %"Z"d pages, maxspan %"Z"d%s\n",
|
||||
*(size_t *)key.mv_data, j, span, bad);
|
||||
if (freinfo > 2) {
|
||||
for (--j; j >= 0; ) {
|
||||
pg = iptr[j];
|
||||
for (span=1; --j >= 0 && iptr[j] == pg+span; span++) ;
|
||||
printf(span>1 ? " %9"Z"u[%"Z"d]\n" : " %9"Z"u\n",
|
||||
pg, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
printf(" Free pages: %"Z"u\n", pages);
|
||||
}
|
||||
|
||||
rc = mdb_open(txn, subname, 0, &dbi);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
|
||||
rc = mdb_stat(txn, dbi, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
printf("Status of %s\n", subname ? subname : "Main DB");
|
||||
prstat(&mst);
|
||||
|
||||
if (alldbs) {
|
||||
MDB_cursor *cursor;
|
||||
MDB_val key;
|
||||
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
|
||||
char *str;
|
||||
MDB_dbi db2;
|
||||
if (memchr(key.mv_data, '\0', key.mv_size))
|
||||
continue;
|
||||
str = malloc(key.mv_size+1);
|
||||
memcpy(str, key.mv_data, key.mv_size);
|
||||
str[key.mv_size] = '\0';
|
||||
rc = mdb_open(txn, str, 0, &db2);
|
||||
if (rc == MDB_SUCCESS)
|
||||
printf("Status of %s\n", str);
|
||||
free(str);
|
||||
if (rc) continue;
|
||||
rc = mdb_stat(txn, db2, &mst);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_stat failed, error %d %s\n", rc, mdb_strerror(rc));
|
||||
goto txn_abort;
|
||||
}
|
||||
prstat(&mst);
|
||||
mdb_close(env, db2);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
}
|
||||
|
||||
if (rc == MDB_NOTFOUND)
|
||||
rc = MDB_SUCCESS;
|
||||
|
||||
mdb_close(env, dbi);
|
||||
txn_abort:
|
||||
mdb_txn_abort(txn);
|
||||
env_close:
|
||||
mdb_env_close(env);
|
||||
|
||||
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
/** @file midl.c
|
||||
* @brief ldap bdb back-end ID List functions */
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2021 The OpenLDAP Foundation.
|
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include "midl.h"
|
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{
|
||||
*/
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
#define CMP(x,y) ( (x) < (y) ? -1 : (x) > (y) )
|
||||
|
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = ids[0];
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( ids[cursor], id );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
#if 0 /* superseded by append/sort */
|
||||
int mdb_midl_insert( MDB_IDL ids, MDB_ID id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
x = mdb_midl_search( ids, id );
|
||||
assert( x > 0 );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0] && ids[x] == id ) {
|
||||
/* duplicate */
|
||||
assert(0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ++ids[0] >= MDB_IDL_DB_MAX ) {
|
||||
/* no room */
|
||||
--ids[0];
|
||||
return -2;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
for (i=ids[0]; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
MDB_IDL mdb_midl_alloc(int num)
|
||||
{
|
||||
MDB_IDL ids = malloc((num+2) * sizeof(MDB_ID));
|
||||
if (ids) {
|
||||
*ids++ = num;
|
||||
*ids = 0;
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
void mdb_midl_free(MDB_IDL ids)
|
||||
{
|
||||
if (ids)
|
||||
free(ids-1);
|
||||
}
|
||||
|
||||
void mdb_midl_shrink( MDB_IDL *idp )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
if (*(--ids) > MDB_IDL_UM_MAX &&
|
||||
(ids = realloc(ids, (MDB_IDL_UM_MAX+2) * sizeof(MDB_ID))))
|
||||
{
|
||||
*ids++ = MDB_IDL_UM_MAX;
|
||||
*idp = ids;
|
||||
}
|
||||
}
|
||||
|
||||
static int mdb_midl_grow( MDB_IDL *idp, int num )
|
||||
{
|
||||
MDB_IDL idn = *idp-1;
|
||||
/* grow it */
|
||||
idn = realloc(idn, (*idn + num + 2) * sizeof(MDB_ID));
|
||||
if (!idn)
|
||||
return ENOMEM;
|
||||
*idn++ += num;
|
||||
*idp = idn;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_need( MDB_IDL *idp, unsigned num )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
num += ids[0];
|
||||
if (num > ids[-1]) {
|
||||
num = (num + num/4 + (256 + 2)) & -256;
|
||||
if (!(ids = realloc(ids-1, num * sizeof(MDB_ID))))
|
||||
return ENOMEM;
|
||||
*ids++ = num - 2;
|
||||
*idp = ids;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] >= ids[-1]) {
|
||||
if (mdb_midl_grow(idp, MDB_IDL_UM_MAX))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
ids[0]++;
|
||||
ids[ids[0]] = id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app )
|
||||
{
|
||||
MDB_IDL ids = *idp;
|
||||
/* Too big? */
|
||||
if (ids[0] + app[0] >= ids[-1]) {
|
||||
if (mdb_midl_grow(idp, app[0]))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
memcpy(&ids[ids[0]+1], &app[1], app[0] * sizeof(MDB_ID));
|
||||
ids[0] += app[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n )
|
||||
{
|
||||
MDB_ID *ids = *idp, len = ids[0];
|
||||
/* Too big? */
|
||||
if (len + n > ids[-1]) {
|
||||
if (mdb_midl_grow(idp, n | MDB_IDL_UM_MAX))
|
||||
return ENOMEM;
|
||||
ids = *idp;
|
||||
}
|
||||
ids[0] = len + n;
|
||||
ids += len;
|
||||
while (n)
|
||||
ids[n--] = id++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge )
|
||||
{
|
||||
MDB_ID old_id, merge_id, i = merge[0], j = idl[0], k = i+j, total = k;
|
||||
idl[0] = (MDB_ID)-1; /* delimiter for idl scan below */
|
||||
old_id = idl[j];
|
||||
while (i) {
|
||||
merge_id = merge[i--];
|
||||
for (; old_id < merge_id; old_id = idl[--j])
|
||||
idl[k--] = old_id;
|
||||
idl[k--] = merge_id;
|
||||
}
|
||||
idl[0] = total;
|
||||
}
|
||||
|
||||
/* Quicksort + Insertion sort for small arrays */
|
||||
|
||||
#define SMALL 8
|
||||
#define MIDL_SWAP(a,b) { itmp=(a); (a)=(b); (b)=itmp; }
|
||||
|
||||
void
|
||||
mdb_midl_sort( MDB_IDL ids )
|
||||
{
|
||||
/* Max possible depth of int-indexed tree * 2 items/level */
|
||||
int istack[sizeof(int)*CHAR_BIT * 2];
|
||||
int i,j,k,l,ir,jstack;
|
||||
MDB_ID a, itmp;
|
||||
|
||||
ir = (int)ids[0];
|
||||
l = 1;
|
||||
jstack = 0;
|
||||
for(;;) {
|
||||
if (ir - l < SMALL) { /* Insertion sort */
|
||||
for (j=l+1;j<=ir;j++) {
|
||||
a = ids[j];
|
||||
for (i=j-1;i>=1;i--) {
|
||||
if (ids[i] >= a) break;
|
||||
ids[i+1] = ids[i];
|
||||
}
|
||||
ids[i+1] = a;
|
||||
}
|
||||
if (jstack == 0) break;
|
||||
ir = istack[jstack--];
|
||||
l = istack[jstack--];
|
||||
} else {
|
||||
k = (l + ir) >> 1; /* Choose median of left, center, right */
|
||||
MIDL_SWAP(ids[k], ids[l+1]);
|
||||
if (ids[l] < ids[ir]) {
|
||||
MIDL_SWAP(ids[l], ids[ir]);
|
||||
}
|
||||
if (ids[l+1] < ids[ir]) {
|
||||
MIDL_SWAP(ids[l+1], ids[ir]);
|
||||
}
|
||||
if (ids[l] < ids[l+1]) {
|
||||
MIDL_SWAP(ids[l], ids[l+1]);
|
||||
}
|
||||
i = l+1;
|
||||
j = ir;
|
||||
a = ids[l+1];
|
||||
for(;;) {
|
||||
do i++; while(ids[i] > a);
|
||||
do j--; while(ids[j] < a);
|
||||
if (j < i) break;
|
||||
MIDL_SWAP(ids[i],ids[j]);
|
||||
}
|
||||
ids[l+1] = ids[j];
|
||||
ids[j] = a;
|
||||
jstack += 2;
|
||||
if (ir-i+1 >= j-l) {
|
||||
istack[jstack] = ir;
|
||||
istack[jstack-1] = i;
|
||||
ir = j-1;
|
||||
} else {
|
||||
istack[jstack] = j-1;
|
||||
istack[jstack-1] = l;
|
||||
l = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id )
|
||||
{
|
||||
/*
|
||||
* binary search of id in ids
|
||||
* if found, returns position of id
|
||||
* if not found, returns first position greater than id
|
||||
*/
|
||||
unsigned base = 0;
|
||||
unsigned cursor = 1;
|
||||
int val = 0;
|
||||
unsigned n = (unsigned)ids[0].mid;
|
||||
|
||||
while( 0 < n ) {
|
||||
unsigned pivot = n >> 1;
|
||||
cursor = base + pivot + 1;
|
||||
val = CMP( id, ids[cursor].mid );
|
||||
|
||||
if( val < 0 ) {
|
||||
n = pivot;
|
||||
|
||||
} else if ( val > 0 ) {
|
||||
base = cursor;
|
||||
n -= pivot + 1;
|
||||
|
||||
} else {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
if( val > 0 ) {
|
||||
++cursor;
|
||||
}
|
||||
return cursor;
|
||||
}
|
||||
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
unsigned x, i;
|
||||
|
||||
x = mdb_mid2l_search( ids, id->mid );
|
||||
|
||||
if( x < 1 ) {
|
||||
/* internal error */
|
||||
return -2;
|
||||
}
|
||||
|
||||
if ( x <= ids[0].mid && ids[x].mid == id->mid ) {
|
||||
/* duplicate */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ( ids[0].mid >= MDB_IDL_UM_MAX ) {
|
||||
/* too big */
|
||||
return -2;
|
||||
|
||||
} else {
|
||||
/* insert id */
|
||||
ids[0].mid++;
|
||||
for (i=(unsigned)ids[0].mid; i>x; i--)
|
||||
ids[i] = ids[i-1];
|
||||
ids[x] = *id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id )
|
||||
{
|
||||
/* Too big? */
|
||||
if (ids[0].mid >= MDB_IDL_UM_MAX) {
|
||||
return -2;
|
||||
}
|
||||
ids[0].mid++;
|
||||
ids[ids[0].mid] = *id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
@@ -0,0 +1,188 @@
|
||||
/** @file midl.h
|
||||
* @brief LMDB ID List header file.
|
||||
*
|
||||
* This file was originally part of back-bdb but has been
|
||||
* modified for use in libmdb. Most of the macros defined
|
||||
* in this file are unused, just left over from the original.
|
||||
*
|
||||
* This file is only used internally in libmdb and its definitions
|
||||
* are not exposed publicly.
|
||||
*/
|
||||
/* $OpenLDAP$ */
|
||||
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
|
||||
*
|
||||
* Copyright 2000-2021 The OpenLDAP Foundation.
|
||||
* Portions Copyright 2001-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
#ifndef _MDB_MIDL_H_
|
||||
#define _MDB_MIDL_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @defgroup internal LMDB Internals
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** @defgroup idls ID List Management
|
||||
* @{
|
||||
*/
|
||||
/** A generic unsigned ID number. These were entryIDs in back-bdb.
|
||||
* Preferably it should have the same size as a pointer.
|
||||
*/
|
||||
typedef size_t MDB_ID;
|
||||
|
||||
/** An IDL is an ID List, a sorted array of IDs. The first
|
||||
* element of the array is a counter for how many actual
|
||||
* IDs are in the list. In the original back-bdb code, IDLs are
|
||||
* sorted in ascending order. For libmdb IDLs are sorted in
|
||||
* descending order.
|
||||
*/
|
||||
typedef MDB_ID *MDB_IDL;
|
||||
|
||||
/* IDL sizes - likely should be even bigger
|
||||
* limiting factors: sizeof(ID), thread stack size
|
||||
*/
|
||||
#ifndef MDB_IDL_LOGN
|
||||
#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
|
||||
#endif
|
||||
#define MDB_IDL_DB_SIZE (1<<MDB_IDL_LOGN)
|
||||
#define MDB_IDL_UM_SIZE (1<<(MDB_IDL_LOGN+1))
|
||||
|
||||
#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE-1)
|
||||
#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE-1)
|
||||
|
||||
#define MDB_IDL_SIZEOF(ids) (((ids)[0]+1) * sizeof(MDB_ID))
|
||||
#define MDB_IDL_IS_ZERO(ids) ( (ids)[0] == 0 )
|
||||
#define MDB_IDL_CPY( dst, src ) (memcpy( dst, src, MDB_IDL_SIZEOF( src ) ))
|
||||
#define MDB_IDL_FIRST( ids ) ( (ids)[1] )
|
||||
#define MDB_IDL_LAST( ids ) ( (ids)[(ids)[0]] )
|
||||
|
||||
/** Current max length of an #mdb_midl_alloc()ed IDL */
|
||||
#define MDB_IDL_ALLOCLEN( ids ) ( (ids)[-1] )
|
||||
|
||||
/** Append ID to IDL. The IDL must be big enough. */
|
||||
#define mdb_midl_xappend(idl, id) do { \
|
||||
MDB_ID *xidl = (idl), xlen = ++(xidl[0]); \
|
||||
xidl[xlen] = (id); \
|
||||
} while (0)
|
||||
|
||||
/** Search for an ID in an IDL.
|
||||
* @param[in] ids The IDL to search.
|
||||
* @param[in] id The ID to search for.
|
||||
* @return The index of the first ID greater than or equal to \b id.
|
||||
*/
|
||||
unsigned mdb_midl_search( MDB_IDL ids, MDB_ID id );
|
||||
|
||||
/** Allocate an IDL.
|
||||
* Allocates memory for an IDL of the given size.
|
||||
* @return IDL on success, NULL on failure.
|
||||
*/
|
||||
MDB_IDL mdb_midl_alloc(int num);
|
||||
|
||||
/** Free an IDL.
|
||||
* @param[in] ids The IDL to free.
|
||||
*/
|
||||
void mdb_midl_free(MDB_IDL ids);
|
||||
|
||||
/** Shrink an IDL.
|
||||
* Return the IDL to the default size if it has grown larger.
|
||||
* @param[in,out] idp Address of the IDL to shrink.
|
||||
*/
|
||||
void mdb_midl_shrink(MDB_IDL *idp);
|
||||
|
||||
/** Make room for num additional elements in an IDL.
|
||||
* @param[in,out] idp Address of the IDL.
|
||||
* @param[in] num Number of elements to make room for.
|
||||
* @return 0 on success, ENOMEM on failure.
|
||||
*/
|
||||
int mdb_midl_need(MDB_IDL *idp, unsigned num);
|
||||
|
||||
/** Append an ID onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] id The ID to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append( MDB_IDL *idp, MDB_ID id );
|
||||
|
||||
/** Append an IDL onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] app The IDL to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append_list( MDB_IDL *idp, MDB_IDL app );
|
||||
|
||||
/** Append an ID range onto an IDL.
|
||||
* @param[in,out] idp Address of the IDL to append to.
|
||||
* @param[in] id The lowest ID to append.
|
||||
* @param[in] n Number of IDs to append.
|
||||
* @return 0 on success, ENOMEM if the IDL is too large.
|
||||
*/
|
||||
int mdb_midl_append_range( MDB_IDL *idp, MDB_ID id, unsigned n );
|
||||
|
||||
/** Merge an IDL onto an IDL. The destination IDL must be big enough.
|
||||
* @param[in] idl The IDL to merge into.
|
||||
* @param[in] merge The IDL to merge.
|
||||
*/
|
||||
void mdb_midl_xmerge( MDB_IDL idl, MDB_IDL merge );
|
||||
|
||||
/** Sort an IDL.
|
||||
* @param[in,out] ids The IDL to sort.
|
||||
*/
|
||||
void mdb_midl_sort( MDB_IDL ids );
|
||||
|
||||
/** An ID2 is an ID/pointer pair.
|
||||
*/
|
||||
typedef struct MDB_ID2 {
|
||||
MDB_ID mid; /**< The ID */
|
||||
void *mptr; /**< The pointer */
|
||||
} MDB_ID2;
|
||||
|
||||
/** An ID2L is an ID2 List, a sorted array of ID2s.
|
||||
* The first element's \b mid member is a count of how many actual
|
||||
* elements are in the array. The \b mptr member of the first element is unused.
|
||||
* The array is sorted in ascending order by \b mid.
|
||||
*/
|
||||
typedef MDB_ID2 *MDB_ID2L;
|
||||
|
||||
/** Search for an ID in an ID2L.
|
||||
* @param[in] ids The ID2L to search.
|
||||
* @param[in] id The ID to search for.
|
||||
* @return The index of the first ID2 whose \b mid member is greater than or equal to \b id.
|
||||
*/
|
||||
unsigned mdb_mid2l_search( MDB_ID2L ids, MDB_ID id );
|
||||
|
||||
|
||||
/** Insert an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to insert into.
|
||||
* @param[in] id The ID2 to insert.
|
||||
* @return 0 on success, -1 if the ID was already present in the ID2L.
|
||||
*/
|
||||
int mdb_mid2l_insert( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** Append an ID2 into a ID2L.
|
||||
* @param[in,out] ids The ID2L to append into.
|
||||
* @param[in] id The ID2 to append.
|
||||
* @return 0 on success, -2 if the ID2L is too big.
|
||||
*/
|
||||
int mdb_mid2l_append( MDB_ID2L ids, MDB_ID2 *id );
|
||||
|
||||
/** @} */
|
||||
/** @} */
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* _MDB_MIDL_H_ */
|
||||
@@ -0,0 +1,580 @@
|
||||
/* mplay.c - memory-mapped database log replay */
|
||||
/*
|
||||
* Copyright 2011-2023 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
#define MDB_SCNy(t) "z" #t
|
||||
|
||||
#define SCMP(s) s, (sizeof(s)-1)
|
||||
char inbuf[8192];
|
||||
char *dbuf, *kbuf;
|
||||
size_t dbufsize;
|
||||
int maxkey;
|
||||
|
||||
#define SOFF(s) (sizeof(s)+1)
|
||||
|
||||
#define MAXENVS 16
|
||||
#define MAXTXNS 16
|
||||
#define MAXCRSS 16
|
||||
|
||||
#define MAXPIDS 16
|
||||
|
||||
typedef struct crspair {
|
||||
void *tcrs; /* scanned text pointer */
|
||||
MDB_cursor *rcrs;
|
||||
} crspair;
|
||||
|
||||
typedef struct txnpair {
|
||||
void *ttxn; /* scanned text pointer */
|
||||
MDB_txn *rtxn;
|
||||
crspair cursors[MAXCRSS];
|
||||
int ncursors;
|
||||
} txnpair;
|
||||
|
||||
typedef struct envpair {
|
||||
void *tenv;
|
||||
MDB_env *renv;
|
||||
txnpair txns[MAXTXNS];
|
||||
int ntxns;
|
||||
} envpair;
|
||||
|
||||
envpair envs[MAXENVS];
|
||||
int nenvs;
|
||||
|
||||
envpair *lastenv;
|
||||
txnpair *lasttxn;
|
||||
crspair *lastcrs;
|
||||
|
||||
typedef struct pidpair {
|
||||
int tpid;
|
||||
pid_t rpid;
|
||||
int fdout, fdin;
|
||||
} pidpair;
|
||||
|
||||
pidpair *lastpid;
|
||||
|
||||
pidpair pids[MAXPIDS];
|
||||
int npids;
|
||||
|
||||
unsigned long lcount;
|
||||
|
||||
static int unhex(unsigned char *c2)
|
||||
{
|
||||
int x, c;
|
||||
x = *c2++ & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c = x << 4;
|
||||
x = *c2 & 0x4f;
|
||||
if (x & 0x40)
|
||||
x -= 55;
|
||||
c |= x;
|
||||
return c;
|
||||
}
|
||||
|
||||
int inhex(char *in, char *out)
|
||||
{
|
||||
char *c2 = out;
|
||||
while (isxdigit(*in)) {
|
||||
*c2++ = unhex((unsigned char *)in);
|
||||
in += 2;
|
||||
}
|
||||
return c2 - out;
|
||||
}
|
||||
|
||||
static void addenv(void *tenv, MDB_env *renv)
|
||||
{
|
||||
assert(nenvs < MAXENVS);
|
||||
envs[nenvs].tenv = tenv;
|
||||
envs[nenvs].renv = renv;
|
||||
envs[nenvs].ntxns = 0;
|
||||
lastenv = envs+nenvs;
|
||||
nenvs++;
|
||||
}
|
||||
|
||||
static envpair *findenv(void *tenv)
|
||||
{
|
||||
int i;
|
||||
if (!lastenv || lastenv->tenv != tenv) {
|
||||
for (i=0; i<nenvs; i++)
|
||||
if (envs[i].tenv == tenv)
|
||||
break;
|
||||
assert(i < nenvs);
|
||||
lastenv = &envs[i];
|
||||
}
|
||||
return lastenv;
|
||||
}
|
||||
|
||||
static void delenv(envpair *ep)
|
||||
{
|
||||
int i = ep - envs;
|
||||
for (; i<nenvs-1; i++)
|
||||
envs[i] = envs[i+1];
|
||||
nenvs--;
|
||||
lastenv = NULL;
|
||||
}
|
||||
|
||||
static void addtxn(void *tenv, void *ttxn, MDB_txn *rtxn)
|
||||
{
|
||||
envpair *ep;
|
||||
txnpair *tp;
|
||||
|
||||
ep = findenv(tenv);
|
||||
assert(ep->ntxns < MAXTXNS);
|
||||
tp = ep->txns+ep->ntxns;
|
||||
tp->ttxn = ttxn;
|
||||
tp->rtxn = rtxn;
|
||||
tp->ncursors = 0;
|
||||
ep->ntxns++;
|
||||
lasttxn = tp;
|
||||
}
|
||||
|
||||
static txnpair *findtxn(void *ttxn)
|
||||
{
|
||||
int i, j;
|
||||
if (lasttxn && lasttxn->ttxn == ttxn)
|
||||
return lasttxn;
|
||||
if (lastenv) {
|
||||
for (i=0; i<lastenv->ntxns; i++) {
|
||||
if (lastenv->txns[i].ttxn == ttxn) {
|
||||
lasttxn = lastenv->txns+i;
|
||||
return lasttxn;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0; i<nenvs; i++) {
|
||||
if (envs+i == lastenv) continue;
|
||||
for (j=0; j<envs[i].ntxns; j++) {
|
||||
if (envs[i].txns[j].ttxn == ttxn) {
|
||||
lastenv = envs+i;
|
||||
lasttxn = envs[i].txns+j;
|
||||
return lasttxn;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0); /* should have found it */
|
||||
}
|
||||
|
||||
static void deltxn(txnpair *tp)
|
||||
{
|
||||
int i = tp - lastenv->txns;
|
||||
for (; i<lastenv->ntxns-1; i++)
|
||||
lastenv->txns[i] = lastenv->txns[i+1];
|
||||
lastenv->ntxns--;
|
||||
lasttxn = NULL;
|
||||
}
|
||||
|
||||
static void addcrs(txnpair *tp, void *tcrs, MDB_cursor *rcrs)
|
||||
{
|
||||
int j = tp->ncursors;
|
||||
assert(tp->ncursors < MAXCRSS);
|
||||
|
||||
tp->cursors[j].tcrs = tcrs;
|
||||
tp->cursors[j].rcrs = rcrs;
|
||||
tp->ncursors++;
|
||||
lastcrs = tp->cursors+j;
|
||||
}
|
||||
|
||||
static crspair *findcrs(void *tcrs)
|
||||
{
|
||||
int i, j, k;
|
||||
envpair *ep;
|
||||
txnpair *tp;
|
||||
crspair *cp;
|
||||
if (lastcrs && lastcrs->tcrs == tcrs)
|
||||
return lastcrs;
|
||||
if (lasttxn) {
|
||||
for (k=0, cp=lasttxn->cursors; k<lasttxn->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastenv) {
|
||||
for (j=0, tp=lastenv->txns; j<lastenv->ntxns; j++, tp++){
|
||||
if (tp == lasttxn)
|
||||
continue;
|
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
lasttxn = tp;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i=0, ep=envs; i<nenvs; i++, ep++) {
|
||||
for (j=0, tp=ep->txns; j<ep->ntxns; j++, tp++) {
|
||||
if (tp == lasttxn)
|
||||
continue;
|
||||
for (k=0, cp = tp->cursors; k<tp->ncursors; k++, cp++) {
|
||||
if (cp->tcrs == tcrs) {
|
||||
lastcrs = cp;
|
||||
lasttxn = tp;
|
||||
lastenv = ep;
|
||||
return lastcrs;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0); /* should have found it already */
|
||||
}
|
||||
|
||||
static void delcrs(void *tcrs)
|
||||
{
|
||||
int i;
|
||||
crspair *cp = findcrs(tcrs);
|
||||
mdb_cursor_close(cp->rcrs);
|
||||
for (i = cp - lasttxn->cursors; i<lasttxn->ncursors-1; i++)
|
||||
lasttxn->cursors[i] = lasttxn->cursors[i+1];
|
||||
lasttxn->ncursors--;
|
||||
lastcrs = NULL;
|
||||
}
|
||||
|
||||
void child()
|
||||
{
|
||||
int rc;
|
||||
MDB_val key, data;
|
||||
char *ptr;
|
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) {
|
||||
ptr = inbuf;
|
||||
if (!strncmp(ptr, SCMP("exit")))
|
||||
break;
|
||||
|
||||
if (!strncmp(ptr, SCMP("mdb_env_create"))) {
|
||||
void *tenv;
|
||||
MDB_env *renv;
|
||||
sscanf(ptr+SOFF("mdb_env_create"), "%p", &tenv);
|
||||
E(mdb_env_create(&renv));
|
||||
addenv(tenv, renv);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_maxdbs"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
unsigned int maxdbs;
|
||||
sscanf(ptr+SOFF("mdb_env_set_maxdbs"), "%p, %u", &tenv, &maxdbs);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_set_maxdbs(ep->renv, maxdbs));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_set_mapsize"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
size_t mapsize;
|
||||
sscanf(ptr+SOFF("mdb_env_set_mapsize"), "%p, %"MDB_SCNy(u), &tenv, &mapsize);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_set_mapsize(ep->renv, mapsize));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_open"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
char *path;
|
||||
int len;
|
||||
unsigned int flags, mode;
|
||||
sscanf(ptr+SOFF("mdb_env_open"), "%p, %n", &tenv, &len);
|
||||
path = ptr+SOFF("mdb_env_open")+len;
|
||||
ptr = strchr(path, ',');
|
||||
*ptr++ = '\0';
|
||||
sscanf(ptr, "%u, %o", &flags, &mode);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_env_open(ep->renv, path, flags, mode));
|
||||
if (!maxkey) {
|
||||
maxkey = mdb_env_get_maxkeysize(ep->renv);
|
||||
kbuf = malloc(maxkey+2);
|
||||
dbuf = malloc(maxkey+2);
|
||||
dbufsize = maxkey;
|
||||
}
|
||||
} else if (!strncmp(ptr, SCMP("mdb_env_close"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
sscanf(ptr+SOFF("mdb_env_close"), "%p", &tenv);
|
||||
ep = findenv(tenv);
|
||||
mdb_env_close(ep->renv);
|
||||
delenv(ep);
|
||||
if (!nenvs) /* if no other envs left, this process is done */
|
||||
break;
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_begin"))) {
|
||||
unsigned int flags;
|
||||
void *tenv, *ttxn;
|
||||
envpair *ep;
|
||||
MDB_txn *rtxn;
|
||||
sscanf(ptr+SOFF("mdb_txn_begin"), "%p, %*p, %u = %p", &tenv, &flags, &ttxn);
|
||||
ep = findenv(tenv);
|
||||
E(mdb_txn_begin(ep->renv, NULL, flags, &rtxn));
|
||||
addtxn(tenv, ttxn, rtxn);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_commit"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
sscanf(ptr+SOFF("mdb_txn_commit"), "%p", &ttxn);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_txn_commit(tp->rtxn));
|
||||
deltxn(tp);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_txn_abort"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
sscanf(ptr+SOFF("mdb_txn_abort"), "%p", &ttxn);
|
||||
tp = findtxn(ttxn);
|
||||
mdb_txn_abort(tp->rtxn);
|
||||
deltxn(tp);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_open"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
char *dbname;
|
||||
unsigned int flags;
|
||||
unsigned int tdbi;
|
||||
MDB_dbi dbi;
|
||||
sscanf(ptr+SOFF("mdb_dbi_open"), "%p, ", &ttxn);
|
||||
dbname = strchr(ptr+SOFF("mdb_dbi_open"), ',');
|
||||
dbname += 2;
|
||||
ptr = strchr(dbname, ',');
|
||||
*ptr++ = '\0';
|
||||
if (!strcmp(dbname, "(null)"))
|
||||
dbname = NULL;
|
||||
sscanf(ptr, "%u = %u", &flags, &tdbi);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_dbi_open(tp->rtxn, dbname, flags, &dbi));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_dbi_close"))) {
|
||||
void *tenv;
|
||||
envpair *ep;
|
||||
unsigned int tdbi;
|
||||
sscanf(ptr+SOFF("mdb_dbi_close"), "%p, %u", &tenv, &tdbi);
|
||||
ep = findenv(tenv);
|
||||
mdb_dbi_close(ep->renv, tdbi);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_open"))) {
|
||||
void *ttxn, *tcrs;
|
||||
txnpair *tp;
|
||||
MDB_cursor *rcrs;
|
||||
unsigned int tdbi;
|
||||
sscanf(ptr+SOFF("mdb_cursor_open"), "%p, %u = %p", &ttxn, &tdbi, &tcrs);
|
||||
tp = findtxn(ttxn);
|
||||
E(mdb_cursor_open(tp->rtxn, tdbi, &rcrs));
|
||||
addcrs(tp, tcrs, rcrs);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_close"))) {
|
||||
void *tcrs;
|
||||
sscanf(ptr+SOFF("mdb_cursor_close"), "%p", &tcrs);
|
||||
delcrs(tcrs);
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_put"))) {
|
||||
void *tcrs;
|
||||
crspair *cp;
|
||||
unsigned int flags;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_cursor_put"), "%p, ", &tcrs);
|
||||
cp = findcrs(tcrs);
|
||||
ptr = strchr(ptr+SOFF("mdb_cursor_put"), ',');
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)",", &key.mv_size);
|
||||
if (key.mv_size) {
|
||||
ptr = strchr(ptr, '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
}
|
||||
ptr = strchr(ptr+1, ',');
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
data.mv_data = dbuf;
|
||||
ptr += data.mv_size * 2 + 2;
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(lasttxn->rtxn));
|
||||
}
|
||||
sscanf(ptr+1, "%u", &flags);
|
||||
E(mdb_cursor_put(cp->rcrs, &key, &data, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_cursor_del"))) {
|
||||
void *tcrs;
|
||||
crspair *cp;
|
||||
unsigned int flags;
|
||||
sscanf(ptr+SOFF("mdb_cursor_del"), "%p, %u", &tcrs, &flags);
|
||||
cp = findcrs(tcrs);
|
||||
E(mdb_cursor_del(cp->rcrs, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_put"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
unsigned int tdbi, flags;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_put"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
|
||||
tp = findtxn(ttxn);
|
||||
ptr = strchr(ptr+SOFF("mdb_put"), '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
ptr += data.mv_size * 2 + 2;
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
|
||||
}
|
||||
data.mv_data = dbuf;
|
||||
sscanf(ptr+1, "%u", &flags);
|
||||
RES(MDB_KEYEXIST,mdb_put(tp->rtxn, tdbi, &key, &data, flags));
|
||||
} else if (!strncmp(ptr, SCMP("mdb_del"))) {
|
||||
void *ttxn;
|
||||
txnpair *tp;
|
||||
unsigned int tdbi;
|
||||
int len;
|
||||
sscanf(ptr+SOFF("mdb_del"),"%p, %u, %"MDB_SCNy(u), &ttxn, &tdbi, &key.mv_size);
|
||||
tp = findtxn(ttxn);
|
||||
ptr = strchr(ptr+SOFF("mdb_del"), '[');
|
||||
inhex(ptr+1, kbuf);
|
||||
key.mv_data = kbuf;
|
||||
ptr += key.mv_size * 2 + 2;
|
||||
sscanf(ptr+1, "%"MDB_SCNy(u)"%n", &data.mv_size, &len);
|
||||
if (data.mv_size > dbufsize) {
|
||||
dbuf = realloc(dbuf, data.mv_size+2);
|
||||
assert(dbuf != NULL);
|
||||
dbufsize = data.mv_size;
|
||||
}
|
||||
ptr += len+1;
|
||||
if (*ptr == '[') {
|
||||
inhex(ptr+1, dbuf);
|
||||
} else {
|
||||
sprintf(dbuf, "%09ld", (long)mdb_txn_id(tp->rtxn));
|
||||
}
|
||||
data.mv_data = dbuf;
|
||||
RES(MDB_NOTFOUND,mdb_del(tp->rtxn, tdbi, &key, &data));
|
||||
}
|
||||
write(1, "\n", 1);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
static pidpair *addpid(int tpid)
|
||||
{
|
||||
int fdout[2], fdin[2];
|
||||
pid_t pid;
|
||||
assert(npids < MAXPIDS);
|
||||
pids[npids].tpid = tpid;
|
||||
pipe(fdout);
|
||||
pipe(fdin);
|
||||
if ((pid = fork()) == 0) {
|
||||
/* child */
|
||||
fflush(stdin);
|
||||
fflush(stdout);
|
||||
dup2(fdout[0], 0);
|
||||
dup2(fdin[1], 1);
|
||||
child();
|
||||
return 0; /* NOTREACHED */
|
||||
} else {
|
||||
pids[npids].rpid = pid;
|
||||
pids[npids].fdout = fdout[1];
|
||||
pids[npids].fdin = fdin[0];
|
||||
lastpid = pids+npids;
|
||||
npids++;
|
||||
return lastpid;
|
||||
}
|
||||
}
|
||||
|
||||
static pidpair *findpid(int tpid)
|
||||
{
|
||||
int i;
|
||||
if (!lastpid || lastpid->tpid != tpid) {
|
||||
for (i=0; i<npids; i++)
|
||||
if (pids[i].tpid == tpid)
|
||||
break;
|
||||
if (i == npids)
|
||||
return NULL;
|
||||
lastpid = &pids[i];
|
||||
}
|
||||
return lastpid;
|
||||
}
|
||||
|
||||
volatile pid_t killpid;
|
||||
|
||||
static void delpid(int tpid)
|
||||
{
|
||||
pidpair *pp = findpid(tpid);
|
||||
if (pp) {
|
||||
pid_t kpid = pp->rpid;
|
||||
killpid = kpid;
|
||||
write(pp->fdout, "exit\n", sizeof("exit"));
|
||||
while (killpid == kpid)
|
||||
usleep(10000);
|
||||
}
|
||||
}
|
||||
|
||||
static void reaper(int sig)
|
||||
{
|
||||
int status, i;
|
||||
pid_t pid = waitpid(-1, &status, 0);
|
||||
if (pid > 0) {
|
||||
fprintf(stderr, "# %s %d\n", WIFEXITED(status) ? "exited" : "killed", pid);
|
||||
for (i=0; i<npids; i++)
|
||||
if (pids[i].rpid == pid)
|
||||
break;
|
||||
assert(i < npids);
|
||||
close(pids[i].fdout);
|
||||
close(pids[i].fdin);
|
||||
for (;i<npids-1; i++)
|
||||
pids[i] = pids[i+1];
|
||||
npids--;
|
||||
killpid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
signal(SIGCHLD, reaper);
|
||||
|
||||
while (fgets(inbuf, sizeof(inbuf), stdin)) {
|
||||
pidpair *pp;
|
||||
int tpid, len;
|
||||
char c, *ptr;
|
||||
lcount++;
|
||||
|
||||
if (inbuf[0] == '#' && !strncmp(inbuf+1, SCMP(" killed"))) {
|
||||
sscanf(inbuf+SOFF("killed"),"%d", &tpid);
|
||||
delpid(tpid);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inbuf[0] != '>')
|
||||
continue;
|
||||
ptr = inbuf+1;
|
||||
sscanf(ptr, "%d:%n", &tpid, &len);
|
||||
pp = findpid(tpid);
|
||||
if (!pp)
|
||||
pp = addpid(tpid); /* new process */
|
||||
|
||||
ptr = inbuf+len+1;
|
||||
len = strlen(ptr);
|
||||
write(pp->fdout, ptr, len); /* send command and wait for ack */
|
||||
read(pp->fdin, &c, 1);
|
||||
}
|
||||
while (npids)
|
||||
delpid(pids[0].tpid);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/* mtest.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor, *cur2;
|
||||
MDB_cursor_op op;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32] = "";
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_maxreaders(env, 1));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP /*|MDB_NOSYNC*/, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, NULL, 0, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
/* Set <data> in each iteration, since MDB_NOOVERWRITE may modify it */
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE))) {
|
||||
j++;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
}
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
key.mv_data = sval;
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%03x ", values[i]);
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor last\n");
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor last/prev\n");
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_LAST));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_PREV));
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
printf("Deleting with cursor\n");
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cur2));
|
||||
for (i=0; i<50; i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, MDB_NEXT)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
E(mdb_del(txn, dbi, &key, NULL));
|
||||
}
|
||||
|
||||
printf("Restarting cursor in txn\n");
|
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cur2, &key, &data, op)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cur2);
|
||||
E(mdb_txn_commit(txn));
|
||||
|
||||
printf("Restarting cursor outside txn\n");
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
for (op=MDB_FIRST, i=0; i<=32; op=MDB_NEXT, i++) {
|
||||
if (RES(MDB_NOTFOUND, mdb_cursor_get(cursor, &key, &data, op)))
|
||||
break;
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/* mtest2.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Just like mtest.c, but using a subDB instead of the main DB */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32] = "";
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_maxreaders(env, 1));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id1", MDB_CREATE, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NOOVERWRITE)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
key.mv_data = sval;
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%03x ", values[i]);
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, NULL))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/* mtest3.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
if (!(i & 0x0f))
|
||||
sprintf(kval, "%03x", values[i]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/* mtest4.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs with fixed-size keys */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[8];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = 510;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = i*5;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id4", MDB_CREATE|MDB_DUPSORT|MDB_DUPFIXED, &dbi));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
strcpy(kval, "001");
|
||||
for (i=0;i<count;i++) {
|
||||
sprintf(sval, "%07x", values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
/* there should be one full page of dups now.
|
||||
*/
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
/* test all 3 branches of split code:
|
||||
* 1: new key in lower half
|
||||
* 2: new key at split point
|
||||
* 3: new key in upper half
|
||||
*/
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
sprintf(sval, "%07x", values[3]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
sprintf(sval, "%07x", values[255]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
sprintf(sval, "%07x", values[500]+1);
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
(void)RES(MDB_KEYEXIST, mdb_put(txn, dbi, &key, &data, MDB_NODUPDATA));
|
||||
E(mdb_txn_commit(txn));
|
||||
|
||||
/* Try MDB_NEXT_MULTIPLE */
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT_MULTIPLE)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%3)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(sval, "%07x", values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/* mtest5.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for sorted duplicate DBs using cursor_put */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
char sval[32];
|
||||
char kval[sizeof(int)];
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
memset(sval, 0, sizeof(sval));
|
||||
|
||||
count = (rand()%384) + 64;
|
||||
values = (int *)malloc(count*sizeof(int));
|
||||
|
||||
for(i = 0;i<count;i++) {
|
||||
values[i] = rand()%1024;
|
||||
}
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id2", MDB_CREATE|MDB_DUPSORT, &dbi));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
printf("Adding %d values\n", count);
|
||||
for (i=0;i<count;i++) {
|
||||
if (!(i & 0x0f))
|
||||
sprintf(kval, "%03x", values[i]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
if (RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NODUPDATA)))
|
||||
j++;
|
||||
}
|
||||
if (j) printf("%d duplicates skipped\n", j);
|
||||
mdb_cursor_close(cursor);
|
||||
E(mdb_txn_commit(txn));
|
||||
E(mdb_env_stat(env, &mst));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/* mtest6.c - memory-mapped database tester/toy */
|
||||
/*
|
||||
* Copyright 2011-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
|
||||
/* Tests for DB splits and merges */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
#define E(expr) CHECK((rc = (expr)) == MDB_SUCCESS, #expr)
|
||||
#define RES(err, expr) ((rc = expr) == (err) || (CHECK(!rc, #expr), 0))
|
||||
#define CHECK(test, msg) ((test) ? (void)0 : ((void)fprintf(stderr, \
|
||||
"%s:%d: %s: %s\n", __FILE__, __LINE__, msg, mdb_strerror(rc)), abort()))
|
||||
|
||||
char dkbuf[1024];
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int i = 0, j = 0, rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data, sdata;
|
||||
MDB_txn *txn;
|
||||
MDB_stat mst;
|
||||
MDB_cursor *cursor;
|
||||
int count;
|
||||
int *values;
|
||||
long kval;
|
||||
char *sval;
|
||||
|
||||
srand(time(NULL));
|
||||
|
||||
E(mdb_env_create(&env));
|
||||
E(mdb_env_set_mapsize(env, 10485760));
|
||||
E(mdb_env_set_maxdbs(env, 4));
|
||||
E(mdb_env_open(env, "./testdb", MDB_FIXEDMAP|MDB_NOSYNC, 0664));
|
||||
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
E(mdb_dbi_open(txn, "id6", MDB_CREATE|MDB_INTEGERKEY, &dbi));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
E(mdb_stat(txn, dbi, &mst));
|
||||
|
||||
sval = calloc(1, mst.ms_psize / 4);
|
||||
key.mv_size = sizeof(long);
|
||||
key.mv_data = &kval;
|
||||
sdata.mv_size = mst.ms_psize / 4 - 30;
|
||||
sdata.mv_data = sval;
|
||||
|
||||
printf("Adding 12 values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
printf("Adding 12 more values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5+4;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
printf("Adding 12 more values, should yield 3 splits\n");
|
||||
for (i=0;i<12;i++) {
|
||||
kval = i*5+1;
|
||||
sprintf(sval, "%08x", kval);
|
||||
data = sdata;
|
||||
(void)RES(MDB_KEYEXIST, mdb_cursor_put(cursor, &key, &data, MDB_NOOVERWRITE));
|
||||
}
|
||||
E(mdb_cursor_get(cursor, &key, &data, MDB_FIRST));
|
||||
|
||||
do {
|
||||
printf("key: %p %s, data: %p %.*s\n",
|
||||
key.mv_data, mdb_dkey(&key, dkbuf),
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
} while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0);
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_commit(txn);
|
||||
|
||||
#if 0
|
||||
j=0;
|
||||
|
||||
for (i= count - 1; i > -1; i-= (rand()%5)) {
|
||||
j++;
|
||||
txn=NULL;
|
||||
E(mdb_txn_begin(env, NULL, 0, &txn));
|
||||
sprintf(kval, "%03x", values[i & ~0x0f]);
|
||||
sprintf(sval, "%03x %d foo bar", values[i], values[i]);
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = kval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
if (RES(MDB_NOTFOUND, mdb_del(txn, dbi, &key, &data))) {
|
||||
j--;
|
||||
mdb_txn_abort(txn);
|
||||
} else {
|
||||
E(mdb_txn_commit(txn));
|
||||
}
|
||||
}
|
||||
free(values);
|
||||
printf("Deleted %d values\n", j);
|
||||
|
||||
E(mdb_env_stat(env, &mst));
|
||||
E(mdb_txn_begin(env, NULL, MDB_RDONLY, &txn));
|
||||
E(mdb_cursor_open(txn, dbi, &cursor));
|
||||
printf("Cursor next\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
printf("Cursor prev\n");
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_PREV)) == 0) {
|
||||
printf("key: %.*s, data: %.*s\n",
|
||||
(int) key.mv_size, (char *) key.mv_data,
|
||||
(int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
CHECK(rc == MDB_NOTFOUND, "mdb_cursor_get");
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
|
||||
mdb_dbi_close(env, dbi);
|
||||
#endif
|
||||
mdb_env_close(env);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/* sample-bdb.txt - BerkeleyDB toy/sample
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-mdb.txt
|
||||
*/
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <db.h>
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
DB_ENV *env;
|
||||
DB *dbi;
|
||||
DBT key, data;
|
||||
DB_TXN *txn;
|
||||
DBC *cursor;
|
||||
char sval[32], kval[32];
|
||||
|
||||
/* Note: Most error checking omitted for simplicity */
|
||||
|
||||
#define FLAGS (DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_TXN|DB_INIT_MPOOL|DB_CREATE|DB_THREAD)
|
||||
rc = db_env_create(&env, 0);
|
||||
rc = env->open(env, "./testdb", FLAGS, 0664);
|
||||
rc = db_create(&dbi, env, 0);
|
||||
rc = env->txn_begin(env, NULL, &txn, 0);
|
||||
rc = dbi->open(dbi, txn, "test.bdb", NULL, DB_BTREE, DB_CREATE, 0664);
|
||||
|
||||
memset(&key, 0, sizeof(DBT));
|
||||
memset(&data, 0, sizeof(DBT));
|
||||
key.size = sizeof(int);
|
||||
key.data = sval;
|
||||
data.size = sizeof(sval);
|
||||
data.data = sval;
|
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592);
|
||||
rc = dbi->put(dbi, txn, &key, &data, 0);
|
||||
rc = txn->commit(txn, 0);
|
||||
if (rc) {
|
||||
fprintf(stderr, "txn->commit: (%d) %s\n", rc, db_strerror(rc));
|
||||
goto leave;
|
||||
}
|
||||
rc = env->txn_begin(env, NULL, &txn, 0);
|
||||
rc = dbi->cursor(dbi, txn, &cursor, 0);
|
||||
key.flags = DB_DBT_USERMEM;
|
||||
key.data = kval;
|
||||
key.ulen = sizeof(kval);
|
||||
data.flags = DB_DBT_USERMEM;
|
||||
data.data = sval;
|
||||
data.ulen = sizeof(sval);
|
||||
while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.data, (int) key.size, (char *) key.data,
|
||||
data.data, (int) data.size, (char *) data.data);
|
||||
}
|
||||
rc = cursor->c_close(cursor);
|
||||
rc = txn->abort(txn);
|
||||
leave:
|
||||
rc = dbi->close(dbi, 0);
|
||||
rc = env->close(env, 0);
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/* sample-mdb.txt - MDB toy/sample
|
||||
*
|
||||
* Do a line-by-line comparison of this and sample-bdb.txt
|
||||
*/
|
||||
/*
|
||||
* Copyright 2012-2021 Howard Chu, Symas Corp.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted only as authorized by the OpenLDAP
|
||||
* Public License.
|
||||
*
|
||||
* A copy of this license is available in the file LICENSE in the
|
||||
* top-level directory of the distribution or, alternatively, at
|
||||
* <http://www.OpenLDAP.org/license.html>.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "lmdb.h"
|
||||
|
||||
int main(int argc,char * argv[])
|
||||
{
|
||||
int rc;
|
||||
MDB_env *env;
|
||||
MDB_dbi dbi;
|
||||
MDB_val key, data;
|
||||
MDB_txn *txn;
|
||||
MDB_cursor *cursor;
|
||||
char sval[32];
|
||||
|
||||
/* Note: Most error checking omitted for simplicity */
|
||||
|
||||
rc = mdb_env_create(&env);
|
||||
rc = mdb_env_open(env, "./testdb", 0, 0664);
|
||||
rc = mdb_txn_begin(env, NULL, 0, &txn);
|
||||
rc = mdb_dbi_open(txn, NULL, 0, &dbi);
|
||||
|
||||
key.mv_size = sizeof(int);
|
||||
key.mv_data = sval;
|
||||
data.mv_size = sizeof(sval);
|
||||
data.mv_data = sval;
|
||||
|
||||
sprintf(sval, "%03x %d foo bar", 32, 3141592);
|
||||
rc = mdb_put(txn, dbi, &key, &data, 0);
|
||||
rc = mdb_txn_commit(txn);
|
||||
if (rc) {
|
||||
fprintf(stderr, "mdb_txn_commit: (%d) %s\n", rc, mdb_strerror(rc));
|
||||
goto leave;
|
||||
}
|
||||
rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
|
||||
rc = mdb_cursor_open(txn, dbi, &cursor);
|
||||
while ((rc = mdb_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
|
||||
printf("key: %p %.*s, data: %p %.*s\n",
|
||||
key.mv_data, (int) key.mv_size, (char *) key.mv_data,
|
||||
data.mv_data, (int) data.mv_size, (char *) data.mv_data);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
mdb_txn_abort(txn);
|
||||
leave:
|
||||
mdb_dbi_close(env, dbi);
|
||||
mdb_env_close(env);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<tagfile>
|
||||
<compound kind="page">
|
||||
<name>mdb_copy_1</name>
|
||||
<title>mdb_copy - environment copy tool</title>
|
||||
<filename>mdb_copy.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_dump_1</name>
|
||||
<title>mdb_dump - environment export tool</title>
|
||||
<filename>mdb_dump.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_load_1</name>
|
||||
<title>mdb_load - environment import tool</title>
|
||||
<filename>mdb_load.1</filename>
|
||||
</compound>
|
||||
<compound kind="page">
|
||||
<name>mdb_stat_1</name>
|
||||
<title>mdb_stat - environment status tool</title>
|
||||
<filename>mdb_stat.1</filename>
|
||||
</compound>
|
||||
</tagfile>
|
||||
+225
-77
@@ -46,12 +46,27 @@ static inline void queue_check_thread(struct ll_queue* q) {
|
||||
// Предварительные объявления внутренних функций
|
||||
static void queue_resume_timeout_cb(void* arg);
|
||||
static void check_waiters(struct ll_queue* q);
|
||||
static void add_to_hash(struct ll_queue* q, struct ll_entry* entry);
|
||||
static void remove_from_hash(struct ll_queue* q, struct ll_entry* entry);
|
||||
static void empty_trampoline(void* arg);
|
||||
static void on_get_trampoline(void* arg);
|
||||
|
||||
static void empty_trampoline(void* arg) {
|
||||
struct ll_queue* q = (struct ll_queue*)arg;
|
||||
q->empty_call_soon_id = NULL;
|
||||
queue_callback_fn cb = q->empty_callback;
|
||||
void* cb_arg = q->empty_callback_arg;
|
||||
q->empty_callback = NULL;
|
||||
q->empty_callback_arg = NULL;
|
||||
if (q->count == 0 && cb) cb(q, cb_arg);
|
||||
}
|
||||
|
||||
static void on_get_trampoline(void* arg) {
|
||||
struct ll_queue* q = (struct ll_queue*)arg;
|
||||
if (q->on_get) q->on_get(q, q->on_get_arg);
|
||||
}
|
||||
|
||||
// ==================== Управление очередью ====================
|
||||
|
||||
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, char* name) {
|
||||
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_offset, uint16_t index_size, char* name) {
|
||||
if (!ua) return NULL;
|
||||
|
||||
struct ll_queue* q = u_calloc(1, sizeof(struct ll_queue));
|
||||
@@ -60,7 +75,12 @@ struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, char* name) {
|
||||
q->name = name;
|
||||
q->ua = ua;
|
||||
q->size_limit = -1; // Без ограничения по умолчанию
|
||||
q->threshold_max_packets = 0; // По умолчанию: ждать пустой очереди
|
||||
q->threshold_max_bytes = 0; // По умолчанию: не проверять байты
|
||||
q->waiter_defer = 0;
|
||||
q->hash_size = hash_size;
|
||||
q->index_offset = index_offset;
|
||||
q->index_size = index_size;
|
||||
|
||||
#ifdef QUEUE_THREAD_CHECK
|
||||
#ifdef _WIN32
|
||||
@@ -110,6 +130,17 @@ void queue_free(struct ll_queue* q) {
|
||||
// ВАЖНО: Не освобождаем элементы в очереди - они должны быть извлечены отдельно
|
||||
// Это упрощает архитектуру и предотвращает double-u_free
|
||||
|
||||
// Освободить список waiters (сбросить handle->internal)
|
||||
struct queue_waiter* w = q->waiter_head;
|
||||
while (w) {
|
||||
struct queue_waiter* next = w->next;
|
||||
if (w->handle) w->handle->internal = NULL;
|
||||
u_free(w);
|
||||
w = next;
|
||||
}
|
||||
q->waiter_head = NULL;
|
||||
q->waiter_tail = NULL;
|
||||
|
||||
// Освободить хеш-таблицу
|
||||
if (q->hash_table) {
|
||||
u_free(q->hash_table);
|
||||
@@ -120,6 +151,14 @@ void queue_free(struct ll_queue* q) {
|
||||
uasync_call_soon_cancel(q->ua, q->resume_timeout_id);
|
||||
q->resume_timeout_id = NULL;
|
||||
}
|
||||
|
||||
void* old_eid = q->empty_call_soon_id;
|
||||
q->empty_call_soon_id = NULL;
|
||||
if (old_eid) uasync_call_soon_cancel(q->ua, old_eid);
|
||||
q->empty_callback = NULL;
|
||||
q->empty_callback_arg = NULL;
|
||||
q->on_get = NULL;
|
||||
q->on_get_arg = NULL;
|
||||
|
||||
u_free(q);
|
||||
}
|
||||
@@ -225,8 +264,8 @@ void queue_entry_free(struct ll_entry* entry) {
|
||||
static void add_to_hash(struct ll_queue* q, struct ll_entry* entry) {
|
||||
if (!q || !entry) return;
|
||||
|
||||
if (q->hash_size == 0 || entry->index_size == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Empty hash_size=%d or index_size=%d",q->name, q->hash_size, entry->index_size);
|
||||
if (q->hash_size == 0 || q->index_size == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Empty hash_size=%d or index_size=%d",q->name, q->hash_size, q->index_size);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -264,24 +303,44 @@ static uint32_t make_hash(const void* data, uint16_t len) {// алгоритм F
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Проверить и запустить ожидающие коллбэки
|
||||
static void waiter_defer_cb(void* arg) {
|
||||
struct queue_waiter_handle* h = (struct queue_waiter_handle*)arg;
|
||||
h->defer_cb(h->defer_q, h->defer_arg);
|
||||
}
|
||||
|
||||
static inline void queue_waiter_call(struct ll_queue* q, queue_threshold_callback_fn cb, void* arg,
|
||||
struct queue_waiter_handle* h) {
|
||||
if (q->waiter_defer) {
|
||||
h->defer_q = q; h->defer_cb = cb; h->defer_arg = arg;
|
||||
uasync_call_soon(q->ua, h, waiter_defer_cb);
|
||||
} else {
|
||||
cb(q, arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Проверить и запустить первый ожидающий коллбэк (FIFO / round-robin)
|
||||
static void check_waiters(struct ll_queue* q) {
|
||||
if (!q) return;
|
||||
if (!q || !q->waiter_head) return;
|
||||
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: checking waiters, count=%d, bytes=%zu", q->count, q->total_bytes);
|
||||
const int cond = q->count <= q->threshold_max_packets
|
||||
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes);
|
||||
if (!cond) return;
|
||||
|
||||
struct queue_waiter* waiter = &q->waiter;
|
||||
struct queue_waiter* waiter = q->waiter_head;
|
||||
q->waiter_head = waiter->next;
|
||||
if (!q->waiter_head) q->waiter_tail = NULL;
|
||||
|
||||
if (waiter->callback) {
|
||||
// Проверить условие: не больше max_packets и не больше max_bytes
|
||||
// max_bytes = 0 означает "не проверять байты"
|
||||
if (q->count <= waiter->max_packets && (waiter->max_bytes == 0 || q->total_bytes <= waiter->max_bytes)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: condition met, calling callback, count=%d<=%d, bytes=%zu<=%zu (max_bytes_check=%s)",
|
||||
q->count, waiter->max_packets, q->total_bytes, waiter->max_bytes,
|
||||
waiter->max_bytes == 0 ? "disabled" : "enabled");
|
||||
waiter->callback(q, waiter->callback_arg);
|
||||
memset(waiter, 0, sizeof(*waiter));
|
||||
}
|
||||
struct queue_waiter_handle* h = waiter->handle;
|
||||
if (h) h->internal = NULL;
|
||||
|
||||
queue_threshold_callback_fn cb = waiter->callback;
|
||||
void* arg = waiter->callback_arg;
|
||||
u_free(waiter);
|
||||
|
||||
if (cb) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_LL_QUEUE, "check_waiters: waking head waiter, count=%d<=%d, bytes=%zu<=%zu",
|
||||
q->count, q->threshold_max_packets, q->total_bytes, q->threshold_max_bytes);
|
||||
queue_waiter_call(q, cb, arg, h);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,9 +350,11 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
entry->index_offset = 0;
|
||||
entry->index_size = 0;
|
||||
entry->index_hash = 0;
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put detected! entry=%p prev=%p next=%p len=%u",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next, entry->len);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->hash_size > 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Put no-hash data to hash queue",q->name);
|
||||
@@ -330,23 +391,26 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry, uint16_t index_offset, uint16_t index_size) {
|
||||
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry) {
|
||||
if (!q || !entry) return -1;
|
||||
#ifdef QUEUE_THREAD_CHECK
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (index_size == 0 || (size_t)index_offset + index_size > (size_t)entry->size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, index_offset, index_size, entry->size);
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_with_index detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->index_size == 0 || (size_t)q->index_offset + q->index_size > (size_t)entry->size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, q->index_offset, q->index_size, entry->size);
|
||||
queue_dgram_free(entry);
|
||||
queue_entry_free(entry);
|
||||
return -1;
|
||||
}
|
||||
|
||||
entry->index_offset = index_offset;
|
||||
entry->index_size = index_size;
|
||||
|
||||
entry->index_hash = make_hash(entry->data + index_offset, index_size);
|
||||
entry->index_hash = make_hash(entry->data + q->index_offset, q->index_size);
|
||||
|
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) {
|
||||
queue_dgram_free(entry);
|
||||
@@ -382,18 +446,14 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
entry->index_offset = 0;
|
||||
entry->index_size = 0;
|
||||
entry->index_hash = 0;
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_first detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->hash_size > 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] Put no-hash data to hash queue",q->name);
|
||||
queue_dgram_free(entry);
|
||||
queue_entry_free(entry);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) {
|
||||
queue_dgram_free(entry);
|
||||
queue_entry_free(entry); // как было в оригинале
|
||||
return -1;
|
||||
@@ -419,22 +479,26 @@ int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry) {
|
||||
}
|
||||
|
||||
// С хешем для put_first
|
||||
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry, uint16_t index_offset, uint16_t index_size) {
|
||||
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry) {
|
||||
if (!q || !entry) return -1;
|
||||
#ifdef QUEUE_THREAD_CHECK
|
||||
queue_check_thread(q);
|
||||
#endif
|
||||
|
||||
if (index_size == 0 || (size_t)index_offset + index_size > (size_t)entry->size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, index_offset, index_size, entry->size);
|
||||
if (entry->prev || entry->next || q->head == entry || q->tail == entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] double put_first_with_index detected! entry=%p prev=%p next=%p",
|
||||
q->name, (void*)entry, (void*)entry->prev, (void*)entry->next);
|
||||
abort();
|
||||
}
|
||||
|
||||
if (q->index_size == 0 || (size_t)q->index_offset + q->index_size > (size_t)entry->size) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] invalid index: offset=%u size=%u (data_size=%u)",q->name, q->index_offset, q->index_size, entry->size);
|
||||
queue_dgram_free(entry);
|
||||
queue_entry_free(entry);
|
||||
return -1;
|
||||
}
|
||||
|
||||
entry->index_offset = index_offset;
|
||||
entry->index_size = index_size;
|
||||
entry->index_hash = make_hash(entry->data + index_offset, index_size);
|
||||
entry->index_hash = make_hash(entry->data + q->index_offset, q->index_size);
|
||||
|
||||
if (q->size_limit >= 0 && q->count >= q->size_limit) {
|
||||
queue_dgram_free(entry);
|
||||
@@ -493,6 +557,12 @@ struct ll_entry* queue_data_get(struct ll_queue* q) {
|
||||
// Проверить ожидающие коллбэки
|
||||
check_waiters(q);
|
||||
|
||||
if (q->count == 0 && q->empty_callback && !q->empty_call_soon_id)
|
||||
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
|
||||
|
||||
if (q->on_get)
|
||||
uasync_call_soon(q->ua, q, (timeout_callback_t)on_get_trampoline);
|
||||
|
||||
#ifdef QUEUE_DEBUG
|
||||
queue_check_consistency(q);// !!!! for debug
|
||||
#endif
|
||||
@@ -506,14 +576,27 @@ int queue_entry_count(struct ll_queue* q) {
|
||||
// Функция проверки консистентности count и total_bytes
|
||||
// Возвращает 0 если ok, -1 если есть несоответствия
|
||||
int queue_check_consistency(struct ll_queue* q) {
|
||||
if (!q) return -1; // Недопустимая очередь
|
||||
if (!q) return -1;
|
||||
|
||||
// Проверка: если count > 0, то head не должен быть NULL
|
||||
if (q->count > 0 && !q->head) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': count=%d but head is NULL!",
|
||||
q->name ? q->name : "unknown", q->count);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Floyd cycle detection — за O(N) без риска зависания
|
||||
{
|
||||
struct ll_entry *slow = q->head, *fast = q->head;
|
||||
while (fast && fast->next) {
|
||||
slow = slow->next;
|
||||
fast = fast->next->next;
|
||||
if (slow == fast) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': CYCLE detected! entry=%p count=%d",
|
||||
q->name ? q->name : "unknown", (void*)slow, q->count);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int actual_count = 0;
|
||||
size_t actual_bytes = 0;
|
||||
@@ -524,7 +607,6 @@ int queue_check_consistency(struct ll_queue* q) {
|
||||
actual_bytes += current->int_len;
|
||||
if (current->next) {
|
||||
if (current->next->prev != current) {
|
||||
// Несоответствие в связях prev/next
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "Queue '%s': prev/next error at entry %p != %p entries: %d!=%d bytes: %zu!=%zu",
|
||||
q->name ? q->name : "unknown", (void*)current, (void*)current->next->prev, actual_count, q->count, actual_bytes, q->total_bytes);
|
||||
return -1;
|
||||
@@ -549,50 +631,116 @@ int queue_check_consistency(struct ll_queue* q) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ==================== Асинхронное ожидание ====================
|
||||
// ==================== Пороговое ожидание (backpressure) ====================
|
||||
|
||||
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
|
||||
queue_threshold_callback_fn callback, void* arg) {
|
||||
if (!q || !callback) return NULL;
|
||||
|
||||
struct queue_waiter* waiter = &q->waiter;
|
||||
|
||||
// Проверить условие немедленно
|
||||
if (q->count <= max_packets && (max_bytes == 0 || q->total_bytes <= max_bytes)) {
|
||||
// Условие уже выполнено - вызвать коллбэк немедленно
|
||||
callback(q, arg);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Установить waiter для отложенного вызова
|
||||
waiter->max_packets = max_packets;
|
||||
waiter->max_bytes = max_bytes;
|
||||
waiter->callback = callback;
|
||||
waiter->callback_arg = arg;
|
||||
|
||||
return waiter;
|
||||
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes) {
|
||||
if (!q) return;
|
||||
q->threshold_max_packets = max_packets;
|
||||
q->threshold_max_bytes = max_bytes;
|
||||
}
|
||||
|
||||
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter) {
|
||||
if (!q || !waiter || waiter != &q->waiter) return;
|
||||
void queue_set_waiter_defer(struct ll_queue* q, int enable) {
|
||||
if (!q) return;
|
||||
q->waiter_defer = enable;
|
||||
}
|
||||
|
||||
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
|
||||
if (!q) return;
|
||||
void* old = q->empty_call_soon_id;
|
||||
q->empty_call_soon_id = NULL;
|
||||
if (old) uasync_call_soon_cancel(q->ua, old);
|
||||
q->empty_callback = cbk_fn;
|
||||
q->empty_callback_arg = arg;
|
||||
if (cbk_fn && q->count == 0)
|
||||
q->empty_call_soon_id = uasync_call_soon(q->ua, q, (timeout_callback_t)empty_trampoline);
|
||||
}
|
||||
|
||||
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg) {
|
||||
if (!q) return;
|
||||
q->on_get = cbk_fn;
|
||||
q->on_get_arg = arg;
|
||||
}
|
||||
|
||||
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg) {
|
||||
if (!q || !h) return -1;
|
||||
|
||||
if (h->internal) queue_waiter_cancel(q, h);
|
||||
|
||||
if (q->count <= q->threshold_max_packets
|
||||
&& (q->threshold_max_bytes == 0 || q->total_bytes <= q->threshold_max_bytes)) {
|
||||
queue_waiter_call(q, callback, arg, h);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct queue_waiter* waiter = u_malloc(sizeof(struct queue_waiter));
|
||||
if (!waiter) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_wait: u_malloc failed", q->name);
|
||||
return -1;
|
||||
}
|
||||
waiter->next = NULL;
|
||||
waiter->handle = h;
|
||||
waiter->callback = callback;
|
||||
waiter->callback_arg = arg;
|
||||
h->internal = waiter;
|
||||
|
||||
if (q->waiter_tail) q->waiter_tail->next = waiter; else q->waiter_head = waiter;
|
||||
q->waiter_tail = waiter;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h) {
|
||||
if (!q || !h || !h->internal) return;
|
||||
|
||||
memset(waiter, 0, sizeof(*waiter));
|
||||
struct queue_waiter* waiter = h->internal;
|
||||
struct queue_waiter* prev = NULL;
|
||||
struct queue_waiter* curr = q->waiter_head;
|
||||
while (curr && curr != waiter) {
|
||||
prev = curr;
|
||||
curr = curr->next;
|
||||
}
|
||||
if (!curr) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_LL_QUEUE, "[%s] queue_waiter_cancel: waiter not in list", q->name);
|
||||
h->internal = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (prev) prev->next = waiter->next; else q->waiter_head = waiter->next;
|
||||
if (waiter == q->waiter_tail) q->waiter_tail = prev;
|
||||
|
||||
h->internal = NULL;
|
||||
u_free(waiter);
|
||||
}
|
||||
|
||||
// ==================== Поиск и удаление по ID ====================
|
||||
|
||||
struct ll_entry* queue_find_data_by_index(struct ll_queue* q, const void* index_key, uint16_t index_size) {
|
||||
if (!q || q->hash_size == 0 || !q->hash_table || index_size == 0 || !index_key) return NULL;
|
||||
struct ll_entry* queue_find_data_by_index(struct ll_queue* q, const void* index_key) {
|
||||
if (!q || q->hash_size == 0 || !q->hash_table || q->index_size == 0 || !index_key) return NULL;
|
||||
|
||||
uint32_t hash_val = make_hash(index_key, index_size);
|
||||
uint32_t hash_val = make_hash(index_key, q->index_size);
|
||||
|
||||
uint32_t slot = hash_val % q->hash_size;
|
||||
struct ll_entry* entry = q->hash_table[slot];
|
||||
|
||||
while (entry) {
|
||||
if (entry->index_size == index_size &&
|
||||
(size_t)entry->index_offset + index_size <= (size_t)entry->size &&
|
||||
memcmp(entry->data + entry->index_offset, index_key, index_size) == 0) {
|
||||
if ((size_t)q->index_offset + q->index_size <= (size_t)entry->size &&
|
||||
memcmp(entry->data + q->index_offset, index_key, q->index_size) == 0) {
|
||||
return entry;
|
||||
}
|
||||
entry = entry->hash_next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct ll_entry* queue_find_next_by_index(struct ll_queue* q, const void* index_key,
|
||||
struct ll_entry* prev_entry) {
|
||||
if (!q || q->hash_size == 0 || !index_key || q->index_size == 0 || !prev_entry) return NULL;
|
||||
|
||||
struct ll_entry* entry = prev_entry->hash_next;
|
||||
while (entry) {
|
||||
if ((size_t)q->index_offset + q->index_size <= (size_t)entry->size &&
|
||||
memcmp(entry->data + q->index_offset, index_key, q->index_size) == 0) {
|
||||
return entry;
|
||||
}
|
||||
entry = entry->hash_next;
|
||||
|
||||
+111
-39
@@ -12,7 +12,7 @@
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#define QUEUE_DEBUG 1
|
||||
//#define QUEUE_DEBUG 1 // раскомментировать для отладки очереди
|
||||
//#define QUEUE_THREAD_CHECK 1 // 0 to disable
|
||||
|
||||
/**
|
||||
@@ -26,7 +26,7 @@
|
||||
* - Обязательный вызов queue_resume_callback после обработки элемента
|
||||
* - Поддержка пулов памяти для entry и отдельно для dgram
|
||||
* - Хеш-таблица для быстрого поиска по ID (опционально)
|
||||
* - Встроенный одиночный waiter для ожидания освобождения очереди до заданного порога
|
||||
* - Связный список waiters для backpressure c общим порогом (round-robin)
|
||||
* - Использует uasync для отложенного возобновления callback'ов (без рекурсии в стеке)
|
||||
*
|
||||
* @note Важные правила использования автозабора (queue_set_callback):
|
||||
@@ -37,13 +37,15 @@
|
||||
* 5. queue_data_get() автоматически suspend'ит коллбэки для предотвращения рекурсии
|
||||
*
|
||||
* @note Память:
|
||||
* - queue_free() освобождает ТОЛЬКО структуру очереди и хеш-таблицу
|
||||
* - queue_free() освобождает структуру очереди, хеш-таблицу и список waiters
|
||||
* (handle->internal каждого ожидающего сбрасывается в NULL)
|
||||
* - Все элементы должны быть извлечены через queue_data_get() и освобождены через queue_entry_free()
|
||||
* - dgram освобождается отдельно через queue_dgram_free() или кастомную dgram_free_fn
|
||||
*/
|
||||
|
||||
// Forward declaration
|
||||
// Forward declarations
|
||||
struct ll_queue;
|
||||
struct queue_waiter_handle;
|
||||
|
||||
/**
|
||||
* @struct ll_entry
|
||||
@@ -63,8 +65,6 @@ struct ll_entry {
|
||||
uint8_t* dgram; // Указатель на данные пакета
|
||||
void (*dgram_free_fn)(uint8_t* data); // Кастомная функция освобождения dgram
|
||||
struct memory_pool* dgram_pool; // Пул для dgram (если используется)
|
||||
uint16_t index_offset; // Смещение индекса в data[]
|
||||
uint16_t index_size; // Длина индекса (0 = без индекса)
|
||||
uint32_t index_hash; // Хеш по первым 4 байтам индекса
|
||||
struct ll_entry* hash_next; // Следующий в хеш-цепочке
|
||||
struct memory_pool* pool; // Пул, из которого выделен сам entry (NULL = malloc)
|
||||
@@ -86,7 +86,7 @@ typedef void (*queue_callback_fn)(struct ll_queue* q, void* arg);
|
||||
|
||||
/**
|
||||
* @typedef queue_threshold_callback_fn
|
||||
* @brief Одноразовый коллбэк при достижении порога очереди (queue_wait_threshold).
|
||||
* @brief Одноразовый коллбэк при достижении порога очереди.
|
||||
* @param q указатель на очередь
|
||||
* @param arg пользовательский аргумент
|
||||
*/
|
||||
@@ -94,13 +94,29 @@ typedef void (*queue_threshold_callback_fn)(struct ll_queue* q, void* arg);
|
||||
|
||||
/**
|
||||
* @struct queue_waiter
|
||||
* @brief Описание ожидания освобождения места в очереди (встроен в ll_queue — только один).
|
||||
* @brief Внутренний узел в связном списке ожидающих (очередь владеет, аллоцирует/освобождает сама).
|
||||
*/
|
||||
struct queue_waiter {
|
||||
int max_packets; ///< Максимально допустимое количество элементов
|
||||
size_t max_bytes; ///< Максимально допустимый объём данных (0 = не проверять)
|
||||
queue_threshold_callback_fn callback; ///< Коллбэк (вызывается один раз)
|
||||
void* callback_arg; ///< Аргумент коллбэка
|
||||
struct queue_waiter* next; ///< Следующий в списке ожидающих
|
||||
struct queue_waiter_handle* handle; ///< Обратная ссылка на публичную структуру
|
||||
queue_threshold_callback_fn callback;
|
||||
void* callback_arg;
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct queue_waiter_handle
|
||||
* @brief Публичная управляющая структура (встраивается в структуру вызывающей стороны).
|
||||
*
|
||||
* Вызывающая сторона:
|
||||
* 1. Объявляет поле struct queue_waiter_handle в своей структуре (= {0} или u_calloc)
|
||||
* 3. Вызывает queue_waiter_wait() когда хочет дождаться освобождения очереди
|
||||
* 4. Вызывает queue_waiter_cancel() при деинициализации для отмены ожидания
|
||||
*/
|
||||
struct queue_waiter_handle {
|
||||
struct queue_waiter* internal; ///< NULL = не ждём; иначе — внутренний узел
|
||||
struct ll_queue* defer_q;
|
||||
queue_threshold_callback_fn defer_cb;
|
||||
void* defer_arg;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -122,10 +138,23 @@ struct ll_queue {
|
||||
void* resume_timeout_id; // ID таймера uasync для отложенного resume
|
||||
struct UASYNC* ua; // Экземпляр uasync (обязателен для таймеров)
|
||||
|
||||
struct queue_waiter waiter; // Встроенный waiter (только один)
|
||||
struct queue_waiter* waiter_head; // Голова списка ожидающих
|
||||
struct queue_waiter* waiter_tail; // Хвост списка ожидающих
|
||||
int threshold_max_packets; // Общий порог: макс. кол-во элементов (по умолчанию 0)
|
||||
size_t threshold_max_bytes; // Общий порог: макс. объём данных (0 = не проверять)
|
||||
int waiter_defer; // 0=immediate callback, 1=defer via uasync_call_soon
|
||||
|
||||
queue_callback_fn empty_callback; // одноразовый коллбэк при опустошении очереди
|
||||
void* empty_callback_arg;
|
||||
void* empty_call_soon_id; // handle от uasync_call_soon
|
||||
|
||||
queue_callback_fn on_get; // коллбэк после каждого queue_data_get (deferred)
|
||||
void* on_get_arg;
|
||||
|
||||
struct ll_entry** hash_table; // Хеш-таблица для поиска по id (если hash_size > 0)
|
||||
size_t hash_size; // Размер хеш-таблицы
|
||||
uint16_t index_offset; // Смещение индекса в data[] (для всех entry очереди)
|
||||
uint16_t index_size; // Размер индекса (0 = без индекса)
|
||||
|
||||
#ifdef QUEUE_THREAD_CHECK
|
||||
#ifdef _WIN32
|
||||
@@ -142,9 +171,12 @@ struct ll_queue {
|
||||
* @brief Создаёт новую пустую очередь.
|
||||
* @param ua экземпляр uasync (обязателен для отложенного resume коллбэков)
|
||||
* @param hash_size размер хеш-таблицы (0 = поиск по id отключён)
|
||||
* @param index_offset смещение ключа индекса в data[] каждого entry (0 если hash_size=0)
|
||||
* @param index_size размер ключа индекса (0 если hash_size=0)
|
||||
* @param name имя очереди для отладки
|
||||
* @return указатель на очередь или NULL при ошибке выделения памяти
|
||||
*/
|
||||
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, char* name);
|
||||
struct ll_queue* queue_new(struct UASYNC* ua, size_t hash_size, uint16_t index_offset, uint16_t index_size, char* name);
|
||||
|
||||
/**
|
||||
* @brief Освобождает структуру очереди.
|
||||
@@ -187,26 +219,62 @@ void queue_set_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg)
|
||||
*/
|
||||
void queue_resume_callback(struct ll_queue* q);
|
||||
|
||||
/* ==================== Пороговое ожидание ==================== */
|
||||
/* ==================== Пороговое ожидание (backpressure) ==================== */
|
||||
|
||||
/**
|
||||
* @brief Регистрирует одноразовый коллбэк, который сработает, когда очередь освободится до заданного порога.
|
||||
* @brief Устанавливает общий порог освобождения очереди.
|
||||
* @param q очередь
|
||||
* @param max_packets максимальное количество элементов
|
||||
* @param max_packets максимальное количество элементов (условие: count <= max_packets)
|
||||
* @param max_bytes максимальный объём данных (0 = не проверять)
|
||||
*
|
||||
* Порог — общий на всю очередь. Все waiters в связном списке используют этот порог.
|
||||
* При достижении порога вызывается следующий waiter из списка (FIFO / round-robin).
|
||||
* По умолчанию: max_packets=0, max_bytes=0 (пустая очередь).
|
||||
*/
|
||||
void queue_set_threshold(struct ll_queue* q, int max_packets, size_t max_bytes);
|
||||
|
||||
void queue_set_waiter_defer(struct ll_queue* q, int enable);
|
||||
|
||||
/**
|
||||
* @brief Устанавливает одноразовый коллбэк при опустошении очереди (count==0).
|
||||
* @param q очередь
|
||||
* @param cbk_fn коллбэк (NULL для отмены)
|
||||
* @param arg пользовательский аргумент
|
||||
*
|
||||
* Коллбэк вызывается асинхронно (через uasync_call_soon) когда count становится 0.
|
||||
* После вызова сбрасывается — для повторного использования нужно вызвать снова.
|
||||
*/
|
||||
void queue_set_empty_callback(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Устанавливает коллбэк вызываемый после каждого queue_data_get (deferred через call_soon).
|
||||
* @param q очередь
|
||||
* @param cbk_fn коллбэк (NULL для отмены)
|
||||
* @param arg пользовательский аргумент
|
||||
*/
|
||||
void queue_set_on_get(struct ll_queue* q, queue_callback_fn cbk_fn, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Регистрирует ожидание освобождения очереди до общего порога.
|
||||
* @param q очередь
|
||||
* @param h указатель на handle (создаётся как `struct queue_waiter_handle h = {0}` или `h.internal = NULL`)
|
||||
* @param callback функция, вызываемая при достижении порога
|
||||
* @param arg аргумент коллбэка
|
||||
* @return указатель на waiter (для отмены) или NULL, если условие уже выполнено (коллбэк вызван сразу)
|
||||
* @return 1 — условие уже выполнено (callback вызван немедленно), 0 — зарегистрирован в списке, -1 — ошибка
|
||||
*
|
||||
* Если handle уже ожидает (h->internal != NULL) — старый waiter отменяется и создаётся новый.
|
||||
*/
|
||||
struct queue_waiter* queue_wait_threshold(struct ll_queue* q, int max_packets, size_t max_bytes,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
int queue_waiter_wait(struct ll_queue* q, struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
|
||||
/**
|
||||
* @brief Отменяет ранее установленное ожидание порога.
|
||||
* @brief Отменяет ожидание (удаляет из списка, если зарегистрирован).
|
||||
* @param q очередь
|
||||
* @param waiter указатель, возвращённый queue_wait_threshold()
|
||||
* @param h указатель на handle
|
||||
*
|
||||
* Безопасно вызывать в любом состоянии: если waiter не зарегистрирован — ничего не делает.
|
||||
*/
|
||||
void queue_cancel_wait(struct ll_queue* q, struct queue_waiter* waiter);
|
||||
void queue_waiter_cancel(struct ll_queue* q, struct queue_waiter_handle* h);
|
||||
|
||||
/* ==================== Работа с данными ==================== */
|
||||
|
||||
@@ -229,26 +297,20 @@ int queue_data_put(struct ll_queue* q, struct ll_entry* entry);
|
||||
int queue_data_put_first(struct ll_queue* q, struct ll_entry* entry);
|
||||
|
||||
/**
|
||||
* @brief Добавляет элемент в конец очереди с явным указанием смещения индекса для hash.
|
||||
* @brief Добавляет элемент в конец очереди с хеш-индексом (использует index_offset/index_size из очереди).
|
||||
* @param q очередь
|
||||
* @param entry элемент
|
||||
* @param index_offset смещение индекса (node_id) в data[]
|
||||
* @param index_size размер индекса (8 для uint64_t node_id)
|
||||
* @return 0 — успех, -1 — ошибка
|
||||
*/
|
||||
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry,
|
||||
uint16_t index_offset, uint16_t index_size);
|
||||
int queue_data_put_with_index(struct ll_queue* q, struct ll_entry* entry);
|
||||
|
||||
/**
|
||||
* @brief Добавляет элемент в начало очереди с явным указанием смещения индекса для hash.
|
||||
* @brief Добавляет элемент в начало очереди с хеш-индексом (использует index_offset/index_size из очереди).
|
||||
* @param q очередь
|
||||
* @param entry элемент
|
||||
* @param index_offset смещение индекса в data[]
|
||||
* @param index_size размер индекса
|
||||
* @return 0 — успех, -1 — ошибка
|
||||
*/
|
||||
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry,
|
||||
uint16_t index_offset, uint16_t index_size);
|
||||
int queue_data_put_first_with_index(struct ll_queue* q, struct ll_entry* entry);
|
||||
|
||||
|
||||
/**
|
||||
@@ -311,14 +373,24 @@ void queue_dgram_free(struct ll_entry* entry);
|
||||
/* ==================== Поиск и удаление ==================== */
|
||||
|
||||
/**
|
||||
* @brief Находит элемент по идентификатору (требуется hash_size > 0 при создании).
|
||||
* @param q очередь
|
||||
* @param id искомый идентификатор
|
||||
* @brief Находит элемент по ключу индекса (требуется hash_size > 0 при создании).
|
||||
* @param q очередь (размер ключа берётся из q->index_size)
|
||||
* @param index_key искомый ключ
|
||||
* @return элемент или NULL
|
||||
*/
|
||||
struct ll_entry* queue_find_data_by_index(struct ll_queue* q,
|
||||
const void* index_key,
|
||||
uint16_t index_size);
|
||||
const void* index_key);
|
||||
|
||||
/**
|
||||
* @brief Находит следующий элемент с тем же ключом (продолжение поиска).
|
||||
* @param q очередь (размер ключа берётся из q->index_size)
|
||||
* @param index_key искомый ключ
|
||||
* @param prev_entry предыдущий найденный элемент (от queue_find_data_by_index или предыдущего queue_find_next_by_index)
|
||||
* @return следующий элемент с тем же ключом или NULL
|
||||
*/
|
||||
struct ll_entry* queue_find_next_by_index(struct ll_queue* q,
|
||||
const void* index_key,
|
||||
struct ll_entry* prev_entry);
|
||||
|
||||
/**
|
||||
* @brief Удаляет элемент из очереди (не освобождает память).
|
||||
@@ -336,7 +408,7 @@ int queue_remove_data(struct ll_queue* q, struct ll_entry* entry);
|
||||
* @return количество байт
|
||||
*/
|
||||
static inline size_t queue_total_bytes(struct ll_queue* q) {
|
||||
return q ? q->total_bytes : 0;
|
||||
return q->total_bytes;
|
||||
}
|
||||
|
||||
#endif // LL_QUEUE_H
|
||||
@@ -14,13 +14,14 @@
|
||||
#define PADDING_FILL 0xAA
|
||||
#define METADATA_SIZE 128
|
||||
#define POINTER_SIZE sizeof(void*)
|
||||
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE)
|
||||
#define NEXT_OFFSET LOCATION_MAX
|
||||
#define LOCATION_MAX (METADATA_SIZE - POINTER_SIZE * 2)
|
||||
#define PREV_OFFSET LOCATION_MAX
|
||||
#define NEXT_OFFSET (LOCATION_MAX + POINTER_SIZE)
|
||||
|
||||
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
|
||||
#include <execinfo.h>
|
||||
#include <pthread.h>
|
||||
#define BACKTRACE_ENABLED 1
|
||||
#define BACKTRACE_ENABLED 0
|
||||
#elif defined(_WIN32)
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
@@ -50,28 +51,31 @@ static void init_mutex() {
|
||||
|
||||
static void* allocated_head = NULL;
|
||||
static size_t allocated_count = 0;
|
||||
static size_t allocations_count = 0;
|
||||
static size_t frees_count = 0;
|
||||
|
||||
static void hex_dump(const void* mem, size_t len) {
|
||||
const uint8_t* data = (const uint8_t*)mem;
|
||||
char line[128];
|
||||
for (size_t i = 0; i < len; i += 16) {
|
||||
printf("%08zx: ", i);
|
||||
int off = snprintf(line, sizeof(line), "%08zx: ", i);
|
||||
for (size_t j = 0; j < 16; j++) {
|
||||
if (i + j < len) {
|
||||
printf("%02x ", data[i + j]);
|
||||
off += snprintf(line + off, sizeof(line) - off, "%02x ", data[i + j]);
|
||||
} else {
|
||||
printf(" ");
|
||||
off += snprintf(line + off, sizeof(line) - off, " ");
|
||||
}
|
||||
}
|
||||
printf(" ");
|
||||
off += snprintf(line + off, sizeof(line) - off, " ");
|
||||
for (size_t j = 0; j < 16; j++) {
|
||||
if (i + j < len) {
|
||||
uint8_t c = data[i + j];
|
||||
printf("%c", (c >= 32 && c <= 126) ? c : '.');
|
||||
off += snprintf(line + off, sizeof(line) - off, "%c", (c >= 32 && c <= 126) ? c : '.');
|
||||
} else {
|
||||
printf(" ");
|
||||
off += snprintf(line + off, sizeof(line) - off, " ");
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s", line);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,34 +91,30 @@ void u_check(void* ptr, const char* text, const char* location) {
|
||||
bool error = false;
|
||||
for (size_t i = 0; i < BOUNDARY_CHECK_SIZE; i++) {
|
||||
if (prefix_start[i] != PADDING_FILL) {
|
||||
printf("%s: %s: underflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, prefix_start[i]);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, prefix_start[i]);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)", location, text, ptr, i, PADDING_FILL, prefix_start[i]);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
if (*pre_canary != CANARY) {
|
||||
printf("%s: %s: underflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *pre_canary);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *pre_canary);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: underflow canary corrupted at %p (expected 0x%X, got 0x%X)", location, text, ptr, CANARY, *pre_canary);
|
||||
error = true;
|
||||
}
|
||||
uint8_t* post_start = user + size + 4;
|
||||
for (size_t i = 0; i < BOUNDARY_CHECK_SIZE; i++) {
|
||||
if (post_start[i] != PADDING_FILL) {
|
||||
printf("%s: %s: overflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, post_start[i]);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)\n", location, text, ptr, i, PADDING_FILL, post_start[i]);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow prefix corrupted at %p, byte %zu (expected 0x%02X, got 0x%02X)", location, text, ptr, i, PADDING_FILL, post_start[i]);
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
uint32_t* post_canary = (uint32_t*)(user + size);
|
||||
if (*post_canary != CANARY) {
|
||||
printf("%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *post_canary);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)\n", location, text, ptr, CANARY, *post_canary);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: overflow canary corrupted at %p (expected 0x%X, got 0x%X)", location, text, ptr, CANARY, *post_canary);
|
||||
error = true;
|
||||
}
|
||||
if (error) {
|
||||
printf("%s: %s: Allocated at: %s\n", location, text, alloc_loc);
|
||||
printf("%s: %s: Memory block size (user): %u bytes, total allocated: %u bytes\n", location, text, size, total);
|
||||
printf("%s: %s: Full memory dump at base %p:\n", location, text, base);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Allocated at: %s", location, text, alloc_loc);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Memory block size (user): %u bytes, total allocated: %u bytes", location, text, size, total);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "%s: %s: Full memory dump at base %p:", location, text, base);
|
||||
hex_dump(base, total);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@@ -137,10 +137,15 @@ void* u_malloc_impl(uint32_t size, const char* location) {
|
||||
*(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 8 + size) = CANARY;
|
||||
// Add to list (thread-safe)
|
||||
LOCK();
|
||||
void** prev_ptr = (void**)(base + PREV_OFFSET);
|
||||
void** next_ptr = (void**)(base + NEXT_OFFSET);
|
||||
*prev_ptr = NULL;
|
||||
*next_ptr = allocated_head;
|
||||
if (allocated_head)
|
||||
*(void**)((uint8_t*)allocated_head + PREV_OFFSET) = base;
|
||||
allocated_head = base;
|
||||
allocated_count++;
|
||||
allocations_count++;
|
||||
UNLOCK();
|
||||
return prefix_start + BOUNDARY_CHECK_SIZE + 8;
|
||||
}
|
||||
@@ -173,30 +178,48 @@ void u_free_impl(void* ptr, const char* location) {
|
||||
if (!ptr) return;
|
||||
u_check(ptr, "u_free", location);
|
||||
uint8_t* base = (uint8_t*)ptr - 8 - BOUNDARY_CHECK_SIZE - METADATA_SIZE;
|
||||
// Remove from linked list (thread-safe)
|
||||
char* alloc_loc = (char*)base;
|
||||
uint32_t size = *(uint32_t*)(base + METADATA_SIZE + BOUNDARY_CHECK_SIZE + 4);
|
||||
// Remove from doubly-linked list (O(1))
|
||||
LOCK();
|
||||
void* prev = NULL;
|
||||
void* curr = allocated_head;
|
||||
bool found = false;
|
||||
while (curr) {
|
||||
if (curr == base) {
|
||||
if (prev) {
|
||||
*(void**)((uint8_t*)prev + NEXT_OFFSET) = *(void**)((uint8_t*)curr + NEXT_OFFSET);
|
||||
} else {
|
||||
allocated_head = *(void**)((uint8_t*)curr + NEXT_OFFSET);
|
||||
}
|
||||
allocated_count--;
|
||||
found = true;
|
||||
break;
|
||||
void* prev = *(void**)(base + PREV_OFFSET);
|
||||
void* next = *(void**)(base + NEXT_OFFSET);
|
||||
size_t active_count = allocated_count;
|
||||
|
||||
if (prev == NULL && allocated_head != base) {
|
||||
UNLOCK();
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "DOUBLE FREE DETECTED");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "========================================");
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Free attempt at: %s", location);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Pointer: %p", ptr);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Base: %p", (void*)base);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Allocated at: %s", alloc_loc);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "User size: %u bytes", size);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Total allocated: %u bytes", size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Active allocs: %zu", active_count);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- Metadata dump at %p (%u bytes) ---", (void*)base, (unsigned)(METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8));
|
||||
hex_dump(base, METADATA_SIZE + BOUNDARY_CHECK_SIZE + 8);
|
||||
if (size > 0 && size <= 1024 * 1024) {
|
||||
uint32_t dump_size = (size > 256) ? 256 : size;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "--- User data dump at %p (%u of %u bytes) ---", ptr, dump_size, size);
|
||||
hex_dump(ptr, dump_size);
|
||||
}
|
||||
prev = curr;
|
||||
curr = *(void**)((uint8_t*)curr + NEXT_OFFSET);
|
||||
}
|
||||
UNLOCK();
|
||||
if (!found) {
|
||||
printf("%s: u_free: Attempt to free unallocated or already freed pointer %p\n", location, ptr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (prev)
|
||||
*(void**)((uint8_t*)prev + NEXT_OFFSET) = next;
|
||||
else
|
||||
allocated_head = next;
|
||||
if (next)
|
||||
*(void**)((uint8_t*)next + PREV_OFFSET) = prev;
|
||||
|
||||
allocated_count--;
|
||||
frees_count++;
|
||||
UNLOCK();
|
||||
uint32_t total = size + 12 + 2 * BOUNDARY_CHECK_SIZE + METADATA_SIZE;
|
||||
memset(base, 0, total);
|
||||
free(base);
|
||||
}
|
||||
|
||||
@@ -212,12 +235,15 @@ void u_report_unfreed_blocks(void) {
|
||||
LOCK();
|
||||
size_t count = allocated_count;
|
||||
void* head = allocated_head;
|
||||
size_t alloc_count = allocations_count;
|
||||
size_t free_count = frees_count;
|
||||
UNLOCK();
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory stats: allocations=%zu, frees=%zu, current=%zu", alloc_count, free_count, count);
|
||||
if (count == 0) {
|
||||
printf("No unfreed memory blocks.\n");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "No unfreed memory blocks.");
|
||||
return;
|
||||
}
|
||||
printf("Unfreed memory blocks: %zu\n", count);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Unfreed memory blocks: %zu, allocations: %zu frees: %zu", count, allocations_count, frees_count);
|
||||
void* curr = head;
|
||||
while (curr) {
|
||||
char* alloc_loc = (char*)curr;
|
||||
@@ -225,7 +251,13 @@ void u_report_unfreed_blocks(void) {
|
||||
uint32_t size = *(uint32_t*)(prefix_start + BOUNDARY_CHECK_SIZE + 4);
|
||||
void* user_ptr = prefix_start + BOUNDARY_CHECK_SIZE + 8;
|
||||
u_check(user_ptr, "unfreed_check", "u_report_unfreed_blocks");
|
||||
printf(" - Allocated at: %s, size: %u bytes\n", alloc_loc, size);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " - Allocated at: %s, size: %u bytes", alloc_loc, size);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " Full memory dump at %p:", user_ptr);
|
||||
hex_dump(user_ptr, size);
|
||||
curr = *(void**)((uint8_t*)curr + NEXT_OFFSET);
|
||||
}
|
||||
}
|
||||
|
||||
size_t u_get_allocated_count(void) {
|
||||
return allocated_count;
|
||||
}
|
||||
@@ -24,6 +24,7 @@ void* u_realloc_impl(void* ptr, uint32_t size, const char* location);
|
||||
void u_free_impl(void* ptr, const char* location);
|
||||
char* u_strdup_impl(const char* s, const char* location);
|
||||
void u_report_unfreed_blocks(void);
|
||||
size_t u_get_allocated_count(void);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+82
-11
@@ -5,8 +5,57 @@
|
||||
#include "memory_pool.h"
|
||||
#include "mem.h"
|
||||
|
||||
// Layout: [user_data: object_size] [canary: 4 bytes] [alloc_loc: pointer] [counter: 1 byte]
|
||||
#define POOL_CANARY_OFF (pool->object_size)
|
||||
#define POOL_LOC_OFF (pool->object_size + 4)
|
||||
#define POOL_COUNTER_OFF (pool->object_size + 4 + sizeof(const char*))
|
||||
#define POOL_ALLOC_SIZE (pool->object_size + 4 + sizeof(const char*) + 1)
|
||||
#define POOL_CANARY_VAL 0xDEADBEAF
|
||||
|
||||
static void pool_init_tags(struct memory_pool* pool, void* obj, const char* location)
|
||||
{
|
||||
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
|
||||
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
|
||||
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
|
||||
*canary = POOL_CANARY_VAL;
|
||||
*loc = location;
|
||||
*counter = 1;
|
||||
}
|
||||
|
||||
static void pool_check_and_clear_tags(struct memory_pool* pool, void* obj, const char* location)
|
||||
{
|
||||
uint32_t* canary = (uint32_t*)((uint8_t*)obj + POOL_CANARY_OFF);
|
||||
const char** loc = (const char**)((uint8_t*)obj + POOL_LOC_OFF);
|
||||
uint8_t* counter = (uint8_t*)obj + POOL_COUNTER_OFF;
|
||||
|
||||
if (*canary != POOL_CANARY_VAL) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free BUFFER OVERFLOW pool=%p name=%s sz=%zu allocs=%zu reuse=%zu free=%d",
|
||||
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count, pool->free_count);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, " obj=%p alloc=%s free=%s canary=0x%08x expected=0x%08x",
|
||||
obj, *loc ? *loc : "(null)", location, *canary, POOL_CANARY_VAL);
|
||||
if (pool->object_size)
|
||||
log_dump(DEBUG_LEVEL_ERROR, DEBUG_CATEGORY_MEMORY, " user_data", obj, pool->object_size);
|
||||
volatile int _halt = 1;
|
||||
while (_halt) {}
|
||||
}
|
||||
if (*counter == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "pool_free DOUBLE FREE pool=%p name=%s sz=%zu allocs=%zu reuse=%zu alloc=%s free=%s",
|
||||
pool, pool->name ? pool->name : "?", pool->object_size, pool->allocations, pool->reuse_count,
|
||||
*loc ? *loc : "(null)", location);
|
||||
volatile int _halt = 1;
|
||||
while (_halt) {}
|
||||
}
|
||||
*counter = 0;
|
||||
}
|
||||
|
||||
static size_t g_total_free = 0;
|
||||
|
||||
size_t memory_pool_get_total_free_blocks(void) {
|
||||
return g_total_free;
|
||||
}
|
||||
|
||||
// Инициализировать пул памяти
|
||||
struct memory_pool* memory_pool_init(size_t object_size) {
|
||||
struct memory_pool* memory_pool_init(size_t object_size, const char* name) {
|
||||
struct memory_pool* pool = u_calloc(1, sizeof(struct memory_pool));
|
||||
if (!pool) {
|
||||
return NULL;
|
||||
@@ -16,44 +65,55 @@ struct memory_pool* memory_pool_init(size_t object_size) {
|
||||
pool->free_count = 0;
|
||||
pool->allocations = 0;
|
||||
pool->reuse_count = 0;
|
||||
pool->alloc_tag_counter = 1;
|
||||
pool->name = name;
|
||||
return pool;
|
||||
}
|
||||
|
||||
// Выделить объект из пула или из malloc
|
||||
void* memory_pool_alloc(struct memory_pool* pool) {
|
||||
void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location) {
|
||||
if (!pool) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pool->free_head) {
|
||||
void* obj = pool->free_head;
|
||||
pool->free_head = *(void**)obj; // Извлекаем next из начала блока
|
||||
pool->free_head = *(void**)obj;
|
||||
pool->free_count--;
|
||||
g_total_free--;
|
||||
pool->reuse_count++;
|
||||
memset(obj, 0, pool->object_size); // Опционально: очищаем память для безопасности
|
||||
memset(obj, 0, pool->object_size);
|
||||
pool_init_tags(pool, obj, location);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc reused: %s, remaining=%zu", location, pool->free_count);
|
||||
return obj;
|
||||
}
|
||||
|
||||
pool->allocations++;
|
||||
return u_calloc(1, pool->object_size); // Используем calloc для инициализации нулями
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_alloc: %s, total_allocs=%zu", location, pool->allocations);
|
||||
void* obj = u_calloc_impl(1, POOL_ALLOC_SIZE, location);
|
||||
if (obj) pool_init_tags(pool, obj, location);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Освободить объект в пул или в free
|
||||
void memory_pool_free(struct memory_pool* pool, void* obj) {
|
||||
void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* location) {
|
||||
if (!pool || !obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Если пул не заполнен, сохранить объект для повторного использования
|
||||
pool_check_and_clear_tags(pool, obj, location);
|
||||
|
||||
if (pool->free_count < MEMORY_POOL_MAX_FREE) {
|
||||
*(void**)obj = pool->free_head; // Сохраняем next в начало блока
|
||||
*(void**)obj = pool->free_head;
|
||||
pool->free_head = obj;
|
||||
pool->free_count++;
|
||||
g_total_free++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_free returned: %s, free_count=%zu", location, pool->free_count);
|
||||
return;
|
||||
}
|
||||
|
||||
// Иначе освободить через free
|
||||
u_free(obj);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_MEMORY, "pool_free direct: %s", location);
|
||||
u_free_impl(obj, location);
|
||||
}
|
||||
|
||||
// Получить статистику пула
|
||||
@@ -75,10 +135,10 @@ void memory_pool_destroy(struct memory_pool* pool) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Освободить все объекты в пуле
|
||||
void* obj = pool->free_head;
|
||||
while (obj) {
|
||||
void* next = *(void**)obj;
|
||||
g_total_free--;
|
||||
u_free(obj);
|
||||
obj = next;
|
||||
}
|
||||
@@ -87,3 +147,14 @@ void memory_pool_destroy(struct memory_pool* pool) {
|
||||
pool->free_count = 0;
|
||||
u_free(pool);
|
||||
}
|
||||
|
||||
int memory_pool_is_freed(struct memory_pool* pool, void* obj)
|
||||
{
|
||||
if (!pool || !obj) return 0;
|
||||
void* cur = pool->free_head;
|
||||
while (cur) {
|
||||
if (cur == obj) return 1;
|
||||
cur = *(void**)cur;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+22
-9
@@ -3,25 +3,38 @@
|
||||
#define MEMORY_POOL_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include "debug_config.h"
|
||||
|
||||
#define MEMORY_POOL_MAX_FREE 64 // Максимальное количество свободных объектов в пуле (чтобы не накапливать слишком много)
|
||||
#define MEMORY_POOL_MAX_FREE 64
|
||||
|
||||
#define XSTR(s) STR(s)
|
||||
#define STR(s) #s
|
||||
#define PLOCATION __FILE__ ":" XSTR(__LINE__) "[POOL]"
|
||||
|
||||
struct memory_pool {
|
||||
void* free_head; // Голова linked list свободных блоков (next хранится в начале каждого блока)
|
||||
int free_count; // Количество свободных блоков
|
||||
size_t object_size; // Размер объектов в пуле
|
||||
size_t allocations; // Общее количество аллокаций (включая новые malloc)
|
||||
size_t reuse_count; // Количество повторных использований из пула
|
||||
void* free_head; // Голова linked list свободных блоков (next хранится в начале каждого блока)
|
||||
int free_count; // Количество свободных блоков
|
||||
size_t object_size; // Размер объектов в пуле (пользовательские данные)
|
||||
size_t allocations; // Общее количество аллокаций (включая новые malloc)
|
||||
size_t reuse_count; // Количество повторных использований из пула
|
||||
uint8_t alloc_tag_counter; // монотонный счётчик для детекции double-free (1 байт)
|
||||
const char* name; // Имя пула для диагностики (напр. "data_pool", "pkt_pool")
|
||||
};
|
||||
|
||||
// сам пул:
|
||||
// если используем с элементами ll_queue то не забываем что к object_size надо прибавить sizeof(struct ll_entry)
|
||||
struct memory_pool* memory_pool_init(size_t object_size);
|
||||
struct memory_pool* memory_pool_init(size_t object_size, const char* name);
|
||||
void memory_pool_destroy(struct memory_pool* pool);
|
||||
|
||||
// элементы пула:
|
||||
void* memory_pool_alloc(struct memory_pool* pool);
|
||||
void memory_pool_free(struct memory_pool* pool, void* obj);
|
||||
void* memory_pool_alloc_impl(struct memory_pool* pool, const char* location);
|
||||
void memory_pool_free_impl(struct memory_pool* pool, void* obj, const char* location);
|
||||
void memory_pool_get_stats(struct memory_pool* pool, size_t* allocations, size_t* reuse_count);
|
||||
size_t memory_pool_get_total_free_blocks(void);
|
||||
|
||||
#define memory_pool_alloc(pool) memory_pool_alloc_impl(pool, PLOCATION)
|
||||
#define memory_pool_free(pool, obj) memory_pool_free_impl(pool, obj, PLOCATION)
|
||||
|
||||
int memory_pool_is_freed(struct memory_pool* pool, void* obj);
|
||||
|
||||
#endif // MEMORY_POOL_H
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
/* outgoing_iface.c
|
||||
*
|
||||
* Определяет IPv4-адрес и индекс/имя исходящего интерфейса,
|
||||
* который ОС выберет при отправке на заданный destination
|
||||
* после bind на 0.0.0.0 (INADDR_ANY).
|
||||
*
|
||||
* Сборка:
|
||||
* Linux / BSD: gcc -o out out.c
|
||||
* Windows (MSVC): cl out.c /link ws2_32.lib iphlpapi.lib
|
||||
* Windows (MinGW): gcc -o out.exe out.c -lws2_32 -liphlpapi
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* ===================== Платформенные заголовки ===================== */
|
||||
#ifdef _WIN32
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <winsock2.h>
|
||||
# include <ws2tcpip.h>
|
||||
# include <iphlpapi.h>
|
||||
# pragma comment(lib, "ws2_32.lib")
|
||||
# pragma comment(lib, "iphlpapi.lib")
|
||||
|
||||
typedef SOCKET socket_t;
|
||||
# define SOCKET_INVALID INVALID_SOCKET
|
||||
# define SOCKET_ERRNO WSAGetLastError()
|
||||
|
||||
/* На Windows нет IFNAMSIZ; берём с запасом */
|
||||
# ifndef IF_NAMESIZE
|
||||
# define IF_NAMESIZE 256
|
||||
# endif
|
||||
|
||||
#else /* Linux, FreeBSD, OpenBSD, NetBSD, macOS, … */
|
||||
# include <unistd.h>
|
||||
# include <sys/types.h>
|
||||
# include <sys/socket.h>
|
||||
# include <netinet/in.h>
|
||||
# include <arpa/inet.h>
|
||||
# include <ifaddrs.h>
|
||||
# include <net/if.h>
|
||||
# include <errno.h>
|
||||
|
||||
typedef int socket_t;
|
||||
# define SOCKET_INVALID (-1)
|
||||
# define SOCKET_ERRNO errno
|
||||
|
||||
# ifndef IF_NAMESIZE
|
||||
# define IF_NAMESIZE IFNAMSIZ /* обычно 16 */
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/* ===================== Структура результата ===================== */
|
||||
typedef struct {
|
||||
char ip[INET_ADDRSTRLEN]; /* IP строкой, например "192.168.1.5" */
|
||||
char name[IF_NAMESIZE]; /* имя интерфейса: "eth0", "em0", "Wi-Fi" … */
|
||||
uint32_t ifindex; /* числовой ifindex (if_nametoindex) */
|
||||
} iface_info_t;
|
||||
|
||||
/* ===================== Утилиты ===================== */
|
||||
static int net_init(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
static int done = 0;
|
||||
if (!done) {
|
||||
WSADATA wsa;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
|
||||
return -1;
|
||||
done = 1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void socket_close(socket_t s)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (s != SOCKET_INVALID)
|
||||
closesocket(s);
|
||||
#else
|
||||
if (s >= 0)
|
||||
close(s);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ===================== Основная функция ===================== */
|
||||
/*
|
||||
* dst_ip — адрес назначения (например, "8.8.8.8" или IP вашего VPN-сервера)
|
||||
* dst_port — любой открытый порт (например, 53 или 123), реальных пакетов не уйдёт
|
||||
* info — заполняемая структура
|
||||
*
|
||||
* Возвращает 0 при успехе, -1 при ошибке.
|
||||
*/
|
||||
int get_outgoing_iface(const char *dst_ip, int dst_port, iface_info_t *info)
|
||||
{
|
||||
socket_t sock = SOCKET_INVALID;
|
||||
int ret = -1;
|
||||
struct sockaddr_in dst, src;
|
||||
socklen_t src_len = sizeof(src);
|
||||
|
||||
memset(info, 0, sizeof(*info));
|
||||
if (net_init() != 0)
|
||||
return -1;
|
||||
|
||||
/* 1. UDP-сокет */
|
||||
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sock == SOCKET_INVALID)
|
||||
return -1;
|
||||
|
||||
/* 2. Bind на 0.0.0.0 — именно так, как в вопросе */
|
||||
memset(&dst, 0, sizeof(dst));
|
||||
dst.sin_family = AF_INET;
|
||||
dst.sin_addr.s_addr = INADDR_ANY; /* 0.0.0.0 */
|
||||
dst.sin_port = 0; /* система выберет порт */
|
||||
|
||||
if (bind(sock, (struct sockaddr *)&dst, sizeof(dst)) != 0)
|
||||
goto end;
|
||||
|
||||
/* 3. "Соединяемся" с destination.
|
||||
* Для UDP это НЕ шлёт реальных пакетов, но ядро просчитывает
|
||||
* маршрут и фиксирует выбранный source IP в сокете. */
|
||||
memset(&dst, 0, sizeof(dst));
|
||||
dst.sin_family = AF_INET;
|
||||
dst.sin_port = htons((uint16_t)dst_port);
|
||||
if (inet_pton(AF_INET, dst_ip, &dst.sin_addr) != 1)
|
||||
goto end;
|
||||
if (connect(sock, (struct sockaddr *)&dst, sizeof(dst)) != 0)
|
||||
goto end;
|
||||
|
||||
/* 4. Забираем выбранный локальный адрес */
|
||||
if (getsockname(sock, (struct sockaddr *)&src, &src_len) != 0)
|
||||
goto end;
|
||||
if (!inet_ntop(AF_INET, &src.sin_addr, info->ip, sizeof(info->ip)))
|
||||
goto end;
|
||||
|
||||
/* 5. По source IP ищем интерфейс в системном списке */
|
||||
#ifdef _WIN32
|
||||
{
|
||||
ULONG size = 0;
|
||||
DWORD rv;
|
||||
PIP_ADAPTER_ADDRESSES adapters = NULL, a;
|
||||
PIP_ADAPTER_UNICAST_ADDRESS u;
|
||||
|
||||
/* первый вызов — узнать размер буфера */
|
||||
rv = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX,
|
||||
NULL, NULL, &size);
|
||||
if (rv != ERROR_BUFFER_OVERFLOW)
|
||||
goto end;
|
||||
|
||||
adapters = (PIP_ADAPTER_ADDRESSES)malloc(size);
|
||||
if (!adapters)
|
||||
goto end;
|
||||
|
||||
rv = GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX,
|
||||
NULL, adapters, &size);
|
||||
if (rv != NO_ERROR) {
|
||||
free(adapters);
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (a = adapters; a != NULL; a = a->Next) {
|
||||
for (u = a->FirstUnicastAddress; u; u = u->Next) {
|
||||
struct sockaddr_in *ua =
|
||||
(struct sockaddr_in *)u->Address.lpSockaddr;
|
||||
if (ua->sin_addr.s_addr == src.sin_addr.s_addr) {
|
||||
info->ifindex = (uint32_t)a->IfIndex;
|
||||
/* FriendlyName — WCHAR, приводим к UTF-8 */
|
||||
WideCharToMultiByte(CP_UTF8, 0, a->FriendlyName, -1,
|
||||
info->name, IF_NAMESIZE,
|
||||
NULL, NULL);
|
||||
ret = 0;
|
||||
goto win_done;
|
||||
}
|
||||
}
|
||||
}
|
||||
win_done:
|
||||
free(adapters);
|
||||
}
|
||||
|
||||
#else /* Linux / BSD / macOS */
|
||||
{
|
||||
struct ifaddrs *ifap, *ifa;
|
||||
|
||||
if (getifaddrs(&ifap) != 0)
|
||||
goto end;
|
||||
|
||||
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
|
||||
if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET)
|
||||
continue;
|
||||
|
||||
struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr;
|
||||
if (addr->sin_addr.s_addr == src.sin_addr.s_addr) {
|
||||
strncpy(info->name, ifa->ifa_name, IF_NAMESIZE - 1);
|
||||
info->ifindex = if_nametoindex(ifa->ifa_name);
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
freeifaddrs(ifap);
|
||||
}
|
||||
#endif
|
||||
|
||||
end:
|
||||
socket_close(sock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ===================== Пример использования =====================
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
iface_info_t info;
|
||||
const char *dst = (argc > 1) ? argv[1] : "8.8.8.8";
|
||||
int port = (argc > 2) ? atoi(argv[2]) : 53;
|
||||
|
||||
if (get_outgoing_iface(dst, port, &info) == 0) {
|
||||
printf("Destination : %s:%d\n", dst, port);
|
||||
printf("Source IP : %s\n", info.ip);
|
||||
printf("Iface name : %s\n", info.name);
|
||||
printf("Iface index : %u\n", info.ifindex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Failed to determine outgoing interface (errno=%d)\n",
|
||||
#ifdef _WIN32
|
||||
(int)WSAGetLastError()
|
||||
#else
|
||||
errno
|
||||
#endif
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
*/
|
||||
+127
-67
@@ -24,6 +24,9 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#ifndef IFA_F_TEMPORARY
|
||||
#define IFA_F_TEMPORARY 0x01
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -48,49 +51,16 @@ int random_bytes(uint8_t *buffer, size_t len) {
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
static uint32_t get_default_route_ip_linux(void) {
|
||||
FILE *f = fopen("/proc/net/route", "r");
|
||||
if (!f) return 0;
|
||||
|
||||
char line[256];
|
||||
char *ifname = NULL;
|
||||
unsigned int dest, gw;
|
||||
|
||||
if (!fgets(line, sizeof(line), f)) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
int items = sscanf(line, "%ms %x %x", &ifname, &dest, &gw);
|
||||
if (items != 3) {
|
||||
free(ifname);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dest == 0) {
|
||||
free(ifname);
|
||||
break;
|
||||
}
|
||||
free(ifname);
|
||||
ifname = NULL;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
if (!ifname) return 0;
|
||||
|
||||
uint32_t get_interface_ip_by_index(uint32_t netif_index) {
|
||||
struct ifaddrs *ifaddr, *ifa;
|
||||
if (getifaddrs(&ifaddr) == -1) {
|
||||
free(ifname);
|
||||
return 0;
|
||||
}
|
||||
if (getifaddrs(&ifaddr) == -1) return 0;
|
||||
|
||||
uint32_t result = 0;
|
||||
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
|
||||
if (ifa->ifa_addr == NULL) continue;
|
||||
if (ifa->ifa_name == NULL) continue;
|
||||
if (strcmp(ifa->ifa_name, ifname) != 0) continue;
|
||||
unsigned int idx = if_nametoindex(ifa->ifa_name);
|
||||
if (idx != netif_index) continue;
|
||||
if (ifa->ifa_addr->sa_family != AF_INET) continue;
|
||||
|
||||
struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr;
|
||||
@@ -99,40 +69,130 @@ static uint32_t get_default_route_ip_linux(void) {
|
||||
}
|
||||
|
||||
freeifaddrs(ifaddr);
|
||||
free(ifname);
|
||||
return result;
|
||||
}
|
||||
|
||||
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out) {
|
||||
if (!addr_out) return -1;
|
||||
memset(addr_out, 0, 16);
|
||||
|
||||
struct ifaddrs *ifaddr, *ifa;
|
||||
if (getifaddrs(&ifaddr) == -1) return -1;
|
||||
|
||||
int found = 0;
|
||||
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
|
||||
if (ifa->ifa_addr == NULL) continue;
|
||||
if (ifa->ifa_name == NULL) continue;
|
||||
unsigned int idx = if_nametoindex(ifa->ifa_name);
|
||||
if (idx != netif_index) continue;
|
||||
if (ifa->ifa_addr->sa_family != AF_INET6) continue;
|
||||
// Skip link-local addresses (fe80::/10)
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)ifa->ifa_addr;
|
||||
if ((sin6->sin6_addr.s6_addr[0] & 0xfe) == 0xfe && sin6->sin6_addr.s6_addr[1] == 0x80) continue;
|
||||
int is_temporary = (ifa->ifa_flags & IFA_F_TEMPORARY) ? 1 : 0;
|
||||
if (temporary && is_temporary) {
|
||||
memcpy(addr_out, &sin6->sin6_addr, 16);
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
if (!temporary && !is_temporary) {
|
||||
memcpy(addr_out, &sin6->sin6_addr, 16);
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
freeifaddrs(ifaddr);
|
||||
return found ? 0 : -1;
|
||||
}
|
||||
|
||||
uint32_t get_default_route_netif_index(int family) {
|
||||
if (family != AF_INET && family != AF_INET6) return 0;
|
||||
|
||||
int sock = socket(family, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sock < 0) return 0;
|
||||
|
||||
struct sockaddr_storage bind_addr;
|
||||
memset(&bind_addr, 0, sizeof(bind_addr));
|
||||
if (family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&bind_addr;
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_addr.s_addr = INADDR_ANY;
|
||||
} else {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&bind_addr;
|
||||
sin6->sin6_family = AF_INET6;
|
||||
sin6->sin6_addr = in6addr_any;
|
||||
}
|
||||
|
||||
socklen_t bind_len = (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
|
||||
if (bind(sock, (struct sockaddr*)&bind_addr, bind_len) != 0) { close(sock); return 0; }
|
||||
|
||||
struct sockaddr_storage remote;
|
||||
memset(&remote, 0, sizeof(remote));
|
||||
socklen_t remote_len;
|
||||
if (family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&remote;
|
||||
sin->sin_family = AF_INET;
|
||||
sin->sin_port = htons(53);
|
||||
inet_pton(AF_INET, "8.8.8.8", &sin->sin_addr);
|
||||
remote_len = sizeof(struct sockaddr_in);
|
||||
} else {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&remote;
|
||||
sin6->sin6_family = AF_INET6;
|
||||
sin6->sin6_port = htons(53);
|
||||
inet_pton(AF_INET6, "2001:4860:4860::8888", &sin6->sin6_addr);
|
||||
remote_len = sizeof(struct sockaddr_in6);
|
||||
}
|
||||
|
||||
if (connect(sock, (struct sockaddr*)&remote, remote_len) != 0) { close(sock); return 0; }
|
||||
|
||||
struct sockaddr_storage src;
|
||||
socklen_t src_len = sizeof(src);
|
||||
if (getsockname(sock, (struct sockaddr*)&src, &src_len) != 0) { close(sock); return 0; }
|
||||
|
||||
close(sock);
|
||||
|
||||
struct ifaddrs *ifaddr, *ifa;
|
||||
if (getifaddrs(&ifaddr) == -1) return 0;
|
||||
|
||||
uint32_t result = 0;
|
||||
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
|
||||
if (ifa->ifa_addr == NULL) continue;
|
||||
if (ifa->ifa_addr->sa_family != family) continue;
|
||||
if (family == AF_INET) {
|
||||
struct sockaddr_in* src_sin = (struct sockaddr_in*)&src;
|
||||
struct sockaddr_in* ifa_sin = (struct sockaddr_in*)ifa->ifa_addr;
|
||||
if (src_sin->sin_addr.s_addr == ifa_sin->sin_addr.s_addr) {
|
||||
if (ifa->ifa_name) result = if_nametoindex(ifa->ifa_name);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
struct sockaddr_in6* src_sin6 = (struct sockaddr_in6*)&src;
|
||||
struct sockaddr_in6* ifa_sin6 = (struct sockaddr_in6*)ifa->ifa_addr;
|
||||
if (memcmp(&src_sin6->sin6_addr, &ifa_sin6->sin6_addr, 16) == 0) {
|
||||
if (ifa->ifa_name) result = if_nametoindex(ifa->ifa_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeifaddrs(ifaddr);
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
static uint32_t get_default_route_ip_windows(void) {
|
||||
SOCKET s = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (s == INVALID_SOCKET) return 0;
|
||||
uint32_t get_interface_ip_by_index(uint32_t netif_index) {
|
||||
(void)netif_index;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct sockaddr_in remote;
|
||||
remote.sin_family = AF_INET;
|
||||
remote.sin_port = htons(53);
|
||||
remote.sin_addr.s_addr = inet_addr("8.8.8.8");
|
||||
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out) {
|
||||
(void)netif_index; (void)temporary;
|
||||
if (addr_out) memset(addr_out, 0, 16);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (connect(s, (struct sockaddr *)&remote, sizeof(remote)) == SOCKET_ERROR) {
|
||||
closesocket(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct sockaddr_in local;
|
||||
int len = sizeof(local);
|
||||
if (getsockname(s, (struct sockaddr *)&local, &len) == SOCKET_ERROR) {
|
||||
closesocket(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
closesocket(s);
|
||||
return local.sin_addr.s_addr;
|
||||
uint32_t get_default_route_netif_index(int family) {
|
||||
(void)family;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t get_default_route_ip(void) {
|
||||
#ifdef _WIN32
|
||||
return get_default_route_ip_windows();
|
||||
#else
|
||||
return get_default_route_ip_linux();
|
||||
#endif
|
||||
}
|
||||
+17
-3
@@ -17,9 +17,13 @@
|
||||
// Byte order conversion functions
|
||||
#ifdef _WIN32
|
||||
#include <stdlib.h>
|
||||
#define htobe16(x) _byteswap_ushort(x)
|
||||
#define be16toh(x) _byteswap_ushort(x)
|
||||
#define htobe32(x) _byteswap_ulong(x)
|
||||
#define be32toh(x) _byteswap_ulong(x)
|
||||
#define htobe64(x) _byteswap_uint64(x)
|
||||
#define be64toh(x) _byteswap_uint64(x)
|
||||
#elif defined(__linux__)
|
||||
#else
|
||||
#include <endian.h>
|
||||
#endif
|
||||
|
||||
@@ -155,9 +159,19 @@
|
||||
// Returns 0 on success, -1 on error
|
||||
int random_bytes(uint8_t *buffer, size_t len);
|
||||
|
||||
// Get IPv4 address of interface with default route (0.0.0.0 destination)
|
||||
// Get IPv4 address of interface by its index (ifindex)
|
||||
// Returns IPv4 address in network byte order, 0 on error
|
||||
uint32_t get_default_route_ip(void);
|
||||
uint32_t get_interface_ip_by_index(uint32_t netif_index);
|
||||
|
||||
// Get IPv6 address of interface by its index (ifindex)
|
||||
// temporary=1: get temporary (privacy) IPv6, temporary=0: get permanent IPv6
|
||||
// Returns 0 on success (fills addr_out with 16 bytes), -1 on error or not found
|
||||
int get_interface_ipv6_by_index(uint32_t netif_index, int temporary, uint8_t* addr_out);
|
||||
|
||||
// Determine the interface index for the default route (outgoing traffic)
|
||||
// Creates a temp UDP socket, connects to a well-known address to force
|
||||
// kernel routing, then matches the chosen source IP to getifaddrs()
|
||||
// family: AF_INET or AF_INET6, returns ifindex (0 on failure)
|
||||
uint32_t get_default_route_netif_index(int family);
|
||||
|
||||
#endif // PLATFORM_COMPAT_H
|
||||
+906
@@ -0,0 +1,906 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Copyright (c) 1988, 1989, 1993
|
||||
* The Regents of the University of California. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the University nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Routines to build and maintain radix trees for routing lookups.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "mem.h"
|
||||
#include "debug_config.h"
|
||||
#include "radix.h"
|
||||
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
|
||||
static struct radix_node
|
||||
*rn_insert(void *, struct radix_head *, int *,
|
||||
struct radix_node [2]),
|
||||
*rn_newpair(void *, int, struct radix_node[2]),
|
||||
*rn_search(const void *, struct radix_node *),
|
||||
*rn_search_m(const void *, struct radix_node *, void *);
|
||||
static struct radix_node *rn_addmask(const void *, struct radix_mask_head *, int,int);
|
||||
|
||||
static void rn_detachhead_internal(struct radix_head *);
|
||||
|
||||
#define RADIX_MAX_KEY_LEN 32
|
||||
|
||||
static uint8_t rn_zeros[RADIX_MAX_KEY_LEN];
|
||||
static uint8_t rn_ones[RADIX_MAX_KEY_LEN] = {
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
};
|
||||
|
||||
static int rn_lexobetter(const void *m_arg, const void *n_arg);
|
||||
static struct radix_mask *
|
||||
rn_new_radix_mask(struct radix_node *tt,
|
||||
struct radix_mask *next);
|
||||
static int rn_satisfies_leaf(const uint8_t *trial, struct radix_node *leaf,
|
||||
int skip);
|
||||
|
||||
/*
|
||||
* The data structure for the keys is a radix tree with one way
|
||||
* branching removed. The index rn_bit at an internal node n represents a bit
|
||||
* position to be tested. The tree is arranged so that all descendants
|
||||
* of a node n have keys whose bits all agree up to position rn_bit - 1.
|
||||
* (We say the index of n is rn_bit.)
|
||||
*
|
||||
* There is at least one descendant which has a one bit at position rn_bit,
|
||||
* and at least one with a zero there.
|
||||
*
|
||||
* A route is determined by a pair of key and mask. We require that the
|
||||
* bit-wise logical and of the key and mask to be the key.
|
||||
* We define the index of a route to associated with the mask to be
|
||||
* the first bit number in the mask where 0 occurs (with bit number 0
|
||||
* representing the highest order bit).
|
||||
*
|
||||
* We say a mask is normal if every bit is 0, past the index of the mask.
|
||||
* If a node n has a descendant (k, m) with index(m) == index(n) == rn_bit,
|
||||
* and m is a normal mask, then the route applies to every descendant of n.
|
||||
* If the index(m) < rn_bit, this implies the trailing last few bits of k
|
||||
* before bit b are all 0, (and hence consequently true of every descendant
|
||||
* of n), so the route applies to all descendants of the node as well.
|
||||
*
|
||||
* Similar logic shows that a non-normal mask m such that
|
||||
* index(m) <= index(n) could potentially apply to many children of n.
|
||||
* Thus, for each non-host route, we attach its mask to a list at an internal
|
||||
* node as high in the tree as we can go.
|
||||
*
|
||||
* The present version of the code makes use of normal routes in short-
|
||||
* circuiting an explict mask and compare operation when testing whether
|
||||
* a key satisfies a normal route, and also in remembering the unique leaf
|
||||
* that governs a subtree.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Most of the functions in this code assume that the key/mask arguments
|
||||
* are sockaddr-like structures, where the first byte is a uint8_t
|
||||
* indicating the size of the entire structure.
|
||||
*
|
||||
* To make the assumption more explicit, we use the LEN() macro to access
|
||||
* this field. It is safe to pass an expression with side effects
|
||||
* to LEN() as the argument is evaluated only once.
|
||||
*/
|
||||
#define LEN(x) ( (int) (*(const uint8_t *)(x)) )
|
||||
|
||||
static struct radix_node *
|
||||
rn_search(const void *v_arg, struct radix_node *head)
|
||||
{
|
||||
struct radix_node *x;
|
||||
const uint8_t *v;
|
||||
|
||||
for (x = head, v = v_arg; x->rn_bit >= 0;) {
|
||||
if (x->rn_bmask & v[x->rn_offset])
|
||||
x = x->rn_right;
|
||||
else
|
||||
x = x->rn_left;
|
||||
}
|
||||
return (x);
|
||||
}
|
||||
|
||||
static struct radix_node *
|
||||
rn_search_m(const void *v_arg, struct radix_node *head, void *m_arg)
|
||||
{
|
||||
struct radix_node *x;
|
||||
const uint8_t *v = v_arg, *m = m_arg;
|
||||
|
||||
for (x = head; x->rn_bit >= 0;) {
|
||||
if ((x->rn_bmask & m[x->rn_offset]) &&
|
||||
(x->rn_bmask & v[x->rn_offset]))
|
||||
x = x->rn_right;
|
||||
else
|
||||
x = x->rn_left;
|
||||
}
|
||||
return (x);
|
||||
}
|
||||
|
||||
int
|
||||
rn_refines(const void *m_arg, const void *n_arg)
|
||||
{
|
||||
const uint8_t *m = m_arg, *n = n_arg;
|
||||
const uint8_t *lim, *lim2 = lim = n + LEN(n);
|
||||
int longer = LEN(n++) - LEN(m++);
|
||||
int masks_are_equal = 1;
|
||||
|
||||
if (longer > 0)
|
||||
lim -= longer;
|
||||
while (n < lim) {
|
||||
if (*n & ~(*m))
|
||||
return (0);
|
||||
if (*n++ != *m++)
|
||||
masks_are_equal = 0;
|
||||
}
|
||||
while (n < lim2)
|
||||
if (*n++)
|
||||
return (0);
|
||||
if (masks_are_equal && (longer < 0))
|
||||
for (lim2 = m - longer; m < lim2; )
|
||||
if (*m++)
|
||||
return (1);
|
||||
return (!masks_are_equal);
|
||||
}
|
||||
|
||||
struct radix_node *
|
||||
rn_lookup(const void *v_arg, const void *m_arg, struct radix_head *head)
|
||||
{
|
||||
struct radix_node *x;
|
||||
uint8_t *netmask;
|
||||
|
||||
if (m_arg != NULL) {
|
||||
x = rn_addmask(m_arg, head->rnh_masks, 1,
|
||||
head->rnh_treetop->rn_offset);
|
||||
if (x == NULL)
|
||||
return (NULL);
|
||||
netmask = x->rn_key;
|
||||
|
||||
x = rn_match(v_arg, head);
|
||||
|
||||
while (x != NULL && x->rn_mask != netmask)
|
||||
x = x->rn_dupedkey;
|
||||
|
||||
return (x);
|
||||
}
|
||||
|
||||
if ((x = rn_match(v_arg, head)) == NULL)
|
||||
return (NULL);
|
||||
|
||||
if (LEN(x->rn_key) != LEN(v_arg) || memcmp(x->rn_key, v_arg, LEN(v_arg)))
|
||||
return (NULL);
|
||||
|
||||
if (x->rn_mask != NULL)
|
||||
return (NULL);
|
||||
|
||||
return (x);
|
||||
}
|
||||
|
||||
static int
|
||||
rn_satisfies_leaf(const uint8_t *trial, struct radix_node *leaf, int skip)
|
||||
{
|
||||
const uint8_t *cp = trial, *cp2 = leaf->rn_key, *cp3 = leaf->rn_mask;
|
||||
const uint8_t *cplim;
|
||||
int length = min(LEN(cp), LEN(cp2));
|
||||
|
||||
if (cp3 == NULL)
|
||||
cp3 = rn_ones;
|
||||
else
|
||||
length = min(length, LEN(cp3));
|
||||
cplim = cp + length; cp3 += skip; cp2 += skip;
|
||||
for (cp += skip; cp < cplim; cp++, cp2++, cp3++)
|
||||
if ((*cp ^ *cp2) & *cp3)
|
||||
return (0);
|
||||
return (1);
|
||||
}
|
||||
|
||||
struct radix_node *
|
||||
rn_match(const void *v_arg, struct radix_head *head)
|
||||
{
|
||||
const uint8_t *v = v_arg;
|
||||
struct radix_node *t = head->rnh_treetop, *x;
|
||||
const uint8_t *cp = v, *cp2;
|
||||
const uint8_t *cplim;
|
||||
struct radix_node *saved_t, *top = t;
|
||||
int off = t->rn_offset, vlen = LEN(cp), matched_off;
|
||||
int test, b, rn_bit;
|
||||
|
||||
for (; t->rn_bit >= 0; ) {
|
||||
if (t->rn_bmask & cp[t->rn_offset])
|
||||
t = t->rn_right;
|
||||
else
|
||||
t = t->rn_left;
|
||||
}
|
||||
if (t->rn_mask)
|
||||
vlen = *t->rn_mask;
|
||||
cp += off; cp2 = t->rn_key + off; cplim = v + vlen;
|
||||
for (; cp < cplim; cp++, cp2++)
|
||||
if (*cp != *cp2)
|
||||
goto on1;
|
||||
if (t->rn_flags & RNF_ROOT)
|
||||
t = t->rn_dupedkey;
|
||||
return (t);
|
||||
on1:
|
||||
test = (*cp ^ *cp2) & 0xff;
|
||||
for (b = 7; (test >>= 1) > 0;)
|
||||
b--;
|
||||
matched_off = cp - v;
|
||||
b += matched_off << 3;
|
||||
rn_bit = -1 - b;
|
||||
if ((saved_t = t)->rn_mask == 0)
|
||||
t = t->rn_dupedkey;
|
||||
for (; t; t = t->rn_dupedkey)
|
||||
if (t->rn_flags & RNF_NORMAL) {
|
||||
if (rn_bit <= t->rn_bit)
|
||||
return (t);
|
||||
} else if (rn_satisfies_leaf(v, t, matched_off))
|
||||
return (t);
|
||||
t = saved_t;
|
||||
do {
|
||||
struct radix_mask *m;
|
||||
t = t->rn_parent;
|
||||
m = t->rn_mklist;
|
||||
while (m) {
|
||||
if (m->rm_flags & RNF_NORMAL) {
|
||||
if (rn_bit <= m->rm_bit)
|
||||
return (m->rm_leaf);
|
||||
} else {
|
||||
off = min(t->rn_offset, matched_off);
|
||||
x = rn_search_m(v, t, m->rm_mask);
|
||||
while (x && x->rn_mask != m->rm_mask)
|
||||
x = x->rn_dupedkey;
|
||||
if (x && rn_satisfies_leaf(v, x, off))
|
||||
return (x);
|
||||
}
|
||||
m = m->rm_mklist;
|
||||
}
|
||||
} while (t != top);
|
||||
return (0);
|
||||
}
|
||||
|
||||
struct radix_node *
|
||||
rn_nextprefix(struct radix_node *rn)
|
||||
{
|
||||
for (rn = rn->rn_dupedkey; rn != NULL; rn = rn->rn_dupedkey) {
|
||||
if (!(rn->rn_flags & RNF_ROOT))
|
||||
return (rn);
|
||||
}
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
static struct radix_node *
|
||||
rn_newpair(void *v, int b, struct radix_node nodes[2])
|
||||
{
|
||||
struct radix_node *tt = nodes, *t = tt + 1;
|
||||
t->rn_bit = b;
|
||||
t->rn_bmask = 0x80 >> (b & 7);
|
||||
t->rn_left = tt;
|
||||
t->rn_offset = b >> 3;
|
||||
t->rn_parent = t->rn_right = NULL;
|
||||
|
||||
tt->rn_bit = -1;
|
||||
tt->rn_mask = NULL;
|
||||
tt->rn_dupedkey = NULL;
|
||||
tt->rn_bmask = 0;
|
||||
tt->rn_key = (uint8_t *)v;
|
||||
tt->rn_parent = t;
|
||||
tt->rn_flags = t->rn_flags = RNF_ACTIVE;
|
||||
tt->rn_mklist = t->rn_mklist = 0;
|
||||
return (t);
|
||||
}
|
||||
|
||||
static struct radix_node *
|
||||
rn_insert(void *v_arg, struct radix_head *head, int *dupentry,
|
||||
struct radix_node nodes[2])
|
||||
{
|
||||
uint8_t *v = v_arg;
|
||||
struct radix_node *top = head->rnh_treetop;
|
||||
int head_off = top->rn_offset, vlen = LEN(v);
|
||||
struct radix_node *t = rn_search(v_arg, top);
|
||||
uint8_t *cp = v + head_off;
|
||||
unsigned b;
|
||||
struct radix_node *p, *tt, *x;
|
||||
uint8_t *cp2 = t->rn_key + head_off;
|
||||
int cmp_res;
|
||||
uint8_t *cplim = v + vlen;
|
||||
|
||||
while (cp < cplim)
|
||||
if (*cp2++ != *cp++)
|
||||
goto on1;
|
||||
*dupentry = 1;
|
||||
return (t);
|
||||
on1:
|
||||
*dupentry = 0;
|
||||
cmp_res = (cp[-1] ^ cp2[-1]) & 0xff;
|
||||
for (b = (cp - v) << 3; cmp_res; b--)
|
||||
cmp_res >>= 1;
|
||||
|
||||
x = top;
|
||||
cp = v;
|
||||
do {
|
||||
p = x;
|
||||
if (cp[x->rn_offset] & x->rn_bmask)
|
||||
x = x->rn_right;
|
||||
else
|
||||
x = x->rn_left;
|
||||
} while (b > (unsigned) x->rn_bit);
|
||||
|
||||
t = rn_newpair(v_arg, b, nodes);
|
||||
tt = t->rn_left;
|
||||
if ((cp[p->rn_offset] & p->rn_bmask) == 0)
|
||||
p->rn_left = t;
|
||||
else
|
||||
p->rn_right = t;
|
||||
x->rn_parent = t;
|
||||
t->rn_parent = p;
|
||||
if ((cp[t->rn_offset] & t->rn_bmask) == 0) {
|
||||
t->rn_right = x;
|
||||
} else {
|
||||
t->rn_right = tt;
|
||||
t->rn_left = x;
|
||||
}
|
||||
return (tt);
|
||||
}
|
||||
|
||||
static struct radix_node *
|
||||
rn_addmask(const void *n_arg, struct radix_mask_head *maskhead, int search, int skip)
|
||||
{
|
||||
const uint8_t *netmask = n_arg;
|
||||
const uint8_t *c, *clim;
|
||||
uint8_t *cp;
|
||||
struct radix_node *x;
|
||||
int b = 0, mlen, j;
|
||||
int maskduplicated, isnormal;
|
||||
struct radix_node *saved_x;
|
||||
uint8_t addmask_key[RADIX_MAX_KEY_LEN];
|
||||
|
||||
if ((mlen = LEN(netmask)) > RADIX_MAX_KEY_LEN)
|
||||
mlen = RADIX_MAX_KEY_LEN;
|
||||
if (skip == 0)
|
||||
skip = 1;
|
||||
if (mlen <= skip)
|
||||
return (maskhead->mask_nodes);
|
||||
|
||||
memset(addmask_key, 0, RADIX_MAX_KEY_LEN);
|
||||
if (skip > 1)
|
||||
memcpy(addmask_key + 1, rn_ones + 1, skip - 1);
|
||||
memcpy(addmask_key + skip, netmask + skip, mlen - skip);
|
||||
for (cp = addmask_key + mlen; (cp > addmask_key) && cp[-1] == 0;)
|
||||
cp--;
|
||||
mlen = cp - addmask_key;
|
||||
if (mlen <= skip)
|
||||
return (maskhead->mask_nodes);
|
||||
*addmask_key = mlen;
|
||||
x = rn_search(addmask_key, maskhead->head.rnh_treetop);
|
||||
if (memcmp(addmask_key, x->rn_key, mlen) != 0)
|
||||
x = NULL;
|
||||
if (x || search)
|
||||
return (x);
|
||||
R_Zalloc(x, struct radix_node *, RADIX_MAX_KEY_LEN + 2 * sizeof (*x));
|
||||
if ((saved_x = x) == NULL)
|
||||
return (0);
|
||||
netmask = cp = (uint8_t *)(x + 2);
|
||||
memcpy(cp, addmask_key, mlen);
|
||||
x = rn_insert(cp, &maskhead->head, &maskduplicated, x);
|
||||
if (maskduplicated) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_addmask: mask impossibly already in tree");
|
||||
R_Free(saved_x);
|
||||
return (x);
|
||||
}
|
||||
#define CONTIG(_c) (((~(_c) + 1) & (_c)) == (uint8_t)(~(_c) + 1))
|
||||
clim = netmask + mlen;
|
||||
isnormal = 1;
|
||||
for (c = netmask + skip; c < clim && *c == 0xff; c++);
|
||||
if (c != clim) {
|
||||
for (j = 0x80; (j & *c) != 0; j >>= 1)
|
||||
b++;
|
||||
if (!CONTIG(*c) || c != (clim - 1))
|
||||
isnormal = 0;
|
||||
}
|
||||
b += (c - netmask) << 3;
|
||||
x->rn_bit = -1 - b;
|
||||
if (isnormal)
|
||||
x->rn_flags |= RNF_NORMAL;
|
||||
return (x);
|
||||
}
|
||||
|
||||
static int
|
||||
rn_lexobetter(const void *m_arg, const void *n_arg)
|
||||
{
|
||||
const uint8_t *mp = m_arg, *np = n_arg, *lim;
|
||||
|
||||
if (LEN(mp) > LEN(np))
|
||||
return (1);
|
||||
if (LEN(mp) == LEN(np))
|
||||
for (lim = mp + LEN(mp); mp < lim;)
|
||||
if (*mp++ > *np++)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
|
||||
static struct radix_mask *
|
||||
rn_new_radix_mask(struct radix_node *tt, struct radix_mask *next)
|
||||
{
|
||||
struct radix_mask *m;
|
||||
|
||||
R_Malloc(m, struct radix_mask *, sizeof (struct radix_mask));
|
||||
if (m == NULL) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "Failed to allocate route mask");
|
||||
return (0);
|
||||
}
|
||||
memset(m, 0, sizeof(*m));
|
||||
m->rm_bit = tt->rn_bit;
|
||||
m->rm_flags = tt->rn_flags;
|
||||
if (tt->rn_flags & RNF_NORMAL)
|
||||
m->rm_leaf = tt;
|
||||
else
|
||||
m->rm_mask = tt->rn_mask;
|
||||
m->rm_mklist = next;
|
||||
tt->rn_mklist = m;
|
||||
return (m);
|
||||
}
|
||||
|
||||
struct radix_node *
|
||||
rn_addroute(void *v_arg, const void *n_arg, struct radix_head *head,
|
||||
struct radix_node treenodes[2])
|
||||
{
|
||||
uint8_t *v = (uint8_t *)v_arg, *netmask = NULL;
|
||||
struct radix_node *t, *x = NULL, *tt;
|
||||
struct radix_node *saved_tt, *top = head->rnh_treetop;
|
||||
short b = 0, b_leaf = 0;
|
||||
int keyduplicated;
|
||||
uint8_t *mmask;
|
||||
struct radix_mask *m, **mp;
|
||||
|
||||
if (n_arg) {
|
||||
x = rn_addmask(n_arg, head->rnh_masks, 0, top->rn_offset);
|
||||
if (x == NULL)
|
||||
return (0);
|
||||
b_leaf = x->rn_bit;
|
||||
b = -1 - x->rn_bit;
|
||||
netmask = x->rn_key;
|
||||
}
|
||||
saved_tt = tt = rn_insert(v, head, &keyduplicated, treenodes);
|
||||
if (keyduplicated) {
|
||||
for (t = tt; tt; t = tt, tt = tt->rn_dupedkey) {
|
||||
if (tt->rn_mask == netmask)
|
||||
return (0);
|
||||
if (netmask == 0 ||
|
||||
(tt->rn_mask &&
|
||||
((b_leaf < tt->rn_bit)
|
||||
|| rn_refines(netmask, tt->rn_mask)
|
||||
|| rn_lexobetter(netmask, tt->rn_mask))))
|
||||
break;
|
||||
}
|
||||
if (tt == saved_tt) {
|
||||
struct radix_node *xx = x;
|
||||
(tt = treenodes)->rn_dupedkey = t;
|
||||
tt->rn_flags = t->rn_flags;
|
||||
tt->rn_parent = x = t->rn_parent;
|
||||
t->rn_parent = tt;
|
||||
if (x->rn_left == t)
|
||||
x->rn_left = tt;
|
||||
else
|
||||
x->rn_right = tt;
|
||||
saved_tt = tt; x = xx;
|
||||
} else {
|
||||
(tt = treenodes)->rn_dupedkey = t->rn_dupedkey;
|
||||
t->rn_dupedkey = tt;
|
||||
tt->rn_parent = t;
|
||||
if (tt->rn_dupedkey)
|
||||
tt->rn_dupedkey->rn_parent = tt;
|
||||
}
|
||||
tt->rn_key = (uint8_t *) v;
|
||||
tt->rn_bit = -1;
|
||||
tt->rn_flags = RNF_ACTIVE;
|
||||
}
|
||||
if (netmask) {
|
||||
tt->rn_mask = netmask;
|
||||
tt->rn_bit = x->rn_bit;
|
||||
tt->rn_flags |= x->rn_flags & RNF_NORMAL;
|
||||
}
|
||||
t = saved_tt->rn_parent;
|
||||
if (keyduplicated)
|
||||
goto on2;
|
||||
b_leaf = -1 - t->rn_bit;
|
||||
if (t->rn_right == saved_tt)
|
||||
x = t->rn_left;
|
||||
else
|
||||
x = t->rn_right;
|
||||
if (x->rn_bit < 0) {
|
||||
for (mp = &t->rn_mklist; x; x = x->rn_dupedkey)
|
||||
if (x->rn_mask && (x->rn_bit >= b_leaf) && x->rn_mklist == 0) {
|
||||
*mp = m = rn_new_radix_mask(x, 0);
|
||||
if (m)
|
||||
mp = &m->rm_mklist;
|
||||
}
|
||||
} else if (x->rn_mklist) {
|
||||
for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist)
|
||||
if (m->rm_bit >= b_leaf)
|
||||
break;
|
||||
t->rn_mklist = m; *mp = NULL;
|
||||
}
|
||||
on2:
|
||||
if ((netmask == 0) || (b > t->rn_bit ))
|
||||
return (tt);
|
||||
b_leaf = tt->rn_bit;
|
||||
do {
|
||||
x = t;
|
||||
t = t->rn_parent;
|
||||
} while (b <= t->rn_bit && x != top);
|
||||
for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist) {
|
||||
if (m->rm_bit < b_leaf)
|
||||
continue;
|
||||
if (m->rm_bit > b_leaf)
|
||||
break;
|
||||
if (m->rm_flags & RNF_NORMAL) {
|
||||
mmask = m->rm_leaf->rn_mask;
|
||||
if (tt->rn_flags & RNF_NORMAL) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,
|
||||
"Non-unique normal route, mask not entered");
|
||||
return (tt);
|
||||
}
|
||||
} else
|
||||
mmask = m->rm_mask;
|
||||
if (mmask == netmask) {
|
||||
m->rm_refs++;
|
||||
tt->rn_mklist = m;
|
||||
return (tt);
|
||||
}
|
||||
if (rn_refines(netmask, mmask)
|
||||
|| rn_lexobetter(netmask, mmask))
|
||||
break;
|
||||
}
|
||||
*mp = rn_new_radix_mask(tt, *mp);
|
||||
return (tt);
|
||||
}
|
||||
|
||||
struct radix_node *
|
||||
rn_delete(const void *v_arg, const void *netmask_arg, struct radix_head *head)
|
||||
{
|
||||
struct radix_node *t, *p, *x, *tt;
|
||||
struct radix_mask *m, *saved_m, **mp;
|
||||
struct radix_node *dupedkey, *saved_tt, *top;
|
||||
const uint8_t *v;
|
||||
const uint8_t *netmask;
|
||||
int b, head_off, vlen;
|
||||
|
||||
v = v_arg;
|
||||
netmask = netmask_arg;
|
||||
x = head->rnh_treetop;
|
||||
tt = rn_search(v, x);
|
||||
head_off = x->rn_offset;
|
||||
vlen = LEN(v);
|
||||
saved_tt = tt;
|
||||
top = x;
|
||||
if (tt == NULL ||
|
||||
memcmp(v + head_off, tt->rn_key + head_off, vlen - head_off))
|
||||
return (0);
|
||||
if (netmask) {
|
||||
x = rn_addmask(netmask, head->rnh_masks, 1, head_off);
|
||||
if (x == NULL)
|
||||
return (0);
|
||||
netmask = x->rn_key;
|
||||
while (tt->rn_mask != netmask)
|
||||
if ((tt = tt->rn_dupedkey) == NULL)
|
||||
return (0);
|
||||
}
|
||||
if (tt->rn_mask == 0 || (saved_m = m = tt->rn_mklist) == NULL)
|
||||
goto on1;
|
||||
if (tt->rn_flags & RNF_NORMAL) {
|
||||
if (m->rm_leaf != tt || m->rm_refs > 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: inconsistent annotation");
|
||||
return (0);
|
||||
}
|
||||
} else {
|
||||
if (m->rm_mask != tt->rn_mask) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: inconsistent annotation");
|
||||
goto on1;
|
||||
}
|
||||
if (--m->rm_refs >= 0)
|
||||
goto on1;
|
||||
}
|
||||
b = -1 - tt->rn_bit;
|
||||
t = saved_tt->rn_parent;
|
||||
if (b > t->rn_bit)
|
||||
goto on1;
|
||||
do {
|
||||
x = t;
|
||||
t = t->rn_parent;
|
||||
} while (b <= t->rn_bit && x != top);
|
||||
for (mp = &x->rn_mklist; (m = *mp); mp = &m->rm_mklist)
|
||||
if (m == saved_m) {
|
||||
*mp = m->rm_mklist;
|
||||
R_Free(m);
|
||||
break;
|
||||
}
|
||||
if (m == NULL) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: couldn't find our annotation");
|
||||
if (tt->rn_flags & RNF_NORMAL)
|
||||
return (0);
|
||||
}
|
||||
on1:
|
||||
if (tt->rn_flags & RNF_ROOT)
|
||||
return (0);
|
||||
t = tt->rn_parent;
|
||||
dupedkey = saved_tt->rn_dupedkey;
|
||||
if (dupedkey) {
|
||||
if (tt == saved_tt) {
|
||||
x = dupedkey; x->rn_parent = t;
|
||||
if (t->rn_left == tt)
|
||||
t->rn_left = x;
|
||||
else
|
||||
t->rn_right = x;
|
||||
} else {
|
||||
for (x = p = saved_tt; p && p->rn_dupedkey != tt;)
|
||||
p = p->rn_dupedkey;
|
||||
if (p) {
|
||||
p->rn_dupedkey = tt->rn_dupedkey;
|
||||
if (tt->rn_dupedkey)
|
||||
tt->rn_dupedkey->rn_parent = p;
|
||||
} else DEBUG_ERROR(DEBUG_CATEGORY_ROUTING, "rn_delete: couldn't find us");
|
||||
}
|
||||
t = tt + 1;
|
||||
if (t->rn_flags & RNF_ACTIVE) {
|
||||
*++x = *t;
|
||||
p = t->rn_parent;
|
||||
if (p->rn_left == t)
|
||||
p->rn_left = x;
|
||||
else
|
||||
p->rn_right = x;
|
||||
x->rn_left->rn_parent = x;
|
||||
x->rn_right->rn_parent = x;
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
if (t->rn_left == tt)
|
||||
x = t->rn_right;
|
||||
else
|
||||
x = t->rn_left;
|
||||
p = t->rn_parent;
|
||||
if (p->rn_right == t)
|
||||
p->rn_right = x;
|
||||
else
|
||||
p->rn_left = x;
|
||||
x->rn_parent = p;
|
||||
if (t->rn_mklist) {
|
||||
if (x->rn_bit >= 0) {
|
||||
for (mp = &x->rn_mklist; (m = *mp);)
|
||||
mp = &m->rm_mklist;
|
||||
*mp = t->rn_mklist;
|
||||
} else {
|
||||
for (m = t->rn_mklist; m && x; x = x->rn_dupedkey)
|
||||
if (m == x->rn_mklist) {
|
||||
struct radix_mask *mm = m->rm_mklist;
|
||||
x->rn_mklist = 0;
|
||||
if (--(m->rm_refs) < 0)
|
||||
R_Free(m);
|
||||
m = mm;
|
||||
}
|
||||
if (m)
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ROUTING,
|
||||
"rn_delete: Orphaned Mask %p at %p", m, x);
|
||||
}
|
||||
}
|
||||
x = tt + 1;
|
||||
if (t != x) {
|
||||
*t = *x;
|
||||
t->rn_left->rn_parent = t;
|
||||
t->rn_right->rn_parent = t;
|
||||
p = x->rn_parent;
|
||||
if (p->rn_left == x)
|
||||
p->rn_left = t;
|
||||
else
|
||||
p->rn_right = t;
|
||||
}
|
||||
out:
|
||||
tt->rn_flags &= ~RNF_ACTIVE;
|
||||
tt[1].rn_flags &= ~RNF_ACTIVE;
|
||||
return (tt);
|
||||
}
|
||||
|
||||
int
|
||||
rn_walktree_from(struct radix_head *h, void *a, void *m,
|
||||
walktree_f_t *f, void *w)
|
||||
{
|
||||
int error;
|
||||
struct radix_node *base, *next;
|
||||
uint8_t *xa = (uint8_t *)a;
|
||||
uint8_t *xm = (uint8_t *)m;
|
||||
struct radix_node *rn, *last = NULL;
|
||||
int stopping = 0;
|
||||
int lastb;
|
||||
|
||||
assert(m != NULL);
|
||||
|
||||
for (rn = h->rnh_treetop; rn->rn_bit >= 0; ) {
|
||||
last = rn;
|
||||
if (!(rn->rn_bmask & xm[rn->rn_offset]))
|
||||
break;
|
||||
if (rn->rn_bmask & xa[rn->rn_offset])
|
||||
rn = rn->rn_right;
|
||||
else
|
||||
rn = rn->rn_left;
|
||||
}
|
||||
|
||||
if (rn->rn_bit >= 0)
|
||||
rn = last;
|
||||
lastb = last->rn_bit;
|
||||
|
||||
while (rn->rn_bit >= 0)
|
||||
rn = rn->rn_left;
|
||||
|
||||
while (!stopping) {
|
||||
base = rn;
|
||||
while (rn->rn_parent->rn_right == rn
|
||||
&& !(rn->rn_flags & RNF_ROOT)) {
|
||||
rn = rn->rn_parent;
|
||||
if (rn->rn_bit <= lastb)
|
||||
stopping = 1;
|
||||
}
|
||||
|
||||
if (rn->rn_parent->rn_flags & RNF_ROOT)
|
||||
stopping = 1;
|
||||
|
||||
for (rn = rn->rn_parent->rn_right; rn->rn_bit >= 0;)
|
||||
rn = rn->rn_left;
|
||||
next = rn;
|
||||
while ((rn = base) != NULL) {
|
||||
base = rn->rn_dupedkey;
|
||||
if (!(rn->rn_flags & RNF_ROOT)
|
||||
&& (error = (*f)(rn, w)))
|
||||
return (error);
|
||||
}
|
||||
rn = next;
|
||||
|
||||
if (rn->rn_flags & RNF_ROOT)
|
||||
stopping = 1;
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
rn_walktree(struct radix_head *h, walktree_f_t *f, void *w)
|
||||
{
|
||||
int error;
|
||||
struct radix_node *base, *next;
|
||||
struct radix_node *rn = h->rnh_treetop;
|
||||
|
||||
while (rn->rn_bit >= 0)
|
||||
rn = rn->rn_left;
|
||||
for (;;) {
|
||||
base = rn;
|
||||
while (rn->rn_parent->rn_right == rn
|
||||
&& (rn->rn_flags & RNF_ROOT) == 0)
|
||||
rn = rn->rn_parent;
|
||||
for (rn = rn->rn_parent->rn_right; rn->rn_bit >= 0;)
|
||||
rn = rn->rn_left;
|
||||
next = rn;
|
||||
while ((rn = base)) {
|
||||
base = rn->rn_dupedkey;
|
||||
if (!(rn->rn_flags & RNF_ROOT)
|
||||
&& (error = (*f)(rn, w)))
|
||||
return (error);
|
||||
}
|
||||
rn = next;
|
||||
if (rn->rn_flags & RNF_ROOT)
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
rn_inithead_internal(struct radix_head *rh, struct radix_node *base_nodes, int off)
|
||||
{
|
||||
struct radix_node *t, *tt, *ttt;
|
||||
|
||||
t = rn_newpair(rn_zeros, off, base_nodes);
|
||||
ttt = base_nodes + 2;
|
||||
t->rn_right = ttt;
|
||||
t->rn_parent = t;
|
||||
tt = t->rn_left;
|
||||
tt->rn_flags = t->rn_flags = RNF_ROOT | RNF_ACTIVE;
|
||||
tt->rn_bit = -1 - off;
|
||||
*ttt = *tt;
|
||||
ttt->rn_key = rn_ones;
|
||||
|
||||
rh->rnh_treetop = t;
|
||||
}
|
||||
|
||||
static void
|
||||
rn_detachhead_internal(struct radix_head *head)
|
||||
{
|
||||
assert(head != NULL);
|
||||
R_Free(head);
|
||||
}
|
||||
|
||||
int
|
||||
rn_inithead(void **head, int off)
|
||||
{
|
||||
struct radix_node_head *rnh;
|
||||
struct radix_mask_head *rmh;
|
||||
|
||||
rnh = *head;
|
||||
rmh = NULL;
|
||||
|
||||
if (*head != NULL)
|
||||
return (1);
|
||||
|
||||
R_Zalloc(rnh, struct radix_node_head *, sizeof (*rnh));
|
||||
R_Zalloc(rmh, struct radix_mask_head *, sizeof (*rmh));
|
||||
if (rnh == NULL || rmh == NULL) {
|
||||
if (rnh != NULL)
|
||||
R_Free(rnh);
|
||||
if (rmh != NULL)
|
||||
R_Free(rmh);
|
||||
return (0);
|
||||
}
|
||||
|
||||
rn_inithead_internal(&rnh->rh, rnh->rnh_nodes, off);
|
||||
rn_inithead_internal(&rmh->head, rmh->mask_nodes, 0);
|
||||
*head = rnh;
|
||||
rnh->rh.rnh_masks = rmh;
|
||||
|
||||
rnh->rnh_addaddr = rn_addroute;
|
||||
rnh->rnh_deladdr = rn_delete;
|
||||
rnh->rnh_matchaddr = rn_match;
|
||||
rnh->rnh_lookup = rn_lookup;
|
||||
rnh->rnh_walktree = rn_walktree;
|
||||
rnh->rnh_walktree_from = rn_walktree_from;
|
||||
|
||||
return (1);
|
||||
}
|
||||
|
||||
static int
|
||||
rn_freeentry(struct radix_node *rn, void *arg)
|
||||
{
|
||||
struct radix_head * const rnh = arg;
|
||||
struct radix_node *x;
|
||||
|
||||
x = (struct radix_node *)rn_delete(rn + 2, NULL, rnh);
|
||||
if (x != NULL)
|
||||
R_Free(x);
|
||||
return (0);
|
||||
}
|
||||
|
||||
int
|
||||
rn_detachhead(void **head)
|
||||
{
|
||||
struct radix_node_head *rnh;
|
||||
|
||||
assert(head != NULL && *head != NULL);
|
||||
rnh = (struct radix_node_head *)(*head);
|
||||
|
||||
rn_walktree(&rnh->rh.rnh_masks->head, rn_freeentry, rnh->rh.rnh_masks);
|
||||
rn_detachhead_internal(&rnh->rh.rnh_masks->head);
|
||||
rn_detachhead_internal(&rnh->rh);
|
||||
|
||||
*head = NULL;
|
||||
|
||||
return (1);
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/*-
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*
|
||||
* Copyright (c) 1988, 1989, 1993
|
||||
* The Regents of the University of California. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the University nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#ifndef _RADIX_H_
|
||||
#define _RADIX_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mem.h"
|
||||
|
||||
/*
|
||||
* Radix search tree node layout.
|
||||
*/
|
||||
|
||||
struct radix_node {
|
||||
struct radix_mask *rn_mklist; /* list of masks contained in subtree */
|
||||
struct radix_node *rn_parent; /* parent */
|
||||
short rn_bit; /* bit offset; -1-index(netmask) */
|
||||
char rn_bmask; /* node: mask for bit test*/
|
||||
uint8_t rn_flags; /* enumerated next */
|
||||
#define RNF_NORMAL 1 /* leaf contains normal route */
|
||||
#define RNF_ROOT 2 /* leaf is root leaf for tree */
|
||||
#define RNF_ACTIVE 4 /* This node is alive (for rtfree) */
|
||||
union {
|
||||
struct { /* leaf only data: */
|
||||
uint8_t *rn_Key; /* object of search */
|
||||
uint8_t *rn_Mask; /* netmask, if present */
|
||||
struct radix_node *rn_Dupedkey;
|
||||
} rn_leaf;
|
||||
struct { /* node only data: */
|
||||
int rn_Off; /* where to start compare */
|
||||
struct radix_node *rn_L;/* progeny */
|
||||
struct radix_node *rn_R;/* progeny */
|
||||
} rn_node;
|
||||
} rn_u;
|
||||
};
|
||||
|
||||
#define rn_dupedkey rn_u.rn_leaf.rn_Dupedkey
|
||||
#define rn_key rn_u.rn_leaf.rn_Key
|
||||
#define rn_mask rn_u.rn_leaf.rn_Mask
|
||||
#define rn_offset rn_u.rn_node.rn_Off
|
||||
#define rn_left rn_u.rn_node.rn_L
|
||||
#define rn_right rn_u.rn_node.rn_R
|
||||
|
||||
/*
|
||||
* Annotations to tree concerning potential routes applying to subtrees.
|
||||
*/
|
||||
|
||||
struct radix_mask {
|
||||
short rm_bit; /* bit offset; -1-index(netmask) */
|
||||
char rm_unused; /* cf. rn_bmask */
|
||||
uint8_t rm_flags; /* cf. rn_flags */
|
||||
struct radix_mask *rm_mklist; /* more masks to try */
|
||||
union {
|
||||
uint8_t *rmu_mask; /* the mask */
|
||||
struct radix_node *rmu_leaf; /* for normal routes */
|
||||
} rm_rmu;
|
||||
int rm_refs; /* # of references to this struct */
|
||||
};
|
||||
|
||||
#define rm_mask rm_rmu.rmu_mask
|
||||
#define rm_leaf rm_rmu.rmu_leaf /* extra field would make 32 bytes */
|
||||
|
||||
struct radix_head;
|
||||
|
||||
typedef int walktree_f_t(struct radix_node *, void *);
|
||||
typedef struct radix_node *rn_matchaddr_f_t(const void *v,
|
||||
struct radix_head *head);
|
||||
typedef struct radix_node *rn_addaddr_f_t(void *v, const void *mask,
|
||||
struct radix_head *head, struct radix_node nodes[]);
|
||||
typedef struct radix_node *rn_deladdr_f_t(const void *v, const void *mask,
|
||||
struct radix_head *head);
|
||||
typedef struct radix_node *rn_lookup_f_t(const void *v, const void *mask,
|
||||
struct radix_head *head);
|
||||
typedef int rn_walktree_t(struct radix_head *head, walktree_f_t *f,
|
||||
void *w);
|
||||
typedef int rn_walktree_from_t(struct radix_head *head,
|
||||
void *a, void *m, walktree_f_t *f, void *w);
|
||||
typedef void rn_close_t(struct radix_node *rn, struct radix_head *head);
|
||||
struct radix_node *rn_nextprefix(struct radix_node *rn);
|
||||
|
||||
struct radix_mask_head;
|
||||
|
||||
struct radix_head {
|
||||
struct radix_node *rnh_treetop;
|
||||
struct radix_mask_head *rnh_masks; /* Storage for our masks */
|
||||
};
|
||||
|
||||
struct radix_node_head {
|
||||
struct radix_head rh;
|
||||
rn_matchaddr_f_t *rnh_matchaddr; /* longest match for sockaddr */
|
||||
rn_addaddr_f_t *rnh_addaddr; /* add based on sockaddr*/
|
||||
rn_deladdr_f_t *rnh_deladdr; /* remove based on sockaddr */
|
||||
rn_lookup_f_t *rnh_lookup; /* exact match for sockaddr */
|
||||
rn_walktree_t *rnh_walktree; /* traverse tree */
|
||||
rn_walktree_from_t *rnh_walktree_from; /* traverse tree below a */
|
||||
rn_close_t *rnh_close; /*do something when the last ref drops*/
|
||||
struct radix_node rnh_nodes[3]; /* empty tree for common case */
|
||||
};
|
||||
|
||||
struct radix_mask_head {
|
||||
struct radix_head head;
|
||||
struct radix_node mask_nodes[3];
|
||||
};
|
||||
|
||||
void rn_inithead_internal(struct radix_head *rh, struct radix_node *base_nodes,
|
||||
int off);
|
||||
|
||||
#define R_Malloc(p, t, n) (p = (t) u_malloc((unsigned int)(n)))
|
||||
#define R_Zalloc(p, t, n) (p = (t) u_calloc(1, (unsigned int)(n)))
|
||||
#define R_Free(p) u_free(p)
|
||||
|
||||
/* Stub lock macros — single-threaded with u_async */
|
||||
#define RADIX_NODE_HEAD_RLOCK_TRACKER
|
||||
#define RADIX_NODE_HEAD_LOCK_INIT(rnh)
|
||||
#define RADIX_NODE_HEAD_LOCK(rnh)
|
||||
#define RADIX_NODE_HEAD_UNLOCK(rnh)
|
||||
#define RADIX_NODE_HEAD_RLOCK(rnh)
|
||||
#define RADIX_NODE_HEAD_RUNLOCK(rnh)
|
||||
#define RADIX_NODE_HEAD_DESTROY(rnh)
|
||||
#define RADIX_NODE_HEAD_LOCK_ASSERT(rnh)
|
||||
#define RADIX_NODE_HEAD_WLOCK_ASSERT(rnh)
|
||||
|
||||
int rn_inithead(void **, int);
|
||||
int rn_detachhead(void **);
|
||||
int rn_refines(const void *, const void *);
|
||||
struct radix_node *rn_addroute(void *, const void *, struct radix_head *,
|
||||
struct radix_node[2]);
|
||||
struct radix_node *rn_delete(const void *, const void *, struct radix_head *);
|
||||
struct radix_node *rn_lookup (const void *v_arg, const void *m_arg,
|
||||
struct radix_head *head);
|
||||
struct radix_node *rn_match(const void *, struct radix_head *);
|
||||
int rn_walktree_from(struct radix_head *h, void *a, void *m,
|
||||
walktree_f_t *f, void *w);
|
||||
int rn_walktree(struct radix_head *, walktree_f_t *, void *);
|
||||
|
||||
#endif /* _RADIX_H_ */
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
// serialize.c - Universal binary serialization with dynamic allocation
|
||||
#include "serialize.h"
|
||||
#include <string.h>
|
||||
|
||||
#define SERIALIZE_ERR_SIZE -3 ///< превышен max_size схемы
|
||||
#define SERIALIZE_ERR_NOTSUP -4 ///< неподдерживаемый тип
|
||||
|
||||
static uint16_t length_encode(uint8_t* out, uint16_t val) {
|
||||
if (out) {
|
||||
out[0] = (uint8_t)(val >> 8);
|
||||
out[1] = (uint8_t)val;
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
static uint16_t length_decode(const uint8_t* in, size_t in_len, size_t* consumed) {
|
||||
if (!in || in_len < 2) {
|
||||
if (consumed) *consumed = 0;
|
||||
return 0;
|
||||
}
|
||||
if (consumed) *consumed = 2;
|
||||
return ((uint16_t)in[0] << 8) | in[1];
|
||||
}
|
||||
|
||||
static uint32_t get_var_count(const uint8_t* data, const struct SerializeField* f) {
|
||||
if (!data || !f) return 0;
|
||||
const uint8_t* len_ptr = data + f->len_offset;
|
||||
if (f->type == SERIALIZE_TYPE_ARRAY_U8) return *len_ptr;
|
||||
if (f->type == SERIALIZE_TYPE_ARRAY_U16) return *(const uint16_t*)len_ptr;
|
||||
if (f->type == SERIALIZE_TYPE_ARRAY_U32) return *(const uint32_t*)len_ptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_var_count(uint8_t* data, const struct SerializeField* f, uint16_t count) {
|
||||
if (!data || !f) return;
|
||||
uint8_t* len_ptr = data + f->len_offset;
|
||||
if (f->type == SERIALIZE_TYPE_ARRAY_U8) *len_ptr = (uint8_t)count;
|
||||
else if (f->type == SERIALIZE_TYPE_ARRAY_U16) *(uint16_t*)len_ptr = count;
|
||||
else if (f->type == SERIALIZE_TYPE_ARRAY_U32) *(uint32_t*)len_ptr = count;
|
||||
}
|
||||
|
||||
int serialize_encode(const void* structure,
|
||||
const struct SerializeSchema* schema,
|
||||
const uint8_t* header,
|
||||
uint8_t** out_buf,
|
||||
size_t* out_len) {
|
||||
if (!schema || !structure || !out_buf || !out_len) return SERIALIZE_ERR_NULL;
|
||||
|
||||
const uint8_t* data = (const uint8_t*)structure;
|
||||
size_t total = schema->header_len;
|
||||
|
||||
for (size_t i = 0; i < schema->field_count; i++) {
|
||||
const struct SerializeField* f = &schema->fields[i];
|
||||
|
||||
if (f->type == SERIALIZE_TYPE_FIXED || f->type == SERIALIZE_TYPE_ARRAY_FIXED) {
|
||||
total += f->elem_size;
|
||||
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
|
||||
f->type == SERIALIZE_TYPE_ARRAY_U8 ||
|
||||
f->type == SERIALIZE_TYPE_ARRAY_U16 ||
|
||||
f->type == SERIALIZE_TYPE_ARRAY_U32) {
|
||||
uint32_t count = 0;
|
||||
if (f->type == SERIALIZE_TYPE_ASCIIZ) {
|
||||
const char* str = *(const char**)(data + f->offset);
|
||||
count = str ? (uint32_t)strlen(str) : 0;
|
||||
} else {
|
||||
count = get_var_count(data, f);
|
||||
}
|
||||
uint16_t len = (count > 0xFFFFu) ? 0xFFFFu : (uint16_t)count;
|
||||
total += 2;
|
||||
if (f->elem_size == 1) {
|
||||
total += (size_t)len + 1; // данные + '\0'
|
||||
} else {
|
||||
total += (size_t)len * f->elem_size;
|
||||
}
|
||||
} else if (f->type == SERIALIZE_TYPE_LINKED) {
|
||||
// подсчёт узлов (максимум 65535)
|
||||
void* cur = *(void**)(data + f->offset);
|
||||
uint16_t count = 0;
|
||||
while (cur && count < 0xFFFFu) {
|
||||
count++;
|
||||
cur = *(void**)((uint8_t*)cur + f->len_offset);
|
||||
}
|
||||
total += 2 + (size_t)count * f->elem_size;
|
||||
} else {
|
||||
return SERIALIZE_ERR_NOTSUP;
|
||||
}
|
||||
}
|
||||
|
||||
if (schema->max_size > 0 && total > schema->max_size) {
|
||||
return SERIALIZE_ERR_SIZE;
|
||||
}
|
||||
|
||||
uint8_t* buf = u_malloc(total);
|
||||
if (!buf) return SERIALIZE_ERR_BUF;
|
||||
|
||||
uint8_t* ptr = buf;
|
||||
if (header && schema->header_len > 0) {
|
||||
memcpy(ptr, header, schema->header_len);
|
||||
ptr += schema->header_len;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < schema->field_count; i++) {
|
||||
const struct SerializeField* f = &schema->fields[i];
|
||||
const uint8_t* fld = data + f->offset;
|
||||
|
||||
if (f->type == SERIALIZE_TYPE_FIXED) {
|
||||
if (f->elem_size > 0) {
|
||||
memcpy(ptr, fld, f->elem_size);
|
||||
ptr += f->elem_size;
|
||||
}
|
||||
} else if (f->type == SERIALIZE_TYPE_ARRAY_FIXED) {
|
||||
const uint8_t* arr = *(const uint8_t**)fld;
|
||||
if (f->elem_size > 0) {
|
||||
if (arr) memcpy(ptr, arr, f->elem_size);
|
||||
else memset(ptr, 0, f->elem_size);
|
||||
}
|
||||
ptr += f->elem_size;
|
||||
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
|
||||
f->type == SERIALIZE_TYPE_ARRAY_U8 ||
|
||||
f->type == SERIALIZE_TYPE_ARRAY_U16 ||
|
||||
f->type == SERIALIZE_TYPE_ARRAY_U32) {
|
||||
uint32_t raw_count = 0;
|
||||
if (f->type == SERIALIZE_TYPE_ASCIIZ) {
|
||||
const char* str = *(const char**)fld;
|
||||
raw_count = str ? (uint32_t)strlen(str) : 0;
|
||||
} else {
|
||||
raw_count = get_var_count(data, f);
|
||||
}
|
||||
uint16_t len = (raw_count > 0xFFFFu) ? 0xFFFFu : (uint16_t)raw_count;
|
||||
if (len > 0 && f->type == SERIALIZE_TYPE_ASCIIZ) {
|
||||
if (!*(const char**)fld) len = 0;
|
||||
} else if (len > 0 && f->type != SERIALIZE_TYPE_ASCIIZ) {
|
||||
if (!*(const uint8_t**)fld) len = 0;
|
||||
}
|
||||
|
||||
ptr += length_encode(ptr, len);
|
||||
|
||||
if (f->elem_size == 1) { // ASCIIZ
|
||||
if (len > 0) {
|
||||
const char* str = *(const char**)fld;
|
||||
if (str) memcpy(ptr, str, len);
|
||||
ptr += len;
|
||||
}
|
||||
*ptr++ = '\0';
|
||||
} else { // array
|
||||
if (len > 0) {
|
||||
const uint8_t* arr = *(const uint8_t**)fld;
|
||||
if (arr) memcpy(ptr, arr, (size_t)len * f->elem_size);
|
||||
ptr += (size_t)len * f->elem_size;
|
||||
}
|
||||
}
|
||||
} else if (f->type == SERIALIZE_TYPE_LINKED) {
|
||||
void* cur = *(void**)(data + f->offset);
|
||||
uint16_t count = 0;
|
||||
while (cur && count < 0xFFFFu) {
|
||||
count++;
|
||||
cur = *(void**)((uint8_t*)cur + f->len_offset);
|
||||
}
|
||||
|
||||
ptr += length_encode(ptr, count);
|
||||
|
||||
cur = *(void**)(data + f->offset);
|
||||
for (uint16_t i = 0; i < count; i++) {
|
||||
if (!cur) break;
|
||||
memcpy(ptr, cur, f->elem_size);
|
||||
ptr += f->elem_size;
|
||||
cur = *(void**)((uint8_t*)cur + f->len_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*out_buf = buf;
|
||||
*out_len = ptr - buf;
|
||||
return SERIALIZE_ERR_OK;
|
||||
}
|
||||
|
||||
int serialize_decode(const uint8_t* in_buf,
|
||||
size_t in_len,
|
||||
const struct SerializeSchema* schema,
|
||||
void** structure) {
|
||||
if (!in_buf || !schema || !structure) {
|
||||
if (structure) *structure = NULL;
|
||||
return SERIALIZE_ERR_NULL;
|
||||
}
|
||||
|
||||
void* obj = u_malloc(schema->struct_size);
|
||||
if (!obj) {
|
||||
*structure = NULL;
|
||||
return SERIALIZE_ERR_BUF;
|
||||
}
|
||||
memset(obj, 0, schema->struct_size);
|
||||
|
||||
const uint8_t* ptr = in_buf;
|
||||
const uint8_t* end = in_buf + in_len;
|
||||
uint8_t* data = (uint8_t*)obj;
|
||||
size_t consumed;
|
||||
|
||||
for (size_t i = 0; i < schema->field_count; i++) {
|
||||
const struct SerializeField* f = &schema->fields[i];
|
||||
|
||||
if (f->type == SERIALIZE_TYPE_FIXED) {
|
||||
if (ptr + f->elem_size > end) goto decode_error;
|
||||
if (f->elem_size > 0) {
|
||||
memcpy(data + f->offset, ptr, f->elem_size);
|
||||
ptr += f->elem_size;
|
||||
}
|
||||
} else if (f->type == SERIALIZE_TYPE_ARRAY_FIXED) {
|
||||
if (ptr + f->elem_size > end) goto decode_error;
|
||||
uint8_t* arr = NULL;
|
||||
if (f->elem_size > 0) {
|
||||
arr = u_malloc(f->elem_size);
|
||||
if (arr) memcpy(arr, ptr, f->elem_size);
|
||||
}
|
||||
*(uint8_t**)(data + f->offset) = arr;
|
||||
if (f->elem_size > 0 && !arr) goto decode_error;
|
||||
ptr += f->elem_size;
|
||||
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
|
||||
f->type == SERIALIZE_TYPE_ARRAY_U8 ||
|
||||
f->type == SERIALIZE_TYPE_ARRAY_U16 ||
|
||||
f->type == SERIALIZE_TYPE_ARRAY_U32) {
|
||||
uint16_t len = length_decode(ptr, end - ptr, &consumed);
|
||||
if (consumed == 0) goto decode_error;
|
||||
ptr += consumed;
|
||||
|
||||
int is_str = (f->elem_size == 1);
|
||||
size_t needed = is_str ? (size_t)len + 1 : (size_t)len * f->elem_size;
|
||||
|
||||
if ((size_t)(end - ptr) < needed) goto decode_error;
|
||||
|
||||
if (is_str) {
|
||||
char* str = NULL;
|
||||
if (len == 0) {
|
||||
str = u_malloc(1);
|
||||
if (str) str[0] = '\0';
|
||||
} else {
|
||||
str = u_malloc(len + 1);
|
||||
if (str) {
|
||||
memcpy(str, ptr, len);
|
||||
str[len] = '\0';
|
||||
}
|
||||
}
|
||||
*(char**)(data + f->offset) = str;
|
||||
if (!str) goto decode_error;
|
||||
ptr += needed;
|
||||
} else {
|
||||
uint8_t* arr = NULL;
|
||||
if (needed > 0) {
|
||||
arr = u_malloc(needed);
|
||||
if (arr) memcpy(arr, ptr, needed);
|
||||
}
|
||||
*(uint8_t**)(data + f->offset) = arr;
|
||||
if (needed > 0 && !arr) goto decode_error;
|
||||
ptr += needed;
|
||||
}
|
||||
|
||||
if (f->type >= SERIALIZE_TYPE_ARRAY_U8 && f->type <= SERIALIZE_TYPE_ARRAY_U32) {
|
||||
set_var_count(data, f, len);
|
||||
}
|
||||
} else if (f->type == SERIALIZE_TYPE_LINKED) {
|
||||
uint16_t len = length_decode(ptr, end - ptr, &consumed);
|
||||
if (consumed == 0) goto decode_error;
|
||||
ptr += consumed;
|
||||
|
||||
void* head = NULL;
|
||||
void* prev = NULL;
|
||||
|
||||
for (uint16_t i = 0; i < len; i++) {
|
||||
if (ptr + f->elem_size > end) {
|
||||
// освободить уже созданную часть списка
|
||||
void* n = head;
|
||||
while (n) {
|
||||
void* nxt = *(void**)((uint8_t*)n + f->len_offset);
|
||||
u_free(n);
|
||||
n = nxt;
|
||||
}
|
||||
goto decode_error;
|
||||
}
|
||||
|
||||
void* new_node = u_malloc(f->elem_size);
|
||||
if (!new_node) {
|
||||
void* n = head;
|
||||
while (n) {
|
||||
void* nxt = *(void**)((uint8_t*)n + f->len_offset);
|
||||
u_free(n);
|
||||
n = nxt;
|
||||
}
|
||||
goto decode_error;
|
||||
}
|
||||
|
||||
memcpy(new_node, ptr, f->elem_size);
|
||||
// обязательно обнуляем next в новом узле
|
||||
*(void**)((uint8_t*)new_node + f->len_offset) = NULL;
|
||||
|
||||
if (!head) {
|
||||
head = new_node;
|
||||
} else {
|
||||
*(void**)((uint8_t*)prev + f->len_offset) = new_node;
|
||||
}
|
||||
prev = new_node;
|
||||
ptr += f->elem_size;
|
||||
}
|
||||
|
||||
*(void**)(data + f->offset) = head;
|
||||
} else {
|
||||
goto decode_error;
|
||||
}
|
||||
}
|
||||
|
||||
*structure = obj;
|
||||
return SERIALIZE_ERR_OK;
|
||||
|
||||
decode_error:
|
||||
serialize_free(schema, &obj);
|
||||
*structure = NULL;
|
||||
return SERIALIZE_ERR_BUF;
|
||||
}
|
||||
|
||||
void serialize_free(const struct SerializeSchema* schema, void** structure) {
|
||||
if (!schema || !structure || !*structure) return;
|
||||
|
||||
uint8_t* data = (uint8_t*)*structure;
|
||||
|
||||
for (size_t i = 0; i < schema->field_count; i++) {
|
||||
const struct SerializeField* f = &schema->fields[i];
|
||||
|
||||
if (f->type == SERIALIZE_TYPE_LINKED) {
|
||||
void** headp = (void**)(data + f->offset);
|
||||
void* node = *headp;
|
||||
while (node) {
|
||||
void* next = *(void**)((uint8_t*)node + f->len_offset);
|
||||
u_free(node);
|
||||
node = next;
|
||||
}
|
||||
*headp = NULL;
|
||||
} else if (f->type == SERIALIZE_TYPE_ASCIIZ ||
|
||||
f->type == SERIALIZE_TYPE_ARRAY_FIXED ||
|
||||
(f->type >= SERIALIZE_TYPE_ARRAY_U8 && f->type <= SERIALIZE_TYPE_ARRAY_U32)) {
|
||||
uint8_t** p = (uint8_t**)(data + f->offset);
|
||||
if (*p) {
|
||||
u_free(*p);
|
||||
*p = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u_free(*structure);
|
||||
*structure = NULL;
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/**
|
||||
* @file serialize.h
|
||||
* @brief Бинарная сериализация структур с динамическим выделением памяти
|
||||
*
|
||||
* Библиотека преобразует C-структуру, содержащую:
|
||||
* - фиксированные поля,
|
||||
* - ASCIIZ-строки (char*),
|
||||
* - динамические массивы (uint8_t* / char*),
|
||||
* - singly-linked списки
|
||||
* в компактный бинарный буфер и обратно.
|
||||
*
|
||||
* Поддерживаемые типы полей (см. SERIALIZE_TYPE_*):
|
||||
* 0 — фиксированные данные (встроены в структуру)
|
||||
* 1 — ASCIIZ-строка (char*, длина = strlen)
|
||||
* 2 — массив ровно из 1 элемента (указатель)
|
||||
* 3 — массив, количество элементов в UINT8 (по len_offset)
|
||||
* 4 — массив, количество элементов в UINT16 (по len_offset)
|
||||
* 5 — массив, количество элементов в UINT32 (по len_offset)
|
||||
* 6 — singly-linked list (next-указатель по смещению len_offset)
|
||||
*
|
||||
* Особенности:
|
||||
* • Все динамические данные (строки, массивы, списки) выделяются через u_malloc.
|
||||
* • При encode длина переменных полей кодируется всегда 2 байтами (uint16_t, big-endian).
|
||||
* • Для linked list в буфере сохраняется только количество узлов + сырые данные узлов
|
||||
* (next-указатели НЕ сериализуются, они восстанавливаются при decode).
|
||||
* • serialize_decode принимает буфер БЕЗ заголовка.
|
||||
* • serialize_free освобождает ВСЮ структуру и все вложенные динамические объекты.
|
||||
* • Поля в schema.fields должны идти в порядке возрастания offset.
|
||||
* • max_size (если > 0) — жёсткий лимит размера выходного буфера.
|
||||
*
|
||||
* @example
|
||||
* // 1. Описание структуры
|
||||
* typedef struct Node {
|
||||
* uint32_t value;
|
||||
* struct Node* next; // next по offset = 4
|
||||
* } Node;
|
||||
*
|
||||
* typedef struct {
|
||||
* uint8_t id;
|
||||
* char* name;
|
||||
* uint8_t name_len;
|
||||
* uint8_t* data;
|
||||
* uint16_t data_cnt;
|
||||
* Node* list; // linked list
|
||||
* } MyStruct;
|
||||
*
|
||||
* // 2. Схема
|
||||
* static const struct SerializeField my_fields[] = {
|
||||
* {offsetof(MyStruct, id), 1, SERIALIZE_TYPE_FIXED, 0},
|
||||
* {offsetof(MyStruct, name), 1, SERIALIZE_TYPE_ARRAY_U8, offsetof(MyStruct, name_len)},
|
||||
* {offsetof(MyStruct, data), 1, SERIALIZE_TYPE_ARRAY_U16, offsetof(MyStruct, data_cnt)},
|
||||
* {offsetof(MyStruct, list), sizeof(Node), SERIALIZE_TYPE_LINKED, offsetof(Node, next)}
|
||||
* };
|
||||
*
|
||||
* static const struct SerializeSchema my_schema = {
|
||||
* .field_count = 4,
|
||||
* .struct_size = sizeof(MyStruct),
|
||||
* .max_size = 4096, // опционально
|
||||
* .header_len = 4,
|
||||
* .fields = my_fields
|
||||
* };
|
||||
*
|
||||
* // 3. Использование
|
||||
* uint8_t header[] = {0x01, 0x02, 0x03, 0x04};
|
||||
* uint8_t *buf = NULL;
|
||||
* size_t len;
|
||||
*
|
||||
* serialize_encode(&orig, &my_schema, header, &buf, &len);
|
||||
*
|
||||
* MyStruct *restored = NULL;
|
||||
* uint8_t *data = buf + my_schema.header_len;
|
||||
* serialize_decode(data, len - my_schema.header_len, &my_schema, (void**)&restored);
|
||||
*
|
||||
* serialize_free(&my_schema, (void**)&restored);
|
||||
* u_free(buf);
|
||||
*/
|
||||
#ifndef SERIALIZE_H
|
||||
#define SERIALIZE_H
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define SERIALIZE_TYPE_FIXED 0 ///< фиксированные данные, elem_size = размер
|
||||
#define SERIALIZE_TYPE_ASCIIZ 1 ///< char* (ASCIIZ, длина = strlen)
|
||||
#define SERIALIZE_TYPE_ARRAY_FIXED 2 ///< *array ровно 1 элемент
|
||||
#define SERIALIZE_TYPE_ARRAY_U8 3 ///< *array, счётчик UINT8
|
||||
#define SERIALIZE_TYPE_ARRAY_U16 4 ///< *array, счётчик UINT16
|
||||
#define SERIALIZE_TYPE_ARRAY_U32 5 ///< *array, счётчик UINT32
|
||||
#define SERIALIZE_TYPE_LINKED 6 ///< linked_list (next по смещению len_offset)
|
||||
|
||||
/**
|
||||
* @brief Описание одного поля структуры для сериализации
|
||||
*/
|
||||
struct SerializeField {
|
||||
uint16_t offset; ///< смещение поля в структуре
|
||||
uint8_t elem_size; ///< размер элемента (для fixed = размер данных, для array/linked = размер одного элемента)
|
||||
uint8_t type; ///< SERIALIZE_TYPE_*
|
||||
uint16_t len_offset; ///< смещение поля-счётчика (игнорируется для type 0,1,2)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Схема структуры для сериализации
|
||||
*/
|
||||
struct SerializeSchema {
|
||||
uint16_t field_count; ///< число полей
|
||||
uint16_t struct_size; ///< размер основной структуры
|
||||
uint16_t max_size; ///< максимальный размер сериализованных данных (0 = без ограничения)
|
||||
uint16_t header_len; ///< длина заголовка
|
||||
const struct SerializeField* fields;
|
||||
};
|
||||
|
||||
#define SERIALIZE_ERR_OK 0 ///< успех
|
||||
#define SERIALIZE_ERR_BUF -1 ///< ошибка выделения памяти
|
||||
#define SERIALIZE_ERR_NULL -2 ///< NULL-указатель
|
||||
#define SERIALIZE_ERR_SIZE -3 ///< превышен max_size
|
||||
#define SERIALIZE_ERR_NOTSUP -4 ///< неподдерживаемый тип
|
||||
|
||||
int serialize_encode(const void* structure,
|
||||
const struct SerializeSchema* schema,
|
||||
const uint8_t* header,
|
||||
uint8_t** out_buf,
|
||||
size_t* out_len);
|
||||
|
||||
int serialize_decode(const uint8_t* in_buf, size_t in_len,
|
||||
const struct SerializeSchema* schema,
|
||||
void** structure);
|
||||
|
||||
void serialize_free(const struct SerializeSchema* schema, void** structure);
|
||||
|
||||
#endif // SERIALIZE_H
|
||||
+16
-1
@@ -211,7 +211,7 @@ int socket_set_mark(socket_t sock, int mark) {
|
||||
}
|
||||
|
||||
ssize_t socket_sendto(socket_t sock, const void* buf, size_t len,
|
||||
struct sockaddr* dest, socklen_t dest_len) {
|
||||
const struct sockaddr* dest, socklen_t dest_len) {
|
||||
#ifdef _WIN32
|
||||
return sendto(sock, (const char*)buf, (int)len, 0, dest, dest_len);
|
||||
#else
|
||||
@@ -230,3 +230,18 @@ ssize_t socket_recvfrom(socket_t sock, void* buf, size_t len,
|
||||
return recvfrom(sock, buf, len, 0, src, src_len);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t ss_get_port(const struct sockaddr_storage *addr) {
|
||||
if (!addr) return 0;
|
||||
|
||||
if (addr->ss_family == AF_INET) {
|
||||
const struct sockaddr_in *ipv4 = (const struct sockaddr_in *)addr;
|
||||
return ntohs(ipv4->sin_port);
|
||||
}
|
||||
else if (addr->ss_family == AF_INET6) {
|
||||
const struct sockaddr_in6 *ipv6 = (const struct sockaddr_in6 *)addr;
|
||||
return ntohs(ipv6->sin6_port);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+5
-1
@@ -5,6 +5,7 @@
|
||||
|
||||
#ifndef SOCKET_COMPAT_H
|
||||
#define SOCKET_COMPAT_H
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
@@ -62,11 +63,14 @@ int socket_set_mark(socket_t sock, int mark);
|
||||
|
||||
// I/O operations
|
||||
ssize_t socket_sendto(socket_t sock, const void* buf, size_t len,
|
||||
struct sockaddr* dest, socklen_t dest_len);
|
||||
const struct sockaddr* dest, socklen_t dest_len);
|
||||
ssize_t socket_recvfrom(socket_t sock, void* buf, size_t len,
|
||||
struct sockaddr* src, socklen_t* src_len);
|
||||
|
||||
// Utility
|
||||
const char* socket_strerror(int err);
|
||||
|
||||
// взять порт из sockaddr_storage
|
||||
uint16_t ss_get_port(const struct sockaddr_storage *addr);
|
||||
|
||||
#endif // SOCKET_COMPAT_H
|
||||
@@ -0,0 +1,99 @@
|
||||
/* sliding_window_min.c */
|
||||
#include "swm_min.h"
|
||||
#include <stdlib.h>
|
||||
#include <limits.h>
|
||||
|
||||
struct SlidingWindowMin {
|
||||
int* data; /* кольцевой буфер значений */
|
||||
int* deq; /* deque индексов (тоже кольцевой) */
|
||||
int dq_head;
|
||||
int dq_tail;
|
||||
int deq_count;
|
||||
int cur_pos;
|
||||
int window_size;
|
||||
};
|
||||
|
||||
SlidingWindowMin* swm_create(int window_size)
|
||||
{
|
||||
if (window_size <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SlidingWindowMin* swm = (SlidingWindowMin*)malloc(sizeof(SlidingWindowMin));
|
||||
if (!swm) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
swm->data = (int*)malloc((size_t)window_size * sizeof(int));
|
||||
if (!swm->data) {
|
||||
free(swm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
swm->deq = (int*)malloc((size_t)window_size * sizeof(int));
|
||||
if (!swm->deq) {
|
||||
free(swm->data);
|
||||
free(swm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
swm->window_size = window_size;
|
||||
swm->dq_head = 0;
|
||||
swm->dq_tail = 0;
|
||||
swm->deq_count = 0;
|
||||
swm->cur_pos = 0;
|
||||
|
||||
return swm;
|
||||
}
|
||||
|
||||
void swm_destroy(SlidingWindowMin* swm)
|
||||
{
|
||||
if (swm) {
|
||||
free(swm->data);
|
||||
free(swm->deq);
|
||||
free(swm);
|
||||
}
|
||||
}
|
||||
|
||||
void swm_add(SlidingWindowMin* swm, int val)
|
||||
{
|
||||
if (!swm) return;
|
||||
|
||||
int idx = swm->cur_pos;
|
||||
int w = swm->window_size;
|
||||
|
||||
/* записываем в кольцевой буфер */
|
||||
swm->data[idx % w] = val;
|
||||
|
||||
/* 1. удаляем из начала deque индексы, которые уже вышли за окно */
|
||||
while (swm->deq_count > 0 && swm->deq[swm->dq_head] <= idx - w) {
|
||||
swm->dq_head = (swm->dq_head + 1) % w;
|
||||
swm->deq_count--;
|
||||
}
|
||||
|
||||
/* 2. удаляем из хвоста все элементы, которые хуже текущего */
|
||||
while (swm->deq_count > 0) {
|
||||
int last_idx = swm->deq[(swm->dq_tail - 1 + w) % w];
|
||||
if (swm->data[last_idx % w] < val) {
|
||||
break;
|
||||
}
|
||||
swm->dq_tail = (swm->dq_tail - 1 + w) % w;
|
||||
swm->deq_count--;
|
||||
}
|
||||
|
||||
/* 3. добавляем текущий индекс */
|
||||
swm->deq[swm->dq_tail] = idx;
|
||||
swm->dq_tail = (swm->dq_tail + 1) % w;
|
||||
swm->deq_count++;
|
||||
|
||||
swm->cur_pos++;
|
||||
}
|
||||
|
||||
int swm_get_min(const SlidingWindowMin* swm)
|
||||
{
|
||||
if (!swm || swm->deq_count == 0) {
|
||||
return INT_MAX;
|
||||
}
|
||||
int min_idx = swm->deq[swm->dq_head];
|
||||
return swm->data[min_idx % swm->window_size];
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/* sliding_window_min.h */
|
||||
#ifndef SLIDING_WINDOW_MIN_H
|
||||
#define SLIDING_WINDOW_MIN_H
|
||||
|
||||
#include <stddef.h> /* для size_t */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct SlidingWindowMin SlidingWindowMin;
|
||||
|
||||
/**
|
||||
* Создаёт новую структуру скользящего минимума.
|
||||
* @param window_size размер окна (должен быть > 0)
|
||||
* @return указатель на структуру или NULL при ошибке выделения памяти
|
||||
*/
|
||||
SlidingWindowMin* swm_create(int window_size);
|
||||
|
||||
/**
|
||||
* Уничтожает структуру и освобождает всю динамически выделенную память.
|
||||
*/
|
||||
void swm_destroy(SlidingWindowMin* swm);
|
||||
|
||||
/**
|
||||
* Добавляет новое значение в окно.
|
||||
* После вызова swm_get_min() сразу вернёт минимум последних min(window_size, добавленных) значений.
|
||||
*/
|
||||
void swm_add(SlidingWindowMin* swm, int val);
|
||||
|
||||
/**
|
||||
* Возвращает текущий минимум в окне.
|
||||
* Если окно пустое — возвращает INT_MAX.
|
||||
*/
|
||||
int swm_get_min(const SlidingWindowMin* swm);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SLIDING_WINDOW_MIN_H */
|
||||
+441
@@ -0,0 +1,441 @@
|
||||
// tcp_io.c — управление TCP-соединением через uasync + ll_queue
|
||||
#include "tcp_io.h"
|
||||
#include "debug_config.h"
|
||||
#include "mem.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
#ifndef MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
|
||||
static void read_cb(socket_t sock, void* arg);
|
||||
static void write_cb(socket_t sock, void* arg);
|
||||
static void error_cb(socket_t sock, void* arg);
|
||||
static void resume_read_cb(struct ll_queue* q, void* arg);
|
||||
static void fin_deferred_cb(struct ll_queue* q, void* arg);
|
||||
static void write_queue_fetch_cb(struct ll_queue* q, void* arg);
|
||||
static int flush_write_buf(struct tcp_conn* tc);
|
||||
static void tcp_conn_handle_error(struct tcp_conn* tc, int err);
|
||||
static const uint8_t tcp_fin_sentinel;
|
||||
|
||||
struct tcp_conn* tcp_conn_create(
|
||||
struct UASYNC* ua, socket_t sock,
|
||||
size_t entry_data_size, size_t write_chunk_size,
|
||||
int read_high_water, int read_low_water, int rcvbuf_size,
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg),
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg)
|
||||
{
|
||||
if (!ua || sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: invalid args ua=%p", ua); return NULL; }
|
||||
|
||||
struct tcp_conn* tc = u_calloc(1, sizeof(struct tcp_conn));
|
||||
if (!tc) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: u_calloc failed"); return NULL; }
|
||||
|
||||
tc->ua = ua;
|
||||
tc->sock = sock;
|
||||
tc->entry_data_size = entry_data_size;
|
||||
tc->write_chunk_size = write_chunk_size;
|
||||
tc->read_high_water = read_high_water;
|
||||
tc->read_low_water = read_low_water;
|
||||
tc->rcvbuf_size = rcvbuf_size;
|
||||
tc->on_fin = on_fin;
|
||||
tc->on_error = on_error;
|
||||
tc->arg = arg;
|
||||
|
||||
tc->entry_pool = memory_pool_init(sizeof(struct ll_entry), "entry_pool");
|
||||
{
|
||||
size_t ds = entry_data_size > write_chunk_size ? entry_data_size : write_chunk_size;
|
||||
tc->data_pool = memory_pool_init(ds, "data_pool");
|
||||
}
|
||||
if (!tc->entry_pool || !tc->data_pool) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: memory_pool_init failed");
|
||||
if (tc->entry_pool) memory_pool_destroy(tc->entry_pool);
|
||||
if (tc->data_pool) memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc); return NULL;
|
||||
}
|
||||
|
||||
tc->read_queue = queue_new(ua, 0, 0, 0, "tcp_io_rq");
|
||||
tc->write_queue = queue_new(ua, 0, 0, 0, "tcp_io_wq");
|
||||
if (!tc->read_queue || !tc->write_queue) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: queue_new failed");
|
||||
if (tc->read_queue) queue_free(tc->read_queue);
|
||||
if (tc->write_queue) queue_free(tc->write_queue);
|
||||
memory_pool_destroy(tc->entry_pool);
|
||||
memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc); return NULL;
|
||||
}
|
||||
queue_set_threshold(tc->read_queue, read_low_water, 0);
|
||||
queue_set_threshold(tc->write_queue, 32, 0);
|
||||
queue_set_callback(tc->write_queue, write_queue_fetch_cb, tc);
|
||||
queue_set_waiter_defer(tc->write_queue, 1);
|
||||
|
||||
tc->socket_id = uasync_add_socket_t(ua, sock, read_cb, write_cb, error_cb, tc);
|
||||
if (!tc->socket_id) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: uasync_add_socket_t failed");
|
||||
queue_free(tc->read_queue);
|
||||
queue_free(tc->write_queue);
|
||||
memory_pool_destroy(tc->entry_pool);
|
||||
memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc); return NULL;
|
||||
}
|
||||
tc->write_monitor = 1;
|
||||
|
||||
if (rcvbuf_size > 0) {
|
||||
if (socket_set_buffers(sock, 0, rcvbuf_size) < 0)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d failed fd=%d", rcvbuf_size, (int)sock);
|
||||
else
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: socket_set_buffers rcvbuf=%d ok fd=%d", rcvbuf_size, (int)sock);
|
||||
}
|
||||
|
||||
{
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t alen = sizeof(addr);
|
||||
if (getpeername(sock, (struct sockaddr*)&addr, &alen) == 0) tc->connected = 1;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_create: fd=%d entry=%zu chunk=%zu hw=%d lw=%d rcvbuf=%d connected=%d",
|
||||
(int)sock, entry_data_size, write_chunk_size, read_high_water, read_low_water, rcvbuf_size, tc->connected);
|
||||
return tc;
|
||||
}
|
||||
|
||||
void tcp_conn_destroy(struct tcp_conn* tc) {
|
||||
if (!tc) return;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_conn_destroy: fd=%d connected=%d error=%d fin_remote=%d fin_local=%d closed=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed);
|
||||
|
||||
if (tc->socket_id) {
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
tc->socket_id = NULL;
|
||||
}
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
queue_set_empty_callback(tc->read_queue, NULL, NULL);
|
||||
queue_set_callback(tc->read_queue, NULL, NULL);
|
||||
queue_set_callback(tc->write_queue, NULL, NULL);
|
||||
|
||||
struct ll_entry* e;
|
||||
while ((e = queue_data_get(tc->read_queue)) != NULL) {
|
||||
if (e->dgram) memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e); queue_resume_callback(tc->read_queue);
|
||||
}
|
||||
while ((e = queue_data_get(tc->write_queue)) != NULL) {
|
||||
if (e->dgram && e->dgram != &tcp_fin_sentinel) memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e); queue_resume_callback(tc->write_queue);
|
||||
}
|
||||
queue_free(tc->read_queue);
|
||||
queue_free(tc->write_queue);
|
||||
|
||||
if (tc->write_buf) memory_pool_free(tc->data_pool, tc->write_buf);
|
||||
if (tc->sock != SOCKET_INVALID) socket_close_wrapper(tc->sock);
|
||||
memory_pool_destroy(tc->entry_pool);
|
||||
memory_pool_destroy(tc->data_pool);
|
||||
u_free(tc);
|
||||
}
|
||||
|
||||
static void tcp_conn_handle_error(struct tcp_conn* tc, int err)
|
||||
{
|
||||
tc->error = 1;
|
||||
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
queue_set_empty_callback(tc->read_queue, NULL, NULL);
|
||||
queue_set_callback(tc->read_queue, NULL, NULL);
|
||||
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
while (1) {
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) break;
|
||||
uint8_t* buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!buf) { queue_entry_free(e); break; }
|
||||
ssize_t n = recv(tc->sock, buf, tc->entry_data_size, 0);
|
||||
if (n > 0) { e->dgram = buf; e->len = (uint16_t)n; queue_data_put(tc->read_queue, e); }
|
||||
else { memory_pool_free(tc->data_pool, buf); queue_entry_free(e); break; }
|
||||
}
|
||||
}
|
||||
|
||||
if (tc->socket_id) {
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
tc->socket_id = NULL;
|
||||
}
|
||||
if (tc->sock != SOCKET_INVALID) {
|
||||
socket_close_wrapper(tc->sock);
|
||||
tc->sock = SOCKET_INVALID;
|
||||
}
|
||||
|
||||
queue_set_callback(tc->write_queue, NULL, NULL);
|
||||
|
||||
if (tc->on_error) {
|
||||
void (*on_err)(struct tcp_conn*, int, void*) = tc->on_error;
|
||||
void* arg = tc->arg;
|
||||
tc->on_error = NULL;
|
||||
on_err(tc, err, arg);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Чтение из сокета
|
||||
// ====================================================================
|
||||
|
||||
static void read_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
uint8_t* buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!e || !buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: read_cb alloc failed fd=%d", (int)tc->sock);
|
||||
if (e) queue_entry_free(e);
|
||||
if (buf) memory_pool_free(tc->data_pool, buf);
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
tc->read_paused = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t n = recv(tc->sock, buf, tc->entry_data_size, 0);
|
||||
if (n > 0) {
|
||||
e->dgram = buf;
|
||||
e->len = (uint16_t)n;
|
||||
queue_data_put(tc->read_queue, e);
|
||||
if (tc->read_queue->count >= tc->read_high_water && !tc->read_paused) {
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
tc->read_paused = 1;
|
||||
queue_waiter_wait(tc->read_queue, &tc->read_waiter, resume_read_cb, tc);
|
||||
}
|
||||
} else if (n == 0) {
|
||||
memory_pool_free(tc->data_pool, buf);
|
||||
queue_entry_free(e);
|
||||
tc->fin_remote = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
if (tc->read_queue->count == 0) {
|
||||
if (tc->on_fin) tc->on_fin(tc, tc->arg);
|
||||
} else {
|
||||
queue_set_empty_callback(tc->read_queue, fin_deferred_cb, tc);
|
||||
}
|
||||
} else {
|
||||
memory_pool_free(tc->data_pool, buf);
|
||||
queue_entry_free(e);
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: recv error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
}
|
||||
}
|
||||
|
||||
static void resume_read_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 1);
|
||||
tc->read_paused = 0;
|
||||
}
|
||||
|
||||
static void fin_deferred_cb(struct ll_queue* q, void* arg) {
|
||||
(void)q;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (tc->on_fin) tc->on_fin(tc, tc->arg);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Запись в сокет
|
||||
// ====================================================================
|
||||
|
||||
static int flush_write_buf(struct tcp_conn* tc) {
|
||||
while (tc->write_buf && tc->write_offset < tc->write_len) {
|
||||
ssize_t n = send(tc->sock, tc->write_buf + tc->write_offset, tc->write_len - tc->write_offset, MSG_NOSIGNAL);
|
||||
if (n > 0) {
|
||||
tc->write_offset += (size_t)n;
|
||||
if (tc->write_offset >= tc->write_len) {
|
||||
memory_pool_free(tc->data_pool, tc->write_buf);
|
||||
tc->write_buf = NULL;
|
||||
tc->write_len = 0;
|
||||
tc->write_offset = 0;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: flush_wbuf done fd=%d", (int)tc->sock);
|
||||
return 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) return 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void write_queue_fetch_cb(struct ll_queue* q, void* arg) {
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
|
||||
if (tc->error || tc->closed) { DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch skip fd=%d error=%d closed=%d", (int)tc->sock, tc->error, tc->closed); return; }
|
||||
|
||||
if (tc->write_buf) {
|
||||
if (flush_write_buf(tc)) return;
|
||||
if (tc->write_buf) return; // остался остаток, ждём write_cb
|
||||
}
|
||||
|
||||
if (!tc->connected) { DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch skip fd=%d not connected", (int)tc->sock); return; }
|
||||
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
if (!e) {
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 0);
|
||||
tc->write_monitor = 0;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT OFF fd=%d (queue empty)", (int)tc->sock);
|
||||
queue_resume_callback(q);
|
||||
if (tc->on_flushed) {
|
||||
void (*cb)(struct tcp_conn*, void*) = tc->on_flushed;
|
||||
tc->on_flushed = NULL;
|
||||
cb(tc, tc->arg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e->len == 0) {
|
||||
if (!e->dgram) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: CLOSE fd=%d", (int)tc->sock);
|
||||
queue_entry_free(e); queue_resume_callback(q);
|
||||
uasync_remove_socket_t(tc->ua, tc->sock);
|
||||
socket_close_wrapper(tc->sock);
|
||||
tc->socket_id = NULL; tc->sock = SOCKET_INVALID; tc->closed = 1;
|
||||
if (tc->on_closed) tc->on_closed(tc, tc->arg);
|
||||
} else if (e->dgram == &tcp_fin_sentinel) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: FIN fd=%d", (int)tc->sock);
|
||||
queue_entry_free(e); queue_resume_callback(q);
|
||||
shutdown(tc->sock, SHUT_WR);
|
||||
tc->fin_local = 1;
|
||||
if (tc->on_fin_sent) tc->on_fin_sent(tc, tc->arg);
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch zero-len entry with unknown dgram=%p fd=%d", (void*)e->dgram, (int)tc->sock);
|
||||
queue_entry_free(e); queue_resume_callback(q);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t n = send(tc->sock, e->dgram, e->len, MSG_NOSIGNAL);
|
||||
if (n == (ssize_t)e->len) {
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
queue_resume_callback(q);
|
||||
} else if (n > 0) {
|
||||
size_t rem = e->len - (size_t)n;
|
||||
tc->write_buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, ENOMEM);
|
||||
return;
|
||||
}
|
||||
memcpy(tc->write_buf, e->dgram + n, rem);
|
||||
tc->write_len = rem;
|
||||
tc->write_offset = 0;
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 1);
|
||||
tc->write_monitor = 1;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT ON fd=%d (partial send, wbuf=%zu bytes)", (int)tc->sock, tc->write_len);
|
||||
} else if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) {
|
||||
tc->write_buf = memory_pool_alloc(tc->data_pool);
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: write_buf alloc failed fd=%d", (int)tc->sock);
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, ENOMEM);
|
||||
return;
|
||||
}
|
||||
memcpy(tc->write_buf, e->dgram, e->len);
|
||||
tc->write_len = e->len;
|
||||
tc->write_offset = 0;
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
uasync_set_socket_write(tc->ua, tc->socket_id, 1);
|
||||
tc->write_monitor = 1;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: wq_fetch POLLOUT ON fd=%d (EAGAIN, wbuf=%zu bytes)", (int)tc->sock, tc->write_len);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: send error fd=%d errno=%d", (int)tc->sock, errno);
|
||||
memory_pool_free(tc->data_pool, e->dgram);
|
||||
queue_entry_free(e);
|
||||
tcp_conn_handle_error(tc, errno);
|
||||
}
|
||||
}
|
||||
|
||||
static void write_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
|
||||
if (!tc->connected) {
|
||||
int err = 0;
|
||||
socklen_t len = sizeof(err);
|
||||
if (getsockopt(tc->sock, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) {
|
||||
tc->connected = 1;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: connect ok fd=%d", (int)tc->sock);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: connect fail fd=%d err=%d", (int)tc->sock, err);
|
||||
tcp_conn_handle_error(tc, err ? err : -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (flush_write_buf(tc)) return;
|
||||
if (!tc->write_buf) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_SOCKET, "tcp_io: write_cb flushed fd=%d wmon=%d (resume WQ cb)", (int)tc->sock, tc->write_monitor);
|
||||
queue_resume_callback(tc->write_queue);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Асинхронная ошибка сокета
|
||||
// ====================================================================
|
||||
|
||||
static void error_cb(socket_t sock, void* arg) {
|
||||
(void)sock;
|
||||
struct tcp_conn* tc = (struct tcp_conn*)arg;
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: async error fd=%d connected=%d err=%d fin_r=%d fin_l=%d closed=%d wmon=%d",
|
||||
(int)tc->sock, tc->connected, tc->error, tc->fin_remote, tc->fin_local, tc->closed, tc->write_monitor);
|
||||
tcp_conn_handle_error(tc, -1);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// FIN / Close через очередь отправки
|
||||
// ====================================================================
|
||||
|
||||
int tcp_conn_push_fin(struct tcp_conn* tc) {
|
||||
if (!tc || tc->sock == SOCKET_INVALID) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin invalid tc"); return -1; }
|
||||
if (tc->fin_local) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin already fin_local fd=%d", (int)tc->sock); return -1; }
|
||||
if (tc->closed) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin already closed fd=%d", (int)tc->sock); return -1; }
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
|
||||
e->dgram = (uint8_t*)&tcp_fin_sentinel; e->len = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_fin fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
queue_data_put(tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tcp_conn_push_close(struct tcp_conn* tc) {
|
||||
if (!tc || tc->sock == SOCKET_INVALID) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close invalid tc"); return -1; }
|
||||
if (tc->closed) { DEBUG_WARN(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close already closed fd=%d", (int)tc->sock); return -1; }
|
||||
struct ll_entry* e = queue_entry_new_from_pool(tc->entry_pool);
|
||||
if (!e) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close entry_pool exhausted fd=%d", (int)tc->sock); return -1; }
|
||||
e->dgram = NULL; e->len = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_SOCKET, "tcp_io: push_close fd=%d wq=%d", (int)tc->sock, tc->write_queue->count + 1);
|
||||
queue_data_put(tc->write_queue, e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcp_conn_set_flushed(struct tcp_conn* tc, void (*on_flushed)(struct tcp_conn* tc, void* arg)) {
|
||||
if (!tc) return;
|
||||
tc->on_flushed = on_flushed;
|
||||
}
|
||||
|
||||
void tcp_conn_pause_read(struct tcp_conn* tc) {
|
||||
if (!tc || tc->sock == SOCKET_INVALID) return;
|
||||
uasync_set_socket_read(tc->ua, tc->socket_id, 0);
|
||||
queue_waiter_cancel(tc->read_queue, &tc->read_waiter);
|
||||
tc->read_paused = 1;
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
// tcp_io.h — управление TCP-соединением через uasync + ll_queue
|
||||
//
|
||||
// Один tcp_conn = одно TCP-соединение. Два пула:
|
||||
// entry_pool — struct ll_entry (без inline data)
|
||||
// data_pool — буферы (max(entry_data_size, write_chunk_size))
|
||||
//
|
||||
// Чтение: read_cb → recv() в data_pool → entry в read_queue → callback внешнего кода.
|
||||
// high_water/low_water управляют backpressure (EPOLLIN on/off).
|
||||
// FIN (recv==0) → on_fin (сразу или через empty_callback после дренажа очереди).
|
||||
//
|
||||
// Запись: внешний код кладёт entry в write_queue через queue_data_put().
|
||||
// write_queue_fetch_cb (deferred) → send(), при EAGAIN → write_buf + EPOLLOUT.
|
||||
// Всё отправлено → on_flushed.
|
||||
//
|
||||
// FIN/Close: tcp_conn_push_fin/close ставят сентинел в write_queue.
|
||||
// FIN → shutdown(SHUT_WR) → on_fin_sent
|
||||
// CLOSE → close сокета → on_closed
|
||||
//
|
||||
// Ошибка: любая ошибка сокета → tcp_conn_handle_error → close + null коллбэков → on_error.
|
||||
// Реализация on_error обязана вызвать tcp_conn_destroy. После возврата tc недействителен.
|
||||
|
||||
#ifndef TCP_IO_H
|
||||
#define TCP_IO_H
|
||||
|
||||
#include "u_async.h"
|
||||
#include "socket_compat.h"
|
||||
#include "ll_queue.h"
|
||||
#include "memory_pool.h"
|
||||
|
||||
struct tcp_conn {
|
||||
socket_t sock;
|
||||
struct UASYNC* ua;
|
||||
void* socket_id;
|
||||
|
||||
struct ll_queue* read_queue; // сокет → данные (блоки до entry_data_size)
|
||||
struct ll_queue* write_queue; // данные → сокет (блоки до write_chunk_size)
|
||||
|
||||
int read_high_water;
|
||||
int read_low_water;
|
||||
int rcvbuf_size;
|
||||
uint8_t read_paused;
|
||||
|
||||
uint8_t write_monitor; // 1 = EPOLLOUT активен
|
||||
uint8_t connected;
|
||||
uint8_t error;
|
||||
uint8_t fin_remote; // FIN получен от удалённой стороны (recv == 0)
|
||||
uint8_t fin_local; // FIN отправлен удалённой стороне (shutdown SHUT_WR)
|
||||
uint8_t closed; // сокет полностью закрыт (close)
|
||||
|
||||
// Частичная отправка (из data_pool, не в очереди — досылается первой)
|
||||
uint8_t* write_buf;
|
||||
size_t write_len;
|
||||
size_t write_offset;
|
||||
size_t write_chunk_size;
|
||||
|
||||
// Пулы памяти
|
||||
struct memory_pool* entry_pool; // sizeof(struct ll_entry)
|
||||
struct memory_pool* data_pool; // max(entry_data_size, write_chunk_size)
|
||||
size_t entry_data_size;
|
||||
|
||||
struct queue_waiter_handle read_waiter;
|
||||
|
||||
// Коллбэки
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg); // FIN получен от удалённой стороны (fin_remote=1)
|
||||
void (*on_fin_sent)(struct tcp_conn* tc, void* arg); // FIN отправлен удалённой стороне (fin_local=1)
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg); // фатальная ошибка (вызывается после close сокета; обязан вызвать tcp_conn_destroy)
|
||||
void (*on_flushed)(struct tcp_conn* tc, void* arg); // все данные записи отправлены (write_queue + write_buf пусты)
|
||||
void (*on_closed)(struct tcp_conn* tc, void* arg); // сокет закрыт через очередь (после close)
|
||||
void* arg;
|
||||
};
|
||||
|
||||
struct tcp_conn* tcp_conn_create(
|
||||
struct UASYNC* ua,
|
||||
socket_t sock,
|
||||
size_t entry_data_size,
|
||||
size_t write_chunk_size,
|
||||
int read_high_water,
|
||||
int read_low_water,
|
||||
int rcvbuf_size,
|
||||
void (*on_fin)(struct tcp_conn* tc, void* arg),
|
||||
void (*on_error)(struct tcp_conn* tc, int err, void* arg),
|
||||
void* arg);
|
||||
|
||||
// Дренирует очереди, закрывает сокет, уничтожает пулы, освобождает tc.
|
||||
void tcp_conn_destroy(struct tcp_conn* tc);
|
||||
|
||||
// Запись: queue_data_put(tc->write_queue, entry) напрямую.
|
||||
// Автозабор (deferred) сам шлёт когда сокет готов.
|
||||
// Порог: 32 записи, при заполнении queue_waiter_wait.
|
||||
|
||||
// Поставить сигналы в очередь отправки. Все данные в очереди перед сигналом
|
||||
// будут отправлены до его обработки. Кодирование: dgram=NULL → close, len=0+dgram!=NULL → FIN.
|
||||
// tcp_conn_push_fin(tc) — FIN: после отправки предшествующих данных вызывает
|
||||
// shutdown(SHUT_WR) и on_fin_sent.
|
||||
// Повторный вызов игнорируется (fin_sent уже установлен).
|
||||
// tcp_conn_push_close(tc) — CLOSE: после отправки предшествующих данных закрывает
|
||||
// сокет (close), вызывает on_closed.
|
||||
// После close сокет удалён из uasync, tc->closed=1.
|
||||
// Повторный вызов игнорируется.
|
||||
int tcp_conn_push_fin(struct tcp_conn* tc);
|
||||
int tcp_conn_push_close(struct tcp_conn* tc);
|
||||
|
||||
// Одноразовый коллбэк: вызывается когда write_queue + write_buf полностью опустели.
|
||||
// После вызова сбрасывается. Установить повторно можно в любой момент.
|
||||
void tcp_conn_set_flushed(struct tcp_conn* tc, void (*on_flushed)(struct tcp_conn* tc, void* arg));
|
||||
|
||||
// Принудительная остановка чтения: убирает EPOLLIN и отменяет read_waiter.
|
||||
void tcp_conn_pause_read(struct tcp_conn* tc);
|
||||
|
||||
#endif
|
||||
+112
-85
@@ -11,21 +11,21 @@
|
||||
#define LEFT_CHILD(i) (2 * (i))
|
||||
#define RIGHT_CHILD(i) (2 * (i) + 1)
|
||||
|
||||
TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH1...");
|
||||
TimeoutHeap *h = u_malloc(sizeof(TimeoutHeap));
|
||||
if (!h) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH0 error...");
|
||||
return NULL;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH2...");
|
||||
h->heap = u_malloc(sizeof(TimeoutEntry) * initial_capacity);
|
||||
if (!h->heap) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH1 error...");
|
||||
u_free(h);
|
||||
return NULL;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH3...");
|
||||
TimeoutHeap *timeout_heap_create(size_t initial_capacity) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
|
||||
TimeoutHeap *h = u_malloc(sizeof(TimeoutHeap));
|
||||
if (!h) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH0 error...");
|
||||
return NULL;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH2...");
|
||||
h->heap = u_malloc(sizeof(TimeoutEntry) * initial_capacity);
|
||||
if (!h->heap) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH1 error...");
|
||||
u_free(h);
|
||||
return NULL;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH3...");
|
||||
h->size = 0;
|
||||
h->capacity = initial_capacity;
|
||||
h->freed_count = 0;
|
||||
@@ -50,70 +50,81 @@ void timeout_heap_destroy(TimeoutHeap *h) {
|
||||
}
|
||||
|
||||
|
||||
static void bubble_up(TimeoutHeap *h, size_t i) {
|
||||
// i is 1-based
|
||||
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) {
|
||||
// Swap with parent
|
||||
TimeoutEntry temp = h->heap[PARENT(i) - 1];
|
||||
h->heap[PARENT(i) - 1] = h->heap[i - 1];
|
||||
h->heap[i - 1] = temp;
|
||||
i = PARENT(i);
|
||||
}
|
||||
static void update_index(TimeoutHeap *h, size_t idx) {
|
||||
if (h->heap[idx].index_ptr)
|
||||
*h->heap[idx].index_ptr = idx;
|
||||
}
|
||||
|
||||
static void swap_entries(TimeoutHeap *h, size_t a, size_t b) {
|
||||
TimeoutEntry temp = h->heap[a];
|
||||
h->heap[a] = h->heap[b];
|
||||
h->heap[b] = temp;
|
||||
update_index(h, a);
|
||||
update_index(h, b);
|
||||
}
|
||||
|
||||
static void bubble_up(TimeoutHeap *h, size_t i) {
|
||||
// i is 1-based
|
||||
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) {
|
||||
swap_entries(h, PARENT(i) - 1, i - 1);
|
||||
i = PARENT(i);
|
||||
}
|
||||
}
|
||||
|
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data) {
|
||||
if (h->size == h->capacity) {
|
||||
size_t new_cap = h->capacity ? h->capacity * 2 : 1;
|
||||
TimeoutEntry *new_heap = u_realloc(h->heap, sizeof(TimeoutEntry) * new_cap);
|
||||
if (!new_heap) return -1; // Allocation failed
|
||||
h->heap = new_heap;
|
||||
h->capacity = new_cap;
|
||||
}
|
||||
|
||||
// Insert at end (0-based)
|
||||
size_t idx = h->size++;
|
||||
h->heap[idx].expiration = expiration;
|
||||
h->heap[idx].data = data;
|
||||
h->heap[idx].deleted = 0;
|
||||
|
||||
// Bubble up (1-based)
|
||||
bubble_up(h, idx + 1);
|
||||
return 0;
|
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr) {
|
||||
if (h->size == h->capacity) {
|
||||
size_t new_cap = h->capacity ? h->capacity * 2 : 1;
|
||||
TimeoutEntry *new_heap = u_realloc(h->heap, sizeof(TimeoutEntry) * new_cap);
|
||||
if (!new_heap) return -1; // Allocation failed
|
||||
h->heap = new_heap;
|
||||
h->capacity = new_cap;
|
||||
}
|
||||
|
||||
// Insert at end (0-based)
|
||||
size_t idx = h->size++;
|
||||
h->heap[idx].expiration = expiration;
|
||||
h->heap[idx].data = data;
|
||||
h->heap[idx].index_ptr = index_ptr;
|
||||
h->heap[idx].deleted = 0;
|
||||
if (index_ptr)
|
||||
*index_ptr = idx;
|
||||
|
||||
// Bubble up (1-based)
|
||||
bubble_up(h, idx + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void heapify_down(TimeoutHeap *h, size_t i) {
|
||||
// i is 1-based
|
||||
while (1) {
|
||||
size_t smallest = i;
|
||||
size_t left = LEFT_CHILD(i);
|
||||
size_t right = RIGHT_CHILD(i);
|
||||
|
||||
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) {
|
||||
smallest = left;
|
||||
}
|
||||
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) {
|
||||
smallest = right;
|
||||
}
|
||||
if (smallest == i) break;
|
||||
|
||||
// Swap
|
||||
TimeoutEntry temp = h->heap[smallest - 1];
|
||||
h->heap[smallest - 1] = h->heap[i - 1];
|
||||
h->heap[i - 1] = temp;
|
||||
i = smallest;
|
||||
}
|
||||
static void heapify_down(TimeoutHeap *h, size_t i) {
|
||||
// i is 1-based
|
||||
while (1) {
|
||||
size_t smallest = i;
|
||||
size_t left = LEFT_CHILD(i);
|
||||
size_t right = RIGHT_CHILD(i);
|
||||
|
||||
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) {
|
||||
smallest = left;
|
||||
}
|
||||
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) {
|
||||
smallest = right;
|
||||
}
|
||||
if (smallest == i) break;
|
||||
|
||||
swap_entries(h, smallest - 1, i - 1);
|
||||
i = smallest;
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_root(TimeoutHeap *h) {
|
||||
if (h->size == 0) return;
|
||||
|
||||
// Move last to root
|
||||
h->heap[0] = h->heap[--h->size];
|
||||
|
||||
// Heapify down (1-based)
|
||||
if (h->size > 0) {
|
||||
heapify_down(h, 1);
|
||||
}
|
||||
static void remove_root(TimeoutHeap *h) {
|
||||
if (h->size == 0) return;
|
||||
|
||||
// Move last to root
|
||||
h->heap[0] = h->heap[--h->size];
|
||||
update_index(h, 0);
|
||||
|
||||
// Heapify down (1-based)
|
||||
if (h->size > 0) {
|
||||
heapify_down(h, 1);
|
||||
}
|
||||
}
|
||||
|
||||
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) {
|
||||
@@ -149,18 +160,34 @@ int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) {
|
||||
for (size_t i = 0; i < h->size; ++i) {
|
||||
if (h->heap[i].expiration == expiration && h->heap[i].data == data) {
|
||||
h->heap[i].deleted = 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1; // Not found
|
||||
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) {
|
||||
for (size_t i = 0; i < h->size; ++i) {
|
||||
if (h->heap[i].expiration == expiration && h->heap[i].data == data) {
|
||||
h->heap[i].deleted = 1;
|
||||
h->heap[i].expiration = 0;
|
||||
bubble_up(h, i + 1);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1; // Not found
|
||||
}
|
||||
|
||||
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data) {
|
||||
if (index >= h->size || h->heap[index].data != data) return -1;
|
||||
|
||||
h->heap[index].deleted = 1;
|
||||
h->heap[index].expiration = 0;
|
||||
bubble_up(h, index + 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)) {
|
||||
if (!h) return;
|
||||
h->user_data = user_data;
|
||||
h->free_callback = callback;
|
||||
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)) {
|
||||
if (!h) return;
|
||||
h->user_data = user_data;
|
||||
h->free_callback = callback;
|
||||
}
|
||||
|
||||
size_t timeout_heap_get_size(TimeoutHeap *h) {
|
||||
if (!h) return 0;
|
||||
return h->size;
|
||||
}
|
||||
+6
-1
@@ -11,6 +11,7 @@ typedef uint64_t TimeoutTime; // e.g., milliseconds since epoch or from now
|
||||
typedef struct {
|
||||
TimeoutTime expiration; // Sort key (smaller = earlier)
|
||||
void *data; // User data (e.g., callback or ID)
|
||||
size_t *index_ptr; // Pointer to node's heap_index (NULL = no tracking)
|
||||
int deleted; // 0 = active, 1 = deleted
|
||||
} TimeoutEntry;
|
||||
|
||||
@@ -53,7 +54,7 @@ void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*call
|
||||
* @param data User data associated with the timeout.
|
||||
* @return 0 on success, -1 on allocation failure.
|
||||
*/
|
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data);
|
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr);
|
||||
|
||||
/**
|
||||
* Peek at the earliest non-deleted timeout without removing it.
|
||||
@@ -82,6 +83,8 @@ int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out);
|
||||
*/
|
||||
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data);
|
||||
|
||||
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data);
|
||||
|
||||
/**
|
||||
* Get the number of freed timer nodes.
|
||||
* @param h The heap.
|
||||
@@ -89,4 +92,6 @@ int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data);
|
||||
*/
|
||||
size_t timeout_heap_get_freed_count(TimeoutHeap *h);
|
||||
|
||||
size_t timeout_heap_get_size(TimeoutHeap *h);
|
||||
|
||||
#endif // TIMEOUT_HEAP_H
|
||||
+396
-268
@@ -32,25 +32,29 @@
|
||||
|
||||
// Timeout node with safe cancellation
|
||||
struct timeout_node {
|
||||
char name[16];
|
||||
void* arg;
|
||||
timeout_callback_t callback;
|
||||
uint64_t expiration_ms; // absolute expiration time in milliseconds
|
||||
struct UASYNC* ua; // Pointer back to uasync instance for counter updates
|
||||
struct timeout_node* next; // For immediate queue (FIFO)
|
||||
size_t heap_index; // Position in timeout_heap (SIZE_MAX if not in heap)
|
||||
};
|
||||
|
||||
// Socket node with array-based storage
|
||||
struct socket_node {
|
||||
int fd; // File descriptor (for pipe, file)
|
||||
socket_t sock; // Socket (for cross-platform sockets)
|
||||
int type; // SOCKET_NODE_TYPE_FD or SOCKET_NODE_TYPE_SOCK
|
||||
socket_callback_t read_cbk; // For FD type
|
||||
socket_callback_t write_cbk; // For FD type
|
||||
socket_t_callback_t read_cbk_sock; // For SOCK type
|
||||
socket_t_callback_t write_cbk_sock; // For SOCK type
|
||||
socket_callback_t except_cbk;
|
||||
void* user_data;
|
||||
int active; // 1 if socket is active, 0 if u_freed (for reuse)
|
||||
struct socket_node {
|
||||
int fd; // File descriptor (for pipe, file)
|
||||
socket_t sock; // Socket (for cross-platform sockets)
|
||||
int type; // SOCKET_NODE_TYPE_FD or SOCKET_NODE_TYPE_SOCK
|
||||
socket_callback_t read_cbk; // For FD type
|
||||
socket_callback_t write_cbk; // For FD type
|
||||
socket_t_callback_t read_cbk_sock; // For SOCK type
|
||||
socket_t_callback_t write_cbk_sock; // For SOCK type
|
||||
socket_callback_t except_cbk;
|
||||
void* user_data;
|
||||
int active; // 1 if socket is active, 0 if u_freed (for reuse)
|
||||
int enable_read; // 1 if read monitoring is enabled
|
||||
int enable_write; // 1 if write monitoring is enabled
|
||||
};
|
||||
|
||||
// Array-based socket management for O(1) operations
|
||||
@@ -185,10 +189,12 @@ static int socket_array_add_internal(struct socket_array* sa, int fd, socket_t s
|
||||
sa->sockets[index].write_cbk = write_cbk_fd;
|
||||
sa->sockets[index].read_cbk_sock = read_cbk_sock;
|
||||
sa->sockets[index].write_cbk_sock = write_cbk_sock;
|
||||
sa->sockets[index].except_cbk = except_cbk;
|
||||
sa->sockets[index].user_data = user_data;
|
||||
sa->sockets[index].active = 1;
|
||||
|
||||
sa->sockets[index].except_cbk = except_cbk;
|
||||
sa->sockets[index].user_data = user_data;
|
||||
sa->sockets[index].active = 1;
|
||||
sa->sockets[index].enable_read = (read_cbk_fd != NULL || read_cbk_sock != NULL) ? 1 : 0;
|
||||
sa->sockets[index].enable_write = (write_cbk_fd != NULL || write_cbk_sock != NULL) ? 1 : 0;
|
||||
|
||||
sa->fd_to_index[fd] = index;
|
||||
sa->index_to_fd[index] = fd;
|
||||
sa->active_indices[sa->count] = index; // Add to active list
|
||||
@@ -227,11 +233,19 @@ static int socket_array_remove(struct socket_array* sa, int fd) {
|
||||
int index = sa->fd_to_index[fd];
|
||||
if (index == -1 || !sa->sockets[index].active) return -1; // FD not found
|
||||
|
||||
// Mark as inactive
|
||||
sa->sockets[index].active = 0;
|
||||
sa->sockets[index].fd = -1;
|
||||
sa->sockets[index].sock = SOCKET_INVALID;
|
||||
sa->sockets[index].type = SOCKET_NODE_TYPE_FD;
|
||||
// Mark as inactive and clear all pointers
|
||||
sa->sockets[index].active = 0;
|
||||
sa->sockets[index].fd = -1;
|
||||
sa->sockets[index].sock = SOCKET_INVALID;
|
||||
sa->sockets[index].type = SOCKET_NODE_TYPE_FD;
|
||||
sa->sockets[index].read_cbk = NULL;
|
||||
sa->sockets[index].write_cbk = NULL;
|
||||
sa->sockets[index].read_cbk_sock = NULL;
|
||||
sa->sockets[index].write_cbk_sock = NULL;
|
||||
sa->sockets[index].except_cbk = NULL;
|
||||
sa->sockets[index].user_data = NULL;
|
||||
sa->sockets[index].enable_read = 0;
|
||||
sa->sockets[index].enable_write = 0;
|
||||
sa->fd_to_index[fd] = -1;
|
||||
sa->index_to_fd[index] = -1;
|
||||
|
||||
@@ -319,12 +333,27 @@ uint64_t get_time_tb(void) {
|
||||
// QueryPerformanceCounter(&count); // Получаем текущее значение счётчика
|
||||
// return (uint64_t)(count.QuadPart * 10000ULL) / (uint64_t)freq.QuadPart; // Преобразуем в требуемые единицы времени
|
||||
//}
|
||||
#else
|
||||
uint64_t get_time_tb(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 10000ULL + (uint64_t)ts.tv_nsec / 100000ULL; // Преобразуем в требуемые единицы времени
|
||||
}
|
||||
#else
|
||||
uint64_t get_time_tb(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 10000ULL + (uint64_t)ts.tv_nsec / 100000ULL; // Преобразуем в требуемые единицы времени
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
uint64_t get_time_us(void) {
|
||||
LARGE_INTEGER freq, count;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
QueryPerformanceCounter(&count);
|
||||
return (uint64_t)(count.QuadPart * 1000000ULL / freq.QuadPart);
|
||||
}
|
||||
#else
|
||||
uint64_t get_time_us(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -421,6 +450,7 @@ static void process_timeouts(struct UASYNC* ua) {
|
||||
}
|
||||
|
||||
if (node && node->callback) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "timer→immediate %s", node->name[0] ? node->name : "");
|
||||
node->callback(node->arg);
|
||||
}
|
||||
|
||||
@@ -446,7 +476,7 @@ static void process_timeouts(struct UASYNC* ua) {
|
||||
struct timeout_node* node = (struct timeout_node*)entry.data;
|
||||
|
||||
if (node && node->callback) {
|
||||
// Execute callback only if not cancelled
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "timer→%s expired", node->name[0] ? node->name : "");
|
||||
node->callback(node->arg);
|
||||
}
|
||||
|
||||
@@ -491,32 +521,39 @@ static void get_next_timeout(struct UASYNC* ua, struct timeval* tv) {
|
||||
|
||||
|
||||
|
||||
// Instance version
|
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback) {
|
||||
if (!ua || timeout_tb < 0 || !callback) return NULL;
|
||||
if (!ua->timeout_heap) return NULL;
|
||||
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: timeout=%d.%d ms, arg=%p, callback=%p", timeout_tb/10, timeout_tb%10, arg, callback);
|
||||
|
||||
// Instance version
|
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* arg, timeout_callback_t callback, const char* name) {
|
||||
if (!ua || timeout_tb < 0 || !callback) return NULL;
|
||||
if (!ua->timeout_heap) return NULL;
|
||||
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: timeout=%d.%d ms, arg=%p, callback=%p", timeout_tb/10, timeout_tb%10, arg, callback);
|
||||
|
||||
struct timeout_node* node = memory_pool_alloc(ua->timeout_pool);
|
||||
if (!node) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to allocate node");
|
||||
return NULL;
|
||||
}
|
||||
ua->timer_alloc_count++;
|
||||
|
||||
node->arg = arg;
|
||||
node->callback = callback;
|
||||
node->ua = ua;
|
||||
|
||||
// Calculate expiration time in milliseconds
|
||||
struct timeval now;
|
||||
get_current_time(&now);
|
||||
timeval_add_tb(&now, timeout_tb);
|
||||
node->expiration_ms = timeval_to_ms(&now);
|
||||
|
||||
}
|
||||
ua->timer_alloc_count++;
|
||||
|
||||
if (name) {
|
||||
strncpy(node->name, name, sizeof(node->name) - 1);
|
||||
node->name[sizeof(node->name) - 1] = '\0';
|
||||
} else {
|
||||
node->name[0] = '\0';
|
||||
}
|
||||
node->arg = arg;
|
||||
node->callback = callback;
|
||||
node->ua = ua;
|
||||
node->heap_index = SIZE_MAX;
|
||||
|
||||
// Calculate expiration time in milliseconds
|
||||
struct timeval now;
|
||||
get_current_time(&now);
|
||||
timeval_add_tb(&now, timeout_tb);
|
||||
node->expiration_ms = timeval_to_ms(&now);
|
||||
|
||||
// Add to heap
|
||||
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node) != 0) {
|
||||
if (timeout_heap_push(ua->timeout_heap, node->expiration_ms, node, &node->heap_index) != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_set_timeout: failed to push to heap");
|
||||
memory_pool_free(ua->timeout_pool, node);
|
||||
ua->timer_free_count++; // Balance the alloc counter
|
||||
@@ -538,11 +575,13 @@ void* uasync_call_soon(struct UASYNC* ua, void* user_arg, timeout_callback_t cal
|
||||
}
|
||||
ua->timer_alloc_count++;
|
||||
|
||||
node->name[0] = '\0';
|
||||
node->arg = user_arg;
|
||||
node->callback = callback;
|
||||
node->ua = ua;
|
||||
node->expiration_ms = 0;
|
||||
node->next = NULL;
|
||||
node->heap_index = SIZE_MAX;
|
||||
|
||||
// FIFO: добавляем в конец очереди
|
||||
if (ua->immediate_queue_tail) {
|
||||
@@ -571,28 +610,29 @@ err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id) {
|
||||
|
||||
|
||||
// Instance version
|
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
|
||||
if (!ua || !t_id || !ua->timeout_heap) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
|
||||
ua, t_id, ua ? ua->timeout_heap : NULL);
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
struct timeout_node* node = (struct timeout_node*)t_id;
|
||||
|
||||
// Try to cancel from heap first
|
||||
if (timeout_heap_cancel(ua->timeout_heap, node->expiration_ms, node) == 0) {
|
||||
// Successfully marked as deleted - u_free will happen lazily in heap
|
||||
node->callback = NULL;
|
||||
// DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: successfully cancelled timer %p from heap", node);
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: not found in heap: ua=%p, t_id=%p, node=%p, expires=%llu ms",
|
||||
ua, t_id, node, (unsigned long long)node->expiration_ms);
|
||||
|
||||
// If not found in heap, it may have already expired or been invalid
|
||||
return ERR_FAIL;
|
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id) {
|
||||
if (!ua || !t_id || !ua->timeout_heap) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: invalid parameters ua=%p, t_id=%p, heap=%p",
|
||||
ua, t_id, ua ? ua->timeout_heap : NULL);
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
struct timeout_node* node = (struct timeout_node*)t_id;
|
||||
|
||||
if (node->heap_index == SIZE_MAX || node->heap_index >= ua->timeout_heap->size ||
|
||||
ua->timeout_heap->heap[node->heap_index].data != node) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_cancel_timeout: not found in heap: ua=%p, t_id=%p, node=%p, expires=%llu ms",
|
||||
ua, t_id, node, (unsigned long long)node->expiration_ms);
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
if (timeout_heap_cancel_at(ua->timeout_heap, node->heap_index, node) == 0) {
|
||||
node->heap_index = SIZE_MAX;
|
||||
node->callback = NULL;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
|
||||
@@ -618,10 +658,10 @@ void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, s
|
||||
if (read_cbk) ev.events |= EPOLLIN;
|
||||
if (write_cbk) ev.events |= EPOLLOUT;
|
||||
// Use level-triggered mode (default) for compatibility with UDP sockets
|
||||
ev.data.ptr = &ua->sockets->sockets[index];
|
||||
|
||||
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
||||
// Failed to add to epoll - remove from socket array and return error
|
||||
ev.data.fd = fd;
|
||||
|
||||
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
||||
// Failed to add to epoll - remove from socket array and return error
|
||||
socket_array_remove(ua->sockets, fd);
|
||||
ua->socket_alloc_count--;
|
||||
return NULL;
|
||||
@@ -675,14 +715,13 @@ void* uasync_add_socket_t(struct UASYNC* ua, socket_t sock, socket_t_callback_t
|
||||
ev.events = 0;
|
||||
if (read_cbk) ev.events |= EPOLLIN;
|
||||
if (write_cbk) ev.events |= EPOLLOUT;
|
||||
ev.data.ptr = &ua->sockets->sockets[index];
|
||||
|
||||
// On Windows, need to cast socket_t to int for epoll_ctl
|
||||
#ifdef _WIN32
|
||||
int fd = (int)(intptr_t)sock;
|
||||
#else
|
||||
int fd = sock;
|
||||
#endif
|
||||
// On Windows, need to cast socket_t to int for epoll_ctl
|
||||
#ifdef _WIN32
|
||||
int fd = (int)(intptr_t)sock;
|
||||
#else
|
||||
int fd = sock;
|
||||
#endif
|
||||
ev.data.fd = fd;
|
||||
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
||||
socket_array_remove(ua->sockets, fd);
|
||||
ua->socket_alloc_count--;
|
||||
@@ -723,10 +762,84 @@ err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock) {
|
||||
ua->poll_fds_dirty = 1;
|
||||
return ERR_OK;
|
||||
}
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
// Helper function to rebuild cached pollfd array
|
||||
return ERR_FAIL;
|
||||
}
|
||||
|
||||
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable) {
|
||||
if (!ua || !s_id) return ERR_FAIL;
|
||||
|
||||
struct socket_node* node = (struct socket_node*)s_id;
|
||||
if (!node->active || node->fd < 0) return ERR_FAIL;
|
||||
|
||||
int val = enable ? 1 : 0;
|
||||
if (node->enable_read == val) return ERR_OK;
|
||||
node->enable_read = val;
|
||||
|
||||
#if HAS_EPOLL
|
||||
if (ua->use_epoll && ua->epoll_fd >= 0) {
|
||||
struct epoll_event ev;
|
||||
ev.events = 0;
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk_sock && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->sock;
|
||||
} else {
|
||||
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->fd;
|
||||
}
|
||||
if (node->except_cbk) ev.events |= EPOLLPRI;
|
||||
#ifdef _WIN32
|
||||
int efd = (int)(intptr_t)ev.data.fd;
|
||||
#else
|
||||
int efd = ev.data.fd;
|
||||
#endif
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
|
||||
}
|
||||
#endif
|
||||
|
||||
ua->poll_fds_dirty = 1;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable) {
|
||||
if (!ua || !s_id) return ERR_FAIL;
|
||||
|
||||
struct socket_node* node = (struct socket_node*)s_id;
|
||||
if (!node->active || node->fd < 0) return ERR_FAIL;
|
||||
|
||||
int val = enable ? 1 : 0;
|
||||
if (node->enable_write == val) return ERR_OK;
|
||||
node->enable_write = val;
|
||||
|
||||
#if HAS_EPOLL
|
||||
if (ua->use_epoll && ua->epoll_fd >= 0) {
|
||||
struct epoll_event ev;
|
||||
ev.events = 0;
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk_sock && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->sock;
|
||||
} else {
|
||||
if (node->read_cbk && node->enable_read) ev.events |= EPOLLIN;
|
||||
if (node->write_cbk && node->enable_write) ev.events |= EPOLLOUT;
|
||||
ev.data.fd = node->fd;
|
||||
}
|
||||
if (node->except_cbk) ev.events |= EPOLLPRI;
|
||||
#ifdef _WIN32
|
||||
int efd = (int)(intptr_t)ev.data.fd;
|
||||
#else
|
||||
int efd = ev.data.fd;
|
||||
#endif
|
||||
epoll_ctl(ua->epoll_fd, EPOLL_CTL_MOD, efd, &ev);
|
||||
}
|
||||
#endif
|
||||
|
||||
ua->poll_fds_dirty = 1;
|
||||
return ERR_OK;
|
||||
}
|
||||
|
||||
// Helper function to rebuild cached pollfd array
|
||||
static void rebuild_poll_fds(struct UASYNC* ua) {
|
||||
if (!ua || !ua->sockets) return;
|
||||
|
||||
@@ -776,14 +889,14 @@ static void rebuild_poll_fds(struct UASYNC* ua) {
|
||||
ua->poll_fds[idx].events = 0;
|
||||
ua->poll_fds[idx].revents = 0;
|
||||
|
||||
if (cur->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (cur->read_cbk_sock) ua->poll_fds[idx].events |= POLLIN;
|
||||
if (cur->write_cbk_sock) ua->poll_fds[idx].events |= POLLOUT;
|
||||
} else {
|
||||
if (cur->read_cbk) ua->poll_fds[idx].events |= POLLIN;
|
||||
if (cur->write_cbk) ua->poll_fds[idx].events |= POLLOUT;
|
||||
}
|
||||
if (cur->write_cbk) ua->poll_fds[idx].events |= POLLOUT;
|
||||
if (cur->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (cur->read_cbk_sock && cur->enable_read) ua->poll_fds[idx].events |= POLLIN;
|
||||
if (cur->write_cbk_sock && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
|
||||
} else {
|
||||
if (cur->read_cbk && cur->enable_read) ua->poll_fds[idx].events |= POLLIN;
|
||||
if (cur->write_cbk && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
|
||||
}
|
||||
if (cur->write_cbk && cur->enable_write) ua->poll_fds[idx].events |= POLLOUT;
|
||||
if (cur->except_cbk) ua->poll_fds[idx].events |= POLLPRI;
|
||||
|
||||
idx++;
|
||||
@@ -797,49 +910,61 @@ static void rebuild_poll_fds(struct UASYNC* ua) {
|
||||
#if HAS_EPOLL
|
||||
static void process_epoll_events(struct UASYNC* ua, struct epoll_event* events, int n_events) {
|
||||
for (int i = 0; i < n_events; i++) {
|
||||
// Check if this is the wakeup fd (data.ptr is NULL)
|
||||
if (events[i].data.ptr == NULL) {
|
||||
if (events[i].events & EPOLLIN) {
|
||||
drain_wakeup_pipe(ua);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Socket event
|
||||
struct socket_node* node = (struct socket_node*)events[i].data.ptr;
|
||||
if (!node || !node->active) continue;
|
||||
|
||||
/* Check for error conditions first */
|
||||
if (events[i].events & (EPOLLERR | EPOLLHUP)) {
|
||||
if (node->except_cbk) {
|
||||
node->except_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read readiness - use appropriate callback based on socket type */
|
||||
if (events[i].events & EPOLLIN) {
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock) {
|
||||
node->read_cbk_sock(node->sock, node->user_data);
|
||||
}
|
||||
} else {
|
||||
if (node->read_cbk) {
|
||||
node->read_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write readiness - use appropriate callback based on socket type */
|
||||
if (events[i].events & EPOLLOUT) {
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->write_cbk_sock) {
|
||||
node->write_cbk_sock(node->sock, node->user_data);
|
||||
}
|
||||
} else {
|
||||
if (node->write_cbk) {
|
||||
node->write_cbk(node->fd, node->user_data);
|
||||
}
|
||||
}
|
||||
// Check if this is the wakeup fd (data.fd is -1)
|
||||
if (events[i].data.fd < 0) {
|
||||
if (events[i].events & EPOLLIN) {
|
||||
drain_wakeup_pipe(ua);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Socket event — save node fields locally: callbacks may realloc
|
||||
// the socket array, invalidating `node`.
|
||||
struct socket_node* node = socket_array_get(ua->sockets, events[i].data.fd);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll→fd=%d ev=0x%x%s", events[i].data.fd, events[i].events,
|
||||
!node || !node->active ? " (inactive)" : "");
|
||||
if (!node || !node->active) continue;
|
||||
int local_fd = node->fd;
|
||||
socket_t local_sock = node->sock;
|
||||
int local_type = node->type;
|
||||
void* local_ud = node->user_data;
|
||||
socket_callback_t local_except = node->except_cbk;
|
||||
socket_callback_t local_read = node->read_cbk;
|
||||
socket_callback_t local_write = node->write_cbk;
|
||||
socket_t_callback_t local_read_sock = node->read_cbk_sock;
|
||||
socket_t_callback_t local_write_sock = node->write_cbk_sock;
|
||||
|
||||
/* Check for error conditions first */
|
||||
if (events[i].events & (EPOLLERR | EPOLLHUP)) {
|
||||
if (local_except) {
|
||||
local_except(local_fd, local_ud);
|
||||
}
|
||||
}
|
||||
|
||||
/* Read readiness - use appropriate callback based on socket type */
|
||||
if (events[i].events & EPOLLIN) {
|
||||
if (local_type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (local_read_sock) {
|
||||
local_read_sock(local_sock, local_ud);
|
||||
}
|
||||
} else {
|
||||
if (local_read) {
|
||||
local_read(local_fd, local_ud);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Write readiness - use appropriate callback based on socket type */
|
||||
if (events[i].events & EPOLLOUT) {
|
||||
if (local_type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (local_write_sock) {
|
||||
local_write_sock(local_sock, local_ud);
|
||||
}
|
||||
} else {
|
||||
if (local_write) {
|
||||
local_write(local_fd, local_ud);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -850,7 +975,9 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
if (!ua) return;
|
||||
if (!ua->sockets || !ua->timeout_heap) return;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "poll");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll(%d sockets, %zu timers, timeout=%d.%dms)",
|
||||
ua->sockets->count, ua->timeout_heap->size, timeout_tb >= 0 ? timeout_tb / 10000 : -1,
|
||||
timeout_tb >= 0 ? (timeout_tb % 10000) / 10 : 0);
|
||||
|
||||
// Handle negative or zero timeout
|
||||
if (timeout_tb < 0) timeout_tb = -1; // Infinite wait
|
||||
@@ -867,20 +994,19 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
req_timeout.tv_usec = (timeout_tb % 10000) * 100;
|
||||
}
|
||||
|
||||
// Use minimum of requested and next timer if both finite
|
||||
struct timeval poll_timeout;
|
||||
if (timeout_tb < 0) {
|
||||
// Infinite requested - use next timer if any
|
||||
poll_timeout = next_timeout;
|
||||
} else {
|
||||
// Finite requested - min of requested and next
|
||||
if (next_timeout.tv_sec < req_timeout.tv_sec ||
|
||||
(next_timeout.tv_sec == req_timeout.tv_sec && next_timeout.tv_usec < req_timeout.tv_usec)) {
|
||||
poll_timeout = next_timeout;
|
||||
} else {
|
||||
poll_timeout = req_timeout;
|
||||
}
|
||||
}
|
||||
struct timeval poll_timeout;
|
||||
if (timeout_tb < 0) {
|
||||
poll_timeout = next_timeout;
|
||||
} else {
|
||||
if (next_timeout.tv_sec < req_timeout.tv_sec ||
|
||||
(next_timeout.tv_sec == req_timeout.tv_sec && next_timeout.tv_usec < req_timeout.tv_usec)) {
|
||||
poll_timeout = next_timeout;
|
||||
} else {
|
||||
poll_timeout = req_timeout;
|
||||
}
|
||||
}
|
||||
if (poll_timeout.tv_sec == 0 && poll_timeout.tv_usec == 0 && timeout_tb > 0) poll_timeout = req_timeout;
|
||||
|
||||
|
||||
int timeout_ms;
|
||||
if (timeout_tb < 0 && (next_timeout.tv_sec > 0 || next_timeout.tv_usec > 0)) {
|
||||
@@ -971,12 +1097,12 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
s = (SOCKET)node->fd;
|
||||
}
|
||||
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock) FD_SET(s, &read_fds);
|
||||
if (node->write_cbk_sock) FD_SET(s, &write_fds);
|
||||
} else {
|
||||
if (node->read_cbk) FD_SET(s, &read_fds);
|
||||
if (node->write_cbk) FD_SET(s, &write_fds);
|
||||
if (node->type == SOCKET_NODE_TYPE_SOCK) {
|
||||
if (node->read_cbk_sock && node->enable_read) FD_SET(s, &read_fds);
|
||||
if (node->write_cbk_sock && node->enable_write) FD_SET(s, &write_fds);
|
||||
} else {
|
||||
if (node->read_cbk && node->enable_read) FD_SET(s, &read_fds);
|
||||
if (node->write_cbk && node->enable_write) FD_SET(s, &write_fds);
|
||||
}
|
||||
if (node->except_cbk) FD_SET(s, &except_fds);
|
||||
|
||||
@@ -1014,7 +1140,8 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
int has_write = FD_ISSET(s, &write_fds);
|
||||
int has_except = FD_ISSET(s, &except_fds);
|
||||
|
||||
if (!has_read && !has_write && !has_except) continue;
|
||||
if (!has_read && !has_write && !has_except) continue;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "select→fd=%d r=%d w=%d e=%d", (int)s, has_read, has_write, has_except);
|
||||
|
||||
if (has_except) {
|
||||
if (node->except_cbk) {
|
||||
@@ -1072,7 +1199,8 @@ void uasync_poll(struct UASYNC* ua, int timeout_tb) {
|
||||
/* Process socket events first to give sockets higher priority */
|
||||
if (ret > 0) {
|
||||
for (int i = 0; i < ua->poll_fds_count; i++) {
|
||||
if (ua->poll_fds[i].revents == 0) continue;
|
||||
if (ua->poll_fds[i].revents == 0) continue;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_UASYNC, "poll→fd=%d rev=0x%x", ua->poll_fds[i].fd, ua->poll_fds[i].revents);
|
||||
|
||||
/* Handle wakeup fd separately */
|
||||
if (wakeup_fd_present && i == 0) {
|
||||
@@ -1185,9 +1313,9 @@ struct UASYNC* uasync_create(void) {
|
||||
ua->immediate_queue_head = NULL;
|
||||
ua->immediate_queue_tail = NULL;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating SA...");
|
||||
ua->sockets = socket_array_create(16);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating SA1...");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating SA...");
|
||||
ua->sockets = socket_array_create(16);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating SA1...");
|
||||
if (!ua->sockets) {
|
||||
if (ua->wakeup_initialized) {
|
||||
#ifdef _WIN32
|
||||
@@ -1219,7 +1347,7 @@ struct UASYNC* uasync_create(void) {
|
||||
}
|
||||
|
||||
// Initialize timeout pool
|
||||
ua->timeout_pool = memory_pool_init(sizeof(struct timeout_node));
|
||||
ua->timeout_pool = memory_pool_init(sizeof(struct timeout_node), "timeout_pool");
|
||||
if (!ua->timeout_pool) {
|
||||
timeout_heap_destroy(ua->timeout_heap);
|
||||
socket_array_destroy(ua->sockets);
|
||||
@@ -1239,7 +1367,7 @@ struct UASYNC* uasync_create(void) {
|
||||
// Set callback to u_free timeout nodes and update counters
|
||||
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH1...");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1...");
|
||||
// Initialize epoll on Linux
|
||||
ua->epoll_fd = -1;
|
||||
ua->use_epoll = 0;
|
||||
@@ -1252,7 +1380,7 @@ struct UASYNC* uasync_create(void) {
|
||||
if (ua->wakeup_initialized) {
|
||||
struct epoll_event ev;
|
||||
ev.events = EPOLLIN;
|
||||
ev.data.ptr = NULL; // NULL ptr indicates wakeup fd
|
||||
ev.data.fd = -1; // -1 fd indicates wakeup
|
||||
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, ua->wakeup_pipe[0], &ev) < 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to add wakeup pipe to epoll: %s", strerror(errno));
|
||||
}
|
||||
@@ -1262,7 +1390,7 @@ struct UASYNC* uasync_create(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH2...");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH2...");
|
||||
|
||||
#ifdef _WIN32
|
||||
// Windows: self-connected UDP socket for wakeup
|
||||
@@ -1308,8 +1436,19 @@ struct UASYNC* uasync_create(void) {
|
||||
u_free(ua);
|
||||
return NULL;
|
||||
}
|
||||
ua->wakeup_initialized = 1;
|
||||
|
||||
ua->wakeup_initialized = 1;
|
||||
|
||||
#if HAS_EPOLL
|
||||
if (ua->use_epoll && ua->epoll_fd >= 0) {
|
||||
struct epoll_event ev;
|
||||
ev.events = EPOLLIN;
|
||||
ev.data.fd = -1;
|
||||
if (epoll_ctl(ua->epoll_fd, EPOLL_CTL_ADD, ua->wakeup_pipe[0], &ev) < 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_UASYNC, "Failed to add wakeup pipe to epoll: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
fcntl(ua->wakeup_pipe[0], F_SETFL, fcntl(ua->wakeup_pipe[0], F_GETFL, 0) | O_NONBLOCK);
|
||||
fcntl(ua->wakeup_pipe[1], F_SETFL, fcntl(ua->wakeup_pipe[1], F_GETFL, 0) | O_NONBLOCK);
|
||||
|
||||
@@ -1319,84 +1458,95 @@ struct UASYNC* uasync_create(void) {
|
||||
pthread_mutex_init(&ua->posted_lock, NULL);
|
||||
|
||||
#endif
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Creating TH3...");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH3...");
|
||||
|
||||
return ua;
|
||||
}
|
||||
|
||||
// Print all resources for debugging
|
||||
void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
|
||||
if (!ua) {
|
||||
printf("%s: NULL uasync instance\n", prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
printf("\n🔍 %s: UASYNC Resource Report for %p\n", prefix, ua);
|
||||
printf(" Timer Statistics: allocated=%zu, u_freed=%zu, active=%zd\n",
|
||||
ua->timer_alloc_count, ua->timer_free_count,
|
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
|
||||
printf(" Socket Statistics: allocated=%zu, u_freed=%zu, active=%zd\n",
|
||||
ua->socket_alloc_count, ua->socket_free_count,
|
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
|
||||
|
||||
// Показать активные таймеры
|
||||
if (ua->timeout_heap) {
|
||||
size_t active_timers = 0;
|
||||
// Безопасное чтение без извлечения - просто итерируем по массиву
|
||||
for (size_t i = 0; i < ua->timeout_heap->size; i++) {
|
||||
if (!ua->timeout_heap->heap[i].deleted) {
|
||||
active_timers++;
|
||||
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
|
||||
printf(" Timer: node=%p, expires=%llu ms\n",
|
||||
node, (unsigned long long)ua->timeout_heap->heap[i].expiration);
|
||||
}
|
||||
}
|
||||
printf(" Active timers in heap: %zu\n", active_timers);
|
||||
}
|
||||
|
||||
// Показать активные сокеты
|
||||
if (ua->sockets) {
|
||||
int active_sockets = 0;
|
||||
printf(" Socket array capacity: %d, active: %d\n",
|
||||
ua->sockets->capacity, ua->sockets->count);
|
||||
for (int i = 0; i < ua->sockets->capacity; i++) {
|
||||
if (ua->sockets->sockets[i].active) {
|
||||
active_sockets++;
|
||||
printf(" Socket: fd=%d, active=%d\n",
|
||||
ua->sockets->sockets[i].fd,
|
||||
ua->sockets->sockets[i].active);
|
||||
}
|
||||
}
|
||||
printf(" Total active sockets: %d\n", active_sockets);
|
||||
}
|
||||
|
||||
printf("🔚 %s: End of resource report\n\n", prefix);
|
||||
// Print all resources for debugging
|
||||
void uasync_print_resources(struct UASYNC* ua, const char* prefix) {
|
||||
if (!ua) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "%s: NULL uasync instance", prefix);
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "%s: UASYNC Resource Report for %p", prefix, ua);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer Statistics: allocated=%zu, u_freed=%zu, active=%zd",
|
||||
ua->timer_alloc_count, ua->timer_free_count,
|
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Socket Statistics: allocated=%zu, u_freed=%zu, active=%zd",
|
||||
ua->socket_alloc_count, ua->socket_free_count,
|
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
|
||||
|
||||
// Показать активные таймеры
|
||||
if (ua->timeout_heap) {
|
||||
struct timeval now_tv;
|
||||
get_current_time(&now_tv);
|
||||
uint64_t now_ms = timeval_to_ms(&now_tv);
|
||||
size_t active_timers = 0;
|
||||
size_t deleted_timers = 0;
|
||||
for (size_t i = 0; i < ua->timeout_heap->size; i++) {
|
||||
uint64_t exp = ua->timeout_heap->heap[i].expiration;
|
||||
int64_t remain = (int64_t)(exp - now_ms);
|
||||
if (ua->timeout_heap->heap[i].deleted) {
|
||||
deleted_timers++;
|
||||
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer DELETED: node=%p name='%s' exp=%llu remain=%lldms",
|
||||
node, node ? node->name : "null", (unsigned long long)exp, (long long)remain);
|
||||
} else {
|
||||
active_timers++;
|
||||
struct timeout_node* node = (struct timeout_node*)ua->timeout_heap->heap[i].data;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Timer ACTIVE: node=%p name='%s' exp=%llu remain=%lldms",
|
||||
node, node ? node->name : "null", (unsigned long long)exp, (long long)remain);
|
||||
}
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Active timers in heap: %zu, deleted: %zu, total: %zu",
|
||||
active_timers, deleted_timers, ua->timeout_heap->size);
|
||||
}
|
||||
|
||||
// Показать активные сокеты
|
||||
if (ua->sockets) {
|
||||
int active_sockets = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Socket array capacity: %d, active: %d",
|
||||
ua->sockets->capacity, ua->sockets->count);
|
||||
for (int i = 0; i < ua->sockets->capacity; i++) {
|
||||
if (ua->sockets->sockets[i].active) {
|
||||
active_sockets++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Socket: fd=%d, active=%d",
|
||||
ua->sockets->sockets[i].fd,
|
||||
ua->sockets->sockets[i].active);
|
||||
}
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, " Total active sockets: %d", active_sockets);
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_UASYNC, "%s: End of resource report", prefix);
|
||||
}
|
||||
|
||||
// Modified function in u_async.c: uasync_destroy
|
||||
// Changes: Close wakeup sockets properly on Windows.
|
||||
|
||||
void uasync_destroy(struct UASYNC* ua, int close_fds) {
|
||||
if (!ua) return;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua);
|
||||
|
||||
// Диагностика ресурсов перед очисткой
|
||||
uasync_print_resources(ua, "BEFORE_DESTROY");
|
||||
|
||||
// Check for potential memory leaks
|
||||
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu",
|
||||
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, u_freed=%zu, diff=%zd",
|
||||
ua->timer_alloc_count, ua->timer_free_count,
|
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, u_freed=%zu, diff=%zd",
|
||||
ua->socket_alloc_count, ua->socket_free_count,
|
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
|
||||
// Continue cleanup, will abort after if leaks remain
|
||||
}
|
||||
|
||||
void uasync_destroy(struct UASYNC* ua, int close_fds) {
|
||||
if (!ua) return;
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_TIMERS, "uasync_destroy: starting cleanup for ua=%p", ua);
|
||||
|
||||
// Диагностика ресурсов перед очисткой
|
||||
uasync_print_resources(ua, "BEFORE_DESTROY");
|
||||
|
||||
// Check for potential memory leaks
|
||||
if (ua->timer_alloc_count != ua->timer_free_count || ua->socket_alloc_count != ua->socket_free_count) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Memory leaks detected before cleanup: timers %zu/%zu, sockets %zu/%zu",
|
||||
ua->timer_alloc_count, ua->timer_free_count, ua->socket_alloc_count, ua->socket_free_count);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Timer leak: allocated=%zu, u_freed=%zu, diff=%zd",
|
||||
ua->timer_alloc_count, ua->timer_free_count,
|
||||
(ssize_t)(ua->timer_alloc_count - ua->timer_free_count));
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Socket leak: allocated=%zu, u_freed=%zu, diff=%zd",
|
||||
ua->socket_alloc_count, ua->socket_free_count,
|
||||
(ssize_t)(ua->socket_alloc_count - ua->socket_free_count));
|
||||
// Continue cleanup, will abort after if leaks remain
|
||||
}
|
||||
|
||||
// Free all remaining timeouts
|
||||
|
||||
// Очистить immediate_queue
|
||||
@@ -1412,7 +1562,6 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) {
|
||||
|
||||
// Очистить heap
|
||||
if (ua->timeout_heap) {
|
||||
size_t u_freed_count = 0;
|
||||
while (1) {
|
||||
TimeoutEntry entry;
|
||||
if (timeout_heap_pop(ua->timeout_heap, &entry) != 0) break;
|
||||
@@ -1512,31 +1661,6 @@ void uasync_destroy(struct UASYNC* ua, int close_fds) {
|
||||
socket_platform_cleanup();
|
||||
}
|
||||
|
||||
void uasync_init_instance(struct UASYNC* ua) {
|
||||
if (!ua) return;
|
||||
|
||||
// Initialize socket array if not present
|
||||
if (!ua->sockets) {
|
||||
ua->sockets = socket_array_create(16);
|
||||
}
|
||||
|
||||
if (!ua->timeout_pool) {
|
||||
ua->timeout_pool = memory_pool_init(sizeof(struct timeout_node));
|
||||
}
|
||||
|
||||
if (!ua->immediate_queue_head) {
|
||||
ua->immediate_queue_head = NULL;
|
||||
ua->immediate_queue_tail = NULL;
|
||||
}
|
||||
|
||||
if (!ua->timeout_heap) {
|
||||
ua->timeout_heap = timeout_heap_create(16);
|
||||
if (ua->timeout_heap) {
|
||||
timeout_heap_set_free_callback(ua->timeout_heap, ua, timeout_node_free_callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debug statistics
|
||||
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_u_free, size_t* socket_alloc, size_t* socket_u_free) {
|
||||
if (!ua) return;
|
||||
@@ -1625,8 +1749,12 @@ int uasync_lookup_socket(struct UASYNC* ua, int fd, void** socket_id) {
|
||||
return (*socket_id != NULL) ? 0 : -1;
|
||||
}
|
||||
|
||||
void uasync_mainloop(struct UASYNC* ua) {
|
||||
while (1) {
|
||||
uasync_poll(ua, -1); // Infinite wait
|
||||
}
|
||||
void uasync_stop(struct UASYNC* ua) {
|
||||
if (ua) ua->stop = 1;
|
||||
}
|
||||
|
||||
void uasync_mainloop(struct UASYNC* ua) {
|
||||
while (!ua->stop) {
|
||||
uasync_poll(ua, -1);
|
||||
}
|
||||
}
|
||||
+22
-9
@@ -5,9 +5,10 @@
|
||||
#ifndef UASYNC_H
|
||||
#define UASYNC_H
|
||||
|
||||
#include "platform_compat.h"
|
||||
#include <stddef.h>
|
||||
#include "timeout_heap.h"
|
||||
#include "platform_compat.h"
|
||||
#include <stddef.h>
|
||||
#include <signal.h>
|
||||
#include "timeout_heap.h"
|
||||
#include "socket_compat.h"
|
||||
|
||||
typedef void (*timeout_callback_t)(void* user_arg);// передаёт user_arg из uasync_set_timeout
|
||||
@@ -66,8 +67,9 @@ struct UASYNC {
|
||||
#ifdef _WIN32
|
||||
CRITICAL_SECTION posted_lock;
|
||||
#else
|
||||
pthread_mutex_t posted_lock;
|
||||
#endif
|
||||
pthread_mutex_t posted_lock;
|
||||
#endif
|
||||
volatile sig_atomic_t stop;
|
||||
};
|
||||
|
||||
// Type definitions
|
||||
@@ -77,13 +79,19 @@ typedef struct UASYNC UASYNC_t;
|
||||
// Instance API - основной API для работы с uasync
|
||||
struct UASYNC* uasync_create(void);
|
||||
void uasync_destroy(struct UASYNC* ua, int close_fds);
|
||||
void uasync_init_instance(struct UASYNC* ua);
|
||||
|
||||
// текущее время (timebase 0.1ms)
|
||||
uint64_t get_time_tb(void);
|
||||
// текущее время в микросекундах (для burst-измерений)
|
||||
uint64_t get_time_us(void);
|
||||
|
||||
// Timeouts, timebase = 0.1 mS
|
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback);
|
||||
// Как работать с таймаутами если таймаут может быть использован несколько раз:
|
||||
// 1. заводим дескриптор таймаута и обнуляем
|
||||
// 2. перед активацией таймаута проверяем дескриптор (t_id) на null
|
||||
// 3. при cancel или срабатывании дескриптор обнуляем
|
||||
// это обеспечит отсутствие утечек и задвоений таймаутов
|
||||
void* uasync_set_timeout(struct UASYNC* ua, int timeout_tb, void* user_arg, timeout_callback_t callback, const char* name);
|
||||
err_t uasync_cancel_timeout(struct UASYNC* ua, void* t_id);
|
||||
|
||||
// Immediate execution in next mainloop (FIFO order)
|
||||
@@ -94,14 +102,19 @@ err_t uasync_call_soon_cancel(struct UASYNC* ua, void* t_id);
|
||||
void* uasync_add_socket(struct UASYNC* ua, int fd, socket_callback_t read_cbk, socket_callback_t write_cbk, socket_callback_t except_cbk, void* user_arg);
|
||||
// Sockets - for socket_t (cross-platform sockets)
|
||||
void* uasync_add_socket_t(struct UASYNC* ua, socket_t sock, socket_t_callback_t read_cbk, socket_t_callback_t write_cbk, socket_t_callback_t except_cbk, void* user_arg);
|
||||
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
|
||||
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
|
||||
err_t uasync_remove_socket(struct UASYNC* ua, void* s_id);
|
||||
err_t uasync_remove_socket_t(struct UASYNC* ua, socket_t sock);
|
||||
|
||||
// Динамическое управление мониторингом чтения/записи для сокета
|
||||
err_t uasync_set_socket_read(struct UASYNC* ua, void* s_id, int enable);
|
||||
err_t uasync_set_socket_write(struct UASYNC* ua, void* s_id, int enable);
|
||||
|
||||
// Single iteration of event loop with timeout (timebase units)
|
||||
void uasync_poll(struct UASYNC* ua, int timeout_tb);
|
||||
|
||||
// Mainloop (бесконечный цикл, __noreturn)
|
||||
void uasync_mainloop(struct UASYNC* ua);
|
||||
void uasync_stop(struct UASYNC* ua);
|
||||
|
||||
// Debug statistics
|
||||
void uasync_get_stats(struct UASYNC* ua, size_t* timer_alloc, size_t* timer_free, size_t* socket_alloc, size_t* socket_free);
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
Диаграмма вызовов функций при определении типа NAT (NAT detection)
|
||||
=====================================================================
|
||||
|
||||
=== ИНИЦИАЛИЗАЦИЯ BGP ===
|
||||
|
||||
route_bgp_init(instance)
|
||||
└── etcp_set_new_conn_cbk(instance, route_bgp_etcp_conn_cbk, NULL)
|
||||
└── При создании нового ETCP_CONN:
|
||||
route_bgp_etcp_conn_cbk(conn, arg)
|
||||
├── etcp_conn_set_up_cbk(conn, route_bgp_on_conn_up, bgp)
|
||||
└── etcp_conn_set_down_cbk(conn, route_bgp_on_conn_down, bgp)
|
||||
|
||||
|
||||
=== УСТАНОВКА СОЕДИНЕНИЯ (сервер получает INIT_REQUEST) ===
|
||||
|
||||
etcp_connections_read_callback_socket(sock, arg)
|
||||
└── Получает ETCP_INIT_REQUEST
|
||||
├── etcp_connection_create(instance, name) [если новый peer]
|
||||
│ └── Вызывает instance->etcp_new_conn_cbk [route_bgp_etcp_conn_cbk]
|
||||
├── etcp_link_new(etcp, e_sock, remote_addr, is_server=1)
|
||||
├── Отправляет ETCP_INIT_RESPONSE (с NAT info)
|
||||
├── link->initialized = 1
|
||||
└── etcp_conn_ready(link->etcp)
|
||||
└── conn->ready_cbk
|
||||
└── etcp_on_up(etcp)
|
||||
└── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp)
|
||||
|
||||
|
||||
=== УСТАНОВКА СОЕДИНЕНИЯ (клиент получает INIT_RESPONSE) ===
|
||||
|
||||
etcp_connections_read_callback_socket(sock, arg)
|
||||
└── Получает ETCP_INIT_RESPONSE
|
||||
├── Сохраняет NAT address: link->nat_ip, link->nat_port
|
||||
├── link->initialized = 1
|
||||
└── etcp_conn_ready(link->etcp)
|
||||
└── etcp_on_up(etcp)
|
||||
└── etcp->up_cbk = route_bgp_on_conn_up(etcp, bgp)
|
||||
|
||||
|
||||
=== ЗАПУСК ПРОВЕРКИ NAT (при поднятии соединения) ===
|
||||
|
||||
route_bgp_on_conn_up(etcp, arg)
|
||||
└── route_bgp_new_conn(conn)
|
||||
├── route_bgp_add_to_senders(bgp, conn)
|
||||
└── Сканирует ВСЕ линки ВСЕХ соединений:
|
||||
if (link->initialized && link->conn && link->nat_check_status < NAT_CHECK_IN_PROGRESS)
|
||||
└── route_bgp_start_link_nat_check(bgp, link)
|
||||
├── if (nat_check_status == IN_PROGRESS) return
|
||||
├── route_bgp_find_third_node(bgp, exclude=link->etcp)
|
||||
│ └── Ищет другой ETCP_CONN в senders_list
|
||||
├── Определяет target_ip/target_port:
|
||||
│ ├── Если link->nat_ip != 0: использует nat_ip/nat_port
|
||||
│ └── Иначе: использует link->remote_addr
|
||||
├── Проверяет is_local_subnet(target_ip) [если запрещено - skip]
|
||||
├── route_bgp_extract_nat_addr(conn, &nat_ip, &nat_port)
|
||||
│ └── Ищет первый линк с nat_ip != 0
|
||||
├── Создает nat_check_arg { link, nat_ip, nat_port }
|
||||
└── route_ping_send_req_addr(bgp, third_conn, target_ip, target_port,
|
||||
count=3, interval=500ms, timeout=1000ms,
|
||||
wait_timeout=5000ms,
|
||||
cb=nat_link_check_cb, arg=nat_check_arg,
|
||||
pubkey=peer_pubkey)
|
||||
|
||||
|
||||
=== ОТПРАВКА ЗАПРОСА НА ПИНГ (через third node) ===
|
||||
|
||||
route_ping_send_req_addr(bgp, to_conn, target_ip, target_port, ...)
|
||||
├── Создает BGP_PING_REQUEST { cmd, subcmd=PING_REQ, request_id, count, interval, timeout, target_ipv4, target_port, pubkey }
|
||||
├── etcp_send(to_conn, entry) [отправляет запрос third node]
|
||||
├── Создает route_ping_pending { request_id, callback=nat_link_check_cb, arg, timeout_timer }
|
||||
└── Добавляет в bgp->ping_pending
|
||||
|
||||
|
||||
=== ОБРАБОТКА PING_REQ (на third node) ===
|
||||
|
||||
route_bgp_receive_cbk(data, len, from_conn, bgp)
|
||||
└── ROUTE_SUBCMD_PING_REQ
|
||||
└── route_ping_handle_req(bgp, from_conn, data, len)
|
||||
├── Создает route_ping_series_ctx { reply_conn, request_id, target_addr, pubkey, local_sock, count_total }
|
||||
├── Находит первый IPv4 ETCP_SOCKET -> local_sock
|
||||
└── etcp_send_ping_to_socket(instance, local_sock, pubkey, &target_addr,
|
||||
timeout_ms, route_ping_single_cb, ctx, NULL, 0)
|
||||
└── Отправляет ETCP пинг на target_addr
|
||||
|
||||
|
||||
=== ОТВЕТ НА ПИНГ (целевой узел) ===
|
||||
|
||||
etcp_connections_read_callback_socket()
|
||||
└── Получает ETCP пинг -> отправляет pong
|
||||
|
||||
|
||||
=== ПОЛУЧЕНИЕ ОТВЕТА НА ПИНГ (third node) ===
|
||||
|
||||
route_ping_single_cb(success, rtt, arg, nonce, resp_data, resp_data_len)
|
||||
├── Обновляет статистику: count_sent++, count_ok++, sum_rtt += rtt
|
||||
├── Если count_sent < count_total:
|
||||
│ └── etcp_send_ping_to_socket(...) [следующий пинг]
|
||||
└── Иначе:
|
||||
└── route_ping_series_finish(ctx)
|
||||
├── Вычисляет avg_rtt
|
||||
└── Создает BGP_PING_RESPONSE { cmd, subcmd=PING_RESP, request_id, count_sent, count_ok, avg_rtt }
|
||||
└── etcp_send(reply_conn, entry) [отправляет результат инициатору]
|
||||
|
||||
|
||||
=== ПОЛУЧЕНИЕ PING_RESP (на инициаторе) ===
|
||||
|
||||
route_bgp_receive_cbk(data, len, from_conn, bgp)
|
||||
└── ROUTE_SUBCMD_PING_RESP
|
||||
└── route_ping_handle_resp(bgp, from_conn, data, len)
|
||||
├── Ищет route_ping_pending по request_id
|
||||
├── Отменяет timeout_timer
|
||||
└── Вызывает callback: nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)
|
||||
|
||||
|
||||
=== CALLBACK ПРОВЕРКИ NAT ===
|
||||
|
||||
nat_link_check_cb(success, avg_rtt, count_sent, count_ok, arg)
|
||||
├── Определяет nat_type = success ? NAT_TYPE_OPEN : NAT_TYPE_RESTRICTED
|
||||
├── link->nat_type = nat_type
|
||||
├── link->nat_check_status = success ? NAT_CHECK_OPEN : NAT_CHECK_RESTRICTED
|
||||
└── route_bgp_send_nat_info(link->etcp, socket_id, nat_ip, nat_port, nat_type)
|
||||
├── Создает BGP_NAT_INFO { cmd, subcmd=NAT_INFO, socket_id, nat_ip[4], nat_port, nat_type }
|
||||
└── etcp_send(conn, entry) [отправляет результат клиенту]
|
||||
|
||||
|
||||
=== ПРИЕМ NAT_INFO (на клиенте) ===
|
||||
|
||||
route_bgp_receive_cbk(data, len, from_conn, bgp)
|
||||
└── ROUTE_SUBCMD_NAT_INFO
|
||||
└── route_bgp_handle_nat_info(bgp, from_conn, data, len)
|
||||
├── Ищет линки по remote_socket_id
|
||||
├── Устанавливает link->nat_type = info->nat_type
|
||||
└── Логирует результат NAT detection
|
||||
|
||||
|
||||
=== ЗАПРОС НА ПРОВЕРКУ NAT (NAT_CHECK_REQ - новый механизм) ===
|
||||
|
||||
route_bgp_send_nat_check_req(conn, socket_id)
|
||||
├── Создает BGP_NAT_CHECK_REQ { cmd, subcmd=NAT_CHECK_REQ, socket_id, interface_ip, interface_port }
|
||||
│ ├── Ищет линк с remote_socket_id == socket_id
|
||||
│ └── Берет interface_addr из линка -> interface_ip/interface_port
|
||||
└── etcp_send(conn, entry)
|
||||
|
||||
route_bgp_handle_nat_check_req(bgp, from_conn, data, len)
|
||||
├── Принимает BGP_NAT_CHECK_REQ
|
||||
├── Сохраняет interface_ip/interface_port в struct sockaddr_storage dummy
|
||||
├── Ищет линк с remote_socket_id == req->socket_id
|
||||
└── Если линк найден и не в процессе проверки:
|
||||
└── route_bgp_start_link_nat_check(bgp, target_link)
|
||||
[далее по стандартному flow]
|
||||
|
||||
|
||||
=== СВЯЗЬ СТРУКТУР ===
|
||||
|
||||
struct ETCP_SOCKET
|
||||
├── local_addr [адрес бинда из конфига]
|
||||
├── interface_addr [IP интерфейса + порт: интерфейс/default route/конфиг]
|
||||
└── ...
|
||||
|
||||
struct ETCP_LINK
|
||||
├── remote_addr [адрес удаленного узла]
|
||||
├── nat_ip [NAT IP, полученный из INIT_RESPONSE]
|
||||
├── nat_port [NAT port, полученный из INIT_RESPONSE]
|
||||
├── nat_type [OPEN/RESTRICTED, результат проверки]
|
||||
├── nat_check_status [NONE/WAITING/IN_PROGRESS/OPEN/RESTRICTED]
|
||||
├── initialized [0/1, флаг завершения handshake]
|
||||
└── ...
|
||||
|
||||
struct ETCP_CONN
|
||||
├── links [список ETCP_LINK]
|
||||
├── up_cbk [route_bgp_on_conn_up]
|
||||
├── down_cbk [route_bgp_on_conn_down]
|
||||
├── ready_cbk [вызывается при etcp_conn_ready]
|
||||
├── initialized [0/1, флаг готовности]
|
||||
├── links_up [0/1, есть ли активные линки]
|
||||
└── ...
|
||||
|
||||
|
||||
=== СТАТУСЫ NAT CHECK ===
|
||||
|
||||
NAT_CHECK_NONE = 0 [не проверялся]
|
||||
NAT_CHECK_WAITING = 1 [ожидает]
|
||||
NAT_CHECK_IN_PROGRESS = 2 [проверка запущена]
|
||||
NAT_CHECK_OPEN = 3 [результат: OPEN]
|
||||
NAT_CHECK_RESTRICTED = 4 [результат: RESTRICTED]
|
||||
|
||||
NAT_TYPE_UNKNOWN = 0
|
||||
NAT_TYPE_OPEN = 1
|
||||
NAT_TYPE_RESTRICTED = 2
|
||||
|
||||
NAT_VERIFIED_UNKNOWN = 4
|
||||
NAT_VERIFIED_OPEN = 5
|
||||
NAT_VERIFIED_RESTRICTED = 6
|
||||
NAT_VERIFIED_DIRECT = 7
|
||||
|
||||
|
||||
=== СООБЩЕНИЯ ПРОТОКОЛА ===
|
||||
|
||||
ETCP_INIT_REQUEST (0x02) [клиент -> сервер]
|
||||
ETCP_INIT_RESPONSE (0x03) [сервер -> клиент, включает NAT info]
|
||||
ETCP_INIT_REQUEST_NOINIT (0x04)
|
||||
ETCP_INIT_RESPONSE_NOINIT (0x05)
|
||||
ETCP_PING (0x06)
|
||||
ETCP_PONG (0x07)
|
||||
|
||||
ROUTE_SUBCMD_NODEINFO (0x04)
|
||||
ROUTE_SUBCMD_REQUEST_TABLE (0x05)
|
||||
ROUTE_SUBCMD_WITHDRAW (0x06)
|
||||
ROUTE_SUBCMD_PING_REQ (0x07)
|
||||
ROUTE_SUBCMD_PING_RESP (0x08)
|
||||
ROUTE_SUBCMD_NAT_INFO (0x09)
|
||||
ROUTE_SUBCMD_NAT_CHECK_REQ (0x0A)
|
||||
|
||||
|
||||
=== ПОЛНЫЙ FLOW (одна строка) ===
|
||||
|
||||
Handshake: etcp_connections_read_callback_socket -> INIT_REQUEST/RESPONSE -> link->initialized=1 -> etcp_conn_ready -> etcp_on_up -> route_bgp_on_conn_up -> route_bgp_new_conn -> route_bgp_start_link_nat_check -> route_ping_send_req_addr -> (через ETCP) -> route_ping_handle_req -> etcp_send_ping_to_socket -> (UDP) -> route_ping_single_cb -> route_ping_series_finish -> route_ping_handle_resp -> nat_link_check_cb -> route_bgp_send_nat_info -> route_bgp_handle_nat_info
|
||||
|
||||
NAT_CHECK_REQ flow: route_bgp_send_nat_check_req -> BGP_NAT_CHECK_REQ -> route_bgp_handle_nat_check_req -> route_bgp_start_link_nat_check -> [стандартный ping flow]
|
||||
@@ -376,7 +376,7 @@ static void socket_callback(int fd, void *user_arg) {
|
||||
u_free(packet);
|
||||
return;
|
||||
}
|
||||
uasync_set_timeout(ep->emulator->ua, delay_tb, packet, (timeout_callback_t)send_delayed_packet);
|
||||
uasync_set_timeout(ep->emulator->ua, delay_tb, packet, (timeout_callback_t)send_delayed_packet, "netem_delay");
|
||||
}
|
||||
|
||||
// Callback for sending delayed packet
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
tui enable
|
||||
stepi # шаг по инструкции
|
||||
nexti
|
||||
step # шаг по строке исходника
|
||||
next
|
||||
|
||||
bt
|
||||
frame n
|
||||
p *var
|
||||
+2408
File diff suppressed because it is too large.
Load diff
+31
-93
@@ -7,8 +7,13 @@ utun_CORE_SOURCES = \
|
||||
config_parser.c \
|
||||
config_updater.c \
|
||||
route_lib.c \
|
||||
route6_lib.c \
|
||||
route_bgp.c \
|
||||
route_ping.c \
|
||||
route_node.c \
|
||||
route_node_lmdb.c \
|
||||
route_connectivity.c \
|
||||
conn_mgr.c \
|
||||
routing.c \
|
||||
tun_if.c \
|
||||
tun_route.c \
|
||||
@@ -17,121 +22,54 @@ utun_CORE_SOURCES = \
|
||||
tun_windows.c \
|
||||
etcp.c \
|
||||
etcp_connections.c \
|
||||
etcp_bbr.c \
|
||||
etcp_loadbalancer.c \
|
||||
etcp_debug.c \
|
||||
etcp_dump.c \
|
||||
secure_channel.c \
|
||||
crc32.c \
|
||||
stcp_link.c \
|
||||
stcp.c \
|
||||
stcp_server.c \
|
||||
stcp_client.c \
|
||||
pkt_normalizer.c \
|
||||
packet_dump.c \
|
||||
etcp_api.c \
|
||||
control_server.c \
|
||||
firewall.c
|
||||
msg_transport.c \
|
||||
firewall.c \
|
||||
eim_nat.c \
|
||||
nat_transport.c \
|
||||
dummynet.c \
|
||||
proxy/tcp_proxy_client.c \
|
||||
etcp_router.c \
|
||||
proxy/tcp_proxy_server.c \
|
||||
proxy/udp_proxy.c \
|
||||
proxy/icmp_proxy.c \
|
||||
lwip_tcp/lwip_pbuf.c \
|
||||
lwip_tcp/lwip_tcp.c \
|
||||
lwip_tcp/lwip_tcp_in.c \
|
||||
lwip_tcp/lwip_tcp_out.c
|
||||
|
||||
# Platform-specific TUN libs (Windows only)
|
||||
utun_TUN_LIBS = @TUN_LIBS@
|
||||
|
||||
# TinyCrypt sources (only used without OpenSSL)
|
||||
utun_TINYCRYPT_SOURCES = \
|
||||
$(top_srcdir)/tinycrypt/lib/source/aes_encrypt.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/aes_decrypt.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/cbc_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ccm_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/cmac_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ctr_mode.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc_dh.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc_dsa.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/ecc_platform_specific.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/hmac.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/sha256.c \
|
||||
$(top_srcdir)/tinycrypt/lib/source/utils.c
|
||||
|
||||
# Combine sources based on OpenSSL usage
|
||||
if USE_OPENSSL
|
||||
utun_SOURCES = $(utun_CORE_SOURCES) $(utun_TUN_SOURCES)
|
||||
else
|
||||
utun_SOURCES = $(utun_CORE_SOURCES) $(utun_TINYCRYPT_SOURCES) $(utun_TUN_SOURCES)
|
||||
endif
|
||||
|
||||
# Include paths
|
||||
utun_CORE_CFLAGS = \
|
||||
utun_CFLAGS = \
|
||||
-I$(top_srcdir)/lib \
|
||||
-I$(top_srcdir)/tinycrypt/lib/include \
|
||||
-I$(top_srcdir)/tinycrypt/lib/source \
|
||||
-I$(top_srcdir)/src/uip \
|
||||
-g \
|
||||
$(DEBUG_FLAGS)
|
||||
|
||||
utun_CFLAGS = $(utun_CORE_CFLAGS)
|
||||
|
||||
# Libraries
|
||||
utun_CORE_LDADD = \
|
||||
utun_LDADD = \
|
||||
$(top_builddir)/lib/libuasync.a \
|
||||
-lpthread \
|
||||
-lm
|
||||
|
||||
if USE_OPENSSL
|
||||
utun_LDADD = $(utun_CORE_LDADD) -lcrypto $(utun_TUN_LIBS)
|
||||
else
|
||||
utun_LDADD = $(utun_CORE_LDADD) $(utun_TUN_LIBS)
|
||||
endif
|
||||
|
||||
# TinyCrypt object files (for tests that need them)
|
||||
TINYCRYPT_SRCDIR = $(top_srcdir)/tinycrypt/lib/source
|
||||
|
||||
TINYCRYPT_OBJS = \
|
||||
utun-aes_encrypt.o \
|
||||
utun-aes_decrypt.o \
|
||||
utun-ccm_mode.o \
|
||||
utun-cmac_mode.o \
|
||||
utun-ctr_mode.o \
|
||||
utun-ecc.o \
|
||||
utun-ecc_dh.o \
|
||||
utun-ecc_dsa.o \
|
||||
utun-sha256.o \
|
||||
utun-ecc_platform_specific.o \
|
||||
utun-utils.o
|
||||
|
||||
# Rules to build TinyCrypt objects (for tests)
|
||||
TINYCRYPT_CFLAGS = -g -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
|
||||
utun-aes_encrypt.o: $(TINYCRYPT_SRCDIR)/aes_encrypt.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-aes_decrypt.o: $(TINYCRYPT_SRCDIR)/aes_decrypt.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ccm_mode.o: $(TINYCRYPT_SRCDIR)/ccm_mode.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-cmac_mode.o: $(TINYCRYPT_SRCDIR)/cmac_mode.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ctr_mode.o: $(TINYCRYPT_SRCDIR)/ctr_mode.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ecc.o: $(TINYCRYPT_SRCDIR)/ecc.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ecc_dh.o: $(TINYCRYPT_SRCDIR)/ecc_dh.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-ecc_dsa.o: $(TINYCRYPT_SRCDIR)/ecc_dsa.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
utun-sha256.o: $(TINYCRYPT_SRCDIR)/sha256.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
# Force rebuild of ecc_platform_specific.o to pick up platform changes
|
||||
utun-ecc_platform_specific.o: $(TINYCRYPT_SRCDIR)/ecc_platform_specific.c FORCE
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
FORCE:
|
||||
|
||||
utun-utils.o: $(TINYCRYPT_SRCDIR)/utils.c
|
||||
$(AM_V_CC)$(COMPILE) $(TINYCRYPT_CFLAGS) -c -o $@ $<
|
||||
|
||||
# Build TinyCrypt objects for tests (convenience target)
|
||||
tinycrypt-objects: $(TINYCRYPT_OBJS)
|
||||
-lm \
|
||||
-lcrypto \
|
||||
$(utun_TUN_LIBS)
|
||||
|
||||
# Copy binary to project root after building
|
||||
all-local: copy-to-root
|
||||
|
||||
+390
-49
@@ -13,6 +13,7 @@
|
||||
#include <netdb.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
#include "../lib/mem.h"
|
||||
|
||||
@@ -23,6 +24,16 @@
|
||||
static int assign_string(char *dest, size_t dest_size, const char *src);
|
||||
static int parse_firewall_rule(const char *rule_str, struct global_config *global);
|
||||
|
||||
static int hex_to_binary(const char *hex_str, uint8_t *binary, size_t binary_len) {
|
||||
if (!hex_str || !binary || strlen(hex_str) != binary_len * 2) return -1;
|
||||
for (size_t i = 0; i < binary_len; i++) {
|
||||
unsigned int byte;
|
||||
if (sscanf(hex_str + i * 2, "%2x", &byte) != 1) return -1;
|
||||
binary[i] = (uint8_t)byte;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
SECTION_UNKNOWN,
|
||||
SECTION_GLOBAL,
|
||||
@@ -30,7 +41,13 @@ typedef enum {
|
||||
SECTION_CLIENT,
|
||||
SECTION_ROUTING,
|
||||
SECTION_DEBUG,
|
||||
SECTION_FIREWALL
|
||||
SECTION_FIREWALL,
|
||||
SECTION_CONTROL,
|
||||
SECTION_ALLOWED_KEYS,
|
||||
SECTION_NAT,
|
||||
SECTION_TCP_PROXY_CLIENT,
|
||||
SECTION_TCP_PROXY_SERVER,
|
||||
SECTION_MSG_TRANSPORT
|
||||
} section_type_t;
|
||||
|
||||
static char* trim(char *str) {
|
||||
@@ -109,9 +126,21 @@ static int parse_sockaddr(const char *addr_str, const char *port_str, struct soc
|
||||
struct addrinfo hints = {0};
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
|
||||
|
||||
char clean_addr[MAX_ADDR_LEN];
|
||||
const char *a = addr_str;
|
||||
if (addr_str[0] == '[') {
|
||||
size_t len = strlen(addr_str);
|
||||
if (len > 2 && addr_str[len - 1] == ']') {
|
||||
if (len - 2 >= sizeof(clean_addr)) return -1;
|
||||
memcpy(clean_addr, addr_str + 1, len - 2);
|
||||
clean_addr[len - 2] = '\0';
|
||||
a = clean_addr;
|
||||
}
|
||||
}
|
||||
|
||||
struct addrinfo *result;
|
||||
if (getaddrinfo(addr_str, port_str, &hints, &result) != 0) {
|
||||
if (getaddrinfo(a, port_str, &hints, &result) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -258,6 +287,51 @@ static int parse_firewall_rule(const char *rule_str, struct global_config *globa
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_control_allow(const char *rule_str, struct global_config *global) {
|
||||
if (!rule_str || !global) return -1;
|
||||
|
||||
char rule_copy[64];
|
||||
strncpy(rule_copy, rule_str, sizeof(rule_copy) - 1);
|
||||
rule_copy[sizeof(rule_copy) - 1] = '\0';
|
||||
trim(rule_copy);
|
||||
|
||||
uint8_t cidr = 32;
|
||||
char *slash = strchr(rule_copy, '/');
|
||||
if (slash) {
|
||||
*slash = '\0';
|
||||
cidr = (uint8_t)atoi(slash + 1);
|
||||
if (cidr > 32) cidr = 32;
|
||||
}
|
||||
|
||||
struct in_addr addr;
|
||||
if (inet_pton(AF_INET, rule_copy, &addr) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "parse_control_allow: invalid IP: %s", rule_str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t ip = ntohl(addr.s_addr);
|
||||
uint32_t mask = (cidr == 32) ? 0xFFFFFFFF : (~0U << (32 - cidr));
|
||||
|
||||
if ((global->control_allow_count % INITIAL_ARRAY_CAPACITY) == 0) {
|
||||
int new_cap = global->control_allow_count + INITIAL_ARRAY_CAPACITY;
|
||||
struct CFG_CONTROL_ALLOW *new_allows = u_realloc(global->control_allows,
|
||||
new_cap * sizeof(struct CFG_CONTROL_ALLOW));
|
||||
if (!new_allows) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to realloc control allows");
|
||||
return -1;
|
||||
}
|
||||
global->control_allows = new_allows;
|
||||
}
|
||||
|
||||
struct CFG_CONTROL_ALLOW *rule = &global->control_allows[global->control_allow_count];
|
||||
rule->network = ip & mask;
|
||||
rule->netmask = mask;
|
||||
global->control_allow_count++;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Added control_allow: %s/%u", rule_str, (unsigned)cidr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const char *name) {
|
||||
struct CFG_SERVER *srv = servers;
|
||||
while (srv) {
|
||||
@@ -269,7 +343,7 @@ static struct CFG_SERVER* find_server_by_name(struct CFG_SERVER *servers, const
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int parse_global(const char *key, const char *value, struct global_config *global) {
|
||||
static int parse_global(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
if (strcmp(key, "my_node_name") == 0) {
|
||||
return assign_string(global->name, sizeof(global->name), value);
|
||||
}
|
||||
@@ -303,35 +377,19 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
global->keepalive_interval = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "control_ip") == 0) {
|
||||
// Store control_ip for later processing with control_port
|
||||
strncpy(global->control_ip, value, sizeof(global->control_ip) - 1);
|
||||
global->control_ip[sizeof(global->control_ip) - 1] = '\0';
|
||||
if (strcmp(key, "bbr_max_cwnd") == 0) {
|
||||
global->bbr_max_cwnd = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "control_port") == 0) {
|
||||
// Use control_ip from config, fallback to localhost if not set
|
||||
char control_ip[MAX_ADDR_LEN];
|
||||
if (global->control_ip[0] != '\0') {
|
||||
strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1);
|
||||
} else {
|
||||
strcpy(control_ip, "127.0.0.1");
|
||||
}
|
||||
char port_str[16];
|
||||
snprintf(port_str, sizeof(port_str), "%s", value);
|
||||
parse_sockaddr(control_ip, port_str, &global->control_sock);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "net_debug") == 0) {
|
||||
global->net_debug = atoi(value);
|
||||
}
|
||||
|
||||
if (strcmp(key, "debug_level") == 0) {
|
||||
return assign_string(global->debug_level, sizeof(global->debug_level), value);
|
||||
}
|
||||
if (strcmp(key, "log_file") == 0) {
|
||||
return assign_string(global->log_file, sizeof(global->log_file), value);
|
||||
}
|
||||
if (strcmp(key, "db_path") == 0) {
|
||||
return assign_string(global->db_path, sizeof(global->db_path), value);
|
||||
}
|
||||
/* debug_categories key is deprecated - use [debug] section instead */
|
||||
if (strcmp(key, "enable_timestamp") == 0) {
|
||||
global->enable_timestamp = atoi(value);
|
||||
@@ -353,11 +411,200 @@ static int parse_global(const char *key, const char *value, struct global_config
|
||||
global->tun_test_mode = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown global option '%s'. Valid: my_node_name, my_private_key, my_public_key, my_node_id, tun_ifname, tun_ip, mtu, keepalive_timeout, keepalive_interval, bbr_max_cwnd, debug_level, log_file, db_path, enable_timestamp, enable_function_names, enable_file_lines, enable_colors, tun_test_mode", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv) {
|
||||
static int parse_control(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
if (strcmp(key, "ip") == 0 || strcmp(key, "control_ip") == 0) {
|
||||
strncpy(global->control_ip, value, sizeof(global->control_ip) - 1);
|
||||
global->control_ip[sizeof(global->control_ip) - 1] = '\0';
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "port") == 0 || strcmp(key, "control_port") == 0) {
|
||||
char control_ip[MAX_ADDR_LEN];
|
||||
if (global->control_ip[0] != '\0') {
|
||||
strncpy(control_ip, global->control_ip, sizeof(control_ip) - 1);
|
||||
} else {
|
||||
strcpy(control_ip, "127.0.0.1");
|
||||
}
|
||||
char port_str[16];
|
||||
snprintf(port_str, sizeof(port_str), "%s", value);
|
||||
parse_sockaddr(control_ip, port_str, &global->control_sock);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "allow") == 0 || strcmp(key, "control_allow") == 0) {
|
||||
return parse_control_allow(value, global);
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown control option '%s'. Valid: ip/control_ip, port/control_port, allow/control_allow", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_msg_transport(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
if (strcmp(key, "port") == 0) {
|
||||
char port_str[16];
|
||||
snprintf(port_str, sizeof(port_str), "%s", value);
|
||||
parse_sockaddr("127.0.0.1", port_str, &global->msg_transport_sock);
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown msg_transport option '%s'. Valid: port", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_tcp_proxy_client(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
if (strcmp(key, "enabled") == 0) {
|
||||
global->tcp_proxy_client_enabled = strcasecmp(value, "yes") == 0 || strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0;
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "tun_name") == 0) {
|
||||
strncpy(global->tcp_proxy_client_tun_name, value, sizeof(global->tcp_proxy_client_tun_name) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "tun_ip") == 0) {
|
||||
strncpy(global->tcp_proxy_client_tun_ip, value, sizeof(global->tcp_proxy_client_tun_ip) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "mtu") == 0) {
|
||||
global->tcp_proxy_client_mtu = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "via_node") == 0) {
|
||||
global->tcp_proxy_client_via_node_id = strtoull(value, NULL, 16);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "forward") == 0) {
|
||||
if (global->tcp_proxy_client_mapping_count >= MAX_TCP_PROXY_CLIENT_MAPPINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many tcp_proxy_client forward rules (max %d)", MAX_TCP_PROXY_CLIENT_MAPPINGS);
|
||||
return -1;
|
||||
}
|
||||
char buf[256];
|
||||
strncpy(buf, value, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
trim(buf);
|
||||
|
||||
char* local_port_str = strtok(buf, " ");
|
||||
char* arrow = strtok(NULL, " ");
|
||||
char* remote_str = strtok(NULL, "");
|
||||
if (!local_port_str || !arrow || !remote_str || strcmp(arrow, "->") != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward format: %s (expected: port -> ip:port)", value);
|
||||
return -1;
|
||||
}
|
||||
int local_port = atoi(local_port_str);
|
||||
if (local_port <= 0 || local_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward local port: %s", local_port_str); return -1; }
|
||||
|
||||
char* remote_ip_str = strtok(remote_str, ":");
|
||||
char* remote_port_str = strtok(NULL, "");
|
||||
if (!remote_ip_str || !remote_port_str) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward remote: %s (expected ip:port)", remote_str);
|
||||
return -1;
|
||||
}
|
||||
int remote_port = atoi(remote_port_str);
|
||||
if (remote_port <= 0 || remote_port > 65535) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid tcp_proxy_client forward remote port: %s", remote_port_str); return -1; }
|
||||
|
||||
int idx = global->tcp_proxy_client_mapping_count;
|
||||
global->tcp_proxy_client_mappings[idx].local_port = (uint16_t)local_port;
|
||||
strncpy(global->tcp_proxy_client_mappings[idx].remote_ip, remote_ip_str, sizeof(global->tcp_proxy_client_mappings[idx].remote_ip) - 1);
|
||||
global->tcp_proxy_client_mappings[idx].remote_port = (uint16_t)remote_port;
|
||||
global->tcp_proxy_client_mapping_count++;
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown tcp_proxy_client option '%s'. Valid: enabled, tun_name, tun_ip, mtu, via_node, forward", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_nat(const char *key, const char *value, struct global_config *global, const char *filename, int line_num) {
|
||||
if (strcmp(key, "tun_ifname") == 0) {
|
||||
strncpy(global->nat_tun_ifname, value, sizeof(global->nat_tun_ifname) - 1);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "tun_ip") == 0) {
|
||||
uint8_t netmask;
|
||||
parse_ip_with_netmask(value, &global->nat_tun_ip, &netmask);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "nat_via") == 0) {
|
||||
global->nat_via_node_id = strtoull(value, NULL, 16);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "port_start") == 0) {
|
||||
global->nat_port_start = (uint16_t)atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "port_end") == 0) {
|
||||
global->nat_port_end = (uint16_t)atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "forward") == 0) {
|
||||
if (global->nat_forward_count >= MAX_NAT_FORWARDS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Too many NAT forward rules (max %d)", MAX_NAT_FORWARDS);
|
||||
return -1;
|
||||
}
|
||||
// Format: proto:internal_ip:internal_port:external_port
|
||||
// Example: tcp:192.168.1.100:8080:8080
|
||||
char buf[128];
|
||||
strncpy(buf, value, sizeof(buf) - 1);
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
trim(buf);
|
||||
|
||||
char* proto_str = strtok(buf, ":");
|
||||
char* ip_str = strtok(NULL, ":");
|
||||
char* int_port = strtok(NULL, ":");
|
||||
char* ext_port = strtok(NULL, ":");
|
||||
if (!proto_str || !ip_str || !int_port || !ext_port) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward format: %s (expected proto:ip:internal_port:external_port)", value);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t proto;
|
||||
if (strcasecmp(proto_str, "tcp") == 0) proto = IPPROTO_TCP;
|
||||
else if (strcasecmp(proto_str, "udp") == 0) proto = IPPROTO_UDP;
|
||||
else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward proto: %s (tcp/udp)", proto_str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct in_addr addr;
|
||||
if (inet_pton(AF_INET, ip_str, &addr) != 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "Invalid NAT forward IP: %s", ip_str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int idx = global->nat_forward_count;
|
||||
global->nat_forwards[idx].proto = proto;
|
||||
global->nat_forwards[idx].internal_ip_host = ntohl(addr.s_addr);
|
||||
global->nat_forwards[idx].internal_port_net = htons((uint16_t)atoi(int_port));
|
||||
global->nat_forwards[idx].external_port = (uint16_t)atoi(ext_port);
|
||||
global->nat_forward_count++;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "NAT forward: %s %s:%s -> :%s",
|
||||
proto_str, ip_str, int_port, ext_port);
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown NAT option '%s'. Valid: tun_ifname, tun_ip, nat_via, port_start, port_end, forward", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_server(const char *key, const char *value, struct CFG_SERVER *srv, const char *filename, int line_num) {
|
||||
if (strcmp(key, "addr") == 0) {
|
||||
if (strncmp(value, "temporary_ipv6:", 15) == 0) {
|
||||
srv->ipv6_mode = CFG_IPV6_MODE_TEMPORARY;
|
||||
const char* port_str = value + 15;
|
||||
if (!port_str[0]) return -1;
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&srv->ip;
|
||||
memset(&srv->ip, 0, sizeof(srv->ip));
|
||||
sin6->sin6_family = AF_INET6;
|
||||
sin6->sin6_port = htons((uint16_t)atoi(port_str));
|
||||
return 0;
|
||||
}
|
||||
if (strncmp(value, "permanent_ipv6:", 15) == 0) {
|
||||
srv->ipv6_mode = CFG_IPV6_MODE_PERMANENT;
|
||||
const char* port_str = value + 15;
|
||||
if (!port_str[0]) return -1;
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&srv->ip;
|
||||
memset(&srv->ip, 0, sizeof(srv->ip));
|
||||
sin6->sin6_family = AF_INET6;
|
||||
sin6->sin6_port = htons((uint16_t)atoi(port_str));
|
||||
return 0;
|
||||
}
|
||||
return parse_address_and_port(value, &srv->ip);
|
||||
}
|
||||
if (strcmp(key, "so_mark") == 0) {
|
||||
@@ -388,16 +635,20 @@ static int parse_server(const char *key, const char *value, struct CFG_SERVER *s
|
||||
srv->mtu = atoi(value);
|
||||
return 0;
|
||||
}
|
||||
if (strcmp(key, "loss_rate") == 0) {
|
||||
srv->loss_rate = atoi(value);
|
||||
if (srv->loss_rate < 0) srv->loss_rate = 0;
|
||||
if (srv->loss_rate > 100) srv->loss_rate = 100;
|
||||
if (strcmp(key, "only_local") == 0) {
|
||||
srv->only_local = atoi(value) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
if (strcmp(key, "transport") == 0) {
|
||||
if (strcmp(value, "tcp") == 0) srv->transport = 1;
|
||||
else srv->transport = 0; // default udp
|
||||
return 0;
|
||||
}
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown server option '%s'. Valid: addr, so_mark, fib, netif, type, mtu, only_local, transport", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int parse_client(const char *key, const char *value, struct CFG_CLIENT *cli, struct CFG_SERVER *servers) {
|
||||
static int parse_client(const char *key, const char *value, struct CFG_CLIENT *cli, struct CFG_SERVER *servers, const char *filename, int line_num) {
|
||||
if (strcmp(key, "link") == 0) {
|
||||
char link_copy[MAX_CONN_NAME_LEN + MAX_ADDR_LEN];
|
||||
if (strlen(value) >= sizeof(link_copy)) return -1;
|
||||
@@ -435,7 +686,8 @@ static int parse_client(const char *key, const char *value, struct CFG_CLIENT *c
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown client option '%s'. Valid: link, peer_public_key, keepalive", filename, line_num, key);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static section_type_t parse_section_header(const char *line, char *name, size_t name_len) {
|
||||
@@ -455,6 +707,12 @@ static section_type_t parse_section_header(const char *line, char *name, size_t
|
||||
if (strcasecmp(section, "routing") == 0) return SECTION_ROUTING;
|
||||
if (strcasecmp(section, "debug") == 0) return SECTION_DEBUG;
|
||||
if (strcasecmp(section, "firewall") == 0) return SECTION_FIREWALL;
|
||||
if (strcasecmp(section, "control") == 0) return SECTION_CONTROL;
|
||||
if (strcasecmp(section, "allowed_keys") == 0) return SECTION_ALLOWED_KEYS;
|
||||
if (strcasecmp(section, "nat") == 0) return SECTION_NAT;
|
||||
if (strcasecmp(section, "tcp_proxy_client") == 0) return SECTION_TCP_PROXY_CLIENT;
|
||||
if (strcasecmp(section, "tcp_proxy_server") == 0) return SECTION_TCP_PROXY_SERVER;
|
||||
if (strcasecmp(section, "msg_transport") == 0) return SECTION_MSG_TRANSPORT;
|
||||
|
||||
char *colon = strchr(section, ':');
|
||||
if (!colon) return SECTION_UNKNOWN;
|
||||
@@ -482,9 +740,14 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
// Set default values
|
||||
cfg->global.name[0] = '\0';
|
||||
cfg->global.keepalive_timeout = 2000; // Default 2 seconds
|
||||
cfg->global.keepalive_interval = 200; // Default 0.2 s
|
||||
cfg->global.bbr_max_cwnd = 1048576; // Default 1MB (was INFLIGHT_LIM_MAX)
|
||||
cfg->global.tcp_recv_buf = 0; // 0 = OS default
|
||||
cfg->global.firewall_rules = NULL;
|
||||
cfg->global.firewall_rule_count = 0;
|
||||
cfg->global.firewall_bypass_all = 0;
|
||||
cfg->global.control_allows = NULL;
|
||||
cfg->global.control_allow_count = 0;
|
||||
|
||||
section_type_t cur_section = SECTION_UNKNOWN;
|
||||
struct CFG_SERVER *cur_server = NULL;
|
||||
@@ -543,18 +806,18 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
|
||||
switch (cur_section) {
|
||||
case SECTION_GLOBAL:
|
||||
if (parse_global(key, value, &cfg->global) < 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid global key '%s'", filename, line_num, key);
|
||||
if (parse_global(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_global already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_SERVER:
|
||||
if (cur_server && parse_server(key, value, cur_server) < 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid server key '%s'", filename, line_num, key);
|
||||
if (cur_server && parse_server(key, value, cur_server, filename, line_num) < 0) {
|
||||
// parse_server already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_CLIENT:
|
||||
if (cur_client && parse_client(key, value, cur_client, cfg->servers) < 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid client key '%s'", filename, line_num, key);
|
||||
if (cur_client && parse_client(key, value, cur_client, cfg->servers, filename, line_num) < 0) {
|
||||
// parse_client already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_ROUTING:
|
||||
@@ -562,6 +825,8 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
add_route_entry(&cfg->route_subnets, value);
|
||||
} else if (strcmp(key, "my_subnet") == 0) {
|
||||
add_route_entry(&cfg->my_subnets, value);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown routing option '%s'. Valid: route_subnet, my_subnet", filename, line_num, key);
|
||||
}
|
||||
break;
|
||||
case SECTION_DEBUG:
|
||||
@@ -581,6 +846,58 @@ static struct utun_config* parse_config_internal(FILE *fp, const char *filename)
|
||||
if (parse_firewall_rule(value, &cfg->global) < 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid firewall rule: %s", filename, line_num, value);
|
||||
}
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown firewall option '%s'. Valid: allow", filename, line_num, key);
|
||||
}
|
||||
break;
|
||||
case SECTION_CONTROL:
|
||||
if (parse_control(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_control already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_ALLOWED_KEYS:
|
||||
if (strcmp(key, "allow_all") == 0) {
|
||||
cfg->global.allowed_keys_allow_all = atoi(value) ? 1 : 0;
|
||||
} else if (strcmp(key, "key") == 0) {
|
||||
struct CFG_ALLOWED_KEY *new_key = u_calloc(1, sizeof(struct CFG_ALLOWED_KEY));
|
||||
if (!new_key) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_MEMORY, "Failed to allocate allowed key");
|
||||
} else if (strlen(value) != SC_PUBKEY_SIZE * 2) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid key length %zu, expected %d", filename, line_num, strlen(value), SC_PUBKEY_SIZE * 2);
|
||||
u_free(new_key);
|
||||
} else if (hex_to_binary(value, new_key->key_bin, SC_PUBKEY_SIZE) != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONFIG, "%s:%d: Invalid hex key format", filename, line_num);
|
||||
u_free(new_key);
|
||||
} else {
|
||||
new_key->next = cfg->global.allowed_keys;
|
||||
cfg->global.allowed_keys = new_key;
|
||||
cfg->global.allowed_keys_count++;
|
||||
}
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "%s:%d: Unknown allowed_keys option '%s'. Valid: allow_all, key", filename, line_num, key);
|
||||
}
|
||||
break;
|
||||
case SECTION_NAT:
|
||||
cfg->global.nat_enabled = 1;
|
||||
if (parse_nat(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_nat already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_TCP_PROXY_CLIENT:
|
||||
cfg->global.tcp_proxy_client_enabled = 1;
|
||||
if (parse_tcp_proxy_client(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_tcp_proxy_client already printed the error
|
||||
}
|
||||
break;
|
||||
case SECTION_TCP_PROXY_SERVER:
|
||||
cfg->global.tcp_proxy_server_enabled = 1;
|
||||
if (strcmp(key, "tcp_recv_buf") == 0) {
|
||||
cfg->global.tcp_recv_buf = atoi(value);
|
||||
}
|
||||
break;
|
||||
case SECTION_MSG_TRANSPORT:
|
||||
if (parse_msg_transport(key, value, &cfg->global, filename, line_num) < 0) {
|
||||
// parse_msg_transport already printed the error
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -657,6 +974,15 @@ void free_config(struct utun_config *config) {
|
||||
|
||||
// Free firewall rules
|
||||
u_free(config->global.firewall_rules);
|
||||
// Free control allow rules (array allocated with realloc)
|
||||
u_free(config->global.control_allows);
|
||||
// Free allowed keys
|
||||
struct CFG_ALLOWED_KEY *ak = config->global.allowed_keys;
|
||||
while (ak) {
|
||||
struct CFG_ALLOWED_KEY *next = ak->next;
|
||||
u_free(ak);
|
||||
ak = next;
|
||||
}
|
||||
|
||||
u_free(config);
|
||||
}
|
||||
@@ -678,20 +1004,20 @@ void print_config(const struct utun_config *cfg) {
|
||||
const struct global_config *g = &cfg->global;
|
||||
char ip_buffer[64];
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Global: priv_key=%s, pub_key=%s, node_id=%llx, tun_if=%s, tun_ip=%s, mtu=%d, keepalive=%d, test_mode=%d, net_debug=%d",
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Global: priv_key=%s, pub_key=%s, node_id=%llx, tun_if=%s, tun_ip=%s, mtu=%d, keepalive=%d, test_mode=%d",
|
||||
g->my_private_key_hex, g->my_public_key_hex,
|
||||
(unsigned long long)g->my_node_id,
|
||||
g->tun_ifname[0] ? g->tun_ifname : "auto",
|
||||
ip_to_string(&g->tun_ip, ip_buffer, sizeof(ip_buffer)),
|
||||
g->mtu, g->keepalive_timeout, g->tun_test_mode, g->net_debug);
|
||||
g->mtu, g->keepalive_timeout, g->tun_test_mode);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Servers:");
|
||||
struct CFG_SERVER *s = cfg->servers;
|
||||
while (s) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)&s->ip;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s:%d (mark=%d, netif=%u, type=%u, mtu=%d, loss_rate=%d%%)",
|
||||
s->name, ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port),
|
||||
s->so_mark, s->netif_index, s->type, s->mtu, s->loss_rate);
|
||||
ip_str_t addr_str = sockaddr_storage_to_str(&s->ip);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: %s (mark=%d, netif=%u, type=%u, mtu=%d)",
|
||||
s->name, addr_str.str,
|
||||
s->so_mark, s->netif_index, s->type, s->mtu);
|
||||
s = s->next;
|
||||
}
|
||||
|
||||
@@ -701,9 +1027,9 @@ void print_config(const struct utun_config *cfg) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " %s: peer_key=%s, keepalive=%d", c->name, c->peer_public_key_hex, c->keepalive);
|
||||
struct CFG_CLIENT_LINK *link = c->links;
|
||||
while (link) {
|
||||
struct sockaddr_in *sin = (struct sockaddr_in *)&link->remote_addr;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s:%d (via %s)",
|
||||
ip_to_str(&sin->sin_addr, AF_INET).str, ntohs(sin->sin_port), link->local_srv->name);
|
||||
ip_str_t addr_str = sockaddr_storage_to_str(&link->remote_addr);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " Link: %s (via %s)",
|
||||
addr_str.str, link->local_srv->name);
|
||||
link = link->next;
|
||||
}
|
||||
c = c->next;
|
||||
@@ -741,6 +1067,21 @@ void print_config(const struct utun_config *cfg) {
|
||||
} else {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " no rules");
|
||||
}
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Control allows: %d rules (default deny all if 0)", g->control_allow_count);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, "Allowed keys: allow_all=%d, count=%d", g->allowed_keys_allow_all, g->allowed_keys_count);
|
||||
if (!g->allowed_keys_allow_all && g->allowed_keys_count > 0) {
|
||||
struct CFG_ALLOWED_KEY *ak = g->allowed_keys;
|
||||
while (ak) {
|
||||
char key_hex[SC_PUBKEY_SIZE * 2 + 1];
|
||||
for (int i = 0; i < SC_PUBKEY_SIZE; i++) {
|
||||
snprintf(key_hex + i * 2, sizeof(key_hex) - i * 2, "%02x", ak->key_bin[i]);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONFIG, " key=%s", key_hex);
|
||||
ak = ak->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int update_config_keys(const char *filename, const char *priv_key, const char *pub_key) {
|
||||
|
||||
+67
-3
@@ -5,6 +5,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "secure_channel.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -27,6 +28,10 @@ struct IP {
|
||||
#define CFG_SERVER_TYPE_NAT 2
|
||||
#define CFG_SERVER_TYPE_PRIVATE 3
|
||||
|
||||
#define CFG_IPV6_MODE_NONE 0 // normal explicit address
|
||||
#define CFG_IPV6_MODE_TEMPORARY 1 // temporary IPv6 from interface
|
||||
#define CFG_IPV6_MODE_PERMANENT 2 // permanent IPv6 from interface
|
||||
|
||||
struct CFG_SERVER {
|
||||
struct CFG_SERVER* next;
|
||||
char name[MAX_CONN_NAME_LEN];
|
||||
@@ -35,8 +40,10 @@ struct CFG_SERVER {
|
||||
int so_mark;
|
||||
int fib; // FreeBSD FIB (routing table), -1 = don't set
|
||||
uint8_t type; // public/nat/private
|
||||
uint8_t transport; // 0=udp (default), 1=tcp
|
||||
uint8_t ipv6_mode; // CFG_IPV6_MODE_*
|
||||
int mtu;
|
||||
int loss_rate; // packet loss rate in percent (0-100), default 0
|
||||
uint8_t only_local; // 1 = only local connections, no forwarding
|
||||
};
|
||||
|
||||
struct CFG_CLIENT_LINK {
|
||||
@@ -66,6 +73,23 @@ struct CFG_FIREWALL_RULE {
|
||||
uint8_t bypass; // 1 for allow=all (bypass all checks)
|
||||
};
|
||||
|
||||
struct CFG_CONTROL_ALLOW {
|
||||
uint32_t network; // IPv4 network in host byte order
|
||||
uint32_t netmask; // netmask in host byte order (e.g. 0xffffff00 for /24)
|
||||
};
|
||||
|
||||
struct CFG_ALLOWED_KEY {
|
||||
uint8_t key_bin[SC_PUBKEY_SIZE]; // public key in binary (32 bytes, X25519)
|
||||
struct CFG_ALLOWED_KEY *next;
|
||||
};
|
||||
|
||||
#define MAX_TCP_PROXY_CLIENT_MAPPINGS 32
|
||||
struct tcp_proxy_client_mapping_config {
|
||||
uint16_t local_port;
|
||||
char remote_ip[64];
|
||||
uint16_t remote_port;
|
||||
};
|
||||
|
||||
struct global_config {
|
||||
char name[16]; // Instance name
|
||||
char my_private_key_hex[MAX_KEY_LEN];
|
||||
@@ -76,10 +100,11 @@ struct global_config {
|
||||
int mtu;
|
||||
struct sockaddr_storage control_sock;
|
||||
char control_ip[MAX_ADDR_LEN]; // Control server IP address
|
||||
int net_debug;
|
||||
|
||||
struct sockaddr_storage msg_transport_sock;
|
||||
|
||||
// Debug and logging configuration
|
||||
char log_file[256]; // Path to log file (empty = stdout)
|
||||
char db_path[256]; // Path to LMDB nodeinfo database (empty = disabled)
|
||||
char debug_level[16]; // debug level: error, warn, info, debug, trace
|
||||
int enable_timestamp; // enable timestamps in logs
|
||||
int enable_function_names; // enable function names in logs
|
||||
@@ -96,11 +121,50 @@ struct global_config {
|
||||
int tun_test_mode; // test mode: 1 = don't open real TUN, queues only
|
||||
int keepalive_timeout; // keepalive timeout in ms (default: 2000)
|
||||
int keepalive_interval; // keepalive interval in ms (default: 200)
|
||||
int bbr_max_cwnd; // BBR cwnd cap in bytes (default: 100000)
|
||||
|
||||
// Firewall configuration
|
||||
struct CFG_FIREWALL_RULE *firewall_rules;
|
||||
int firewall_rule_count;
|
||||
int firewall_bypass_all;
|
||||
|
||||
// Control server configuration ([control] section)
|
||||
struct CFG_CONTROL_ALLOW *control_allows;
|
||||
int control_allow_count;
|
||||
|
||||
// Allowed keys configuration ([allowed_keys] section)
|
||||
struct CFG_ALLOWED_KEY *allowed_keys;
|
||||
int allowed_keys_count;
|
||||
int allowed_keys_allow_all;
|
||||
|
||||
// NAT configuration ([nat] section)
|
||||
int nat_enabled;
|
||||
char nat_tun_ifname[16];
|
||||
struct IP nat_tun_ip;
|
||||
uint64_t nat_via_node_id; // 0 = this node is provider; !=0 = client, use this provider
|
||||
uint16_t nat_port_start;
|
||||
uint16_t nat_port_end;
|
||||
#define MAX_NAT_FORWARDS 64
|
||||
struct {
|
||||
uint8_t proto;
|
||||
uint32_t internal_ip_host;
|
||||
uint16_t internal_port_net;
|
||||
uint16_t external_port;
|
||||
} nat_forwards[MAX_NAT_FORWARDS];
|
||||
int nat_forward_count;
|
||||
|
||||
// TCP proxy client configuration ([tcp_proxy_client] section)
|
||||
int tcp_proxy_client_enabled;
|
||||
char tcp_proxy_client_tun_name[16];
|
||||
char tcp_proxy_client_tun_ip[64];
|
||||
int tcp_proxy_client_mtu;
|
||||
uint64_t tcp_proxy_client_via_node_id; // через этот узел проксируются все forward-правила
|
||||
struct tcp_proxy_client_mapping_config tcp_proxy_client_mappings[MAX_TCP_PROXY_CLIENT_MAPPINGS];
|
||||
int tcp_proxy_client_mapping_count;
|
||||
|
||||
// TCP proxy server (exit node) configuration ([tcp_proxy_server] section)
|
||||
int tcp_proxy_server_enabled;
|
||||
int tcp_recv_buf; // SO_RCVBUF для TCP сокетов (0=default ОС)
|
||||
};
|
||||
|
||||
struct utun_config {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "../lib/mem.h"
|
||||
|
||||
#define PRIV_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
|
||||
#define PUB_HEXKEY_LEN 129 // 64 bytes * 2 hex chars + null
|
||||
#define PUB_HEXKEY_LEN 65 // 32 bytes * 2 hex chars + null
|
||||
#define HEXNODEID_LEN 17 // 8 bytes * 2 hex chars + null
|
||||
#define MAX_LINE_LEN 1024
|
||||
|
||||
@@ -41,8 +41,8 @@ static int is_valid_priv_key(const char *key) {
|
||||
}
|
||||
|
||||
static int is_valid_pub_key(const char *key) {
|
||||
if (!key || strlen(key) != 128) return 0;
|
||||
for (int i = 0; i < 128; i++) {
|
||||
if (!key || strlen(key) != 64) return 0;
|
||||
for (int i = 0; i < 64; i++) {
|
||||
if (!isxdigit((unsigned char)key[i])) return 0;
|
||||
}
|
||||
return 1;
|
||||
@@ -257,10 +257,10 @@ int config_ensure_keys_and_node_id(const char *filename) {
|
||||
|
||||
// Debug: print what we found
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_CONFIG, "Checking config - priv_key='%s' (len=%zu), pub_key='%s' (len=%zu), node_id=%016llx",
|
||||
global->my_private_key_hex ? global->my_private_key_hex : "NULL",
|
||||
global->my_private_key_hex ? strlen(global->my_private_key_hex) : 0,
|
||||
global->my_public_key_hex ? global->my_public_key_hex : "NULL",
|
||||
global->my_public_key_hex ? strlen(global->my_public_key_hex) : 0,
|
||||
global->my_private_key_hex[0] ? global->my_private_key_hex : "NULL",
|
||||
global->my_private_key_hex[0] ? strlen(global->my_private_key_hex) : 0,
|
||||
global->my_public_key_hex[0] ? global->my_public_key_hex : "NULL",
|
||||
global->my_public_key_hex[0] ? strlen(global->my_public_key_hex) : 0,
|
||||
(unsigned long long)global->my_node_id);
|
||||
|
||||
|
||||
|
||||
+1000
@@ -0,0 +1,1000 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "utun_instance.h"
|
||||
#include "etcp.h"
|
||||
#include "etcp_connections.h"
|
||||
#include "etcp_router.h"
|
||||
#include "config_parser.h"
|
||||
#include "secure_channel.h"
|
||||
#include "route_node.h"
|
||||
#include "route_bgp.h"
|
||||
#include "route_ping.h"
|
||||
#include "route_connectivity.h"
|
||||
#include "conn_mgr.h"
|
||||
|
||||
static struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id);
|
||||
static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry);
|
||||
static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry);
|
||||
static int cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry);
|
||||
static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry);
|
||||
static void cm_idle_timer_cb(void* arg);
|
||||
static void cm_deliver_result(struct CONN_MGR_ENTRY* entry, int result);
|
||||
static void cm_bg_ping_timer_cb(void* arg);
|
||||
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_meta_type);
|
||||
static uint16_t cm_get_node_min_rtt(struct NODEINFO_Q* nq);
|
||||
static int cm_has_direct_ip(struct NODEINFO_Q* nq);
|
||||
static int cm_has_local_addr(struct NODEINFO_Q* nq);
|
||||
static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry);
|
||||
static uint64_t cm_get_node_max_probe_time(struct NODEINFO_Q* nq);
|
||||
static int cm_is_rtt_fresh(struct NODEINFO_Q* nq, uint64_t now_tb);
|
||||
|
||||
static void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry);
|
||||
static void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len);
|
||||
static void cm_handle_interm_exchange_req(struct ETCP_CONN* conn, struct CONN_MGR_INTERM_EXCHANGE_REQ* req);
|
||||
static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len);
|
||||
static void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len);
|
||||
static void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id);
|
||||
static void cm_direct_ready_cb(struct ETCP_CONN* conn, void* arg);
|
||||
static void cm_reverse_ready_cb(struct ETCP_CONN* conn, void* arg);
|
||||
static void cm_reverse_timeout_cb(void* arg);
|
||||
static void cm_exchange_timeout_cb(void* arg);
|
||||
static void cm_candidate_ping_timer_cb(void* arg);
|
||||
|
||||
struct cm_reverse_pending {
|
||||
struct cm_reverse_pending* next;
|
||||
uint32_t request_id;
|
||||
struct CONN_MGR_ENTRY* entry;
|
||||
struct ETCP_CONN** conns;
|
||||
uint8_t conn_count;
|
||||
};
|
||||
|
||||
struct cm_exchange_pending {
|
||||
struct cm_exchange_pending* next;
|
||||
uint32_t request_id;
|
||||
struct CONN_MGR_ENTRY* entry;
|
||||
void* timeout_timer;
|
||||
struct CONN_MGR_INTERM_EXCHANGE_RESP cached_resp;
|
||||
uint8_t resp_received;
|
||||
uint8_t probes_done;
|
||||
};
|
||||
|
||||
struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance) {
|
||||
if (!instance) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: NULL instance"); return NULL; }
|
||||
struct CONN_MGR* mgr = u_calloc(1, sizeof(struct CONN_MGR));
|
||||
if (!mgr) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: alloc failed"); return NULL; }
|
||||
mgr->instance = instance;
|
||||
mgr->entry_capacity = 8;
|
||||
mgr->entries = u_calloc(mgr->entry_capacity, sizeof(struct CONN_MGR_ENTRY));
|
||||
if (!mgr->entries) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_init: entries alloc failed"); u_free(mgr); return NULL; }
|
||||
etcp_router_bind(instance, ETCP_ID_CONN_MGR, conn_mgr_router_recv_handler);
|
||||
mgr->bg_ping_timer = uasync_set_timeout(instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
|
||||
mgr->candidate_ping_timer = uasync_set_timeout(instance->ua, CONN_MGR_CANDIDATE_PING_TB, mgr, cm_candidate_ping_timer_cb, "conn_mgr_cand_ping");
|
||||
mgr->bg_ping_cycle_start_tb = get_time_tb();
|
||||
mgr->initialized = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: initialized, bg_ping started");
|
||||
return mgr;
|
||||
}
|
||||
|
||||
void conn_mgr_destroy(struct CONN_MGR* mgr) {
|
||||
if (!mgr) return;
|
||||
if (mgr->bg_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->bg_ping_timer); mgr->bg_ping_timer = NULL; }
|
||||
if (mgr->candidate_ping_timer) { uasync_cancel_timeout(mgr->instance->ua, mgr->candidate_ping_timer); mgr->candidate_ping_timer = NULL; }
|
||||
etcp_router_unbind(mgr->instance, ETCP_ID_CONN_MGR);
|
||||
for (size_t i = 0; i < mgr->entry_count; i++) cm_entry_destroy(&mgr->entries[i]);
|
||||
u_free(mgr->entries);
|
||||
mgr->initialized = 0;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: destroyed, entries=%zu", (size_t)mgr->entry_count);
|
||||
u_free(mgr);
|
||||
}
|
||||
|
||||
static void cm_entry_destroy(struct CONN_MGR_ENTRY* entry) {
|
||||
if (!entry || entry->state == CONN_MGR_STATE_DISCONNECTED) return;
|
||||
if (entry->idle_timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; }
|
||||
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
|
||||
entry->state = CONN_MGR_STATE_DISCONNECTED;
|
||||
entry->conn_type = CONN_TYPE_NONE;
|
||||
}
|
||||
|
||||
static struct CONN_MGR_ENTRY* cm_ensure_entry(struct CONN_MGR* mgr, uint64_t node_id) {
|
||||
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id);
|
||||
if (e) return e;
|
||||
if (mgr->entry_count >= mgr->entry_capacity) {
|
||||
size_t new_cap = mgr->entry_capacity * 2;
|
||||
struct CONN_MGR_ENTRY* new_e = u_realloc(mgr->entries, new_cap * sizeof(struct CONN_MGR_ENTRY));
|
||||
if (!new_e) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: realloc entries failed"); return NULL; }
|
||||
memset(new_e + mgr->entry_capacity, 0, (new_cap - mgr->entry_capacity) * sizeof(struct CONN_MGR_ENTRY));
|
||||
mgr->entries = new_e;
|
||||
mgr->entry_capacity = new_cap;
|
||||
}
|
||||
struct CONN_MGR_ENTRY* new_entry = &mgr->entries[mgr->entry_count++];
|
||||
memset(new_entry, 0, sizeof(*new_entry));
|
||||
new_entry->node_id = node_id;
|
||||
new_entry->mgr = mgr;
|
||||
return new_entry;
|
||||
}
|
||||
|
||||
static struct CONN_MGR_ENTRY* cm_find_entry(struct CONN_MGR* mgr, uint64_t node_id) {
|
||||
for (size_t i = 0; i < mgr->entry_count; i++)
|
||||
if (mgr->entries[i].node_id == node_id && mgr->entries[i].state != CONN_MGR_STATE_DISCONNECTED)
|
||||
return &mgr->entries[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void cm_deliver_result(struct CONN_MGR_ENTRY* entry, int result) {
|
||||
struct cm_cb_node* node = entry->cb_list;
|
||||
while (node) { struct cm_cb_node* next = node->next; node->cb(result, entry->node_id, node->arg); u_free(node); node = next; }
|
||||
entry->cb_list = NULL;
|
||||
}
|
||||
|
||||
static void cm_update_nodeinfo(struct CONN_MGR_ENTRY* entry) {
|
||||
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
|
||||
if (!bgp) return;
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, entry->node_id);
|
||||
if (!nq) return;
|
||||
nq->conn_mgr_type = entry->conn_type;
|
||||
memcpy(nq->conn_mgr_intermediaries, entry->intermediaries, sizeof(entry->intermediaries));
|
||||
nq->conn_mgr_intermediariy_count = entry->intermediariy_count;
|
||||
}
|
||||
|
||||
static void cm_clear_nodeinfo(struct CONN_MGR* mgr, uint64_t node_id) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, node_id);
|
||||
if (!nq) return;
|
||||
nq->conn_mgr_type = CONN_TYPE_NONE;
|
||||
nq->conn_mgr_intermediariy_count = 0;
|
||||
}
|
||||
|
||||
int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id, uint32_t idle_timeout_ms,
|
||||
conn_mgr_connect_callback_t cb, void* cb_arg) {
|
||||
if (!mgr) return CONN_MGR_ERR_INTERNAL;
|
||||
struct ROUTE_BGP* bgp = mgr->instance->bgp;
|
||||
if (!bgp) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: BGP not initialized"); return CONN_MGR_ERR_INTERNAL; }
|
||||
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, node_id);
|
||||
if (!target) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx not found", (unsigned long long)node_id); return CONN_MGR_ERR_NOT_FOUND; }
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
|
||||
if (entry && entry->state == CONN_MGR_STATE_CONNECTED) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx already connected", (unsigned long long)node_id);
|
||||
return CONN_MGR_ERR_ALREADY_CONNECTED;
|
||||
}
|
||||
if (entry && entry->state == CONN_MGR_STATE_CONNECTING) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr_connect: node 0x%016llx already connecting, adding cb", (unsigned long long)node_id);
|
||||
struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
|
||||
if (cn) { cn->cb = cb; cn->arg = cb_arg; cn->next = entry->cb_list; entry->cb_list = cn; }
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
struct ETCP_CONN* existing = route_bgp_find_conn_for_node(bgp, node_id);
|
||||
if (existing && existing->peer_node_id == node_id && existing->links) {
|
||||
struct ETCP_LINK* l = existing->links;
|
||||
while (l) { if (l->link_status && l->initialized) break; l = l->next; }
|
||||
if (l) {
|
||||
entry = cm_ensure_entry(mgr, node_id);
|
||||
if (!entry) return CONN_MGR_ERR_INTERNAL;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
entry->conn_type = CONN_TYPE_DIRECT;
|
||||
entry->idle_timeout_ms = idle_timeout_ms;
|
||||
entry->alien = target->alien;
|
||||
entry->last_traffic_tb = get_time_tb();
|
||||
{ struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
|
||||
if (cn) { cn->cb = cb; cn->arg = cb_arg; entry->cb_list = cn; } }
|
||||
cm_update_nodeinfo(entry);
|
||||
cm_deliver_result(entry, CONN_MGR_OK);
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
}
|
||||
entry = cm_ensure_entry(mgr, node_id);
|
||||
if (!entry) return CONN_MGR_ERR_INTERNAL;
|
||||
entry->state = CONN_MGR_STATE_CONNECTING;
|
||||
entry->alien = target->alien;
|
||||
{ struct cm_cb_node* cn = u_calloc(1, sizeof(struct cm_cb_node));
|
||||
if (cn) { cn->cb = cb; cn->arg = cb_arg; cn->next = entry->cb_list; entry->cb_list = cn; } }
|
||||
entry->idle_timeout_ms = idle_timeout_ms;
|
||||
entry->last_traffic_tb = get_time_tb();
|
||||
entry->local_scan_state = CM_TRY_NONE;
|
||||
entry->main_connect_state = CM_TRY_NONE;
|
||||
entry->main.phase = 0;
|
||||
entry->main.timer = NULL;
|
||||
entry->main.request_id = 0;
|
||||
int has_our_local = cm_has_local_addr(bgp->local_node);
|
||||
int has_target_local = cm_has_local_addr(target);
|
||||
if (has_our_local && has_target_local) { entry->local_scan_state = CM_TRY_PENDING; cm_start_local_scan(entry); }
|
||||
entry->main_connect_state = CM_TRY_PENDING;
|
||||
cm_start_phase_direct(entry);
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id) {
|
||||
if (!mgr) return CONN_MGR_ERR_INTERNAL;
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
|
||||
if (!entry) return CONN_MGR_ERR_NOT_FOUND;
|
||||
struct CONN_MGR_DISCONNECT pkt;
|
||||
pkt.cmd = ETCP_ID_CONN_MGR;
|
||||
pkt.subcmd = CONN_MGR_SUBCMD_DISCONNECT;
|
||||
pkt.node_id = node_id;
|
||||
struct ll_entry* qe = queue_entry_new(sizeof(pkt));
|
||||
if (qe) { memcpy(qe->data, &pkt, sizeof(pkt)); etcp_route_send(mgr->instance, node_id, qe, 1); }
|
||||
cm_clear_nodeinfo(mgr, node_id);
|
||||
cm_entry_destroy(entry);
|
||||
entry->state = CONN_MGR_STATE_DISCONNECTED;
|
||||
entry->conn_type = CONN_TYPE_NONE;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: disconnected node 0x%016llx", (unsigned long long)node_id);
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms) {
|
||||
if (!mgr) return CONN_MGR_ERR_INTERNAL;
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
|
||||
if (!entry) return CONN_MGR_ERR_NOT_FOUND;
|
||||
entry->idle_timeout_ms = timeout_ms;
|
||||
if (entry->state == CONN_MGR_STATE_CONNECTED && timeout_ms > 0 && !entry->idle_timer)
|
||||
entry->idle_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle");
|
||||
if (timeout_ms == 0 && entry->idle_timer) { uasync_cancel_timeout(mgr->instance->ua, entry->idle_timer); entry->idle_timer = NULL; }
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id, uint8_t* out_state, uint8_t* out_conn_type) {
|
||||
if (!mgr || !out_state || !out_conn_type) return CONN_MGR_ERR_INTERNAL;
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
|
||||
if (!entry) { *out_state = CONN_MGR_STATE_DISCONNECTED; *out_conn_type = CONN_TYPE_NONE; return CONN_MGR_OK; }
|
||||
*out_state = entry->state;
|
||||
*out_conn_type = entry->conn_type;
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len) {
|
||||
if (!mgr || !nodeinfo_data || len < sizeof(struct NODEINFO)) return CONN_MGR_ERR_INTERNAL;
|
||||
struct ROUTE_BGP* bgp = mgr->instance->bgp;
|
||||
if (!bgp) return CONN_MGR_ERR_INTERNAL;
|
||||
const struct NODEINFO* ni = (const struct NODEINFO*)nodeinfo_data;
|
||||
uint64_t node_id = ni->node_id;
|
||||
struct NODEINFO_Q* existing = nodeinfo_find_by_id(bgp, node_id);
|
||||
if (existing) { DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: alien node 0x%016llx already exists, ignoring", (unsigned long long)node_id); return CONN_MGR_OK; }
|
||||
size_t dyn_sz = len - sizeof(struct NODEINFO);
|
||||
size_t data_sz = sizeof(struct NODEINFO_Q) - sizeof(struct ll_entry) + dyn_sz + 8;
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)queue_entry_new(data_sz);
|
||||
if (!nq) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: alien node alloc failed"); return CONN_MGR_ERR_INTERNAL; }
|
||||
memcpy(&nq->node, nodeinfo_data, len);
|
||||
nq->alien = 1;
|
||||
nq->dirty = 0;
|
||||
nq->last_ver = ni->ver;
|
||||
nq->connectivity.ping_req_time = 0;
|
||||
if (bgp->nodes) queue_data_put_with_index(bgp->nodes, &nq->ll);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: added alien node 0x%016llx", (unsigned long long)node_id);
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry) {
|
||||
if (!mgr || !entry) return -1;
|
||||
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, node_id);
|
||||
if (!e || e->state != CONN_MGR_STATE_CONNECTED) { queue_entry_free(entry); return -1; }
|
||||
e->last_traffic_tb = get_time_tb();
|
||||
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(mgr->instance, node_id, ETCP_ID_CONN_MGR);
|
||||
if (!rconn) { queue_entry_free(entry); return -1; }
|
||||
int ret = etcp_router_conn_send(rconn, entry->dgram, entry->len);
|
||||
queue_entry_free(entry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt) {
|
||||
if (!mgr || node_id == mgr->instance->node_id || rtt == 0) return;
|
||||
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
|
||||
if (mgr->best_candidates[i].node_id == node_id) {
|
||||
mgr->best_candidates[i].rtt = rtt;
|
||||
for (uint8_t j = i; j > 0 && mgr->best_candidates[j].rtt < mgr->best_candidates[j-1].rtt; j--) {
|
||||
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[j];
|
||||
mgr->best_candidates[j] = mgr->best_candidates[j-1];
|
||||
mgr->best_candidates[j-1] = tmp;
|
||||
}
|
||||
for (uint8_t j = i; j + 1 < mgr->best_candidate_count && mgr->best_candidates[j].rtt > mgr->best_candidates[j+1].rtt; j++) {
|
||||
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[j];
|
||||
mgr->best_candidates[j] = mgr->best_candidates[j+1];
|
||||
mgr->best_candidates[j+1] = tmp;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mgr->best_candidate_count < CONN_MGR_MAX_CANDIDATES) {
|
||||
mgr->best_candidates[mgr->best_candidate_count].node_id = node_id;
|
||||
mgr->best_candidates[mgr->best_candidate_count].rtt = rtt;
|
||||
mgr->best_candidate_count++;
|
||||
} else if (rtt < mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].rtt) {
|
||||
mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].node_id = node_id;
|
||||
mgr->best_candidates[CONN_MGR_MAX_CANDIDATES - 1].rtt = rtt;
|
||||
} else return;
|
||||
for (uint8_t i = mgr->best_candidate_count - 1; i > 0; i--) {
|
||||
if (mgr->best_candidates[i].rtt >= mgr->best_candidates[i-1].rtt) break;
|
||||
struct CONN_MGR_CANDIDATE tmp = mgr->best_candidates[i];
|
||||
mgr->best_candidates[i] = mgr->best_candidates[i-1];
|
||||
mgr->best_candidates[i-1] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void cm_idle_timer_cb(void* arg) {
|
||||
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
|
||||
struct CONN_MGR* mgr = entry->mgr;
|
||||
uint64_t now_tb = get_time_tb();
|
||||
if (entry->state != CONN_MGR_STATE_CONNECTED || entry->idle_timeout_ms == 0) return;
|
||||
uint64_t idle = now_tb - entry->last_traffic_tb;
|
||||
if (idle > (uint64_t)entry->idle_timeout_ms * 10) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: node 0x%016llx idle %llums, disconnecting",
|
||||
(unsigned long long)entry->node_id, (unsigned long long)(idle / 10));
|
||||
conn_mgr_disconnect_node(mgr, entry->node_id);
|
||||
return;
|
||||
}
|
||||
entry->idle_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_IDLE_CHECK_INTERVAL_TB, entry, cm_idle_timer_cb, "conn_mgr_idle");
|
||||
}
|
||||
|
||||
static int cm_nat_compatible(struct ETCP_SOCKET* our, uint8_t target_meta_type) {
|
||||
uint8_t tt = target_meta_type;
|
||||
if (tt == NAT_VERIFIED_DIRECT) tt = CFG_SERVER_TYPE_PUBLIC;
|
||||
else if (tt >= NAT_VERIFIED_UNKNOWN) tt = CFG_SERVER_TYPE_PUBLIC;
|
||||
uint8_t ot = our->type;
|
||||
if (ot == CFG_SERVER_TYPE_PUBLIC) return 1;
|
||||
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_TYPE_EIM && tt == CFG_SERVER_TYPE_PUBLIC) return 1;
|
||||
if (ot == CFG_SERVER_TYPE_NAT && our->nat_type == NAT_TYPE_EIM && tt == CFG_SERVER_TYPE_NAT) return 1;
|
||||
if (ot == CFG_SERVER_TYPE_PRIVATE && tt == CFG_SERVER_TYPE_PRIVATE) return 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint16_t cm_get_node_min_rtt(struct NODEINFO_Q* nq) {
|
||||
uint16_t rtt = 0xFFFF;
|
||||
if (nq->connectivity.interface_status == PROBE_RESULT_REACHABLE && nq->connectivity.interface_min_rtt < rtt)
|
||||
rtt = nq->connectivity.interface_min_rtt;
|
||||
if (nq->connectivity.nat_status == PROBE_RESULT_REACHABLE && nq->connectivity.nat_min_rtt < rtt)
|
||||
rtt = nq->connectivity.nat_min_rtt;
|
||||
if (nq->connectivity.real_status == PROBE_RESULT_REACHABLE && nq->connectivity.real_min_rtt < rtt)
|
||||
rtt = nq->connectivity.real_min_rtt;
|
||||
return rtt;
|
||||
}
|
||||
|
||||
static uint64_t cm_get_node_max_probe_time(struct NODEINFO_Q* nq) {
|
||||
uint64_t t = nq->connectivity.interface_probe_time;
|
||||
if (nq->connectivity.nat_probe_time > t) t = nq->connectivity.nat_probe_time;
|
||||
if (nq->connectivity.real_probe_time > t) t = nq->connectivity.real_probe_time;
|
||||
return t;
|
||||
}
|
||||
|
||||
static int cm_is_rtt_fresh(struct NODEINFO_Q* nq, uint64_t now_tb) {
|
||||
uint64_t last = cm_get_node_max_probe_time(nq);
|
||||
return (last > 0 && (now_tb - last) < (uint64_t)(CONN_MGR_CANDIDATE_CACHE_MS * 10));
|
||||
}
|
||||
|
||||
static int cm_has_direct_ip(struct NODEINFO_Q* nq) {
|
||||
if (!nq) return 0;
|
||||
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
|
||||
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
|
||||
int addr_count = get_node_v4_addrs(nq, &addrs);
|
||||
int meta_count = get_node_v4_sockets_meta(nq, &metas);
|
||||
for (int i = 0; i < addr_count; i++) {
|
||||
if (addrs[i].type == ADDR_TYPE_REAL || addrs[i].type == ADDR_TYPE_NAT) {
|
||||
for (int j = 0; j < meta_count; j++) {
|
||||
if (metas[j].id == addrs[i].socket_id &&
|
||||
(metas[j].type == CFG_SERVER_TYPE_PUBLIC || metas[j].type == NAT_VERIFIED_EIM || metas[j].type == NAT_VERIFIED_DIRECT))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cm_has_local_addr(struct NODEINFO_Q* nq) {
|
||||
if (!nq) return 0;
|
||||
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
|
||||
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
|
||||
int addr_count = get_node_v4_addrs(nq, &addrs);
|
||||
int meta_count = get_node_v4_sockets_meta(nq, &metas);
|
||||
for (int i = 0; i < addr_count; i++) {
|
||||
if (addrs[i].type == ADDR_TYPE_INTERFACE) {
|
||||
for (int j = 0; j < meta_count; j++) {
|
||||
if (metas[j].id == addrs[i].socket_id && metas[j].type == CFG_SERVER_TYPE_PRIVATE)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce,
|
||||
const uint8_t* resp_data, size_t resp_data_len);
|
||||
static void cm_direct_timeout_cb(void* arg);
|
||||
|
||||
struct cm_ping_ctx {
|
||||
struct CONN_MGR_ENTRY* entry;
|
||||
struct sockaddr_storage addr;
|
||||
struct ETCP_SOCKET* sock;
|
||||
uint8_t phase; /* 0=local_scan, 1=direct */
|
||||
uint8_t attempt;
|
||||
};
|
||||
|
||||
static void cm_start_local_scan(struct CONN_MGR_ENTRY* entry) {
|
||||
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
|
||||
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
|
||||
if (!target) { entry->local_scan_state = CM_TRY_FAILED; return; }
|
||||
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
|
||||
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
|
||||
int addr_count = get_node_v4_addrs(target, &addrs);
|
||||
int meta_count = get_node_v4_sockets_meta(target, &metas);
|
||||
for (int i = 0; i < addr_count; i++) {
|
||||
if (addrs[i].type != ADDR_TYPE_INTERFACE) continue;
|
||||
for (int j = 0; j < meta_count; j++) {
|
||||
if (metas[j].id != addrs[i].socket_id || metas[j].type != CFG_SERVER_TYPE_PRIVATE) continue;
|
||||
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
|
||||
while (s) {
|
||||
if (s->type == CFG_SERVER_TYPE_PRIVATE && s->local_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
memcpy(&sin.sin_addr.s_addr, addrs[i].addr, 4);
|
||||
sin.sin_port = htons(addrs[i].port);
|
||||
struct sockaddr_storage sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
memcpy(&sa, &sin, sizeof(sin));
|
||||
struct cm_ping_ctx* ctx = u_calloc(1, sizeof(struct cm_ping_ctx));
|
||||
if (!ctx) continue;
|
||||
ctx->entry = entry;
|
||||
ctx->addr = sa;
|
||||
ctx->sock = s;
|
||||
ctx->phase = 0;
|
||||
ctx->attempt = 0;
|
||||
etcp_send_ping_to_socket(entry->mgr->instance, s, target->node.public_key, &sa,
|
||||
CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0);
|
||||
return;
|
||||
}
|
||||
s = s->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
entry->local_scan_state = CM_TRY_FAILED;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan no candidates for 0x%016llx", (unsigned long long)entry->node_id);
|
||||
}
|
||||
|
||||
static void cm_start_phase_direct(struct CONN_MGR_ENTRY* entry) {
|
||||
if (entry->main_connect_state == CM_TRY_OK) return;
|
||||
if (entry->local_scan_state == CM_TRY_OK) return;
|
||||
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
|
||||
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
|
||||
if (!target) { cm_deliver_result(entry, CONN_MGR_ERR_NOT_FOUND); return; }
|
||||
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
|
||||
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
|
||||
int addr_count = get_node_v4_addrs(target, &addrs);
|
||||
int meta_count = get_node_v4_sockets_meta(target, &metas);
|
||||
uint8_t priority_order[] = { ADDR_TYPE_REAL, ADDR_TYPE_NAT, ADDR_TYPE_INTERFACE };
|
||||
for (int pri = 0; pri < 3; pri++) {
|
||||
for (int i = 0; i < addr_count; i++) {
|
||||
if (addrs[i].type != priority_order[pri]) continue;
|
||||
for (int j = 0; j < meta_count; j++) {
|
||||
if (metas[j].id != addrs[i].socket_id) continue;
|
||||
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
|
||||
while (s) {
|
||||
int compat = cm_nat_compatible(s, metas[j].type);
|
||||
if (compat && s->local_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
memcpy(&sin.sin_addr.s_addr, addrs[i].addr, 4);
|
||||
sin.sin_port = htons(addrs[i].port);
|
||||
struct sockaddr_storage sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
memcpy(&sa, &sin, sizeof(sin));
|
||||
struct ETCP_CONN* conn = etcp_connection_create(entry->mgr->instance, NULL);
|
||||
if (conn) {
|
||||
etcp_conn_set_ready_cbk(conn, cm_direct_ready_cb, entry);
|
||||
sc_set_peer_public_key(&conn->crypto_ctx, target->node.public_key, 0);
|
||||
struct ETCP_LINK* link = etcp_link_new(conn, s, &sa, 0);
|
||||
if (link) {
|
||||
entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua,
|
||||
CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS * 10, entry, cm_direct_timeout_cb, "conn_mgr_direct");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 1 (direct) started INIT to 0x%016llx",
|
||||
(unsigned long long)entry->node_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
s = s->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 1 (direct) no compatible pairs for 0x%016llx",
|
||||
(unsigned long long)entry->node_id);
|
||||
int has_our = cm_has_direct_ip(bgp->local_node);
|
||||
int has_target = cm_has_direct_ip(target);
|
||||
if (has_our && !has_target) { cm_start_phase_reverse(entry); return; }
|
||||
cm_start_phase_indirect(entry);
|
||||
}
|
||||
|
||||
static void cm_direct_timeout_cb(void* arg) {
|
||||
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
|
||||
entry->main.timer = NULL;
|
||||
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
|
||||
struct NODEINFO_Q* target = nodeinfo_find_by_id(bgp, entry->node_id);
|
||||
int has_our = cm_has_direct_ip(bgp->local_node);
|
||||
int has_target = target ? cm_has_direct_ip(target) : 0;
|
||||
if (entry->local_scan_state != CM_TRY_OK) {
|
||||
if (has_our && !has_target) { cm_start_phase_reverse(entry); return; }
|
||||
cm_start_phase_indirect(entry);
|
||||
return;
|
||||
}
|
||||
if (entry->local_scan_state == CM_TRY_FAILED && entry->main_connect_state == CM_TRY_FAILED)
|
||||
cm_deliver_result(entry, CONN_MGR_ERR_UNREACHABLE);
|
||||
}
|
||||
|
||||
static void cm_direct_ready_cb(struct ETCP_CONN* conn, void* arg) {
|
||||
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
|
||||
if (!conn || conn->peer_node_id != entry->node_id) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct ready cb peer mismatch conn=%p peer=0x%llx entry=0x%llx",
|
||||
(void*)conn, (unsigned long long)conn->peer_node_id, (unsigned long long)entry->node_id);
|
||||
return;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: direct ready for 0x%016llx", (unsigned long long)entry->node_id);
|
||||
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
|
||||
entry->main_connect_state = CM_TRY_OK;
|
||||
entry->conn_type = CONN_TYPE_DIRECT;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
cm_update_nodeinfo(entry);
|
||||
cm_deliver_result(entry, CONN_MGR_OK);
|
||||
}
|
||||
|
||||
static void cm_ping_cb(int success, uint16_t rtt, void* arg, uint64_t nonce,
|
||||
const uint8_t* resp_data, size_t resp_data_len) {
|
||||
struct cm_ping_ctx* ctx = (struct cm_ping_ctx*)arg;
|
||||
struct CONN_MGR_ENTRY* entry = ctx->entry;
|
||||
(void)nonce; (void)resp_data; (void)resp_data_len;
|
||||
if (entry->main_connect_state == CM_TRY_OK) { u_free(ctx); return; }
|
||||
if (success && ctx->phase == 0) {
|
||||
entry->local_scan_state = CM_TRY_OK;
|
||||
entry->conn_type = CONN_TYPE_DIRECT;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: local scan OK for 0x%016llx rtt=%u", (unsigned long long)entry->node_id, rtt);
|
||||
cm_update_nodeinfo(entry);
|
||||
cm_deliver_result(entry, CONN_MGR_OK);
|
||||
u_free(ctx);
|
||||
return;
|
||||
}
|
||||
ctx->attempt++;
|
||||
if (ctx->attempt < CONN_MGR_LOCAL_SCAN_ATTEMPTS && ctx->phase == 0) {
|
||||
etcp_send_ping_to_socket(entry->mgr->instance, ctx->sock,
|
||||
nodeinfo_find_by_id(entry->mgr->instance->bgp, entry->node_id)->node.public_key,
|
||||
&ctx->addr, CONN_MGR_LOCAL_SCAN_TIMEOUT_MS, cm_ping_cb, ctx, NULL, 0);
|
||||
return;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: %s failed for 0x%016llx after %d attempts",
|
||||
ctx->phase == 0 ? "local scan" : "direct", (unsigned long long)entry->node_id, ctx->attempt);
|
||||
if (ctx->phase == 0) entry->local_scan_state = CM_TRY_FAILED;
|
||||
u_free(ctx);
|
||||
}
|
||||
|
||||
static void cm_start_phase_reverse(struct CONN_MGR_ENTRY* entry) {
|
||||
struct ROUTE_BGP* bgp = entry->mgr->instance->bgp;
|
||||
struct NODEINFO_Q* local = bgp->local_node;
|
||||
if (!local) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
|
||||
uint32_t req_id = ++entry->mgr->next_request_id;
|
||||
entry->main.request_id = req_id;
|
||||
entry->main.phase = 2;
|
||||
const struct NODEINFO_IPV4_ADDR* addrs = NULL;
|
||||
const struct NODEINFO_IPV4_SOCKET_META* metas = NULL;
|
||||
int addr_count = get_node_v4_addrs(local, &addrs);
|
||||
int meta_count = get_node_v4_sockets_meta(local, &metas);
|
||||
uint8_t direct_count = 0;
|
||||
struct { uint8_t type; uint8_t ip[4]; uint16_t port; uint8_t socket_id; } out_addrs[8];
|
||||
for (int i = 0; i < addr_count && direct_count < 8; i++) {
|
||||
if (addrs[i].type != ADDR_TYPE_REAL && addrs[i].type != ADDR_TYPE_NAT) continue;
|
||||
for (int j = 0; j < meta_count; j++) {
|
||||
if (metas[j].id == addrs[i].socket_id &&
|
||||
(metas[j].type == CFG_SERVER_TYPE_PUBLIC || metas[j].type == NAT_VERIFIED_EIM || metas[j].type == NAT_VERIFIED_DIRECT)) {
|
||||
out_addrs[direct_count].type = addrs[i].type;
|
||||
memcpy(out_addrs[direct_count].ip, addrs[i].addr, 4);
|
||||
out_addrs[direct_count].port = htons(addrs[i].port);
|
||||
out_addrs[direct_count].socket_id = addrs[i].socket_id;
|
||||
direct_count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (direct_count == 0) { cm_start_phase_indirect(entry); return; }
|
||||
size_t pkt_size = CONN_MGR_DIRECT_REQ_HDR_SIZE + (size_t)direct_count * 8;
|
||||
uint8_t* pkt = u_malloc(pkt_size);
|
||||
if (!pkt) { cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
|
||||
struct CONN_MGR_DIRECT_REQ* req = (struct CONN_MGR_DIRECT_REQ*)pkt;
|
||||
req->cmd = ETCP_ID_CONN_MGR;
|
||||
req->subcmd = CONN_MGR_SUBCMD_DIRECT_REQ;
|
||||
req->request_id = req_id;
|
||||
req->addr_count = direct_count;
|
||||
uint8_t* p = pkt + CONN_MGR_DIRECT_REQ_HDR_SIZE;
|
||||
for (uint8_t i = 0; i < direct_count; i++) {
|
||||
*p++ = out_addrs[i].type;
|
||||
memcpy(p, out_addrs[i].ip, 4); p += 4;
|
||||
memcpy(p, &out_addrs[i].port, 2); p += 2;
|
||||
*p++ = out_addrs[i].socket_id;
|
||||
}
|
||||
struct ll_entry* qe = queue_entry_new(pkt_size);
|
||||
if (!qe) { u_free(pkt); cm_deliver_result(entry, CONN_MGR_ERR_INTERNAL); return; }
|
||||
memcpy(qe->data, pkt, pkt_size);
|
||||
qe->len = (uint16_t)pkt_size;
|
||||
u_free(pkt);
|
||||
etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1);
|
||||
|
||||
struct cm_reverse_pending* rp = u_calloc(1, sizeof(struct cm_reverse_pending));
|
||||
if (rp) { rp->request_id = req_id; rp->entry = entry; rp->next = entry->mgr->reverse_pending;
|
||||
entry->mgr->reverse_pending = rp; }
|
||||
entry->main.timer = uasync_set_timeout(entry->mgr->instance->ua, CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS * 10,
|
||||
entry, cm_reverse_timeout_cb, "conn_mgr_reverse");
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 2 (reverse) sent DIRECT_REQ to 0x%016llx with %u addrs",
|
||||
(unsigned long long)entry->node_id, direct_count);
|
||||
}
|
||||
|
||||
static void cm_reverse_timeout_cb(void* arg) {
|
||||
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
|
||||
entry->main.timer = NULL;
|
||||
struct cm_reverse_pending** pp = &entry->mgr->reverse_pending;
|
||||
while (*pp) { if ((*pp)->entry == entry) { struct cm_reverse_pending* rp = *pp; *pp = rp->next; u_free(rp); break; } pp = &(*pp)->next; }
|
||||
cm_start_phase_indirect(entry);
|
||||
}
|
||||
|
||||
static void cm_reverse_ready_cb(struct ETCP_CONN* conn, void* arg) {
|
||||
struct cm_reverse_pending* rp = (struct cm_reverse_pending*)arg;
|
||||
struct CONN_MGR_ENTRY* entry = rp->entry;
|
||||
if (!conn || conn->peer_node_id != entry->node_id) { u_free(rp); return; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: reverse ready for 0x%016llx", (unsigned long long)entry->node_id);
|
||||
if (entry->main.timer) { uasync_cancel_timeout(entry->mgr->instance->ua, entry->main.timer); entry->main.timer = NULL; }
|
||||
struct cm_reverse_pending** pp = &entry->mgr->reverse_pending;
|
||||
while (*pp) { if (*pp == rp) { *pp = rp->next; break; } pp = &(*pp)->next; }
|
||||
entry->main_connect_state = CM_TRY_OK;
|
||||
entry->conn_type = CONN_TYPE_REVERSE;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
cm_update_nodeinfo(entry);
|
||||
cm_deliver_result(entry, CONN_MGR_OK);
|
||||
u_free(rp);
|
||||
}
|
||||
|
||||
static int cm_start_phase_indirect(struct CONN_MGR_ENTRY* entry) {
|
||||
struct CONN_MGR* mgr = entry->mgr;
|
||||
if (mgr->best_candidate_count == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 (indirect) no candidates for 0x%016llx",
|
||||
(unsigned long long)entry->node_id);
|
||||
return CONN_MGR_ERR_UNREACHABLE;
|
||||
}
|
||||
uint32_t req_id = ++mgr->next_request_id;
|
||||
entry->main.request_id = req_id;
|
||||
entry->main.phase = 3;
|
||||
struct CONN_MGR_INTERM_EXCHANGE_REQ req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.cmd = ETCP_ID_CONN_MGR;
|
||||
req.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ;
|
||||
req.request_id = req_id;
|
||||
req.candidate_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count;
|
||||
for (uint8_t i = 0; i < req.candidate_count; i++)
|
||||
req.candidates[i] = mgr->best_candidates[i];
|
||||
struct ll_entry* qe = queue_entry_new(sizeof(req));
|
||||
if (!qe) return CONN_MGR_ERR_INTERNAL;
|
||||
memcpy(qe->data, &req, sizeof(req));
|
||||
etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1);
|
||||
entry->main.timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS * 10,
|
||||
entry, cm_exchange_timeout_cb, "conn_mgr_interm_exch");
|
||||
{
|
||||
struct cm_exchange_pending* ep = u_calloc(1, sizeof(struct cm_exchange_pending));
|
||||
if (ep) { ep->request_id = req_id; ep->entry = entry; ep->timeout_timer = entry->main.timer;
|
||||
ep->next = mgr->exchange_pending; mgr->exchange_pending = ep; }
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: phase 3 (indirect) sent EXCHANGE_REQ to 0x%016llx with %u candidates",
|
||||
(unsigned long long)entry->node_id, req.candidate_count);
|
||||
return CONN_MGR_OK;
|
||||
}
|
||||
|
||||
static void cm_exchange_timeout_cb(void* arg) {
|
||||
struct CONN_MGR_ENTRY* entry = (struct CONN_MGR_ENTRY*)arg;
|
||||
entry->main.timer = NULL;
|
||||
struct cm_exchange_pending** pp = &entry->mgr->exchange_pending;
|
||||
while (*pp) { if ((*pp)->entry == entry) { struct cm_exchange_pending* ep = *pp; *pp = ep->next; u_free(ep); break; } pp = &(*pp)->next; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: exchange timeout for 0x%016llx", (unsigned long long)entry->node_id);
|
||||
cm_deliver_result(entry, CONN_MGR_ERR_TIMEOUT);
|
||||
}
|
||||
|
||||
static void cm_compute_intermediaries(struct CONN_MGR_ENTRY* entry, struct CONN_MGR_INTERM_EXCHANGE_RESP* resp) {
|
||||
struct { uint64_t node_id; uint64_t total_rtt; } all[8];
|
||||
uint8_t count = 0;
|
||||
|
||||
for (uint8_t i = 0; i < resp->your_count && count < 8; i++) {
|
||||
uint64_t id = resp->your_candidates[i].node_id;
|
||||
uint16_t our_rtt = 0;
|
||||
for (uint8_t j = 0; j < CONN_MGR_MAX_CANDIDATES; j++) {
|
||||
if (entry->mgr->best_candidates[j].node_id == id) { our_rtt = entry->mgr->best_candidates[j].rtt; break; }
|
||||
}
|
||||
all[count].node_id = id;
|
||||
all[count].total_rtt = (uint64_t)our_rtt + resp->your_candidates[i].rtt;
|
||||
count++;
|
||||
}
|
||||
for (uint8_t i = 0; i < resp->my_count && count < 8; i++) {
|
||||
uint64_t id = resp->my_candidates[i].node_id;
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(entry->mgr->instance->bgp, id);
|
||||
uint16_t our_rtt = nq ? cm_get_node_min_rtt(nq) : 0;
|
||||
if (our_rtt == 0xFFFF) our_rtt = 0;
|
||||
all[count].node_id = id;
|
||||
all[count].total_rtt = (uint64_t)our_rtt + resp->my_candidates[i].rtt;
|
||||
count++;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < count; i++)
|
||||
for (uint8_t j = i + 1; j < count; j++)
|
||||
if (all[j].total_rtt < all[i].total_rtt) { typeof(all[0]) t = all[i]; all[i] = all[j]; all[j] = t; }
|
||||
|
||||
uint8_t sel = count > CONN_MGR_MAX_INTERMEDIARIES ? CONN_MGR_MAX_INTERMEDIARIES : count;
|
||||
for (uint8_t i = 0; i < sel; i++) entry->intermediaries[i] = all[i].node_id;
|
||||
entry->intermediariy_count = sel;
|
||||
entry->rr_idx = 0;
|
||||
|
||||
struct CONN_MGR_INTERM_SELECTED pkt;
|
||||
memset(&pkt, 0, sizeof(pkt));
|
||||
pkt.cmd = ETCP_ID_CONN_MGR;
|
||||
pkt.subcmd = CONN_MGR_SUBCMD_INTERM_SELECTED;
|
||||
pkt.request_id = 0;
|
||||
pkt.count = sel;
|
||||
for (uint8_t i = 0; i < sel; i++) {
|
||||
pkt.selected[i].node_id = all[i].node_id;
|
||||
pkt.selected[i].rtt = 0;
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(entry->mgr->instance->bgp, all[i].node_id);
|
||||
if (nq) pkt.selected[i].rtt = cm_get_node_min_rtt(nq);
|
||||
}
|
||||
struct ll_entry* qe = queue_entry_new(sizeof(pkt));
|
||||
if (qe) { memcpy(qe->data, &pkt, sizeof(pkt)); etcp_route_send(entry->mgr->instance, entry->node_id, qe, 1); }
|
||||
|
||||
entry->conn_type = CONN_TYPE_INDIRECT;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: indirect OK for 0x%016llx via %u intermediaries",
|
||||
(unsigned long long)entry->node_id, sel);
|
||||
cm_update_nodeinfo(entry);
|
||||
cm_deliver_result(entry, CONN_MGR_OK);
|
||||
}
|
||||
|
||||
static void cm_handle_interm_exchange_resp(struct CONN_MGR* mgr, const uint8_t* data, size_t len) {
|
||||
const struct CONN_MGR_INTERM_EXCHANGE_RESP* resp = (const struct CONN_MGR_INTERM_EXCHANGE_RESP*)data;
|
||||
size_t min_size = offsetof(struct CONN_MGR_INTERM_EXCHANGE_RESP, your_candidates);
|
||||
if (len < min_size) return;
|
||||
struct cm_exchange_pending* ep = mgr->exchange_pending;
|
||||
while (ep) { if (ep->request_id == resp->request_id) break; ep = ep->next; }
|
||||
if (!ep) return;
|
||||
|
||||
if (ep->entry->main.timer) { uasync_cancel_timeout(mgr->instance->ua, ep->entry->main.timer); ep->entry->main.timer = NULL; }
|
||||
|
||||
memcpy(&ep->cached_resp, resp, len < sizeof(ep->cached_resp) ? len : sizeof(ep->cached_resp));
|
||||
ep->resp_received = 1;
|
||||
ep->probes_done = 0;
|
||||
|
||||
uint8_t need_probe = 0;
|
||||
uint64_t now_tb = get_time_tb();
|
||||
for (uint8_t i = 0; i < ep->cached_resp.my_count && i < 4; i++) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, ep->cached_resp.my_candidates[i].node_id);
|
||||
if (!nq || !cm_is_rtt_fresh(nq, now_tb)) { need_probe++; route_connectivity_probe_node(mgr->instance, nq); }
|
||||
}
|
||||
if (need_probe == 0) { ep->probes_done = 1; cm_compute_intermediaries(ep->entry, &ep->cached_resp);
|
||||
struct cm_exchange_pending** pp = &mgr->exchange_pending;
|
||||
while (*pp) { if (*pp == ep) { *pp = ep->next; break; } pp = &(*pp)->next; }
|
||||
u_free(ep); return; }
|
||||
uasync_set_timeout(mgr->instance->ua, 2000, ep, NULL, "cm_exch_probe");
|
||||
}
|
||||
|
||||
static void cm_handle_interm_selected(struct CONN_MGR* mgr, const uint8_t* data, size_t len) {
|
||||
const struct CONN_MGR_INTERM_SELECTED* sel = (const struct CONN_MGR_INTERM_SELECTED*)data;
|
||||
if (len < offsetof(struct CONN_MGR_INTERM_SELECTED, selected)) return;
|
||||
struct CONN_MGR_ENTRY* entry = NULL;
|
||||
for (size_t i = 0; i < mgr->entry_count; i++)
|
||||
if (mgr->entries[i].state == CONN_MGR_STATE_CONNECTING) { entry = &mgr->entries[i]; break; }
|
||||
if (!entry) return;
|
||||
uint8_t cnt = sel->count > CONN_MGR_MAX_INTERMEDIARIES ? CONN_MGR_MAX_INTERMEDIARIES : sel->count;
|
||||
for (uint8_t i = 0; i < cnt; i++) entry->intermediaries[i] = sel->selected[i].node_id;
|
||||
entry->intermediariy_count = cnt;
|
||||
entry->rr_idx = 0;
|
||||
entry->conn_type = CONN_TYPE_INDIRECT;
|
||||
entry->state = CONN_MGR_STATE_CONNECTED;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: got INTERM_SELECTED count=%u", cnt);
|
||||
}
|
||||
|
||||
static void conn_mgr_router_recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!entry || !entry->dgram || entry->len < 3) return;
|
||||
uint8_t* dgram = entry->dgram + 1; // skip svc_id byte from router
|
||||
size_t len = entry->len - 1;
|
||||
uint8_t subcmd = dgram[1];
|
||||
struct CONN_MGR* mgr = conn->instance->conn_mgr;
|
||||
|
||||
if (mgr) {
|
||||
struct CONN_MGR_ENTRY* e = cm_find_entry(mgr, conn->peer_node_id);
|
||||
if (e && e->state == CONN_MGR_STATE_CONNECTED) e->last_traffic_tb = get_time_tb();
|
||||
}
|
||||
|
||||
switch (subcmd) {
|
||||
case CONN_MGR_SUBCMD_DIRECT_REQ:
|
||||
cm_handle_direct_req(conn, dgram, len);
|
||||
break;
|
||||
case CONN_MGR_SUBCMD_DIRECT_RESP:
|
||||
break;
|
||||
case CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ:
|
||||
if (len >= CONN_MGR_INTERM_EXCHANGE_REQ_SIZE)
|
||||
cm_handle_interm_exchange_req(conn, (struct CONN_MGR_INTERM_EXCHANGE_REQ*)dgram);
|
||||
break;
|
||||
case CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP:
|
||||
if (mgr) cm_handle_interm_exchange_resp(mgr, dgram, len);
|
||||
break;
|
||||
case CONN_MGR_SUBCMD_INTERM_SELECTED:
|
||||
if (mgr) cm_handle_interm_selected(mgr, dgram, len);
|
||||
break;
|
||||
case CONN_MGR_SUBCMD_DISCONNECT:
|
||||
if (len >= CONN_MGR_DISCONNECT_SIZE && mgr) {
|
||||
struct CONN_MGR_DISCONNECT* disc = (struct CONN_MGR_DISCONNECT*)dgram;
|
||||
cm_handle_disconnect(mgr, disc->node_id);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void cm_handle_direct_req(struct ETCP_CONN* conn, const uint8_t* data, size_t len) {
|
||||
struct CONN_MGR* mgr = conn->instance->conn_mgr;
|
||||
if (!mgr) return;
|
||||
struct CONN_MGR_DIRECT_REQ* req = (struct CONN_MGR_DIRECT_REQ*)data;
|
||||
size_t expected = CONN_MGR_DIRECT_REQ_HDR_SIZE + (size_t)req->addr_count * 8;
|
||||
if (len < expected) { DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "conn_mgr: DIRECT_REQ too short"); return; }
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: got DIRECT_REQ from 0x%016llx with %u addrs",
|
||||
(unsigned long long)conn->peer_node_id, req->addr_count);
|
||||
uint8_t* p = (uint8_t*)data + CONN_MGR_DIRECT_REQ_HDR_SIZE;
|
||||
uint64_t src_node_id = conn->peer_node_id;
|
||||
for (uint8_t i = 0; i < req->addr_count; i++) {
|
||||
uint8_t type = *p++;
|
||||
uint8_t ip[4]; memcpy(ip, p, 4); p += 4;
|
||||
uint16_t port; memcpy(&port, p, 2); p += 2;
|
||||
uint8_t sock_id = *p++;
|
||||
(void)sock_id; (void)type;
|
||||
struct ETCP_SOCKET* s = conn->instance->etcp_sockets;
|
||||
while (s) {
|
||||
if (s->type == CFG_SERVER_TYPE_PUBLIC && s->local_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET;
|
||||
memcpy(&sin.sin_addr.s_addr, ip, 4); sin.sin_port = port;
|
||||
struct sockaddr_storage sa;
|
||||
memset(&sa, 0, sizeof(sa)); memcpy(&sa, &sin, sizeof(sin));
|
||||
struct ETCP_CONN* new_conn = etcp_connection_create(conn->instance, NULL);
|
||||
if (new_conn) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(conn->instance->bgp, src_node_id);
|
||||
if (nq) sc_set_peer_public_key(&new_conn->crypto_ctx, nq->node.public_key, 0);
|
||||
struct cm_reverse_pending* rp = u_calloc(1, sizeof(struct cm_reverse_pending));
|
||||
if (rp) { rp->request_id = req->request_id; rp->entry = NULL; rp->next = mgr->reverse_pending;
|
||||
etcp_conn_set_ready_cbk(new_conn, cm_reverse_ready_cb, rp); mgr->reverse_pending = rp; }
|
||||
etcp_link_new(new_conn, s, &sa, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
s = s->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cm_handle_interm_exchange_req(struct ETCP_CONN* conn,
|
||||
struct CONN_MGR_INTERM_EXCHANGE_REQ* req) {
|
||||
struct CONN_MGR* mgr = conn->instance->conn_mgr;
|
||||
if (!mgr) return;
|
||||
uint64_t now_tb = get_time_tb();
|
||||
for (uint8_t i = 0; i < req->candidate_count && i < 4; i++) {
|
||||
uint64_t cand_id = req->candidates[i].node_id;
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, cand_id);
|
||||
if (nq && cm_is_rtt_fresh(nq, now_tb)) continue;
|
||||
route_connectivity_probe_node(mgr->instance, nq);
|
||||
}
|
||||
struct CONN_MGR_INTERM_EXCHANGE_RESP resp;
|
||||
memset(&resp, 0, sizeof(resp));
|
||||
resp.cmd = ETCP_ID_CONN_MGR;
|
||||
resp.subcmd = CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP;
|
||||
resp.request_id = req->request_id;
|
||||
resp.my_count = mgr->best_candidate_count > 4 ? 4 : mgr->best_candidate_count;
|
||||
for (uint8_t i = 0; i < resp.my_count; i++)
|
||||
resp.my_candidates[i] = mgr->best_candidates[i];
|
||||
resp.your_count = req->candidate_count > 4 ? 4 : req->candidate_count;
|
||||
for (uint8_t i = 0; i < resp.your_count; i++) {
|
||||
resp.your_candidates[i].node_id = req->candidates[i].node_id;
|
||||
struct NODEINFO_Q* nq2 = nodeinfo_find_by_id(mgr->instance->bgp, req->candidates[i].node_id);
|
||||
uint16_t rtt = nq2 ? cm_get_node_min_rtt(nq2) : 0;
|
||||
resp.your_candidates[i].rtt = rtt;
|
||||
}
|
||||
size_t resp_size = offsetof(struct CONN_MGR_INTERM_EXCHANGE_RESP, your_candidates) +
|
||||
(size_t)resp.your_count * sizeof(struct CONN_MGR_CANDIDATE);
|
||||
struct ll_entry* qe = queue_entry_new(resp_size);
|
||||
if (qe) {
|
||||
memcpy(qe->data, &resp, resp_size);
|
||||
etcp_route_send(mgr->instance, conn->peer_node_id, qe, 1);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: sent EXCHANGE_RESP to 0x%016llx my=%u your=%u",
|
||||
(unsigned long long)conn->peer_node_id, resp.my_count, resp.your_count);
|
||||
}
|
||||
|
||||
static void cm_handle_disconnect(struct CONN_MGR* mgr, uint64_t node_id) {
|
||||
struct CONN_MGR_ENTRY* entry = cm_find_entry(mgr, node_id);
|
||||
if (!entry) return;
|
||||
cm_clear_nodeinfo(mgr, node_id);
|
||||
cm_entry_destroy(entry);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: remote disconnect from 0x%016llx", (unsigned long long)node_id);
|
||||
}
|
||||
|
||||
static void cm_bg_ping_timer_cb(void* arg) {
|
||||
struct CONN_MGR* mgr = (struct CONN_MGR*)arg;
|
||||
struct ROUTE_BGP* bgp = mgr->instance->bgp;
|
||||
if (!bgp || !bgp->nodes) {
|
||||
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
|
||||
return;
|
||||
}
|
||||
uint64_t now_tb = get_time_tb();
|
||||
struct ll_entry* e = bgp->nodes->head;
|
||||
size_t total = 0;
|
||||
while (e) { total++; e = e->next; }
|
||||
if (total == 0) {
|
||||
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
|
||||
return;
|
||||
}
|
||||
if (mgr->bg_ping_cursor >= total) {
|
||||
mgr->bg_ping_cursor = 0;
|
||||
if ((now_tb - mgr->bg_ping_cycle_start_tb) < CONN_MGR_BG_PING_CYCLE_MIN_TB) {
|
||||
uint64_t delay = CONN_MGR_BG_PING_CYCLE_MIN_TB - (now_tb - mgr->bg_ping_cycle_start_tb);
|
||||
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, (int)delay, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
|
||||
return;
|
||||
}
|
||||
mgr->bg_ping_cycle_start_tb = now_tb;
|
||||
}
|
||||
size_t cursor = 0;
|
||||
e = bgp->nodes->head;
|
||||
while (e && cursor < mgr->bg_ping_cursor) { cursor++; e = e->next; }
|
||||
if (e) {
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
|
||||
uint64_t node_id = nq->node.node_id;
|
||||
if (node_id != mgr->instance->node_id) {
|
||||
struct ETCP_CONN* dconn = route_bgp_find_conn_for_node(bgp, node_id);
|
||||
int has_active = 0;
|
||||
if (dconn && dconn->links) {
|
||||
struct ETCP_LINK* l = dconn->links;
|
||||
while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; }
|
||||
}
|
||||
if (!has_active) {
|
||||
etcp_send_ping(mgr->instance, nq->node.public_key, NULL, CONN_PROBE_TIMEOUT_MS,
|
||||
NULL, NULL, NULL, 0);
|
||||
}
|
||||
}
|
||||
mgr->bg_ping_cursor++;
|
||||
}
|
||||
mgr->bg_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_BG_PING_INTERVAL_TB, mgr, cm_bg_ping_timer_cb, "conn_mgr_bg_ping");
|
||||
}
|
||||
|
||||
static void cm_candidate_ping_timer_cb(void* arg) {
|
||||
struct CONN_MGR* mgr = (struct CONN_MGR*)arg;
|
||||
uint64_t now_tb = get_time_tb();
|
||||
for (uint8_t i = 0; i < mgr->best_candidate_count; i++) {
|
||||
uint64_t cand_id = mgr->best_candidates[i].node_id;
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(mgr->instance->bgp, cand_id);
|
||||
if (!nq) {
|
||||
memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (mgr->best_candidate_count - i - 1) * sizeof(mgr->best_candidates[0]));
|
||||
mgr->best_candidate_count--; i--; continue;
|
||||
}
|
||||
uint64_t last = cm_get_node_max_probe_time(nq);
|
||||
if (last > 0 && (now_tb - last) >= CONN_MGR_CANDIDATE_STALE_TB) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "conn_mgr: candidate 0x%016llx stale, removing", (unsigned long long)cand_id);
|
||||
memmove(&mgr->best_candidates[i], &mgr->best_candidates[i+1], (mgr->best_candidate_count - i - 1) * sizeof(mgr->best_candidates[0]));
|
||||
mgr->best_candidate_count--; i--; continue;
|
||||
}
|
||||
struct ETCP_CONN* dconn = route_bgp_find_conn_for_node(mgr->instance->bgp, cand_id);
|
||||
int has_active = 0;
|
||||
if (dconn && dconn->links) {
|
||||
struct ETCP_LINK* l = dconn->links;
|
||||
while (l) { if (l->initialized && l->link_status) { has_active = 1; break; } l = l->next; }
|
||||
}
|
||||
if (has_active)
|
||||
etcp_send_ping(mgr->instance, nq->node.public_key, &dconn->links->remote_addr, CONN_PROBE_TIMEOUT_MS, NULL, NULL, NULL, 0);
|
||||
else
|
||||
route_connectivity_probe_node(mgr->instance, nq);
|
||||
}
|
||||
mgr->candidate_ping_timer = uasync_set_timeout(mgr->instance->ua, CONN_MGR_CANDIDATE_PING_TB,
|
||||
mgr, cm_candidate_ping_timer_cb, "conn_mgr_cand_ping");
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
#ifndef CONN_MGR_H
|
||||
#define CONN_MGR_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "etcp_api.h"
|
||||
#include "etcp_router.h"
|
||||
#include "route_node.h"
|
||||
|
||||
struct UTUN_INSTANCE;
|
||||
struct ETCP_CONN;
|
||||
struct NODEINFO_Q;
|
||||
struct ROUTE_BGP;
|
||||
|
||||
#define CONN_MGR_MAX_CANDIDATES 3
|
||||
#define CONN_MGR_CANDIDATE_CACHE_MS 20000
|
||||
#define CONN_MGR_CANDIDATE_STALE_TB 300000
|
||||
#define CONN_MGR_CANDIDATE_PING_TB 20000
|
||||
#define CONN_MGR_BG_PING_INTERVAL_TB 1000
|
||||
#define CONN_MGR_BG_PING_CYCLE_MIN_TB 100000
|
||||
#define CONN_MGR_IDLE_CHECK_INTERVAL_TB 10000
|
||||
#define CONN_MGR_LOCAL_SCAN_ATTEMPTS 3
|
||||
#define CONN_MGR_LOCAL_SCAN_TIMEOUT_MS 100
|
||||
#define CONN_MGR_CONNECT_DIRECT_TIMEOUT_MS 5000
|
||||
#define CONN_MGR_CONNECT_REVERSE_TIMEOUT_MS 15000
|
||||
#define CONN_MGR_INTERM_EXCHANGE_TIMEOUT_MS 15000
|
||||
|
||||
#define CONN_MGR_OK 0
|
||||
#define CONN_MGR_ERR_NOT_FOUND -1
|
||||
#define CONN_MGR_ERR_NO_ADDRESSES -2
|
||||
#define CONN_MGR_ERR_TIMEOUT -3
|
||||
#define CONN_MGR_ERR_UNREACHABLE -4
|
||||
#define CONN_MGR_ERR_REFUSED -5
|
||||
#define CONN_MGR_ERR_ALREADY_CONNECTED -6
|
||||
#define CONN_MGR_ERR_INTERNAL -7
|
||||
|
||||
enum CONN_MGR_STATE {
|
||||
CONN_MGR_STATE_DISCONNECTED = 0,
|
||||
CONN_MGR_STATE_CONNECTING = 1,
|
||||
CONN_MGR_STATE_CONNECTED = 2,
|
||||
};
|
||||
|
||||
enum CM_TRY_STATE {
|
||||
CM_TRY_NONE = 0,
|
||||
CM_TRY_PENDING = 1,
|
||||
CM_TRY_OK = 2,
|
||||
CM_TRY_FAILED = 3,
|
||||
};
|
||||
|
||||
#define CONN_MGR_SUBCMD_DIRECT_REQ 0x01
|
||||
#define CONN_MGR_SUBCMD_DIRECT_RESP 0x02
|
||||
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_REQ 0x03
|
||||
#define CONN_MGR_SUBCMD_INTERM_EXCHANGE_RESP 0x04
|
||||
#define CONN_MGR_SUBCMD_INTERM_SELECTED 0x05
|
||||
#define CONN_MGR_SUBCMD_DISCONNECT 0x06
|
||||
|
||||
struct CONN_MGR_CANDIDATE {
|
||||
uint64_t node_id;
|
||||
uint16_t rtt;
|
||||
} __attribute__((packed));
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct CONN_MGR_DIRECT_REQ {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint32_t request_id;
|
||||
uint8_t addr_count;
|
||||
};
|
||||
|
||||
struct CONN_MGR_DIRECT_RESP {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint32_t request_id;
|
||||
uint8_t accepted;
|
||||
};
|
||||
|
||||
struct CONN_MGR_INTERM_EXCHANGE_REQ {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint32_t request_id;
|
||||
uint8_t candidate_count;
|
||||
struct CONN_MGR_CANDIDATE candidates[4];
|
||||
};
|
||||
|
||||
struct CONN_MGR_INTERM_EXCHANGE_RESP {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint32_t request_id;
|
||||
uint8_t my_count;
|
||||
uint8_t your_count;
|
||||
struct CONN_MGR_CANDIDATE my_candidates[4];
|
||||
struct CONN_MGR_CANDIDATE your_candidates[4];
|
||||
};
|
||||
|
||||
struct CONN_MGR_INTERM_SELECTED {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint32_t request_id;
|
||||
uint8_t count;
|
||||
struct CONN_MGR_CANDIDATE selected[3];
|
||||
};
|
||||
|
||||
struct CONN_MGR_DISCONNECT {
|
||||
uint8_t cmd;
|
||||
uint8_t subcmd;
|
||||
uint64_t node_id;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#define CONN_MGR_DIRECT_REQ_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_REQ)
|
||||
#define CONN_MGR_DIRECT_RESP_HDR_SIZE sizeof(struct CONN_MGR_DIRECT_RESP)
|
||||
#define CONN_MGR_INTERM_EXCHANGE_REQ_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_REQ)
|
||||
#define CONN_MGR_INTERM_EXCHANGE_RESP_SIZE sizeof(struct CONN_MGR_INTERM_EXCHANGE_RESP)
|
||||
#define CONN_MGR_INTERM_SELECTED_SIZE sizeof(struct CONN_MGR_INTERM_SELECTED)
|
||||
#define CONN_MGR_DISCONNECT_SIZE sizeof(struct CONN_MGR_DISCONNECT)
|
||||
|
||||
typedef void (*conn_mgr_connect_callback_t)(int result, uint64_t node_id, void* arg);
|
||||
|
||||
struct cm_cb_node {
|
||||
conn_mgr_connect_callback_t cb;
|
||||
void* arg;
|
||||
struct cm_cb_node* next;
|
||||
};
|
||||
|
||||
struct CONN_MGR_ENTRY {
|
||||
uint64_t node_id;
|
||||
uint8_t state;
|
||||
uint8_t conn_type;
|
||||
uint8_t alien;
|
||||
uint32_t idle_timeout_ms;
|
||||
uint64_t last_traffic_tb;
|
||||
void* idle_timer;
|
||||
|
||||
struct cm_cb_node* cb_list;
|
||||
|
||||
uint64_t intermediaries[CONN_MGR_MAX_INTERMEDIARIES];
|
||||
uint8_t intermediariy_count;
|
||||
uint8_t rr_idx;
|
||||
|
||||
uint8_t local_scan_state;
|
||||
uint8_t main_connect_state;
|
||||
struct {
|
||||
uint8_t phase;
|
||||
void* timer;
|
||||
uint32_t request_id;
|
||||
void* conn_ctx;
|
||||
} main;
|
||||
|
||||
struct CONN_MGR* mgr;
|
||||
};
|
||||
|
||||
struct CONN_MGR {
|
||||
struct UTUN_INSTANCE* instance;
|
||||
struct CONN_MGR_ENTRY* entries;
|
||||
size_t entry_count;
|
||||
size_t entry_capacity;
|
||||
|
||||
void* bg_ping_timer;
|
||||
size_t bg_ping_cursor;
|
||||
uint64_t bg_ping_cycle_start_tb;
|
||||
|
||||
void* candidate_ping_timer;
|
||||
|
||||
struct CONN_MGR_CANDIDATE best_candidates[CONN_MGR_MAX_CANDIDATES];
|
||||
uint8_t best_candidate_count;
|
||||
|
||||
uint32_t next_request_id;
|
||||
uint8_t initialized;
|
||||
|
||||
struct cm_reverse_pending* reverse_pending;
|
||||
struct cm_exchange_pending* exchange_pending;
|
||||
};
|
||||
|
||||
struct CONN_MGR* conn_mgr_init(struct UTUN_INSTANCE* instance);
|
||||
void conn_mgr_destroy(struct CONN_MGR* mgr);
|
||||
|
||||
int conn_mgr_connect_node(struct CONN_MGR* mgr, uint64_t node_id,
|
||||
uint32_t idle_timeout_ms,
|
||||
conn_mgr_connect_callback_t cb, void* cb_arg);
|
||||
int conn_mgr_disconnect_node(struct CONN_MGR* mgr, uint64_t node_id);
|
||||
int conn_mgr_set_idle_timeout(struct CONN_MGR* mgr, uint64_t node_id, uint32_t timeout_ms);
|
||||
int conn_mgr_get_status(struct CONN_MGR* mgr, uint64_t node_id,
|
||||
uint8_t* out_state, uint8_t* out_conn_type);
|
||||
int conn_mgr_add_alien_node(struct CONN_MGR* mgr, const uint8_t* nodeinfo_data, size_t len);
|
||||
int conn_mgr_send(struct CONN_MGR* mgr, uint64_t node_id, struct ll_entry* entry);
|
||||
|
||||
void conn_mgr_update_best_candidates(struct CONN_MGR* mgr, uint64_t node_id, uint16_t rtt);
|
||||
|
||||
#endif
|
||||
+454
-37
@@ -10,8 +10,13 @@
|
||||
#include "etcp_connections.h"
|
||||
#include "tun_if.h"
|
||||
#include "route_lib.h"
|
||||
#include "route_bgp.h"
|
||||
#include "route_node.h"
|
||||
#include "pkt_normalizer.h"
|
||||
#include "../tools/etcpmon/etcpmon_protocol.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/timeout_heap.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
@@ -87,11 +92,21 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
static void close_client(struct control_server* server, struct control_client* client);
|
||||
|
||||
static void send_conn_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id);
|
||||
|
||||
static void control_server_send_node_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
|
||||
static void send_single_node_info(struct control_server* server, struct control_client* client, struct NODEINFO_Q* nq);
|
||||
static int control_client_send(struct control_server* server, struct control_client* client, uint8_t* dgram, uint16_t len);
|
||||
|
||||
static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instance, uint64_t peer_id);
|
||||
|
||||
/* ETCP ID/subcmd constants for BGP_NODEINFO_PACKET (from route_bgp.h) */
|
||||
#define CTRL_BGP_CMD 0x01
|
||||
#define CTRL_BGP_SUBCMD 0x04
|
||||
|
||||
/* ============================================================================
|
||||
* Server Initialization
|
||||
* ============================================================================ */
|
||||
@@ -269,6 +284,24 @@ void control_server_shutdown(struct control_server* server) {
|
||||
* Client Connection Handling
|
||||
* ============================================================================ */
|
||||
|
||||
static int is_control_ip_allowed(const struct control_server* server, uint32_t client_ip) {
|
||||
if (!server || !server->instance || !server->instance->config) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control IP check: no config available");
|
||||
return 0;
|
||||
}
|
||||
const struct global_config *g = &server->instance->config->global;
|
||||
if (g->control_allow_count == 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control connection denied (no allow rules) - add control_allow=IP/mask to [control] in config");
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < g->control_allow_count; i++) {
|
||||
const struct CFG_CONTROL_ALLOW *r = &g->control_allows[i];
|
||||
if ((client_ip & r->netmask) == r->network) return 1;
|
||||
}
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control connection denied from IP (not in allow list) - add control_allow=IP/mask to [control]");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void accept_callback(socket_t fd, void* arg) {
|
||||
struct control_server* server = (struct control_server*)arg;
|
||||
|
||||
@@ -317,6 +350,28 @@ static void accept_callback(socket_t fd, void* arg) {
|
||||
fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
#endif
|
||||
/* Check allowed IP (default deny all) */
|
||||
uint32_t client_ip = 0;
|
||||
if (client_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&client_addr;
|
||||
client_ip = ntohl(sin->sin_addr.s_addr);
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "IPv6 not supported for control allow list");
|
||||
#ifdef _WIN32
|
||||
closesocket(client_fd);
|
||||
#else
|
||||
close(client_fd);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
if (!is_control_ip_allowed(server, client_ip)) {
|
||||
#ifdef _WIN32
|
||||
closesocket(client_fd);
|
||||
#else
|
||||
close(client_fd);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Accept...");
|
||||
|
||||
/* Check max clients */
|
||||
@@ -354,13 +409,26 @@ static void accept_callback(socket_t fd, void* arg) {
|
||||
|
||||
client->fd = client_fd;
|
||||
client->server = server; /* Store back pointer to server */
|
||||
// client->connected = 1;
|
||||
client->selected_peer_id = 0;
|
||||
|
||||
|
||||
client->output_queue = queue_new(server->ua, 0, 0, 0, "ctrl_out");
|
||||
if (!client->output_queue) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to create output queue for client");
|
||||
u_free(client);
|
||||
#ifdef _WIN32
|
||||
closesocket(client_fd);
|
||||
#else
|
||||
close(client_fd);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
client->subscribed_nodes = 0;
|
||||
client->write_registered = 0;
|
||||
|
||||
/* Register with uasync */
|
||||
client->socket_id = uasync_add_socket_t(server->ua, client_fd,
|
||||
client_read_callback,
|
||||
NULL, /* write callback - not needed for now */
|
||||
client_write_callback,
|
||||
client_except_callback,
|
||||
client);
|
||||
|
||||
@@ -487,17 +555,55 @@ static void client_read_callback(socket_t fd, void* arg) {
|
||||
}
|
||||
|
||||
client->recv_len += received;
|
||||
|
||||
/* Process messages */
|
||||
if (client->recv_len > ETCPMON_MAX_MSG_SIZE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control recv buffer overflow");
|
||||
if (server) close_client(server, client);
|
||||
return;
|
||||
}
|
||||
if (server) {
|
||||
handle_client_data(server, client);
|
||||
}
|
||||
}
|
||||
|
||||
static void client_write_callback(socket_t fd, void* arg) {
|
||||
/* Not used for now - writes are immediate */
|
||||
struct control_client* client = (struct control_client*)arg;
|
||||
struct control_server* server = client->server;
|
||||
struct ll_queue* q = client->output_queue;
|
||||
|
||||
while (q->head) {
|
||||
struct ll_entry* entry = q->head;
|
||||
|
||||
#ifdef _WIN32
|
||||
int sent = send(fd, (const char*)entry->dgram, entry->len, 0);
|
||||
if (sent == SOCKET_ERROR) {
|
||||
int err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Client send error: %d, closing", err);
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#else
|
||||
ssize_t sent = send(fd, entry->dgram, entry->len, 0);
|
||||
if (sent < 0) {
|
||||
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Client send error: %s, closing", strerror(errno));
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if ((size_t)sent < entry->len) { entry->dgram += sent; entry->len -= (uint16_t)sent; q->total_bytes -= (size_t)sent; return; }
|
||||
|
||||
struct ll_entry* e = queue_data_get(q);
|
||||
queue_dgram_free(e); queue_entry_free(e);
|
||||
}
|
||||
|
||||
uasync_set_socket_write(server->ua, client->socket_id, 0);
|
||||
client->write_registered = 0;
|
||||
(void)fd;
|
||||
(void)arg;
|
||||
}
|
||||
|
||||
static void client_except_callback(socket_t fd, void* arg) {
|
||||
@@ -511,6 +617,21 @@ static void client_except_callback(socket_t fd, void* arg) {
|
||||
(void)fd;
|
||||
}
|
||||
|
||||
static int control_client_send(struct control_server* server, struct control_client* client, uint8_t* dgram, uint16_t len) {
|
||||
(void)server;
|
||||
if (!client || !client->output_queue || !dgram || len == 0) return -1;
|
||||
struct ll_entry* entry = queue_entry_new(0);
|
||||
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate output queue entry"); return -1; }
|
||||
entry->dgram = dgram;
|
||||
entry->len = len;
|
||||
entry->int_len = len;
|
||||
queue_data_put(client->output_queue, entry);
|
||||
if (!client->write_registered) {
|
||||
uasync_set_socket_write(client->server->ua, client->socket_id, 1);
|
||||
client->write_registered = 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void close_client(struct control_server* server, struct control_client* client) {
|
||||
if (!server || !client) return;
|
||||
@@ -534,7 +655,9 @@ static void close_client(struct control_server* server, struct control_client* c
|
||||
close(client->fd);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (client->output_queue) { queue_free(client->output_queue); client->output_queue = NULL; }
|
||||
|
||||
/* Remove from list */
|
||||
struct control_client** curr = &server->clients;
|
||||
while (*curr) {
|
||||
@@ -560,6 +683,17 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
while (client->recv_len >= sizeof(struct etcpmon_msg_header)) {
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)client->recv_buffer;
|
||||
|
||||
if (hdr->size == 0 || hdr->size > ETCPMON_MAX_MSG_SIZE) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid message size from client: %u", hdr->size);
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SUBSCRIBE_NODES) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Validate header */
|
||||
if (etcpmon_validate_header(hdr) != 0) {
|
||||
if (server->log_file) {
|
||||
@@ -605,6 +739,10 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Client selected connection: %016llX",
|
||||
(unsigned long long)cmd->peer_node_id);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Bad SELECT_CONN payload size %u", payload_size);
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -617,6 +755,57 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
}
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_LIST_SOCKETS:
|
||||
send_socket_list(server, client, req_seq);
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_ACTION:
|
||||
if (payload_size >= sizeof(struct etcpmon_cmd_action)) {
|
||||
struct etcpmon_cmd_action* cmd = (struct etcpmon_cmd_action*)payload;
|
||||
if (strncmp(cmd->action, "nat", 3) == 0) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nat' received, requesting NAT check for all links");
|
||||
route_bgp_request_nat_check_all(server->instance->bgp);
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown action received: '%s'", cmd->action);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_GET_DEBUG_CONFIG:
|
||||
send_debug_config(server, client, req_seq);
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_SET_DEBUG_CONFIG:
|
||||
if (payload_size >= 2) {
|
||||
uint8_t global_lvl = payload[0];
|
||||
uint8_t cat_count = payload[1];
|
||||
if (cat_count > ETCPMON_MAX_DEBUG_CATEGORIES) cat_count = ETCPMON_MAX_DEBUG_CATEGORIES;
|
||||
if (global_lvl <= DEBUG_LEVEL_TRACE) debug_set_level((debug_level_t)global_lvl);
|
||||
const uint8_t* levels = payload + 2;
|
||||
for (uint8_t i = 0; i < cat_count && i + 1 < DEBUG_CATEGORY_COUNT; i++) {
|
||||
if (levels[i] <= DEBUG_LEVEL_TRACE)
|
||||
debug_set_category_level((debug_category_t)(i + 1), (debug_level_t)levels[i]);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Debug config applied: global=%d categories=%d", global_lvl, cat_count);
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Debug config set: global=%d categories=%d\n",
|
||||
(unsigned long long)get_timestamp_ms(), global_lvl, cat_count);
|
||||
fflush(server->log_file);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_SUBSCRIBE_NODES:
|
||||
client->subscribed_nodes = 1;
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Client subscribed to node updates\n",
|
||||
(unsigned long long)get_timestamp_ms());
|
||||
fflush(server->log_file);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Client subscribed to node updates");
|
||||
control_server_send_node_list(server, client, req_seq);
|
||||
break;
|
||||
|
||||
case ETCPMON_CMD_DISCONNECT:
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Client requested disconnect\n",
|
||||
@@ -627,14 +816,9 @@ static void handle_client_data(struct control_server* server, struct control_cli
|
||||
return;
|
||||
|
||||
default:
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [ERROR] Unknown command from client: 0x%02X\n",
|
||||
(unsigned long long)get_timestamp_ms(), hdr->type);
|
||||
fflush(server->log_file);
|
||||
}
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown command from client: 0x%02X", hdr->type);
|
||||
send_error(client, ETCPMON_ERR_INVALID_CMD, "Unknown command", req_seq);
|
||||
break;
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Unknown command from client: 0x%02X", hdr->type);
|
||||
close_client(server, client);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove processed message from buffer */
|
||||
@@ -696,13 +880,98 @@ static void send_conn_list(struct control_server* server, struct control_client*
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
struct UTUN_INSTANCE* instance = server->instance;
|
||||
|
||||
if (!instance || !instance->etcp_sockets) {
|
||||
uint16_t rsp_size = ETCPMON_SOCKET_LIST_SIZE(0);
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
|
||||
if (!buffer) return;
|
||||
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, sizeof(struct etcpmon_rsp_socket_list),
|
||||
ETCPMON_RSP_SOCKET_LIST, seq_id);
|
||||
|
||||
struct etcpmon_rsp_socket_list* rsp = (struct etcpmon_rsp_socket_list*)(buffer + sizeof(*hdr));
|
||||
rsp->count = 0;
|
||||
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t count = 0;
|
||||
struct ETCP_SOCKET* sock = instance->etcp_sockets;
|
||||
while (sock && count < ETCPMON_MAX_CONNECTIONS) {
|
||||
count++;
|
||||
sock = sock->next;
|
||||
}
|
||||
|
||||
if (count > ETCPMON_MAX_CONNECTIONS) count = ETCPMON_MAX_CONNECTIONS;
|
||||
|
||||
uint16_t rsp_size = ETCPMON_SOCKET_LIST_SIZE(count);
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
|
||||
if (!buffer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate socket list buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, sizeof(struct etcpmon_rsp_socket_list) +
|
||||
count * sizeof(struct etcpmon_socket_info),
|
||||
ETCPMON_RSP_SOCKET_LIST,
|
||||
seq_id);
|
||||
|
||||
struct etcpmon_rsp_socket_list* rsp = (struct etcpmon_rsp_socket_list*)(buffer + sizeof(*hdr));
|
||||
rsp->count = count;
|
||||
|
||||
struct etcpmon_socket_info* info = (struct etcpmon_socket_info*)(buffer + sizeof(*hdr) + sizeof(*rsp));
|
||||
sock = instance->etcp_sockets;
|
||||
for (uint8_t i = 0; i < count && sock; i++) {
|
||||
strncpy(info[i].name, sock->name, ETCPMON_MAX_CONN_NAME - 1);
|
||||
info[i].name[ETCPMON_MAX_CONN_NAME - 1] = '\0';
|
||||
info[i].is_ipv6 = (sock->interface_addr.ss_family == AF_INET6) ? 1 : 0;
|
||||
if (sock->interface_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&sock->interface_addr;
|
||||
inet_ntop(AF_INET, &sin->sin_addr, info[i].ip, sizeof(info[i].ip));
|
||||
info[i].port = ntohs(sin->sin_port);
|
||||
} else if (sock->interface_addr.ss_family == AF_INET6) {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&sock->interface_addr;
|
||||
inet_ntop(AF_INET6, &sin6->sin6_addr, info[i].ip, sizeof(info[i].ip));
|
||||
info[i].port = ntohs(sin6->sin6_port);
|
||||
} else {
|
||||
strncpy(info[i].ip, "unknown", sizeof(info[i].ip) - 1);
|
||||
info[i].ip[sizeof(info[i].ip) - 1] = '\0';
|
||||
info[i].port = 0;
|
||||
}
|
||||
info[i].nat_type = sock->nat_type;
|
||||
|
||||
/* NAT address from socket (set when INIT_RESPONSE received) */
|
||||
info[i].nat_ip[0] = '\0';
|
||||
info[i].nat_port = 0;
|
||||
if (sock->nat_addr.ss_family == AF_INET) {
|
||||
struct sockaddr_in* sin = (struct sockaddr_in*)&sock->nat_addr;
|
||||
inet_ntop(AF_INET, &sin->sin_addr, info[i].nat_ip, sizeof(info[i].nat_ip));
|
||||
info[i].nat_port = ntohs(sin->sin_port);
|
||||
} else if (sock->nat_addr.ss_family == AF_INET6) {
|
||||
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)&sock->nat_addr;
|
||||
inet_ntop(AF_INET6, &sin6->sin6_addr, info[i].nat_ip, sizeof(info[i].nat_ip));
|
||||
info[i].nat_port = ntohs(sin6->sin6_port);
|
||||
}
|
||||
|
||||
sock = sock->next;
|
||||
}
|
||||
|
||||
if (server->log_file) {
|
||||
log_hex_data(server->log_file, "TX", buffer, rsp_size);
|
||||
fprintf(server->log_file, "%llu: [LOG] Sent RSP_SOCKET_LIST count=%d\n",
|
||||
(unsigned long long)get_timestamp_ms(), count);
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_metrics(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
@@ -823,10 +1092,21 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
rsp->etcp.cnt_ack_hit_sndq = conn->cnt_ack_hit_sndq;
|
||||
rsp->etcp.cnt_ack_miss = conn->cnt_ack_miss;
|
||||
rsp->etcp.cnt_link_wait = conn->cnt_link_wait;
|
||||
rsp->etcp.rx_dup_count = conn->rx_dup_count;
|
||||
rsp->etcp.tx_dup_count = conn->tx_dup_count;
|
||||
rsp->etcp.tx_state = conn->tx_state;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
rsp->etcp.debug[i] = conn->debug[i];
|
||||
}
|
||||
|
||||
/* System resources */
|
||||
if (instance->ua && instance->ua->timeout_heap) {
|
||||
rsp->etcp.active_timeouts = (uint32_t)timeout_heap_get_size(instance->ua->timeout_heap);
|
||||
} else {
|
||||
rsp->etcp.active_timeouts = 0;
|
||||
}
|
||||
|
||||
rsp->etcp.busy_memory_blocks = (uint32_t)(u_get_allocated_count() - memory_pool_get_total_free_blocks());
|
||||
|
||||
/* Fill TUN metrics */
|
||||
if (instance->tun) {
|
||||
@@ -857,6 +1137,15 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
rsp->tun.rt_local = 0;
|
||||
rsp->tun.rt_learned = 0;
|
||||
}
|
||||
|
||||
/* BGP routing */
|
||||
if (instance->bgp) {
|
||||
rsp->tun.rt_bgp_senders = (uint32_t)queue_entry_count(instance->bgp->senders_list);
|
||||
rsp->tun.rt_bgp_nodes = (uint32_t)queue_entry_count(instance->bgp->nodes);
|
||||
} else {
|
||||
rsp->tun.rt_bgp_senders = 0;
|
||||
rsp->tun.rt_bgp_nodes = 0;
|
||||
}
|
||||
} else {
|
||||
memset(&rsp->tun, 0, sizeof(rsp->tun));
|
||||
}
|
||||
@@ -876,7 +1165,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
link_info[i].bandwidth = link->bandwidth;
|
||||
link_info[i].nat_changes_count = link->nat_changes_count;
|
||||
link_info[i].rtt_last = link->rtt_last;
|
||||
link_info[i].rtt_avg10 = link->rtt_avg10;
|
||||
link_info[i].rtt_avg10 = link->bbr ? link->bbr->min_rtt_us / 100 : 0;
|
||||
link_info[i].tt_last = link->tt_last;
|
||||
link_info[i].init_timer_active = (link->init_timer != NULL) ? 1 : 0;
|
||||
link_info[i].keepalive_timer_active = (link->keepalive_timer != NULL) ? 1 : 0;
|
||||
@@ -885,6 +1174,26 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
link_info[i].keepalive_recv = (uint32_t)link->keepalive_recv_count;
|
||||
link_info[i].inflight_bytes = link->inflight_bytes;
|
||||
link_info[i].inflight_packets = link->inflight_packets;
|
||||
link_info[i].inflight_lim_bytes = link->inflight_lim_bytes;
|
||||
|
||||
/* BBR fields */
|
||||
if (link->bbr) {
|
||||
link_info[i].bbr_mode = link->bbr->mode;
|
||||
link_info[i].bbr_cycle_idx = link->bbr->cycle_idx;
|
||||
link_info[i].bbr_full_bw_reached = link->bbr->full_bw_reached;
|
||||
link_info[i].bbr_loss_in_round = link->bbr->loss_in_round;
|
||||
link_info[i].bbr_pacing_rate = link->bbr_pacing_rate;
|
||||
link_info[i].bbr_min_rtt_us = link->bbr->min_rtt_us;
|
||||
link_info[i].bbr_pacing_gain = link->bbr->pacing_gain;
|
||||
link_info[i].bbr_inflight_hi = link->bbr->inflight_hi;
|
||||
link_info[i].bbr_inflight_lo = link->bbr->inflight_lo;
|
||||
/* bw_hi/lo: convert from BW_UNIT scale (pkts/usec<<24) to bytes/sec */
|
||||
uint32_t max_bw = link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1];
|
||||
link_info[i].bbr_bw_hi = max_bw ? (uint32_t)((uint64_t)max_bw * link->mtu * 1000000ULL / 16777216ULL) : 0;
|
||||
link_info[i].bbr_bw_lo = link->bbr->bw_lo != ~0U ? (uint32_t)((uint64_t)link->bbr->bw_lo * link->mtu * 1000000ULL / 16777216ULL) : 0;
|
||||
} else {
|
||||
memset(&link_info[i].bbr_mode, 0, 11 * 4 + 2 + 2); // zero out all BBR fields
|
||||
}
|
||||
link = link->next;
|
||||
}
|
||||
|
||||
@@ -899,13 +1208,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
|
||||
u_free(buffer);
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
static void send_error(struct control_client* client, uint8_t error_code, const char* msg, uint8_t seq_id) {
|
||||
@@ -933,13 +1236,46 @@ static void send_error(struct control_client* client, uint8_t error_code, const
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
send(client->fd, (const char*)buffer, rsp_size, 0);
|
||||
#else
|
||||
send(client->fd, buffer, rsp_size, 0);
|
||||
#endif
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
u_free(buffer);
|
||||
static void send_debug_config(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
uint8_t cat_count = 0;
|
||||
char names_buf[512];
|
||||
names_buf[0] = '\0';
|
||||
size_t off = 0;
|
||||
for (int i = 1; i < DEBUG_CATEGORY_COUNT; i++) {
|
||||
const char* name = debug_get_category_name(i);
|
||||
size_t name_len = strlen(name);
|
||||
if (off + name_len + 2 > sizeof(names_buf)) break;
|
||||
if (cat_count > 0) { names_buf[off++] = ','; }
|
||||
memcpy(names_buf + off, name, name_len); off += name_len;
|
||||
cat_count++;
|
||||
}
|
||||
names_buf[off] = '\0';
|
||||
uint16_t names_len = (uint16_t)(off + 1);
|
||||
|
||||
uint16_t payload_size = 2 + names_len + cat_count;
|
||||
uint16_t rsp_size = sizeof(struct etcpmon_msg_header) + payload_size;
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
|
||||
if (!buffer) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate debug config buffer"); return; }
|
||||
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, payload_size, ETCPMON_RSP_DEBUG_CONFIG, seq_id);
|
||||
|
||||
uint8_t* pay = buffer + sizeof(*hdr);
|
||||
pay[0] = (uint8_t)g_debug_config.level;
|
||||
pay[1] = cat_count;
|
||||
memcpy(pay + 2, names_buf, names_len);
|
||||
for (uint8_t i = 0; i < cat_count; i++) pay[2 + names_len + i] = (uint8_t)g_debug_config.category_levels[i + 1];
|
||||
|
||||
if (server->log_file) {
|
||||
fprintf(server->log_file, "%llu: [LOG] Sent RSP_DEBUG_CONFIG global=%d categories=%d\n",
|
||||
(unsigned long long)get_timestamp_ms(), g_debug_config.level, cat_count);
|
||||
fflush(server->log_file);
|
||||
}
|
||||
|
||||
control_client_send(server, client, buffer, rsp_size);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
@@ -958,6 +1294,87 @@ static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instan
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Node Info Helpers (send_single_node_info, send_node_list, notify_change)
|
||||
* ============================================================================ */
|
||||
|
||||
static void send_single_node_info(struct control_server* server, struct control_client* client, struct NODEINFO_Q* nq) {
|
||||
int dyn = nodeinfo_dyn_size(&nq->node);
|
||||
size_t np_size = sizeof(struct etcpmon_msg_header) + 2 + sizeof(struct NODEINFO) + dyn;
|
||||
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(np_size);
|
||||
if (!buffer) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate node info buffer"); return; }
|
||||
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, 2 + sizeof(struct NODEINFO) + dyn, ETCPMON_RSP_NODE_INFO, 0);
|
||||
|
||||
uint8_t* pay = buffer + sizeof(*hdr);
|
||||
pay[0] = CTRL_BGP_CMD; pay[1] = CTRL_BGP_SUBCMD;
|
||||
memcpy(pay + 2, &nq->node, sizeof(struct NODEINFO));
|
||||
memcpy(pay + 2 + sizeof(struct NODEINFO), (uint8_t*)&nq->node + sizeof(struct NODEINFO), dyn);
|
||||
|
||||
control_client_send(server, client, buffer, (uint16_t)np_size);
|
||||
}
|
||||
|
||||
static void control_server_send_node_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
|
||||
struct ROUTE_BGP* bgp = server->instance->bgp;
|
||||
if (!bgp) { send_error(client, ETCPMON_ERR_SERVER_BUSY, "BGP not available", seq_id); return; }
|
||||
|
||||
/* Send local node first */
|
||||
if (bgp->local_node) send_single_node_info(server, client, bgp->local_node);
|
||||
|
||||
/* Send all known remote nodes */
|
||||
struct ll_entry* e = bgp->nodes ? bgp->nodes->head : NULL;
|
||||
while (e) {
|
||||
struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e;
|
||||
send_single_node_info(server, client, nq);
|
||||
e = e->next;
|
||||
}
|
||||
|
||||
/* Send end-of-list marker */
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header));
|
||||
if (!buffer) return;
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, 0, ETCPMON_RSP_NODE_INFO_END, seq_id);
|
||||
control_client_send(server, client, buffer, sizeof(struct etcpmon_msg_header));
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Sent node list to client (seq=%d)", seq_id);
|
||||
}
|
||||
|
||||
void control_server_notify_node_change(struct control_server* server, struct NODEINFO_Q* node) {
|
||||
if (!server || !node) return;
|
||||
struct control_client* client = server->clients;
|
||||
while (client) {
|
||||
if (client->subscribed_nodes) send_single_node_info(server, client, node);
|
||||
client = client->next;
|
||||
}
|
||||
}
|
||||
|
||||
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id) {
|
||||
if (!server) return;
|
||||
|
||||
uint8_t* buffer = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
if (!buffer) return;
|
||||
|
||||
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
|
||||
etcpmon_build_header(hdr, sizeof(uint64_t), ETCPMON_RSP_NODE_REMOVED, 0);
|
||||
*(uint64_t*)(buffer + sizeof(*hdr)) = node_id;
|
||||
|
||||
struct control_client* client = server->clients;
|
||||
while (client) {
|
||||
if (client->subscribed_nodes) {
|
||||
/* Every client needs its own copy since ownership transfers to the queue */
|
||||
uint8_t* copy = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
if (copy) {
|
||||
memcpy(copy, buffer, sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
control_client_send(server, client, copy, sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
|
||||
}
|
||||
}
|
||||
client = client->next;
|
||||
}
|
||||
u_free(buffer);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Public API Implementation
|
||||
* ============================================================================ */
|
||||
|
||||
@@ -19,6 +19,8 @@ extern "C" {
|
||||
/* Forward declarations */
|
||||
struct UTUN_INSTANCE;
|
||||
struct UASYNC;
|
||||
struct NODEINFO_Q;
|
||||
struct ll_queue;
|
||||
|
||||
/* Forward declaration */
|
||||
struct control_server;
|
||||
@@ -36,9 +38,10 @@ struct control_client {
|
||||
|
||||
/* Selected connection */
|
||||
uint64_t selected_peer_id; /* 0 = none selected */
|
||||
|
||||
/* Client state */
|
||||
// uint8_t connected;
|
||||
|
||||
struct ll_queue* output_queue; /* Output queue for async sends */
|
||||
uint8_t subscribed_nodes; /* 1 = subscribed to node updates */
|
||||
uint8_t write_registered; /* 1 = write events registered in uasync */
|
||||
};
|
||||
|
||||
/* Control server state */
|
||||
@@ -113,6 +116,9 @@ void control_server_process_updates(struct control_server* server);
|
||||
*/
|
||||
uint32_t control_server_get_client_count(const struct control_server* server);
|
||||
|
||||
void control_server_notify_node_change(struct control_server* server, struct NODEINFO_Q* node);
|
||||
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+124
-11
@@ -10,6 +10,7 @@
|
||||
#include "../lib/socket_compat.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "etcp_connections.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -130,7 +131,6 @@ static void dummynet_process_queue(struct dummynet* dn, int dir_idx) {
|
||||
}
|
||||
|
||||
struct dummynet_pkt* pkt = (struct dummynet_pkt*)entry->data;
|
||||
|
||||
ssize_t n = socket_sendto(dn->sock, pkt->data, pkt->len,
|
||||
(struct sockaddr*)&dir->dest_addr, dir->dest_len);
|
||||
if (n < 0) {
|
||||
@@ -149,7 +149,7 @@ static void dummynet_process_queue(struct dummynet* dn, int dir_idx) {
|
||||
int delay_tb = 0;
|
||||
|
||||
if (dir->bandwidth_kbps > 0) {
|
||||
uint64_t delay_calc = (uint64_t)bytes_sent * 80000ULL / (uint64_t)dir->bandwidth_kbps;
|
||||
uint64_t delay_calc = (uint64_t)bytes_sent * 80ULL / (uint64_t)dir->bandwidth_kbps;
|
||||
delay_tb = (int)delay_calc;
|
||||
if (delay_tb == 0) delay_tb = 1;
|
||||
}
|
||||
@@ -158,7 +158,7 @@ static void dummynet_process_queue(struct dummynet* dn, int dir_idx) {
|
||||
if (sarg) {
|
||||
sarg->dn = dn;
|
||||
sarg->dir_idx = dir_idx;
|
||||
dir->shaper_timer = uasync_set_timeout(dn->ua, delay_tb, sarg, dummynet_shaper_callback);
|
||||
dir->shaper_timer = uasync_set_timeout(dn->ua, delay_tb, sarg, dummynet_shaper_callback, "dummynet_shaper");
|
||||
if (dir->shaper_timer) {
|
||||
dir->shaper_timer_set++;
|
||||
} else {
|
||||
@@ -270,7 +270,7 @@ static void dummynet_read_callback(socket_t sock, void* user_arg) {
|
||||
targ->dn = dn;
|
||||
targ->entry = entry;
|
||||
|
||||
void* timer = uasync_set_timeout(dn->ua, delay_tb, targ, dummynet_delay_callback);
|
||||
void* timer = uasync_set_timeout(dn->ua, delay_tb, targ, dummynet_delay_callback, "dummynet_delay");
|
||||
if (!timer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to set delay timer");
|
||||
dir->stats.dropped++;
|
||||
@@ -304,7 +304,7 @@ static void dummynet_delay_callback(void* user_arg) {
|
||||
|
||||
/* Добавляем в очередь */
|
||||
uint32_t id = (uint32_t)(uintptr_t)entry;
|
||||
if (queue_data_put(dir->queue, entry, id) != 0) {
|
||||
if (queue_data_put(dir->queue, entry) != 0) {
|
||||
dir->stats.dropped++;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DUMMYNET, "Dir %d: queue_data_put failed", dir_idx);
|
||||
queue_entry_free(entry);
|
||||
@@ -316,9 +316,6 @@ static void dummynet_delay_callback(void* user_arg) {
|
||||
dir->stats.queue_max = queue_count + 1;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DUMMYNET, "Dir %d: packet added to queue (size=%d)",
|
||||
dir_idx, queue_count + 1);
|
||||
|
||||
/* Запускаем шейпер если не активен */
|
||||
if (!dir->shaper_pending) {
|
||||
dir->shaper_pending = 1;
|
||||
@@ -326,7 +323,15 @@ static void dummynet_delay_callback(void* user_arg) {
|
||||
if (sarg) {
|
||||
sarg->dn = dn;
|
||||
sarg->dir_idx = dir_idx;
|
||||
dir->shaper_timer = uasync_set_timeout(dn->ua, 0, sarg, dummynet_shaper_callback);
|
||||
int shaper_delay_tb = 0;
|
||||
if (dir->bandwidth_kbps > 0) {
|
||||
uint64_t delay_calc = (uint64_t)pkt->len * 80ULL / (uint64_t)dir->bandwidth_kbps;
|
||||
shaper_delay_tb = (int)delay_calc;
|
||||
if (shaper_delay_tb == 0) shaper_delay_tb = 1;
|
||||
}
|
||||
dir->shaper_timer = uasync_set_timeout(dn->ua, shaper_delay_tb, sarg, dummynet_shaper_callback, "dummynet_shaper0");
|
||||
if (dir->shaper_timer) dir->shaper_timer_set++;
|
||||
else { u_free(sarg); dir->shaper_pending = 0; }
|
||||
} else {
|
||||
dir->shaper_pending = 0;
|
||||
}
|
||||
@@ -350,7 +355,7 @@ struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_
|
||||
dn->listen_port = listen_port;
|
||||
|
||||
/* Создаём пул для пакетов */
|
||||
dn->pkt_pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt));
|
||||
dn->pkt_pool = memory_pool_init(sizeof(struct ll_entry) + sizeof(struct dummynet_pkt), "pkt_pool");
|
||||
if (!dn->pkt_pool) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to create packet pool");
|
||||
u_free(dn);
|
||||
@@ -359,7 +364,7 @@ struct dummynet* dummynet_create(struct UASYNC* ua, const char* bind_ip, uint16_
|
||||
|
||||
/* Создаём очереди для направлений */
|
||||
for (int i = 0; i < DUMMYNET_DIR_COUNT; i++) {
|
||||
dn->dirs[i].queue = queue_new(ua, 0, "dummynet1");
|
||||
dn->dirs[i].queue = queue_new(ua, 0, 0, 0, "dummynet1");
|
||||
if (!dn->dirs[i].queue) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_DUMMYNET, "Failed to create queue for direction %d", i);
|
||||
dummynet_destroy(dn);
|
||||
@@ -562,3 +567,111 @@ void dummynet_get_debug_counters(struct dummynet *dn, int direction,
|
||||
*shaper_set = dir->shaper_timer_set;
|
||||
*shaper_fire = dir->shaper_timer_fire;
|
||||
}
|
||||
|
||||
// === Inline filter: send_hook для etcp_udp_send ===
|
||||
|
||||
struct dummynet_filter {
|
||||
struct UASYNC* ua;
|
||||
uint32_t loss_permille;
|
||||
uint32_t delay_ms;
|
||||
uint32_t jitter_ms;
|
||||
struct ETCP_LINK* link;
|
||||
struct dummynet_stats stats;
|
||||
struct {
|
||||
uint32_t ip;
|
||||
uint16_t port;
|
||||
} block_addrs[4];
|
||||
uint8_t block_count;
|
||||
};
|
||||
|
||||
static ssize_t dummynet_filter_send_hook(socket_t fd, const void* buf, size_t len,
|
||||
const struct sockaddr* addr, socklen_t addr_len,
|
||||
struct ETCP_LINK* link, void* ctx) {
|
||||
struct dummynet_filter* df = (struct dummynet_filter*)ctx;
|
||||
(void)link;
|
||||
df->stats.recv++;
|
||||
if (addr && addr->sa_family == AF_INET && df->block_count > 0) {
|
||||
const struct sockaddr_in* sin = (const struct sockaddr_in*)addr;
|
||||
for (uint8_t i = 0; i < df->block_count; i++) {
|
||||
if (sin->sin_addr.s_addr == df->block_addrs[i].ip &&
|
||||
sin->sin_port == df->block_addrs[i].port) {
|
||||
df->stats.lost++;
|
||||
return (ssize_t)len;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (df->loss_permille > 0) {
|
||||
uint32_t roll = (uint32_t)rand() % 1000;
|
||||
if (roll < df->loss_permille) {
|
||||
df->stats.lost++;
|
||||
return (ssize_t)len;
|
||||
}
|
||||
}
|
||||
ssize_t sent = socket_sendto(fd, buf, len, addr, addr_len);
|
||||
if (sent > 0) df->stats.sent++;
|
||||
return sent;
|
||||
}
|
||||
|
||||
struct dummynet_filter* dummynet_filter_create(struct UASYNC* ua) {
|
||||
struct dummynet_filter* df = u_calloc(1, sizeof(*df));
|
||||
if (!df) return NULL;
|
||||
df->ua = ua;
|
||||
return df;
|
||||
}
|
||||
|
||||
void dummynet_filter_set_loss(struct dummynet_filter* df, uint32_t loss_permille) {
|
||||
if (!df) return;
|
||||
if (loss_permille > 1000) loss_permille = 1000;
|
||||
df->loss_permille = loss_permille;
|
||||
}
|
||||
|
||||
void dummynet_filter_set_delay(struct dummynet_filter* df, uint32_t delay_ms, uint32_t jitter_ms) {
|
||||
if (!df) return;
|
||||
df->delay_ms = delay_ms;
|
||||
df->jitter_ms = jitter_ms;
|
||||
}
|
||||
|
||||
void dummynet_filter_attach(struct dummynet_filter* df, struct ETCP_LINK* link) {
|
||||
if (!df || !link) return;
|
||||
dummynet_filter_detach(df);
|
||||
df->link = link;
|
||||
link->send_hook = dummynet_filter_send_hook;
|
||||
link->send_hook_ctx = df;
|
||||
}
|
||||
|
||||
void dummynet_filter_detach(struct dummynet_filter* df) {
|
||||
if (!df || !df->link) return;
|
||||
df->link->send_hook = NULL;
|
||||
df->link->send_hook_ctx = NULL;
|
||||
df->link = NULL;
|
||||
}
|
||||
|
||||
void dummynet_filter_destroy(struct dummynet_filter* df) {
|
||||
if (!df) return;
|
||||
dummynet_filter_detach(df);
|
||||
u_free(df);
|
||||
}
|
||||
|
||||
void dummynet_filter_block_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port) {
|
||||
if (!df || df->block_count >= 4) return;
|
||||
for (uint8_t i = 0; i < df->block_count; i++)
|
||||
if (df->block_addrs[i].ip == ip && df->block_addrs[i].port == port) return;
|
||||
df->block_addrs[df->block_count].ip = ip;
|
||||
df->block_addrs[df->block_count].port = port;
|
||||
df->block_count++;
|
||||
}
|
||||
|
||||
void dummynet_filter_unblock_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port) {
|
||||
if (!df) return;
|
||||
for (uint8_t i = 0; i < df->block_count; i++) {
|
||||
if (df->block_addrs[i].ip == ip && df->block_addrs[i].port == port) {
|
||||
df->block_count--;
|
||||
if (i < df->block_count) memmove(&df->block_addrs[i], &df->block_addrs[i+1], (df->block_count - i) * sizeof(df->block_addrs[0]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df) {
|
||||
return df ? &df->stats : NULL;
|
||||
}
|
||||
@@ -163,4 +163,20 @@ void dummynet_get_debug_counters(struct dummynet *dn, int direction,
|
||||
uint64_t *delay_set, uint64_t *delay_fire,
|
||||
uint64_t *shaper_set, uint64_t *shaper_fire);
|
||||
|
||||
// === Inline filter: встраивается в etcp_udp_send через link->send_hook ===
|
||||
// Не создаёт свой сокет — перехватывает пакеты напрямую из пути отправки.
|
||||
|
||||
struct ETCP_LINK;
|
||||
struct dummynet_filter;
|
||||
|
||||
struct dummynet_filter* dummynet_filter_create(struct UASYNC* ua);
|
||||
void dummynet_filter_set_loss(struct dummynet_filter* df, uint32_t loss_permille);
|
||||
void dummynet_filter_set_delay(struct dummynet_filter* df, uint32_t delay_ms, uint32_t jitter_ms);
|
||||
void dummynet_filter_attach(struct dummynet_filter* df, struct ETCP_LINK* link);
|
||||
void dummynet_filter_detach(struct dummynet_filter* df);
|
||||
void dummynet_filter_destroy(struct dummynet_filter* df);
|
||||
const struct dummynet_stats* dummynet_filter_get_stats(struct dummynet_filter* df);
|
||||
void dummynet_filter_block_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port);
|
||||
void dummynet_filter_unblock_addr(struct dummynet_filter* df, uint32_t ip, uint16_t port);
|
||||
|
||||
#endif // DUMMYNET_H
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
#include "eim_nat.h"
|
||||
#include "config_parser.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// IP header offsets
|
||||
#define IP_IHL_OFFSET 0
|
||||
#define IP_PROTO_OFFSET 9
|
||||
#define IP_CHECKSUM_OFFSET 10
|
||||
#define IP_SRC_ADDR_OFFSET 12
|
||||
#define IP_DST_ADDR_OFFSET 16
|
||||
#define IP_HDR_MIN_SIZE 20
|
||||
|
||||
#define IP_FRAG_MF_MASK 0x2000
|
||||
#define IP_FRAG_OFF_MASK 0x1FFF
|
||||
|
||||
#define TCP_SRC_PORT_OFFSET 0
|
||||
#define TCP_DST_PORT_OFFSET 2
|
||||
#define TCP_CHECKSUM_OFFSET 16
|
||||
#define UDP_SRC_PORT_OFFSET 0
|
||||
#define UDP_DST_PORT_OFFSET 2
|
||||
#define UDP_CHECKSUM_OFFSET 6
|
||||
#define ICMP_TYPE_OFFSET 0
|
||||
#define ICMP_ID_OFFSET 4
|
||||
#define ICMP_CHECKSUM_OFFSET 2
|
||||
#define ICMP_ECHO_REQUEST 8
|
||||
#define ICMP_ECHO_REPLY 0
|
||||
|
||||
static ip_str_t ip_host_to_str(uint32_t ip_host) {
|
||||
struct in_addr a; a.s_addr = htonl(ip_host);
|
||||
return ip_to_str(&a, AF_INET);
|
||||
}
|
||||
|
||||
static uint16_t csum_update_n(uint16_t old_csum, const uint16_t* old_vals,
|
||||
const uint16_t* new_vals, int n) {
|
||||
uint32_t sum = (uint32_t)(uint16_t)(~old_csum);
|
||||
for (int i = 0; i < n; i++) { sum -= old_vals[i]; sum += new_vals[i]; }
|
||||
sum = (sum & 0xFFFF) + (sum >> 16); sum = (sum & 0xFFFF) + (sum >> 16);
|
||||
return (uint16_t)(~sum);
|
||||
}
|
||||
|
||||
static void csum_update_ip(uint8_t* ip_data, uint32_t old_ip_host, uint32_t new_ip_host) {
|
||||
uint16_t old_csum; memcpy(&old_csum, ip_data + IP_CHECKSUM_OFFSET, 2);
|
||||
uint32_t old_ip_net = htonl(old_ip_host), new_ip_net = htonl(new_ip_host);
|
||||
uint16_t old_w[2] = {(uint16_t)(old_ip_net & 0xFFFF), (uint16_t)(old_ip_net >> 16)};
|
||||
uint16_t new_w[2] = {(uint16_t)(new_ip_net & 0xFFFF), (uint16_t)(new_ip_net >> 16)};
|
||||
uint16_t new_csum = csum_update_n(old_csum, old_w, new_w, 2);
|
||||
memcpy(ip_data + IP_CHECKSUM_OFFSET, &new_csum, 2);
|
||||
}
|
||||
|
||||
static void csum_update_transport(uint8_t* transport, int csum_off,
|
||||
uint32_t old_ip_host, uint32_t new_ip_host,
|
||||
uint16_t old_port_net, uint16_t new_port_net) {
|
||||
uint16_t old_csum; memcpy(&old_csum, transport + csum_off, 2);
|
||||
uint32_t old_ip_net = htonl(old_ip_host), new_ip_net = htonl(new_ip_host);
|
||||
uint16_t old_w[3] = {(uint16_t)(old_ip_net & 0xFFFF), (uint16_t)(old_ip_net >> 16), old_port_net};
|
||||
uint16_t new_w[3] = {(uint16_t)(new_ip_net & 0xFFFF), (uint16_t)(new_ip_net >> 16), new_port_net};
|
||||
uint16_t new_csum = csum_update_n(old_csum, old_w, new_w, 3);
|
||||
memcpy(transport + csum_off, &new_csum, 2);
|
||||
}
|
||||
|
||||
static void csum_update_icmp(uint8_t* icmp, uint16_t old_word, uint16_t new_word) {
|
||||
uint16_t old_csum; memcpy(&old_csum, icmp + ICMP_CHECKSUM_OFFSET, 2);
|
||||
uint16_t new_csum = csum_update_n(old_csum, &old_word, &new_word, 1);
|
||||
memcpy(icmp + ICMP_CHECKSUM_OFFSET, &new_csum, 2);
|
||||
}
|
||||
|
||||
static struct eim_nat_entry* eim_nat_find_egress(struct eim_nat_ctx* ctx,
|
||||
uint8_t proto, uint32_t ip_host, uint16_t port_net) {
|
||||
for (uint16_t i = ctx->port_start; i <= ctx->port_end; i++) {
|
||||
struct eim_nat_entry* e = &ctx->table[i];
|
||||
if (e->state != EIM_NAT_ENTRY_FREE && e->proto == proto &&
|
||||
e->internal_ip == ip_host && e->internal_port == port_net) return e;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static uint16_t eim_nat_alloc_port(struct eim_nat_ctx* ctx) {
|
||||
uint16_t start = ctx->next_port;
|
||||
do {
|
||||
if (ctx->table[ctx->next_port].state == EIM_NAT_ENTRY_FREE) {
|
||||
uint16_t port = ctx->next_port;
|
||||
ctx->next_port++;
|
||||
if (ctx->next_port > ctx->port_end || ctx->next_port < ctx->port_start) ctx->next_port = ctx->port_start;
|
||||
return port;
|
||||
}
|
||||
ctx->next_port++;
|
||||
if (ctx->next_port > ctx->port_end || ctx->next_port < ctx->port_start) ctx->next_port = ctx->port_start;
|
||||
} while (ctx->next_port != start);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ==================== Public API ====================
|
||||
|
||||
int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
|
||||
uint64_t src_node_id, struct ETCP_CONN* src_conn) {
|
||||
if (ip_len < IP_HDR_MIN_SIZE) return -1;
|
||||
uint8_t ihl = ip_data[IP_IHL_OFFSET] & 0x0F;
|
||||
if (ihl < 5) return -1;
|
||||
uint16_t ip_hdr_len = ihl * 4;
|
||||
if (ip_len < ip_hdr_len) return -1;
|
||||
|
||||
uint16_t frag_off = ntohs(*(uint16_t*)(ip_data + 6));
|
||||
if ((frag_off & (IP_FRAG_MF_MASK | IP_FRAG_OFF_MASK)) != 0) return 0;
|
||||
|
||||
uint8_t proto = ip_data[IP_PROTO_OFFSET];
|
||||
uint32_t src_ip_net, src_ip_host;
|
||||
memcpy(&src_ip_net, ip_data + IP_SRC_ADDR_OFFSET, 4);
|
||||
src_ip_host = ntohl(src_ip_net);
|
||||
|
||||
uint8_t* transport = ip_data + ip_hdr_len;
|
||||
size_t tlen = ip_len - ip_hdr_len;
|
||||
uint16_t src_port_net = 0;
|
||||
int is_tcp = (proto == IPPROTO_TCP_UINT8 && tlen >= 20);
|
||||
int is_udp = (proto == IPPROTO_UDP_UINT8 && tlen >= 8);
|
||||
int is_icmp_echo = 0;
|
||||
|
||||
if (is_tcp) memcpy(&src_port_net, transport + TCP_SRC_PORT_OFFSET, 2);
|
||||
else if (is_udp) memcpy(&src_port_net, transport + UDP_SRC_PORT_OFFSET, 2);
|
||||
else if (proto == IPPROTO_ICMP_UINT8 && tlen >= 8) {
|
||||
uint8_t icmp_type = transport[ICMP_TYPE_OFFSET];
|
||||
if (icmp_type == ICMP_ECHO_REQUEST || icmp_type == ICMP_ECHO_REPLY) {
|
||||
is_icmp_echo = 1; memcpy(&src_port_net, transport + ICMP_ID_OFFSET, 2);
|
||||
}
|
||||
}
|
||||
if (!is_tcp && !is_udp && !is_icmp_echo) return 0;
|
||||
|
||||
struct eim_nat_entry* entry = eim_nat_find_egress(ctx, proto, src_ip_host, src_port_net);
|
||||
uint16_t ext_port_host;
|
||||
if (entry) {
|
||||
ext_port_host = (uint16_t)(entry - ctx->table);
|
||||
} else {
|
||||
ext_port_host = eim_nat_alloc_port(ctx);
|
||||
if (ext_port_host == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT port range exhausted (%s:%u proto=%u)",
|
||||
ip_host_to_str(src_ip_host).str, ntohs(src_port_net), proto);
|
||||
return -1;
|
||||
}
|
||||
entry = &ctx->table[ext_port_host];
|
||||
entry->internal_ip = src_ip_host;
|
||||
entry->internal_port = src_port_net;
|
||||
entry->proto = proto;
|
||||
entry->state = EIM_NAT_ENTRY_ACTIVE;
|
||||
entry->src_node_id = src_node_id;
|
||||
entry->src_conn = src_conn;
|
||||
entry->last_seen = 0;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT map: %s:%u -> %s:%u (proto=%u) from node %016llx",
|
||||
ip_host_to_str(src_ip_host).str, ntohs(src_port_net),
|
||||
ip_host_to_str(ctx->gateway_ip).str, ext_port_host, proto,
|
||||
(unsigned long long)src_node_id);
|
||||
}
|
||||
|
||||
uint32_t gw_ip_net = htonl(ctx->gateway_ip);
|
||||
memcpy(ip_data + IP_SRC_ADDR_OFFSET, &gw_ip_net, 4);
|
||||
csum_update_ip(ip_data, src_ip_host, ctx->gateway_ip);
|
||||
|
||||
uint16_t new_port_net = htons(ext_port_host);
|
||||
if (is_tcp) {
|
||||
memcpy(transport + TCP_SRC_PORT_OFFSET, &new_port_net, 2);
|
||||
csum_update_transport(transport, TCP_CHECKSUM_OFFSET, src_ip_host, ctx->gateway_ip, src_port_net, new_port_net);
|
||||
} else if (is_udp) {
|
||||
memcpy(transport + UDP_SRC_PORT_OFFSET, &new_port_net, 2);
|
||||
csum_update_transport(transport, UDP_CHECKSUM_OFFSET, src_ip_host, ctx->gateway_ip, src_port_net, new_port_net);
|
||||
} else if (is_icmp_echo) {
|
||||
memcpy(transport + ICMP_ID_OFFSET, &new_port_net, 2);
|
||||
csum_update_icmp(transport, src_port_net, new_port_net);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int eim_nat_ingress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
|
||||
struct eim_nat_entry** out_entry) {
|
||||
if (ip_len < IP_HDR_MIN_SIZE) return -1;
|
||||
uint8_t ihl = ip_data[IP_IHL_OFFSET] & 0x0F;
|
||||
if (ihl < 5) return -1;
|
||||
uint16_t ip_hdr_len = ihl * 4;
|
||||
if (ip_len < ip_hdr_len) return -1;
|
||||
|
||||
uint16_t frag_off = ntohs(*(uint16_t*)(ip_data + 6));
|
||||
if ((frag_off & (IP_FRAG_MF_MASK | IP_FRAG_OFF_MASK)) != 0) return 0;
|
||||
|
||||
uint8_t proto = ip_data[IP_PROTO_OFFSET];
|
||||
uint32_t dst_ip_net;
|
||||
memcpy(&dst_ip_net, ip_data + IP_DST_ADDR_OFFSET, 4);
|
||||
if (dst_ip_net != htonl(ctx->gateway_ip)) return 0;
|
||||
|
||||
uint8_t* transport = ip_data + ip_hdr_len;
|
||||
size_t tlen = ip_len - ip_hdr_len;
|
||||
uint16_t dst_port_net = 0;
|
||||
int is_tcp = (proto == IPPROTO_TCP_UINT8 && tlen >= 20);
|
||||
int is_udp = (proto == IPPROTO_UDP_UINT8 && tlen >= 8);
|
||||
int is_icmp_echo = 0;
|
||||
|
||||
if (is_tcp) memcpy(&dst_port_net, transport + TCP_DST_PORT_OFFSET, 2);
|
||||
else if (is_udp) memcpy(&dst_port_net, transport + UDP_DST_PORT_OFFSET, 2);
|
||||
else if (proto == IPPROTO_ICMP_UINT8 && tlen >= 8) {
|
||||
uint8_t icmp_type = transport[ICMP_TYPE_OFFSET];
|
||||
if (icmp_type == ICMP_ECHO_REQUEST || icmp_type == ICMP_ECHO_REPLY) {
|
||||
is_icmp_echo = 1; memcpy(&dst_port_net, transport + ICMP_ID_OFFSET, 2);
|
||||
}
|
||||
}
|
||||
if (!is_tcp && !is_udp && !is_icmp_echo) return 0;
|
||||
|
||||
uint16_t ext_port_host = ntohs(dst_port_net);
|
||||
if (ext_port_host < ctx->port_start || ext_port_host > ctx->port_end) return 0;
|
||||
|
||||
struct eim_nat_entry* entry = &ctx->table[ext_port_host];
|
||||
if (entry->state == EIM_NAT_ENTRY_FREE) return 0;
|
||||
if (entry->proto != proto) return 0;
|
||||
|
||||
uint32_t dst_ip_host = ntohl(dst_ip_net);
|
||||
uint32_t internal_ip_net = htonl(entry->internal_ip);
|
||||
memcpy(ip_data + IP_DST_ADDR_OFFSET, &internal_ip_net, 4);
|
||||
csum_update_ip(ip_data, dst_ip_host, entry->internal_ip);
|
||||
|
||||
uint16_t internal_port_net = entry->internal_port;
|
||||
if (is_tcp) {
|
||||
memcpy(transport + TCP_DST_PORT_OFFSET, &internal_port_net, 2);
|
||||
csum_update_transport(transport, TCP_CHECKSUM_OFFSET, dst_ip_host, entry->internal_ip, dst_port_net, internal_port_net);
|
||||
} else if (is_udp) {
|
||||
memcpy(transport + UDP_DST_PORT_OFFSET, &internal_port_net, 2);
|
||||
csum_update_transport(transport, UDP_CHECKSUM_OFFSET, dst_ip_host, entry->internal_ip, dst_port_net, internal_port_net);
|
||||
} else if (is_icmp_echo) {
|
||||
memcpy(transport + ICMP_ID_OFFSET, &internal_port_net, 2);
|
||||
csum_update_icmp(transport, dst_port_net, internal_port_net);
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT unmap: %s:%u <- %s:%u (proto=%u) back to node %016llx",
|
||||
ip_host_to_str(entry->internal_ip).str, ntohs(entry->internal_port),
|
||||
ip_host_to_str(ctx->gateway_ip).str, ext_port_host, proto,
|
||||
(unsigned long long)entry->src_node_id);
|
||||
|
||||
if (out_entry) *out_entry = entry;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ==================== Init / Destroy ====================
|
||||
|
||||
int eim_nat_init_ctx(struct eim_nat_ctx* ctx, const struct global_config* g) {
|
||||
if (!ctx || !g) return -1;
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
|
||||
if (!g->nat_enabled) return 0;
|
||||
|
||||
if (g->nat_tun_ip.family == AF_INET) {
|
||||
ctx->gateway_ip = ntohl(g->nat_tun_ip.addr.v4.s_addr);
|
||||
} else if (g->tun_ip.family == AF_INET) {
|
||||
ctx->gateway_ip = ntohl(g->tun_ip.addr.v4.s_addr);
|
||||
} else {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "No valid IP for NAT gateway");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ctx->port_start = g->nat_port_start;
|
||||
ctx->port_end = g->nat_port_end;
|
||||
if (ctx->port_start == 0 || ctx->port_end == 0 || ctx->port_start >= ctx->port_end) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Invalid NAT port range: %u-%u", ctx->port_start, ctx->port_end);
|
||||
return -1;
|
||||
}
|
||||
ctx->next_port = ctx->port_start;
|
||||
|
||||
ctx->table_size = EIM_NAT_TABLE_SIZE;
|
||||
ctx->table = u_calloc(ctx->table_size, sizeof(struct eim_nat_entry));
|
||||
if (!ctx->table) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Failed to allocate NAT table"); return -1; }
|
||||
|
||||
for (int i = 0; i < g->nat_forward_count; i++) {
|
||||
uint16_t ext = g->nat_forwards[i].external_port;
|
||||
if (ext >= ctx->table_size) continue;
|
||||
struct eim_nat_entry* e = &ctx->table[ext];
|
||||
e->internal_ip = g->nat_forwards[i].internal_ip_host;
|
||||
e->internal_port = g->nat_forwards[i].internal_port_net;
|
||||
e->proto = g->nat_forwards[i].proto;
|
||||
e->state = EIM_NAT_ENTRY_STATIC;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Port forward: %s:%u <- :%u (proto=%u)",
|
||||
ip_host_to_str(e->internal_ip).str, ntohs(e->internal_port), ext, e->proto);
|
||||
}
|
||||
|
||||
ctx->initialized = 1;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT engine initialized: gw=%s ports=%u-%u",
|
||||
ip_host_to_str(ctx->gateway_ip).str, ctx->port_start, ctx->port_end);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void eim_nat_destroy_ctx(struct eim_nat_ctx* ctx) {
|
||||
if (!ctx || !ctx->initialized) return;
|
||||
u_free(ctx->table);
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT engine destroyed");
|
||||
}
|
||||
|
||||
int eim_nat_add_forward(struct eim_nat_ctx* ctx, uint8_t proto,
|
||||
uint32_t internal_ip_host, uint16_t internal_port_net,
|
||||
uint16_t external_port) {
|
||||
if (!ctx || !ctx->initialized || external_port >= ctx->table_size) return -1;
|
||||
if (ctx->table[external_port].state != EIM_NAT_ENTRY_FREE) return -1;
|
||||
struct eim_nat_entry* e = &ctx->table[external_port];
|
||||
e->internal_ip = internal_ip_host;
|
||||
e->internal_port = internal_port_net;
|
||||
e->proto = proto;
|
||||
e->state = EIM_NAT_ENTRY_STATIC;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "Port forward: %s:%u <- :%u (proto=%u)",
|
||||
ip_host_to_str(internal_ip_host).str, ntohs(internal_port_net), external_port, proto);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef EIM_NAT_H
|
||||
#define EIM_NAT_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct ETCP_CONN;
|
||||
|
||||
#define EIM_NAT_TABLE_SIZE 65536
|
||||
|
||||
#define EIM_NAT_ENTRY_FREE 0
|
||||
#define EIM_NAT_ENTRY_ACTIVE 1
|
||||
#define EIM_NAT_ENTRY_STATIC 2
|
||||
|
||||
#define IPPROTO_TCP_UINT8 6
|
||||
#define IPPROTO_UDP_UINT8 17
|
||||
#define IPPROTO_ICMP_UINT8 1
|
||||
|
||||
struct eim_nat_entry {
|
||||
uint32_t internal_ip;
|
||||
uint16_t internal_port;
|
||||
uint8_t proto;
|
||||
uint8_t state;
|
||||
uint64_t src_node_id; // which node sent the original packet
|
||||
uint64_t last_seen;
|
||||
struct ETCP_CONN* src_conn; // cached connection for sendback
|
||||
};
|
||||
|
||||
// Pure NAT engine state (no transport/TUN/ETCP fields)
|
||||
struct eim_nat_ctx {
|
||||
uint32_t gateway_ip;
|
||||
uint16_t port_start;
|
||||
uint16_t port_end;
|
||||
uint16_t next_port;
|
||||
struct eim_nat_entry* table;
|
||||
size_t table_size;
|
||||
int initialized;
|
||||
};
|
||||
|
||||
struct global_config;
|
||||
|
||||
int eim_nat_init_ctx(struct eim_nat_ctx* ctx, const struct global_config* g);
|
||||
void eim_nat_destroy_ctx(struct eim_nat_ctx* ctx);
|
||||
|
||||
int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
|
||||
uint64_t src_node_id, struct ETCP_CONN* src_conn);
|
||||
int eim_nat_ingress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
|
||||
struct eim_nat_entry** out_entry);
|
||||
|
||||
int eim_nat_add_forward(struct eim_nat_ctx* ctx, uint8_t proto,
|
||||
uint32_t internal_ip_host, uint16_t internal_port_net,
|
||||
uint16_t external_port);
|
||||
|
||||
#endif
|
||||
+596
-174
File diff suppressed because it is too large.
Load diff
+42
-5
@@ -13,6 +13,7 @@ extern "C" {
|
||||
|
||||
#include "pkt_normalizer.h"
|
||||
|
||||
struct stcp_link; // forward declaration
|
||||
// In struct ETCP_CONN, add:
|
||||
//struct pn_pair* normalizer;
|
||||
|
||||
@@ -28,15 +29,38 @@ uint16_t get_current_timestamp(void);
|
||||
#define ETCP_SECTION_PAYLOAD 0x00 // Data payload
|
||||
#define ETCP_SECTION_ACK 0x01 // ACK section
|
||||
#define ETCP_SECTION_TIMESTAMP 0x06 // Channel timestamp (example, adjust if needed)
|
||||
#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth
|
||||
#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response
|
||||
#define ETCP_SECTION_MEAS_TS 0x07 // Measurement timestamp for bandwidth (burst packet)
|
||||
#define ETCP_SECTION_MEAS_RESP 0x08 // Measurement response (burst result)
|
||||
#define ETCP_SECTION_FILLER 0x09 // Filler/dummy data (discarded by receiver)
|
||||
|
||||
// Inflight packet states
|
||||
// Burst measurement constants
|
||||
#define BURST_PACKET_COUNT 12 // число пакетов в burst
|
||||
#define BURST_SKIP_COUNT 3 // сколько первых пакетов пропустить при замере gap
|
||||
#define MIN_BURST_INTERVAL_TB 5000 // минимальный интервал между burst (500ms в 0.1ms)
|
||||
#define BURST_RESP_TIMEOUT_TB 20000 // таймаут ожидания ответа на burst (2s в 0.1ms)
|
||||
|
||||
// Inflight phases
|
||||
#define INFLIGHT_PHASE_SLOW_START 0
|
||||
#define INFLIGHT_PHASE_CONG_AVOIDANCE 1
|
||||
|
||||
// Section sizes
|
||||
#define MEAS_TS_SECTION_SIZE 9 // type(1) + burst_id(2) + flags(1) + seq(1) + ts_us(2) + pkt_sz(2)
|
||||
#define MEAS_RESP_SECTION_SIZE 13 // type(1) + burst_id(2) + valid(1) + gap_avg(4) + gap_min(4) + pkt_count(1)
|
||||
#define FILLER_HDR_SIZE 3 // type(1) + len(2)
|
||||
|
||||
// MEAS_TS flags
|
||||
#define MEAS_FLAG_IS_FILLER 0x01 // пакет содержит FILLER вместо PAYLOAD
|
||||
#define MEAS_FLAG_IS_LAST 0x02 // последний пакет в burst
|
||||
#define MEAS_FLAG_IS_FIRST 0x04 // первый пакет в burst
|
||||
|
||||
// MEAS_RESP valid
|
||||
#define MEAS_RESP_VALID 1
|
||||
#define INFLIGHT_STATE_WAIT_ACK 0
|
||||
#define INFLIGHT_STATE_WAIT_SEND 1
|
||||
#define INFLIGHT_INITIAL_HASH_SIZE 1024
|
||||
|
||||
#define MAX_INFLIGHT_SIZE 16384 // максимальное число элементов в inflight приёмной очереди (для предотвращения атак)
|
||||
#define ASM_BUF_MAX_SIZE (64 * 1024) // максимальный размер буфера сборки фрагментов
|
||||
|
||||
// в этот список пакет добавляется когда перемещается из input_queue в input_send_q, при этом к пакету добавляется struct INFLIGHT_PACKET из inflight_pool.
|
||||
// пакет полностью удаляется когда приходит ACK (либо conn_reset/close)
|
||||
@@ -49,6 +73,9 @@ struct INFLIGHT_PACKET {// выделяется из etcp->inflight_pool
|
||||
uint8_t retrans_req_count; // Number of retrans requests
|
||||
uint8_t state; // WAIT_ACK or WAIT_SEND
|
||||
uint8_t send_hist[8]; // через какие каналы передавался пакет (NEW). send_count - head ptr
|
||||
uint64_t delivered_at_send; // link->delivered_bytes на момент отправки (для BBR prior_delivered)
|
||||
uint32_t inflight_at_send; // link->inflight_bytes на момент отправки (для BBR tx_in_flight)
|
||||
uint8_t is_app_limited; // данные ограничены приложением на момент отправки
|
||||
};
|
||||
|
||||
// Список пакетов для сборки. собирается в ll_queue (используем быстрый поиск с хешем)
|
||||
@@ -77,6 +104,8 @@ struct ETCP_CONN {
|
||||
|
||||
// Links (channels) - linked list
|
||||
struct ETCP_LINK* links;
|
||||
struct ETCP_LINK* last_rr_link; // последний линк, выбранный round-robin
|
||||
struct stcp_link *transport_link; // non-NULL for TCP connections
|
||||
|
||||
// Crypto and state
|
||||
struct secure_channel crypto_ctx;
|
||||
@@ -104,7 +133,7 @@ struct ETCP_CONN {
|
||||
|
||||
|
||||
// IDs and state
|
||||
uint32_t next_tx_id; // Next TX ID
|
||||
uint32_t next_tx_id; // ID для добавления в очередь отправки (с этим id будет добавлен следующий пакет)
|
||||
uint32_t last_rx_id; // Last received ID
|
||||
uint32_t last_delivered_id; // Last delivered to output_queue
|
||||
|
||||
@@ -127,7 +156,7 @@ struct ETCP_CONN {
|
||||
|
||||
// Window and inflight management
|
||||
uint32_t unacked_bytes; // Current inflight bytes
|
||||
// uint32_t window_size; // Receive window - Not used
|
||||
uint32_t max_inflight; // Max inflight cap from config (bbr_max_cwnd)
|
||||
uint32_t optimal_inflight; // Sum over links
|
||||
|
||||
// Timers
|
||||
@@ -149,6 +178,7 @@ struct ETCP_CONN {
|
||||
uint8_t routing_exchange_active; // 0 - не активен, 1 - надо инициировать обмен маршрутами (клиент), 2 - обмен маршрутами активен
|
||||
uint8_t got_initial_pkt; //
|
||||
uint8_t initialized; // 0 - только созданный ETCP, 1 - хотя бы один линк проинициалзирован (обмен ключами произведен)
|
||||
uint32_t session_id; // случайный ID сессии (генерируется клиентом) для защиты от ложного reinit
|
||||
uint8_t tx_state; // 0 - n/a, 1 - data_wait (queues empty), 2 - link_wait (link busy)
|
||||
uint8_t links_up; // 0 - канал не готов для передачи, 1 - канал готов для передачи (хотя бы один линк не down)
|
||||
|
||||
@@ -164,6 +194,8 @@ struct ETCP_CONN {
|
||||
uint32_t cnt_ack_hit_sndq; // счетчик удалений inflight пакетов из sndq
|
||||
uint32_t cnt_ack_miss; // счетчик не найденных ack
|
||||
uint32_t cnt_link_wait; // счетчик переходов в ожидание когда link busy
|
||||
uint32_t rx_dup_count; // счетчик принятых дубликатов (локальные)
|
||||
uint32_t tx_dup_count; // счетчик дубликатов удаленной стороны (из ACK)
|
||||
uint32_t debug[8]; // 8 значений для дебага (live watch)
|
||||
|
||||
// Logging identifier (format: "XXXX→XXXX [name]" - last 4 digits of local and peer node_id + optional name)
|
||||
@@ -200,6 +232,9 @@ struct ETCP_DGRAM* etcp_request_pkt(struct ETCP_CONN* etcp);
|
||||
// Process ACK receipt - remove acknowledged packet from inflight queues
|
||||
void etcp_ack_recv(struct ETCP_CONN* etcp, uint32_t seq, uint16_t ts, uint16_t dts);
|
||||
|
||||
// Recalculate optimal_inflight and resume input queue after link inflight_lim change
|
||||
void etcp_conn_on_inflight_lim_changed(struct ETCP_CONN* etcp);
|
||||
|
||||
// Update log_name when peer_node_id becomes known
|
||||
void etcp_update_log_name(struct ETCP_CONN* etcp);
|
||||
|
||||
@@ -208,6 +243,8 @@ void etcp_conn_ready(struct ETCP_CONN* conn);
|
||||
|
||||
void etcp_on_link_down(struct ETCP_CONN* etcp);
|
||||
|
||||
void etcp_update_mtu(struct ETCP_CONN* etcp);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
+31
-170
@@ -1,204 +1,65 @@
|
||||
/**
|
||||
* @file etcp_api.c
|
||||
* @brief Реализация API для приёма-передачи пакетов через ETCP (per-instance bindings)
|
||||
* @brief ETCP API — send/bind/recv dispatch
|
||||
*/
|
||||
|
||||
#include "etcp_api.h"
|
||||
#include "etcp.h"
|
||||
#include "pkt_normalizer.h"
|
||||
#include "utun_instance.h"
|
||||
#include "stcp_link.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <string.h>
|
||||
#include "route_lib.h"
|
||||
#include "route_bgp.h"
|
||||
|
||||
#define DEBUG_CATEGORY_ETCP_API 1
|
||||
#define DEBUG_CATEGORY_ETCP_API DEBUG_CATEGORY_ETCP
|
||||
|
||||
#define ETCP_MAX_BINDINGS 256 /**< Максимальное количество bindings (по одному на каждый ID 0-255) */
|
||||
|
||||
// после создания подключения надо выждать conn_ready. только после этого можно передавать сообщения. Иначе сообщения могут потеряться.
|
||||
// conn_ready может вызваться несколько раз (после каждого переподключения - например когда удаленный узел перегрузился или возникла неустранимая ошибка).
|
||||
void etcp_conn_set_ready_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->ready_cbk = callback_fn;
|
||||
etcp->ready_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
void etcp_conn_set_ready_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->ready_cbk = fn; e->ready_arg = arg; }
|
||||
}
|
||||
|
||||
void etcp_conn_set_up_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->up_cbk = callback_fn;
|
||||
etcp->up_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
void etcp_conn_set_up_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->up_cbk = fn; e->up_arg = arg; }
|
||||
}
|
||||
|
||||
void etcp_conn_set_down_cbk(struct ETCP_CONN* etcp, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!etcp) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "NULL conn"); return; }
|
||||
etcp->down_cbk = callback_fn;
|
||||
etcp->down_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "conn=%s cbk=%p arg=%p", etcp->log_name, (void*)callback_fn, arg);
|
||||
void etcp_conn_set_down_cbk(struct ETCP_CONN* e, etcp_cbk_fn fn, void* arg) {
|
||||
if (e) { e->down_cbk = fn; e->down_arg = arg; }
|
||||
}
|
||||
|
||||
// Установить callback при создании нового ETCP соединения
|
||||
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* instance, etcp_cbk_fn callback_fn, void* arg) {
|
||||
if (!instance) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_set_new_conn_cbk: NULL instance");
|
||||
return;
|
||||
}
|
||||
instance->etcp_new_conn_cbk = callback_fn;
|
||||
instance->etcp_new_conn_arg = arg;
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP_API, "etcp_set_new_conn_cbk: instance=%p cbk=%p arg=%p",
|
||||
(void*)instance, (void*)callback_fn, arg);
|
||||
void etcp_set_new_conn_cbk(struct UTUN_INSTANCE* inst, etcp_cbk_fn fn, void* arg) {
|
||||
if (inst) { inst->etcp_new_conn_cbk = fn; inst->etcp_new_conn_arg = arg; }
|
||||
}
|
||||
|
||||
int etcp_bind(struct UTUN_INSTANCE* inst, uint8_t id, etcp_recv_fn callback) {
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Invalid ID %u (max=%d)", id, ETCP_MAX_BINDINGS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_bind: NULL callback for id=%u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] != NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_bind: ID %u already bound, overwriting", id);
|
||||
}
|
||||
|
||||
if (!inst || !callback || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
inst->api_bindings.callbacks[id] = callback;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_bind: Bound ID %u to callback %p for instance %p",
|
||||
id, (void*)callback, (void*)inst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_unbind(struct UTUN_INSTANCE* inst, uint8_t id) {
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: NULL instance");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (id >= ETCP_MAX_BINDINGS) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Invalid ID %u", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (inst->api_bindings.callbacks[id] == NULL) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: ID %u not bound", id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!inst || (unsigned)id >= ETCP_MAX_BINDINGS) return -1;
|
||||
inst->api_bindings.callbacks[id] = NULL;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP_API, "etcp_unbind: Unbound ID %u for instance %p", id, (void*)inst);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_send(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL connection");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: NULL entry");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!conn->normalizer) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Connection has no normalizer");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct PKTNORM* pn = conn->normalizer;
|
||||
|
||||
if (!pn->input) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_send: Normalizer has no input queue");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Помещаем entry в очередь input normalizer
|
||||
// queue_data_put забирает ownership entry
|
||||
// DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "Before put to input");
|
||||
int result = queue_data_put(pn->input, entry);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_NORMALIZER, "After put to input");
|
||||
|
||||
if (result != 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_send: queue_data_put failed (queue full?)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_send: Packet queued for sending to [%s]", conn->log_name);
|
||||
return 0;
|
||||
if (!conn || !entry) return -1;
|
||||
// TCP (stcp_link) — send directly
|
||||
if (conn->transport_link)
|
||||
return stcp_link_send((struct stcp_link *)conn->transport_link, entry->dgram, entry->len);
|
||||
// UDP — через normalizer
|
||||
if (!conn->normalizer || !conn->normalizer->input) return -1;
|
||||
return queue_data_put(conn->normalizer->input, entry);
|
||||
}
|
||||
|
||||
void etcp_int_recv(struct ll_queue* queue, void* arg) {
|
||||
struct ETCP_CONN* conn = (struct ETCP_CONN*)arg;
|
||||
|
||||
if (!queue || !conn) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Invalid arguments (queue=%p, conn=%p)",
|
||||
(void*)queue, (void*)conn);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем элемент из очереди
|
||||
struct ll_entry* entry = queue_data_get(queue);
|
||||
if (!entry) {
|
||||
// Очередь пуста - это нормально, просто возобновляем коллбэк
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем что есть данные
|
||||
if (!entry->dgram || entry->len == 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Empty packet received");
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем ID пакета (первый байт кодограммы)
|
||||
uint8_t id = entry->dgram[0];
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Received packet with ID %u from conn [%s] %p",
|
||||
id, conn->log_name, (void*)conn);
|
||||
|
||||
// Получаем instance из соединения
|
||||
if (!queue || !conn) return;
|
||||
struct ll_entry* e = queue_data_get(queue);
|
||||
if (!e) { queue_resume_callback(queue); return; }
|
||||
if (!e->dgram || e->len == 0) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; }
|
||||
uint8_t id = e->dgram[0];
|
||||
struct UTUN_INSTANCE* inst = conn->instance;
|
||||
if (!inst) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Connection has no instance");
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
queue_resume_callback(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ищем binding для этого ID в instance
|
||||
if (id < ETCP_MAX_BINDINGS && inst->api_bindings.callbacks[id] != NULL) {
|
||||
// Вызываем коллбэк
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: Delivering packet ID %u to callback %p",
|
||||
id, (void*)inst->api_bindings.callbacks[id]);
|
||||
inst->api_bindings.callbacks[id](conn, entry);
|
||||
} else {
|
||||
// Нет binding для этого ID - по умолчанию считаем что это данные (ID=0)
|
||||
if (inst->api_bindings.callbacks[0] != NULL) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u, routing to default (ID=0)", id);
|
||||
inst->api_bindings.callbacks[0](conn, entry);
|
||||
} else {
|
||||
// Нет default binding - освобождаем пакет
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP_API, "etcp_recv: No binding for ID %u and no default handler, dropping packet", id);
|
||||
queue_entry_free(entry);
|
||||
queue_dgram_free(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Возобновляем коллбэк для получения следующего пакета
|
||||
if (!inst) { queue_entry_free(e); queue_dgram_free(e); queue_resume_callback(queue); return; }
|
||||
if (inst->api_bindings.callbacks[id])
|
||||
inst->api_bindings.callbacks[id](conn, e);
|
||||
else if (inst->api_bindings.callbacks[0])
|
||||
inst->api_bindings.callbacks[0](conn, e);
|
||||
else { queue_entry_free(e); queue_dgram_free(e); }
|
||||
queue_resume_callback(queue);
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
* - etcp_send() - отправить пакет в очередь normalizer
|
||||
* - etcp_bind() - подписаться на пакеты с определенным ID
|
||||
* - etcp_int_recv() - коллбэк для сбора пакетов из всех подключений
|
||||
* !!! для обычной отправки-приёма между узлами используем более универсальный модушь etcp_router.
|
||||
* это в первую очередь - апи для использования etcp-router-ом
|
||||
*
|
||||
* Формат кодограмм: <cmd 1 byte> <data ... n bytes>
|
||||
* cmd = 0 - пакет для передачи адресату
|
||||
@@ -23,6 +25,14 @@
|
||||
// ETCP packet IDs
|
||||
#define ETCP_ID_DATA 0x00 // Пакет для передачи адресату
|
||||
#define ETCP_ID_ROUTE_ENTRY 0x01 // Элемент роутинг-таблицы
|
||||
#define ETCP_ID_NAT 0x02 // NAT трафик между узлами
|
||||
#define ETCP_ID_SVC_ROUTE 0x03 // Маршрутизируемые сервисные пакеты (etcp_router)
|
||||
#define ETCP_ID_TCP_PROXY 0x04 // TCP proxy exit node (server)
|
||||
#define ETCP_ID_UDP_PROXY 0x05 // UDP datagram прокси (client ↔ exit)
|
||||
#define ETCP_ID_ICMP_PROXY 0x06 // ICMP echo прокси (ping через exit)
|
||||
#define ETCP_ID_TCP_PROXY_CLIENT 0x07 // TCP proxy client (клиент)
|
||||
#define ETCP_ID_MSG_TRANSPORT 0x10 // msg_transport — локальный IPC транспорт сообщений
|
||||
#define ETCP_ID_CONN_MGR 0x11 // Connection Manager — management connections
|
||||
|
||||
// Forward declarations
|
||||
struct ETCP_CONN;
|
||||
|
||||
+938
@@ -0,0 +1,938 @@
|
||||
#include "etcp_bbr.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static uint64_t bbr_now(const struct bbr* bbr) { return bbr->now_tb ? bbr->now_tb : get_time_tb(); }
|
||||
|
||||
static void bbr_start_bw_probe_down(struct bbr* bbr);
|
||||
static void bbr_start_bw_probe_cruise(struct bbr* bbr);
|
||||
static void bbr_exit_probe_rtt(struct bbr* bbr);
|
||||
|
||||
static const int bbr_pacing_gain[] = {
|
||||
BBR_UNIT * 5 / 4, /* PROBE_UP: 1.25 */
|
||||
BBR_UNIT * 3 / 4, /* PROBE_DOWN: 0.75 */
|
||||
BBR_UNIT, /* CRUISE: 1.00 */
|
||||
BBR_UNIT, /* REFILL: 1.00 */
|
||||
};
|
||||
|
||||
static uint32_t bbr_max_bw(const struct bbr* bbr)
|
||||
{
|
||||
return (uint32_t)(bbr->bw_hi[0] > bbr->bw_hi[1] ? bbr->bw_hi[0] : bbr->bw_hi[1]);
|
||||
}
|
||||
|
||||
static uint32_t bbr_bw(const struct bbr* bbr)
|
||||
{
|
||||
uint32_t max_bw = bbr_max_bw(bbr);
|
||||
return (uint32_t)(max_bw < bbr->bw_lo ? max_bw : bbr->bw_lo);
|
||||
}
|
||||
|
||||
static int bbr_full_bw_reached(const struct bbr* bbr)
|
||||
{
|
||||
return bbr->full_bw_reached;
|
||||
}
|
||||
|
||||
static uint16_t bbr_extra_acked(const struct bbr* bbr)
|
||||
{
|
||||
return (uint16_t)(bbr->extra_acked[0] > bbr->extra_acked[1] ?
|
||||
bbr->extra_acked[0] : bbr->extra_acked[1]);
|
||||
}
|
||||
|
||||
static uint64_t bbr_rate_bytes_per_sec(uint64_t rate, int gain, int margin, uint32_t mss)
|
||||
{
|
||||
rate *= mss;
|
||||
rate *= (uint64_t)gain;
|
||||
rate >>= BBR_SCALE;
|
||||
rate *= (uint64_t)(1000000 / 100) * (uint64_t)(100 - margin);
|
||||
rate >>= BW_SCALE;
|
||||
if (rate < 1) rate = 1;
|
||||
return rate;
|
||||
}
|
||||
|
||||
static uint32_t bbr_bw_to_pacing_rate(uint32_t bw, int gain, uint32_t mss)
|
||||
{
|
||||
uint64_t rate = bbr_rate_bytes_per_sec(bw, gain, 1, mss);
|
||||
return (uint32_t)rate;
|
||||
}
|
||||
|
||||
static uint32_t bbr_bdp(const struct bbr* bbr, uint32_t bw, int gain, uint32_t mss)
|
||||
{
|
||||
uint64_t w;
|
||||
if (bbr->min_rtt_us == ~0U)
|
||||
return (uint32_t)bbr->init_cwnd * mss;
|
||||
w = (uint64_t)bw * bbr->min_rtt_us;
|
||||
return (uint32_t)((((w * (uint64_t)gain) >> BBR_SCALE) + BW_UNIT - 1) / BW_UNIT) * mss;
|
||||
}
|
||||
|
||||
static uint32_t bbr_quantization_budget(struct bbr* bbr, uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
cwnd = (uint32_t)(cwnd > BBR_CWND_MIN_TARGET * mss ? cwnd : BBR_CWND_MIN_TARGET * mss);
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
|
||||
cwnd += 2;
|
||||
return cwnd;
|
||||
}
|
||||
|
||||
static uint32_t bbr_inflight(const struct bbr* bbr, uint32_t bw, int gain, uint32_t mss)
|
||||
{
|
||||
uint32_t inflight = bbr_bdp(bbr, bw, gain, mss);
|
||||
return bbr_quantization_budget((struct bbr*)bbr, inflight, mss);
|
||||
}
|
||||
|
||||
static uint32_t bbr_ack_aggregation_cwnd(const struct bbr* bbr, uint32_t mss)
|
||||
{
|
||||
uint32_t max_aggr_cwnd, aggr_cwnd = 0;
|
||||
(void)mss;
|
||||
max_aggr_cwnd = (uint32_t)((uint64_t)bbr_bw(bbr) * 100000U / BW_UNIT);
|
||||
aggr_cwnd = (uint32_t)((uint32_t)BBR_EXTRA_ACKED_GAIN * bbr_extra_acked(bbr) >> BBR_SCALE);
|
||||
aggr_cwnd = (uint32_t)(aggr_cwnd < max_aggr_cwnd ? aggr_cwnd : max_aggr_cwnd);
|
||||
return aggr_cwnd;
|
||||
}
|
||||
|
||||
static uint32_t bbr_target_inflight(const struct bbr* bbr, uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t bdp = bbr_inflight(bbr, bbr_bw(bbr), BBR_UNIT, mss);
|
||||
return (uint32_t)(bdp < cwnd ? bdp : cwnd);
|
||||
}
|
||||
|
||||
static int bbr_is_probing_bandwidth(const struct bbr* bbr)
|
||||
{
|
||||
return (bbr->mode == BBR_STARTUP) ||
|
||||
(bbr->mode == BBR_PROBE_BW &&
|
||||
(bbr->cycle_idx == BBR_BW_PROBE_REFILL ||
|
||||
bbr->cycle_idx == BBR_BW_PROBE_UP));
|
||||
}
|
||||
|
||||
static int bbr_has_elapsed_in_phase(const struct bbr* bbr, uint32_t interval_us)
|
||||
{
|
||||
uint64_t now_tb = bbr_now(bbr);
|
||||
uint64_t expire_tb = bbr->cycle_mstamp_tb + (uint64_t)(interval_us / 100U);
|
||||
return (int32_t)((uint32_t)expire_tb - (uint32_t)now_tb) <= 0;
|
||||
}
|
||||
|
||||
static uint32_t bbr_update_round_start(struct bbr* bbr, const struct bbr_rate_sample* rs)
|
||||
{
|
||||
uint32_t round_delivered = 0;
|
||||
bbr->round_start = 0;
|
||||
if (rs->interval_us > 0 && !((int32_t)(rs->prior_delivered - bbr->next_rtt_delivered) < 0)) {
|
||||
round_delivered = bbr->delivered - bbr->next_rtt_delivered;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr->round_start = 1;
|
||||
}
|
||||
return round_delivered;
|
||||
}
|
||||
|
||||
static uint32_t bbr_calculate_bw_sample(const struct bbr_rate_sample* rs)
|
||||
{
|
||||
if (rs->interval_us > 0)
|
||||
return (uint32_t)(((uint64_t)rs->delivered * BW_UNIT + rs->interval_us - 1) / rs->interval_us);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bbr_update_latest_delivery_signals(struct bbr* bbr,
|
||||
const struct bbr_rate_sample* rs, uint32_t sample_bw)
|
||||
{
|
||||
bbr->loss_round_start = 0;
|
||||
if (rs->interval_us <= 0 || !rs->acked_sacked)
|
||||
return;
|
||||
bbr->bw_latest = (uint32_t)(bbr->bw_latest > sample_bw ? bbr->bw_latest : sample_bw);
|
||||
bbr->inflight_latest = (uint32_t)(bbr->inflight_latest > (uint32_t)rs->delivered ?
|
||||
bbr->inflight_latest : (uint32_t)rs->delivered);
|
||||
if (!((int32_t)(rs->prior_delivered - bbr->loss_round_delivered) < 0)) {
|
||||
bbr->loss_round_delivered = bbr->delivered;
|
||||
bbr->loss_round_start = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_advance_latest_delivery_signals(struct bbr* bbr,
|
||||
const struct bbr_rate_sample* rs, uint32_t sample_bw)
|
||||
{
|
||||
if (bbr->loss_round_start) {
|
||||
bbr->bw_latest = sample_bw;
|
||||
bbr->inflight_latest = (uint32_t)rs->delivered;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_take_max_bw_sample(struct bbr* bbr, uint32_t bw)
|
||||
{
|
||||
bbr->bw_hi[1] = (uint32_t)(bw > bbr->bw_hi[1] ? bw : bbr->bw_hi[1]);
|
||||
}
|
||||
|
||||
static void bbr_init_lower_bounds(struct bbr* bbr, int init_bw, uint32_t cwnd)
|
||||
{
|
||||
if (init_bw && bbr->bw_lo == ~0U)
|
||||
bbr->bw_lo = bbr_max_bw(bbr);
|
||||
if (bbr->inflight_lo == ~0U)
|
||||
bbr->inflight_lo = cwnd;
|
||||
if (bbr->inflight_hi == ~0U)
|
||||
bbr->inflight_hi = (uint32_t)(cwnd > 4U * (uint32_t)bbr->init_cwnd ? cwnd : 4U * (uint32_t)bbr->init_cwnd);
|
||||
}
|
||||
|
||||
static void bbr_loss_lower_bounds(struct bbr* bbr)
|
||||
{
|
||||
uint32_t loss_cut = BBR_UNIT - BBR_BETA;
|
||||
bbr->bw_lo = (uint32_t)(bbr->bw_latest >
|
||||
(uint32_t)((uint64_t)bbr->bw_lo * loss_cut >> BBR_SCALE) ?
|
||||
bbr->bw_latest :
|
||||
(uint32_t)((uint64_t)bbr->bw_lo * loss_cut >> BBR_SCALE));
|
||||
bbr->inflight_lo = (uint32_t)(bbr->inflight_latest >
|
||||
(uint32_t)((uint64_t)bbr->inflight_lo * loss_cut >> BBR_SCALE) ?
|
||||
bbr->inflight_latest :
|
||||
(uint32_t)((uint64_t)bbr->inflight_lo * loss_cut >> BBR_SCALE));
|
||||
}
|
||||
|
||||
static void bbr_adapt_lower_bounds(struct bbr* bbr, uint32_t cwnd)
|
||||
{
|
||||
if (bbr_is_probing_bandwidth(bbr))
|
||||
return;
|
||||
if (bbr->loss_in_round) {
|
||||
bbr_init_lower_bounds(bbr, 1, cwnd);
|
||||
bbr_loss_lower_bounds(bbr);
|
||||
}
|
||||
bbr->bw_lo = (uint32_t)(bbr->bw_lo > 1U ? bbr->bw_lo : 1U);
|
||||
}
|
||||
|
||||
static void bbr_reset_lower_bounds(struct bbr* bbr)
|
||||
{
|
||||
bbr->bw_lo = ~0U;
|
||||
bbr->inflight_lo = ~0U;
|
||||
}
|
||||
|
||||
static void bbr_reset_congestion_signals(struct bbr* bbr)
|
||||
{
|
||||
bbr->loss_in_round = 0;
|
||||
bbr->loss_in_cycle = 0;
|
||||
bbr->bw_latest = 0;
|
||||
bbr->inflight_latest = 0;
|
||||
}
|
||||
|
||||
static int bbr_is_inflight_too_high(const struct bbr* bbr, const struct bbr_rate_sample* rs)
|
||||
{
|
||||
uint32_t loss_thresh;
|
||||
(void)bbr;
|
||||
if (rs->lost > 0 && rs->tx_in_flight) {
|
||||
loss_thresh = (uint32_t)((uint64_t)rs->tx_in_flight * BBR_LOSS_THRESH >> BBR_SCALE);
|
||||
if ((uint32_t)rs->lost > loss_thresh)
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bbr_handle_queue_too_high_in_startup(struct bbr* bbr, uint32_t mss)
|
||||
{
|
||||
uint32_t bdp;
|
||||
(void)mss;
|
||||
bbr->full_bw_reached = 1;
|
||||
bdp = bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss);
|
||||
bbr->inflight_hi = (uint32_t)(bdp > bbr->inflight_latest ? bdp : bbr->inflight_latest);
|
||||
}
|
||||
|
||||
static void bbr_check_loss_too_high_in_startup(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t mss)
|
||||
{
|
||||
if (bbr->full_bw_reached)
|
||||
return;
|
||||
if (rs->lost > 0 && bbr->loss_events_in_round < 0xf)
|
||||
bbr->loss_events_in_round++;
|
||||
if (bbr->loss_round_start &&
|
||||
bbr->loss_events_in_round >= BBR_FULL_LOSS_CNT &&
|
||||
bbr_is_inflight_too_high(bbr, rs)) {
|
||||
bbr_handle_queue_too_high_in_startup(bbr, mss);
|
||||
return;
|
||||
}
|
||||
if (bbr->loss_round_start)
|
||||
bbr->loss_events_in_round = 0;
|
||||
}
|
||||
|
||||
static void bbr_reset_full_bw(struct bbr* bbr)
|
||||
{
|
||||
bbr->full_bw = 0;
|
||||
bbr->full_bw_cnt = 0;
|
||||
bbr->full_bw_now = 0;
|
||||
}
|
||||
|
||||
static void bbr_advance_max_bw_filter(struct bbr* bbr)
|
||||
{
|
||||
if (!bbr->bw_hi[1])
|
||||
return;
|
||||
bbr->bw_hi[0] = bbr->bw_hi[1];
|
||||
bbr->bw_hi[1] = 0;
|
||||
}
|
||||
|
||||
static void bbr_check_full_bw_reached(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw)
|
||||
{
|
||||
uint32_t bw_thresh;
|
||||
if (bbr->full_bw_now || rs->is_app_limited)
|
||||
return;
|
||||
bw_thresh = (uint32_t)((uint64_t)bbr->full_bw * BBR_FULL_BW_THRESH >> BBR_SCALE);
|
||||
if (sample_bw >= bw_thresh) {
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->full_bw = sample_bw;
|
||||
return;
|
||||
}
|
||||
if (!bbr->round_start)
|
||||
return;
|
||||
bbr->full_bw_cnt = (uint8_t)(bbr->full_bw_cnt + 1);
|
||||
bbr->full_bw_now = (uint8_t)(bbr->full_bw_cnt >= BBR_FULL_BW_CNT ? 1 : 0);
|
||||
bbr->full_bw_reached |= bbr->full_bw_now;
|
||||
}
|
||||
|
||||
static void bbr_raise_inflight_hi_slope(struct bbr* bbr, uint32_t cwnd)
|
||||
{
|
||||
uint32_t growth_this_round = 1U << bbr->bw_probe_up_rounds;
|
||||
uint32_t cnt;
|
||||
bbr->bw_probe_up_rounds = (uint8_t)(bbr->bw_probe_up_rounds + 1 < 30 ?
|
||||
bbr->bw_probe_up_rounds + 1 : 30);
|
||||
cnt = cwnd / growth_this_round;
|
||||
cnt = (uint32_t)(cnt > 1U ? cnt : 1U);
|
||||
bbr->bw_probe_up_cnt = cnt;
|
||||
}
|
||||
|
||||
static void bbr_set_cycle_idx(struct bbr* bbr, int cycle_idx)
|
||||
{
|
||||
bbr->cycle_idx = (uint8_t)cycle_idx;
|
||||
bbr->try_fast_path = 0;
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_refill(struct bbr* bbr, uint32_t bw_probe_up_rounds)
|
||||
{
|
||||
bbr_reset_lower_bounds(bbr);
|
||||
bbr->bw_probe_up_rounds = (uint8_t)bw_probe_up_rounds;
|
||||
bbr->bw_probe_up_acks = 0;
|
||||
bbr->stopped_risky_probe = 0;
|
||||
bbr->ack_phase = BBR_ACKS_REFILLING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_REFILL);
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_up(struct bbr* bbr, uint32_t sample_bw, uint32_t cwnd)
|
||||
{
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_STARTING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr->cycle_mstamp_tb = bbr_now(bbr);
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->full_bw = sample_bw;
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_UP);
|
||||
bbr_raise_inflight_hi_slope(bbr, cwnd);
|
||||
}
|
||||
|
||||
static void bbr_save_cwnd(struct bbr* bbr, uint32_t cwnd)
|
||||
{
|
||||
if (bbr->mode != BBR_PROBE_RTT)
|
||||
bbr->prior_cwnd = cwnd;
|
||||
else
|
||||
bbr->prior_cwnd = (uint32_t)(bbr->prior_cwnd > cwnd ? bbr->prior_cwnd : cwnd);
|
||||
}
|
||||
|
||||
static void bbr_exit_probe_rtt(struct bbr* bbr)
|
||||
{
|
||||
bbr_reset_lower_bounds(bbr);
|
||||
if (bbr->full_bw_reached) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → PROBE_BW",
|
||||
bbr->link_id, bbr->mode, BBR_PROBE_BW);
|
||||
bbr->mode = BBR_PROBE_BW;
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
bbr_start_bw_probe_cruise(bbr);
|
||||
} else {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d exit probe_rtt → STARTUP",
|
||||
bbr->link_id, bbr->mode, BBR_STARTUP);
|
||||
bbr->mode = BBR_STARTUP;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t bbr_probe_rtt_cwnd(const struct bbr* bbr, uint32_t mss)
|
||||
{
|
||||
(void)bbr;
|
||||
return BBR_CWND_MIN_TARGET * mss;
|
||||
}
|
||||
|
||||
static uint32_t bbr_inflight_with_headroom(const struct bbr* bbr)
|
||||
{
|
||||
uint32_t headroom;
|
||||
if (bbr->inflight_hi == ~0U)
|
||||
return ~0U;
|
||||
headroom = (uint32_t)((uint64_t)bbr->inflight_hi * BBR_INFLIGHT_HEADROOM >> BBR_SCALE);
|
||||
headroom = (uint32_t)(headroom > 1U ? headroom : 1U);
|
||||
return bbr->inflight_hi - headroom;
|
||||
}
|
||||
|
||||
static void bbr_pick_probe_wait(struct bbr* bbr)
|
||||
{
|
||||
bbr->rounds_since_probe = (uint8_t)((uint32_t)rand() % BBR_BW_PROBE_RAND_ROUNDS);
|
||||
bbr->probe_wait_us = BBR_BW_PROBE_BASE_US + (uint32_t)rand() % BBR_BW_PROBE_RAND_US;
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_down(struct bbr* bbr)
|
||||
{
|
||||
bbr_reset_congestion_signals(bbr);
|
||||
bbr->bw_probe_up_cnt = ~0U;
|
||||
bbr_pick_probe_wait(bbr);
|
||||
bbr->cycle_mstamp_tb = bbr_now(bbr);
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_STOPPING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_DOWN);
|
||||
}
|
||||
|
||||
static void bbr_start_bw_probe_cruise(struct bbr* bbr)
|
||||
{
|
||||
if (bbr->inflight_lo != ~0U)
|
||||
bbr->inflight_lo = (uint32_t)(bbr->inflight_lo < bbr->inflight_hi ?
|
||||
bbr->inflight_lo : bbr->inflight_hi);
|
||||
bbr_set_cycle_idx(bbr, BBR_BW_PROBE_CRUISE);
|
||||
}
|
||||
|
||||
static void bbr_handle_inflight_too_high(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
const uint32_t beta = BBR_BETA;
|
||||
bbr->prev_probe_too_high = 1;
|
||||
bbr->bw_probe_samples = 0;
|
||||
if (!rs->is_app_limited) {
|
||||
uint32_t ti = bbr_target_inflight(bbr, cwnd, mss);
|
||||
bbr->inflight_hi = (uint32_t)(rs->tx_in_flight >
|
||||
(uint32_t)((uint64_t)ti * (BBR_UNIT - beta) >> BBR_SCALE) ?
|
||||
rs->tx_in_flight :
|
||||
(uint32_t)((uint64_t)ti * (BBR_UNIT - beta) >> BBR_SCALE));
|
||||
}
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
}
|
||||
|
||||
static void bbr_probe_inflight_hi_upward(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
int is_cwnd_limited, uint32_t cwnd)
|
||||
{
|
||||
uint32_t delta;
|
||||
if (!is_cwnd_limited || cwnd < bbr->inflight_hi)
|
||||
return;
|
||||
bbr->bw_probe_up_acks += rs->acked_sacked;
|
||||
if (bbr->bw_probe_up_acks >= bbr->bw_probe_up_cnt &&
|
||||
bbr->bw_probe_up_cnt > 0) {
|
||||
delta = bbr->bw_probe_up_acks / bbr->bw_probe_up_cnt;
|
||||
bbr->bw_probe_up_acks -= delta * bbr->bw_probe_up_cnt;
|
||||
bbr->inflight_hi += delta;
|
||||
bbr->try_fast_path = 0;
|
||||
}
|
||||
if (bbr->round_start)
|
||||
bbr_raise_inflight_hi_slope(bbr, cwnd);
|
||||
}
|
||||
|
||||
static void bbr_adapt_upper_bounds(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, int is_cwnd_limited,
|
||||
uint32_t cwnd, uint32_t mss, int* decided)
|
||||
{
|
||||
(void)sample_bw;
|
||||
*decided = 0;
|
||||
if (bbr->ack_phase == BBR_ACKS_PROBE_STARTING && bbr->round_start)
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_FEEDBACK;
|
||||
if (bbr->ack_phase == BBR_ACKS_PROBE_STOPPING && bbr->round_start) {
|
||||
bbr->bw_probe_samples = 0;
|
||||
bbr->ack_phase = BBR_ACKS_INIT;
|
||||
if (bbr->mode == BBR_PROBE_BW && !rs->is_app_limited)
|
||||
bbr_advance_max_bw_filter(bbr);
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->stopped_risky_probe && !bbr->prev_probe_too_high) {
|
||||
bbr_start_bw_probe_refill(bbr, 0);
|
||||
*decided = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (bbr_is_inflight_too_high(bbr, rs)) {
|
||||
if (bbr->bw_probe_samples)
|
||||
bbr_handle_inflight_too_high(bbr, rs, cwnd, mss);
|
||||
} else {
|
||||
if (bbr->inflight_hi == ~0U)
|
||||
return;
|
||||
if (rs->tx_in_flight > bbr->inflight_hi)
|
||||
bbr->inflight_hi = rs->tx_in_flight;
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_UP)
|
||||
bbr_probe_inflight_hi_upward(bbr, rs, is_cwnd_limited, cwnd);
|
||||
}
|
||||
}
|
||||
|
||||
static int bbr_is_reno_coexistence_probe_time(struct bbr* bbr, uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t rounds = bbr_target_inflight(bbr, cwnd, mss);
|
||||
if (rounds > BBR_BW_PROBE_MAX_ROUNDS)
|
||||
rounds = BBR_BW_PROBE_MAX_ROUNDS;
|
||||
return bbr->rounds_since_probe >= rounds;
|
||||
}
|
||||
|
||||
static int bbr_check_time_to_probe_bw(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
(void)rs;
|
||||
if (bbr_has_elapsed_in_phase(bbr, bbr->probe_wait_us) ||
|
||||
bbr_is_reno_coexistence_probe_time(bbr, cwnd, mss)) {
|
||||
bbr_start_bw_probe_refill(bbr, 0);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bbr_check_time_to_cruise(const struct bbr* bbr, uint32_t inflight,
|
||||
uint32_t bw, uint32_t mss)
|
||||
{
|
||||
if (inflight > bbr_inflight_with_headroom(bbr))
|
||||
return 0;
|
||||
return inflight <= bbr_inflight(bbr, bw, BBR_UNIT, mss);
|
||||
}
|
||||
|
||||
static void bbr_update_cycle_phase(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, uint32_t mss,
|
||||
uint32_t inflight_packets, int is_cwnd_limited,
|
||||
uint32_t cwnd)
|
||||
{
|
||||
int decided = 0;
|
||||
int is_bw_probe_done;
|
||||
uint32_t inflight, bw;
|
||||
|
||||
if (!bbr->full_bw_reached)
|
||||
return;
|
||||
bbr_adapt_upper_bounds(bbr, rs, sample_bw, is_cwnd_limited, cwnd, mss, &decided);
|
||||
if (decided)
|
||||
return;
|
||||
if (bbr->mode != BBR_PROBE_BW)
|
||||
return;
|
||||
|
||||
inflight = inflight_packets;
|
||||
bw = bbr_max_bw(bbr);
|
||||
|
||||
switch (bbr->cycle_idx) {
|
||||
case BBR_BW_PROBE_CRUISE:
|
||||
if (bbr_check_time_to_probe_bw(bbr, rs, cwnd, mss))
|
||||
return;
|
||||
break;
|
||||
case BBR_BW_PROBE_REFILL:
|
||||
if (bbr->round_start) {
|
||||
bbr->bw_probe_samples = 1;
|
||||
bbr_start_bw_probe_up(bbr, sample_bw, cwnd);
|
||||
}
|
||||
break;
|
||||
case BBR_BW_PROBE_UP:
|
||||
is_bw_probe_done = 0;
|
||||
if (bbr->prev_probe_too_high && inflight >= bbr->inflight_hi) {
|
||||
bbr->stopped_risky_probe = 1;
|
||||
is_bw_probe_done = 1;
|
||||
} else {
|
||||
if (is_cwnd_limited && cwnd >= bbr->inflight_hi) {
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->full_bw = sample_bw;
|
||||
} else if (bbr->full_bw_now) {
|
||||
is_bw_probe_done = 1;
|
||||
}
|
||||
}
|
||||
if (is_bw_probe_done) {
|
||||
bbr->prev_probe_too_high = 0;
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
}
|
||||
break;
|
||||
case BBR_BW_PROBE_DOWN:
|
||||
if (bbr_check_time_to_probe_bw(bbr, rs, cwnd, mss))
|
||||
return;
|
||||
if (bbr_check_time_to_cruise(bbr, inflight, bw, mss))
|
||||
bbr_start_bw_probe_cruise(bbr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_check_drain(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, uint32_t mss, uint32_t inflight_packets)
|
||||
{
|
||||
(void)rs; (void)sample_bw;
|
||||
if (bbr->mode == BBR_STARTUP && bbr->full_bw_reached) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d STARTUP→DRAIN", bbr->link_id, bbr->mode, BBR_DRAIN);
|
||||
bbr->mode = BBR_DRAIN;
|
||||
bbr_reset_congestion_signals(bbr);
|
||||
}
|
||||
if (bbr->mode == BBR_DRAIN &&
|
||||
inflight_packets <= bbr_inflight(bbr, bbr_max_bw(bbr), BBR_UNIT, mss)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d DRAIN→PROBE_BW infl_pkt=%u", bbr->link_id, bbr->mode, BBR_PROBE_BW, inflight_packets);
|
||||
bbr->mode = BBR_PROBE_BW;
|
||||
bbr_start_bw_probe_down(bbr);
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_check_probe_rtt_done(struct bbr* bbr, uint32_t* cwnd)
|
||||
{
|
||||
if (!bbr->probe_rtt_done_stamp_tb)
|
||||
return;
|
||||
if (!((int32_t)((uint32_t)bbr_now(bbr) - (uint32_t)bbr->probe_rtt_done_stamp_tb) >= 0))
|
||||
return;
|
||||
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
|
||||
*cwnd = (uint32_t)(*cwnd > bbr->prior_cwnd ? *cwnd : bbr->prior_cwnd);
|
||||
bbr_exit_probe_rtt(bbr);
|
||||
}
|
||||
|
||||
static void bbr_update_min_rtt(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t* cwnd, uint32_t mss, uint32_t inflight_packets)
|
||||
{
|
||||
uint32_t expire;
|
||||
int probe_rtt_expired, min_rtt_expired;
|
||||
(void)mss;
|
||||
|
||||
expire = (uint32_t)(bbr->probe_rtt_min_stamp_tb + BBR_PROBE_RTT_WIN_MS * 10U);
|
||||
probe_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
|
||||
if (rs->rtt_us > 0 &&
|
||||
(rs->rtt_us < bbr->probe_rtt_min_us || probe_rtt_expired)) {
|
||||
bbr->probe_rtt_min_us = rs->rtt_us;
|
||||
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
|
||||
bbr->has_seen_rtt = 1;
|
||||
}
|
||||
if (bbr->probe_rtt_min_us == 0)
|
||||
bbr->probe_rtt_min_us = ~0U;
|
||||
|
||||
expire = (uint32_t)(bbr->min_rtt_stamp_tb + BBR_MIN_RTT_WIN_SEC * 10000U);
|
||||
min_rtt_expired = ((int32_t)(expire - (uint32_t)bbr_now(bbr)) < 0);
|
||||
if (bbr->probe_rtt_min_us <= bbr->min_rtt_us || min_rtt_expired) {
|
||||
bbr->min_rtt_us = bbr->probe_rtt_min_us;
|
||||
bbr->min_rtt_stamp_tb = bbr_now(bbr);
|
||||
}
|
||||
|
||||
if (BBR_PROBE_RTT_MS > 0 && probe_rtt_expired &&
|
||||
!bbr->idle_restart && bbr->mode != BBR_PROBE_RTT) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →PROBE_RTT cwnd=%u infl_pkt=%u",
|
||||
bbr->link_id, bbr->mode, BBR_PROBE_RTT, *cwnd, inflight_packets);
|
||||
bbr->mode = BBR_PROBE_RTT;
|
||||
bbr_save_cwnd(bbr, *cwnd);
|
||||
bbr->probe_rtt_done_stamp_tb = 0;
|
||||
bbr->ack_phase = BBR_ACKS_PROBE_STOPPING;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
}
|
||||
|
||||
if (bbr->mode == BBR_PROBE_RTT) {
|
||||
if (!bbr->probe_rtt_done_stamp_tb &&
|
||||
inflight_packets <= bbr_probe_rtt_cwnd(bbr, mss)) {
|
||||
bbr->probe_rtt_done_stamp_tb = (uint32_t)bbr_now(bbr) + BBR_PROBE_RTT_MS * 10U;
|
||||
bbr->probe_rtt_round_done = 0;
|
||||
bbr->next_rtt_delivered = bbr->delivered;
|
||||
} else if (bbr->probe_rtt_done_stamp_tb) {
|
||||
if (bbr->round_start)
|
||||
bbr->probe_rtt_round_done = 1;
|
||||
if (bbr->probe_rtt_round_done)
|
||||
bbr_check_probe_rtt_done(bbr, cwnd);
|
||||
}
|
||||
}
|
||||
if (rs->delivered > 0)
|
||||
bbr->idle_restart = 0;
|
||||
}
|
||||
|
||||
static void bbr_reset_startup_mode(struct bbr* bbr)
|
||||
{
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_BBR, "[L%u] mode %d→%d →STARTUP (reset)", bbr->link_id, bbr->mode, BBR_STARTUP);
|
||||
bbr->mode = BBR_STARTUP;
|
||||
}
|
||||
|
||||
static void bbr_update_gains(struct bbr* bbr)
|
||||
{
|
||||
switch (bbr->mode) {
|
||||
case BBR_STARTUP:
|
||||
bbr->pacing_gain = BBR_STARTUP_PACING_GAIN;
|
||||
bbr->cwnd_gain = BBR_STARTUP_CWND_GAIN;
|
||||
break;
|
||||
case BBR_DRAIN:
|
||||
bbr->pacing_gain = BBR_DRAIN_GAIN;
|
||||
bbr->cwnd_gain = BBR_STARTUP_CWND_GAIN;
|
||||
break;
|
||||
case BBR_PROBE_BW:
|
||||
bbr->pacing_gain = bbr_pacing_gain[bbr->cycle_idx];
|
||||
bbr->cwnd_gain = BBR_CWND_GAIN;
|
||||
break;
|
||||
case BBR_PROBE_RTT:
|
||||
bbr->pacing_gain = BBR_UNIT;
|
||||
bbr->cwnd_gain = BBR_UNIT;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_update_congestion_signals(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t sample_bw, uint32_t cwnd)
|
||||
{
|
||||
uint32_t bw;
|
||||
if (rs->interval_us <= 0 || !rs->acked_sacked)
|
||||
return;
|
||||
bw = sample_bw;
|
||||
if (!rs->is_app_limited || bw >= bbr_max_bw(bbr))
|
||||
bbr_take_max_bw_sample(bbr, bw);
|
||||
bbr->loss_in_round |= (rs->lost > 0);
|
||||
if (!bbr->loss_round_start)
|
||||
return;
|
||||
bbr_adapt_lower_bounds(bbr, cwnd);
|
||||
bbr->loss_in_round = 0;
|
||||
}
|
||||
|
||||
static void bbr_update_ack_aggregation(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t epoch_us, expected_acked, extra_acked;
|
||||
uint32_t extra_acked_win_rtts_thresh = BBR_EXTRA_ACKED_WIN_RTTS;
|
||||
uint64_t now_us;
|
||||
(void)mss;
|
||||
|
||||
if (rs->acked_sacked <= 0 || rs->interval_us <= 0)
|
||||
return;
|
||||
|
||||
if (bbr->round_start) {
|
||||
bbr->extra_acked_win_rtts = (uint8_t)(bbr->extra_acked_win_rtts + 1 < 0x1F ?
|
||||
bbr->extra_acked_win_rtts + 1 : 0x1F);
|
||||
if (!bbr->full_bw_reached)
|
||||
extra_acked_win_rtts_thresh = 1;
|
||||
if (bbr->extra_acked_win_rtts >= extra_acked_win_rtts_thresh) {
|
||||
bbr->extra_acked_win_rtts = 0;
|
||||
bbr->extra_acked_win_idx = bbr->extra_acked_win_idx ? 0 : 1;
|
||||
bbr->extra_acked[bbr->extra_acked_win_idx] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
now_us = bbr_now(bbr) * 100ULL;
|
||||
if (now_us > bbr->ack_epoch_mstamp_tb * 100ULL)
|
||||
epoch_us = (uint32_t)(now_us - bbr->ack_epoch_mstamp_tb * 100ULL);
|
||||
else
|
||||
epoch_us = 0;
|
||||
expected_acked = (uint32_t)((uint64_t)bbr_bw(bbr) * epoch_us / BW_UNIT);
|
||||
|
||||
if (bbr->ack_epoch_acked <= expected_acked ||
|
||||
(bbr->ack_epoch_acked + rs->acked_sacked >= BBR_ACK_EPOCH_ACKED_RESET_THRESH)) {
|
||||
bbr->ack_epoch_acked = 0;
|
||||
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
|
||||
expected_acked = 0;
|
||||
}
|
||||
|
||||
bbr->ack_epoch_acked = (uint32_t)(bbr->ack_epoch_acked + rs->acked_sacked < 0xFFFFF ?
|
||||
bbr->ack_epoch_acked + rs->acked_sacked : 0xFFFFF);
|
||||
extra_acked = bbr->ack_epoch_acked - expected_acked;
|
||||
extra_acked = (uint32_t)(extra_acked < cwnd ? extra_acked : cwnd);
|
||||
if (extra_acked > bbr->extra_acked[bbr->extra_acked_win_idx])
|
||||
bbr->extra_acked[bbr->extra_acked_win_idx] = (uint16_t)extra_acked;
|
||||
}
|
||||
|
||||
static void bbr_init_pacing_rate_from_rtt(struct bbr* bbr, uint32_t cwnd,
|
||||
uint32_t mss, uint32_t* pacing_rate_out)
|
||||
{
|
||||
uint64_t bw;
|
||||
uint32_t rtt_us;
|
||||
|
||||
if (bbr->has_seen_rtt && bbr->min_rtt_us != ~0U) {
|
||||
rtt_us = bbr->min_rtt_us;
|
||||
if (rtt_us < 1) rtt_us = 1;
|
||||
} else {
|
||||
rtt_us = 1000;
|
||||
}
|
||||
bw = (uint64_t)cwnd * BW_UNIT / rtt_us;
|
||||
*pacing_rate_out = (uint32_t)bbr_rate_bytes_per_sec(bw, BBR_STARTUP_PACING_GAIN, 1, mss);
|
||||
}
|
||||
|
||||
static void bbr_set_pacing_rate(struct bbr* bbr, uint32_t bw, int gain,
|
||||
uint32_t mss, uint32_t* pacing_rate_out)
|
||||
{
|
||||
uint32_t rate = bbr_bw_to_pacing_rate(bw, gain, mss);
|
||||
|
||||
if (!bbr->has_seen_rtt && bbr->min_rtt_us != ~0U)
|
||||
bbr_init_pacing_rate_from_rtt(bbr, *pacing_rate_out > 0 ? *pacing_rate_out : 4, mss, pacing_rate_out);
|
||||
if (bbr->full_bw_reached || rate > *pacing_rate_out)
|
||||
*pacing_rate_out = rate;
|
||||
}
|
||||
|
||||
static void bbr_set_cwnd(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t acked, uint32_t bw, int gain,
|
||||
uint32_t mss, uint32_t* cwnd)
|
||||
{
|
||||
uint32_t target_cwnd = 0;
|
||||
uint32_t local_cwnd = *cwnd;
|
||||
|
||||
(void)rs;
|
||||
if (!acked)
|
||||
goto done;
|
||||
|
||||
target_cwnd = bbr_bdp(bbr, bw, gain, mss);
|
||||
target_cwnd += bbr_ack_aggregation_cwnd(bbr, mss);
|
||||
target_cwnd = bbr_quantization_budget(bbr, target_cwnd, mss);
|
||||
|
||||
bbr->try_fast_path = 0;
|
||||
if (bbr->full_bw_reached) {
|
||||
local_cwnd += acked;
|
||||
if (local_cwnd >= target_cwnd) {
|
||||
local_cwnd = target_cwnd;
|
||||
bbr->try_fast_path = 1;
|
||||
}
|
||||
} else if (local_cwnd < target_cwnd || local_cwnd < 2 * (uint32_t)bbr->init_cwnd * mss) {
|
||||
local_cwnd += acked;
|
||||
} else {
|
||||
bbr->try_fast_path = 1;
|
||||
}
|
||||
|
||||
local_cwnd = (uint32_t)(local_cwnd > BBR_CWND_MIN_TARGET * mss ?
|
||||
local_cwnd : BBR_CWND_MIN_TARGET * mss);
|
||||
done:
|
||||
*cwnd = local_cwnd;
|
||||
if (bbr->mode == BBR_PROBE_RTT) {
|
||||
uint32_t pr_cwnd = bbr_probe_rtt_cwnd(bbr, mss);
|
||||
*cwnd = (uint32_t)(*cwnd < pr_cwnd ? *cwnd : pr_cwnd);
|
||||
}
|
||||
}
|
||||
|
||||
static void bbr_bound_cwnd_for_inflight_model(struct bbr* bbr, uint32_t* cwnd, uint32_t mss)
|
||||
{
|
||||
uint32_t cap = ~0U;
|
||||
|
||||
if (!bbr->initialized)
|
||||
return;
|
||||
|
||||
if (bbr->mode == BBR_PROBE_BW && bbr->cycle_idx != BBR_BW_PROBE_CRUISE)
|
||||
cap = bbr->inflight_hi;
|
||||
else if (bbr->mode == BBR_PROBE_RTT ||
|
||||
(bbr->mode == BBR_PROBE_BW && bbr->cycle_idx == BBR_BW_PROBE_CRUISE))
|
||||
cap = bbr_inflight_with_headroom(bbr);
|
||||
|
||||
cap = (uint32_t)(cap > BBR_CWND_MIN_TARGET * mss ? cap : BBR_CWND_MIN_TARGET * mss);
|
||||
cap = (uint32_t)(cap < bbr->inflight_lo ? cap : bbr->inflight_lo);
|
||||
*cwnd = (uint32_t)(cap < *cwnd ? cap : *cwnd);
|
||||
}
|
||||
|
||||
static int bbr_run_fast_path(struct bbr* bbr, int* update_model,
|
||||
const struct bbr_rate_sample* rs, uint32_t sample_bw,
|
||||
uint32_t mss, uint32_t inflight_packets,
|
||||
int is_cwnd_limited, uint32_t* cwnd)
|
||||
{
|
||||
uint32_t prev_min_rtt_us;
|
||||
uint8_t prev_mode;
|
||||
|
||||
if (bbr->try_fast_path && rs->is_app_limited &&
|
||||
sample_bw < bbr_max_bw(bbr) && !bbr->loss_in_round) {
|
||||
prev_mode = bbr->mode;
|
||||
prev_min_rtt_us = bbr->min_rtt_us;
|
||||
bbr_check_drain(bbr, rs, sample_bw, mss, inflight_packets);
|
||||
bbr_update_cycle_phase(bbr, rs, sample_bw, mss, inflight_packets,
|
||||
is_cwnd_limited, *cwnd);
|
||||
bbr_update_min_rtt(bbr, rs, cwnd, mss, inflight_packets);
|
||||
|
||||
if (bbr->mode == prev_mode &&
|
||||
bbr->min_rtt_us == prev_min_rtt_us &&
|
||||
bbr->try_fast_path)
|
||||
return 1;
|
||||
*update_model = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bbr_init(struct bbr* bbr)
|
||||
{
|
||||
bbr->initialized = 1;
|
||||
bbr->init_cwnd = 0;
|
||||
bbr->prior_cwnd = 0;
|
||||
bbr->delivered = 0;
|
||||
bbr->next_rtt_delivered = 0;
|
||||
|
||||
bbr->probe_rtt_done_stamp_tb = 0;
|
||||
bbr->probe_rtt_round_done = 0;
|
||||
bbr->probe_rtt_min_us = ~0U;
|
||||
bbr->probe_rtt_min_stamp_tb = bbr_now(bbr);
|
||||
bbr->min_rtt_us = ~0U;
|
||||
bbr->min_rtt_stamp_tb = bbr_now(bbr);
|
||||
|
||||
bbr->has_seen_rtt = 0;
|
||||
|
||||
bbr->round_start = 0;
|
||||
bbr->idle_restart = 0;
|
||||
bbr->full_bw_reached = 0;
|
||||
bbr->full_bw = 0;
|
||||
bbr->full_bw_cnt = 0;
|
||||
bbr->full_bw_now = 0;
|
||||
bbr->cycle_mstamp_tb = 0;
|
||||
bbr->cycle_idx = 0;
|
||||
|
||||
bbr_reset_startup_mode(bbr);
|
||||
|
||||
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
|
||||
bbr->ack_epoch_acked = 0;
|
||||
bbr->extra_acked_win_rtts = 0;
|
||||
bbr->extra_acked_win_idx = 0;
|
||||
bbr->extra_acked[0] = 0;
|
||||
bbr->extra_acked[1] = 0;
|
||||
|
||||
bbr->try_fast_path = 0;
|
||||
bbr->loss_round_delivered = 1;
|
||||
bbr->loss_round_start = 0;
|
||||
bbr->loss_events_in_round = 0;
|
||||
bbr_reset_congestion_signals(bbr);
|
||||
bbr->bw_lo = ~0U;
|
||||
bbr->bw_hi[0] = 0;
|
||||
bbr->bw_hi[1] = 0;
|
||||
bbr->inflight_lo = ~0U;
|
||||
bbr->inflight_hi = ~0U;
|
||||
bbr_reset_full_bw(bbr);
|
||||
bbr->bw_probe_up_cnt = ~0U;
|
||||
bbr->bw_probe_up_acks = 0;
|
||||
bbr->bw_probe_up_rounds = 0;
|
||||
bbr->probe_wait_us = 0;
|
||||
bbr->stopped_risky_probe = 0;
|
||||
bbr->ack_phase = BBR_ACKS_INIT;
|
||||
bbr->rounds_since_probe = 0;
|
||||
bbr->bw_probe_samples = 0;
|
||||
bbr->prev_probe_too_high = 0;
|
||||
bbr->on_cwnd_update = NULL;
|
||||
bbr->cwnd_update_ctx = NULL;
|
||||
}
|
||||
|
||||
void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t* cwnd_out, uint32_t* pacing_rate_out,
|
||||
uint32_t mss, uint32_t inflight_bytes, int is_cwnd_limited)
|
||||
{
|
||||
uint32_t cwnd = *cwnd_out;
|
||||
uint32_t sample_bw, bw;
|
||||
int update_model = 1;
|
||||
|
||||
bbr->delivered += rs->delivered;
|
||||
bbr_update_round_start(bbr, rs);
|
||||
|
||||
if (bbr->round_start)
|
||||
bbr->rounds_since_probe = (uint8_t)(bbr->rounds_since_probe + 1 < 0xFF ?
|
||||
bbr->rounds_since_probe + 1 : 0xFF);
|
||||
|
||||
sample_bw = bbr_calculate_bw_sample(rs);
|
||||
bbr_update_latest_delivery_signals(bbr, rs, sample_bw);
|
||||
|
||||
if (bbr_run_fast_path(bbr, &update_model, rs, sample_bw, mss,
|
||||
inflight_bytes, is_cwnd_limited, &cwnd))
|
||||
goto out;
|
||||
|
||||
if (update_model) {
|
||||
bbr_update_congestion_signals(bbr, rs, sample_bw, cwnd);
|
||||
bbr_update_ack_aggregation(bbr, rs, cwnd, mss);
|
||||
bbr_check_loss_too_high_in_startup(bbr, rs, mss);
|
||||
bbr_check_full_bw_reached(bbr, rs, sample_bw);
|
||||
bbr_check_drain(bbr, rs, sample_bw, mss, inflight_bytes);
|
||||
bbr_update_cycle_phase(bbr, rs, sample_bw, mss, inflight_bytes,
|
||||
is_cwnd_limited, cwnd);
|
||||
bbr_update_min_rtt(bbr, rs, &cwnd, mss, inflight_bytes);
|
||||
}
|
||||
|
||||
bbr_update_gains(bbr);
|
||||
bw = bbr_bw(bbr);
|
||||
bbr_set_pacing_rate(bbr, bw, bbr->pacing_gain, mss, pacing_rate_out);
|
||||
bbr_set_cwnd(bbr, rs, rs->acked_sacked, bw, bbr->cwnd_gain, mss, &cwnd);
|
||||
bbr_bound_cwnd_for_inflight_model(bbr, &cwnd, mss);
|
||||
|
||||
out:
|
||||
bbr_advance_latest_delivery_signals(bbr, rs, sample_bw);
|
||||
bbr->loss_in_cycle |= (rs->lost > 0);
|
||||
if (bbr->on_cwnd_update) bbr->on_cwnd_update(bbr->cwnd_update_ctx, cwnd);
|
||||
*cwnd_out = cwnd;
|
||||
}
|
||||
|
||||
void bbr_note_loss(struct bbr* bbr)
|
||||
{
|
||||
if (!bbr->loss_in_round)
|
||||
bbr->loss_round_delivered = bbr->delivered;
|
||||
bbr->loss_in_round = 1;
|
||||
bbr->loss_in_cycle = 1;
|
||||
}
|
||||
|
||||
void bbr_tx_start(struct bbr* bbr)
|
||||
{
|
||||
bbr->idle_restart = 1;
|
||||
bbr->ack_epoch_mstamp_tb = bbr_now(bbr);
|
||||
bbr->ack_epoch_acked = 0;
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#define BBR_SCALE 8
|
||||
#define BBR_UNIT (1 << BBR_SCALE)
|
||||
#define BW_SCALE 24
|
||||
#define BW_UNIT (1 << BW_SCALE)
|
||||
|
||||
#define BBR_STARTUP_PACING_GAIN (BBR_UNIT * 277 / 100 + 1)
|
||||
#define BBR_STARTUP_CWND_GAIN (BBR_UNIT * 2)
|
||||
#define BBR_DRAIN_GAIN (BBR_UNIT * 1000 / 2885)
|
||||
#define BBR_CWND_GAIN (BBR_UNIT * 2)
|
||||
#define BBR_PROBE_RTT_CWND_GAIN (BBR_UNIT * 1 / 2)
|
||||
#define BBR_CWND_MIN_TARGET 4
|
||||
#define BBR_FULL_BW_THRESH (BBR_UNIT * 5 / 4)
|
||||
#define BBR_FULL_BW_CNT 3
|
||||
#define BBR_LOSS_THRESH (BBR_UNIT * 2 / 100)
|
||||
#define BBR_BETA (BBR_UNIT * 30 / 100)
|
||||
#define BBR_INFLIGHT_HEADROOM (BBR_UNIT * 15 / 100)
|
||||
#define BBR_EXTRA_ACKED_GAIN BBR_UNIT
|
||||
#define BBR_EXTRA_ACKED_WIN_RTTS 5
|
||||
#define BBR_ACK_EPOCH_ACKED_RESET_THRESH (1U << 20)
|
||||
#define BBR_EXTRA_ACKED_MAX_US (100 * 1000)
|
||||
#define BBR_FULL_LOSS_CNT 6
|
||||
#define BBR_BW_PROBE_MAX_ROUNDS 63
|
||||
#define BBR_BW_PROBE_RAND_ROUNDS 2
|
||||
#define BBR_BW_PROBE_BASE_US (2 * 1000000)
|
||||
#define BBR_BW_PROBE_RAND_US (1 * 1000000)
|
||||
#define BBR_PROBE_RTT_WIN_MS 5000
|
||||
#define BBR_MIN_RTT_WIN_SEC 10
|
||||
#define BBR_PROBE_RTT_MS 200
|
||||
|
||||
enum bbr_mode { BBR_STARTUP = 0, BBR_DRAIN = 1, BBR_PROBE_BW = 2, BBR_PROBE_RTT = 3 };
|
||||
enum bbr_bw_probe_cyc { BBR_BW_PROBE_UP = 0, BBR_BW_PROBE_DOWN = 1, BBR_BW_PROBE_CRUISE = 2, BBR_BW_PROBE_REFILL = 3 };
|
||||
enum bbr_ack_phase { BBR_ACKS_INIT = 0, BBR_ACKS_REFILLING, BBR_ACKS_PROBE_STARTING, BBR_ACKS_PROBE_FEEDBACK, BBR_ACKS_PROBE_STOPPING };
|
||||
|
||||
struct bbr_rate_sample {
|
||||
uint32_t delivered;
|
||||
uint32_t interval_us;
|
||||
uint32_t rtt_us;
|
||||
uint32_t acked_sacked;
|
||||
uint32_t prior_delivered;
|
||||
uint32_t tx_in_flight;
|
||||
int lost;
|
||||
int is_app_limited;
|
||||
};
|
||||
|
||||
typedef void (*bbr_cwnd_update_fn)(void* ctx, uint32_t new_cwnd);
|
||||
|
||||
struct bbr {
|
||||
uint32_t min_rtt_us;
|
||||
uint32_t bw_lo;
|
||||
uint32_t bw_hi[2];
|
||||
uint32_t inflight_lo;
|
||||
uint32_t inflight_hi;
|
||||
uint32_t bw_latest;
|
||||
uint32_t inflight_latest;
|
||||
uint8_t mode;
|
||||
uint8_t cycle_idx;
|
||||
uint16_t pacing_gain;
|
||||
uint16_t cwnd_gain;
|
||||
uint32_t next_rtt_delivered;
|
||||
uint8_t round_start : 1;
|
||||
uint8_t full_bw_reached : 1;
|
||||
uint8_t loss_in_round : 1;
|
||||
uint8_t loss_in_cycle : 1;
|
||||
uint8_t idle_restart : 1;
|
||||
uint8_t try_fast_path : 1;
|
||||
uint8_t has_seen_rtt : 1;
|
||||
uint8_t initialized : 1;
|
||||
uint32_t loss_round_delivered;
|
||||
uint32_t bw_probe_up_cnt;
|
||||
uint32_t bw_probe_up_acks;
|
||||
uint8_t bw_probe_up_rounds;
|
||||
uint8_t bw_probe_samples : 1;
|
||||
uint8_t prev_probe_too_high: 1;
|
||||
uint8_t stopped_risky_probe: 1;
|
||||
uint8_t rounds_since_probe;
|
||||
uint32_t probe_wait_us;
|
||||
uint64_t cycle_mstamp_tb;
|
||||
uint32_t probe_rtt_min_us;
|
||||
uint64_t probe_rtt_min_stamp_tb;
|
||||
uint64_t probe_rtt_done_stamp_tb;
|
||||
uint8_t probe_rtt_round_done : 1;
|
||||
uint16_t extra_acked[2];
|
||||
uint32_t ack_epoch_acked;
|
||||
uint64_t ack_epoch_mstamp_tb;
|
||||
uint8_t extra_acked_win_idx : 1;
|
||||
uint8_t extra_acked_win_rtts : 5;
|
||||
uint32_t prior_cwnd;
|
||||
uint8_t init_cwnd : 7;
|
||||
uint8_t full_bw_cnt : 2;
|
||||
uint8_t ack_phase : 3;
|
||||
uint8_t loss_events_in_round : 4;
|
||||
uint32_t delivered;
|
||||
uint32_t full_bw;
|
||||
uint64_t min_rtt_stamp_tb;
|
||||
uint8_t loss_round_start : 1;
|
||||
uint8_t full_bw_now : 1;
|
||||
uint8_t pad_unused : 6;
|
||||
uint8_t link_id; // local_link_id for debug logging
|
||||
uint64_t now_tb; // 0 = real get_time_tb(); >0 = test-controlled time
|
||||
bbr_cwnd_update_fn on_cwnd_update;
|
||||
void* cwnd_update_ctx;
|
||||
};
|
||||
|
||||
void bbr_init(struct bbr* bbr);
|
||||
void bbr_main(struct bbr* bbr, const struct bbr_rate_sample* rs,
|
||||
uint32_t* cwnd_out, uint32_t* pacing_rate_out,
|
||||
uint32_t mss, uint32_t inflight_bytes, int is_cwnd_limited);
|
||||
void bbr_note_loss(struct bbr* bbr);
|
||||
void bbr_tx_start(struct bbr* bbr);
|
||||
+1051
-416
File diff suppressed because it is too large.
Load diff
+178
-33
@@ -5,20 +5,36 @@
|
||||
|
||||
#include "secure_channel.h"
|
||||
#include "utun_instance.h"
|
||||
#include "etcp_bbr.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define UDP_HDR_SIZE 28// размер udp header + ethernet заголовков (ipv4) [для ipv6 = 48 байт]
|
||||
#define UDP_SC_HDR_SIZE (13+8+4 + 5)// 13+8+4 - sc_nonce+tag size+crc, 5 - payload hdr
|
||||
#define ACK_REZERV 100// сколько байт резервировать под ack и прочие заголовки
|
||||
|
||||
#define INFLIGHT_LIM_MIN 8192 // 8K
|
||||
#define INFLIGHT_LIM_MAX 1048576 // 1M
|
||||
|
||||
#define PACKET_DATA_SIZE 1600//1536
|
||||
#define PACKET_DATA_MAX_MTU 1600
|
||||
#define ETCP_MAX_PAYLOAD_SIZE (PACKET_DATA_SIZE - ETCP_ACK_BASE_SIZE - 5) /* 1587 */
|
||||
#define ETCP_RFC791_MIN_MTU 576
|
||||
|
||||
// Типы кодограмм протокола
|
||||
#define ETCP_INIT_REQUEST 0x02
|
||||
#define ETCP_INIT_RESPONSE 0x03
|
||||
#define ETCP_INIT_REQUEST_NOINIT 0x04
|
||||
#define ETCP_INIT_RESPONSE_NOINIT 0x05
|
||||
#define ETCP_PING 0x06
|
||||
#define ETCP_PONG 0x07
|
||||
#define ETCP_KEEPALIVE 0x08
|
||||
|
||||
/* Адаптивный keepalive */
|
||||
#define KA_PERIOD_MIN_MS 200
|
||||
#define KA_PERIOD_MAX_MS 10000
|
||||
#define KA_TIMEOUT_MULT 10 /* timeout = period * mult */
|
||||
|
||||
|
||||
#pragma pack(push, 1)
|
||||
@@ -32,6 +48,65 @@ struct ETCP_DGRAM {// пакет (незашифрованный)
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
/* Размер зашифрованного заголовка ETCP_DGRAM (timestamp + flag_up + padding) */
|
||||
#define ETCP_ENCRYPTED_HDR_SIZE (offsetof(struct ETCP_DGRAM, data) - offsetof(struct ETCP_DGRAM, timestamp))
|
||||
|
||||
/* Размер базового ACK: type(1) + count(1) + last_delivered_id(4) + rx_dup_count(2) */
|
||||
#define ETCP_ACK_BASE_SIZE (2 + 4 + 2)
|
||||
|
||||
// --- INIT packet wire formats (inside encrypted payload, before padding) ---
|
||||
|
||||
// INIT REQUEST: client → server
|
||||
struct ETCP_INIT_REQUEST_PKT {
|
||||
uint8_t code; // 0: ETCP_INIT_REQUEST (0x02) или ETCP_INIT_REQUEST_NOINIT (0x04)
|
||||
uint8_t node_id[8]; // 1: sender node_id (big-endian)
|
||||
uint8_t session_id[4]; // 9: session (big-endian)
|
||||
uint8_t mtu[2]; // 13: client MTU (big-endian)
|
||||
uint8_t keepalive[2]; // 15: keepalive interval (big-endian)
|
||||
uint8_t recovery[2]; // 17: recovery interval/100 (big-endian)
|
||||
uint8_t link_id; // 19: client's local link id
|
||||
uint8_t socket_id; // 20: client's socket id
|
||||
uint8_t only_local; // 21: client only_local flag
|
||||
uint8_t type; // 22: client socket type (CFG_SERVER_TYPE_*)
|
||||
// V2 fields (NAT_DIRECT detection):
|
||||
uint8_t src_ipv4[4]; // 23: client interface_addr IPv4 (big-endian, 0 if N/A)
|
||||
uint8_t src_port[2]; // 27: client interface_addr port (big-endian)
|
||||
} __attribute__((packed));
|
||||
#define ETCP_INIT_REQ_V1_SIZE 23 // size without src_ipv4/src_port (backward compat)
|
||||
#define ETCP_INIT_REQ_V2_SIZE 29 // size with src_ipv4/src_port
|
||||
|
||||
// INIT RESPONSE: server → client
|
||||
struct ETCP_INIT_RESPONSE_PKT {
|
||||
uint8_t code; // 0: ETCP_INIT_RESPONSE (0x03) или ETCP_INIT_RESPONSE_NOINIT (0x05)
|
||||
uint8_t node_id[8]; // 1: server node_id (big-endian)
|
||||
uint8_t session_id[4]; // 9: session (big-endian)
|
||||
uint8_t mtu[2]; // 13: server MTU (big-endian)
|
||||
uint8_t link_id; // 15: server's local link id
|
||||
uint8_t remote_socket_id; // 16: echo of client's socket_id
|
||||
uint8_t only_local; // 17: server only_local flag
|
||||
uint8_t type; // 18: server socket type (CFG_SERVER_TYPE_*)
|
||||
// V2 fields:
|
||||
uint8_t peer_ipv4[4]; // 19: client's external NAT address (big-endian)
|
||||
uint8_t peer_port[2]; // 23: client's external NAT port (big-endian)
|
||||
} __attribute__((packed));
|
||||
#define ETCP_INIT_RESP_V1_SIZE 19 // size without peer_ipv4/peer_port
|
||||
#define ETCP_INIT_RESP_V2_SIZE 25 // size with peer_ipv4/peer_port
|
||||
#define ETCP_INIT_RESP_MIN_SIZE ETCP_INIT_RESP_V1_SIZE
|
||||
|
||||
typedef void (*etcp_ping_callback_t)(int success, uint16_t rtt, void* arg, uint64_t nonce,
|
||||
const uint8_t* resp_data, size_t resp_data_len);
|
||||
struct PING_CONTEXT {
|
||||
struct PING_CONTEXT* next;
|
||||
struct UTUN_INSTANCE* instance;
|
||||
etcp_ping_callback_t cb;
|
||||
void* arg;
|
||||
uint64_t nonce;
|
||||
void* timeout_timer;
|
||||
uint64_t send_time; // время отправки пинга в 0.1ms
|
||||
uint8_t* user_data;
|
||||
size_t user_data_len;
|
||||
};
|
||||
|
||||
// список активных подключений которые обслуживает сокет. каждый сокет может обслуживать много подключений
|
||||
struct ETCP_SOCKET {
|
||||
struct ETCP_SOCKET* next; // Linked list для всех соединений
|
||||
@@ -40,7 +115,6 @@ struct ETCP_SOCKET {
|
||||
socket_t fd; // UDP socket (cross-platform)
|
||||
struct sockaddr_storage local_addr; // Локальный адрес
|
||||
int mtu; // MTU для этого сокета
|
||||
int loss_rate; // packet loss rate in percent (0-100)
|
||||
|
||||
// для входящих подключений (links) - массив упорядоченный по ip_port_hash
|
||||
size_t max_channels; // сколько выделено памяти
|
||||
@@ -51,9 +125,44 @@ struct ETCP_SOCKET {
|
||||
|
||||
void* socket_id; // Socket ID from uasync_add_socket
|
||||
uint8_t type; // CFG_SERVER_TYPE_PUBLIC/NAT/PRIVATE
|
||||
uint8_t sock_id; // unique socket id (0-255) for NAT matching
|
||||
uint8_t nat_type; // NAT_TYPE_* (detected by server)
|
||||
uint32_t local_defaultroute_ip; // auto-detected IPv4 for public servers (network byte order)
|
||||
uint8_t local_defaultroute_ip6[16]; // auto-detected IPv6 for public servers
|
||||
uint8_t only_local; // 1 = only local connections, no forwarding
|
||||
struct sockaddr_storage interface_addr; // адрес интерфейса: если bind на интерфейс - его IP, если 0.0.0.0 без интерфейса - IP интерфейса через который идет default route, иначе - адрес из конфига
|
||||
struct sockaddr_storage nat_addr; // NAT адрес (network byte order), ss_family=0 = не определён
|
||||
};
|
||||
|
||||
// NAT check status
|
||||
#define NAT_CHECK_NONE 0
|
||||
#define NAT_CHECK_WAITING 1
|
||||
#define NAT_CHECK_IN_PROGRESS 2
|
||||
#define NAT_CHECK_EIM 3
|
||||
#define NAT_CHECK_STRICT 4
|
||||
|
||||
// NAT type (detected by server during NAT check)
|
||||
#define NAT_TYPE_UNKNOWN 0
|
||||
#define NAT_TYPE_EIM 1 // Endpoint-Independent Mapping
|
||||
#define NAT_TYPE_STRICT 2 // Address/Restricted or Symmetric
|
||||
#define NAT_TYPE_DIRECT 3 // real public IP, no NAT (detected during INIT handshake)
|
||||
|
||||
// Verified NAT types (published in nodeinfo after server-side NAT detection)
|
||||
// Values above CFG_SERVER_TYPE_PRIVATE to avoid collision with config types
|
||||
#define NAT_VERIFIED_UNKNOWN 4 // detection failed or inconclusive
|
||||
#define NAT_VERIFIED_EIM 5 // EIM NAT
|
||||
#define NAT_VERIFIED_STRICT 6 // strict NAT
|
||||
#define NAT_VERIFIED_DIRECT 7 // real public IP, no NAT
|
||||
|
||||
|
||||
// Тип функции-перехватчика отправки UDP.
|
||||
// Если link->send_hook != NULL, etcp_udp_send вызывает её вместо socket_sendto.
|
||||
struct ETCP_LINK;
|
||||
typedef ssize_t (*etcp_udp_send_fn_t)(socket_t fd, const void* buf, size_t len,
|
||||
const struct sockaddr* addr, socklen_t addr_len,
|
||||
struct ETCP_LINK* link, void* context);
|
||||
|
||||
|
||||
// ETCP Link - одно динамическое соединение (один путь)
|
||||
struct ETCP_LINK {
|
||||
uint32_t ip_port_hash; // crc32 для быстрого поиска
|
||||
@@ -61,7 +170,6 @@ struct ETCP_LINK {
|
||||
|
||||
struct ETCP_CONN* etcp; // подключение (parent)
|
||||
struct ETCP_SOCKET* conn; // сокет через который работаем
|
||||
|
||||
// Путь соединения
|
||||
struct sockaddr_storage remote_addr; // Удалённый адрес
|
||||
|
||||
@@ -75,6 +183,10 @@ struct ETCP_LINK {
|
||||
uint8_t initialized; // Флаг инициализации (1=подтверждено или получен request)
|
||||
uint8_t local_link_id; // id моего линка
|
||||
uint8_t remote_link_id; // id этого линка на peer (устанавливается в момент initialized)
|
||||
uint8_t remote_socket_id; // socket id peer
|
||||
uint8_t remote_only_local; // only_local flag from peer
|
||||
uint8_t remote_type; // CFG_SERVER_TYPE_* (type of peer's socket)
|
||||
uint8_t nat_type; // NAT_TYPE_* (detected for this client link)
|
||||
uint8_t recv_keepalive; // 1 - up, 0 - down (принимаются ли пакеты)
|
||||
uint8_t remote_keepalive; // 1 - up, 0 - down (удаленная сторона сообщает - принимаются ли у нее пакеты)
|
||||
uint8_t link_status; // 1 - up, 0 - down (итоговый статус - если есть проблемы на любой стороне - линк down)
|
||||
@@ -99,26 +211,6 @@ struct ETCP_LINK {
|
||||
uint32_t inflight_packets;
|
||||
uint32_t inflight_lim_bytes;
|
||||
|
||||
/* Биты блокировки отправки по причине (для корректного resume) */
|
||||
// unsigned send_blocked_input_queue : 1;
|
||||
// unsigned send_blocked_sack_full : 1;
|
||||
// unsigned send_blocked_bandwidth : 1;
|
||||
unsigned send_blocked_inflight : 1;
|
||||
// unsigned send_blocked_other : 4;
|
||||
|
||||
// lim периодически обновляется. = (rtt_avg10 + jitter * K2[=1.0]) * K1[=1.5] * channel_bandwidth
|
||||
uint32_t win_timebase; // x1 us (по умолчанию 50 000 = 50 ms, потом подстраивается под rtt/2)
|
||||
uint32_t window_pkt_transmitted; // текущие накопители для следующего окна (обнуляются в таймере)
|
||||
uint32_t window_retransmissions;
|
||||
void* stats_timer; // Таймер для обновления окна
|
||||
uint32_t win_ptr; // текущий слот в кольцевом буфере (0..31)
|
||||
struct {
|
||||
uint16_t rtt; // x 0.1ms (avg10 в конеце окна)
|
||||
// uint16_t avg_rtt; // x 0.1ms (avg10 усредненный за период)
|
||||
uint16_t pkt_loss; // штук (за период)
|
||||
uint32_t pkt_transmitted; // штук (за период)
|
||||
} stat_win[32]; // последние 32 замера статистики
|
||||
|
||||
// statistics
|
||||
size_t encrypt_errors;
|
||||
size_t decrypt_errors;
|
||||
@@ -127,15 +219,12 @@ struct ETCP_LINK {
|
||||
size_t total_encrypted;
|
||||
size_t total_decrypted;
|
||||
uint32_t total_retransmissions;
|
||||
uint64_t acked_bytes; // подтверждённых байт на этом линке
|
||||
uint64_t acked_packets; // подтверждённых пакетов на этом линке
|
||||
|
||||
uint16_t rtt_last; // round trip (время отправки + приёма)
|
||||
uint16_t rtt_history[10]; // Circular buffer for last 10 RTT values
|
||||
uint8_t rtt_history_count; // Number of valid entries in history (0-10)
|
||||
uint8_t rtt_history_index; // Current write position in history buffer
|
||||
uint32_t jitter; // Current jitter [>>16] x0.1 ms
|
||||
uint16_t rtt_max_val; // Current max RTT in history
|
||||
uint8_t rtt_max_idx; // Index of max (255 = needs recalc)
|
||||
uint16_t rtt_avg10; // round trip average (excl. max)
|
||||
// rtt_avg10 удалён — заменён BBR (bbr->min_rtt_us/100)
|
||||
uint32_t recv_dt_avg_tx; // дельта времени для отправленных пакетов (относительное время отправки) x256
|
||||
uint32_t recv_dt_avg_rx; // дельта времени для принятых пакетов (относительное время отправки) x256
|
||||
|
||||
@@ -145,20 +234,61 @@ struct ETCP_LINK {
|
||||
|
||||
// NAT address tracking (from INIT_RESPONSE)
|
||||
uint32_t nat_ip; // NAT IPv4 address (network byte order), 0 = not set
|
||||
uint16_t nat_port; // NAT port (network byte order)
|
||||
uint16_t nat_port; // NAT port (host byte order)
|
||||
uint32_t nat_changes_count; // Counter of NAT address changes
|
||||
uint32_t nat_hits_count; // Counter of NAT address matches (new init response with same IP:port)
|
||||
uint8_t nat_check_status; // NAT_CHECK_*
|
||||
|
||||
// Keepalive state
|
||||
void* keepalive_timer; // Таймер для отправки keepalive пакетов
|
||||
uint32_t keepalive_timeout; // таймаут (ms)
|
||||
uint16_t ka_period_ms; // адаптивный период отправки keepalive (200→10000->200)
|
||||
uint8_t pkt_sent_since_keepalive; // Флаг: был ли отправлен пакет с последнего keepalive тика
|
||||
uint32_t keepalive_sent_count; // Счётчик отправленных keepalive
|
||||
uint32_t keepalive_recv_count; // Счётчик полученных keepalive
|
||||
uint16_t handshake_minsize; // минимальный размер udp при handshake
|
||||
uint16_t handshake_maxsize; // мax размер udp при handshake (выбирает рандом)
|
||||
|
||||
// === Burst-измерение bandwidth ===
|
||||
// Sender-сторона
|
||||
uint16_t burst_id; // монотонно возрастающий ID burst
|
||||
uint8_t burst_active; // 1 = burst в процессе отправки
|
||||
uint8_t burst_seq; // текущий номер пакета в burst (0..burst_count-1)
|
||||
uint8_t burst_count; // общее число пакетов в burst
|
||||
uint64_t burst_last_time_tb; // время последнего burst (0.1ms), для MIN_BURST_INTERVAL
|
||||
void* burst_resp_timer; // таймер ожидания ответа на burst
|
||||
uint32_t burst_target_bdp; // BDP из последнего успешного burst (байты)
|
||||
uint16_t burst_pkt_size; // сохранённый размер пакета для вычисления BW
|
||||
|
||||
// Receiver-сторона
|
||||
uint16_t burst_recv_id; // ID отслеживаемого burst
|
||||
uint8_t burst_recv_count; // ожидаемое число пакетов в burst
|
||||
uint8_t burst_recv_next_seq; // следующий ожидаемый seq
|
||||
uint8_t burst_recv_valid; // 1 = порядок пакетов не нарушен
|
||||
uint8_t burst_recv_received; // сколько пакетов уже принято
|
||||
uint64_t burst_recv_times[16]; // время прихода каждого пакета (µs)
|
||||
uint16_t burst_recv_pkt_size; // размер пакета из MEAS_TS (для ответа)
|
||||
uint8_t burst_resp_pending; // 1 = есть готовый ответ для piggyback
|
||||
uint32_t burst_resp_gap_avg; // средний inter-packet gap (µs)
|
||||
uint32_t burst_resp_gap_min; // минимальный inter-packet gap (µs)
|
||||
uint8_t burst_resp_pkt_count; // число пакетов в измерении
|
||||
uint8_t burst_resp_valid; // валидность измерения
|
||||
|
||||
// === BBR congestion control ===
|
||||
struct bbr* bbr; // BBR state (per-link, создаётся в etcp_link_new)
|
||||
uint64_t delivered_bytes; // кумулятивно delivered bytes на линке (для round detection)
|
||||
uint64_t last_ack_time_tb; // 0.1ms timestamp последнего ACK (для interval_us)
|
||||
uint32_t bbr_pacing_rate; // bytes/sec output BBR → shaper
|
||||
uint32_t bbr_loss_since_ack; // счётчик потерь на линке с последнего ACK (для rs->lost)
|
||||
|
||||
etcp_udp_send_fn_t send_hook; // NULL = socket_sendto напрямую
|
||||
void* send_hook_ctx;
|
||||
};
|
||||
|
||||
// Единая точка отправки всех ETCP-пакетов.
|
||||
ssize_t etcp_udp_send(struct ETCP_LINK* link, socket_t fd, const void* buf, size_t len,
|
||||
const struct sockaddr* addr, socklen_t addr_len);
|
||||
|
||||
// INITIALIZATION
|
||||
// Создаёт только listen-сокеты из конфига (серверы для incoming connections)
|
||||
int init_sockets(struct UTUN_INSTANCE* instance);
|
||||
@@ -174,19 +304,34 @@ void etcp_socket_remove(struct ETCP_SOCKET* conn);
|
||||
// connection functions
|
||||
// создает новый канал связи для etcp подключения (ETCP_CONN)
|
||||
struct ETCP_LINK* etcp_link_new(struct ETCP_CONN* etcp, struct ETCP_SOCKET* conn, struct sockaddr_storage* remote_addr, uint8_t is_server);
|
||||
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
|
||||
void etcp_link_close(struct ETCP_LINK* link);
|
||||
//int etcp_input_cbk(struct packet_buffer* pkt, struct ETCP_SOCKET* conn);// получает расшифрованный пакет
|
||||
int etcp_encrypt_send(struct ETCP_DGRAM* dgram);// зашифровывает и отправляет пакет
|
||||
// find link by address
|
||||
struct ETCP_LINK* etcp_link_find_by_addr(struct ETCP_SOCKET* e_sock, struct sockaddr_storage* addr);
|
||||
|
||||
// обновляет лимит inflight_bytes для линка и корректно снимает блокировку если нужно
|
||||
void etcp_link_update_inflight_lim(struct ETCP_LINK* link, uint32_t new_lim);
|
||||
|
||||
// find free local_link_id for connection
|
||||
// scans all links in connection, marks used ids in bit array
|
||||
// returns first free id (0-255) or -1 if all occupied
|
||||
int etcp_find_free_local_link_id(struct ETCP_CONN* etcp);
|
||||
void start_stats_timer(struct ETCP_LINK* link);
|
||||
|
||||
// Burst measurement functions
|
||||
void etcp_link_burst_start(struct ETCP_LINK* link);
|
||||
void etcp_link_burst_check(struct ETCP_LINK* link);
|
||||
void etcp_link_burst_finish(struct ETCP_LINK* link);
|
||||
|
||||
int etcp_send_ping(struct UTUN_INSTANCE* instance, const uint8_t* peer_pubkey_bin,
|
||||
const struct sockaddr_storage* addr, int timeout_ms,
|
||||
etcp_ping_callback_t cb, void* user_arg,
|
||||
const uint8_t* user_data, size_t user_data_len);
|
||||
|
||||
// шлёт пинг на addr используя pubkey. в пакет помещает user_data[user_len]. при получении ответа или таймауте вызывает callback.
|
||||
int etcp_send_ping_to_socket(struct UTUN_INSTANCE* instance, struct ETCP_SOCKET* e_sock,
|
||||
const uint8_t* peer_pubkey_bin, const struct sockaddr_storage* addr,
|
||||
int timeout_ms, etcp_ping_callback_t cb, void* user_arg,
|
||||
const uint8_t* user_data, size_t user_data_len);
|
||||
|
||||
void etcp_connections_read_callback_socket(socket_t sock, void* arg);
|
||||
|
||||
#endif // ETCP_CONNECTIONS_H
|
||||
+4
-3
@@ -42,11 +42,12 @@ void etcp_dump_pkt_sections(struct ETCP_DGRAM* pkt, struct ETCP_LINK* link, int
|
||||
case ETCP_SECTION_ACK: {
|
||||
if (offset + 2 > len) { offset = len; break; }
|
||||
ack_count = data[offset + 1];
|
||||
if (offset + 6 > len) { offset = len; break; }
|
||||
if (offset + 8 > len) { offset = len; break; }
|
||||
ack_last_delivered = data[offset + 2] | (data[offset + 3] << 8) |
|
||||
(data[offset + 4] << 16) | (data[offset + 5] << 24);
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, " ack=%u/%d", ack_last_delivered, ack_count);
|
||||
offset += 6 + ack_count * 8;
|
||||
uint16_t ack_rx_dup = data[offset + 6] | (data[offset + 7] << 8);
|
||||
pos += snprintf(buf + pos, sizeof(buf) - pos, " ack=%u/%d dup=%u", ack_last_delivered, ack_count, ack_rx_dup);
|
||||
offset += 8 + ack_count * 8;
|
||||
break;
|
||||
}
|
||||
case ETCP_SECTION_TIMESTAMP: {
|
||||
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
#include "etcp_dump.h"
|
||||
#include "etcp.h"
|
||||
#include "pkt_normalizer.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/memory_pool.h"
|
||||
#include "../lib/socket_compat.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DUMP_FP stdout
|
||||
|
||||
#define DUMP_LINE(fmt, ...) do { \
|
||||
fprintf(DUMP_FP, " " fmt "\n", ##__VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
#define DUMP_HDR(fmt, ...) do { \
|
||||
fprintf(DUMP_FP, fmt "\n", ##__VA_ARGS__); \
|
||||
} while(0)
|
||||
|
||||
static void dump_queue(const char* name, struct ll_queue* q) {
|
||||
if (!q) { DUMP_LINE("%-14s NULL", name); return; }
|
||||
DUMP_LINE("%-14s %d pkts / %zu bytes", name, queue_entry_count(q), queue_total_bytes(q));
|
||||
}
|
||||
|
||||
static void dump_pool(const char* name, struct memory_pool* pool) {
|
||||
if (!pool) return;
|
||||
size_t allocs, reuse;
|
||||
memory_pool_get_stats(pool, &allocs, &reuse);
|
||||
DUMP_LINE(" %-12s alloc=%zu reuse=%zu", name, allocs, reuse);
|
||||
}
|
||||
|
||||
static void dump_link_timer(const char* name, void* timer) {
|
||||
DUMP_LINE(" %-14s %s", name, timer ? "ACTIVE" : "free");
|
||||
}
|
||||
|
||||
static const char* ip_to_str_buf(uint32_t ip, char* buf, size_t sz) {
|
||||
uint8_t* b = (uint8_t*)&ip;
|
||||
snprintf(buf, sz, "%u.%u.%u.%u", b[0], b[1], b[2], b[3]);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char* sockaddr_to_str(const struct sockaddr_storage* addr, char* buf, size_t sz) {
|
||||
if (!addr || addr->ss_family != AF_INET) { snprintf(buf, sz, "?"); return buf; }
|
||||
const struct sockaddr_in* sin = (const struct sockaddr_in*)addr;
|
||||
uint8_t* b = (uint8_t*)&sin->sin_addr.s_addr;
|
||||
snprintf(buf, sz, "%u.%u.%u.%u:%u", b[0], b[1], b[2], b[3], ntohs(sin->sin_port));
|
||||
return buf;
|
||||
}
|
||||
|
||||
void etcp_dump_conn_state(struct ETCP_CONN* conn) {
|
||||
if (!conn) { DUMP_HDR("etcp_dump_conn_state: NULL conn"); return; }
|
||||
|
||||
char addr_buf[32], nat_buf[32];
|
||||
|
||||
DUMP_HDR("=== ETCP CONN STATE [%s] ===", conn->log_name);
|
||||
|
||||
/* --- GENERAL --- */
|
||||
DUMP_LINE("GENERAL: %s peer=0x%llx initialized=%d links_up=%d tx_state=%d session=0x%08x mtu=%d routing_ex=%d",
|
||||
conn->name ? conn->name : "",
|
||||
(unsigned long long)conn->peer_node_id, conn->initialized, conn->links_up,
|
||||
conn->tx_state, conn->session_id, conn->mtu, conn->routing_exchange_active);
|
||||
|
||||
/* --- IDS --- */
|
||||
DUMP_LINE("IDS: next_tx=%u last_rx=%u last_del=%u rx_ack_till=%u",
|
||||
conn->next_tx_id, conn->last_rx_id, conn->last_delivered_id, conn->rx_ack_till);
|
||||
|
||||
/* --- RTT --- */
|
||||
DUMP_LINE("RTT: last=%u avg10=%u avg100=%u jitter=%u",
|
||||
conn->rtt_last, conn->rtt_avg_10, conn->rtt_avg_100, conn->jitter);
|
||||
|
||||
/* --- STATS --- */
|
||||
DUMP_LINE("STATS: bytes_sent=%u retrans=%u reinit=%u reset=%u ack_pkts=%u",
|
||||
conn->bytes_sent_total, conn->retransmissions_count,
|
||||
conn->reinit_count, conn->reset_count, conn->ack_packets_count);
|
||||
|
||||
/* --- INFLIGHT --- */
|
||||
DUMP_LINE("INFLIGHT: unacked=%u optimal=%u",
|
||||
conn->unacked_bytes, conn->optimal_inflight);
|
||||
|
||||
/* --- ACK DEBUG --- */
|
||||
DUMP_LINE("ACK_DEBUG: hit_inf=%u hit_sndq=%u miss=%u link_wait=%u rx_dup=%u tx_dup=%u",
|
||||
conn->cnt_ack_hit_inf, conn->cnt_ack_hit_sndq, conn->cnt_ack_miss,
|
||||
conn->cnt_link_wait, conn->rx_dup_count, conn->tx_dup_count);
|
||||
|
||||
/* --- DEBUG[8] --- */
|
||||
DUMP_LINE("DEBUG: [0]=%u [1]=%u [2]=%u [3]=%u [4]=%u [5]=%u [6]=%u [7]=%u",
|
||||
conn->debug[0], conn->debug[1], conn->debug[2], conn->debug[3],
|
||||
conn->debug[4], conn->debug[5], conn->debug[6], conn->debug[7]);
|
||||
|
||||
/* --- TIMERS --- */
|
||||
DUMP_LINE("TIMERS: retrans=%s ack_resp=%s",
|
||||
conn->retrans_timer ? "ACTIVE" : "free",
|
||||
conn->ack_resp_timer ? "ACTIVE" : "free");
|
||||
|
||||
/* --- QUEUES --- */
|
||||
DUMP_HDR(" --- QUEUES ---");
|
||||
dump_queue("input", conn->input_queue);
|
||||
dump_queue("input_send_q", conn->input_send_q);
|
||||
dump_queue("input_wait_ack", conn->input_wait_ack);
|
||||
dump_queue("ack_q", conn->ack_q);
|
||||
dump_queue("recv_q", conn->recv_q);
|
||||
dump_queue("output", conn->output_queue);
|
||||
|
||||
/* --- NORMALIZER --- */
|
||||
if (conn->normalizer) {
|
||||
struct PKTNORM* pn = (struct PKTNORM*)conn->normalizer;
|
||||
DUMP_HDR(" --- NORMALIZER ---");
|
||||
DUMP_LINE("frag=%u data_ptr=%u/%u flush=%s pending=%s recvpart=%s",
|
||||
pn->frag_size, pn->data_ptr, pn->data_size,
|
||||
pn->flush_timer ? "ACTIVE" : "free",
|
||||
pn->pending ? "ACTIVE" : "free",
|
||||
pn->recvpart ? "ACTIVE" : "free");
|
||||
dump_queue("pn-input", pn->input);
|
||||
dump_queue("pn-output", pn->output);
|
||||
DUMP_LINE("alloc_err=%u logic_err=%u in=%llu/%llu out=%llu/%llu",
|
||||
pn->alloc_errors, pn->logic_errors,
|
||||
(unsigned long long)pn->in_total_pkts, (unsigned long long)pn->in_total_bytes,
|
||||
(unsigned long long)pn->out_total_pkts, (unsigned long long)pn->out_total_bytes);
|
||||
} else {
|
||||
DUMP_HDR(" --- NORMALIZER --- NULL");
|
||||
}
|
||||
|
||||
/* --- POOLS --- */
|
||||
DUMP_HDR(" --- POOLS ---");
|
||||
dump_pool("inflight", conn->inflight_pool);
|
||||
dump_pool("io", conn->io_pool);
|
||||
if (conn->instance) {
|
||||
dump_pool("data", conn->instance->data_pool);
|
||||
dump_pool("ack", conn->instance->ack_pool);
|
||||
dump_pool("pkt", conn->instance->pkt_pool);
|
||||
}
|
||||
|
||||
/* --- LINKS --- */
|
||||
int link_idx = 0;
|
||||
struct ETCP_LINK* link = conn->links;
|
||||
while (link) {
|
||||
DUMP_HDR(" --- LINK %d ---", link_idx);
|
||||
|
||||
sockaddr_to_str(&link->remote_addr, addr_buf, sizeof(addr_buf));
|
||||
|
||||
/* basic link state */
|
||||
DUMP_LINE("BASIC: id=%d/%d is_server=%d link_state=%d link_status=%d initialized=%d mtu=%d",
|
||||
link->local_link_id, link->remote_link_id, link->is_server,
|
||||
link->link_state, link->link_status, link->initialized, link->mtu);
|
||||
DUMP_LINE("ADDR: remote=%s remote_sock=%d remote_type=%d remote_only_local=%d",
|
||||
addr_buf, link->remote_socket_id, link->remote_type, link->remote_only_local);
|
||||
|
||||
/* keepalive */
|
||||
DUMP_LINE("KA: recv=%d remote=%d sent=%u recv=%u interval=%u timeout=%u",
|
||||
link->recv_keepalive, link->remote_keepalive,
|
||||
link->keepalive_sent_count, link->keepalive_recv_count,
|
||||
link->keepalive_interval, link->keepalive_timeout);
|
||||
|
||||
/* timers */
|
||||
DUMP_LINE("TIMERS: init=%s(%u/%u) ka=%s shaper=%s burst=%s keepalive=%s",
|
||||
link->init_timer ? "ACTIVE" : "free",
|
||||
link->init_timeout, link->init_retry_count,
|
||||
link->keepalive_timer ? "ACTIVE" : "free",
|
||||
link->shaper_timer ? "ACTIVE" : "free",
|
||||
link->burst_resp_timer ? "ACTIVE" : "free",
|
||||
link->keepalive_sent_count > 0 ? "has_sent" : "idle");
|
||||
|
||||
/* inflight (BBR) */
|
||||
DUMP_LINE("BBR: bytes=%u pkts=%u lim=%u mode=%d cycle=%d pacing=%u",
|
||||
link->inflight_bytes, link->inflight_packets, link->inflight_lim_bytes,
|
||||
link->bbr ? link->bbr->mode : -1,
|
||||
link->bbr ? link->bbr->cycle_idx : -1,
|
||||
link->bbr_pacing_rate);
|
||||
|
||||
/* rtt (BBR) */
|
||||
DUMP_LINE("RTT: last=%u jitter=%u min_rtt_us=%u bw_lo=%u bw_hi=%u inflight_lo=%u inflight_hi=%u",
|
||||
link->rtt_last, link->jitter,
|
||||
link->bbr ? link->bbr->min_rtt_us : 0,
|
||||
link->bbr ? link->bbr->bw_lo : 0,
|
||||
link->bbr ? (link->bbr->bw_hi[0] > link->bbr->bw_hi[1] ? link->bbr->bw_hi[0] : link->bbr->bw_hi[1]) : 0,
|
||||
link->bbr ? link->bbr->inflight_lo : 0,
|
||||
link->bbr ? link->bbr->inflight_hi : 0);
|
||||
|
||||
/* tt/rt/bandwidth */
|
||||
DUMP_LINE("TT/RT: tt=%u rt=%u recv_dt_tx=%u recv_dt_rx=%u bw=%u",
|
||||
link->tt_last, link->rt_last,
|
||||
link->recv_dt_avg_tx, link->recv_dt_avg_rx,
|
||||
link->bandwidth);
|
||||
|
||||
/* errors */
|
||||
DUMP_LINE("MTU: %u local=%u remote=%u", link->mtu, link->mtu_local, link->mtu_remote);
|
||||
DUMP_LINE("ERRORS: enc=%zu dec=%zu snd=%zu rcv=%zu total_enc=%zu total_dec=%zu retrans=%u",
|
||||
link->encrypt_errors, link->decrypt_errors,
|
||||
link->send_errors, link->recv_errors,
|
||||
link->total_encrypted, link->total_decrypted,
|
||||
link->total_retransmissions);
|
||||
|
||||
/* nat */
|
||||
ip_to_str_buf(link->nat_ip, nat_buf, sizeof(nat_buf));
|
||||
DUMP_LINE("NAT: ip=%s:%u chg=%u hits=%u check=%d type=%d",
|
||||
nat_buf, link->nat_port,
|
||||
link->nat_changes_count, link->nat_hits_count,
|
||||
link->nat_check_status, link->nat_type);
|
||||
|
||||
/* last recv */
|
||||
DUMP_LINE("LAST_RECV: time=%llu ts=%u updated=%d",
|
||||
(unsigned long long)link->last_recv_local_time,
|
||||
link->last_recv_timestamp, link->last_recv_updated);
|
||||
|
||||
/* handshake */
|
||||
DUMP_LINE("HANDSHAKE: min=%u max=%u", link->handshake_minsize, link->handshake_maxsize);
|
||||
|
||||
/* burst */
|
||||
DUMP_LINE("BURST: active=%d id=%u seq=%u/%u last_tb=%llu resp=%s",
|
||||
link->burst_active, link->burst_id,
|
||||
link->burst_seq, link->burst_count,
|
||||
(unsigned long long)link->burst_last_time_tb,
|
||||
link->burst_resp_timer ? "ACTIVE" : "free");
|
||||
|
||||
/* shaper */
|
||||
DUMP_LINE("SHAPER: load_tb=%llu sub=%llu state=%d",
|
||||
(unsigned long long)link->shaper_load_time_tb,
|
||||
(unsigned long long)link->shaper_sub_nanotime,
|
||||
link->shaper_state);
|
||||
|
||||
link = link->next;
|
||||
link_idx++;
|
||||
}
|
||||
if (link_idx == 0) DUMP_HDR(" --- NO LINKS ---");
|
||||
|
||||
DUMP_HDR("=== END [%s] ===", conn->log_name);
|
||||
}
|
||||
|
||||
void etcp_dump_all_conns(struct UTUN_INSTANCE* instance) {
|
||||
if (!instance) {
|
||||
DUMP_HDR("etcp_dump_all_conns: NULL instance");
|
||||
return;
|
||||
}
|
||||
DUMP_HDR("=== DUMP ALL CONNS for instance node=0x%llx ===",
|
||||
(unsigned long long)instance->node_id);
|
||||
struct ETCP_CONN* conn = instance->connections;
|
||||
int idx = 0;
|
||||
while (conn) {
|
||||
DUMP_HDR("--- CONN %d ---", idx);
|
||||
etcp_dump_conn_state(conn);
|
||||
conn = conn->next;
|
||||
idx++;
|
||||
}
|
||||
if (idx == 0) DUMP_HDR("--- NO CONNECTIONS ---");
|
||||
DUMP_HDR("=== END DUMP ALL ===");
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef ETCP_DUMP_H
|
||||
#define ETCP_DUMP_H
|
||||
|
||||
#include "etcp.h"
|
||||
#include "utun_instance.h"
|
||||
|
||||
void etcp_dump_conn_state(struct ETCP_CONN* conn);
|
||||
void etcp_dump_all_conns(struct UTUN_INSTANCE* instance);
|
||||
|
||||
#endif
|
||||
+74
-55
@@ -25,6 +25,7 @@
|
||||
static void shaper_timer_cb(void* arg); // Shaper wait callback
|
||||
|
||||
// Select link for transmission
|
||||
// Algorithm: min inflight_bytes, round-robin among ties
|
||||
struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
|
||||
if (!etcp || !etcp->links) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] invalid parameters (etcp=%p, links=%p)",
|
||||
@@ -32,19 +33,12 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct ETCP_LINK* best = NULL;
|
||||
uint64_t min_load_tb = UINT64_MAX;
|
||||
uint64_t now_tb=get_time_tb();
|
||||
// First pass: find minimum inflight_bytes among available links
|
||||
uint32_t min_inflight = UINT32_MAX;
|
||||
int available_count = 0;
|
||||
|
||||
struct ETCP_LINK* link = etcp->links;
|
||||
int link_index = 0;
|
||||
while (link) {
|
||||
link_index++;
|
||||
|
||||
// DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_select_link: link %d (%p) - initialized=%u, state=%u, load_tb=%llu, sub=%llu",
|
||||
// link_index, link, link->initialized, link->shaper_timer==NULL?1:0,
|
||||
// (unsigned long long)link->shaper_load_time_tb, (unsigned long long)link->shaper_sub_nanotime);
|
||||
|
||||
if (!link->initialized || link->link_status != 1) {
|
||||
link = link->next;
|
||||
continue;
|
||||
@@ -54,40 +48,73 @@ struct ETCP_LINK* etcp_loadbalancer_select_link(struct ETCP_CONN* etcp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (link->shaper_load_time_tb < now_tb-ALLOWED_DELTA) link->shaper_load_time_tb = now_tb-ALLOWED_DELTA;
|
||||
|
||||
// Check if ready (load < now + burst allowance)
|
||||
uint64_t effective_load_ns = link->shaper_load_time_tb * TIMEBASE_NS + (link->shaper_sub_nanotime / 10); // To ns
|
||||
uint64_t now_ns = now_tb * TIMEBASE_NS;
|
||||
int64_t delta_t=(int64_t)(now_ns - effective_load_ns);// uint64 может переполняться. чтобы обеспечить правильную цикличность.
|
||||
if (link->shaper_load_time_tb < min_load_tb || (link->shaper_load_time_tb == min_load_tb && link->shaper_sub_nanotime < best->shaper_sub_nanotime)) {
|
||||
min_load_tb = link->shaper_load_time_tb;
|
||||
best = link;
|
||||
available_count++;
|
||||
if (link->inflight_bytes < min_inflight) {
|
||||
min_inflight = link->inflight_bytes;
|
||||
}
|
||||
link = link->next;
|
||||
}
|
||||
|
||||
if (best) {
|
||||
// DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] selected link %p (load_tb=%llu)",
|
||||
// etcp->log_name, best, (unsigned long long)min_load_tb);
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found", etcp->log_name);
|
||||
if (available_count == 0) {
|
||||
if (etcp->links)
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "[%s] no suitable link found: tmr:%s",
|
||||
etcp->log_name, etcp->links->shaper_timer?"wait":"rdy");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return best;
|
||||
// Count how many links have the minimum inflight
|
||||
int min_count = 0;
|
||||
link = etcp->links;
|
||||
while (link) {
|
||||
if (link->initialized && link->link_status == 1 &&
|
||||
loadbalancer_link_can_send(link) && link->inflight_bytes == min_inflight) {
|
||||
min_count++;
|
||||
}
|
||||
link = link->next;
|
||||
}
|
||||
|
||||
if (min_count == 1) {
|
||||
// Only one link with minimum inflight - return it directly
|
||||
link = etcp->links;
|
||||
while (link) {
|
||||
if (link->initialized && link->link_status == 1 &&
|
||||
loadbalancer_link_can_send(link) && link->inflight_bytes == min_inflight) {
|
||||
return link;
|
||||
}
|
||||
link = link->next;
|
||||
}
|
||||
}
|
||||
|
||||
// Round-robin among links with minimum inflight
|
||||
// Start from the link after last_rr_link (or from the beginning if last_rr_link is NULL)
|
||||
struct ETCP_LINK* start = etcp->last_rr_link ? etcp->last_rr_link->next : etcp->links;
|
||||
if (!start) start = etcp->links;
|
||||
|
||||
link = start;
|
||||
do {
|
||||
if (link->initialized && link->link_status == 1 &&
|
||||
loadbalancer_link_can_send(link) && link->inflight_bytes == min_inflight) {
|
||||
etcp->last_rr_link = link;
|
||||
return link;
|
||||
}
|
||||
link = link->next;
|
||||
if (!link) link = etcp->links;
|
||||
} while (link != start);
|
||||
|
||||
// Should not reach here, but return NULL just in case
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// New: Send dgram (select link, encrypt/send, update shaper)
|
||||
void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
|
||||
if (!dgram) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_send: called with NULL dgram");
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "called with NULL dgram");
|
||||
return;
|
||||
}
|
||||
|
||||
struct ETCP_CONN* etcp = dgram->link ? dgram->link->etcp : NULL;
|
||||
if (!etcp) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "[%s] no ETCP_CONN associated with dgram", etcp->log_name);
|
||||
memory_pool_free(etcp->instance->pkt_pool, dgram);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "no ETCP_CONN associated with dgram (link=%p)", (void*)dgram->link);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,12 +141,14 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
|
||||
}
|
||||
|
||||
// Update shaper after successful send
|
||||
if (link->bandwidth == 0) {
|
||||
if (link->burst_active) {
|
||||
// burst bypasses shaper — не обновляем нагрузку
|
||||
} else if (link->bandwidth == 0) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] unlimited bandwidth, skipping shaper update", etcp->log_name);
|
||||
// Don't return here, let the function continue to free dgram at the end
|
||||
} else {
|
||||
// Time to transmit (ns per byte = 8e9 / bw for bits/sec)
|
||||
double byte_time_ns = 8000000000.0 / (double)link->bandwidth;
|
||||
// Time to transmit (ns per byte = 8e6 / bw for Kbits/sec)
|
||||
double byte_time_ns = 8000000.0 / (double)link->bandwidth;
|
||||
uint64_t tx_time_ns = (uint64_t)(pkt_size * byte_time_ns + 0.5); // Round nearest
|
||||
|
||||
// To sub_nanotime (*10 for 0.1ns)
|
||||
@@ -135,8 +164,9 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
|
||||
uint64_t now_tb=get_time_tb();
|
||||
if (link->shaper_load_time_tb >= now_tb + SHAPER_BURST_DELAY_TB) {
|
||||
uint64_t wait_tb = link->shaper_load_time_tb - now_tb;
|
||||
link->shaper_timer = uasync_set_timeout(link->etcp->instance->ua, wait_tb, link, shaper_timer_cb);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduled shaper timer (wait_tb=%llu)", etcp->log_name, (unsigned long long)wait_tb);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "[%s] scheduling shaper timer (wait_tb=%llu) BW=%d pkt_size=%d", etcp->log_name, (unsigned long long)wait_tb, link->bandwidth, pkt_size);
|
||||
link->shaper_timer = uasync_set_timeout(link->etcp->instance->ua, wait_tb, link, shaper_timer_cb, "shaper");
|
||||
if (link->shaper_timer == NULL) DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "Filed to allocate timer");
|
||||
}
|
||||
|
||||
// Inactivity correction
|
||||
@@ -150,43 +180,32 @@ void etcp_loadbalancer_send(struct ETCP_DGRAM* dgram) {
|
||||
|
||||
int loadbalancer_link_can_send(struct ETCP_LINK* link) {
|
||||
if (!link) return 0;
|
||||
int can = 1;
|
||||
if (link->inflight_bytes >= link->inflight_lim_bytes) {
|
||||
link->send_blocked_inflight = 1;
|
||||
can = 0;
|
||||
} else {
|
||||
link->send_blocked_inflight = 0;
|
||||
}
|
||||
if (link->shaper_timer) {
|
||||
// link->send_blocked_bandwidth = 1;
|
||||
can = 0;
|
||||
} else {
|
||||
// link->send_blocked_bandwidth = 0;
|
||||
}
|
||||
return can;
|
||||
if (link->burst_active) return 1; // burst bypasses all limits
|
||||
if (link->shaper_timer) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void loadbalancer_link_ready(struct ETCP_LINK* link) {
|
||||
if (!link || !link->etcp) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: invalid link (%p)", link);
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "invalid link (%p)", link);
|
||||
return;
|
||||
}
|
||||
if (loadbalancer_link_can_send(link) == 0) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: link still blocked");
|
||||
if (link->shaper_timer) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link still blocked by shaper");
|
||||
return;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: link=%p now ready, notifying ETCP_CONN", link);
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "link=%p now ready, notifying ETCP_CONN", link);
|
||||
if (link->etcp->link_ready_for_send_fn) {
|
||||
link->etcp->link_ready_for_send_fn(link->etcp);
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "loadbalancer_link_ready: no link_ready_for_send_fn set");
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "no link_ready_for_send_fn set");
|
||||
}
|
||||
}
|
||||
|
||||
// Get ETCP link status: 1 = at least one link is up, 0 = all links down or no links
|
||||
int etcp_loadbalancer_get_link_status(struct ETCP_CONN* etcp) {
|
||||
if (!etcp) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "etcp_loadbalancer_get_link_status: NULL etcp");
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCP, "NULL etcp");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -201,7 +220,7 @@ int etcp_loadbalancer_get_link_status(struct ETCP_CONN* etcp) {
|
||||
}
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCP, "[%s] link status check: %d alive links",
|
||||
etcp->log_name ? etcp->log_name : "????→????", alive_count);
|
||||
etcp->log_name[0] ? etcp->log_name : "????→????", alive_count);
|
||||
|
||||
return (alive_count > 0) ? 1 : 0;
|
||||
}
|
||||
@@ -212,7 +231,7 @@ static void shaper_timer_cb(void* arg) {
|
||||
if (!link) return;
|
||||
|
||||
link->shaper_timer = NULL;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "shaper_timer_cb: link=%p now ready", link);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "link=%p now ready", link);
|
||||
|
||||
// Notify loadbalancer that link is ready
|
||||
loadbalancer_link_ready(link);
|
||||
|
||||
@@ -0,0 +1,840 @@
|
||||
// etcp_router.c — Сервисный слой маршрутизации поверх ETCP
|
||||
// Упрощённый TCP: восстановление порядка (recv_q), дедупликация, без переповторов
|
||||
// ACK: периодическая отправка rx_seq (не чаще 100ms), idle-таймер для последнего seq
|
||||
// Inflight контроль через send_q: при переполнении — очередь + retry-таймер, без дропов
|
||||
#include "etcp_router.h"
|
||||
#include "etcp.h"
|
||||
#include "utun_instance.h"
|
||||
#include "route_bgp.h"
|
||||
#include "../lib/debug_config.h"
|
||||
#include "../lib/mem.h"
|
||||
#include "../lib/ll_queue.h"
|
||||
#include "../lib/u_async.h"
|
||||
#include <string.h>
|
||||
|
||||
// ====================================================================
|
||||
// Внутренние forward declarations
|
||||
// ====================================================================
|
||||
static void router_ack_timer_cb(void* arg);
|
||||
static void router_idle_ack_timer_cb(void* arg);
|
||||
static void router_send_ack(struct ETCP_ROUTER_CONN* rconn);
|
||||
static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn);
|
||||
static void router_try_assembly(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn);
|
||||
static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn, const uint8_t* payload, size_t payload_len);
|
||||
static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len);
|
||||
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags, int is_signed);
|
||||
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint32_t seq_flags);
|
||||
static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn);
|
||||
static void router_send_resume_cb(void* arg);
|
||||
static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn);
|
||||
static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn);
|
||||
|
||||
// ====================================================================
|
||||
// Управление ROUTER_CONN
|
||||
// ====================================================================
|
||||
|
||||
static struct ETCP_ROUTER_CONN* router_conn_find(struct UTUN_INSTANCE* inst,
|
||||
uint64_t remote_node_id, uint8_t svc_id) {
|
||||
if (!inst || !inst->router_conns) return NULL;
|
||||
uint8_t key[9];
|
||||
memcpy(key, &remote_node_id, 8);
|
||||
key[8] = svc_id;
|
||||
return (struct ETCP_ROUTER_CONN*)queue_find_data_by_index(inst->router_conns, key);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Отправка: router_send_one + send_q
|
||||
// ====================================================================
|
||||
|
||||
static int router_send_one(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len) {
|
||||
return router_send_one_flags(rconn, payload, pl_len, 0, 0);
|
||||
}
|
||||
|
||||
static int router_send_one_flags(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, uint32_t seq_flags, int is_signed) {
|
||||
struct UTUN_INSTANCE* inst = rconn->inst;
|
||||
uint32_t seq = (seq_flags & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))
|
||||
? 0 : rconn->tx_seq++;
|
||||
seq |= seq_flags;
|
||||
size_t sig_len = is_signed ? SC_SIGN_SIZE : 0;
|
||||
size_t total_len = SVC_ROUTE_HDR_SIZE + pl_len + sig_len;
|
||||
uint8_t* dgram = u_malloc(total_len);
|
||||
if (!dgram) { if (!seq_flags) rconn->tx_seq--; return -1; }
|
||||
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)dgram;
|
||||
hdr->cmd = ETCP_ID_SVC_ROUTE;
|
||||
hdr->dst_node_id = rconn->remote_node_id;
|
||||
hdr->src_node_id = inst->node_id;
|
||||
hdr->seq = seq;
|
||||
hdr->svc_id = rconn->svc_id;
|
||||
{
|
||||
uint8_t f = (rconn->sess_id << ROUTER_SESS_ID_SHIFT);
|
||||
if (!rconn->start_sent && pl_len > 0 && !(seq_flags & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))) {
|
||||
f |= ROUTER_FLAG_START;
|
||||
rconn->start_sent = 1;
|
||||
}
|
||||
if (is_signed) f |= ROUTER_FLAG_SIGNED;
|
||||
hdr->flags = f;
|
||||
}
|
||||
if (pl_len > 0) memcpy(dgram + SVC_ROUTE_HDR_SIZE, payload, pl_len);
|
||||
|
||||
struct ETCP_CONN* conn = NULL;
|
||||
struct ROUTE_BGP* bgp = inst->bgp;
|
||||
if (bgp) {
|
||||
struct NODEINFO_Q* nq = nodeinfo_find_by_id(bgp, rconn->remote_node_id);
|
||||
if (nq && nq->conn_mgr_type == CONN_TYPE_INDIRECT && nq->conn_mgr_intermediariy_count > 0) {
|
||||
for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++) {
|
||||
conn = route_bgp_find_conn_for_node(bgp, nq->conn_mgr_intermediaries[i]);
|
||||
if (conn) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!conn) conn = route_bgp_find_conn_for_node(bgp, rconn->remote_node_id);
|
||||
if (!conn) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: no route to %016llx svc_id=%u",
|
||||
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
|
||||
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
|
||||
}
|
||||
|
||||
if (is_signed) {
|
||||
struct sc_stream_sign_state sign_state;
|
||||
if (sc_stream_sign_init(&conn->crypto_ctx, &sign_state) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_init failed svc_id=%u", rconn->svc_id);
|
||||
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
|
||||
}
|
||||
size_t data_len = SVC_ROUTE_HDR_SIZE + pl_len;
|
||||
if (sc_stream_sign_update(&sign_state, dgram, data_len) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_update failed svc_id=%u", rconn->svc_id);
|
||||
sc_stream_sign_cleanup(&sign_state); u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
|
||||
}
|
||||
size_t actual_sig_len = SC_SIGN_SIZE;
|
||||
if (sc_stream_sign_final(&sign_state, dgram + data_len, &actual_sig_len) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_send_one: sign_final failed svc_id=%u", rconn->svc_id);
|
||||
u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1;
|
||||
}
|
||||
}
|
||||
|
||||
struct ll_entry* entry = queue_entry_new(0);
|
||||
if (!entry) { u_free(dgram); if (!seq_flags) rconn->tx_seq--; return -1; }
|
||||
entry->dgram = dgram;
|
||||
entry->len = total_len;
|
||||
|
||||
rconn->last_dgram_ts = get_current_timestamp();
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_SEND: svc_id=%u seq=%u len=%zu inflight=%d signed=%d → %016llx",
|
||||
rconn->svc_id, seq, pl_len,
|
||||
(int32_t)(rconn->tx_seq - rconn->tx_acked),
|
||||
is_signed,
|
||||
(unsigned long long)rconn->remote_node_id);
|
||||
return etcp_send(conn, entry);
|
||||
}
|
||||
|
||||
// Отправка без conn (RST для неизвестного src)
|
||||
static void router_send_to(struct UTUN_INSTANCE* inst, uint64_t dst, uint8_t svc_id, uint32_t seq_flags) {
|
||||
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, dst);
|
||||
if (!conn) return;
|
||||
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)u_malloc(SVC_ROUTE_HDR_SIZE);
|
||||
if (!hdr) return;
|
||||
hdr->cmd = ETCP_ID_SVC_ROUTE;
|
||||
hdr->dst_node_id = dst;
|
||||
hdr->src_node_id = inst->node_id;
|
||||
hdr->seq = seq_flags;
|
||||
hdr->svc_id = svc_id;
|
||||
struct ll_entry* entry = queue_entry_new(0);
|
||||
if (!entry) { u_free(hdr); return; }
|
||||
entry->dgram = (uint8_t*)hdr;
|
||||
entry->len = SVC_ROUTE_HDR_SIZE;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_to: seq_flags=0x%08x → %016llx svc_id=%u",
|
||||
seq_flags, (unsigned long long)dst, svc_id);
|
||||
etcp_send(conn, entry);
|
||||
}
|
||||
|
||||
static void router_send_close_to_service(struct ETCP_ROUTER_CONN* rconn) {
|
||||
struct UTUN_INSTANCE* inst = rconn->inst;
|
||||
etcp_recv_fn cb = inst->router_bindings.callbacks[rconn->svc_id];
|
||||
if (!cb) return;
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (!e) return;
|
||||
e->dgram = u_malloc(9);
|
||||
if (!e->dgram) { queue_entry_free(e); return; }
|
||||
e->dgram[0] = rconn->svc_id;
|
||||
memcpy(e->dgram + 1, &rconn->remote_node_id, 8);
|
||||
e->len = 9;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_close_svc: svc_id=%u remote=%016llx",
|
||||
rconn->svc_id, (unsigned long long)rconn->remote_node_id);
|
||||
cb(NULL, e);
|
||||
}
|
||||
|
||||
// Закрыть conn: отправить CLOSE удалённой стороне, уведомить локальный сервис, очистить
|
||||
static void router_close_and_notify(struct ETCP_ROUTER_CONN* rconn) {
|
||||
if (!rconn) return;
|
||||
struct UTUN_INSTANCE* inst = rconn->inst;
|
||||
router_send_close_to_service(rconn);
|
||||
// Отправляем CLOSE удалённой стороне
|
||||
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
|
||||
if (conn) router_send_one_flags(rconn, NULL, 0, ROUTER_SEQ_CLOSE_FLAG, 0);
|
||||
// Таймеры
|
||||
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
|
||||
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
|
||||
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
|
||||
if (rconn->recv_q) {
|
||||
struct ll_entry* f;
|
||||
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->recv_q); rconn->recv_q = NULL;
|
||||
}
|
||||
if (rconn->send_q) {
|
||||
struct ll_entry* f;
|
||||
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->send_q); rconn->send_q = NULL;
|
||||
}
|
||||
if (inst->router_conns)
|
||||
queue_remove_data(inst->router_conns, &rconn->ll);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: closed remote=%016llx svc_id=%u",
|
||||
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
|
||||
queue_entry_free(&rconn->ll);
|
||||
}
|
||||
|
||||
static void router_drain_send_q(struct ETCP_ROUTER_CONN* rconn) {
|
||||
int sq = queue_entry_count(rconn->send_q);
|
||||
if (sq > 0) DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "SEND_Q_DRAIN: svc_id=%u send_q=%d inflight=%d",
|
||||
rconn->svc_id, sq, (int32_t)(rconn->tx_seq - rconn->tx_acked));
|
||||
while (1) {
|
||||
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) break;
|
||||
struct ll_entry* e = queue_data_get(rconn->send_q);
|
||||
if (!e) { rconn->send_blocked = 0; break; }
|
||||
router_send_one(rconn, e->dgram, e->len);
|
||||
queue_dgram_free(e); queue_entry_free(e);
|
||||
}
|
||||
if (rconn->send_blocked && !rconn->send_resume_timer)
|
||||
rconn->send_resume_timer = uasync_set_timeout(rconn->inst->ua,
|
||||
ROUTER_SEND_RESUME_TB, rconn, router_send_resume_cb, "router_send_resume");
|
||||
if (!rconn->send_blocked && rconn->send_resume_timer) {
|
||||
uasync_cancel_timeout(rconn->inst->ua, rconn->send_resume_timer);
|
||||
rconn->send_resume_timer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void router_send_resume_cb(void* arg) {
|
||||
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
|
||||
rconn->send_resume_timer = NULL;
|
||||
router_drain_send_q(rconn);
|
||||
}
|
||||
|
||||
static int router_enqueue_send(struct ETCP_ROUTER_CONN* rconn, const uint8_t* payload, size_t pl_len, int force) {
|
||||
if (!force && queue_entry_count(rconn->send_q) >= ROUTER_MAX_SEND_Q_PACKETS) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_send_q: FULL svc_id=%u count=%d — backpressure",
|
||||
rconn->svc_id, queue_entry_count(rconn->send_q));
|
||||
return -1;
|
||||
}
|
||||
struct ll_entry* qe = queue_entry_new(0);
|
||||
if (!qe) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_enqueue_send: queue_entry_new failed"); return -1; }
|
||||
qe->dgram = u_malloc(pl_len);
|
||||
if (!qe->dgram) { queue_entry_free(qe); return -1; }
|
||||
qe->len = pl_len;
|
||||
if (pl_len > 0) memcpy(qe->dgram, payload, pl_len);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router_send_q: queued svc_id=%u inflight=%d send_q=%d",
|
||||
rconn->svc_id, (int32_t)(rconn->tx_seq - rconn->tx_acked), queue_entry_count(rconn->send_q));
|
||||
queue_data_put(rconn->send_q, qe);
|
||||
rconn->send_blocked = 1;
|
||||
if (!rconn->send_resume_timer)
|
||||
rconn->send_resume_timer = uasync_set_timeout(rconn->inst->ua,
|
||||
ROUTER_SEND_RESUME_TB, rconn, router_send_resume_cb, "router_send_resume");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// ACK timers
|
||||
// ====================================================================
|
||||
|
||||
static void router_ack_do_send(struct ETCP_ROUTER_CONN* rconn) {
|
||||
router_send_ack(rconn);
|
||||
rconn->last_sent_ack_seq = rconn->rx_seq;
|
||||
rconn->last_ack_sent_tb = get_time_tb();
|
||||
}
|
||||
|
||||
static void router_send_ack(struct ETCP_ROUTER_CONN* rconn) {
|
||||
struct UTUN_INSTANCE* inst = rconn->inst;
|
||||
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, rconn->remote_node_id);
|
||||
if (!conn) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_ack: no route to %016llx svc_id=%u",
|
||||
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
|
||||
return;
|
||||
}
|
||||
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)u_malloc(SVC_ROUTE_HDR_SIZE);
|
||||
if (!hdr) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_ack: u_malloc failed"); return; }
|
||||
hdr->cmd = ETCP_ID_SVC_ROUTE;
|
||||
hdr->dst_node_id = rconn->remote_node_id;
|
||||
hdr->src_node_id = inst->node_id;
|
||||
hdr->seq = rconn->rx_seq;
|
||||
hdr->svc_id = rconn->svc_id;
|
||||
hdr->flags = (rconn->sess_id << ROUTER_SESS_ID_SHIFT);
|
||||
|
||||
struct ll_entry* entry = queue_entry_new(0);
|
||||
if (!entry) { u_free(hdr); return; }
|
||||
entry->dgram = (uint8_t*)hdr;
|
||||
entry->len = SVC_ROUTE_HDR_SIZE;
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ACK_SEND: svc_id=%u rx_seq=%u → %016llx",
|
||||
rconn->svc_id, rconn->rx_seq, (unsigned long long)rconn->remote_node_id);
|
||||
etcp_send(conn, entry);
|
||||
}
|
||||
|
||||
static void router_schedule_ack(struct ETCP_ROUTER_CONN* rconn) {
|
||||
if (rconn->idle_ack_timer) {
|
||||
uasync_cancel_timeout(rconn->inst->ua, rconn->idle_ack_timer);
|
||||
rconn->idle_ack_timer = NULL;
|
||||
}
|
||||
if (rconn->rx_seq == rconn->last_sent_ack_seq) return;
|
||||
uint64_t now = get_time_tb();
|
||||
uint64_t elapsed = now - rconn->last_ack_sent_tb;
|
||||
if (elapsed >= ROUTER_ACK_INTERVAL_TB) {
|
||||
router_ack_do_send(rconn);
|
||||
return;
|
||||
}
|
||||
if (!rconn->ack_timer)
|
||||
rconn->ack_timer = uasync_set_timeout(rconn->inst->ua,
|
||||
ROUTER_ACK_INTERVAL_TB - (uint32_t)elapsed, rconn, router_ack_timer_cb, "router_ack");
|
||||
}
|
||||
|
||||
static void router_ack_timer_cb(void* arg) {
|
||||
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
|
||||
rconn->ack_timer = NULL;
|
||||
if (rconn->rx_seq != rconn->last_sent_ack_seq)
|
||||
router_ack_do_send(rconn);
|
||||
if (rconn->rx_seq != rconn->last_sent_ack_seq) {
|
||||
if (!rconn->ack_timer)
|
||||
rconn->ack_timer = uasync_set_timeout(rconn->inst->ua,
|
||||
ROUTER_ACK_INTERVAL_TB, rconn, router_ack_timer_cb, "router_ack");
|
||||
} else if (!rconn->idle_ack_timer) {
|
||||
rconn->idle_ack_timer = uasync_set_timeout(rconn->inst->ua,
|
||||
ROUTER_ACK_IDLE_TB, rconn, router_idle_ack_timer_cb, "router_idle_ack");
|
||||
}
|
||||
}
|
||||
|
||||
static void router_idle_ack_timer_cb(void* arg) {
|
||||
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)arg;
|
||||
rconn->idle_ack_timer = NULL;
|
||||
if (rconn->rx_seq != rconn->last_sent_ack_seq)
|
||||
router_ack_do_send(rconn);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Reorder / assembly (аналог etcp_output_try_assembly)
|
||||
// ====================================================================
|
||||
|
||||
static void router_deliver(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn,
|
||||
const uint8_t* payload, size_t payload_len) {
|
||||
struct UTUN_INSTANCE* inst = rconn->inst;
|
||||
etcp_recv_fn cb = inst->router_bindings.callbacks[rconn->svc_id];
|
||||
if (!cb) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_deliver: no handler for svc_id=%u", rconn->svc_id);
|
||||
return;
|
||||
}
|
||||
size_t entry_len = 1 + payload_len;
|
||||
struct ll_entry* svc_entry = queue_entry_new(0);
|
||||
if (!svc_entry) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_deliver: queue_entry_new failed"); return; }
|
||||
svc_entry->len = entry_len;
|
||||
svc_entry->dgram = u_malloc(entry_len);
|
||||
if (!svc_entry->dgram) { queue_entry_free(svc_entry); return; }
|
||||
svc_entry->dgram[0] = rconn->svc_id;
|
||||
if (payload_len > 0) memcpy(svc_entry->dgram + 1, payload, payload_len);
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "router_deliver: svc_id=%u len=%zu from remote=%016llx",
|
||||
rconn->svc_id, payload_len, (unsigned long long)rconn->remote_node_id);
|
||||
cb(conn, svc_entry);
|
||||
}
|
||||
|
||||
static void router_try_assembly(struct ETCP_ROUTER_CONN* rconn, struct ETCP_CONN* conn) {
|
||||
uint32_t next = rconn->rx_seq;
|
||||
while (1) {
|
||||
struct ll_entry* entry = queue_find_data_by_index(rconn->recv_q, &next);
|
||||
if (!entry) break;
|
||||
queue_remove_data(rconn->recv_q, entry);
|
||||
router_deliver(rconn, conn, entry->dgram, entry->len);
|
||||
rconn->rx_seq = next + 1;
|
||||
next++;
|
||||
queue_dgram_free(entry);
|
||||
queue_entry_free(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// etcp_router_recv_cb — обработчик ETCP_ID_SVC_ROUTE
|
||||
// ====================================================================
|
||||
|
||||
static void etcp_router_recv_cb(struct ETCP_CONN* conn, struct ll_entry* entry) {
|
||||
if (!entry) return;
|
||||
struct UTUN_INSTANCE* inst = conn ? conn->instance : NULL;
|
||||
if (!inst || entry->len < SVC_ROUTE_HDR_SIZE) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: invalid packet inst=%p len=%zu min=%u",
|
||||
(void*)inst, entry->len, (unsigned)SVC_ROUTE_HDR_SIZE);
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return;
|
||||
}
|
||||
struct SVC_ROUTE_HDR* hdr = (struct SVC_ROUTE_HDR*)entry->dgram;
|
||||
size_t pl_len = entry->len - SVC_ROUTE_HDR_SIZE;
|
||||
uint8_t* pl = entry->dgram + SVC_ROUTE_HDR_SIZE;
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "ETCP_RECV: svc_id=%u seq=%u len=%zu from %016llx",
|
||||
hdr->svc_id, hdr->seq, pl_len, (unsigned long long)hdr->src_node_id);
|
||||
|
||||
if (hdr->dst_node_id == inst->node_id) {
|
||||
// ========== Проверка подписи (если флаг ROUTER_FLAG_SIGNED) ==========
|
||||
size_t actual_payload = pl_len;
|
||||
if (hdr->flags & ROUTER_FLAG_SIGNED) {
|
||||
if (pl_len < SC_SIGN_SIZE) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: SIGNED packet too short len=%zu from %016llx", pl_len, (unsigned long long)hdr->src_node_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
actual_payload = pl_len - SC_SIGN_SIZE;
|
||||
uint8_t* sig = pl + actual_payload;
|
||||
struct ROUTE_BGP* bgp = inst->bgp;
|
||||
struct NODEINFO_Q* nq = bgp ? nodeinfo_find_by_id(bgp, hdr->src_node_id) : NULL;
|
||||
if (!nq) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: SIGNED packet from unknown node %016llx — dropping", (unsigned long long)hdr->src_node_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
{
|
||||
uint8_t zero[SC_PUBKEY_SIZE] = {0};
|
||||
if (memcmp(nq->node.ed25519_public_key, zero, SC_PUBKEY_SIZE) == 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: no Ed25519 pubkey for node %016llx — dropping", (unsigned long long)hdr->src_node_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
struct sc_stream_sign_state vfy_state;
|
||||
if (sc_stream_sign_verify_init(&vfy_state, nq->node.ed25519_public_key) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: sign_verify_init failed for node %016llx", (unsigned long long)hdr->src_node_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
size_t signed_len = SVC_ROUTE_HDR_SIZE + actual_payload;
|
||||
if (sc_stream_sign_update(&vfy_state, entry->dgram, signed_len) != SC_OK) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: sign_update failed for node %016llx", (unsigned long long)hdr->src_node_id);
|
||||
sc_stream_sign_cleanup(&vfy_state); queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
if (sc_stream_sign_verify(&vfy_state, sig, SC_SIGN_SIZE) != SC_OK) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: signature verification FAILED for node %016llx svc_id=%u — dropping", (unsigned long long)hdr->src_node_id, hdr->svc_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
pl_len = actual_payload;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: signature verified for node %016llx svc_id=%u", (unsigned long long)hdr->src_node_id, hdr->svc_id);
|
||||
}
|
||||
|
||||
// ========== Мы — целевая нода ==========
|
||||
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, hdr->src_node_id, hdr->svc_id);
|
||||
|
||||
// CLOSE или RST — pl_len==0 и флаг в seq (закрываем conn)
|
||||
if (pl_len == 0 && (hdr->seq & (ROUTER_SEQ_CLOSE_FLAG | ROUTER_SEQ_RST_FLAG))) {
|
||||
if (rconn) router_close_and_notify(rconn);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pl_len == 0) {
|
||||
if (rconn) {
|
||||
if ((int32_t)(hdr->seq - rconn->tx_acked) >= 0) {
|
||||
rconn->tx_acked = hdr->seq;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "SEND_Q_ACKED: svc_id=%u ack=%u inflight=%d send_q=%d from %016llx",
|
||||
rconn->svc_id, hdr->seq, (int32_t)(rconn->tx_seq - rconn->tx_acked),
|
||||
queue_entry_count(rconn->send_q),
|
||||
(unsigned long long)hdr->src_node_id);
|
||||
} else {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router: stale ACK seq=%u tx_acked=%u from %016llx, ignoring",
|
||||
hdr->seq, rconn->tx_acked, (unsigned long long)hdr->src_node_id);
|
||||
}
|
||||
rconn->last_dgram_ts = get_current_timestamp();
|
||||
if (rconn->send_blocked) router_drain_send_q(rconn);
|
||||
}
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
// Data-пакет — авто-создаём conn если ещё нет
|
||||
if (!rconn) {
|
||||
rconn = etcp_router_conn_get(inst, hdr->src_node_id, hdr->svc_id);
|
||||
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
}
|
||||
|
||||
// Обнаружение перезапуска peer'а: новый sess_id или START-флаг
|
||||
{
|
||||
uint8_t incoming_sess = (hdr->flags >> ROUTER_SESS_ID_SHIFT) & 0x3;
|
||||
if (pl_len > 0 && (incoming_sess != rconn->peer_sess_id ||
|
||||
((hdr->flags & ROUTER_FLAG_START) && rconn->rx_seq > 0))) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE,
|
||||
"router: peer restart sess_id=%u→%u svc_id=%u from %016llx",
|
||||
rconn->peer_sess_id, incoming_sess, hdr->svc_id,
|
||||
(unsigned long long)hdr->src_node_id);
|
||||
etcp_router_conn_restart(inst, hdr->src_node_id, hdr->svc_id);
|
||||
rconn = etcp_router_conn_get(inst, hdr->src_node_id, hdr->svc_id);
|
||||
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
rconn->peer_sess_id = incoming_sess;
|
||||
}
|
||||
// RST-флаг — peer сигналит нарушение последовательности
|
||||
if (hdr->flags & ROUTER_FLAG_RST) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE,
|
||||
"router: RST from %016llx svc_id=%u",
|
||||
(unsigned long long)hdr->src_node_id, hdr->svc_id);
|
||||
router_close_and_notify(rconn);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Seq-состояние есть — reorder
|
||||
rconn->last_dgram_ts = get_current_timestamp();
|
||||
uint32_t seq = hdr->seq;
|
||||
|
||||
// Проверка границ (32-bit circular, аналог ETCP MAX_INFLIGHT_SIZE)
|
||||
{
|
||||
int32_t d = (int32_t)(seq - rconn->rx_seq);
|
||||
if (d > ROUTER_MAX_INFLIGHT || d < -ROUTER_MAX_INFLIGHT) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router: seq=%u out of bounds, rx_seq=%u (d=%d), dropping",
|
||||
seq, rconn->rx_seq, d);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Дубликат (seq уже доставлен или в recv_q)
|
||||
if (((int32_t)(rconn->rx_seq - seq) > 0) ||
|
||||
queue_find_data_by_index(rconn->recv_q, &seq)) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCPROUTE, "router: dup seq=%u rx_seq=%u, dropping", seq, rconn->rx_seq);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
|
||||
// Кладём в recv_q
|
||||
struct ll_entry* qe = queue_entry_new(4);
|
||||
if (!qe) { queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
*(uint32_t*)qe->data = seq;
|
||||
qe->dgram = u_malloc(pl_len);
|
||||
if (!qe->dgram) { queue_entry_free(qe); queue_dgram_free(entry); queue_entry_free(entry); return; }
|
||||
qe->len = pl_len;
|
||||
memcpy(qe->dgram, pl, pl_len);
|
||||
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "router: queued seq=%u rx_seq=%u recv_q=%d from %016llx svc_id=%u",
|
||||
seq, rconn->rx_seq, queue_entry_count(rconn->recv_q),
|
||||
(unsigned long long)hdr->src_node_id, rconn->svc_id);
|
||||
queue_data_put_with_index(rconn->recv_q, qe);
|
||||
|
||||
if (seq == rconn->rx_seq)
|
||||
router_try_assembly(rconn, conn);
|
||||
|
||||
router_schedule_ack(rconn);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
} else {
|
||||
// ========== Транзит ==========
|
||||
struct ETCP_CONN* next = route_bgp_find_conn_for_node(inst->bgp, hdr->dst_node_id);
|
||||
if (!next) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: no route to %016llx svc_id=%u, dropping",
|
||||
(unsigned long long)hdr->dst_node_id, hdr->svc_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return;
|
||||
}
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "etcp_router: forwarding svc_id=%u → %016llx via %s",
|
||||
hdr->svc_id, (unsigned long long)hdr->dst_node_id, next->log_name);
|
||||
etcp_send(next, entry);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Public API
|
||||
// ====================================================================
|
||||
|
||||
int etcp_router_init(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_init: NULL instance"); return -1; }
|
||||
memset(&inst->router_bindings, 0, sizeof(inst->router_bindings));
|
||||
inst->router_conns = queue_new(inst->ua,
|
||||
ROUTER_CONN_HASH_SIZE, 0, 9, "router_conns");
|
||||
if (!inst->router_conns) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_init: queue_new(router_conns) failed");
|
||||
return -1;
|
||||
}
|
||||
int ret = etcp_bind(inst, ETCP_ID_SVC_ROUTE, etcp_router_recv_cb);
|
||||
if (ret != 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_init: etcp_bind failed, ret=%d", ret);
|
||||
queue_free(inst->router_conns); inst->router_conns = NULL;
|
||||
return -1;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "etcp_router initialized for node %016llx", (unsigned long long)inst->node_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void etcp_router_destroy(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) return;
|
||||
etcp_unbind(inst, ETCP_ID_SVC_ROUTE);
|
||||
if (inst->router_conns) {
|
||||
struct ll_entry* entry;
|
||||
while ((entry = queue_data_get(inst->router_conns)) != NULL) {
|
||||
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)entry;
|
||||
router_send_close_to_service(rconn);
|
||||
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
|
||||
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
|
||||
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
|
||||
if (rconn->recv_q) {
|
||||
struct ll_entry* f;
|
||||
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->recv_q);
|
||||
}
|
||||
if (rconn->send_q) {
|
||||
struct ll_entry* f;
|
||||
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->send_q);
|
||||
}
|
||||
queue_entry_free(entry);
|
||||
}
|
||||
queue_free(inst->router_conns);
|
||||
inst->router_conns = NULL;
|
||||
}
|
||||
memset(&inst->router_bindings, 0, sizeof(inst->router_bindings));
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "etcp_router destroyed");
|
||||
}
|
||||
|
||||
int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn callback) {
|
||||
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_bind: NULL instance"); return -1; }
|
||||
if (!callback) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_bind: NULL callback for svc_id=%u", svc_id); return -1; }
|
||||
if (inst->router_bindings.callbacks[svc_id])
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_bind: overwriting svc_id=%u", svc_id);
|
||||
inst->router_bindings.callbacks[svc_id] = callback;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_bind: svc_id=%u → cb=%p", svc_id, (void*)callback);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id) {
|
||||
if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_unbind: NULL instance"); return -1; }
|
||||
if (!inst->router_bindings.callbacks[svc_id]) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_unbind: svc_id=%u not bound", svc_id);
|
||||
return -1;
|
||||
}
|
||||
inst->router_bindings.callbacks[svc_id] = NULL;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "etcp_router_unbind: svc_id=%u", svc_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry, int force) {
|
||||
if (!inst || !entry) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_route_send: NULL inst=%p entry=%p", (void*)inst, (void*)entry);
|
||||
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return -1;
|
||||
}
|
||||
if (!entry->dgram || entry->len < 1) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "etcp_route_send: empty entry"); queue_dgram_free(entry); queue_entry_free(entry); return -1;
|
||||
}
|
||||
uint8_t svc_id = entry->dgram[0];
|
||||
size_t payload_len = entry->len - 1;
|
||||
|
||||
if (dst_node_id == inst->node_id) {
|
||||
DEBUG_TRACE(DEBUG_CATEGORY_ETCPROUTE, "etcp_route_send: loopback svc_id=%u len=%zu", svc_id, payload_len);
|
||||
if (inst->router_bindings.callbacks[svc_id])
|
||||
inst->router_bindings.callbacks[svc_id](NULL, entry);
|
||||
else { queue_dgram_free(entry); queue_entry_free(entry); }
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct ETCP_CONN* etcp_conn = route_bgp_find_conn_for_node(inst->bgp, dst_node_id);
|
||||
if (!etcp_conn) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "etcp_route_send: no BGP route to %016llx svc_id=%u, dropping",
|
||||
(unsigned long long)dst_node_id, svc_id);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(inst, dst_node_id, svc_id);
|
||||
if (!rconn) { queue_dgram_free(entry); queue_entry_free(entry); return -1; }
|
||||
|
||||
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) {
|
||||
int eq_ret = router_enqueue_send(rconn, entry->dgram + 1, payload_len, force);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return eq_ret;
|
||||
}
|
||||
|
||||
int ret = router_send_one(rconn, entry->dgram + 1, payload_len);
|
||||
queue_dgram_free(entry); queue_entry_free(entry);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id) {
|
||||
if (!inst || !inst->bgp) return -1;
|
||||
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, node_id);
|
||||
if (!conn || !conn->normalizer || !conn->normalizer->input) return -1;
|
||||
return queue_entry_count(conn->normalizer->input);
|
||||
}
|
||||
|
||||
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
|
||||
struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg) {
|
||||
if (!inst || !inst->bgp || !h) return;
|
||||
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, peer_node_id);
|
||||
if (!conn || !conn->normalizer || !conn->normalizer->input) return;
|
||||
queue_waiter_wait(conn->normalizer->input, h, callback, arg);
|
||||
}
|
||||
|
||||
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
|
||||
struct queue_waiter_handle* h) {
|
||||
if (!inst || !inst->bgp || !h) return;
|
||||
struct ETCP_CONN* conn = route_bgp_find_conn_for_node(inst->bgp, peer_node_id);
|
||||
if (!conn || !conn->normalizer || !conn->normalizer->input) return;
|
||||
queue_waiter_cancel(conn->normalizer->input, h);
|
||||
}
|
||||
|
||||
void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_id, uint8_t svc_id) {
|
||||
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, remote_node_id, svc_id);
|
||||
if (!rconn) return;
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_restart: svc_id=%u remote=%016llx — resetting local state",
|
||||
svc_id, (unsigned long long)remote_node_id);
|
||||
|
||||
etcp_recv_fn cb = inst->router_bindings.callbacks[svc_id];
|
||||
if (cb) {
|
||||
struct ll_entry* e = queue_entry_new(0);
|
||||
if (e) {
|
||||
e->dgram = u_malloc(9);
|
||||
if (e->dgram) {
|
||||
e->dgram[0] = svc_id;
|
||||
memcpy(e->dgram + 1, &remote_node_id, 8);
|
||||
e->len = 9;
|
||||
cb(NULL, e);
|
||||
} else { queue_entry_free(e); }
|
||||
}
|
||||
}
|
||||
|
||||
struct ll_entry* f;
|
||||
if (rconn->send_q) {
|
||||
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->send_q);
|
||||
}
|
||||
if (rconn->recv_q) {
|
||||
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->recv_q);
|
||||
}
|
||||
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
|
||||
rconn->recv_q = queue_new(inst->ua, ROUTER_RECVQ_HASH_SIZE, 0, 4, "router_recv_q");
|
||||
queue_set_threshold(rconn->send_q, ROUTER_MAX_SEND_Q_PACKETS / 2, 0);
|
||||
|
||||
rconn->tx_seq = 0;
|
||||
rconn->rx_seq = 0;
|
||||
rconn->tx_acked = 0;
|
||||
rconn->last_sent_ack_seq = 0;
|
||||
rconn->sess_id = (rconn->sess_id + 1) & 0x3;
|
||||
rconn->peer_sess_id = 0;
|
||||
rconn->start_sent = 0;
|
||||
rconn->send_blocked = 0;
|
||||
}
|
||||
|
||||
void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
|
||||
struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg) {
|
||||
if (!inst || !h) return;
|
||||
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, node_id, svc_id);
|
||||
if (!rconn || !rconn->send_q) return;
|
||||
queue_waiter_wait(rconn->send_q, h, callback, arg);
|
||||
}
|
||||
|
||||
void etcp_router_cancel_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
|
||||
struct queue_waiter_handle* h) {
|
||||
if (!inst || !h) return;
|
||||
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, node_id, svc_id);
|
||||
if (!rconn || !rconn->send_q) return;
|
||||
queue_waiter_cancel(rconn->send_q, h);
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// Seq-connection API
|
||||
// ====================================================================
|
||||
|
||||
struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
|
||||
uint64_t remote_node_id, uint8_t svc_id) {
|
||||
if (!inst || !inst->router_conns) return NULL;
|
||||
struct ETCP_ROUTER_CONN* rconn = router_conn_find(inst, remote_node_id, svc_id);
|
||||
if (rconn) return rconn;
|
||||
|
||||
size_t data_size = sizeof(struct ETCP_ROUTER_CONN) - sizeof(struct ll_entry);
|
||||
rconn = (struct ETCP_ROUTER_CONN*)queue_entry_new(data_size);
|
||||
if (!rconn) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_entry_new failed"); return NULL; }
|
||||
rconn->remote_node_id = remote_node_id;
|
||||
rconn->svc_id = svc_id;
|
||||
rconn->last_dgram_ts = get_current_timestamp();
|
||||
rconn->tx_seq = 0;
|
||||
rconn->rx_seq = 0;
|
||||
rconn->tx_acked = 0;
|
||||
rconn->last_sent_ack_seq = 0;
|
||||
rconn->last_ack_sent_tb = 0;
|
||||
rconn->inst = inst;
|
||||
rconn->ack_timer = NULL;
|
||||
rconn->idle_ack_timer = NULL;
|
||||
rconn->send_blocked = 0;
|
||||
rconn->send_resume_timer = NULL;
|
||||
rconn->sess_id = 0;
|
||||
rconn->peer_sess_id = 0;
|
||||
rconn->start_sent = 0;
|
||||
rconn->recv_q = queue_new(inst->ua, ROUTER_RECVQ_HASH_SIZE, 0, 4, "router_recv_q");
|
||||
if (!rconn->recv_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(recv_q) failed"); queue_entry_free(&rconn->ll); return NULL; }
|
||||
rconn->send_q = queue_new(inst->ua, 0, 0, 0, "router_send_q");
|
||||
if (!rconn->send_q) { DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_get: queue_new(send_q) failed"); queue_free(rconn->recv_q); queue_entry_free(&rconn->ll); return NULL; }
|
||||
queue_set_threshold(rconn->send_q, ROUTER_MAX_SEND_Q_PACKETS / 2, 0);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: new conn remote=%016llx svc_id=%u",
|
||||
(unsigned long long)remote_node_id, svc_id);
|
||||
queue_data_put_with_index(inst->router_conns, &rconn->ll);
|
||||
return rconn;
|
||||
}
|
||||
|
||||
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
|
||||
const uint8_t* data, size_t len) {
|
||||
if (!rconn || !data || len == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_send: invalid args rconn=%p data=%p len=%zu",
|
||||
(void*)rconn, (const void*)data, len);
|
||||
return -1;
|
||||
}
|
||||
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) {
|
||||
return router_enqueue_send(rconn, data, len, 0);
|
||||
}
|
||||
return router_send_one(rconn, data, len);
|
||||
}
|
||||
|
||||
int etcp_router_conn_send_signed(struct ETCP_ROUTER_CONN* rconn,
|
||||
const uint8_t* data, size_t len) {
|
||||
if (!rconn || !data || len == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCPROUTE, "router_conn_send_signed: invalid args rconn=%p data=%p len=%zu",
|
||||
(void*)rconn, (const void*)data, len);
|
||||
return -1;
|
||||
}
|
||||
if ((int32_t)(rconn->tx_seq - rconn->tx_acked) >= ROUTER_MAX_INFLIGHT) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_ETCPROUTE, "router_conn_send_signed: inflight full svc_id=%u", rconn->svc_id);
|
||||
return -1;
|
||||
}
|
||||
return router_send_one_flags(rconn, data, len, 0, 1);
|
||||
}
|
||||
|
||||
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn) {
|
||||
router_close_and_notify(rconn);
|
||||
}
|
||||
|
||||
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id) {
|
||||
if (!inst || !inst->router_conns) return;
|
||||
struct ll_entry* entry;
|
||||
while ((entry = queue_data_get(inst->router_conns)) != NULL) {
|
||||
struct ETCP_ROUTER_CONN* rconn = (struct ETCP_ROUTER_CONN*)entry;
|
||||
if (rconn->remote_node_id == remote_node_id) {
|
||||
router_send_close_to_service(rconn);
|
||||
if (rconn->ack_timer) { uasync_cancel_timeout(inst->ua, rconn->ack_timer); rconn->ack_timer = NULL; }
|
||||
if (rconn->idle_ack_timer) { uasync_cancel_timeout(inst->ua, rconn->idle_ack_timer); rconn->idle_ack_timer = NULL; }
|
||||
if (rconn->send_resume_timer) { uasync_cancel_timeout(inst->ua, rconn->send_resume_timer); rconn->send_resume_timer = NULL; }
|
||||
if (rconn->recv_q) {
|
||||
struct ll_entry* f;
|
||||
while ((f = queue_data_get(rconn->recv_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->recv_q);
|
||||
}
|
||||
if (rconn->send_q) {
|
||||
struct ll_entry* f;
|
||||
while ((f = queue_data_get(rconn->send_q)) != NULL) { queue_dgram_free(f); queue_entry_free(f); }
|
||||
queue_free(rconn->send_q);
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_ETCPROUTE, "router_conn: closed by node remote=%016llx svc_id=%u",
|
||||
(unsigned long long)rconn->remote_node_id, rconn->svc_id);
|
||||
queue_entry_free(&rconn->ll);
|
||||
} else {
|
||||
queue_data_put(inst->router_conns, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// etcp_router.h — Сервисный слой маршрутизации поверх ETCP
|
||||
// Маршрутизирует сервисные пакеты до целевой ноды, на промежуточных нодах ретранслирует
|
||||
// Упрощённый TCP поверх ETCP: восстановление порядка, дедупликация, без переповторов
|
||||
//
|
||||
// Формат SVC_ROUTE пакета:
|
||||
// [cmd:1] [dst_node_id:8] [src_node_id:8] [seq:4] [svc_id:1] [flags:1] [payload...]
|
||||
// flags: bit7=START, bit6=RST, bit5-4=sess_id(0-3)
|
||||
// ACK-пакет: тот же заголовок, seq=rx_seq, payload_len=0
|
||||
#ifndef ETCP_ROUTER_H
|
||||
#define ETCP_ROUTER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "etcp_api.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct SVC_ROUTE_HDR {
|
||||
uint8_t cmd; // ETCP_ID_SVC_ROUTE (0x03)
|
||||
uint64_t dst_node_id;
|
||||
uint64_t src_node_id;
|
||||
uint32_t seq; // data: tx_seq; ACK: rx_seq (ожидаемый seq)
|
||||
uint8_t svc_id; // идентификатор сервиса
|
||||
uint8_t flags; // bit7=START, bit6=RST, bit5-4=sess_id
|
||||
};
|
||||
#pragma pack(pop)
|
||||
#define SVC_ROUTE_HDR_SIZE sizeof(struct SVC_ROUTE_HDR) // 23
|
||||
#define SVC_ROUTE_MAX_BINDINGS 256
|
||||
|
||||
// Биты в flags
|
||||
#define ROUTER_FLAG_START 0x80
|
||||
#define ROUTER_FLAG_RST 0x40
|
||||
#define ROUTER_FLAG_SIGNED 0x08 // пакет содержит Ed25519 подпись (64 байта после payload)
|
||||
#define ROUTER_SESS_ID_SHIFT 4
|
||||
#define ROUTER_SESS_ID_MASK 0x30
|
||||
|
||||
// Состояние одного логического подключения (remote_node_id + svc_id)
|
||||
struct ETCP_ROUTER_CONN {
|
||||
struct ll_entry ll; // data[0..7]=remote_node_id, data[8]=svc_id — хеш-индекс
|
||||
uint64_t remote_node_id; // = data[0..7]
|
||||
uint8_t svc_id; // = data[8]
|
||||
uint16_t last_dgram_ts; // последний timestamp датаграммы (rx или tx, timebase 0.1ms)
|
||||
|
||||
uint32_t tx_seq; // следующий seq для отправки
|
||||
uint32_t rx_seq; // ожидаемый seq для сборки (next expected)
|
||||
uint32_t tx_acked; // сколько наших пакетов подтвердил remote (для inflight)
|
||||
uint32_t last_sent_ack_seq; // последний отправленный ACK (= rx_seq на момент отправки)
|
||||
uint64_t last_ack_sent_tb; // время отправки последнего ACK (timebase 0.1ms)
|
||||
|
||||
struct UTUN_INSTANCE* inst;
|
||||
struct ll_queue* recv_q; // reorder очередь: hash по seq (4 байта, offset 0)
|
||||
void* ack_timer; // периодический таймер (10ms при consumer_ack)
|
||||
void* idle_ack_timer; // idle таймер (дослать последний ack)
|
||||
|
||||
struct ll_queue* send_q; // очередь ожидающих отправки (inflight полон)
|
||||
void* send_resume_timer; // таймер возобновления отправки
|
||||
uint8_t send_blocked; // 1 = inflight полон, ждём ack/таймер
|
||||
uint8_t sess_id; // наш session id (0-3)
|
||||
uint8_t peer_sess_id; // последний sess_id от peer'а
|
||||
uint8_t start_sent; // 0 = нужно отправить START в первом data
|
||||
};
|
||||
|
||||
#define ROUTER_CONN_HASH_SIZE 256
|
||||
#define ROUTER_RECVQ_HASH_SIZE 1024
|
||||
#define ROUTER_MAX_INFLIGHT 256 // макс пакетов в полёте (для контроля inflight)
|
||||
#define ROUTER_ACK_INTERVAL_TB 100 // интервал ACK: 10ms в timebase (0.1ms)
|
||||
#define ROUTER_ACK_IDLE_TB 5000 // idle таймаут: 500ms
|
||||
#define ROUTER_SEND_RESUME_TB 500 // retry интервал send_q: 50ms
|
||||
#define ROUTER_MAX_SEND_Q_PACKETS 4 // порог backpressure на send_q
|
||||
|
||||
// Флаги в старших битах seq (взаимоисключающие)
|
||||
#define ROUTER_SEQ_CLOSE_FLAG 0x80000000 // нормальное закрытие conn
|
||||
#define ROUTER_SEQ_RST_FLAG 0x40000000 // conn не найден (reset)
|
||||
|
||||
// Bindings для сервисов внутри etcp_router (аналогично ETCP_BINDINGS)
|
||||
struct ETCP_ROUTER_BINDINGS {
|
||||
etcp_recv_fn callbacks[SVC_ROUTE_MAX_BINDINGS];
|
||||
};
|
||||
|
||||
// Инициализация: etcp_bind(ETCP_ID_SVC_ROUTE) + очистка bindings + создание router_conns
|
||||
int etcp_router_init(struct UTUN_INSTANCE* inst);
|
||||
|
||||
// Деинициализация
|
||||
void etcp_router_destroy(struct UTUN_INSTANCE* inst);
|
||||
|
||||
// Зарегистрировать обработчик сервиса
|
||||
int etcp_router_bind(struct UTUN_INSTANCE* inst, uint8_t svc_id, etcp_recv_fn callback);
|
||||
int etcp_router_unbind(struct UTUN_INSTANCE* inst, uint8_t svc_id);
|
||||
|
||||
// Отправить сервисный пакет (авто-conn, seq, inflight-контроль через send_q)
|
||||
int etcp_route_send(struct UTUN_INSTANCE* inst, uint64_t dst_node_id, struct ll_entry* entry, int force);
|
||||
|
||||
// Найти/создать состояние seq-подключения по (remote_node_id, svc_id)
|
||||
struct ETCP_ROUTER_CONN* etcp_router_conn_get(struct UTUN_INSTANCE* inst,
|
||||
uint64_t remote_node_id, uint8_t svc_id);
|
||||
|
||||
// Отправить данные с авто-seq и контролем inflight
|
||||
// data: payload без svc_id, flags: битовые флаги (зарезервировано)
|
||||
int etcp_router_conn_send(struct ETCP_ROUTER_CONN* rconn,
|
||||
const uint8_t* data, size_t len);
|
||||
|
||||
// Отправить данные с Ed25519 подписью (аналог etcp_router_conn_send с флагом ROUTER_FLAG_SIGNED)
|
||||
int etcp_router_conn_send_signed(struct ETCP_ROUTER_CONN* rconn,
|
||||
const uint8_t* data, size_t len);
|
||||
|
||||
// Закрыть seq-подключение
|
||||
void etcp_router_conn_close(struct ETCP_ROUTER_CONN* rconn);
|
||||
|
||||
// Закрыть все router_conn для указанного remote_node_id (peer умер)
|
||||
void etcp_router_conn_close_all_for_node(struct UTUN_INSTANCE* inst, uint64_t remote_node_id);
|
||||
|
||||
// Возвращает количество пакетов в очереди normalizer->input для узла node_id
|
||||
int etcp_router_input_q_count(struct UTUN_INSTANCE* inst, uint64_t node_id);
|
||||
|
||||
// Backpressure: зарегистрировать/отменить waiter на normalizer->input очереди
|
||||
void etcp_router_waiter_register(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
|
||||
struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
void etcp_router_waiter_cancel(struct UTUN_INSTANCE* inst, uint64_t peer_node_id,
|
||||
struct queue_waiter_handle* h);
|
||||
|
||||
// Backpressure: зарегистрировать/отменить waiter на send_q очереди
|
||||
// h — handle из структуры сервиса, callback вызывается когда send_q.count <= threshold (128)
|
||||
void etcp_router_on_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
|
||||
struct queue_waiter_handle* h,
|
||||
queue_threshold_callback_fn callback, void* arg);
|
||||
void etcp_router_cancel_send_ready(struct UTUN_INSTANCE* inst, uint64_t node_id, uint8_t svc_id,
|
||||
struct queue_waiter_handle* h);
|
||||
|
||||
// Сбросить состояние роутера для конкретного peer+svc (перезапуск удалённой стороны).
|
||||
// Очищает send_q/recv_q, уведомляет сервис через cb(NULL, entry) с remote_node_id.
|
||||
void etcp_router_conn_restart(struct UTUN_INSTANCE* inst, uint64_t remote_node_id, uint8_t svc_id);
|
||||
|
||||
#endif // ETCP_ROUTER_H
|
||||
@@ -0,0 +1,130 @@
|
||||
// lwip_pbuf.c — simplified packet buffer (PBUF_RAM only)
|
||||
#include "lwip_pbuf.h"
|
||||
#include "../../lib/mem.h"
|
||||
#include <string.h>
|
||||
|
||||
#define PBUF_POOL_BUFSIZE 256 // not used with PBUF_RAM only
|
||||
|
||||
struct pbuf* pbuf_alloc(uint16_t layer, uint16_t length)
|
||||
{
|
||||
uint16_t offset = (layer == PBUF_TRANSPORT) ? 54 : ((layer == PBUF_IP) ? 34 : (uint16_t)layer);
|
||||
uint16_t total = sizeof(struct pbuf) + offset + length;
|
||||
struct pbuf *p = u_malloc(total);
|
||||
if (!p) return NULL;
|
||||
p->next = NULL;
|
||||
p->payload = (uint8_t*)p + sizeof(struct pbuf) + offset;
|
||||
p->tot_len = length;
|
||||
p->len = length;
|
||||
p->ref = 1;
|
||||
p->flags = 0;
|
||||
p->type_internal = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
void pbuf_free(struct pbuf *p)
|
||||
{
|
||||
while (p) {
|
||||
struct pbuf *next = p->next;
|
||||
if (p->ref > 0) p->ref--;
|
||||
if (p->ref == 0) u_free(p);
|
||||
p = next;
|
||||
}
|
||||
}
|
||||
|
||||
void pbuf_ref(struct pbuf *p)
|
||||
{
|
||||
if (p) p->ref++;
|
||||
}
|
||||
|
||||
int pbuf_header(struct pbuf *p, int16_t increment)
|
||||
{
|
||||
if (!p) return -1;
|
||||
if (increment == 0) return 0;
|
||||
if (increment < 0) {
|
||||
if ((uint16_t)(-increment) > p->len) return -1;
|
||||
} else {
|
||||
if (p->payload < (void*)((uint8_t*)p + sizeof(struct pbuf))) return -1;
|
||||
uint16_t avail = (uint16_t)((uint8_t*)p->payload - ((uint8_t*)p + sizeof(struct pbuf)));
|
||||
if ((uint16_t)increment > avail) return -1;
|
||||
}
|
||||
p->payload = (uint8_t*)p->payload - increment;
|
||||
p->len = (uint16_t)(p->len + increment);
|
||||
p->tot_len = (uint16_t)(p->tot_len + increment);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pbuf_header_force(struct pbuf *p, int16_t increment)
|
||||
{
|
||||
if (!p) return -1;
|
||||
if (increment < 0) {
|
||||
if ((uint16_t)(-increment) > p->len) return -1;
|
||||
}
|
||||
p->payload = (uint8_t*)p->payload - increment;
|
||||
p->len = (uint16_t)(p->len + increment);
|
||||
p->tot_len = (uint16_t)(p->tot_len + increment);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pbuf_chain(struct pbuf *head, struct pbuf *tail)
|
||||
{
|
||||
if (!head) return;
|
||||
struct pbuf *p = head;
|
||||
while (p->next) p = p->next;
|
||||
p->next = tail;
|
||||
}
|
||||
|
||||
struct pbuf* pbuf_dechain(struct pbuf *p)
|
||||
{
|
||||
if (!p) return NULL;
|
||||
struct pbuf *next = p->next;
|
||||
p->next = NULL;
|
||||
p->tot_len = p->len;
|
||||
return next;
|
||||
}
|
||||
|
||||
uint16_t pbuf_clen(const struct pbuf *p)
|
||||
{
|
||||
uint16_t count = 0;
|
||||
while (p) { count++; p = p->next; }
|
||||
return count;
|
||||
}
|
||||
|
||||
uint16_t pbuf_copy_partial(const struct pbuf *p, void *buf, uint16_t len, uint16_t offset)
|
||||
{
|
||||
uint16_t copied = 0;
|
||||
while (p && len > 0) {
|
||||
if (offset >= p->len) { offset = (uint16_t)(offset - p->len); p = p->next; continue; }
|
||||
uint16_t chunk = (uint16_t)(p->len - offset);
|
||||
if (chunk > len) chunk = len;
|
||||
memcpy((uint8_t*)buf + copied, (uint8_t*)p->payload + offset, chunk);
|
||||
copied = (uint16_t)(copied + chunk); len = (uint16_t)(len - chunk); offset = 0;
|
||||
p = p->next;
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
int pbuf_take(struct pbuf *p, const void *data, uint16_t len)
|
||||
{
|
||||
return pbuf_take_at(p, data, len, 0);
|
||||
}
|
||||
|
||||
int pbuf_take_at(struct pbuf *p, const void *data, uint16_t len, uint16_t offset)
|
||||
{
|
||||
uint16_t remaining = len;
|
||||
const uint8_t *src = (const uint8_t*)data;
|
||||
while (p && remaining > 0) {
|
||||
if (offset >= p->len) { offset = (uint16_t)(offset - p->len); p = p->next; continue; }
|
||||
uint16_t chunk = (uint16_t)(p->len - offset);
|
||||
if (chunk > remaining) chunk = remaining;
|
||||
memcpy((uint8_t*)p->payload + offset, src, chunk);
|
||||
src += chunk; remaining = (uint16_t)(remaining - chunk); offset = 0;
|
||||
p = p->next;
|
||||
}
|
||||
return (remaining == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
void* pbuf_get_contiguous(const struct pbuf *p, void *buf, size_t len, size_t offset)
|
||||
{
|
||||
if (pbuf_copy_partial(p, buf, (uint16_t)len, (uint16_t)offset) == (uint16_t)len) return buf;
|
||||
return NULL;
|
||||
}
|
||||
Loaded 100 of 640 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user