Documentation
¶
Overview ¶
Package lease provides persistent, address-limited routes for long transfers. A lease pins setup requests and transfer bytes to one HTTP client, learns throughput as bytes arrive, and can reserve a faster route for resumption.
Index ¶
- Constants
- Variables
- type Client
- type Configuration
- type ExitProber
- type HTTPClient
- func (c *HTTPClient) CloseIdleConnections()
- func (c *HTTPClient) DoOnce(req *http.Request) (*http.Response, error)
- func (c *HTTPClient) NewRequest(ctx context.Context, method, url string, body io.Reader) (*http.Request, error)
- func (c *HTTPClient) ThroughProxy(endpoint string) (Client, error)
- func (c *HTTPClient) WithRoute(ctx context.Context, _, _ string, observe func(Observation)) context.Context
- type Lease
- func (l *Lease) Address() string
- func (l *Lease) ClaimExit(exit string) bool
- func (l *Lease) Claimed() bool
- func (l *Lease) Configured() bool
- func (l *Lease) Constrain()
- func (l *Lease) Context(ctx context.Context) context.Context
- func (l *Lease) Cooldown(until time.Time)
- func (l *Lease) Endpoint() string
- func (l *Lease) Exit() string
- func (l *Lease) Failed(err error, elapsed time.Duration, transport bool)
- func (l *Lease) ID() string
- func (l *Lease) Observe(o Observation)
- func (l *Lease) ProbeContext(ctx context.Context) context.Context
- func (l *Lease) Progress(now time.Time, bytes int64, elapsed time.Duration, limited bool) bool
- func (l *Lease) Release()
- func (l *Lease) Upgrade(now time.Time, remaining int64, currentRate float64) *Lease
- type Observation
- type Option
- type Page
- type Policy
- type Pool
- func (p *Pool) Acquire(site, preferred string, files int) *Lease
- func (p *Pool) AcquireFor(site, preferred string, files int, remaining int64, waf bool) *Lease
- func (p *Pool) Close() error
- func (p *Pool) Configure(c Configuration, discovery bool)
- func (p *Pool) DetectEgress(ctx context.Context, d proxytron.Detector, endpoint string) (proxytron.Egress, error)
- func (p *Pool) Egress(endpoint string) (proxytron.Egress, bool)
- func (p *Pool) Page(site string, q Query) Page
- func (p *Pool) Refresh(ctx context.Context)
- func (p *Pool) RefusedDirect(site string, err error, elapsed time.Duration)
- func (p *Pool) SetDemand(routes map[string]int)
- func (p *Pool) Start(ctx context.Context)
- type Query
- type Row
- type SourceView
- type Summary
Examples ¶
Constants ¶
const Direct = proxytron.Direct
Direct identifies an explicitly configured direct route.
Variables ¶
var ( // ErrExitInUse means the observed exit is reserved or cooling for this scope. ErrExitInUse = errors.New("proxytron: exit address is in use or cooling") // ErrExitUnknown means no configured probe returned a usable address. ErrExitUnknown = errors.New("proxytron: could not determine the route's exit") // ErrExitProbeFailed means probes work independently but not on this route. ErrExitProbeFailed = errors.New("proxytron: route did not pass exit probes") // ErrInactiveLease means the lease was released or its pool was closed. ErrInactiveLease = errors.New("proxytron: lease is no longer active") )
Functions ¶
This section is empty.
Types ¶
type Client ¶
type Client interface {
ThroughProxy(string) (Client, error)
NewRequest(context.Context, string, string, io.Reader) (*http.Request, error)
DoOnce(*http.Request) (*http.Response, error)
WithRoute(context.Context, string, string, func(Observation)) context.Context
CloseIdleConnections()
}
Client adapts an application's HTTP stack to a lease. ThroughProxy must isolate cookies and connections and must never silently bypass the route. WithRoute pins subsequent requests to the client and reports observations. Its string arguments are the route ID and application scope, respectively.
type Configuration ¶
Configuration is immutable once published. ParseConfiguration owns its slices.
func ParseConfiguration ¶
func ParseConfiguration(endpoints, feeds []string) (Configuration, error)
ParseConfiguration validates and deduplicates endpoints and HTTP(S) feeds. Schemeless endpoints use HTTP; SOCKS5 entries use remote destination DNS.
type ExitProber ¶ added in v0.2.0
type ExitProber struct {
URLs []string
Timeout time.Duration // Per probe; nonpositive uses Detector's default.
// IsResponseError optionally recognizes an adapter's HTTP response errors.
// Such errors try the next URL; other I/O errors stop immediately. Standard
// Detector response errors are recognized without this callback.
IsResponseError func(error) bool
// Reachable optionally checks the probes independently after all replies
// were unusable. True confirms a route failure, which is scored once;
// false permits proceeding with an unknown exit during a probe outage.
// An error stops without a penalty. Nil fails with ErrExitUnknown without
// penalizing the route. The caller chooses the diagnostic route, bounds its
// I/O with ctx, and may cache/coalesce checks. Direct checks are never implicit.
Reachable func(context.Context) (bool, error)
}
ExitProber confirms a lease's public address before destination work starts. URLs are tried in order, through the lease's existing client and session. It owns no clients or goroutines and is safe to share if its configuration is not mutated and its callbacks are concurrency-safe. Serialize Claim calls on any one lease, just as the destination setup using that lease is serialized.
func (ExitProber) Claim ¶ added in v0.2.0
Claim probes and atomically claims the observed exit using Lease.ClaimExit. A fresh claim is reused; a cached identity still requires confirmation. An empty URL list disables probing. A nil error with an invalid IP means probing was disabled or Reachable explicitly allowed an outage. No destination request is sent, no lease is released, and no route is silently replaced with Direct. The caller must release the lease after its work, including on a Claim error.
ProbeContext reports transport outcomes through the Client adapter; responses from the probe service do not penalize the destination scope. A confirmed unusable route returns ErrExitProbeFailed; a shared or cooling exit returns ErrExitInUse without a health penalty. Errors may also wrap the last probe error.
Example ¶
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"github.com/JohanLindvall/proxytron/lease"
)
func main() {
// A loopback proxy supplies a synthetic IP-check response in this example.
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_, _ = io.WriteString(w, "198.51.100.8")
}))
defer proxy.Close()
dir, err := os.MkdirTemp("", "proxytron-example-")
if err != nil {
panic(err)
}
defer os.RemoveAll(dir)
pool, err := lease.Open(filepath.Join(dir, "leases.db"), []string{proxy.URL}, nil, nil, nil)
if err != nil {
panic(err)
}
defer pool.Close()
route := pool.Acquire("downloads", "", 1)
if route == nil {
panic("no route available")
}
defer route.Release()
prober := lease.ExitProber{URLs: []string{"http://ip-check.example.test/ip"}}
identity, err := prober.Claim(context.Background(), route)
if err != nil {
panic(err)
}
fmt.Println(identity.IP, route.Claimed())
// Destination requests now use route.Context(ctx) until route.Release().
Output:
type HTTPClient ¶
type HTTPClient struct {
// contains filtered or unexported fields
}
HTTPClient is the standard net/http adapter. Applications with custom TLS fingerprints or challenge handling can implement Client instead.
func NewHTTPClient ¶
func NewHTTPClient(client *http.Client) *HTTPClient
NewHTTPClient uses client for feed downloads. Nil selects a standard client.
func (*HTTPClient) CloseIdleConnections ¶
func (c *HTTPClient) CloseIdleConnections()
CloseIdleConnections releases cached connections.
func (*HTTPClient) DoOnce ¶
DoOnce performs one request, respecting a lease context and reporting the complete body read. Intentional partial closes and cancellation are ignored.
func (*HTTPClient) NewRequest ¶
func (c *HTTPClient) NewRequest(ctx context.Context, method, url string, body io.Reader) (*http.Request, error)
NewRequest constructs a context-bound request.
func (*HTTPClient) ThroughProxy ¶
func (c *HTTPClient) ThroughProxy(endpoint string) (Client, error)
ThroughProxy creates an isolated standard transport and cookie jar.
func (*HTTPClient) WithRoute ¶
func (c *HTTPClient) WithRoute(ctx context.Context, _, _ string, observe func(Observation)) context.Context
WithRoute pins the standard client to the request context.
type Lease ¶
type Lease struct {
// contains filtered or unexported fields
}
Lease reserves one slot for a service on one route, across aliases and jobs.
func (*Lease) ClaimExit ¶
ClaimExit records exit as the public address the lease's route leaves by, and claims it for the lease. It refuses when other leases hold that exit for the service, as many as it allows an address, or the service's host has it cooling. The caller then gives the route back, and the pool passes over the route while that lasts.
func (*Lease) Claimed ¶
Claimed reports whether a fresh observation has confirmed this lease's exit. A cached identity reserves capacity but still permits a fresh probe.
func (*Lease) Configured ¶
Configured checks a reserved replacement again after a live source edit. Existing requests can finish on removed routes; a not-yet-used upgrade should not start a new request there.
func (*Lease) Constrain ¶
func (l *Lease) Constrain()
Constrain also covers transfers that finish between progress ticks: a user-imposed pause or speed ceiling must not train their EOF average.
func (*Lease) Cooldown ¶
Cooldown records an authoritative per-address host timer without treating it as a dead proxy. Other services can keep using the endpoint.
It is written on this route alone. The address's other tried ports take it up where routes are chosen (addressCooling), and those never tried may leave through another public address altogether (untested). Writing it on each port was a synced transaction per port, under the pool's lock: on a gateway of thousands, seconds in which no route could be leased.
func (*Lease) Endpoint ¶
Endpoint returns the selected route, including credentials. Use it only to establish the connection; inventory pages already redact credentials.
func (*Lease) Exit ¶
Exit is the address the route's requests were seen to leave by, once claimed; empty before then, or when no echo service answered.
func (*Lease) Failed ¶
Failed reports failures recognised above HTTP: a stalled media stream or a web page served in place of the file. Callers exclude user cancellation.
func (*Lease) Observe ¶
func (l *Lease) Observe(o Observation)
Observe records an application's classified outcome for this lease. It does not release the lease. The caller should exclude user cancellation.
func (*Lease) ProbeContext ¶
ProbeContext is Context for a question put to a third party through the route, such as which address it leaves by. The route answers for carrying the request: failing to, a dial or a dropped connection, counts against it as on any request, and an answer shows it alive. What the third party says back does not count, a refusal or an outage of its own, since that is not the service's host speaking about the route's address.
func (*Lease) Progress ¶
Progress learns from newly written bytes, never the resumed prefix. The caller samples the file counter after its baseline has been reset. Pauses and an imposed speed ceiling reset the window rather than training the proxy to look slow. No database I/O occurs on this path. The result says a complete measurement window is ready for reconsidering the route. Setup includes CAPTCHA and ticket waits before the first bytes.
func (*Lease) Release ¶
func (l *Lease) Release()
Release frees the reservation exactly once. Finish requests first.
func (*Lease) Upgrade ¶
Upgrade reserves a proven alternative only after sustained evidence that the remaining bytes will finish sooner there, including a new ticket and failure recovery. The caller stops its old request and resumes on this lease, or releases the reservation if cancellation/completion wins. Reservation and comparison share the pool lock, so two slow transfers cannot both abandon their route in favour of the same free address.
type Observation ¶
type Observation struct {
Duration time.Duration
Bytes int64
Status int
Err error
RetryAfter time.Time
Challenged bool
}
Observation describes one attempt. Applications classify challenges and authoritative retry deadlines; the pool owns scoring and cooldowns.
type Option ¶
type Option func(*Pool)
Option configures a lease pool at Open time.
func WithChallengeClassifier ¶
WithChallengeClassifier recognizes an application's challenge errors, including failures reported through Failed or RefusedDirect.
func WithExitTTL ¶
WithExitTTL sets how long a measured exit is trusted for admission decisions. Nonpositive values retain the default of one hour. Freshly claimed leases continue holding their exit until released, even after this interval.
func WithFeedFilter ¶
WithFeedFilter restricts discovered routes to those the application's HTTP adapter supports. Returning false excludes the endpoint. Static endpoints should be validated by the application's configuration boundary.
func WithHealthEpoch ¶
WithHealthEpoch enables an explicit application-owned health migration. Opening a store from another epoch forgets scores and identities, retaining inventory. Omit this option to preserve health regardless of epoch metadata.
func WithPolicy ¶
WithPolicy installs service policy without hard-coding product names.
type Page ¶
type Page struct {
Rows []Row `json:"rows"`
Total int `json:"total"`
Offset int `json:"offset"`
Limit int `json:"limit"`
Summary Summary `json:"summary"`
Sources []SourceView `json:"sources"`
}
Page reports a bounded inventory view and aggregate counts.
type Policy ¶
Policy identifies caller-defined service scopes. RecoveryScopes may borrow measurements from other scopes. Bandwidth from CappedScopes is excluded because a service's own limit does not measure the proxy's capacity.
type Pool ¶
type Pool struct {
// contains filtered or unexported fields
}
Pool owns the Bolt database and never stores active leases. A restart may reuse an idle route, but must still honour its persisted host cooldown.
func Open ¶
func Open(path string, endpoints, feeds []string, client Client, log *slog.Logger, opts ...Option) (*Pool, error)
Open restores inventory before any network request. Invalid configuration and a locked/corrupt database fail once at startup, without replacing it.
func (*Pool) Acquire ¶
Acquire never waits or writes to disk. A preferred route lets a paused or interrupted file reuse its unexpired, IP-bound ticket when it is healthy.
func (*Pool) AcquireFor ¶
AcquireFor ranks routes for the bytes this file still needs. A short remaining tail values setup latency more than a long transfer does. Selection and reservation share one lock: unrated and preferred routes obey the same per-address limit, starting before the first request.
func (*Pool) Configure ¶
func (p *Pool) Configure(c Configuration, discovery bool)
Configure publishes a validated configuration without disk or network I/O. Existing leases keep their client even when their endpoint is removed. The background refresh persists newly added inventory and fetches new feeds.
func (*Pool) DetectEgress ¶
func (p *Pool) DetectEgress(ctx context.Context, d proxytron.Detector, endpoint string) (proxytron.Egress, error)
DetectEgress records the real exit IP and groups leases and service cooldowns by that address. Different gateways can share an exit. Detection runs outside the pool lock; a failed check preserves the previous identity. For rotating gateways the caller should check again when its sticky session changes.
func (*Pool) Egress ¶
Egress returns the last measured identity, false when it has never been checked.
func (*Pool) Page ¶
Page reports recent measurements and evaluates the selector's score at the mean success probability. Reading the UI never draws from the selector's random source or changes its priors. Endpoint credentials are redacted.
func (*Pool) Refresh ¶
Refresh retains surviving health and each failed source's last good list. Routine attempts are a day apart, including across restarts or failures. Earlier top-ups require runnable demand and too few usable, scored routes, and are at least five minutes apart. Network I/O holds neither the pool lock nor a Bolt transaction.
func (*Pool) RefusedDirect ¶
RefusedDirect records a challenge encountered before a file opted into routing. The pool must not immediately select the same refused address. No lease was held, so this observation does not alter active counts.
func (*Pool) SetDemand ¶
SetDemand publishes every service's runnable route demand atomically, so discovery cannot act on a mix of old and new concurrency allocations. An empty map suppresses shortage-driven refreshes while idle or paused. Demand is not persisted: restoring a pool does not mean a queue is ready.
type Query ¶
Query bounds the independent inventory endpoint. Large feeds never ride along with queue snapshots, and sorting/filtering happens on the server.
type Row ¶
type Row struct {
ID string `json:"id"`
URL string `json:"url"`
Source string `json:"source"`
Status string `json:"status"`
Score float64 `json:"score"`
Throughput float64 `json:"throughput"`
CurrentSpeed float64 `json:"currentSpeed"`
SuccessRate float64 `json:"successRate"`
Requests int `json:"requests"`
WAFRequests int `json:"wafRequests,omitempty"`
WAFSuccess float64 `json:"wafSuccess,omitempty"`
Active int `json:"active"`
CooldownUntil time.Time `json:"cooldownUntil,omitzero"`
LastSuccess time.Time `json:"lastSuccess,omitzero"`
}
Row reports a credential-redacted inventory entry and its measurements.