mirror of
https://github.com/go-i2p/go-i2ptunnel-config.git
synced 2025-12-01 06:54:57 -05:00
check in examples, perform round-trip-tests, fix yaml conversion bugs due to inconsistent nesting
This commit is contained in:
12
README.md
12
README.md
@@ -51,6 +51,18 @@ go-i2ptunnel-config --batch "*.config"
|
||||
go-i2ptunnel-config --batch --out-format ini "tunnels/*.properties"
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
The `examples/` directory contains ready-to-use configuration templates for common tunnel types in all three formats:
|
||||
|
||||
- HTTP client (web proxy)
|
||||
- HTTP server (eepsite hosting)
|
||||
- SOCKS proxy
|
||||
- Generic client tunnel
|
||||
- Generic server tunnel
|
||||
|
||||
Each example includes detailed comments explaining the configuration options. See [examples/README.md](examples/README.md) for more information.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork repository
|
||||
|
||||
231
examples/README.md
Normal file
231
examples/README.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# I2P Tunnel Configuration Examples
|
||||
|
||||
This directory contains example tunnel configuration files for all three supported formats:
|
||||
|
||||
- **Java I2P format** (`.properties` files)
|
||||
- **i2pd format** (`.conf` files)
|
||||
- **go-i2p format** (`.yaml` files)
|
||||
|
||||
## Available Examples
|
||||
|
||||
### HTTP Client Tunnel (HTTP Proxy)
|
||||
|
||||
Creates a local HTTP proxy to access I2P websites (eepsites).
|
||||
|
||||
- `httpclient.properties` - Java I2P format
|
||||
- `httpclient.conf` - i2pd format
|
||||
- `httpclient.yaml` - go-i2p format
|
||||
|
||||
**Use case**: Browse eepsites through your web browser configured to use the proxy.
|
||||
|
||||
**Default port**: 4444
|
||||
|
||||
### HTTP Server Tunnel (Eepsite)
|
||||
|
||||
Publishes a local web server as an I2P hidden service.
|
||||
|
||||
- `httpserver.properties` - Java I2P format
|
||||
- `httpserver.conf` - i2pd format
|
||||
- `httpserver.yaml` - go-i2p format
|
||||
|
||||
**Use case**: Host your own website accessible only through I2P.
|
||||
|
||||
**Target**: Local web server (default: 127.0.0.1:8080)
|
||||
|
||||
### SOCKS Tunnel (SOCKS Proxy)
|
||||
|
||||
Creates a SOCKS5 proxy for general I2P network access.
|
||||
|
||||
- `socks.properties` - Java I2P format
|
||||
- `socks.conf` - i2pd format
|
||||
- `socks.yaml` - go-i2p format
|
||||
|
||||
**Use case**: Route any SOCKS-compatible application through I2P (IRC, SSH, etc.).
|
||||
|
||||
**Default port**: 9050
|
||||
|
||||
### Generic Client Tunnel
|
||||
|
||||
Creates a TCP client tunnel for any protocol.
|
||||
|
||||
- `client.properties` - Java I2P format
|
||||
- `client.conf` - i2pd format
|
||||
- `client.yaml` - go-i2p format
|
||||
|
||||
**Use case**: Connect to any I2P service using a custom protocol.
|
||||
|
||||
**Default port**: 7000
|
||||
|
||||
### Generic Server Tunnel
|
||||
|
||||
Publishes any local TCP service as an I2P hidden service.
|
||||
|
||||
- `server.properties` - Java I2P format
|
||||
- `server.conf` - i2pd format
|
||||
- `server.yaml` - go-i2p format
|
||||
|
||||
**Use case**: Make any TCP service accessible through I2P (SSH server, game server, etc.).
|
||||
|
||||
**Target**: Local service (default: 127.0.0.1:9000)
|
||||
|
||||
## Using the Examples
|
||||
|
||||
### Converting Between Formats
|
||||
|
||||
Convert any example to a different format using the tool:
|
||||
|
||||
```bash
|
||||
# Convert Java I2P properties to YAML
|
||||
go-i2ptunnel-config httpclient.properties
|
||||
|
||||
# Convert i2pd conf to properties
|
||||
go-i2ptunnel-config --out-format properties httpserver.conf
|
||||
|
||||
# Convert YAML to i2pd conf
|
||||
go-i2ptunnel-config --out-format ini socks.yaml
|
||||
```
|
||||
|
||||
### Validating Configuration
|
||||
|
||||
Before using a configuration file, validate it:
|
||||
|
||||
```bash
|
||||
go-i2ptunnel-config --validate httpclient.properties
|
||||
go-i2ptunnel-config --validate --strict server.yaml
|
||||
```
|
||||
|
||||
### Customizing Examples
|
||||
|
||||
All examples contain sensible defaults. Modify these fields for your use case:
|
||||
|
||||
#### Required Fields
|
||||
|
||||
- **name**: Unique identifier for your tunnel
|
||||
- **type**: Tunnel type (don't change unless you know what you're doing)
|
||||
|
||||
#### Common Fields to Customize
|
||||
|
||||
**For Client Tunnels (HTTP Client, SOCKS, Generic Client)**:
|
||||
|
||||
- **interface**: Network interface to bind (default: 127.0.0.1)
|
||||
- **port**: Local port to listen on
|
||||
- **target/destination**: I2P destination to connect to (for generic client)
|
||||
|
||||
**For Server Tunnels (HTTP Server, Generic Server)**:
|
||||
|
||||
- **target**: Local service address (format: `host:port`)
|
||||
- **spoofedHost**: Custom hostname for your eepsite (optional)
|
||||
|
||||
#### Advanced Options
|
||||
|
||||
**I2CP Options** (i2cp.*):
|
||||
|
||||
- `leaseSetEncType`: Encryption type for the lease set (recommended: "4,0")
|
||||
- `closeIdleTime`: Time in milliseconds before closing idle connections
|
||||
- `newDestOnResume`: Whether to create a new destination on restart
|
||||
|
||||
**Tunnel Options** (inbound/outbound):
|
||||
|
||||
- `length`: Number of hops in the tunnel (higher = more anonymous, slower)
|
||||
- `quantity`: Number of parallel tunnels (higher = more reliable, more resources)
|
||||
|
||||
**Other Options**:
|
||||
|
||||
- `persistentKey`: Keep the same I2P address across restarts (true/false)
|
||||
- `gzip`: Enable compression (true/false)
|
||||
|
||||
## Format Differences
|
||||
|
||||
### Java I2P Properties Format (`.properties`)
|
||||
|
||||
- Flat key-value pairs
|
||||
- Uses prefixes like `option.i2cp.*`, `option.inbound.*`
|
||||
- Standard in Java I2P router
|
||||
- Example: `option.inbound.length=3`
|
||||
|
||||
### i2pd INI Format (`.conf`)
|
||||
|
||||
- Section-based structure `[TunnelName]`
|
||||
- Simpler syntax, more readable
|
||||
- Native to i2pd router
|
||||
- Example: `inbound.length = 3`
|
||||
|
||||
### go-i2p YAML Format (`.yaml`)
|
||||
|
||||
- Hierarchical nested structure with `tunnels` map
|
||||
- Most readable and maintainable
|
||||
- Native to go-i2p implementation
|
||||
- Supports multiple tunnels in one file
|
||||
- Example:
|
||||
|
||||
```yaml
|
||||
tunnels:
|
||||
MyTunnel:
|
||||
type: client
|
||||
port: 7000
|
||||
inbound:
|
||||
length: 3
|
||||
```
|
||||
|
||||
## Tunnel Types Reference
|
||||
|
||||
| Type | Description | Requires Port | Requires Target |
|
||||
|------|-------------|---------------|-----------------|
|
||||
| `httpclient` | HTTP proxy client | Yes | No |
|
||||
| `httpserver` | HTTP server (eepsite) | No | Yes |
|
||||
| `sockstunnel` | SOCKS5 proxy | Yes | No |
|
||||
| `client` | Generic client tunnel | Yes | Optional |
|
||||
| `server` | Generic server tunnel | No | Yes |
|
||||
| `ircclient` | IRC client tunnel | Yes | No |
|
||||
| `ircserver` | IRC server tunnel | No | Yes |
|
||||
| `streamrclient` | Streaming client | Yes | No |
|
||||
| `streamrserver` | Streaming server | No | Yes |
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **Persistent Keys**: Set `persistentKey: true` to keep the same I2P address across restarts. This is important for servers so users can find you again.
|
||||
- **Tunnel Length**: Higher values (3-7) provide better anonymity but slower performance.
|
||||
- **Tunnel Quantity**: More tunnels provide better reliability and performance but use more resources.
|
||||
- **Local Interface**: Binding to `127.0.0.1` ensures only local applications can use your tunnel. Never bind to `0.0.0.0` unless you understand the security implications.
|
||||
|
||||
## Testing Configurations
|
||||
|
||||
Use dry-run mode to test conversions without creating files:
|
||||
|
||||
```bash
|
||||
go-i2ptunnel-config --dry-run httpclient.properties
|
||||
go-i2ptunnel-config --dry-run --out-format yaml httpserver.conf
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Setting up an eepsite
|
||||
|
||||
1. Use `httpserver.properties` (or `.conf`/`.yaml`)
|
||||
2. Modify `target` to point to your web server (e.g., `127.0.0.1:8080`)
|
||||
3. Set `persistentKey: true` so your address stays the same
|
||||
4. Optionally set `spoofedHost` to a memorable `.i2p` hostname
|
||||
|
||||
### Browsing eepsites
|
||||
|
||||
1. Use `httpclient.properties` (or `.conf`/`.yaml`)
|
||||
2. Keep default port `4444` or choose your own
|
||||
3. Configure your browser to use `127.0.0.1:4444` as HTTP proxy
|
||||
4. Visit `.i2p` addresses in your browser
|
||||
|
||||
### General I2P Network Access
|
||||
|
||||
1. Use `socks.properties` (or `.conf`/`.yaml`)
|
||||
2. Keep default port `9050` or choose your own
|
||||
3. Configure applications to use `127.0.0.1:9050` as SOCKS5 proxy
|
||||
4. Works with SSH, IRC, and other SOCKS-compatible applications
|
||||
|
||||
## Further Reading
|
||||
|
||||
- [I2P Documentation](https://geti2p.net/en/docs)
|
||||
- [i2pd Documentation](https://i2pd.readthedocs.io/)
|
||||
- [go-i2p GitHub](https://github.com/go-i2p)
|
||||
|
||||
## Contributing
|
||||
|
||||
Found an issue with the examples or want to add more? Please submit a pull request or open an issue on GitHub.
|
||||
14
examples/client.conf
Normal file
14
examples/client.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
# Generic Client Tunnel Configuration (i2pd format)
|
||||
# This creates a standard TCP client tunnel for any protocol
|
||||
|
||||
[MyClientTunnel]
|
||||
type = client
|
||||
address = 127.0.0.1
|
||||
port = 7000
|
||||
destination = example.i2p
|
||||
keys = client-keys.dat
|
||||
inbound.length = 3
|
||||
inbound.quantity = 2
|
||||
outbound.length = 3
|
||||
outbound.quantity = 2
|
||||
i2cp.leaseSetEncType = 4,0
|
||||
28
examples/client.properties
Normal file
28
examples/client.properties
Normal file
@@ -0,0 +1,28 @@
|
||||
# Generic Client Tunnel Configuration (Java I2P format)
|
||||
# This creates a standard TCP client tunnel for any protocol
|
||||
|
||||
# Basic tunnel identification
|
||||
name=MyClientTunnel
|
||||
type=client
|
||||
description=Generic client tunnel for custom protocols
|
||||
|
||||
# Network settings
|
||||
interface=127.0.0.1
|
||||
listenPort=7000
|
||||
|
||||
# Target destination (I2P destination base64 or hostname)
|
||||
targetDestination=example.i2p
|
||||
|
||||
# I2CP options
|
||||
option.i2cp.leaseSetEncType=4,0
|
||||
option.i2cp.closeIdleTime=1800000
|
||||
|
||||
# Tunnel options
|
||||
option.inbound.length=3
|
||||
option.inbound.quantity=2
|
||||
option.outbound.length=3
|
||||
option.outbound.quantity=2
|
||||
|
||||
# Client options
|
||||
option.persistentClientKey=true
|
||||
option.i2ptunnel.gzip=false
|
||||
30
examples/client.yaml
Normal file
30
examples/client.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
# Generic Client Tunnel Configuration (YAML format - go-i2p)
|
||||
# This creates a standard TCP client tunnel for any protocol
|
||||
|
||||
tunnels:
|
||||
MyClientTunnel:
|
||||
type: client
|
||||
description: Generic client tunnel for custom protocols
|
||||
interface: 127.0.0.1
|
||||
port: 7000
|
||||
target: example.i2p
|
||||
persistentKey: true
|
||||
|
||||
# I2CP options
|
||||
i2cp:
|
||||
leaseSetEncType: "4,0"
|
||||
closeIdleTime: 1800000
|
||||
|
||||
# Tunnel configuration options
|
||||
options:
|
||||
gzip: false
|
||||
|
||||
# Inbound tunnel settings
|
||||
inbound:
|
||||
length: 3
|
||||
quantity: 2
|
||||
|
||||
# Outbound tunnel settings
|
||||
outbound:
|
||||
length: 3
|
||||
quantity: 2
|
||||
14
examples/httpclient.conf
Normal file
14
examples/httpclient.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
# HTTP Client Tunnel Configuration (i2pd format)
|
||||
# This creates an HTTP proxy that allows you to access I2P sites (eepsites)
|
||||
|
||||
[MyHTTPProxy]
|
||||
type = http
|
||||
address = 127.0.0.1
|
||||
port = 4444
|
||||
keys = httpclient-keys.dat
|
||||
inbound.length = 3
|
||||
inbound.quantity = 2
|
||||
outbound.length = 3
|
||||
outbound.quantity = 2
|
||||
i2cp.leaseSetEncType = 4,0
|
||||
gzip = true
|
||||
28
examples/httpclient.properties
Normal file
28
examples/httpclient.properties
Normal file
@@ -0,0 +1,28 @@
|
||||
# HTTP Client Tunnel Configuration (Java I2P format)
|
||||
# This creates an HTTP proxy that allows you to access I2P sites (eepsites)
|
||||
# through a local proxy on your computer.
|
||||
|
||||
# Basic tunnel identification
|
||||
name=MyHTTPProxy
|
||||
type=httpclient
|
||||
description=HTTP proxy for accessing eepsites
|
||||
|
||||
# Network settings - where the local proxy will listen
|
||||
interface=127.0.0.1
|
||||
listenPort=4444
|
||||
|
||||
# I2CP options - control how the tunnel connects to the I2P router
|
||||
option.i2cp.leaseSetEncType=4,0
|
||||
option.i2cp.closeIdleTime=1800000
|
||||
option.i2cp.newDestOnResume=false
|
||||
|
||||
# Tunnel options - configure tunnel behavior
|
||||
option.inbound.length=3
|
||||
option.inbound.quantity=2
|
||||
option.outbound.length=3
|
||||
option.outbound.quantity=2
|
||||
|
||||
# Client options
|
||||
option.persistentClientKey=true
|
||||
option.i2ptunnel.httpclient.allowInternalSSL=false
|
||||
option.outbound.randomKey=true
|
||||
31
examples/httpclient.yaml
Normal file
31
examples/httpclient.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
# HTTP Client Tunnel Configuration (YAML format - go-i2p)
|
||||
# This creates an HTTP proxy that allows you to access I2P sites (eepsites)
|
||||
|
||||
tunnels:
|
||||
MyHTTPProxy:
|
||||
type: httpclient
|
||||
description: HTTP proxy for accessing eepsites
|
||||
interface: 127.0.0.1
|
||||
port: 4444
|
||||
persistentKey: true
|
||||
|
||||
# I2CP options - control how the tunnel connects to the I2P router
|
||||
i2cp:
|
||||
leaseSetEncType: "4,0"
|
||||
closeIdleTime: 1800000
|
||||
newDestOnResume: false
|
||||
|
||||
# Tunnel configuration options
|
||||
options:
|
||||
gzip: true
|
||||
|
||||
# Inbound tunnel settings
|
||||
inbound:
|
||||
length: 3
|
||||
quantity: 2
|
||||
|
||||
# Outbound tunnel settings
|
||||
outbound:
|
||||
length: 3
|
||||
quantity: 2
|
||||
randomKey: true
|
||||
14
examples/httpserver.conf
Normal file
14
examples/httpserver.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
# HTTP Server Tunnel Configuration (i2pd format)
|
||||
# This publishes a local web server as an I2P hidden service (eepsite)
|
||||
|
||||
[MyWebsite]
|
||||
type = http
|
||||
address = 127.0.0.1:8080
|
||||
keys = mywebsite-keys.dat
|
||||
inbound.length = 3
|
||||
inbound.quantity = 3
|
||||
outbound.length = 3
|
||||
outbound.quantity = 3
|
||||
i2cp.leaseSetEncType = 4,0
|
||||
gzip = true
|
||||
hostoverride = mysite.i2p
|
||||
26
examples/httpserver.properties
Normal file
26
examples/httpserver.properties
Normal file
@@ -0,0 +1,26 @@
|
||||
# HTTP Server Tunnel Configuration (Java I2P format)
|
||||
# This publishes a local web server as an I2P hidden service (eepsite)
|
||||
|
||||
# Basic tunnel identification
|
||||
name=MyWebsite
|
||||
type=httpserver
|
||||
description=My personal eepsite
|
||||
|
||||
# Target - the local web server to publish
|
||||
targetHost=127.0.0.1
|
||||
targetPort=8080
|
||||
|
||||
# I2CP options - control destination management
|
||||
option.i2cp.leaseSetEncType=4,0
|
||||
option.i2cp.closeIdleTime=1800000
|
||||
option.i2cp.newDestOnResume=false
|
||||
|
||||
# Tunnel options - configure tunnel behavior
|
||||
option.inbound.length=3
|
||||
option.inbound.quantity=3
|
||||
option.outbound.length=3
|
||||
option.outbound.quantity=3
|
||||
|
||||
# Server options
|
||||
option.persistentClientKey=true
|
||||
spoofedHost=mysite.i2p
|
||||
30
examples/httpserver.yaml
Normal file
30
examples/httpserver.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
# HTTP Server Tunnel Configuration (YAML format - go-i2p)
|
||||
# This publishes a local web server as an I2P hidden service (eepsite)
|
||||
|
||||
tunnels:
|
||||
MyWebsite:
|
||||
type: httpserver
|
||||
description: My personal eepsite
|
||||
target: 127.0.0.1:8080
|
||||
persistentKey: true
|
||||
|
||||
# I2CP options - control destination management
|
||||
i2cp:
|
||||
leaseSetEncType: "4,0"
|
||||
closeIdleTime: 1800000
|
||||
newDestOnResume: false
|
||||
|
||||
# Tunnel configuration options
|
||||
options:
|
||||
gzip: true
|
||||
spoofedHost: mysite.i2p
|
||||
|
||||
# Inbound tunnel settings
|
||||
inbound:
|
||||
length: 3
|
||||
quantity: 3
|
||||
|
||||
# Outbound tunnel settings
|
||||
outbound:
|
||||
length: 3
|
||||
quantity: 3
|
||||
12
examples/server.conf
Normal file
12
examples/server.conf
Normal file
@@ -0,0 +1,12 @@
|
||||
# Generic Server Tunnel Configuration (i2pd format)
|
||||
# This publishes a local TCP service as an I2P hidden service
|
||||
|
||||
[MyServerTunnel]
|
||||
type = server
|
||||
address = 127.0.0.1:9000
|
||||
keys = server-keys.dat
|
||||
inbound.length = 3
|
||||
inbound.quantity = 3
|
||||
outbound.length = 3
|
||||
outbound.quantity = 3
|
||||
i2cp.leaseSetEncType = 4,0
|
||||
25
examples/server.properties
Normal file
25
examples/server.properties
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generic Server Tunnel Configuration (Java I2P format)
|
||||
# This publishes a local TCP service as an I2P hidden service
|
||||
|
||||
# Basic tunnel identification
|
||||
name=MyServerTunnel
|
||||
type=server
|
||||
description=Generic server tunnel for custom protocols
|
||||
|
||||
# Target - the local service to publish
|
||||
targetHost=127.0.0.1
|
||||
targetPort=9000
|
||||
|
||||
# I2CP options
|
||||
option.i2cp.leaseSetEncType=4,0
|
||||
option.i2cp.closeIdleTime=1800000
|
||||
|
||||
# Tunnel options
|
||||
option.inbound.length=3
|
||||
option.inbound.quantity=3
|
||||
option.outbound.length=3
|
||||
option.outbound.quantity=3
|
||||
|
||||
# Server options
|
||||
option.persistentClientKey=true
|
||||
option.i2ptunnel.gzip=false
|
||||
28
examples/server.yaml
Normal file
28
examples/server.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# Generic Server Tunnel Configuration (YAML format - go-i2p)
|
||||
# This publishes a local TCP service as an I2P hidden service
|
||||
|
||||
tunnels:
|
||||
MyServerTunnel:
|
||||
type: server
|
||||
description: Generic server tunnel for custom protocols
|
||||
target: 127.0.0.1:9000
|
||||
persistentKey: true
|
||||
|
||||
# I2CP options
|
||||
i2cp:
|
||||
leaseSetEncType: "4,0"
|
||||
closeIdleTime: 1800000
|
||||
|
||||
# Tunnel configuration options
|
||||
options:
|
||||
gzip: false
|
||||
|
||||
# Inbound tunnel settings
|
||||
inbound:
|
||||
length: 3
|
||||
quantity: 3
|
||||
|
||||
# Outbound tunnel settings
|
||||
outbound:
|
||||
length: 3
|
||||
quantity: 3
|
||||
14
examples/socks.conf
Normal file
14
examples/socks.conf
Normal file
@@ -0,0 +1,14 @@
|
||||
# SOCKS Tunnel Configuration (i2pd format)
|
||||
# This creates a SOCKS proxy that provides access to I2P network services
|
||||
|
||||
[MySOCKSProxy]
|
||||
type = socks
|
||||
address = 127.0.0.1
|
||||
port = 9050
|
||||
keys = socksproxy-keys.dat
|
||||
inbound.length = 3
|
||||
inbound.quantity = 2
|
||||
outbound.length = 3
|
||||
outbound.quantity = 2
|
||||
i2cp.leaseSetEncType = 4,0
|
||||
gzip = true
|
||||
26
examples/socks.properties
Normal file
26
examples/socks.properties
Normal file
@@ -0,0 +1,26 @@
|
||||
# SOCKS Tunnel Configuration (Java I2P format)
|
||||
# This creates a SOCKS proxy that provides access to I2P network services
|
||||
|
||||
# Basic tunnel identification
|
||||
name=MySOCKSProxy
|
||||
type=sockstunnel
|
||||
description=SOCKS5 proxy for I2P network access
|
||||
|
||||
# Network settings - where the SOCKS proxy will listen
|
||||
interface=127.0.0.1
|
||||
listenPort=9050
|
||||
|
||||
# I2CP options
|
||||
option.i2cp.leaseSetEncType=4,0
|
||||
option.i2cp.closeIdleTime=1800000
|
||||
|
||||
# Tunnel options
|
||||
option.inbound.length=3
|
||||
option.inbound.quantity=2
|
||||
option.outbound.length=3
|
||||
option.outbound.quantity=2
|
||||
|
||||
# SOCKS-specific options
|
||||
option.persistentClientKey=true
|
||||
option.outbound.randomKey=true
|
||||
option.i2ptunnel.gzip=true
|
||||
30
examples/socks.yaml
Normal file
30
examples/socks.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
# SOCKS Tunnel Configuration (YAML format - go-i2p)
|
||||
# This creates a SOCKS proxy that provides access to I2P network services
|
||||
|
||||
tunnels:
|
||||
MySOCKSProxy:
|
||||
type: sockstunnel
|
||||
description: SOCKS5 proxy for I2P network access
|
||||
interface: 127.0.0.1
|
||||
port: 9050
|
||||
persistentKey: true
|
||||
|
||||
# I2CP options
|
||||
i2cp:
|
||||
leaseSetEncType: "4,0"
|
||||
closeIdleTime: 1800000
|
||||
|
||||
# Tunnel configuration options
|
||||
options:
|
||||
gzip: true
|
||||
|
||||
# Inbound tunnel settings
|
||||
inbound:
|
||||
length: 3
|
||||
quantity: 2
|
||||
|
||||
# Outbound tunnel settings
|
||||
outbound:
|
||||
length: 3
|
||||
quantity: 2
|
||||
randomKey: true
|
||||
@@ -145,7 +145,7 @@ func TestCrossFormatConversion(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestYAMLParser tests the parseYAML function specifically (0% coverage)
|
||||
// TestYAMLParser tests the parseYAML function with the nested tunnels format
|
||||
func TestYAMLParser(t *testing.T) {
|
||||
converter := &Converter{}
|
||||
|
||||
@@ -156,8 +156,9 @@ func TestYAMLParser(t *testing.T) {
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid_yaml_direct_format",
|
||||
input: `name: HttpProxy
|
||||
name: "valid_yaml_nested_format",
|
||||
input: `tunnels:
|
||||
HttpProxy:
|
||||
type: httpclient
|
||||
interface: 127.0.0.1
|
||||
port: 4444
|
||||
@@ -203,7 +204,8 @@ outbound:
|
||||
},
|
||||
{
|
||||
name: "minimal_yaml_tunnel",
|
||||
input: `name: MinimalTunnel
|
||||
input: `tunnels:
|
||||
MinimalTunnel:
|
||||
type: client`,
|
||||
expected: &TunnelConfig{
|
||||
Name: "MinimalTunnel",
|
||||
@@ -213,7 +215,8 @@ type: client`,
|
||||
},
|
||||
{
|
||||
name: "invalid_yaml_syntax",
|
||||
input: `name: BadTunnel
|
||||
input: `tunnels:
|
||||
BadTunnel:
|
||||
type: httpclient
|
||||
port: "not_a_number"
|
||||
invalid_yaml: [
|
||||
@@ -222,10 +225,10 @@ invalid_yaml: [
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty_yaml",
|
||||
input: "",
|
||||
expected: &TunnelConfig{},
|
||||
wantErr: false,
|
||||
name: "empty_tunnels_map",
|
||||
input: "tunnels: {}",
|
||||
expected: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -420,9 +423,10 @@ description=Load test tunnel`
|
||||
t.Errorf("LoadConfig() Port = %d, want %d", config.Port, 8080)
|
||||
}
|
||||
|
||||
// Test loading YAML file (direct format, not wrapped)
|
||||
// Test loading YAML file (nested format with tunnels map)
|
||||
yamlFile := filepath.Join(tempDir, "test.yaml")
|
||||
yamlContent := `name: YamlTest
|
||||
yamlContent := `tunnels:
|
||||
YamlTest:
|
||||
type: server
|
||||
port: 9090`
|
||||
err = os.WriteFile(yamlFile, []byte(yamlContent), 0644)
|
||||
|
||||
287
lib/examples_test.go
Normal file
287
lib/examples_test.go
Normal file
@@ -0,0 +1,287 @@
|
||||
package i2pconv
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestExampleFiles validates all example configuration files in the examples directory.
|
||||
// This ensures that the provided examples are syntactically correct and can be parsed.
|
||||
func TestExampleFiles(t *testing.T) {
|
||||
examplesDir := filepath.Join("..", "examples")
|
||||
|
||||
// Check if examples directory exists
|
||||
if _, err := os.Stat(examplesDir); os.IsNotExist(err) {
|
||||
t.Skip("examples directory not found, skipping example validation tests")
|
||||
return
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
format string
|
||||
}{
|
||||
// Properties format examples
|
||||
{"httpclient.properties", "properties"},
|
||||
{"httpserver.properties", "properties"},
|
||||
{"socks.properties", "properties"},
|
||||
{"client.properties", "properties"},
|
||||
{"server.properties", "properties"},
|
||||
|
||||
// INI format examples
|
||||
{"httpclient.conf", "ini"},
|
||||
{"httpserver.conf", "ini"},
|
||||
{"socks.conf", "ini"},
|
||||
{"client.conf", "ini"},
|
||||
{"server.conf", "ini"},
|
||||
|
||||
// YAML format examples
|
||||
{"httpclient.yaml", "yaml"},
|
||||
{"httpserver.yaml", "yaml"},
|
||||
{"socks.yaml", "yaml"},
|
||||
{"client.yaml", "yaml"},
|
||||
{"server.yaml", "yaml"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
filePath := filepath.Join(examplesDir, tc.name)
|
||||
|
||||
// Read the example file
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read example file %s: %v", tc.name, err)
|
||||
}
|
||||
|
||||
// Parse the configuration
|
||||
conv := &Converter{strict: false}
|
||||
config, err := conv.ParseInput(data, tc.format)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse example file %s: %v", tc.name, err)
|
||||
}
|
||||
|
||||
// Validate the configuration
|
||||
if err := conv.validate(config); err != nil {
|
||||
t.Errorf("Example file %s failed validation: %v", tc.name, err)
|
||||
}
|
||||
|
||||
// Verify basic required fields
|
||||
if config.Name == "" {
|
||||
t.Errorf("Example file %s has empty name", tc.name)
|
||||
}
|
||||
if config.Type == "" {
|
||||
t.Errorf("Example file %s has empty type", tc.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestExampleFileConversion verifies that example files can be converted between formats.
|
||||
// This ensures cross-format compatibility and that conversions maintain data integrity.
|
||||
// Note: YAML format has a nested structure (tunnels: map) so conversions TO YAML
|
||||
// produce valid output, but may not round-trip perfectly without special handling.
|
||||
func TestExampleFileConversion(t *testing.T) {
|
||||
examplesDir := filepath.Join("..", "examples")
|
||||
|
||||
if _, err := os.Stat(examplesDir); os.IsNotExist(err) {
|
||||
t.Skip("examples directory not found, skipping conversion tests")
|
||||
return
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
sourceFile string
|
||||
sourceFormat string
|
||||
targetFormat string
|
||||
}{
|
||||
// Properties and INI formats are flat and convert well in both directions
|
||||
{"httpclient.properties", "properties", "ini"},
|
||||
{"httpserver.conf", "ini", "properties"},
|
||||
// YAML format converts FROM yaml to other formats reliably
|
||||
{"socks.yaml", "yaml", "properties"},
|
||||
{"socks.yaml", "yaml", "ini"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.sourceFile+"_to_"+tc.targetFormat, func(t *testing.T) {
|
||||
filePath := filepath.Join(examplesDir, tc.sourceFile)
|
||||
|
||||
// Read source file
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read source file: %v", err)
|
||||
}
|
||||
|
||||
// Convert to target format
|
||||
conv := &Converter{strict: false}
|
||||
output, err := conv.Convert(data, tc.sourceFormat, tc.targetFormat)
|
||||
if err != nil {
|
||||
t.Fatalf("Conversion failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify output is not empty
|
||||
if len(output) == 0 {
|
||||
t.Error("Conversion produced empty output")
|
||||
}
|
||||
|
||||
// Parse the converted output
|
||||
config, err := conv.ParseInput(output, tc.targetFormat)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse converted output: %v", err)
|
||||
}
|
||||
|
||||
// Validate the converted configuration
|
||||
if err := conv.validate(config); err != nil {
|
||||
t.Errorf("Converted configuration failed validation: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestExampleFileRoundTrip verifies that converting an example file to another format
|
||||
// and back produces equivalent configuration (round-trip conversion).
|
||||
// With the nested YAML format, all formats now support proper round-trip conversion.
|
||||
func TestExampleFileRoundTrip(t *testing.T) {
|
||||
examplesDir := filepath.Join("..", "examples")
|
||||
|
||||
if _, err := os.Stat(examplesDir); os.IsNotExist(err) {
|
||||
t.Skip("examples directory not found, skipping round-trip tests")
|
||||
return
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
file string
|
||||
format string
|
||||
intermediate string
|
||||
}{
|
||||
// Properties <-> INI conversions work well in both directions
|
||||
{"httpclient.properties", "properties", "ini"},
|
||||
{"httpserver.conf", "ini", "properties"},
|
||||
{"client.properties", "properties", "ini"},
|
||||
// Properties/INI <-> YAML conversions now work with nested format
|
||||
{"httpclient.properties", "properties", "yaml"},
|
||||
{"socks.conf", "ini", "yaml"},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.file+"_via_"+tc.intermediate, func(t *testing.T) {
|
||||
filePath := filepath.Join(examplesDir, tc.file)
|
||||
|
||||
// Read original file
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read file: %v", err)
|
||||
}
|
||||
|
||||
conv := &Converter{strict: false}
|
||||
|
||||
// Parse original
|
||||
original, err := conv.ParseInput(data, tc.format)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse original: %v", err)
|
||||
}
|
||||
|
||||
// Convert to intermediate format
|
||||
intermediate, err := conv.generateOutput(original, tc.intermediate)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert to intermediate: %v", err)
|
||||
}
|
||||
|
||||
// Parse intermediate
|
||||
intermediateConfig, err := conv.ParseInput(intermediate, tc.intermediate)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse intermediate: %v", err)
|
||||
}
|
||||
|
||||
// Convert back to original format
|
||||
final, err := conv.generateOutput(intermediateConfig, tc.format)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to convert back to original: %v", err)
|
||||
}
|
||||
|
||||
// Parse final result
|
||||
finalConfig, err := conv.ParseInput(final, tc.format)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse final: %v", err)
|
||||
}
|
||||
|
||||
// Compare key fields between original and final
|
||||
if original.Name != finalConfig.Name {
|
||||
t.Errorf("Name mismatch: original=%s, final=%s", original.Name, finalConfig.Name)
|
||||
}
|
||||
if original.Type != finalConfig.Type {
|
||||
t.Errorf("Type mismatch: original=%s, final=%s", original.Type, finalConfig.Type)
|
||||
}
|
||||
if original.Port != finalConfig.Port {
|
||||
t.Errorf("Port mismatch: original=%d, final=%d", original.Port, finalConfig.Port)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestExampleFilesAgainstTunnelTypes verifies that each example file uses
|
||||
// a valid tunnel type and meets the requirements for that type.
|
||||
func TestExampleFilesAgainstTunnelTypes(t *testing.T) {
|
||||
examplesDir := filepath.Join("..", "examples")
|
||||
|
||||
if _, err := os.Stat(examplesDir); os.IsNotExist(err) {
|
||||
t.Skip("examples directory not found, skipping tunnel type tests")
|
||||
return
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
file string
|
||||
format string
|
||||
tunnelType string
|
||||
requirePort bool
|
||||
requireTarget bool
|
||||
}{
|
||||
{"httpclient.properties", "properties", "httpclient", true, false},
|
||||
{"httpclient.conf", "ini", "http", true, false},
|
||||
{"httpclient.yaml", "yaml", "httpclient", true, false},
|
||||
{"httpserver.properties", "properties", "httpserver", false, true},
|
||||
{"httpserver.conf", "ini", "http", false, true},
|
||||
{"httpserver.yaml", "yaml", "httpserver", false, true},
|
||||
{"socks.properties", "properties", "sockstunnel", true, false},
|
||||
{"socks.conf", "ini", "socks", true, false},
|
||||
{"socks.yaml", "yaml", "sockstunnel", true, false},
|
||||
{"client.properties", "properties", "client", true, false},
|
||||
{"client.conf", "ini", "client", true, false},
|
||||
{"client.yaml", "yaml", "client", true, false},
|
||||
{"server.properties", "properties", "server", false, true},
|
||||
{"server.conf", "ini", "server", false, true},
|
||||
{"server.yaml", "yaml", "server", false, true},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.file, func(t *testing.T) {
|
||||
filePath := filepath.Join(examplesDir, tc.file)
|
||||
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read file: %v", err)
|
||||
}
|
||||
|
||||
conv := &Converter{strict: false}
|
||||
config, err := conv.ParseInput(data, tc.format)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse: %v", err)
|
||||
}
|
||||
|
||||
// Note: tunnel types may vary between formats (e.g., "http" in i2pd vs "httpclient" in Java I2P)
|
||||
// We check if the type is reasonable but don't enforce exact matches across formats
|
||||
if config.Type == "" {
|
||||
t.Error("Tunnel type is empty")
|
||||
}
|
||||
|
||||
// Check port requirement
|
||||
if tc.requirePort && config.Port == 0 {
|
||||
t.Errorf("Expected port to be set for %s tunnel type", tc.tunnelType)
|
||||
}
|
||||
|
||||
// Check target requirement
|
||||
if tc.requireTarget && config.Target == "" {
|
||||
t.Errorf("Expected target to be set for %s tunnel type", tc.tunnelType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -173,7 +173,7 @@ type client`,
|
||||
}
|
||||
}
|
||||
|
||||
// TestYAMLParserErrors tests enhanced error reporting for YAML format
|
||||
// TestYAMLParserErrors tests enhanced error reporting for YAML format (nested structure)
|
||||
func TestYAMLParserErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -184,7 +184,8 @@ func TestYAMLParserErrors(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "valid yaml",
|
||||
input: `name: test-tunnel
|
||||
input: `tunnels:
|
||||
test-tunnel:
|
||||
type: client
|
||||
interface: 127.0.0.1
|
||||
port: 4444`,
|
||||
@@ -192,7 +193,8 @@ port: 4444`,
|
||||
},
|
||||
{
|
||||
name: "invalid indentation",
|
||||
input: `name: test
|
||||
input: `tunnels:
|
||||
test:
|
||||
type: client
|
||||
interface: 127.0.0.1`,
|
||||
expectError: true,
|
||||
@@ -200,15 +202,18 @@ interface: 127.0.0.1`,
|
||||
},
|
||||
{
|
||||
name: "invalid yaml structure",
|
||||
input: `name: test
|
||||
type client`,
|
||||
input: `tunnels:
|
||||
test
|
||||
type: client`,
|
||||
expectError: true,
|
||||
expectParseErr: true,
|
||||
expectLineNum: 3, // YAML error reports line 3 (end of file)
|
||||
expectLineNum: 3, // YAML error reports line 3
|
||||
},
|
||||
{
|
||||
name: "unclosed quote",
|
||||
input: `name: "test
|
||||
input: `tunnels:
|
||||
test:
|
||||
name: "test
|
||||
type: client`,
|
||||
expectError: true,
|
||||
expectParseErr: true,
|
||||
|
||||
@@ -331,8 +331,19 @@ func (c *Converter) generateJavaProperties(config *TunnelConfig) ([]byte, error)
|
||||
// formatPropertyValue formats a property value for output
|
||||
// Arrays/slices are formatted as comma-separated values
|
||||
func formatPropertyValue(v interface{}) string {
|
||||
// Handle []string
|
||||
if slice, ok := v.([]string); ok {
|
||||
return strings.Join(slice, ",")
|
||||
}
|
||||
|
||||
// Handle []interface{} (common from YAML unmarshaling)
|
||||
if slice, ok := v.([]interface{}); ok {
|
||||
strSlice := make([]string, len(slice))
|
||||
for i, item := range slice {
|
||||
strSlice[i] = fmt.Sprint(item)
|
||||
}
|
||||
return strings.Join(strSlice, ",")
|
||||
}
|
||||
|
||||
return fmt.Sprint(v)
|
||||
}
|
||||
|
||||
27
lib/yaml.go
27
lib/yaml.go
@@ -7,15 +7,35 @@ import (
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// parseYAML parses YAML using the standard nested structure with "tunnels" map.
|
||||
// This is the go-i2p format where tunnels are defined in a "tunnels" map.
|
||||
// The parser extracts the first tunnel for single-tunnel conversion workflows.
|
||||
// The tunnel name is set from the map key.
|
||||
func (c *Converter) parseYAML(input []byte) (*TunnelConfig, error) {
|
||||
config := &TunnelConfig{}
|
||||
err := yaml.Unmarshal(input, config)
|
||||
type wrapper struct {
|
||||
Tunnels map[string]*TunnelConfig `yaml:"tunnels"`
|
||||
}
|
||||
|
||||
var w wrapper
|
||||
err := yaml.Unmarshal(input, &w)
|
||||
if err != nil {
|
||||
return nil, c.enhanceYAMLError(input, err)
|
||||
}
|
||||
|
||||
// Extract the first tunnel (for single-tunnel conversion)
|
||||
if len(w.Tunnels) == 0 {
|
||||
return nil, fmt.Errorf("yaml: no tunnels found in tunnels map")
|
||||
}
|
||||
|
||||
// Return the first tunnel with its name set from the map key
|
||||
for name, config := range w.Tunnels {
|
||||
config.Name = name
|
||||
return config, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("yaml: failed to extract tunnel from nested structure")
|
||||
}
|
||||
|
||||
// enhanceYAMLError wraps YAML parsing errors with line context.
|
||||
// The yaml.v2 library provides line numbers in its error messages.
|
||||
func (c *Converter) enhanceYAMLError(input []byte, err error) error {
|
||||
@@ -60,6 +80,9 @@ func (c *Converter) enhanceYAMLError(input []byte, err error) error {
|
||||
return fmt.Errorf("yaml parse error: %w", err)
|
||||
}
|
||||
|
||||
// generateYAML creates YAML output in the standard nested structure format.
|
||||
// This is the go-i2p format where tunnels are defined in a "tunnels" map.
|
||||
// The tunnel is keyed by its name in the map, allowing for future multi-tunnel support.
|
||||
func (c *Converter) generateYAML(config *TunnelConfig) ([]byte, error) {
|
||||
type wrapper struct {
|
||||
Tunnels map[string]*TunnelConfig `yaml:"tunnels"`
|
||||
|
||||
2
main.go
2
main.go
@@ -13,7 +13,7 @@ func main() {
|
||||
cmd := &cli.App{
|
||||
Name: "go-i2ptunnel-config",
|
||||
Usage: "Convert I2P tunnel configurations between formats",
|
||||
Version: "1.0.0",
|
||||
Version: "0.33.0",
|
||||
Description: `A command line utility to convert I2P tunnel configurations between Java I2P, i2pd, and go-i2p formats.
|
||||
|
||||
Supports automatic format detection based on file extensions:
|
||||
|
||||
Reference in New Issue
Block a user