Browse Source

add framed read for raw fd (2-byte len prefix), TCP_NODELAY, fix test half-close ordering

congestion
Evgeny 5 months ago
parent
commit
d3b6057f82
  1. 102
      .opencode/skills/context7/README.md
  2. 85
      .opencode/skills/context7/SKILL.md
  3. 290
      .opencode/skills/context7/library-registry.md
  4. 51
      .opencode/skills/context7/navigation.md
  5. 93
      src/tcp_proxy.c
  6. 4
      src/tcp_proxy.h
  7. 12
      tests/test_tcp_proxy.c

102
.opencode/skills/context7/README.md

@ -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`

85
.opencode/skills/context7/SKILL.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)

290
.opencode/skills/context7/library-registry.md

@ -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

51
.opencode/skills/context7/navigation.md

@ -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`

93
src/tcp_proxy.c

@ -24,6 +24,7 @@
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <fcntl.h>
#endif
@ -124,14 +125,18 @@ static err_t tcp_output_cb(void *arg, struct pbuf *p, uint32_t src_ip, uint32_t
(void)src_ip; (void)dst_ip;
uint16_t len = p->tot_len;
if (len > 2000) return LERR_BUF;
uint8_t buf[2000];
pbuf_copy_partial(p, buf, len, 0);
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP OUT %u.%u.%u.%u→%u.%u.%u.%u len=%u fd=%d",
uint8_t buf[2002];
if (proxy->tun) {
pbuf_copy_partial(p, buf, len, 0);
tun_platform_write(proxy->tun, buf, len);
} else if (proxy->ip_fd >= 0) {
buf[0] = (len >> 8) & 0xFF; buf[1] = len & 0xFF;
pbuf_copy_partial(p, buf + 2, len, 0);
ssize_t n = write(proxy->ip_fd, buf, 2 + len); (void)n;
}
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP OUT %u.%u.%u.%u→%u.%u.%u.%u len=%u",
(uint8_t)(src_ip), (uint8_t)(src_ip>>8), (uint8_t)(src_ip>>16), (uint8_t)(src_ip>>24),
(uint8_t)(dst_ip), (uint8_t)(dst_ip>>8), (uint8_t)(dst_ip>>16), (uint8_t)(dst_ip>>24),
len, proxy->ip_fd);
if (proxy->tun) tun_platform_write(proxy->tun, buf, len);
else if (proxy->ip_fd >= 0) { ssize_t n = write(proxy->ip_fd, buf, len); (void)n; }
(uint8_t)(dst_ip), (uint8_t)(dst_ip>>8), (uint8_t)(dst_ip>>16), (uint8_t)(dst_ip>>24), len);
return LERR_OK;
}
@ -233,8 +238,8 @@ static err_t proxy_accept_cb(void *arg, struct tcp_pcb *newpcb, err_t err) {
}
pc->next = p->conns; p->conns = pc; p->conn_count++;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: new passive conn to %d.%d.%d.%d:%d (client port %d)",
pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port), ntohs(newpcb->remote_port));
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: new passive conn to %d.%d.%d.%d:%d",
pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port));
return LERR_OK;
}
@ -268,7 +273,6 @@ static err_t proxy_recv_cb(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t
pbuf_free(p);
} else if (pc->transport && !pc->half_closed) {
uint16_t len = p->tot_len;
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "RECV passive pc=%p len=%u transport=%p connected=%d", (void*)pc, len, (void*)pc->transport, ((struct sock_transport*)pc->transport)->connected);
uint8_t *data = u_malloc(len);
if (data) {
pbuf_copy_partial(p, data, len, 0);
@ -333,32 +337,45 @@ static err_t proxy_connected_cb(void *arg, struct tcp_pcb *pcb, err_t err) {
// ====================================================================
static void tcp_proxy_raw_read(int fd, void* arg) {
(void)fd; struct tcp_proxy* p = (struct tcp_proxy*)arg;
uint8_t buf[2000]; ssize_t n = read(p->ip_fd, buf, sizeof(buf));
if (n <= 0) return;
size_t offset = 0;
while (offset < (size_t)n) {
size_t remaining = (size_t)n - offset;
if (remaining < 20) break;
uint8_t *pkt = buf + offset;
if (tcp_proxy_handle_non_tcp(p, pkt, remaining)) { offset += remaining; continue; }
ssize_t n;
if (p->raw_need == 0) {
/* Ждём 2-байтный заголовок длины */
n = read(p->ip_fd, p->raw_buf + p->raw_pos, 2 - p->raw_pos);
if (n <= 0) { if (n < 0 && errno == EAGAIN) return; p->raw_pos = 0; return; }
p->raw_pos += n;
if (p->raw_pos < 2) return;
p->raw_need = ((uint16_t)p->raw_buf[0] << 8) | p->raw_buf[1];
if (p->raw_need == 0 || p->raw_need > 2000) { p->raw_pos = 0; return; }
p->raw_pos = 0;
}
/* Ждём p->raw_need байт данных */
n = read(p->ip_fd, p->raw_buf + 2 + p->raw_pos, p->raw_need - p->raw_pos);
if (n <= 0) { if (n < 0 && errno == EAGAIN) return; p->raw_pos = p->raw_need = 0; return; }
p->raw_pos += n;
if (p->raw_pos < p->raw_need) return;
/* Пакет получен полностью */
uint8_t* pkt = p->raw_buf + 2;
size_t remaining = p->raw_need;
p->raw_pos = 0; p->raw_need = 0;
if (!tcp_proxy_handle_non_tcp(p, pkt, remaining)) {
uint8_t proto = pkt[9];
if (proto != IPPROTO_TCP) { offset += remaining; continue; }
uint16_t ip_hdr_len = (pkt[0] & 0x0F) * 4;
uint16_t ip_total = ((uint16_t)pkt[2] << 8) | pkt[3];
if (ip_hdr_len < 20 || ip_total < ip_hdr_len || (size_t)ip_total > remaining) break;
uint32_t src_ip, dst_ip;
memcpy(&src_ip, pkt + 12, 4); memcpy(&dst_ip, pkt + 16, 4);
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP IN %u.%u.%u.%u→%u.%u.%u.%u proto=%u iplen=%u",
(uint8_t)(src_ip), (uint8_t)(src_ip>>8), (uint8_t)(src_ip>>16), (uint8_t)(src_ip>>24),
(uint8_t)(dst_ip), (uint8_t)(dst_ip>>8), (uint8_t)(dst_ip>>16), (uint8_t)(dst_ip>>24),
proto, ip_total);
uint16_t tcp_len = ip_total - ip_hdr_len;
struct pbuf *pb = pbuf_alloc(PBUF_RAW, tcp_len);
if (!pb) break;
pbuf_take(pb, pkt + ip_hdr_len, tcp_len);
lwip_tcp_input(p->lwip, pb, src_ip, dst_ip);
offset += ip_total;
if (proto == IPPROTO_TCP) {
uint16_t ip_hdr_len = (pkt[0] & 0x0F) * 4;
uint16_t ip_total = ((uint16_t)pkt[2] << 8) | pkt[3];
if (ip_hdr_len >= 20 && ip_total >= ip_hdr_len && (size_t)ip_total <= remaining) {
uint32_t src_ip, dst_ip;
memcpy(&src_ip, pkt + 12, 4); memcpy(&dst_ip, pkt + 16, 4);
uint16_t tcp_len = ip_total - ip_hdr_len;
DEBUG_ERROR(DEBUG_CATEGORY_TRAFFIC, "TCP IN %u.%u.%u.%u→%u.%u.%u.%u iplen=%u",
(uint8_t)(src_ip), (uint8_t)(src_ip>>8), (uint8_t)(src_ip>>16), (uint8_t)(src_ip>>24),
(uint8_t)(dst_ip), (uint8_t)(dst_ip>>8), (uint8_t)(dst_ip>>16), (uint8_t)(dst_ip>>24), ip_total);
struct pbuf *pb = pbuf_alloc(PBUF_RAW, tcp_len);
if (pb) { pbuf_take(pb, pkt + ip_hdr_len, tcp_len); lwip_tcp_input(p->lwip, pb, src_ip, dst_ip); }
}
}
}
}
@ -399,6 +416,7 @@ static struct sock_transport* sock_transport_create(struct proxy_conn* pc, struc
st->sock = socket(AF_INET, SOCK_STREAM, 0);
if(st->sock == SOCKET_INVALID) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "socket() failed: %s", strerror(errno)); u_free(st); return NULL; }
socket_set_nonblocking(st->sock);
{ int one = 1; setsockopt(st->sock, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); }
st->read_id = uasync_add_socket_t(ua, st->sock, sock_transport_read_callback, sock_transport_write_callback, sock_transport_error_callback, st);
if(!st->read_id) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "uasync_add_socket_t failed"); socket_close_wrapper(st->sock); u_free(st); return NULL; }
return st;
@ -408,7 +426,7 @@ static void sock_transport_connect(struct sock_transport* st) {
struct proxy_conn* pc = st->conn;
struct sockaddr_in addr; memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET; memcpy(&addr.sin_addr.s_addr, pc->dest_ip, 4); addr.sin_port = pc->dest_port;
DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "TCP proxy: connecting to %d.%d.%d.%d:%d", pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port));
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "TCP proxy: connecting to %d.%d.%d.%d:%d", pc->dest_ip[0], pc->dest_ip[1], pc->dest_ip[2], pc->dest_ip[3], ntohs(pc->dest_port));
st->connect_called = 1;
int ret = connect(st->sock, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0 && errno != EINPROGRESS) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "connect() failed: %s", strerror(errno)); sock_transport_close(&st->base); return; }
@ -829,7 +847,8 @@ int tcp_proxy_active_send_done(struct tcp_proxy* p, struct proxy_conn* pc) {
(void)p;
if (!pc || !pc->active || !pc->pcb) return 1;
if (pc->closing) return 1;
if (queue_entry_count(pc->transport_to_uip) > 0) return 0;
int q = queue_entry_count(pc->transport_to_uip);
if (q > 0) return 0;
if (pc->pcb->unsent || pc->pcb->unacked) return 0;
return 1;
}

4
src/tcp_proxy.h

@ -82,6 +82,10 @@ struct tcp_proxy {
struct lwip_tcp_ctx* lwip; // lwIP TCP stack context
uint32_t tun_ip_addr; // TUN IP (network byte order) for reply packets
int ip_fd_mode; // 1=raw fd input, 0=TUN input
// Framed read: для raw-fd mode, где поток байт не сохраняет границы пакетов
uint8_t raw_buf[2002]; // 2-byte len + max IP packet (MTU=1500 + 2B prefix)
size_t raw_pos; // сколько байт уже получено в текущем кадре
uint16_t raw_need; // сколько ещё байт ждать (0=ждём 2-байтный заголовок)
};
// ========== API ==========

12
tests/test_tcp_proxy.c

@ -22,7 +22,7 @@
#define ECHO_PORT 19999
#define TEST_SIZE (2 * 1024) // 2KB — minimum viable test
#define POLL_TIMEOUT_MS 10000
#define DATA_TIMEOUT_MS 120000
#define DATA_TIMEOUT_MS 30000
static int g_test_ok = 0;
static pid_t echo_pid = 0;
@ -109,18 +109,18 @@ int main(void) {
}
if(!tcp_proxy_active_send_done(a, pc)) { printf("[FAIL] send timeout\n"); goto fail; }
// 10. Half-close: all data sent, now signal FIN to echo server
tcp_proxy_active_close(a, pc);
// 11. Receive echoed data back
// 10. Receive echoed data back
size_t total_rcvd = 0; timeout_ms = DATA_TIMEOUT_MS;
while(total_rcvd < TEST_SIZE && timeout_ms > 0) {
uasync_poll(ua, 100); timeout_ms -= 10;
ssize_t n = tcp_proxy_active_recv(a, pc, recv_buf + total_rcvd, TEST_SIZE - total_rcvd);
if(n > 0) total_rcvd += n;
if(pc->closing && total_rcvd < TEST_SIZE) break;
if(pc->closing) break;
}
// 11. Half-close after all data received
tcp_proxy_active_close(a, pc);
// 12. Verify
if(total_rcvd == TEST_SIZE && memcmp(send_buf, recv_buf, TEST_SIZE) == 0) {
printf("[PASS] test_tcp_proxy — 1MB echo verified\n"); g_test_ok = 1;

Loading…
Cancel
Save