Compare commits

14 Commits

Author SHA1 Message Date
eyedeekay
837850ac99 Fix gitignore 2025-05-26 18:36:13 -04:00
eyedeekay
1604ecab98 Revise how the listener config is exposed 2025-05-26 18:35:35 -04:00
eyedeekay
6fac659cd6 Fix dishonored envars 2025-05-26 18:18:10 -04:00
eyedeekay
ef7b7e14aa this seems better 2025-05-26 18:09:07 -04:00
eyedeekay
f0354c20eb that approach feels doomed 2025-05-26 17:59:36 -04:00
eyedeekay
fbc444f39c that approach feels doomed 2025-05-26 17:59:09 -04:00
eyedeekay
f26e39e56d work on tcp lib 2025-05-26 17:57:23 -04:00
eyedeekay
c2c53862a4 work on tcp lib 2025-05-26 17:56:21 -04:00
eyedeekay
7775a19ffa work on tcp lib 2025-05-26 17:54:53 -04:00
eyedeekay
905fef98db Create a TCP socket manager based on the one from gitea 2025-05-26 17:45:24 -04:00
eyedeekay
37c4fdbba9 Split up metalistener, fix deadlock in accept routine, I think 2025-05-26 17:05:48 -04:00
eyedeekay
d3aa11b9d2 Use my structured logger instead of just bare log 2025-05-26 16:53:02 -04:00
eyedeekay
e2d0906577 Use my structured logger instead of just bare log 2025-05-26 16:50:08 -04:00
eyedeekay
d5f2de44a3 Use my structured logger instead of just bare log 2025-05-26 16:48:50 -04:00
13 changed files with 431 additions and 223 deletions

2
.gitignore vendored
View File

@@ -23,3 +23,5 @@ go.work.sum
# env file
.env
/*keys
/data-dir*

8
go.mod
View File

@@ -3,18 +3,24 @@ module github.com/go-i2p/go-meta-listener
go 1.23.5
require (
github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c
github.com/go-i2p/onramp v0.33.92
github.com/opd-ai/wileedot v0.0.0-20241217172720-521d4175e624
github.com/samber/oops v1.18.0
)
require (
github.com/cretz/bine v0.2.0 // indirect
github.com/go-i2p/i2pkeys v0.33.10-0.20241113193422-e10de5e60708 // indirect
github.com/go-i2p/sam3 v0.33.9 // indirect
github.com/oklog/ulid/v2 v2.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/samber/lo v1.50.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
go.opentelemetry.io/otel v1.29.0 // indirect
go.opentelemetry.io/otel/trace v1.29.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/net v0.31.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/text v0.22.0 // indirect
)

26
go.sum
View File

@@ -6,22 +6,37 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/go-i2p/i2pkeys v0.0.0-20241108200332-e4f5ccdff8c4/go.mod h1:m5TlHjPZrU5KbTd7Lr+I2rljyC6aJ88HdkeMQXV0U0E=
github.com/go-i2p/i2pkeys v0.33.10-0.20241113193422-e10de5e60708 h1:Tiy9IBwi21maNpK74yCdHursJJMkyH7w87tX1nXGWzg=
github.com/go-i2p/i2pkeys v0.33.10-0.20241113193422-e10de5e60708/go.mod h1:m5TlHjPZrU5KbTd7Lr+I2rljyC6aJ88HdkeMQXV0U0E=
github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c h1:VTiECn3dFEmUlZjto+wOwJ7SSJTHPLyNprQMR5HzIMI=
github.com/go-i2p/logger v0.0.0-20241123010126-3050657e5d0c/go.mod h1:te7Zj3g3oMeIl8uBXAgO62UKmZ6m6kHRNg1Mm+X8Hzk=
github.com/go-i2p/onramp v0.33.92 h1:Dk3A0SGpdEw829rSjW2LqN8o16pUvuhiN0vn36z7Gpc=
github.com/go-i2p/onramp v0.33.92/go.mod h1:5sfB8H2xk05gAS2K7XAUZ7ekOfwGJu3tWF0fqdXzJG4=
github.com/go-i2p/sam3 v0.33.9 h1:3a+gunx75DFc6jxloUZTAVJbdP6736VU1dy2i7I9fKA=
github.com/go-i2p/sam3 v0.33.9/go.mod h1:oDuV145l5XWKKafeE4igJHTDpPwA0Yloz9nyKKh92eo=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/opd-ai/wileedot v0.0.0-20241217172720-521d4175e624 h1:FXCTQV93+31Yj46zpYbd41es+EYgT7qi4RK6KSVrGQM=
github.com/opd-ai/wileedot v0.0.0-20241217172720-521d4175e624/go.mod h1:ftKSvvGC9FnxZeuL3B4MB6q/DOzVSV0kET08YUyDwbM=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
github.com/samber/oops v1.18.0 h1:NnoCdxlOg/ajFos8HIC0+dV8S6cZRcrjW1WrfZe+GOc=
github.com/samber/oops v1.18.0/go.mod h1:DcZbba2s+PzSx14vY6HjvhV1FDsGOZ1TJg7T/ZZARBQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
@@ -38,9 +53,10 @@ golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

68
handler.go Normal file
View File

@@ -0,0 +1,68 @@
package meta
import (
"net"
"time"
)
// handleListener runs in a separate goroutine for each added listener
// and forwards accepted connections to the connCh channel.
func (ml *MetaListener) handleListener(id string, listener net.Listener) {
defer func() {
log.Printf("Listener goroutine for %s exiting", id)
ml.listenerWg.Done()
}()
for {
// First check if the MetaListener is closed
select {
case <-ml.closeCh:
log.Printf("MetaListener closed, stopping %s listener", id)
return
default:
}
// Set a deadline for Accept to prevent blocking indefinitely
if deadline, ok := listener.(interface{ SetDeadline(time.Time) error }); ok {
deadline.SetDeadline(time.Now().Add(1 * time.Second))
}
conn, err := listener.Accept()
if err != nil {
// Check if this is a timeout error (which we expect due to our deadline)
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
// Check if this is any other temporary error
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
log.Printf("Temporary error in %s listener: %v, retrying in 100ms", id, err)
time.Sleep(100 * time.Millisecond)
continue
}
log.Printf("Permanent error in %s listener: %v, stopping", id, err)
ml.mu.Lock()
delete(ml.listeners, id)
ml.mu.Unlock()
return
}
// If we reach here, we have a valid connection
log.Printf("Listener %s accepted connection from %s", id, conn.RemoteAddr())
// Try to forward the connection, but don't block indefinitely
select {
case ml.connCh <- ConnResult{Conn: conn, src: id}:
log.Printf("Connection from %s successfully forwarded via %s", conn.RemoteAddr(), id)
case <-ml.closeCh:
log.Printf("MetaListener closing while forwarding connection, closing connection")
conn.Close()
return
case <-time.After(5 * time.Second):
// If we can't forward within 5 seconds, something is seriously wrong
log.Printf("WARNING: Connection forwarding timed out, closing connection from %s", conn.RemoteAddr())
conn.Close()
}
}
}

89
listener.go Normal file
View File

@@ -0,0 +1,89 @@
package meta
import (
"fmt"
"net"
)
// Accept implements the net.Listener Accept method.
// It returns the next connection from any of the managed listeners.
func (ml *MetaListener) Accept() (net.Conn, error) {
// Check if already closed before entering the select loop
ml.mu.RLock()
if ml.isClosed {
ml.mu.RUnlock()
return nil, ErrListenerClosed
}
ml.mu.RUnlock()
for {
select {
case result, ok := <-ml.connCh:
if !ok {
return nil, ErrListenerClosed
}
// Access RemoteAddr() directly on the connection
return result, nil
case <-ml.closeCh:
// Double-check the closed state under lock to ensure consistency
closed := ml.isClosed
if closed {
return nil, ErrListenerClosed
}
continue
}
}
}
// Close implements the net.Listener Close method.
// It closes all managed listeners and releases resources.
func (ml *MetaListener) Close() error {
ml.mu.Lock()
if ml.isClosed {
ml.mu.Unlock()
return nil
}
log.Printf("Closing MetaListener with %d listeners", len(ml.listeners))
ml.isClosed = true
// Signal all goroutines to stop
close(ml.closeCh)
// Close all listeners
var errs []error
for id, listener := range ml.listeners {
if err := listener.Close(); err != nil {
log.Printf("Error closing %s listener: %v", id, err)
errs = append(errs, err)
}
}
ml.mu.Unlock()
// Wait for all listener goroutines to exit
ml.listenerWg.Wait()
log.Printf("All listener goroutines have exited")
// Return combined errors if any
if len(errs) > 0 {
return fmt.Errorf("errors closing listeners: %v", errs)
}
return nil
}
// Addr implements the net.Listener Addr method.
// It returns a MetaAddr representing all managed listeners.
func (ml *MetaListener) Addr() net.Addr {
ml.mu.RLock()
defer ml.mu.RUnlock()
addresses := make([]net.Addr, 0, len(ml.listeners))
for _, listener := range ml.listeners {
addresses = append(addresses, listener.Addr())
}
return &MetaAddr{addresses: addresses}
}

7
log.go Normal file
View File

@@ -0,0 +1,7 @@
package meta
import (
"github.com/go-i2p/logger"
)
var log = logger.GetGoI2PLogger()

31
metaaddr.go Normal file
View File

@@ -0,0 +1,31 @@
package meta
import "net"
// MetaAddr implements the net.Addr interface for a meta listener.
type MetaAddr struct {
addresses []net.Addr
}
// Network returns the name of the network.
func (ma *MetaAddr) Network() string {
return "meta"
}
// String returns a string representation of all managed addresses.
func (ma *MetaAddr) String() string {
if len(ma.addresses) == 0 {
return "meta(empty)"
}
result := "meta("
for i, addr := range ma.addresses {
if i > 0 {
result += ", "
}
result += addr.String()
}
result += ")"
return result
}

View File

@@ -4,17 +4,17 @@ import (
"context"
"errors"
"fmt"
"log"
"net"
"sync"
"time"
"github.com/samber/oops"
)
var (
// ErrListenerClosed is returned when attempting to accept on a closed listener
ErrListenerClosed = errors.New("listener is closed")
ErrListenerClosed = oops.Errorf("listener is closed")
// ErrNoListeners is returned when the meta listener has no active listeners
ErrNoListeners = errors.New("no active listeners")
ErrNoListeners = oops.Errorf("no active listeners")
)
// MetaListener implements the net.Listener interface and manages multiple
@@ -95,152 +95,6 @@ func (ml *MetaListener) RemoveListener(id string) error {
return err
}
// handleListener runs in a separate goroutine for each added listener
// and forwards accepted connections to the connCh channel.
func (ml *MetaListener) handleListener(id string, listener net.Listener) {
defer func() {
log.Printf("Listener goroutine for %s exiting", id)
ml.listenerWg.Done()
}()
for {
// First check if the MetaListener is closed
select {
case <-ml.closeCh:
log.Printf("MetaListener closed, stopping %s listener", id)
return
default:
}
// Set a deadline for Accept to prevent blocking indefinitely
if deadline, ok := listener.(interface{ SetDeadline(time.Time) error }); ok {
deadline.SetDeadline(time.Now().Add(1 * time.Second))
}
conn, err := listener.Accept()
if err != nil {
// Check if this is a timeout error (which we expect due to our deadline)
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
// Check if this is any other temporary error
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
log.Printf("Temporary error in %s listener: %v, retrying in 100ms", id, err)
time.Sleep(100 * time.Millisecond)
continue
}
log.Printf("Permanent error in %s listener: %v, stopping", id, err)
ml.mu.Lock()
delete(ml.listeners, id)
ml.mu.Unlock()
return
}
// If we reach here, we have a valid connection
log.Printf("Listener %s accepted connection from %s", id, conn.RemoteAddr())
// Try to forward the connection, but don't block indefinitely
select {
case ml.connCh <- ConnResult{Conn: conn, src: id}:
log.Printf("Connection from %s successfully forwarded via %s", conn.RemoteAddr(), id)
case <-ml.closeCh:
log.Printf("MetaListener closing while forwarding connection, closing connection")
conn.Close()
return
case <-time.After(5 * time.Second):
// If we can't forward within 5 seconds, something is seriously wrong
log.Printf("WARNING: Connection forwarding timed out, closing connection from %s", conn.RemoteAddr())
conn.Close()
}
}
}
// Accept implements the net.Listener Accept method.
// It waits for and returns the next connection from any of the managed listeners.
func (ml *MetaListener) Accept() (net.Conn, error) {
for {
ml.mu.RLock()
if ml.isClosed {
ml.mu.RUnlock()
return nil, ErrListenerClosed
}
if len(ml.listeners) == 0 {
ml.mu.RUnlock()
return nil, ErrNoListeners
}
ml.mu.RUnlock()
// Wait for either a connection or close signal
select {
case result, ok := <-ml.connCh:
if !ok {
return nil, ErrListenerClosed
}
log.Printf("Accept returning connection from %s via %s",
result.RemoteAddr(), result.src)
return result.Conn, nil
case <-ml.closeCh:
return nil, ErrListenerClosed
}
}
}
// Close implements the net.Listener Close method.
// It closes all managed listeners and releases resources.
func (ml *MetaListener) Close() error {
ml.mu.Lock()
if ml.isClosed {
ml.mu.Unlock()
return nil
}
log.Printf("Closing MetaListener with %d listeners", len(ml.listeners))
ml.isClosed = true
// Signal all goroutines to stop
close(ml.closeCh)
// Close all listeners
var errs []error
for id, listener := range ml.listeners {
if err := listener.Close(); err != nil {
log.Printf("Error closing %s listener: %v", id, err)
errs = append(errs, err)
}
}
ml.mu.Unlock()
// Wait for all listener goroutines to exit
ml.listenerWg.Wait()
log.Printf("All listener goroutines have exited")
// Return combined errors if any
if len(errs) > 0 {
return fmt.Errorf("errors closing listeners: %v", errs)
}
return nil
}
// Addr implements the net.Listener Addr method.
// It returns a MetaAddr representing all managed listeners.
func (ml *MetaListener) Addr() net.Addr {
ml.mu.RLock()
defer ml.mu.RUnlock()
addresses := make([]net.Addr, 0, len(ml.listeners))
for _, listener := range ml.listeners {
addresses = append(addresses, listener.Addr())
}
return &MetaAddr{addresses: addresses}
}
// ListenerIDs returns the IDs of all active listeners.
func (ml *MetaListener) ListenerIDs() []string {
ml.mu.RLock()
@@ -279,31 +133,3 @@ func (ml *MetaListener) WaitForShutdown(ctx context.Context) error {
return ctx.Err()
}
}
// MetaAddr implements the net.Addr interface for a meta listener.
type MetaAddr struct {
addresses []net.Addr
}
// Network returns the name of the network.
func (ma *MetaAddr) Network() string {
return "meta"
}
// String returns a string representation of all managed addresses.
func (ma *MetaAddr) String() string {
if len(ma.addresses) == 0 {
return "meta(empty)"
}
result := "meta("
for i, addr := range ma.addresses {
if i > 0 {
result += ", "
}
result += addr.String()
}
result += ")"
return result
}

View File

@@ -5,7 +5,6 @@ import (
"bytes"
"crypto/tls"
"io"
"log"
"net"
"net/http"
"time"
@@ -85,7 +84,7 @@ func (ml *Mirror) Accept() (net.Conn, error) {
return nil, err
}
// If the handshake is successful, get the underlying connection
//conn = tlsConn.NetConn()
// conn = tlsConn.NetConn()
}
host := map[string]string{

View File

@@ -2,13 +2,12 @@ package mirror
import (
"fmt"
"log"
"net"
"os"
"strconv"
"strings"
"github.com/go-i2p/go-meta-listener"
"github.com/go-i2p/go-meta-listener/tcp"
"github.com/go-i2p/onramp"
wileedot "github.com/opd-ai/wileedot"
@@ -56,24 +55,29 @@ func NewMirror(name string) (*Mirror, error) {
name = "mirror"
}
log.Printf("Creating new MetaListener with name: '%s'\n", name)
onion, err := onramp.NewOnion("metalistener-" + name)
if err != nil {
return nil, err
}
log.Println("Created new Onion manager")
garlic, err := onramp.NewGarlic("metalistener-"+name, "127.0.0.1:7656", onramp.OPT_WIDE)
if err != nil {
return nil, err
}
log.Println("Created new Garlic manager")
_, port, err := net.SplitHostPort(name)
if err != nil {
port = "3000"
}
onions := make(map[string]*onramp.Onion)
if !DisableTor() {
onion, err := onramp.NewOnion("metalistener-" + name)
if err != nil {
return nil, err
}
log.Println("Created new Onion manager")
onions[port] = onion
}
garlics := make(map[string]*onramp.Garlic)
onions[port] = onion
garlics[port] = garlic
if !DisableI2P() {
garlic, err := onramp.NewGarlic("metalistener-"+name, "127.0.0.1:7656", onramp.OPT_WIDE)
if err != nil {
return nil, err
}
log.Println("Created new Garlic manager")
garlics[port] = garlic
}
ml := &Mirror{
MetaListener: inner,
Onions: onions,
@@ -83,9 +87,9 @@ func NewMirror(name string) (*Mirror, error) {
return ml, nil
}
func (ml Mirror) Listen(name, addr, certdir string, hiddenTls bool) (net.Listener, error) {
func (ml Mirror) Listen(name, addr string) (net.Listener, error) {
log.Println("Starting Mirror Listener")
log.Printf("Actual args: name: '%s' addr: '%s' certDir: '%s' hiddenTls: '%t'\n", name, addr, certdir, hiddenTls)
log.Printf("Actual args: name: '%s' addr: '%s' certDir: '%s' hiddenTls: '%t'\n", name, addr, certDir(), hiddenTls)
// get the port:
_, port, err := net.SplitHostPort(name)
if err != nil {
@@ -95,26 +99,19 @@ func (ml Mirror) Listen(name, addr, certdir string, hiddenTls bool) (net.Listene
}
port = "3000"
}
if strings.HasSuffix(port, "22") {
log.Println("Port ends with 22, setting hiddenTls to false")
log.Println("This is a workaround for the fact that the default port for SSH is 22")
log.Println("This is so self-configuring SSH servers can be used without TLS, which would make connecting to them wierd")
hiddenTls = false
}
hiddenTls := hiddenTls(port)
localAddr := net.JoinHostPort("127.0.0.1", port)
portInt, _ := strconv.Atoi(port)
// Listen on plain HTTP
tcpAddr := &net.TCPAddr{
IP: net.ParseIP(localAddr),
Port: portInt, // let the OS choose a free port
Zone: "",
}
tcpListener, err := net.ListenTCP("tcp", tcpAddr)
listener, err := net.Listen("tcp", localAddr)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to create TCP listener on %s: %w", localAddr, err)
}
//tcpListener.SetDeadline()
if err := ml.AddListener(port, tcpListener); err != nil {
tcpListener := listener.(*net.TCPListener)
hardenedListener, err := tcp.Config(*tcpListener)
if err != nil {
log.Fatal(err)
}
log.Printf("TCP listener created on %s\n", localAddr)
if err := ml.AddListener(port, hardenedListener); err != nil {
return nil, err
}
log.Printf("HTTP Local listener added http://%s\n", tcpListener.Addr())
@@ -197,7 +194,7 @@ func (ml Mirror) Listen(name, addr, certdir string, hiddenTls bool) (net.Listene
cfg := wileedot.Config{
Domain: name,
AllowedDomains: []string{name},
CertDir: certdir,
CertDir: certDir(),
Email: addr,
}
tlsListener, err := wileedot.New(cfg)
@@ -218,12 +215,12 @@ func (ml Mirror) Listen(name, addr, certdir string, hiddenTls bool) (net.Listene
// name is the domain name used for the TLS listener, required for Let's Encrypt.
// addr is the email address used for Let's Encrypt registration.
// It is recommended to use a valid email address for production use.
func Listen(name, addr, certdir string, hiddenTls bool) (net.Listener, error) {
func Listen(name, addr string) (net.Listener, error) {
ml, err := NewMirror(name)
if err != nil {
return nil, err
}
return ml.Listen(name, addr, certdir, hiddenTls)
return ml.Listen(name, addr)
}
func DisableTor() bool {
@@ -243,3 +240,42 @@ func DisableI2P() bool {
}
return false
}
// HIDDEN_TLS is a global variable that determines whether to use hidden TLS.
// It is set to true by default, but can be overridden by the hiddenTls function.
// If the port ends with "22", it will return false, indicating that hidden TLS should not be used.
// This is a useful workaround for SSH connections, which commonly use port 22.
var HIDDEN_TLS = true
func hiddenTls(port string) bool {
// Check if the port is 22, which is commonly used for SSH
if strings.HasSuffix(port, "22") {
log.Println("Port ends with 22, setting hiddenTls to false")
return false
}
// Default to true for other ports
return HIDDEN_TLS
}
var default_CERT_DIR = "./certs"
// CERT_DIR is the directory where certificates are stored.
// It can be overridden by setting the CERT_DIR environment variable.
// if CERT_DIR is not set, it defaults to "./certs".
// if CERT_DIR is set from Go code, it will always return the value set in the code.
// if CERT_DIR is set from the environment, it will return the value from the environment unless overridden by Go code.
var CERT_DIR = default_CERT_DIR
func certDir() string {
// Default certificate directory
certDir := CERT_DIR
if certDir != default_CERT_DIR {
// if the default directory is not used, always return it
return certDir
}
if dir := os.Getenv("CERT_DIR"); dir != "" {
certDir = dir
}
log.Printf("Using certificate directory: %s\n", certDir)
return certDir
}

7
mirror/log.go Normal file
View File

@@ -0,0 +1,7 @@
package mirror
import (
"github.com/go-i2p/logger"
)
var log = logger.GetGoI2PLogger()

View File

@@ -20,9 +20,11 @@ func main() {
certDir := flag.String("certdir", "./certs", "Directory for storing certificates")
hiddenTls := flag.Bool("hidden-tls", false, "Enable hidden TLS")
flag.Parse()
mirror.CERT_DIR = *certDir
mirror.HIDDEN_TLS = *hiddenTls
addr := net.JoinHostPort(*domain, fmt.Sprintf("%d", *listenPort))
// Create a new meta listener
metaListener, err := mirror.Listen(addr, *email, *certDir, *hiddenTls)
metaListener, err := mirror.Listen(addr, *email)
if err != nil {
panic(err)
}

119
tcp/config.go Normal file
View File

@@ -0,0 +1,119 @@
// Package tcp provides production hardening for net.TCPListener with minimal overhead.
//
// This package wraps standard Go TCP listeners with essential TCP socket configuration
// for internet-facing services. It applies conservative defaults without requiring
// configuration, making it safe to use in production environments.
//
// Example usage:
//
// listener, err := net.Listen("tcp", ":8080")
// if err != nil {
// log.Fatal(err)
// }
// tcpListener := listener.(*net.TCPListener)
//
// hardenedListener, err := tcp.Config(*tcpListener)
// if err != nil {
// log.Fatal(err)
// }
// defer hardenedListener.Close()
//
// for {
// conn, err := hardenedListener.Accept()
// if err != nil {
// log.Printf("Accept error: %v", err)
// continue
// }
// go handleConnection(conn)
// }
package tcp
import (
"net"
"time"
)
const (
// keepAliveInterval sets TCP keep-alive probe interval to 15 seconds.
// This provides reasonable connection health detection without excessive overhead.
keepAliveInterval = 15 * time.Second
// socketBufferSize sets both read and write socket buffers to 64KB.
// This balances memory usage with throughput for typical web applications.
socketBufferSize = 64 * 1024
)
// hardenedListener wraps net.TCPListener with production hardening features.
type hardenedListener struct {
listener net.TCPListener
}
// Config wraps a net.TCPListener with production hardening features.
//
// The wrapped listener applies the following enhancements:
// - TCP keep-alive with 15-second intervals
// - TCP_NODELAY for reduced latency
// - 64KB socket buffer sizes for optimal throughput
//
// These settings are chosen to provide reliability and performance improvements
// while maintaining compatibility with standard net.Listener interface.
func Config(listener net.TCPListener) (net.Listener, error) {
return &hardenedListener{
listener: listener,
}, nil
}
// Accept waits for and returns the next connection with hardening applied.
func (hl *hardenedListener) Accept() (net.Conn, error) {
conn, err := hl.listener.AcceptTCP()
if err != nil {
return nil, err
}
// Apply TCP hardening settings
if err := hl.hardenConnection(conn); err != nil {
conn.Close()
return nil, err
}
return conn, nil
}
// hardenConnection applies security and performance settings to a TCP connection.
func (hl *hardenedListener) hardenConnection(conn *net.TCPConn) error {
// Enable TCP keep-alive to detect dead connections
if err := conn.SetKeepAlive(true); err != nil {
return err
}
// Set keep-alive interval for timely detection of connection issues
if err := conn.SetKeepAlivePeriod(keepAliveInterval); err != nil {
return err
}
// Disable Nagle's algorithm for lower latency
if err := conn.SetNoDelay(true); err != nil {
return err
}
// Set socket buffer sizes for optimal throughput
if err := conn.SetReadBuffer(socketBufferSize); err != nil {
return err
}
if err := conn.SetWriteBuffer(socketBufferSize); err != nil {
return err
}
return nil
}
// Close stops the listener and prevents new connections.
func (hl *hardenedListener) Close() error {
return hl.listener.Close()
}
// Addr returns the listener's network address.
func (hl *hardenedListener) Addr() net.Addr {
return hl.listener.Addr()
}