lanshare

package
v0.9.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 12, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Overview

Package lanshare implements Share2Us's offline, account-free, direct peer-to-peer file transfer over a LAN / Tailscale / WireGuard / any reachable IP. It never touches the Share2Us cloud, relay, or TURN — this is the "actual guest mode": two machines, one TLS 1.3 connection, no login.

Security model (layered):

  • Transport is always TLS 1.3 (confidentiality + integrity + forward secrecy).
  • Password auth: a PAKE (schollz/pake) bound to the TLS exporter keying material — MITM-proof and immune to offline dictionary attack even on a weak password.
  • QR / trusted-peer auth: the sender pins the receiver's self-signed cert SHA-256 fingerprint (delivered out-of-band via the QR / saved config).
  • --allow-ip: source-IP allowlist (auth by network identity); TLS still provides confidentiality. Active on-path MITM on an untrusted L2 is out of scope for this mode (callers are warned).

Index

Constants

View Source
const (
	AccessAll     = "all"     // anyone nearby can download
	AccessTrusted = "trusted" // only devices IsTrusted reports true for
	AccessApprove = "approve" // the broadcaster approves each download (OnRequest)
)

Access modes for a broadcast (who may pull the offered file).

View Source
const (
	ModePassword = "password"
	ModeAllowIP  = "allow-ip"
	ModeOpen     = "open"
)

Auth modes reported in ListenInfo.Mode.

View Source
const DefaultPassphraseWords = 10

DefaultPassphraseWords is the number of diceware words in an auto-generated receive passphrase. Ten EFF-large-list words is ~129 bits of entropy, which makes the PAKE's password path immune to any offline guessing.

View Source
const (
	// DefaultPort is the first port a receiver tries when none is pinned.
	DefaultPort = 4300
)
View Source
const PairingScheme = "s2u"

PairingScheme is the URL scheme for a pairing string. A pairing string bundles everything a sender needs for a one-scan/one-paste transfer: the address, the receiver's cert fingerprint (pinned to defeat MITM), and, in password mode, the passphrase (the string is shown on the receiver's own screen, so whoever can read it to scan/paste it is already trusted).

Variables

This section is empty.

Functions

func Advertise(instance string, info ListenInfo) (io.Closer, error)

Advertise announces a live receiver on the local network via mDNS so a sender can find it by name (`--dest <name>`). The TXT record carries the cert fingerprint and mode, but never the passphrase — password-mode receivers are still discovered, but the sender must supply the password out-of-band.

func AdvertiseBroadcast added in v0.8.0

func AdvertiseBroadcast(displayName string, info ListenInfo, fileName string, fileSize int64) (io.Closer, error)

AdvertiseBroadcast announces an offered file (pull) on the local network. It registers under a distinct mDNS instance (so it never collides with a device's receive advert) but carries the clean display name in the "dn" TXT key, plus bc/fn/sz so a browser can show "<device> is broadcasting <file>".

func Broadcast added in v0.8.0

func Broadcast(ctx context.Context, opts BroadcastOptions) error

Broadcast opens a listener, advertises the file, and serves it to downloaders until ctx is cancelled. It reuses the same TLS + framing + identity plumbing as Send/Receive, in the pull direction.

func BuildPairingString

func BuildPairingString(host string, info ListenInfo) string

BuildPairingString encodes a pairing string for a live receiver. host should be the address a sender can reach (e.g. the primary LAN / Tailscale IP).

func GeneratePassphrase

func GeneratePassphrase(n int) (string, error)

GeneratePassphrase returns a space-free, hyphen-joined diceware passphrase of n words drawn from the EFF large word list using crypto/rand. It is used when a receiver is opened without an explicit -p/--password and without -np.

func IdentityFingerprint added in v0.7.0

func IdentityFingerprint(pub ed25519.PublicKey) string

IdentityFingerprint is the stable lowercase hex SHA-256 of an Ed25519 identity public key — the value a receiver stores/pins in its trusted-devices list and (via VerifyCode) shows as a 6-digit code.

func IsPairingString

func IsPairingString(s string) bool

IsPairingString reports whether s looks like a pairing string.

func Send

func Send(ctx context.Context, name string, size int64, isDir bool, body io.Reader, opts SendOptions) (string, error)

Send streams name/size/body to a receiver at opts.Dest. IsDir marks that body is a zip of a directory (the receiver may extract it). It returns the peer's reported SHA-256 on success.

func SweepStalePartials added in v0.8.0

func SweepStalePartials(dir string, maxAge time.Duration)

SweepStalePartials removes .s2u-partial-* files in dir older than maxAge, so abandoned resumes do not accumulate. Best-effort.

func VerifyCode added in v0.6.0

func VerifyCode(fingerprint string) string

VerifyCode derives a short, human-comparable 6-digit numeric code from a receiver's certificate fingerprint. Both ends compute the SAME code from the same certificate, so a sender can confirm — by comparing the code shown on the receiver's own screen — that a device advertised over (unauthenticated) mDNS is the real one and not an impersonator, whose different certificate yields a different code. Formatted "NNN NNN".

Six digits is a deliberate usability choice: it stops casual impersonation and sending to the wrong device, layered under the receiver's per-transfer approval. It is NOT a full safety-number compare — a determined attacker could grind a certificate whose fingerprint matches a target's 6-digit code.

Types

type BroadcastOptions added in v0.8.0

type BroadcastOptions struct {
	Path     string // file to serve
	Name     string // advertised name (defaults to filepath.Base(Path))
	Bind     string // listen interface ("" = all)
	Port     int    // 0 = auto-scan
	Instance string // mDNS instance (device) name

	Identity ed25519.PrivateKey // broadcaster identity (advertised + proven)
	Access   string             // AccessAll | AccessTrusted | AccessApprove

	// IsTrusted reports whether a downloader fingerprint is trusted (AccessTrusted).
	IsTrusted func(fingerprint string) bool
	// OnRequest approves a specific download (AccessApprove). RequestInfo carries
	// the downloader's verified key + name + the file name/size.
	OnRequest func(RequestInfo) bool
	// OnListen fires once the listener is up with the sender-facing details.
	OnListen func(ListenInfo)
	// OnConn reports per-connection progress/lifecycle for the live stats UI.
	OnConn func(ConnEvent)

	HandshakeTimeout time.Duration
}

BroadcastOptions configures serving one file for download ("pull"). The broadcaster advertises the file over mDNS and streams it to each downloader, gated by Access. Identity authenticates the broadcaster so downloaders can trust the source (and get the verify code); for trusted/approve access the downloader must authenticate too.

type ConnEvent added in v0.8.0

type ConnEvent struct {
	PeerIP   string
	PeerKey  []byte // verified downloader identity ("" if anonymous)
	PeerName string
	Sent     int64
	Total    int64
	Done     bool   // transfer completed
	Err      string // non-empty if the connection failed
}

ConnEvent is a broadcast connection lifecycle/progress update.

type DownloadOptions added in v0.8.0

type DownloadOptions struct {
	Dest             string
	PinFingerprint   string
	Name             string
	Size             int64
	DestDir          string
	Identity         ed25519.PrivateKey
	DownloaderName   string
	Overwrite        bool
	DialTimeout      time.Duration
	HandshakeTimeout time.Duration
	OnProgress       func(received, total int64)
}

DownloadOptions configures pulling a broadcast file. Name/Size come from the advertisement; PinFingerprint (the broadcaster's cert fingerprint from the advert) authenticates the source and is REQUIRED. Identity authenticates the downloader (needed for trusted/approve broadcasts). Interrupted downloads resume automatically from a kept partial keyed by (PinFingerprint|Name|Size).

type ListenInfo

type ListenInfo struct {
	BindAddr    string
	Port        int
	Fingerprint string // self-signed cert SHA-256 (for QR / pairing)
	Passphrase  string // effective password in password mode; "" otherwise
	Mode        string // ModePassword | ModeAllowIP | ModeOpen
}

ListenInfo describes a live receiver.

type PairingInfo

type PairingInfo struct {
	Host        string
	Port        int
	Fingerprint string
	Password    string
}

PairingInfo is the decoded content of a pairing string.

func Discover

func Discover(ctx context.Context, name string, timeout time.Duration) (PairingInfo, error)

Discover browses the local network for a receiver whose instance name matches name (case-insensitive) and returns its address + fingerprint. Password is never carried over mDNS, so PairingInfo.Password is always empty here.

func ParsePairingString

func ParsePairingString(s string) (PairingInfo, error)

ParsePairingString decodes a pairing string produced by BuildPairingString.

func (PairingInfo) Addr

func (p PairingInfo) Addr() string

Addr returns host:port.

type Peer added in v0.6.0

type Peer struct {
	Name        string // device display name
	Host        string // reachable IP
	Port        int
	Fingerprint string // cert SHA-256 (for pinning); "" if not advertised
	Mode        string // ModePassword | ModeAllowIP | ModeOpen
	IsBroadcast bool   // true when this advert is an offered file (pull)
	FileName    string // broadcast: offered file name
	FileSize    int64  // broadcast: offered file size
}

Peer is a Share2Us endpoint discovered on the local network — either a receiver (can be sent to) or a broadcaster (IsBroadcast: offering FileName for download).

func Browse added in v0.6.0

func Browse(ctx context.Context, timeout time.Duration) ([]Peer, error)

Browse lists every Share2Us receiver advertising on the local network until timeout elapses, de-duplicated by instance name and sorted by name. It powers a "nearby devices" picker (Discover finds one by name; Browse finds them all).

func (Peer) Addr added in v0.6.0

func (p Peer) Addr() string

Addr returns host:port.

type ReceiveOptions

type ReceiveOptions struct {
	// Bind is the interface address to listen on ("" = all interfaces).
	Bind string
	// Port pins the listen port. 0 = auto-scan DefaultPort..portRangeEnd. A
	// pinned port that is unavailable is a hard error (no fallback).
	Port int
	// Password sets an explicit receive password (PAKE). Empty + !NoPassword +
	// no AllowIPs => a passphrase is auto-generated.
	Password string
	// NoPassword opens the receiver with no password (caller should warn).
	NoPassword bool
	// AllowIPs restricts accepted source IPs. With AllowIPs and no password, the
	// mode is allow-ip (network-identity auth).
	AllowIPs []string
	// TrustedIPs are source IPs whose inbound transfers are auto-accepted even
	// when a password is set for everyone else (trust-by-IP; caller warns).
	TrustedIPs []string
	// DestDir is where files land (default ~/s2u, created if missing).
	DestDir string
	// Overwrite permits replacing an existing destination file.
	Overwrite bool
	// HandshakeTimeout bounds per-connection setup (default 30s).
	HandshakeTimeout time.Duration
	// OnListen fires once the listener is up, before accepting.
	OnListen func(ListenInfo)
	// OnProgress fires as bytes arrive.
	OnProgress func(received, total int64)
	// OnRequest, if set, is consulted after a sender authenticates but before the
	// transfer is accepted, with the sender IP and declared file name/size.
	// Returning false declines it (the sender is told; the receiver keeps
	// listening). This drives an interactive accept/reject prompt for discovered
	// / open receivers (abuse control for "nearby devices" sharing).
	OnRequest func(RequestInfo) bool
	// Loop keeps the receiver running after each completed transfer instead of
	// returning, so one listener accepts many files (a persistent "serve" mode
	// for a discoverable device). OnReceived fires per completed transfer.
	Loop       bool
	OnReceived func(ReceiveResult)
}

ReceiveOptions configures a single inbound transfer.

type ReceiveResult

type ReceiveResult struct {
	Name      string
	Path      string
	Bytes     int64
	SHA256    string
	PeerIP    string
	SenderKey []byte
}

ReceiveResult reports a completed transfer. SenderKey is the peer's verified Ed25519 identity public key (populated on a broadcast Download from the broadcaster's proof; nil otherwise).

func Download added in v0.8.0

func Download(ctx context.Context, opts DownloadOptions) (ReceiveResult, error)

Download pulls the broadcast file at opts.Dest into DestDir, resuming from any kept partial. It verifies the whole-file SHA the broadcaster reports.

func Receive

func Receive(ctx context.Context, opts ReceiveOptions) (ReceiveResult, error)

Receive opens a listener, accepts connections until one completes a full authenticated transfer, writes the file atomically into DestDir, and returns. Connections that fail allow-ip, TLS, auth, or local checks are closed and the listener keeps waiting (so junk/probe connections cannot abort a receive).

type RequestInfo added in v0.6.0

type RequestInfo struct {
	PeerIP     string
	Name       string
	Size       int64
	IsDir      bool
	SenderKey  []byte
	SenderName string
}

RequestInfo describes an inbound transfer an authenticated sender is offering, passed to ReceiveOptions.OnRequest for an accept/reject decision. SenderKey is the sender's verified Ed25519 identity public key (nil for an anonymous sender); SenderName is its self-declared display label (cosmetic — trust must key off IdentityFingerprint(SenderKey), never the name).

type SendOptions

type SendOptions struct {
	// Dest is host or host:port. When no port is given, DefaultPort is used.
	Dest string
	// Password, when non-empty, drives the PAKE. Leave empty for an allow-ip /
	// open receiver.
	Password string
	// PinFingerprint pins the receiver's self-signed cert SHA-256 fingerprint
	// (from a QR / pairing string / trusted-device entry). Empty = unpinned.
	PinFingerprint string
	// DialTimeout / HandshakeTimeout bound connection setup.
	DialTimeout      time.Duration
	HandshakeTimeout time.Duration
	// OnProgress, if set, is called as bytes are sent.
	OnProgress func(sent, total int64)
	// Identity, if set, authenticates the sender: it signs the TLS channel
	// binding with this Ed25519 key and sends the public key, so the receiver can
	// recognise / trust this device by its key fingerprint. SenderName is a
	// display label shown in the receiver's approval prompt / trusted-devices list.
	Identity   ed25519.PrivateKey
	SenderName string
}

SendOptions configures a direct LAN/overlay send. The caller resolves the source (zipping a folder to a temp file first) and supplies its name + size.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL