mirror of
https://github.com/go-i2p/go-i2ptunnel.git
synced 2025-12-20 15:15:52 -05:00
refinements
This commit is contained in:
2
go.mod
2
go.mod
@@ -5,7 +5,7 @@ go 1.23.5
|
||||
require (
|
||||
github.com/go-i2p/go-connfilter v0.0.0-20250205023438-0f2b889a80f6
|
||||
github.com/go-i2p/go-forward v0.0.0-20250202052226-ee8a43dcb664
|
||||
github.com/go-i2p/go-i2ptunnel-config v0.0.0-20250208035926-cff0b0758eda
|
||||
github.com/go-i2p/go-i2ptunnel-config v0.0.0-20250209024146-bb43a7caaf9f
|
||||
github.com/go-i2p/go-limit v0.0.0-20250203203118-210616857c15
|
||||
github.com/go-i2p/i2pkeys v0.33.92
|
||||
github.com/go-i2p/onramp v0.33.92
|
||||
|
||||
2
go.sum
2
go.sum
@@ -16,6 +16,8 @@ github.com/go-i2p/go-i2ptunnel-config v0.0.0-20250203061220-6b5e19741c47 h1:3F1v
|
||||
github.com/go-i2p/go-i2ptunnel-config v0.0.0-20250203061220-6b5e19741c47/go.mod h1:u8CgiYIfehSFpoVWNe1up6TO4sasPpRUHxZw7W2e4sM=
|
||||
github.com/go-i2p/go-i2ptunnel-config v0.0.0-20250208035926-cff0b0758eda h1:I5z+lG0tk6TB/GY1wEZLVJZer8kuA9KCG0IdrJWGghQ=
|
||||
github.com/go-i2p/go-i2ptunnel-config v0.0.0-20250208035926-cff0b0758eda/go.mod h1:u8CgiYIfehSFpoVWNe1up6TO4sasPpRUHxZw7W2e4sM=
|
||||
github.com/go-i2p/go-i2ptunnel-config v0.0.0-20250209024146-bb43a7caaf9f h1:sLuEjwk/1NfH7krFMdyyqeM43IpkcaYBxv4pt6k8l3I=
|
||||
github.com/go-i2p/go-i2ptunnel-config v0.0.0-20250209024146-bb43a7caaf9f/go.mod h1:u8CgiYIfehSFpoVWNe1up6TO4sasPpRUHxZw7W2e4sM=
|
||||
github.com/go-i2p/go-limit v0.0.0-20250203203118-210616857c15 h1:ASjMbwlepoDQfrhv+H2B5ICBPJU5ES1JzmOxzPDx3YQ=
|
||||
github.com/go-i2p/go-limit v0.0.0-20250203203118-210616857c15/go.mod h1:4jjmVRhvKj47sQ6B6wdDhN1IrEZunE6KwkYLQx/BeVE=
|
||||
github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4/go.mod h1:m5TlHjPZrU5KbTd7Lr+I2rljyC6aJ88HdkeMQXV0U0E=
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package i2ptunnel
|
||||
|
||||
import "strings"
|
||||
|
||||
type I2PTunnelStatus string
|
||||
|
||||
const (
|
||||
@@ -24,6 +26,8 @@ type I2PTunnel interface {
|
||||
Stop() error
|
||||
// Get the tunnel's name
|
||||
Name() string
|
||||
// Get the tunnel's ID
|
||||
ID() string
|
||||
// Get the tunnel's type
|
||||
Type() string
|
||||
// Get the tunnel's I2P address
|
||||
@@ -34,6 +38,8 @@ type I2PTunnel interface {
|
||||
Options() map[string]string
|
||||
// Set the tunnel's options
|
||||
SetOptions(map[string]string) error
|
||||
// Load the tunnel config
|
||||
LoadConfig(path string) error
|
||||
// Get the tunnel's status
|
||||
Status() I2PTunnelStatus
|
||||
// Get the tunnel's error message
|
||||
@@ -41,3 +47,20 @@ type I2PTunnel interface {
|
||||
// Get the tunnel's local host:port
|
||||
LocalAddress() (string, error)
|
||||
}
|
||||
|
||||
// Clean the name to form an ID
|
||||
// change newlines to +
|
||||
// change tabs to _
|
||||
// change spaces to -
|
||||
// erase foreslashes
|
||||
func Clean(name string) string {
|
||||
// change newlines to +
|
||||
// change tabs to _
|
||||
// change spaces to -
|
||||
// erase foreslashes
|
||||
clean := strings.ReplaceAll(name, "\n", "+")
|
||||
clean = strings.ReplaceAll(clean, "\t", "_")
|
||||
clean = strings.ReplaceAll(clean, " ", "-")
|
||||
clean = strings.ReplaceAll(clean, "/", "")
|
||||
return clean
|
||||
}
|
||||
|
||||
36
webui/controller/fromhome.go
Normal file
36
webui/controller/fromhome.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
i2ptunnel "github.com/go-i2p/go-i2ptunnel/lib/core"
|
||||
)
|
||||
|
||||
func handler(r *http.Request) string {
|
||||
if r != nil {
|
||||
dir, file := filepath.Split(r.URL.Path)
|
||||
if dir == "/" {
|
||||
if file == "home" {
|
||||
return "group"
|
||||
}
|
||||
} else {
|
||||
if file == "config" {
|
||||
return "config"
|
||||
} else if file == "control" {
|
||||
return "control"
|
||||
}
|
||||
}
|
||||
}
|
||||
return "group"
|
||||
}
|
||||
|
||||
func tunnel(r *http.Request) string {
|
||||
if r != nil {
|
||||
dir, file := filepath.Split(r.URL.Path)
|
||||
if file == "config" || file == "control" {
|
||||
return i2ptunnel.Clean(dir)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
32
webui/controller/i2ptunnelconfig.go
Normal file
32
webui/controller/i2ptunnelconfig.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
i2ptunnel "github.com/go-i2p/go-i2ptunnel/lib/core"
|
||||
)
|
||||
|
||||
/**
|
||||
Config is an I2P Tunnel configuration GUI for a single I2P Tunnel.
|
||||
It manages a single i2ptunnel.I2PTunnel, and can accept any implementation of that interface.
|
||||
It has no specific behaviors for any tunnel type.
|
||||
It presents a simple categorial list of I2PTunnel options.
|
||||
It uses ../templates/i2ptunnelconfig.html as an HTML template
|
||||
*/
|
||||
|
||||
type Config struct {
|
||||
i2ptunnel.I2PTunnel
|
||||
}
|
||||
|
||||
func (c *Config) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
func NewConfig(yamlFile string) (*Config, error) {
|
||||
c := &Config{}
|
||||
err := c.LoadConfig(yamlFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
36
webui/controller/i2ptunnelcontrol.go
Normal file
36
webui/controller/i2ptunnelcontrol.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
/*
|
||||
*
|
||||
Controller is a tunnel controller GUI for a single I2P Tunnel.
|
||||
It manages a single i2ptunnel.I2PTunnel, and can accept any implementation of that interface.
|
||||
It has no specific behaviors for any tunnel type.
|
||||
It uses ../templates/i2ptunnelcontrol.html as an HTML template for the control page.
|
||||
It uses ../templates/i2ptunnelminicontrol.html as an HTML template for the home page.
|
||||
*/
|
||||
type Controller struct {
|
||||
*Config
|
||||
}
|
||||
|
||||
func (c *Controller) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
func (c *Controller) MiniServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
|
||||
func NewController(yamlFile string) (*Controller, error) {
|
||||
cfg, err := NewConfig(yamlFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &Controller{
|
||||
Config: cfg,
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
87
webui/controller/i2ptunnelgroup.go
Normal file
87
webui/controller/i2ptunnelgroup.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
i2ptunnel "github.com/go-i2p/go-i2ptunnel/lib/core"
|
||||
templates "github.com/go-i2p/go-i2ptunnel/webui/templates"
|
||||
)
|
||||
|
||||
type ControllerGroup struct {
|
||||
I2PTunnels []Controller
|
||||
}
|
||||
|
||||
func (cg *ControllerGroup) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
cg.HandleHTMLHeader(r, w)
|
||||
defer cg.HandleHTMLFooter(r, w)
|
||||
switch handler(r) {
|
||||
case "group":
|
||||
cg.HandleGroup(r, w)
|
||||
case "control":
|
||||
for _, controller := range cg.I2PTunnels {
|
||||
if i2ptunnel.Clean(controller.Name()) == tunnel(r) {
|
||||
controller.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
cg.HandleError(r, w)
|
||||
case "config":
|
||||
for _, controller := range cg.I2PTunnels {
|
||||
if i2ptunnel.Clean(controller.Name()) == tunnel(r) {
|
||||
controller.Config.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
cg.HandleError(r, w)
|
||||
default:
|
||||
cg.HandleGroup(r, w)
|
||||
}
|
||||
}
|
||||
|
||||
func (cg *ControllerGroup) HandleHTMLHeader(r *http.Request, w http.ResponseWriter) {
|
||||
templates.HeaderTemplate.Execute(w, nil)
|
||||
}
|
||||
|
||||
func (cg *ControllerGroup) HandleHTMLFooter(r *http.Request, w http.ResponseWriter) {
|
||||
templates.FooterTemplate.Execute(w, nil)
|
||||
}
|
||||
|
||||
func (cg *ControllerGroup) HandleGroup(r *http.Request, w http.ResponseWriter) {
|
||||
for _, controller := range cg.I2PTunnels {
|
||||
if i2ptunnel.Clean(controller.Name()) == tunnel(r) {
|
||||
controller.MiniServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cg *ControllerGroup) HandleError(r *http.Request, w http.ResponseWriter) {
|
||||
r.Form = nil
|
||||
// just redirect back to /home
|
||||
http.Redirect(w, r, "/home", 302)
|
||||
}
|
||||
|
||||
func NewControllerGroup(directory string) (*ControllerGroup, error) {
|
||||
files, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
group := &ControllerGroup{
|
||||
I2PTunnels: make([]Controller, 0),
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !file.IsDir() && strings.HasSuffix(file.Name(), ".yaml") {
|
||||
controller, err := NewController(filepath.Join(directory, file.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
group.I2PTunnels = append(group.I2PTunnels, *controller)
|
||||
}
|
||||
}
|
||||
|
||||
return group, nil
|
||||
}
|
||||
0
webui/css/style.css
Normal file
0
webui/css/style.css
Normal file
0
webui/js/script.js
Normal file
0
webui/js/script.js
Normal file
2
webui/templates/footer.html
Normal file
2
webui/templates/footer.html
Normal file
@@ -0,0 +1,2 @@
|
||||
</body>
|
||||
</html>
|
||||
19
webui/templates/header.html
Normal file
19
webui/templates/header.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
.mini-tunnel {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
margin: 5px;
|
||||
display: inline-block;
|
||||
min-width: 200px;
|
||||
}
|
||||
.status-running { color: green; }
|
||||
.status-stopped { color: red; }
|
||||
.status-starting { color: orange; }
|
||||
.status-stopping { color: orange; }
|
||||
.status-failed { color: darkred; }
|
||||
.status-unknown { color: gray; }
|
||||
</style>
|
||||
</head>
|
||||
98
webui/templates/i2ptunnelconfig.html
Normal file
98
webui/templates/i2ptunnelconfig.html
Normal file
@@ -0,0 +1,98 @@
|
||||
<h1>Configure Tunnel: {{.Name}}</h1>
|
||||
|
||||
<form method="POST" action="/{{.ID}}/config">
|
||||
<fieldset>
|
||||
<legend>Basic Settings</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Name <span class="required">*</span></label>
|
||||
<input type="text" name="name" value="{{.Name}}" required>
|
||||
<div class="help-text">Helpful identifier for this tunnel</div>
|
||||
<label>ID <span class="required">*</span></label>
|
||||
<input type="text" name="id" value="{{.ID}}" required>
|
||||
<div class="help-text">Unique identifier for this tunnel</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Type <span class="required">*</span></label>
|
||||
<select name="type" required>
|
||||
<!-- Server Types -->
|
||||
<optgroup label="Server Tunnels">
|
||||
<option value="tcpserver" {{if eq .Type "tcpserver"}}selected{{end}}>TCP Server</option>
|
||||
<option value="httpserver" {{if eq .Type "httpserver"}}selected{{end}}>HTTP Server</option>
|
||||
<option value="ircserver" {{if eq .Type "ircserver"}}selected{{end}}>IRC Server</option>
|
||||
<option value="udpserver" {{if eq .Type "udpserver"}}selected{{end}}>UDP Server</option>
|
||||
</optgroup>
|
||||
|
||||
<!-- Client Types -->
|
||||
<optgroup label="Client Tunnels">
|
||||
<option value="tcpclient" {{if eq .Type "tcpclient"}}selected{{end}}>TCP Client</option>
|
||||
<option value="udpclient" {{if eq .Type "udpclient"}}selected{{end}}>UDP Client</option>
|
||||
<option value="ircclient" {{if eq .Type "ircclient"}}selected{{end}}>IRC Client</option>
|
||||
</optgroup>
|
||||
|
||||
<!-- Proxy Types -->
|
||||
<optgroup label="Proxy Tunnels">
|
||||
<option value="httpclient" {{if eq .Type "httpclient"}}selected{{end}}>HTTP Proxy</option>
|
||||
<option value="socks" {{if eq .Type "socks"}}selected{{end}}>SOCKS Proxy</option>
|
||||
</optgroup>
|
||||
|
||||
<!-- Special Types -->
|
||||
<!--<optgroup label="Special">
|
||||
<option value="tunclient" {{if eq .Type "tunclient"}}selected{{end}}>TUN Device</option>
|
||||
</optgroup>-->
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Network Settings</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Local Host</label>
|
||||
<input type="text" name="host" value="{{.Options.host}}" placeholder="127.0.0.1">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Local Port <span class="required">*</span></label>
|
||||
<input type="number" name="port" value="{{.Options.port}}" required>
|
||||
</div>
|
||||
|
||||
{{if eq .Type "client"}}
|
||||
<div class="form-group">
|
||||
<label>Target Destination <span class="required">*</span></label>
|
||||
<input type="text" name="destination" value="{{.Target}}">
|
||||
<div class="help-text">I2P destination address</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>I2P Options</legend>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Keys File</label>
|
||||
<input type="text" name="keys" value="{{.Options.keys}}">
|
||||
<div class="help-text">Path to keyfile (leave empty for session-length keys)</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Signature Type</label>
|
||||
<select name="sigtype">
|
||||
<option value="ED25519" {{if eq .Options.sigtype "ED25519"}}selected{{end}}>ED25519</option>
|
||||
<option value="ECDSA" {{if eq .Options.sigtype "ECDSA"}}selected{{end}}>ECDSA</option>
|
||||
</select>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
{{if .Error}}
|
||||
<div class="error">
|
||||
{{.Error}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit">Save Changes</button>
|
||||
<a href="/{{.ID}}/config">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
69
webui/templates/i2ptunnelcontrol.html
Normal file
69
webui/templates/i2ptunnelcontrol.html
Normal file
@@ -0,0 +1,69 @@
|
||||
<h1>Tunnel Control: {{.Name}}</h1>
|
||||
|
||||
<div class="tunnel-info">
|
||||
<h2>Tunnel Information</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Name:</td>
|
||||
<td>{{.Name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Type:</td>
|
||||
<td>{{.Type}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status:</td>
|
||||
<td class="status-{{.Status}}">{{.Status}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Local Address:</td>
|
||||
<td>{{.LocalAddress}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>I2P Address:</td>
|
||||
<td>{{.Address}}</td>
|
||||
</tr>
|
||||
{{if .Target}}
|
||||
<tr>
|
||||
<td>Target:</td>
|
||||
<td>{{.Target}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="tunnel-controls">
|
||||
<h2>Controls</h2>
|
||||
<form method="POST">
|
||||
{{if eq .Status "running"}}
|
||||
<input type="submit" name="action" value="Stop" />
|
||||
{{else if eq .Status "stopped"}}
|
||||
<input type="submit" name="action" value="Start" />
|
||||
{{end}}
|
||||
<input type="submit" name="action" value="Restart" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{if .Error}}
|
||||
<div class="tunnel-errors">
|
||||
<h2>Error Messages</h2>
|
||||
<pre>{{.Error}}</pre>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="tunnel-options">
|
||||
<h2>Tunnel Options</h2>
|
||||
<form method="POST" action="config">
|
||||
{{range $key, $value := .Options}}
|
||||
<div>
|
||||
<label for="{{$key}}">{{$key}}:</label>
|
||||
<input type="text" id="{{$key}}" name="{{$key}}" value="{{$value}}" />
|
||||
</div>
|
||||
{{end}}
|
||||
<input type="submit" value="Update Options" />
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p><a href="/home">Return to Tunnel List</a></p>
|
||||
</footer>
|
||||
4
webui/templates/i2ptunnelgroup.html
Normal file
4
webui/templates/i2ptunnelgroup.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<div class="add-tunnel">
|
||||
<h3>Add New Tunnel</h3>
|
||||
<a href="/new" class="button">Create Tunnel</a>
|
||||
</div>
|
||||
24
webui/templates/i2ptunnelminicontrol.html
Normal file
24
webui/templates/i2ptunnelminicontrol.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<div class="mini-tunnel">
|
||||
<h3>
|
||||
<a href="/tunnel/{{.Name}}">{{.Name}}</a>
|
||||
<small>({{.Type}})</small>
|
||||
</h3>
|
||||
|
||||
<div class="status-{{.Status}}">
|
||||
Status: {{.Status}}
|
||||
</div>
|
||||
|
||||
<form method="POST" style="margin-top: 5px;">
|
||||
{{if eq .Status "running"}}
|
||||
<input type="submit" name="action" value="Stop" />
|
||||
{{else if eq .Status "stopped"}}
|
||||
<input type="submit" name="action" value="Start" />
|
||||
{{end}}
|
||||
</form>
|
||||
|
||||
{{if .Error}}
|
||||
<div style="color: red; font-size: 0.8em;">
|
||||
Error: {{.Error}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
44
webui/templates/templates.go
Normal file
44
webui/templates/templates.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"embed"
|
||||
html "html/template"
|
||||
)
|
||||
|
||||
// embeds the header.html template
|
||||
//
|
||||
//go:embed header.html
|
||||
var BytesHeaderTemplate []byte
|
||||
var HeaderTemplate, _ = html.New("header").Parse(string(BytesHeaderTemplate))
|
||||
|
||||
// embeds the i2ptunnelconfig.html template
|
||||
//
|
||||
//go:embed i2ptunnelconfig.html
|
||||
var BytesI2PTunnelConfigTemplate []byte
|
||||
var I2PTunnelConfigTemplate, _ = html.New("i2ptunnelconfig").Parse(string(BytesI2PTunnelConfigTemplate))
|
||||
|
||||
// embeds the i2ptunnelcontrol.html template
|
||||
//
|
||||
//go:embed i2ptunnelcontrol.html
|
||||
var BytesI2PTunnelControlTemplate []byte
|
||||
var I2PTunnelControlTemplate, _ = html.New("i2ptunnelcontrol").Parse(string(BytesI2PTunnelControlTemplate))
|
||||
|
||||
// embeds the i2ptunnelminicontrol.html template
|
||||
//
|
||||
//go:embed i2ptunnelminicontrol.html
|
||||
var BytesI2PTunnelMiniControlTemplate []byte
|
||||
var I2PTunnelMiniControlTemplate, _ = html.New("i2ptunnelminicontrol").Parse(string(BytesI2PTunnelMiniControlTemplate))
|
||||
|
||||
// embeds the i2ptunnelgroup.html template
|
||||
//
|
||||
//go:embed i2ptunnelgroup.html
|
||||
var BytesI2PTunnelGroupTemplate []byte
|
||||
var I2PTunnelGroupTemplate, _ = html.New("i2ptunnelgroup").Parse(string(BytesI2PTunnelGroupTemplate))
|
||||
|
||||
// embeds the footer.html template
|
||||
//
|
||||
//go:embed footer.html
|
||||
var BytesFooterTemplate []byte
|
||||
var FooterTemplate, _ = html.New("footer").Parse(string(BytesFooterTemplate))
|
||||
|
||||
var efs embed.FS
|
||||
Reference in New Issue
Block a user