Initial commit

This commit is contained in:
eyedeekay
2025-04-30 22:02:58 -04:00
parent c19af99b17
commit de96df9e64
2 changed files with 261 additions and 0 deletions

111
client.go Normal file
View File

@ -0,0 +1,111 @@
package metadialer
import (
"crypto/tls"
"crypto/x509"
"net/http"
"net/url"
"strings"
"time"
)
// MetaHTTPClient is an HTTP client that skips TLS verification for .i2p and .onion domains
// but performs standard verification for all other domains.
type MetaHTTPClient struct {
*http.Client
}
// NewMetaHTTPClient creates a new client with special handling for .i2p and .onion domains.
// It accepts an optional root CA pool for custom certificate authorities.
func NewMetaHTTPClient(rootCAs *x509.CertPool) *MetaHTTPClient {
// Create a custom transport with our special TLS config
transport := &http.Transport{
Dial: Dial,
TLSClientConfig: &tls.Config{
RootCAs: rootCAs, // May be nil, which will use system default
VerifyConnection: func(state tls.ConnectionState) error {
// Skip verification for .onion and .i2p domains
domain := state.ServerName
if strings.HasSuffix(domain, ".onion") || strings.HasSuffix(domain, ".i2p") {
// Skip verification for these special domains
return nil
}
// Use standard verification for all other domains
opts := x509.VerifyOptions{
DNSName: state.ServerName,
Intermediates: x509.NewCertPool(),
}
for _, cert := range state.PeerCertificates[1:] {
opts.Intermediates.AddCert(cert)
}
_, err := state.PeerCertificates[0].Verify(opts)
return err
},
},
// Set reasonable defaults
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableCompression: false,
}
return &MetaHTTPClient{
Client: &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
},
}
}
// Get is a convenience method for making GET requests
func (c *MetaHTTPClient) Get(url string) (*http.Response, error) {
return c.Client.Get(url)
}
// Post is a convenience method for making POST requests
func (c *MetaHTTPClient) Post(url, contentType string, body interface{}) (*http.Response, error) {
return c.Client.Post(url, contentType, nil) // Replace nil with actual body handling
}
// PostForm is a convenience method for making POST requests with form data
func (c *MetaHTTPClient) PostForm(url string, data url.Values) (*http.Response, error) {
return c.Client.PostForm(url, data)
}
// Do is a convenience method for making arbitrary HTTP requests
func (c *MetaHTTPClient) Do(req *http.Request) (*http.Response, error) {
return c.Client.Do(req)
}
// Head is a convenience method for making HEAD requests
func (c *MetaHTTPClient) Head(url string) (*http.Response, error) {
return c.Client.Head(url)
}
// CloseIdleConnections closes any idle connections in the transport
func (c *MetaHTTPClient) CloseIdleConnections() {
c.Client.CloseIdleConnections()
}
// HTTPClient returns the underlying http.Client.
// This is useful for accessing the raw client if needed.
// It is anticipated that this will be necessary for some use cases.
// For example, to configure the default dialer for the application:
//
// http.DefaultClient = client.HTTPClient()
func (c *MetaHTTPClient) HTTPClient() *http.Client {
return c.Client
}
/*
// init initializes the MetaHTTPClient and sets it as the default HTTP client.
// This would be called when the application starts up, if not for being commented out here.
// It is commented out to avoid side effects during package initialization.
You can copy this code to your main package or wherever you want to initialize the client.
func init() {
// Initialize the MetaHTTPClient with default settings
client := NewMetaHTTPClient(nil)
// Set the default HTTP client to our custom client
http.DefaultClient = client.HTTPClient()
}
*/

150
dialer.go Normal file
View File

@ -0,0 +1,150 @@
package metadialer
import (
"context"
"crypto/rand"
"fmt"
"net"
"net/url"
"strings"
"github.com/go-i2p/onramp"
)
var (
// Garlic and Onion are the dialers for I2P and onion connections respectively.
// Garlic is used for I2P connections and Onion is used for onion connections.
// GarlicErr and OnionErr are the errors returned by the dialers.
// It is important to `defer close` the dialers when you include them in your code.
// Otherwise your SAMv3 or Tor sessions may leak. onramp tries to fix it for you but do it anyway.
// in your `main` function, do:
// defer Garlic.Close()
// defer Onion.Close()
Garlic, GarlicErr = onramp.NewGarlic(fmt.Sprintf("metadialer-%s", randomString()), "127.0.0.1:7656", onramp.OPT_DEFAULTS)
Onion, OnionErr = onramp.NewOnion(fmt.Sprintf("metadialer-%s", randomString()))
)
// randomString generates a random string of 4 characters.
// It uses the crypto/rand package to generate a secure random byte slice,
// which is then converted to a string using a custom alphabet.
// The generated string is suitable for use as a unique identifier or token.
func randomString() string {
// Define the alphabet for the random string
const alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// Create a byte slice to hold the random bytes
b := make([]byte, 4)
// Generate secure random bytes
if _, err := rand.Read(b); err != nil {
return ""
}
// Convert the random bytes to a string using the custom alphabet
for i := range b {
b[i] = alphabet[b[i]%byte(len(alphabet))]
}
return string(b)
}
// ANON is a flag to indicate whether to use the onion dialer for all non-I2P connections.
// If true, all non-I2P connections will be routed through the onion dialer.
// If false, regular connection will be made directly.
// Default is true.
var ANON = true
// Dial is a custom dialer that handles .i2p and .onion domains differently.
// It uses the garlic dialer for .i2p domains and the onion dialer for .onion domains.
// For all other domains, it uses the default dialer.
// If ANON is true, it will use the onion dialer for all non-I2P connections.
// It returns a net.Conn interface for the connection.
// If the address is invalid or the connection fails, it returns an error.
// The network parameter is ignored for onion connections.
var Dial = func(network, addr string) (net.Conn, error) {
return dialHelper(network, addr)
}
// DialContext is a custom dialer that handles .i2p and .onion domains differently.
// It uses the garlic dialer for .i2p domains and the onion dialer for .onion domains.
// For all other domains, it uses the default dialer.
// If ANON is true, it will use the onion dialer for all non-I2P connections.
// It returns a net.Conn interface for the connection.
// If the address is invalid or the connection fails, it returns an error.
// The network parameter is ignored for onion connections.
// It accepts a context.Context parameter for cancellation and timeout.
// The context is ignored.
var DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialHelper(network, addr)
}
func dialHelper(network, addr string) (net.Conn, error) {
// convert the addr to a URL
tld, err := GetTLD(addr)
if err != nil {
return nil, err
}
switch tld {
case "i2p":
if GarlicErr != nil {
return nil, GarlicErr
}
// I2P is a special case, we need to use the garlic dialer
return Garlic.Dial(network, addr)
case "onion":
if OnionErr != nil {
return nil, OnionErr
}
// make sure it is a TCP connection
if network != "tcp" && network != "tcp4" && network != "tcp6" && network != "onion" {
return nil, net.InvalidAddrError("only TCP connections are supported")
}
// Onion is a special case, we need to use the onion dialer
return Onion.Dial("onion", addr)
default:
// If ANON is true, we need to use the onion dialer
if ANON {
if OnionErr != nil {
return nil, OnionErr
}
// make sure it is a TCP connection
if network != "tcp" {
return nil, net.InvalidAddrError("only TCP connections are supported")
}
// ANON is a special case, we need to use the onion dialer
return Onion.Dial(network, addr)
} else {
// For everything else, we can use the default dialer
return net.Dial(network, addr)
}
}
}
// GetTLD is a helper function that returns the top-level domain of the given address.
// It takes a string address as input, which can be a fully qualified domain name or a URL.
// If the address does not include a scheme, "http://" is added by default.
// It returns the top-level domain as a string or an error if the address is invalid.
// The function also checks if the domain is an IP address and returns "ip" in that case.
// If there is no top-level domain found, it returns the entire domain as the TLD.
// The function is useful for determining the type of domain (I2P, onion, or regular) for routing purposes.
// It uses the net package to parse the address and extract the hostname.
func GetTLD(addr string) (string, error) {
// Add a default scheme if missing
if !strings.Contains(addr, "://") {
addr = "http://" + addr
}
url, err := url.Parse(addr)
if err != nil {
return "", err
}
domain := url.Hostname()
if domain == "" {
return "", net.InvalidAddrError("invalid address: no hostname found")
}
// Check if the domain is an IP address
if net.ParseIP(domain) != nil {
return "ip", nil
}
lastDot := strings.LastIndex(domain, ".")
if lastDot == -1 || lastDot == len(domain)-1 {
return domain, nil
}
tld := domain[lastDot+1:]
return tld, nil
}