Files
go-meta-listener/eval.md

68 KiB

Project Path: /home/idk/go/src/github.com/go-i2p/go-meta-listener

Source Tree:

go-meta-listener
├── go.sum
├── example
│   └── main.go
├── eval.md
├── mirror
│   ├── listener.go
│   ├── metaproxy
│   │   ├── main.go
│   │   └── README.md
│   ├── README.md
│   └── header.go
├── metalistener.go
├── go.mod
├── LICENSE
├── README.md
└── Makefile

/home/idk/go/src/github.com/go-i2p/go-meta-listener/go.sum:

github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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/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/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/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=
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=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
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/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=

/home/idk/go/src/github.com/go-i2p/go-meta-listener/example/main.go:

package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/go-i2p/go-meta-listener"
)

func main() {
	// Create a new meta listener
	metaListener := meta.NewMetaListener()
	defer metaListener.Close()

	// Create and add TCP listener
	tcpListener, err := net.Listen("tcp", "127.0.0.1:8082")
	if err != nil {
		log.Fatalf("Failed to create TCP listener: %v", err)
	}
	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:8082")

	// Create and add a Unix socket listener (on Unix systems)
	socketPath := "/tmp/example.sock"
	os.Remove(socketPath) // Clean up from previous runs
	unixListener, err := net.Listen("unix", socketPath)
	if err != nil {
		log.Printf("Failed to create Unix socket listener: %v", err)
	} else {
		if err := metaListener.AddListener("unix", unixListener); err != nil {
			log.Printf("Failed to add Unix socket listener: %v", err)
		} else {
			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{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			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)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)

	go func() {
		log.Println("Server starting, listening on multiple transports")
		if err := server.Serve(metaListener); err != nil && err != http.ErrServerClosed {
			log.Fatalf("HTTP server error: %v", err)
		}
	}()

	// Wait for interrupt signal
	<-stop
	log.Println("Shutting down server...")

	// Create a deadline for graceful shutdown
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Shut down the HTTP server
	if err := server.Shutdown(ctx); err != nil {
		log.Printf("Server shutdown error: %v", err)
	}

	// Wait for all listener goroutines to exit
	if err := metaListener.WaitForShutdown(ctx); err != nil {
		log.Printf("Timed out waiting for listener shutdown: %v", err)
	}

	log.Println("Server stopped")
}

/home/idk/go/src/github.com/go-i2p/go-meta-listener/eval.md:

Project Path: /home/idk/go/src/github.com/go-i2p/go-meta-listener

Source Tree:

```
go-meta-listener
├── go.sum
├── example
│   └── main.go
├── mirror
│   ├── listener.go
│   ├── metaproxy
│   │   ├── main.go
│   │   └── README.md
│   ├── README.md
│   └── header.go
├── metalistener.go
├── go.mod
├── LICENSE
├── README.md
└── Makefile

```

`/home/idk/go/src/github.com/go-i2p/go-meta-listener/go.sum`:

```````sum
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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/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/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/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=
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=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
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/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=

/home/idk/go/src/github.com/go-i2p/go-meta-listener/example/main.go:

package main

import (
	"context"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/go-i2p/go-meta-listener"
)

func main() {
	// Create a new meta listener
	metaListener := meta.NewMetaListener()
	defer metaListener.Close()

	// Create and add TCP listener
	tcpListener, err := net.Listen("tcp", "127.0.0.1:8082")
	if err != nil {
		log.Fatalf("Failed to create TCP listener: %v", err)
	}
	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:8082")

	// Create and add a Unix socket listener (on Unix systems)
	socketPath := "/tmp/example.sock"
	os.Remove(socketPath) // Clean up from previous runs
	unixListener, err := net.Listen("unix", socketPath)
	if err != nil {
		log.Printf("Failed to create Unix socket listener: %v", err)
	} else {
		if err := metaListener.AddListener("unix", unixListener); err != nil {
			log.Printf("Failed to add Unix socket listener: %v", err)
		} else {
			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{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			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)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)

	go func() {
		log.Println("Server starting, listening on multiple transports")
		if err := server.Serve(metaListener); err != nil && err != http.ErrServerClosed {
			log.Fatalf("HTTP server error: %v", err)
		}
	}()

	// Wait for interrupt signal
	<-stop
	log.Println("Shutting down server...")

	// Create a deadline for graceful shutdown
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// Shut down the HTTP server
	if err := server.Shutdown(ctx); err != nil {
		log.Printf("Server shutdown error: %v", err)
	}

	// Wait for all listener goroutines to exit
	if err := metaListener.WaitForShutdown(ctx); err != nil {
		log.Printf("Timed out waiting for listener shutdown: %v", err)
	}

	log.Println("Server stopped")
}

/home/idk/go/src/github.com/go-i2p/go-meta-listener/mirror/listener.go:

package mirror

import (
	"fmt"
	"log"
	"net"
	"strings"

	"github.com/go-i2p/go-meta-listener"
	"github.com/go-i2p/onramp"

	wileedot "github.com/opd-ai/wileedot"
)

type Mirror struct {
	*meta.MetaListener
	Onions  map[string]*onramp.Onion
	Garlics map[string]*onramp.Garlic
}

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"
	}
	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)
	garlics := make(map[string]*onramp.Garlic)
	onions[port] = onion
	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) {
	log.Println("Starting Mirror Listener")
	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 {
		// check if host is an IP address
		if net.ParseIP(name) == nil {
			// host = "127.0.0.1"
		}
		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
	}
	localAddr := net.JoinHostPort("127.0.0.1", port)
	// Listen on plain HTTP
	tcpListener, err := net.Listen("tcp", localAddr)
	if err != nil {
		return nil, err
	}
	if err := ml.AddListener(port, tcpListener); 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 {
		// make a new onion listener
		// and add it to the map
		log.Println("Creating new onion listener")
		onion, err := onramp.NewOnion(listenerId)
		if err != nil {
			return nil, err
		}
		log.Println("Onion listener created for port", port)
		ml.Onions[port] = onion
	}
	if ml.Garlics[port] == nil {
		// make a new garlic listener
		// and add it to the map
		log.Println("Creating new garlic listener")
		garlic, err := onramp.NewGarlic(listenerId, "127.0.0.1:7656", onramp.OPT_WIDE)
		if err != nil {
			return nil, err
		}
		log.Println("Garlic listener created for port", port)
		ml.Garlics[port] = garlic
	}
	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
		}
		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())
		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
		}
		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())
		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,
			Email:          addr,
		}
		tlsListener, err := wileedot.New(cfg)
		if err != nil {
			return nil, err
		}
		tid := fmt.Sprintf("tls-%s", tlsListener.Addr().String())
		if err := ml.AddListener(tid, tlsListener); err != nil {
			return nil, err
		}
		log.Printf("TLS listener added https://%s\n", tlsListener.Addr())
	}
	return &ml, nil
}

// Listen creates a new Mirror instance and sets up listeners for TLS, Onion, and Garlic.
// It returns the Mirror instance and any error encountered during setup.
// 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) {
	ml, err := NewMirror(name)
	if err != nil {
		return nil, err
	}
	return ml.Listen(name, addr, certdir, hiddenTls)
}

/home/idk/go/src/github.com/go-i2p/go-meta-listener/mirror/metaproxy/main.go:

package main

import (
	"flag"
	"fmt"
	"io"
	"net"

	"github.com/go-i2p/go-meta-listener/mirror"
)

// main function sets up a meta listener that forwards connections to a specified host and port.
// It listens for incoming connections and forwards them to the specified destination.
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", "", "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()
	addr := net.JoinHostPort(*domain, fmt.Sprintf("%d", *listenPort))
	// Create a new meta listener
	metaListener, err := mirror.Listen(addr, *email, *certDir, *hiddenTls)
	if err != nil {
		panic(err)
	}
	defer metaListener.Close()
	// forward all connections recieved on the meta listener to a local host:port
	for {
		conn, err := metaListener.Accept()
		if err != nil {
			panic(err)
		}
		go func() {
			defer conn.Close()
			localConn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", *host, *port))
			if err != nil {
				panic(err)
			}
			defer localConn.Close()
			go io.Copy(localConn, conn)
			io.Copy(conn, localConn)
		}()
	}
}

/home/idk/go/src/github.com/go-i2p/go-meta-listener/mirror/metaproxy/README.md:

# metaproxy - Connection Forwarder

A simple utility that forwards connections from a meta listener to a specified host and port. Part of the `go-i2p/go-meta-listener` toolset.
Automatically forwards a local service to a TLS Service, an I2P Eepsite, and a Tor Onion service at the same time.

## Installation

To install the metaproxy utility, use:

```bash
go install github.com/go-i2p/go-meta-listener/mirror/metaproxy@latest
```

## Usage

```bash
metaproxy [options]
```

### Options

- `-host`: Host to forward connections to (default: "localhost")
- `-port`: Port to forward connections to (default: 8080)
- `-domain`: Domain name for TLS listener (default: "i2pgit.org")
- `-email`: Email address for Let's Encrypt registration (default: "example@example.com")
- `-certdir`: Directory for storing certificates (default: "./certs")
- `-hidden-tls`: Enable hidden TLS (default: false)

## Description

metaproxy creates a meta listener that can accept connections from multiple transport types and forwards them to a specified destination (host:port).
It supports TLS with automatic certificate management through Let's Encrypt, I2P EepSites, and Tor Onion Services.

## Examples

Forward connections to a local web server:
```bash
metaproxy -host localhost -port 3000
```

Forward connections with custom TLS settings:
```bash
metaproxy -domain yourdomain.com -email you@example.com -certdir /etc/certs -port 8443
```

/home/idk/go/src/github.com/go-i2p/go-meta-listener/mirror/README.md:

# Mirror Listener

A network listener implementation that simultaneously listens on clearnet (TLS), Tor onion services, and I2P garlic services.

## Overview

Mirror Listener is a wrapper around the [go-meta-listener](https://github.com/go-i2p/go-meta-listener) package that provides a simplified interface for setting up multi-protocol listeners. It automatically configures:

- TLS-secured clearnet connections with Let's Encrypt certificates
- Tor onion service endpoints
- I2P garlic service endpoints

This allows you to run a single service that's accessible through multiple network layers and protocols.

## Installation

```bash
go get github.com/go-i2p/go-meta-listener/mirror
```

## Usage

```go
import (
    "github.com/go-i2p/go-meta-listener/mirror"
    "net/http"
)

func main() {
    // Create a multi-protocol listener
    listener, err := mirror.Listen(
        "yourdomain.com",        // Domain name for TLS
        "your.email@example.com", // Email for Let's Encrypt
        "./certs",               // Certificate directory
        false,                   // Enable/disable TLS on hidden services
    )
    if err != nil {
        panic(err)
    }
    defer listener.Close()
    
    // Use with standard library
    http.Serve(listener, yourHandler)
}
```

## Configuration Options

- **Domain Name**: Required for TLS certificate issuance through Let's Encrypt
- **Email Address**: Used for Let's Encrypt registration
- **Certificate Directory**: Where TLS certificates will be stored
- **Hidden TLS**: When set to true, enables TLS for Tor and I2P services as well

## Example: Connection Forwarding

See the [example directory](./example) for a complete example of using Mirror Listener to forward connections to a local service.

/home/idk/go/src/github.com/go-i2p/go-meta-listener/mirror/header.go:

package mirror

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"io"
	"log"
	"net"
	"net/http"
	"time"
)

// AddHeaders adds headers to the connection.
// It takes a net.Conn and a map of headers as input.
// 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 {
	// 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 {
		// Not an HTTP request or couldn't parse, return original connection
		return conn
	}

	// Add our headers
	for key, value := range headers {
		req.Header.Add(key, value)
	}

	// 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,
	}
}

// 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.
func (ml *Mirror) Accept() (net.Conn, error) {
	// Accept a connection from the listener
	conn, err := ml.MetaListener.Accept()
	if err != nil {
		log.Println("Error accepting connection:", err)
		return nil, err
	}

	// Check if the connection is a TLS connection
	if tlsConn, ok := conn.(*tls.Conn); ok {
		// If it is a TLS connection, perform the handshake
		if err := tlsConn.Handshake(); err != nil {
			log.Println("Error performing TLS handshake:", err)
			return nil, err
		}
		// If the handshake is successful, get the underlying connection
		//conn = tlsConn.NetConn()
	}

	host := map[string]string{
		"Host":              conn.LocalAddr().String(),
		"X-Forwarded-For":   conn.RemoteAddr().String(),
		"X-Forwarded-Proto": "http",
	}

	// Add headers to the connection
	conn = AddHeaders(conn, host)

	return conn, nil
}

/home/idk/go/src/github.com/go-i2p/go-meta-listener/metalistener.go:

package meta

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net"
	"sync"
	"time"
)

var (
	// ErrListenerClosed is returned when attempting to accept on a closed listener
	ErrListenerClosed = errors.New("listener is closed")
	// ErrNoListeners is returned when the meta listener has no active listeners
	ErrNoListeners = errors.New("no active listeners")
)

// MetaListener implements the net.Listener interface and manages multiple
// underlying network listeners as a unified interface.
type MetaListener struct {
	// listeners is a map of registered listeners with their unique identifiers
	listeners map[string]net.Listener
	// listenerWg tracks active listener goroutines for graceful shutdown
	listenerWg sync.WaitGroup
	// connCh is used to receive connections from all managed listeners
	connCh chan ConnResult
	// closeCh signals all goroutines to stop
	closeCh chan struct{}
	// isClosed indicates whether the meta listener has been closed
	isClosed bool
	// mu protects concurrent access to the listener's state
	mu sync.RWMutex
}

// ConnResult represents a connection received from a listener
type ConnResult struct {
	net.Conn
	src string // source listener ID
}

// NewMetaListener creates a new MetaListener instance ready to manage multiple listeners.
func NewMetaListener() *MetaListener {
	return &MetaListener{
		listeners: make(map[string]net.Listener),
		connCh:    make(chan ConnResult, 100), // Larger buffer for high connection volume
		closeCh:   make(chan struct{}),
	}
}

// AddListener adds a new listener with the specified ID.
// Returns an error if a listener with the same ID already exists or if the
// provided listener is nil.
func (ml *MetaListener) AddListener(id string, listener net.Listener) error {
	if listener == nil {
		return errors.New("cannot add nil listener")
	}

	ml.mu.Lock()
	defer ml.mu.Unlock()

	if ml.isClosed {
		return ErrListenerClosed
	}

	if _, exists := ml.listeners[id]; exists {
		return fmt.Errorf("listener with ID '%s' already exists", id)
	}

	ml.listeners[id] = listener

	// Start a goroutine to handle connections from this listener
	ml.listenerWg.Add(1)
	go ml.handleListener(id, listener)

	return nil
}

// RemoveListener stops and removes the listener with the specified ID.
// Returns an error if no listener with that ID exists.
func (ml *MetaListener) RemoveListener(id string) error {
	ml.mu.Lock()
	defer ml.mu.Unlock()

	listener, exists := ml.listeners[id]
	if !exists {
		return fmt.Errorf("no listener with ID '%s' exists", id)
	}

	// Close the specific listener
	err := listener.Close()
	delete(ml.listeners, id)

	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()
	defer ml.mu.RUnlock()

	ids := make([]string, 0, len(ml.listeners))
	for id := range ml.listeners {
		ids = append(ids, id)
	}

	return ids
}

// Count returns the number of active listeners.
func (ml *MetaListener) Count() int {
	ml.mu.RLock()
	defer ml.mu.RUnlock()

	return len(ml.listeners)
}

// WaitForShutdown blocks until all listener goroutines have exited.
// This is useful for ensuring clean shutdown in server applications.
func (ml *MetaListener) WaitForShutdown(ctx context.Context) error {
	done := make(chan struct{})

	go func() {
		ml.listenerWg.Wait()
		close(done)
	}()

	select {
	case <-done:
		return nil
	case <-ctx.Done():
		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
}

/home/idk/go/src/github.com/go-i2p/go-meta-listener/go.mod:

module github.com/go-i2p/go-meta-listener

go 1.23.5

require (
	github.com/go-i2p/onramp v0.33.92
	github.com/opd-ai/wileedot v0.0.0-20241217172720-521d4175e624
)

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/pkg/errors v0.9.1 // indirect
	github.com/sirupsen/logrus v1.9.3 // 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
)

/home/idk/go/src/github.com/go-i2p/go-meta-listener/LICENSE:

MIT License

Copyright (c) 2025 I2P For Go

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

/home/idk/go/src/github.com/go-i2p/go-meta-listener/README.md:

# go-meta-listener

A Go package that implements a unified network listener interface capable of simultaneously handling connections from multiple underlying transport protocols.

[![Go Reference](https://pkg.go.dev/badge/github.com/go-i2p/go-meta-listener.svg)](https://pkg.go.dev/github.com/go-i2p/go-meta-listener)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Overview

`go-meta-listener` provides a "meta listener" implementation that:
- Manages multiple network listeners through a single interface
- Supports any `net.Listener` implementation (TCP, Unix sockets, TLS, etc.)
- Handles connections and errors from all managed listeners
- Enables graceful shutdown across all listeners

The package also includes a specialized `mirror` implementation for multi-protocol network services supporting:
- TLS-secured clearnet connections
- Tor onion services
- I2P garlic services

## Installation

```bash
# Install core package
go get github.com/go-i2p/go-meta-listener

# For multi-protocol mirror functionality
go get github.com/go-i2p/go-meta-listener/mirror
```

## Basic Usage

```go
package main

import (
    "log"
    "net"
    "net/http"

    "github.com/go-i2p/go-meta-listener"
)

func main() {
    // Create a new meta listener
    metaListener := meta.NewMetaListener()
    defer metaListener.Close()

    // Add a TCP listener
    tcpListener, _ := net.Listen("tcp", ":8080")
    metaListener.AddListener("tcp", tcpListener)

    // Add a TLS listener
    tlsListener, _ := tls.Listen("tcp", ":8443", tlsConfig)
    metaListener.AddListener("tls", tlsListener)

    // Use with standard http server
    http.Serve(metaListener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello from any protocol!"))
    }))
}
```

## Mirror Functionality

The `mirror` package provides a simpler interface for creating services available on clearnet, Tor, and I2P simultaneously:

```go
import "github.com/go-i2p/go-meta-listener/mirror"

// Create a multi-protocol listener
listener, err := mirror.Listen(
    "yourdomain.com",        // Domain name for TLS
    "your.email@example.com", // Email for Let's Encrypt
    "./certs",               // Certificate directory
    false                    // Enable/disable TLS on hidden services
)
defer listener.Close()

// Use with standard library
http.Serve(listener, yourHandler)
```

## Examples

See the [example directory](./example) for complete HTTP server examples and the [mirror/metaproxy directory](./mirror/metaproxy) for multi-protocol connection forwarding.

## License

MIT License - Copyright (c) 2025 I2P For Go

/home/idk/go/src/github.com/go-i2p/go-meta-listener/Makefile:

fmt:
	find . -name '*.go' -exec gofumpt -s -extra -w {} \;

`/home/idk/go/src/github.com/go-i2p/go-meta-listener/mirror/listener.go`:

```````go
package mirror

import (
	"fmt"
	"log"
	"net"
	"strings"

	"github.com/go-i2p/go-meta-listener"
	"github.com/go-i2p/onramp"

	wileedot "github.com/opd-ai/wileedot"
)

type Mirror struct {
	*meta.MetaListener
	Onions  map[string]*onramp.Onion
	Garlics map[string]*onramp.Garlic
}

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"
	}
	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)
	garlics := make(map[string]*onramp.Garlic)
	onions[port] = onion
	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) {
	log.Println("Starting Mirror Listener")
	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 {
		// check if host is an IP address
		if net.ParseIP(name) == nil {
			// host = "127.0.0.1"
		}
		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
	}
	localAddr := net.JoinHostPort("127.0.0.1", port)
	// Listen on plain HTTP
	tcpListener, err := net.Listen("tcp", localAddr)
	if err != nil {
		return nil, err
	}
	if err := ml.AddListener(port, tcpListener); 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 {
		// make a new onion listener
		// and add it to the map
		log.Println("Creating new onion listener")
		onion, err := onramp.NewOnion(listenerId)
		if err != nil {
			return nil, err
		}
		log.Println("Onion listener created for port", port)
		ml.Onions[port] = onion
	}
	if ml.Garlics[port] == nil {
		// make a new garlic listener
		// and add it to the map
		log.Println("Creating new garlic listener")
		garlic, err := onramp.NewGarlic(listenerId, "127.0.0.1:7656", onramp.OPT_WIDE)
		if err != nil {
			return nil, err
		}
		log.Println("Garlic listener created for port", port)
		ml.Garlics[port] = garlic
	}
	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
		}
		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())
		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
		}
		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())
		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,
			Email:          addr,
		}
		tlsListener, err := wileedot.New(cfg)
		if err != nil {
			return nil, err
		}
		tid := fmt.Sprintf("tls-%s", tlsListener.Addr().String())
		if err := ml.AddListener(tid, tlsListener); err != nil {
			return nil, err
		}
		log.Printf("TLS listener added https://%s\n", tlsListener.Addr())
	}
	return &ml, nil
}

// Listen creates a new Mirror instance and sets up listeners for TLS, Onion, and Garlic.
// It returns the Mirror instance and any error encountered during setup.
// 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) {
	ml, err := NewMirror(name)
	if err != nil {
		return nil, err
	}
	return ml.Listen(name, addr, certdir, hiddenTls)
}

/home/idk/go/src/github.com/go-i2p/go-meta-listener/mirror/metaproxy/main.go:

package main

import (
	"flag"
	"fmt"
	"io"
	"net"

	"github.com/go-i2p/go-meta-listener/mirror"
)

// main function sets up a meta listener that forwards connections to a specified host and port.
// It listens for incoming connections and forwards them to the specified destination.
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", "", "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()
	addr := net.JoinHostPort(*domain, fmt.Sprintf("%d", *listenPort))
	// Create a new meta listener
	metaListener, err := mirror.Listen(addr, *email, *certDir, *hiddenTls)
	if err != nil {
		panic(err)
	}
	defer metaListener.Close()
	// forward all connections recieved on the meta listener to a local host:port
	for {
		conn, err := metaListener.Accept()
		if err != nil {
			panic(err)
		}
		go func() {
			defer conn.Close()
			localConn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", *host, *port))
			if err != nil {
				panic(err)
			}
			defer localConn.Close()
			go io.Copy(localConn, conn)
			io.Copy(conn, localConn)
		}()
	}
}

/home/idk/go/src/github.com/go-i2p/go-meta-listener/mirror/metaproxy/README.md:

# metaproxy - Connection Forwarder

A simple utility that forwards connections from a meta listener to a specified host and port. Part of the `go-i2p/go-meta-listener` toolset.
Automatically forwards a local service to a TLS Service, an I2P Eepsite, and a Tor Onion service at the same time.

## Installation

To install the metaproxy utility, use:

```bash
go install github.com/go-i2p/go-meta-listener/mirror/metaproxy@latest
```

## Usage

```bash
metaproxy [options]
```

### Options

- `-host`: Host to forward connections to (default: "localhost")
- `-port`: Port to forward connections to (default: 8080)
- `-domain`: Domain name for TLS listener (default: "i2pgit.org")
- `-email`: Email address for Let's Encrypt registration (default: "example@example.com")
- `-certdir`: Directory for storing certificates (default: "./certs")
- `-hidden-tls`: Enable hidden TLS (default: false)

## Description

metaproxy creates a meta listener that can accept connections from multiple transport types and forwards them to a specified destination (host:port).
It supports TLS with automatic certificate management through Let's Encrypt, I2P EepSites, and Tor Onion Services.

## Examples

Forward connections to a local web server:
```bash
metaproxy -host localhost -port 3000
```

Forward connections with custom TLS settings:
```bash
metaproxy -domain yourdomain.com -email you@example.com -certdir /etc/certs -port 8443
```

/home/idk/go/src/github.com/go-i2p/go-meta-listener/mirror/README.md:

# Mirror Listener

A network listener implementation that simultaneously listens on clearnet (TLS), Tor onion services, and I2P garlic services.

## Overview

Mirror Listener is a wrapper around the [go-meta-listener](https://github.com/go-i2p/go-meta-listener) package that provides a simplified interface for setting up multi-protocol listeners. It automatically configures:

- TLS-secured clearnet connections with Let's Encrypt certificates
- Tor onion service endpoints
- I2P garlic service endpoints

This allows you to run a single service that's accessible through multiple network layers and protocols.

## Installation

```bash
go get github.com/go-i2p/go-meta-listener/mirror
```

## Usage

```go
import (
    "github.com/go-i2p/go-meta-listener/mirror"
    "net/http"
)

func main() {
    // Create a multi-protocol listener
    listener, err := mirror.Listen(
        "yourdomain.com",        // Domain name for TLS
        "your.email@example.com", // Email for Let's Encrypt
        "./certs",               // Certificate directory
        false,                   // Enable/disable TLS on hidden services
    )
    if err != nil {
        panic(err)
    }
    defer listener.Close()
    
    // Use with standard library
    http.Serve(listener, yourHandler)
}
```

## Configuration Options

- **Domain Name**: Required for TLS certificate issuance through Let's Encrypt
- **Email Address**: Used for Let's Encrypt registration
- **Certificate Directory**: Where TLS certificates will be stored
- **Hidden TLS**: When set to true, enables TLS for Tor and I2P services as well

## Example: Connection Forwarding

See the [example directory](./example) for a complete example of using Mirror Listener to forward connections to a local service.

/home/idk/go/src/github.com/go-i2p/go-meta-listener/mirror/header.go:

package mirror

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"io"
	"log"
	"net"
	"net/http"
	"time"
)

// AddHeaders adds headers to the connection.
// It takes a net.Conn and a map of headers as input.
// 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 {
	// 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 {
		// Not an HTTP request or couldn't parse, return original connection
		return conn
	}

	// Add our headers
	for key, value := range headers {
		req.Header.Add(key, value)
	}

	// 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,
	}
}

// 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.
func (ml *Mirror) Accept() (net.Conn, error) {
	// Accept a connection from the listener
	conn, err := ml.MetaListener.Accept()
	if err != nil {
		log.Println("Error accepting connection:", err)
		return nil, err
	}

	// Check if the connection is a TLS connection
	if tlsConn, ok := conn.(*tls.Conn); ok {
		// If it is a TLS connection, perform the handshake
		if err := tlsConn.Handshake(); err != nil {
			log.Println("Error performing TLS handshake:", err)
			return nil, err
		}
		// If the handshake is successful, get the underlying connection
		//conn = tlsConn.NetConn()
	}

	host := map[string]string{
		"Host":              conn.LocalAddr().String(),
		"X-Forwarded-For":   conn.RemoteAddr().String(),
		"X-Forwarded-Proto": "http",
	}

	// Add headers to the connection
	conn = AddHeaders(conn, host)

	return conn, nil
}

/home/idk/go/src/github.com/go-i2p/go-meta-listener/metalistener.go:

package meta

import (
	"context"
	"errors"
	"fmt"
	"log"
	"net"
	"sync"
	"time"
)

var (
	// ErrListenerClosed is returned when attempting to accept on a closed listener
	ErrListenerClosed = errors.New("listener is closed")
	// ErrNoListeners is returned when the meta listener has no active listeners
	ErrNoListeners = errors.New("no active listeners")
)

// MetaListener implements the net.Listener interface and manages multiple
// underlying network listeners as a unified interface.
type MetaListener struct {
	// listeners is a map of registered listeners with their unique identifiers
	listeners map[string]net.Listener
	// listenerWg tracks active listener goroutines for graceful shutdown
	listenerWg sync.WaitGroup
	// connCh is used to receive connections from all managed listeners
	connCh chan ConnResult
	// closeCh signals all goroutines to stop
	closeCh chan struct{}
	// isClosed indicates whether the meta listener has been closed
	isClosed bool
	// mu protects concurrent access to the listener's state
	mu sync.RWMutex
}

// ConnResult represents a connection received from a listener
type ConnResult struct {
	net.Conn
	src string // source listener ID
}

// NewMetaListener creates a new MetaListener instance ready to manage multiple listeners.
func NewMetaListener() *MetaListener {
	return &MetaListener{
		listeners: make(map[string]net.Listener),
		connCh:    make(chan ConnResult, 100), // Larger buffer for high connection volume
		closeCh:   make(chan struct{}),
	}
}

// AddListener adds a new listener with the specified ID.
// Returns an error if a listener with the same ID already exists or if the
// provided listener is nil.
func (ml *MetaListener) AddListener(id string, listener net.Listener) error {
	if listener == nil {
		return errors.New("cannot add nil listener")
	}

	ml.mu.Lock()
	defer ml.mu.Unlock()

	if ml.isClosed {
		return ErrListenerClosed
	}

	if _, exists := ml.listeners[id]; exists {
		return fmt.Errorf("listener with ID '%s' already exists", id)
	}

	ml.listeners[id] = listener

	// Start a goroutine to handle connections from this listener
	ml.listenerWg.Add(1)
	go ml.handleListener(id, listener)

	return nil
}

// RemoveListener stops and removes the listener with the specified ID.
// Returns an error if no listener with that ID exists.
func (ml *MetaListener) RemoveListener(id string) error {
	ml.mu.Lock()
	defer ml.mu.Unlock()

	listener, exists := ml.listeners[id]
	if !exists {
		return fmt.Errorf("no listener with ID '%s' exists", id)
	}

	// Close the specific listener
	err := listener.Close()
	delete(ml.listeners, id)

	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()
	defer ml.mu.RUnlock()

	ids := make([]string, 0, len(ml.listeners))
	for id := range ml.listeners {
		ids = append(ids, id)
	}

	return ids
}

// Count returns the number of active listeners.
func (ml *MetaListener) Count() int {
	ml.mu.RLock()
	defer ml.mu.RUnlock()

	return len(ml.listeners)
}

// WaitForShutdown blocks until all listener goroutines have exited.
// This is useful for ensuring clean shutdown in server applications.
func (ml *MetaListener) WaitForShutdown(ctx context.Context) error {
	done := make(chan struct{})

	go func() {
		ml.listenerWg.Wait()
		close(done)
	}()

	select {
	case <-done:
		return nil
	case <-ctx.Done():
		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
}

/home/idk/go/src/github.com/go-i2p/go-meta-listener/go.mod:

module github.com/go-i2p/go-meta-listener

go 1.23.5

require (
	github.com/go-i2p/onramp v0.33.92
	github.com/opd-ai/wileedot v0.0.0-20241217172720-521d4175e624
)

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/pkg/errors v0.9.1 // indirect
	github.com/sirupsen/logrus v1.9.3 // 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
)

/home/idk/go/src/github.com/go-i2p/go-meta-listener/LICENSE:

MIT License

Copyright (c) 2025 I2P For Go

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

/home/idk/go/src/github.com/go-i2p/go-meta-listener/README.md:

# go-meta-listener

A Go package that implements a unified network listener interface capable of simultaneously handling connections from multiple underlying transport protocols.

[![Go Reference](https://pkg.go.dev/badge/github.com/go-i2p/go-meta-listener.svg)](https://pkg.go.dev/github.com/go-i2p/go-meta-listener)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Overview

`go-meta-listener` provides a "meta listener" implementation that:
- Manages multiple network listeners through a single interface
- Supports any `net.Listener` implementation (TCP, Unix sockets, TLS, etc.)
- Handles connections and errors from all managed listeners
- Enables graceful shutdown across all listeners

The package also includes a specialized `mirror` implementation for multi-protocol network services supporting:
- TLS-secured clearnet connections
- Tor onion services
- I2P garlic services

## Installation

```bash
# Install core package
go get github.com/go-i2p/go-meta-listener

# For multi-protocol mirror functionality
go get github.com/go-i2p/go-meta-listener/mirror
```

## Basic Usage

```go
package main

import (
    "log"
    "net"
    "net/http"

    "github.com/go-i2p/go-meta-listener"
)

func main() {
    // Create a new meta listener
    metaListener := meta.NewMetaListener()
    defer metaListener.Close()

    // Add a TCP listener
    tcpListener, _ := net.Listen("tcp", ":8080")
    metaListener.AddListener("tcp", tcpListener)

    // Add a TLS listener
    tlsListener, _ := tls.Listen("tcp", ":8443", tlsConfig)
    metaListener.AddListener("tls", tlsListener)

    // Use with standard http server
    http.Serve(metaListener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello from any protocol!"))
    }))
}
```

## Mirror Functionality

The `mirror` package provides a simpler interface for creating services available on clearnet, Tor, and I2P simultaneously:

```go
import "github.com/go-i2p/go-meta-listener/mirror"

// Create a multi-protocol listener
listener, err := mirror.Listen(
    "yourdomain.com",        // Domain name for TLS
    "your.email@example.com", // Email for Let's Encrypt
    "./certs",               // Certificate directory
    false                    // Enable/disable TLS on hidden services
)
defer listener.Close()

// Use with standard library
http.Serve(listener, yourHandler)
```

## Examples

See the [example directory](./example) for complete HTTP server examples and the [mirror/metaproxy directory](./mirror/metaproxy) for multi-protocol connection forwarding.

## License

MIT License - Copyright (c) 2025 I2P For Go

/home/idk/go/src/github.com/go-i2p/go-meta-listener/Makefile:

fmt:
	find . -name '*.go' -exec gofumpt -s -extra -w {} \;