mirror of
https://github.com/go-i2p/go-meta-listener.git
synced 2025-08-19 12:45:26 -04:00
Compare commits
21 Commits
v0.0.4
...
837850ac99
Author | SHA1 | Date | |
---|---|---|---|
![]() |
837850ac99 | ||
![]() |
1604ecab98 | ||
![]() |
6fac659cd6 | ||
![]() |
ef7b7e14aa | ||
![]() |
f0354c20eb | ||
![]() |
fbc444f39c | ||
![]() |
f26e39e56d | ||
![]() |
c2c53862a4 | ||
![]() |
7775a19ffa | ||
![]() |
905fef98db | ||
![]() |
37c4fdbba9 | ||
![]() |
d3aa11b9d2 | ||
![]() |
e2d0906577 | ||
![]() |
d5f2de44a3 | ||
![]() |
a588de13fd | ||
![]() |
ec0f4e5e50 | ||
![]() |
41600bbdf0 | ||
![]() |
cff9eef383 | ||
![]() |
58490c00e7 | ||
![]() |
273f4a33fb | ||
![]() |
2058a43096 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -23,3 +23,5 @@ go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
/*keys
|
||||
/data-dir*
|
||||
|
@@ -27,7 +27,7 @@ func main() {
|
||||
if err := metaListener.AddListener("tcp", tcpListener); err != nil {
|
||||
log.Fatalf("Failed to add TCP listener: %v", err)
|
||||
}
|
||||
log.Println("Added TCP listener on 127.0.0.1:8080")
|
||||
log.Println("Added TCP listener on 127.0.0.1:8082")
|
||||
|
||||
// Create and add a Unix socket listener (on Unix systems)
|
||||
socketPath := "/tmp/example.sock"
|
||||
@@ -42,6 +42,7 @@ func main() {
|
||||
log.Println("Added Unix socket listener on", socketPath)
|
||||
}
|
||||
}
|
||||
log.Println("Starting http server...")
|
||||
|
||||
// Create a simple HTTP server using the meta listener
|
||||
server := &http.Server{
|
||||
@@ -49,6 +50,7 @@ func main() {
|
||||
fmt.Fprintf(w, "Hello from MetaListener! You connected via: %s\n", r.Proto)
|
||||
}),
|
||||
}
|
||||
log.Println("Server is ready to accept connections...")
|
||||
|
||||
// Handle server shutdown gracefully
|
||||
stop := make(chan os.Signal, 1)
|
||||
|
8
go.mod
8
go.mod
@@ -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
26
go.sum
@@ -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
68
handler.go
Normal 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
89
listener.go
Normal 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
7
log.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package meta
|
||||
|
||||
import (
|
||||
"github.com/go-i2p/logger"
|
||||
)
|
||||
|
||||
var log = logger.GetGoI2PLogger()
|
31
metaaddr.go
Normal file
31
metaaddr.go
Normal 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
|
||||
}
|
177
metalistener.go
177
metalistener.go
@@ -6,16 +6,15 @@ import (
|
||||
"fmt"
|
||||
"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")
|
||||
// ErrInternalListenerFailure is returned when an internal listener fails
|
||||
ErrInternalListenerFailure = errors.New("Internal listener error, shutting down metalistener for restart")
|
||||
ErrNoListeners = oops.Errorf("no active listeners")
|
||||
)
|
||||
|
||||
// MetaListener implements the net.Listener interface and manages multiple
|
||||
@@ -27,8 +26,6 @@ type MetaListener struct {
|
||||
listenerWg sync.WaitGroup
|
||||
// connCh is used to receive connections from all managed listeners
|
||||
connCh chan ConnResult
|
||||
// errCh is used to receive errors from all managed listeners
|
||||
errCh chan error
|
||||
// closeCh signals all goroutines to stop
|
||||
closeCh chan struct{}
|
||||
// isClosed indicates whether the meta listener has been closed
|
||||
@@ -47,8 +44,7 @@ type ConnResult struct {
|
||||
func NewMetaListener() *MetaListener {
|
||||
return &MetaListener{
|
||||
listeners: make(map[string]net.Listener),
|
||||
connCh: make(chan ConnResult),
|
||||
errCh: make(chan error, 1), // Buffered to prevent blocking
|
||||
connCh: make(chan ConnResult, 100), // Larger buffer for high connection volume
|
||||
closeCh: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
@@ -99,141 +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 ml.listenerWg.Done()
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
|
||||
select {
|
||||
case <-ml.closeCh:
|
||||
// Meta listener is being closed, exit
|
||||
return
|
||||
default:
|
||||
// Continue processing
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Check if this is a temporary error
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Temporary() {
|
||||
// For temporary errors, wait a bit and try again
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
|
||||
// For non-temporary errors, check if listener was closed
|
||||
ml.mu.RLock()
|
||||
_, stillExists := ml.listeners[id]
|
||||
ml.mu.RUnlock()
|
||||
|
||||
if stillExists {
|
||||
// Create a combined error with both the standard message and original error details
|
||||
combinedErr := fmt.Errorf("%w: listener %s error - %v",
|
||||
ErrInternalListenerFailure, id, err)
|
||||
|
||||
// Send the combined error to notify Accept() calls
|
||||
select {
|
||||
case ml.errCh <- combinedErr:
|
||||
default:
|
||||
// Don't block if no one is reading errors
|
||||
}
|
||||
|
||||
// Then close all listeners
|
||||
go ml.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Send the accepted connection to the connection channel
|
||||
select {
|
||||
case ml.connCh <- ConnResult{Conn: conn, src: id}:
|
||||
// Connection forwarded successfully
|
||||
case <-ml.closeCh:
|
||||
// If we're closing and got a connection, close it
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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, an error, or close signal
|
||||
select {
|
||||
case result := <-ml.connCh:
|
||||
return result.Conn, nil
|
||||
case err := <-ml.errCh:
|
||||
return nil, err
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
errs = append(errs, fmt.Errorf("failed to close listener %s: %w", id, err))
|
||||
}
|
||||
}
|
||||
|
||||
ml.mu.Unlock()
|
||||
|
||||
// Wait for all listener goroutines to exit
|
||||
ml.listenerWg.Wait()
|
||||
|
||||
// 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()
|
||||
@@ -272,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
|
||||
}
|
||||
|
@@ -2,10 +2,12 @@ package mirror
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"log"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AddHeaders adds headers to the connection.
|
||||
@@ -13,30 +15,56 @@ import (
|
||||
// It only adds headers if the connection is an HTTP connection.
|
||||
// It returns a net.Conn with the headers added.
|
||||
func AddHeaders(conn net.Conn, headers map[string]string) net.Conn {
|
||||
// read a request from the connection
|
||||
// if the request is an HTTP request, add the headers
|
||||
// if the request is not an HTTP request, return the connection as is
|
||||
req, err := http.ReadRequest(bufio.NewReader(conn))
|
||||
// Create a buffer to store the original request
|
||||
var buf bytes.Buffer
|
||||
teeReader := io.TeeReader(conn, &buf)
|
||||
|
||||
// Try to read the request, but also save it to our buffer
|
||||
req, err := http.ReadRequest(bufio.NewReader(teeReader))
|
||||
if err != nil {
|
||||
log.Println("Error reading request:", err)
|
||||
// if the request is not an HTTP request, return the connection as is
|
||||
// Not an HTTP request or couldn't parse, return original connection
|
||||
return conn
|
||||
}
|
||||
log.Println("Adding headers to connection:", req.Method, req.URL)
|
||||
|
||||
// Add our headers
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
log.Println("Added header:", key, value)
|
||||
}
|
||||
// write the request back to the connection
|
||||
if err := req.Write(conn); err != nil {
|
||||
log.Println("Error writing request:", err)
|
||||
// if there is an error writing the request, return the connection as is
|
||||
return conn
|
||||
|
||||
// Create a pipe to connect our modified request with the output
|
||||
pr, pw := io.Pipe()
|
||||
|
||||
// Write the modified request to one end of the pipe
|
||||
go func() {
|
||||
req.Write(pw)
|
||||
// Then copy the rest of the original connection
|
||||
io.Copy(pw, conn)
|
||||
pw.Close()
|
||||
}()
|
||||
|
||||
// Return a ReadWriter that reads from our pipe and writes to the original connection
|
||||
return &readWriteConn{
|
||||
Reader: pr,
|
||||
Writer: conn,
|
||||
conn: conn,
|
||||
}
|
||||
// If all goes well, return the connection with the headers added
|
||||
return conn
|
||||
}
|
||||
|
||||
// readWriteConn implements net.Conn
|
||||
type readWriteConn struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
// Implement the rest of net.Conn interface by delegating to the original connection
|
||||
func (rwc *readWriteConn) Close() error { return rwc.conn.Close() }
|
||||
func (rwc *readWriteConn) LocalAddr() net.Addr { return rwc.conn.LocalAddr() }
|
||||
func (rwc *readWriteConn) RemoteAddr() net.Addr { return rwc.conn.RemoteAddr() }
|
||||
func (rwc *readWriteConn) SetDeadline(t time.Time) error { return rwc.conn.SetDeadline(t) }
|
||||
func (rwc *readWriteConn) SetReadDeadline(t time.Time) error { return rwc.conn.SetReadDeadline(t) }
|
||||
func (rwc *readWriteConn) SetWriteDeadline(t time.Time) error { return rwc.conn.SetWriteDeadline(t) }
|
||||
|
||||
// Accept accepts a connection from the listener.
|
||||
// It takes a net.Listener as input and returns a net.Conn with the headers added.
|
||||
// It is used to accept connections from the meta listener and add headers to them.
|
||||
@@ -56,11 +84,11 @@ 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{
|
||||
"Host": ml.MetaListener.Addr().String(),
|
||||
"Host": conn.LocalAddr().String(),
|
||||
"X-Forwarded-For": conn.RemoteAddr().String(),
|
||||
"X-Forwarded-Proto": "http",
|
||||
}
|
||||
|
@@ -2,11 +2,12 @@ package mirror
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"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"
|
||||
@@ -21,56 +22,74 @@ type Mirror struct {
|
||||
var _ net.Listener = &Mirror{}
|
||||
|
||||
func (m *Mirror) Close() error {
|
||||
log.Println("Closing Mirror")
|
||||
if err := m.MetaListener.Close(); err != nil {
|
||||
log.Println("Error closing MetaListener:", err)
|
||||
} else {
|
||||
log.Println("MetaListener closed")
|
||||
}
|
||||
for _, onion := range m.Onions {
|
||||
if err := onion.Close(); err != nil {
|
||||
log.Println("Error closing Onion:", err)
|
||||
} else {
|
||||
log.Println("Onion closed")
|
||||
}
|
||||
}
|
||||
for _, garlic := range m.Garlics {
|
||||
if err := garlic.Close(); err != nil {
|
||||
log.Println("Error closing Garlic:", err)
|
||||
} else {
|
||||
log.Println("Garlic closed")
|
||||
}
|
||||
}
|
||||
log.Println("Mirror closed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMirror(name string) (*Mirror, error) {
|
||||
log.Println("Creating new Mirror")
|
||||
inner := meta.NewMetaListener()
|
||||
name = strings.TrimSpace(name)
|
||||
name = strings.ReplaceAll(name, " ", "")
|
||||
if name == "" {
|
||||
name = "mirror"
|
||||
}
|
||||
onion, err := onramp.NewOnion("metalistener-" + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
garlic, err := onramp.NewGarlic("metalistener-"+name, "127.0.0.1:7656", onramp.OPT_WIDE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Creating new MetaListener with name: '%s'\n", name)
|
||||
_, 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,
|
||||
Garlics: garlics,
|
||||
}
|
||||
log.Printf("Mirror created with name: '%s' and port: '%s', '%s'\n", name, port, ml.MetaListener.Addr().String())
|
||||
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 {
|
||||
@@ -80,26 +99,27 @@ 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 true")
|
||||
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)
|
||||
// Listen on plain HTTP
|
||||
tcpListener, err := net.Listen("tcp", localAddr)
|
||||
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)
|
||||
}
|
||||
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())
|
||||
log.Println("Checking for existing onion and garlic listeners")
|
||||
listenerId := fmt.Sprintf("metalistener-%s-%s", name, port)
|
||||
log.Println("Listener ID:", listenerId)
|
||||
// Check if onion and garlic listeners already exist
|
||||
if ml.Onions[port] == nil {
|
||||
if ml.Onions[port] == nil && !DisableTor() {
|
||||
// make a new onion listener
|
||||
// and add it to the map
|
||||
log.Println("Creating new onion listener")
|
||||
@@ -110,7 +130,7 @@ func (ml Mirror) Listen(name, addr, certdir string, hiddenTls bool) (net.Listene
|
||||
log.Println("Onion listener created for port", port)
|
||||
ml.Onions[port] = onion
|
||||
}
|
||||
if ml.Garlics[port] == nil {
|
||||
if ml.Garlics[port] == nil && !DisableI2P() {
|
||||
// make a new garlic listener
|
||||
// and add it to the map
|
||||
log.Println("Creating new garlic listener")
|
||||
@@ -124,49 +144,57 @@ func (ml Mirror) Listen(name, addr, certdir string, hiddenTls bool) (net.Listene
|
||||
if hiddenTls {
|
||||
// make sure an onion and a garlic listener exist at ml.Onions[port] and ml.Garlics[port]
|
||||
// and listen on them, check existence first
|
||||
onionListener, err := ml.Onions[port].ListenTLS()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if !DisableTor() {
|
||||
onionListener, err := ml.Onions[port].ListenTLS()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oid := fmt.Sprintf("onion-%s", onionListener.Addr().String())
|
||||
if err := ml.AddListener(oid, onionListener); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("OnionTLS listener added https://%s\n", onionListener.Addr())
|
||||
}
|
||||
oid := fmt.Sprintf("onion-%s", onionListener.Addr().String())
|
||||
if err := ml.AddListener(oid, onionListener); err != nil {
|
||||
return nil, err
|
||||
if !DisableI2P() {
|
||||
garlicListener, err := ml.Garlics[port].ListenTLS()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gid := fmt.Sprintf("garlic-%s", garlicListener.Addr().String())
|
||||
if err := ml.AddListener(gid, garlicListener); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("GarlicTLS listener added https://%s\n", garlicListener.Addr())
|
||||
}
|
||||
log.Printf("OnionTLS listener added https://%s\n", onionListener.Addr())
|
||||
garlicListener, err := ml.Garlics[port].ListenTLS()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gid := fmt.Sprintf("garlic-%s", garlicListener.Addr().String())
|
||||
if err := ml.AddListener(gid, garlicListener); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("GarlicTLS listener added https://%s\n", garlicListener.Addr())
|
||||
} else {
|
||||
onionListener, err := ml.Onions[port].Listen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if !DisableTor() {
|
||||
onionListener, err := ml.Onions[port].Listen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oid := fmt.Sprintf("onion-%s", onionListener.Addr().String())
|
||||
if err := ml.AddListener(oid, onionListener); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Onion listener added http://%s\n", onionListener.Addr())
|
||||
}
|
||||
oid := fmt.Sprintf("onion-%s", onionListener.Addr().String())
|
||||
if err := ml.AddListener(oid, onionListener); err != nil {
|
||||
return nil, err
|
||||
if !DisableI2P() {
|
||||
garlicListener, err := ml.Garlics[port].Listen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gid := fmt.Sprintf("garlic-%s", garlicListener.Addr().String())
|
||||
if err := ml.AddListener(gid, garlicListener); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Garlic listener added http://%s\n", garlicListener.Addr())
|
||||
}
|
||||
log.Printf("Onion listener added http://%s\n", onionListener.Addr())
|
||||
garlicListener, err := ml.Garlics[port].Listen()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gid := fmt.Sprintf("garlic-%s", garlicListener.Addr().String())
|
||||
if err := ml.AddListener(gid, garlicListener); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("Garlic listener added http://%s\n", garlicListener.Addr())
|
||||
}
|
||||
if addr != "" {
|
||||
cfg := wileedot.Config{
|
||||
Domain: name,
|
||||
AllowedDomains: []string{name},
|
||||
CertDir: certdir,
|
||||
CertDir: certDir(),
|
||||
Email: addr,
|
||||
}
|
||||
tlsListener, err := wileedot.New(cfg)
|
||||
@@ -187,10 +215,67 @@ 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 {
|
||||
val := os.Getenv("DISABLE_TOR")
|
||||
if val == "1" || strings.ToLower(val) == "true" {
|
||||
log.Println("Tor is disabled by environment variable DISABLE_TOR")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func DisableI2P() bool {
|
||||
val := os.Getenv("DISABLE_I2P")
|
||||
if val == "1" || strings.ToLower(val) == "true" {
|
||||
log.Println("I2P is disabled by environment variable DISABLE_I2P")
|
||||
return true
|
||||
}
|
||||
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
7
mirror/log.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package mirror
|
||||
|
||||
import (
|
||||
"github.com/go-i2p/logger"
|
||||
)
|
||||
|
||||
var log = logger.GetGoI2PLogger()
|
@@ -14,13 +14,17 @@ import (
|
||||
func main() {
|
||||
host := flag.String("host", "localhost", "Host to forward connections to")
|
||||
port := flag.Int("port", 8080, "Port to forward connections to")
|
||||
listenPort := flag.Int("listen-port", 3002, "Port to listen for incoming connections")
|
||||
domain := flag.String("domain", "i2pgit.org", "Domain name for TLS listener")
|
||||
email := flag.String("email", "example@example.com", "Email address for Let's Encrypt registration")
|
||||
email := flag.String("email", "", "Email address for Let's Encrypt registration")
|
||||
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(*domain, *email, *certDir, *hiddenTls)
|
||||
metaListener, err := mirror.Listen(addr, *email)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
119
tcp/config.go
Normal file
119
tcp/config.go
Normal 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()
|
||||
}
|
Reference in New Issue
Block a user