This commit is contained in:
qaq
2025-06-17 12:17:33 +08:00
parent 4a6b2c8e0f
commit 9fa968d07c
648 changed files with 64968 additions and 1621 deletions
+22
View File
@@ -0,0 +1,22 @@
package ctx
import (
"context"
xrecorder "github.com/go-gost/x/recorder"
)
type recorderObjectCtxKey struct{}
var (
ctxKeyRecorderObject = &recorderObjectCtxKey{}
)
func ContextWithRecorderObject(ctx context.Context, ro *xrecorder.HandlerRecorderObject) context.Context {
return context.WithValue(ctx, ctxKeyRecorderObject, ro)
}
func RecorderObjectFromContext(ctx context.Context) *xrecorder.HandlerRecorderObject {
v, _ := ctx.Value(ctxKeyRecorderObject).(*xrecorder.HandlerRecorderObject)
return v
}
+43
View File
@@ -0,0 +1,43 @@
package io
import (
"io"
"time"
)
type readWriter struct {
io.Reader
io.Writer
}
func NewReadWriter(r io.Reader, w io.Writer) io.ReadWriter {
return &readWriter{
Reader: r,
Writer: w,
}
}
type readWriteCloser struct {
io.Reader
io.Writer
io.Closer
}
func NewReadWriteCloser(r io.Reader, w io.Writer, c io.Closer) io.ReadWriteCloser {
return &readWriteCloser{
Reader: r,
Writer: w,
Closer: c,
}
}
type setReadDeadline interface {
SetReadDeadline(t time.Time) error
}
func SetReadDeadline(rw io.ReadWriter, t time.Time) error {
if v, _ := rw.(setReadDeadline); v != nil {
return v.SetReadDeadline(t)
}
return nil
}
+49
View File
@@ -0,0 +1,49 @@
package loader
import (
"bufio"
"bytes"
"context"
"io"
"os"
)
type fileLoader struct {
filename string
}
// FileLoader loads data from file.
func FileLoader(filename string) Loader {
return &fileLoader{
filename: filename,
}
}
func (l *fileLoader) Load(ctx context.Context) (io.Reader, error) {
data, err := os.ReadFile(l.filename)
if err != nil {
return nil, err
}
return bytes.NewReader(data), nil
}
// List implements Lister interface{}
func (l *fileLoader) List(ctx context.Context) (list []string, err error) {
f, err := os.Open(l.filename)
if err != nil {
return
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
list = append(list, scanner.Text())
}
err = scanner.Err()
return
}
func (l *fileLoader) Close() error {
return nil
}
+71
View File
@@ -0,0 +1,71 @@
package loader
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"time"
)
type httpLoaderOptions struct {
timeout time.Duration
}
type HTTPLoaderOption func(opts *httpLoaderOptions)
func TimeoutHTTPLoaderOption(timeout time.Duration) HTTPLoaderOption {
return func(opts *httpLoaderOptions) {
opts.timeout = timeout
}
}
type httpLoader struct {
url string
httpClient *http.Client
}
// HTTPLoader loads data from HTTP request.
func HTTPLoader(url string, opts ...HTTPLoaderOption) Loader {
var options httpLoaderOptions
for _, opt := range opts {
if opt != nil {
opt(&options)
}
}
return &httpLoader{
url: url,
httpClient: &http.Client{
Timeout: options.timeout,
},
}
}
func (l *httpLoader) Load(ctx context.Context) (io.Reader, error) {
req, err := http.NewRequest(http.MethodGet, l.url, nil)
if err != nil {
return nil, err
}
resp, err := l.httpClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("%d %s", resp.StatusCode, resp.Status)
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return bytes.NewReader(data), nil
}
func (l *httpLoader) Close() error {
return nil
}
+19
View File
@@ -0,0 +1,19 @@
package loader
import (
"context"
"io"
)
type Loader interface {
Load(context.Context) (io.Reader, error)
Close() error
}
type Lister interface {
List(ctx context.Context) ([]string, error)
}
type Mapper interface {
Map(ctx context.Context) (map[string]string, error)
}
+242
View File
@@ -0,0 +1,242 @@
package loader
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"github.com/go-redis/redis/v8"
)
const (
DefaultRedisKey = "gost"
)
type redisLoaderOptions struct {
db int
username string
password string
key string
}
type RedisLoaderOption func(opts *redisLoaderOptions)
func DBRedisLoaderOption(db int) RedisLoaderOption {
return func(opts *redisLoaderOptions) {
opts.db = db
}
}
func UsernameRedisLoaderOption(username string) RedisLoaderOption {
return func(opts *redisLoaderOptions) {
opts.username = username
}
}
func PasswordRedisLoaderOption(password string) RedisLoaderOption {
return func(opts *redisLoaderOptions) {
opts.password = password
}
}
func KeyRedisLoaderOption(key string) RedisLoaderOption {
return func(opts *redisLoaderOptions) {
opts.key = key
}
}
type redisStringLoader struct {
client *redis.Client
key string
}
// RedisStringLoader loads data from redis string.
func RedisStringLoader(addr string, opts ...RedisLoaderOption) Loader {
var options redisLoaderOptions
for _, opt := range opts {
if opt != nil {
opt(&options)
}
}
key := options.key
if key == "" {
key = DefaultRedisKey
}
return &redisStringLoader{
client: redis.NewClient(&redis.Options{
Addr: addr,
Password: options.password,
DB: options.db,
}),
key: key,
}
}
func (p *redisStringLoader) Load(ctx context.Context) (io.Reader, error) {
v, err := p.client.Get(ctx, p.key).Bytes()
if err != nil {
return nil, err
}
return bytes.NewReader(v), nil
}
func (p *redisStringLoader) Close() error {
return p.client.Close()
}
type redisSetLoader struct {
client *redis.Client
key string
}
// RedisSetLoader loads data from redis set.
func RedisSetLoader(addr string, opts ...RedisLoaderOption) Loader {
var options redisLoaderOptions
for _, opt := range opts {
if opt != nil {
opt(&options)
}
}
key := options.key
if key == "" {
key = DefaultRedisKey
}
return &redisSetLoader{
client: redis.NewClient(&redis.Options{
Addr: addr,
Password: options.password,
DB: options.db,
}),
key: key,
}
}
func (p *redisSetLoader) Load(ctx context.Context) (io.Reader, error) {
v, err := p.List(ctx)
if err != nil {
return nil, err
}
return bytes.NewReader([]byte(strings.Join(v, "\n"))), nil
}
// List implements Lister interface{}
func (p *redisSetLoader) List(ctx context.Context) ([]string, error) {
return p.client.SMembers(ctx, p.key).Result()
}
func (p *redisSetLoader) Close() error {
return p.client.Close()
}
type redisListLoader struct {
client *redis.Client
key string
}
// RedisListLoader loads data from redis list.
func RedisListLoader(addr string, opts ...RedisLoaderOption) Loader {
var options redisLoaderOptions
for _, opt := range opts {
opt(&options)
}
key := options.key
if key == "" {
key = DefaultRedisKey
}
return &redisListLoader{
client: redis.NewClient(&redis.Options{
Addr: addr,
Password: options.password,
DB: options.db,
}),
key: key,
}
}
func (p *redisListLoader) Load(ctx context.Context) (io.Reader, error) {
v, err := p.List(ctx)
if err != nil {
return nil, err
}
return bytes.NewReader([]byte(strings.Join(v, "\n"))), nil
}
// List implements Lister interface{}
func (p *redisListLoader) List(ctx context.Context) ([]string, error) {
return p.client.LRange(ctx, p.key, 0, -1).Result()
}
func (p *redisListLoader) Close() error {
return p.client.Close()
}
type redisHashLoader struct {
client *redis.Client
key string
}
// RedisHashLoader loads data from redis hash.
func RedisHashLoader(addr string, opts ...RedisLoaderOption) Loader {
var options redisLoaderOptions
for _, opt := range opts {
opt(&options)
}
key := options.key
if key == "" {
key = DefaultRedisKey
}
return &redisHashLoader{
client: redis.NewClient(&redis.Options{
Addr: addr,
Password: options.password,
DB: options.db,
}),
key: key,
}
}
func (p *redisHashLoader) Load(ctx context.Context) (io.Reader, error) {
m, err := p.Map(ctx)
if err != nil {
return nil, err
}
var b strings.Builder
for k, v := range m {
fmt.Fprintf(&b, "%s %s\n", k, v)
}
return bytes.NewBufferString(b.String()), nil
}
// List implements Lister interface{}
func (p *redisHashLoader) List(ctx context.Context) (list []string, err error) {
m, err := p.Map(ctx)
if err != nil {
return
}
for k, v := range m {
list = append(list, fmt.Sprintf("%s %s", k, v))
}
return
}
// Map implements Mapper interface{}
func (p *redisHashLoader) Map(ctx context.Context) (map[string]string, error) {
return p.client.HGetAll(ctx, p.key).Result()
}
func (p *redisHashLoader) Close() error {
return p.client.Close()
}
+261
View File
@@ -0,0 +1,261 @@
package matcher
import (
"net"
"net/netip"
"strconv"
"strings"
xnet "github.com/go-gost/x/internal/net"
"github.com/gobwas/glob"
"github.com/yl2chen/cidranger"
)
// Matcher is a generic pattern matcher,
// it gives the match result of the given pattern for specific v.
type Matcher interface {
Match(v string) bool
}
type ipMatcher struct {
ips map[string]struct{}
}
// IPMatcher creates a Matcher with a list of IP addresses.
func IPMatcher(ips []net.IP) Matcher {
matcher := &ipMatcher{
ips: make(map[string]struct{}),
}
for _, ip := range ips {
matcher.ips[ip.String()] = struct{}{}
}
return matcher
}
func (m *ipMatcher) Match(ip string) bool {
if m == nil || len(m.ips) == 0 {
return false
}
_, ok := m.ips[ip]
return ok
}
type addrMatcher struct {
addrs map[string]*xnet.PortRange
}
// AddrMatcher creates a Matcher with a list of HOST:PORT addresses.
// the host can be an IP (e.g. 192.168.1.1) address, a plain domain such as 'example.com',
// or a special pattern '.example.com' that matches 'example.com'
// and any subdomain 'abc.example.com', 'def.abc.example.com' etc.
// The PORT can be a single port number or port range MIN-MAX(e.g. 0-65535).
func AddrMatcher(addrs []string) Matcher {
matcher := &addrMatcher{
addrs: make(map[string]*xnet.PortRange),
}
for _, addr := range addrs {
host, port, _ := net.SplitHostPort(addr)
if host == "" {
matcher.addrs[addr] = nil
continue
}
pr := &xnet.PortRange{}
if err := pr.Parse(port); err != nil {
pr = nil
}
matcher.addrs[host] = pr
}
return matcher
}
func (m *addrMatcher) Match(addr string) bool {
if m == nil || len(m.addrs) == 0 {
return false
}
host, sp, _ := net.SplitHostPort(addr)
if host == "" {
host = addr
}
port, _ := strconv.Atoi(sp)
if pr, ok := m.addrs[host]; ok {
if pr == nil || pr.Contains(port) {
return true
}
}
if pr, ok := m.addrs["."+host]; ok {
if pr == nil || pr.Contains(port) {
return true
}
}
for {
if index := strings.IndexByte(host, '.'); index > 0 {
if pr, ok := m.addrs[host[index:]]; ok {
if pr == nil || pr.Contains(port) {
return true
}
}
host = host[index+1:]
continue
}
break
}
return false
}
type cidrMatcher struct {
ranger cidranger.Ranger
}
// CIDRMatcher creates a Matcher for a list of CIDR notation IP addresses.
func CIDRMatcher(inets []*net.IPNet) Matcher {
ranger := cidranger.NewPCTrieRanger()
for _, inet := range inets {
ranger.Insert(cidranger.NewBasicRangerEntry(*inet))
}
return &cidrMatcher{ranger: ranger}
}
func (m *cidrMatcher) Match(ip string) bool {
if m == nil || m.ranger == nil {
return false
}
if netIP := net.ParseIP(ip); netIP != nil {
b, _ := m.ranger.Contains(netIP)
return b
}
return false
}
type domainMatcher struct {
domains map[string]struct{}
}
// DomainMatcher creates a Matcher for a list of domains,
// the domain should be a plain domain such as 'example.com',
// or a special pattern '.example.com' that matches 'example.com'
// and any subdomain 'abc.example.com', 'def.abc.example.com' etc.
func DomainMatcher(domains []string) Matcher {
matcher := &domainMatcher{
domains: make(map[string]struct{}),
}
for _, domain := range domains {
matcher.domains[domain] = struct{}{}
}
return matcher
}
func (m *domainMatcher) Match(domain string) bool {
if m == nil || len(m.domains) == 0 {
return false
}
if _, ok := m.domains[domain]; ok {
return true
}
if _, ok := m.domains["."+domain]; ok {
return true
}
for {
if index := strings.IndexByte(domain, '.'); index > 0 {
if _, ok := m.domains[domain[index:]]; ok {
return true
}
domain = domain[index+1:]
continue
}
break
}
return false
}
type wildcardMatcherPattern struct {
glob glob.Glob
pr *xnet.PortRange
}
type wildcardMatcher struct {
patterns []wildcardMatcherPattern
}
// WildcardMatcher creates a Matcher for a specific wildcard domain pattern,
// the pattern can be a wildcard such as '*.exmaple.com', '*.example.com:80', or '*.example.com:0-65535'
func WildcardMatcher(patterns []string) Matcher {
matcher := &wildcardMatcher{}
for _, pattern := range patterns {
host, port, _ := net.SplitHostPort(pattern)
if host == "" {
host = pattern
}
pr := &xnet.PortRange{}
if err := pr.Parse(port); err != nil {
pr = nil
}
matcher.patterns = append(matcher.patterns, wildcardMatcherPattern{
glob: glob.MustCompile(host),
pr: pr,
})
}
return matcher
}
func (m *wildcardMatcher) Match(addr string) bool {
if m == nil || len(m.patterns) == 0 {
return false
}
host, sp, _ := net.SplitHostPort(addr)
if host == "" {
host = addr
}
port, _ := strconv.Atoi(sp)
for _, pattern := range m.patterns {
if pattern.glob.Match(host) {
if pattern.pr == nil || pattern.pr.Contains(port) {
return true
}
}
}
return false
}
type ipRangeMatcher struct {
ranges []xnet.IPRange
}
func IPRangeMatcher(ranges []xnet.IPRange) Matcher {
matcher := &ipRangeMatcher{
ranges: ranges,
}
return matcher
}
func (m *ipRangeMatcher) Match(addr string) bool {
if m == nil || len(m.ranges) == 0 {
return false
}
host, _, _ := net.SplitHostPort(addr)
if host == "" {
host = addr
}
adr, err := netip.ParseAddr(host)
if err != nil {
return false
}
for _, ra := range m.ranges {
if ra.Contains(adr) {
return true
}
}
return false
}
+199
View File
@@ -0,0 +1,199 @@
package net
import (
"fmt"
"net"
"net/netip"
"strconv"
"strings"
)
func ParseInterfaceAddr(ifceName, network string) (ifce string, addr []net.Addr, err error) {
if ifceName == "" {
addr = append(addr, nil)
return
}
ip := net.ParseIP(ifceName)
if ip == nil {
var ife *net.Interface
ife, err = net.InterfaceByName(ifceName)
if err != nil {
return
}
var addrs []net.Addr
addrs, err = ife.Addrs()
if err != nil {
return
}
if len(addrs) == 0 {
err = fmt.Errorf("addr not found for interface %s", ifceName)
return
}
ifce = ifceName
for _, addr_ := range addrs {
if ipNet, ok := addr_.(*net.IPNet); ok {
addr = append(addr, ipToAddr(ipNet.IP, network))
}
}
} else {
ifce, err = findInterfaceByIP(ip)
if err != nil {
return
}
addr = []net.Addr{ipToAddr(ip, network)}
}
return
}
func ipToAddr(ip net.IP, network string) (addr net.Addr) {
port := 0
switch network {
case "tcp", "tcp4", "tcp6":
addr = &net.TCPAddr{IP: ip, Port: port}
return
case "udp", "udp4", "udp6":
addr = &net.UDPAddr{IP: ip, Port: port}
return
default:
addr = &net.IPAddr{IP: ip}
return
}
}
func findInterfaceByIP(ip net.IP) (string, error) {
ifces, err := net.Interfaces()
if err != nil {
return "", err
}
for _, ifce := range ifces {
addrs, _ := ifce.Addrs()
if len(addrs) == 0 {
continue
}
for _, addr := range addrs {
ipAddr, _ := addr.(*net.IPNet)
if ipAddr == nil {
continue
}
// logger.Default().Infof("%s-%s", ipAddr, ip)
if ipAddr.IP.Equal(ip) {
return ifce.Name, nil
}
}
}
return "", nil
}
// AddrPortRange is the network address with port range supported.
// e.g. 192.168.1.1:0-65535
type AddrPortRange string
func (p AddrPortRange) Addrs() (addrs []string) {
// ignore url scheme, e.g. http://, tls://, tcp://.
if strings.Contains(string(p), "://") {
return nil
}
h, sp, err := net.SplitHostPort(string(p))
if err != nil {
return nil
}
pr := PortRange{}
pr.Parse(sp)
for i := pr.Min; i <= pr.Max; i++ {
addrs = append(addrs, net.JoinHostPort(h, strconv.Itoa(i)))
}
return addrs
}
// Port range is a range of port list.
type PortRange struct {
Min int
Max int
}
// Parse parses the s to PortRange.
// The s can be a single port number and will be converted to port range port-port.
func (pr *PortRange) Parse(s string) error {
minmax := strings.Split(s, "-")
switch len(minmax) {
case 1:
port, err := strconv.Atoi(s)
if err != nil {
return err
}
if port < 0 || port > 65535 {
return fmt.Errorf("invalid port: %s", s)
}
pr.Min, pr.Max = port, port
return nil
case 2:
min, err := strconv.Atoi(minmax[0])
if err != nil {
return err
}
max, err := strconv.Atoi(minmax[1])
if err != nil {
return err
}
pr.Min, pr.Max = min, max
return nil
default:
return fmt.Errorf("invalid port range: %s", s)
}
}
func (pr *PortRange) Contains(port int) bool {
return port >= pr.Min && port <= pr.Max
}
type IPRange struct {
Min netip.Addr
Max netip.Addr
}
func (r *IPRange) Parse(s string) error {
minmax := strings.Split(s, "-")
switch len(minmax) {
case 1:
addr, err := netip.ParseAddr(strings.TrimSpace(s))
if err != nil {
return err
}
r.Min, r.Max = addr, addr
return nil
case 2:
min, err := netip.ParseAddr(strings.TrimSpace(minmax[0]))
if err != nil {
return err
}
max, err := netip.ParseAddr(strings.TrimSpace(minmax[1]))
if err != nil {
return err
}
r.Min, r.Max = min, max
return nil
default:
return fmt.Errorf("invalid ip range: %s", s)
}
}
func (r *IPRange) Contains(addr netip.Addr) bool {
return !(addr.Less(r.Min) || r.Max.Less(addr))
}
type ClientAddr interface {
ClientAddr() net.Addr
}
+180
View File
@@ -0,0 +1,180 @@
package dialer
import (
"context"
"fmt"
"net"
"runtime"
"strings"
"syscall"
"time"
"github.com/go-gost/core/logger"
ctxvalue "github.com/go-gost/x/ctx"
xnet "github.com/go-gost/x/internal/net"
"github.com/vishvananda/netns"
)
const (
DefaultTimeout = 10 * time.Second
)
var (
DefaultNetDialer = &Dialer{}
)
type Dialer struct {
Interface string
Netns string
Mark int
DialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
Logger logger.Logger
}
func (d *Dialer) Dial(ctx context.Context, network, addr string) (conn net.Conn, err error) {
if d == nil {
d = DefaultNetDialer
}
log := d.Logger
if log == nil {
log = logger.Default()
}
log = log.WithFields(map[string]any{
"sid": ctxvalue.SidFromContext(ctx),
})
if d.Netns != "" {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
originNs, err := netns.Get()
if err != nil {
return nil, fmt.Errorf("netns.Get(): %v", err)
}
defer netns.Set(originNs)
var ns netns.NsHandle
if strings.HasPrefix(d.Netns, "/") {
ns, err = netns.GetFromPath(d.Netns)
} else {
ns, err = netns.GetFromName(d.Netns)
}
if err != nil {
return nil, fmt.Errorf("netns.Get(%s): %v", d.Netns, err)
}
defer ns.Close()
if err := netns.Set(ns); err != nil {
return nil, fmt.Errorf("netns.Set(%s): %v", d.Netns, err)
}
}
if d.DialFunc != nil {
return d.DialFunc(ctx, network, addr)
}
switch network {
case "unix":
netd := net.Dialer{}
return netd.DialContext(ctx, network, addr)
default:
}
ifces := strings.Split(d.Interface, ",")
for _, ifce := range ifces {
strict := strings.HasSuffix(ifce, "!")
ifce = strings.TrimSuffix(ifce, "!")
var ifceName string
var ifAddrs []net.Addr
ifceName, ifAddrs, err = xnet.ParseInterfaceAddr(ifce, network)
if err != nil && strict {
return
}
for _, ifAddr := range ifAddrs {
conn, err = d.dialOnce(ctx, network, addr, ifceName, ifAddr, log)
if err == nil {
return
}
log.Debugf("dial %s %v@%s failed: %s", network, ifAddr, ifceName, err)
if strict &&
!strings.Contains(err.Error(), "no suitable address found") &&
!strings.Contains(err.Error(), "mismatched local address type") {
return
}
}
}
return
}
func (d *Dialer) dialOnce(ctx context.Context, network, addr, ifceName string, ifAddr net.Addr, log logger.Logger) (net.Conn, error) {
if ifceName != "" {
log.Debugf("interface: %s %v/%s", ifceName, ifAddr, network)
}
switch network {
case "udp", "udp4", "udp6":
if addr == "" {
var laddr *net.UDPAddr
if ifAddr != nil {
laddr, _ = ifAddr.(*net.UDPAddr)
}
c, err := net.ListenUDP(network, laddr)
if err != nil {
return nil, err
}
sc, err := c.SyscallConn()
if err != nil {
log.Error(err)
return nil, err
}
err = sc.Control(func(fd uintptr) {
if ifceName != "" {
if err := bindDevice(fd, ifceName); err != nil {
log.Warnf("bind device: %v", err)
}
}
if d.Mark != 0 {
if err := setMark(fd, d.Mark); err != nil {
log.Warnf("set mark: %v", err)
}
}
})
if err != nil {
log.Error(err)
}
return c, nil
}
case "tcp", "tcp4", "tcp6":
default:
return nil, fmt.Errorf("dial: unsupported network %s", network)
}
netd := net.Dialer{
LocalAddr: ifAddr,
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
if ifceName != "" {
if err := bindDevice(fd, ifceName); err != nil {
log.Warnf("bind device: %v", err)
}
}
if d.Mark != 0 {
if err := setMark(fd, d.Mark); err != nil {
log.Warnf("set mark: %v", err)
}
}
})
},
}
if d.Netns != "" {
// https://github.com/golang/go/issues/44922#issuecomment-796645858
netd.FallbackDelay = -1
}
return netd.DialContext(ctx, network, addr)
}
@@ -0,0 +1,19 @@
package dialer
import (
"golang.org/x/sys/unix"
)
func bindDevice(fd uintptr, ifceName string) error {
if ifceName == "" {
return nil
}
return unix.BindToDevice(int(fd), ifceName)
}
func setMark(fd uintptr, mark int) error {
if mark == 0 {
return nil
}
return unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_MARK, mark)
}
@@ -0,0 +1,11 @@
//go:build !linux
package dialer
func bindDevice(fd uintptr, ifceName string) error {
return nil
}
func setMark(fd uintptr, mark int) error {
return nil
}
+72
View File
@@ -0,0 +1,72 @@
package http
import (
"bytes"
"io"
"net"
"net/http"
"strings"
)
func GetClientIP(req *http.Request) net.IP {
if req == nil {
return nil
}
// cloudflare CDN
sip := req.Header.Get("CF-Connecting-IP")
if sip == "" {
ss := strings.Split(req.Header.Get("X-Forwarded-For"), ",")
if len(ss) > 0 && ss[0] != "" {
sip = ss[0]
}
}
if sip == "" {
sip = req.Header.Get("X-Real-Ip")
}
return net.ParseIP(sip)
}
type Body struct {
r io.ReadCloser
buf bytes.Buffer
length int64
recordSize int
}
func NewBody(r io.ReadCloser, maxRecordSize int) *Body {
p := &Body{
r: r,
recordSize: maxRecordSize,
}
return p
}
func (p *Body) Read(b []byte) (n int, err error) {
n, err = p.r.Read(b)
p.length += int64(n)
if p.recordSize > 0 {
b = b[:n]
if n > p.recordSize {
b = b[:p.recordSize]
}
p.buf.Write(b)
p.recordSize -= n
}
return
}
func (p *Body) Close() error {
return p.r.Close()
}
func (p *Body) Content() []byte {
return p.buf.Bytes()
}
func (p *Body) Length() int64 {
return p.length
}
+26
View File
@@ -0,0 +1,26 @@
package ip
import (
"fmt"
"github.com/songgao/water/waterutil"
)
var prots = map[waterutil.IPProtocol]string{
waterutil.HOPOPT: "HOPOPT",
waterutil.ICMP: "ICMP",
waterutil.IGMP: "IGMP",
waterutil.GGP: "GGP",
waterutil.TCP: "TCP",
waterutil.UDP: "UDP",
waterutil.IPv6_Route: "IPv6-Route",
waterutil.IPv6_Frag: "IPv6-Frag",
waterutil.IPv6_ICMP: "IPv6-ICMP",
}
func Protocol(p waterutil.IPProtocol) string {
if v, ok := prots[p]; ok {
return v
}
return fmt.Sprintf("unknown(%d)", p)
}
+121
View File
@@ -0,0 +1,121 @@
package net
import (
"context"
"fmt"
"io"
"net"
"runtime"
"strings"
"syscall"
"github.com/vishvananda/netns"
)
type SetBuffer interface {
SetReadBuffer(bytes int) error
SetWriteBuffer(bytes int) error
}
type SyscallConn interface {
SyscallConn() (syscall.RawConn, error)
}
type RemoteAddr interface {
RemoteAddr() net.Addr
}
// tcpraw.TCPConn
type SetDSCP interface {
SetDSCP(int) error
}
func IsIPv4(address string) bool {
return address != "" && address[0] != ':' && address[0] != '['
}
type ListenConfig struct {
Netns string
net.ListenConfig
}
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (net.Listener, error) {
if lc.Netns != "" {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
originNs, err := netns.Get()
if err != nil {
return nil, fmt.Errorf("netns.Get(): %v", err)
}
defer netns.Set(originNs)
var ns netns.NsHandle
if strings.HasPrefix(lc.Netns, "/") {
ns, err = netns.GetFromPath(lc.Netns)
} else {
ns, err = netns.GetFromName(lc.Netns)
}
if err != nil {
return nil, fmt.Errorf("netns.Get(%s): %v", lc.Netns, err)
}
defer ns.Close()
if err := netns.Set(ns); err != nil {
return nil, fmt.Errorf("netns.Set(%s): %v", lc.Netns, err)
}
}
return lc.ListenConfig.Listen(ctx, network, address)
}
func (lc *ListenConfig) ListenPacket(ctx context.Context, network, address string) (net.PacketConn, error) {
if lc.Netns != "" {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
originNs, err := netns.Get()
if err != nil {
return nil, fmt.Errorf("netns.Get(): %v", err)
}
defer netns.Set(originNs)
var ns netns.NsHandle
if strings.HasPrefix(lc.Netns, "/") {
ns, err = netns.GetFromPath(lc.Netns)
} else {
ns, err = netns.GetFromName(lc.Netns)
}
if err != nil {
return nil, fmt.Errorf("netns.Get(%s): %v", lc.Netns, err)
}
defer ns.Close()
if err := netns.Set(ns); err != nil {
return nil, fmt.Errorf("netns.Set(%s): %v", lc.Netns, err)
}
}
return lc.ListenConfig.ListenPacket(ctx, network, address)
}
type readWriteConn struct {
net.Conn
r io.Reader
w io.Writer
}
func NewReadWriteConn(r io.Reader, w io.Writer, c net.Conn) net.Conn {
return &readWriteConn{
Conn: c,
r: r,
w: w,
}
}
func (c *readWriteConn) Read(p []byte) (int, error) {
return c.r.Read(p)
}
func (c *readWriteConn) Write(p []byte) (int, error) {
return c.w.Write(p)
}
+17
View File
@@ -0,0 +1,17 @@
package proxyproto
import (
"net"
proxyproto "github.com/pires/go-proxyproto"
)
func WrapClientConn(ppv int, src, dst net.Addr, c net.Conn) net.Conn {
if ppv <= 0 {
return c
}
header := proxyproto.HeaderProxyFromAddrs(byte(ppv), src, dst)
header.WriteTo(c)
return c
}
@@ -0,0 +1,19 @@
package proxyproto
import (
"net"
"time"
proxyproto "github.com/pires/go-proxyproto"
)
func WrapListener(ppv int, ln net.Listener, readHeaderTimeout time.Duration) net.Listener {
if ppv <= 0 {
return ln
}
return &proxyproto.Listener{
Listener: ln,
ReadHeaderTimeout: readHeaderTimeout,
}
}
+52
View File
@@ -0,0 +1,52 @@
package net
import (
"context"
"fmt"
"net"
"github.com/go-gost/core/hosts"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/resolver"
ctxvalue "github.com/go-gost/x/ctx"
)
func Resolve(ctx context.Context, network, addr string, r resolver.Resolver, hosts hosts.HostMapper, log logger.Logger) (string, error) {
if addr == "" {
return addr, nil
}
host, port, _ := net.SplitHostPort(addr)
if host == "" {
return addr, nil
}
if log == nil {
log = logger.Default()
}
log = log.WithFields(map[string]any{
"sid": ctxvalue.SidFromContext(ctx),
})
if hosts != nil {
if ips, _ := hosts.Lookup(ctx, network, host); len(ips) > 0 {
log.Debugf("hit host mapper: %s -> %s", host, ips)
return net.JoinHostPort(ips[0].String(), port), nil
}
}
if r != nil {
ips, err := r.Resolve(ctx, network, host)
if err != nil {
if err == resolver.ErrInvalid {
return addr, nil
}
log.Error(err)
}
if len(ips) == 0 {
return "", fmt.Errorf("resolver: domain %s does not exist", host)
}
return net.JoinHostPort(ips[0].String(), port), nil
}
return addr, nil
}
+36
View File
@@ -0,0 +1,36 @@
package net
import (
"io"
"github.com/go-gost/core/common/bufpool"
)
const (
bufferSize = 64 * 1024
)
func Transport(rw1, rw2 io.ReadWriter) error {
errc := make(chan error, 1)
go func() {
errc <- CopyBuffer(rw1, rw2, bufferSize)
}()
go func() {
errc <- CopyBuffer(rw2, rw1, bufferSize)
}()
if err := <-errc; err != nil && err != io.EOF {
return err
}
return nil
}
func CopyBuffer(dst io.Writer, src io.Reader, bufSize int) error {
buf := bufpool.Get(bufSize)
defer bufpool.Put(buf)
_, err := io.CopyBuffer(dst, src, buf)
return err
}
+29
View File
@@ -0,0 +1,29 @@
package udp
import (
"io"
"net"
xnet "github.com/go-gost/x/internal/net"
)
type Conn interface {
net.PacketConn
io.Reader
io.Writer
ReadUDP
WriteUDP
xnet.SetBuffer
xnet.SyscallConn
xnet.RemoteAddr
}
type ReadUDP interface {
ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error)
}
type WriteUDP interface {
WriteToUDP(b []byte, addr *net.UDPAddr) (int, error)
WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error)
}
+238
View File
@@ -0,0 +1,238 @@
package udp
import (
"errors"
"net"
"sync"
"sync/atomic"
"time"
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/core/logger"
)
type ListenConfig struct {
Addr net.Addr
Backlog int
ReadQueueSize int
ReadBufferSize int
TTL time.Duration
Keepalive bool
Logger logger.Logger
}
type listener struct {
conn net.PacketConn
cqueue chan net.Conn
connPool *connPool
closed chan struct{}
errChan chan error
config *ListenConfig
}
func NewListener(conn net.PacketConn, cfg *ListenConfig) net.Listener {
if cfg == nil {
cfg = &ListenConfig{}
}
ln := &listener{
conn: conn,
cqueue: make(chan net.Conn, cfg.Backlog),
closed: make(chan struct{}),
errChan: make(chan error, 1),
config: cfg,
}
ln.connPool = newConnPool(cfg.TTL).WithLogger(cfg.Logger)
go ln.listenLoop()
return ln
}
func (ln *listener) Accept() (conn net.Conn, err error) {
select {
case conn = <-ln.cqueue:
return
case <-ln.closed:
return nil, net.ErrClosed
case err = <-ln.errChan:
if err == nil {
err = net.ErrClosed
}
return
}
}
func (ln *listener) listenLoop() {
for {
select {
case <-ln.closed:
return
default:
}
b := bufpool.Get(ln.config.ReadBufferSize)
n, raddr, err := ln.conn.ReadFrom(b)
if err != nil {
ln.errChan <- err
close(ln.errChan)
return
}
c := ln.getConn(raddr)
if c == nil {
bufpool.Put(b)
continue
}
if err := c.WriteQueue(b[:n]); err != nil {
ln.config.Logger.Warn("data discarded: ", err)
}
}
}
func (ln *listener) Addr() net.Addr {
if ln.config.Addr != nil {
return ln.config.Addr
}
return ln.conn.LocalAddr()
}
func (ln *listener) Close() error {
select {
case <-ln.closed:
default:
close(ln.closed)
ln.conn.Close()
ln.connPool.Close()
}
return nil
}
func (ln *listener) getConn(raddr net.Addr) *conn {
c, ok := ln.connPool.Get(raddr.String())
if ok && !c.isClosed() {
return c
}
c = newConn(ln.conn, ln.Addr(), raddr, ln.config.ReadQueueSize, ln.config.Keepalive)
select {
case ln.cqueue <- c:
ln.connPool.Set(raddr.String(), c)
return c
default:
c.Close()
ln.config.Logger.Warnf("connection queue is full, client %s discarded", raddr)
return nil
}
}
// conn is a server side connection for UDP client peer, it implements net.Conn and net.PacketConn.
type conn struct {
net.PacketConn
localAddr net.Addr
remoteAddr net.Addr
rc chan []byte // data receive queue
idle int32 // indicate the connection is idle
closed chan struct{}
closeMutex sync.Mutex
keepalive bool
}
func newConn(c net.PacketConn, laddr, remoteAddr net.Addr, queueSize int, keepalive bool) *conn {
return &conn{
PacketConn: c,
localAddr: laddr,
remoteAddr: remoteAddr,
rc: make(chan []byte, queueSize),
closed: make(chan struct{}),
keepalive: keepalive,
}
}
func (c *conn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
select {
case bb := <-c.rc:
n = copy(b, bb)
c.SetIdle(false)
bufpool.Put(bb)
case <-c.closed:
err = net.ErrClosed
return
}
addr = c.remoteAddr
return
}
func (c *conn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *conn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
if !c.keepalive {
defer c.Close()
}
return c.PacketConn.WriteTo(b, addr)
}
func (c *conn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.remoteAddr)
}
func (c *conn) Close() error {
c.closeMutex.Lock()
defer c.closeMutex.Unlock()
select {
case <-c.closed:
default:
close(c.closed)
}
return nil
}
func (c *conn) isClosed() bool {
select {
case <-c.closed:
return true
default:
return false
}
}
func (c *conn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *conn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *conn) IsIdle() bool {
return atomic.LoadInt32(&c.idle) > 0
}
func (c *conn) SetIdle(idle bool) {
v := int32(0)
if idle {
v = 1
}
atomic.StoreInt32(&c.idle, v)
}
func (c *conn) WriteQueue(b []byte) error {
select {
case c.rc <- b:
return nil
case <-c.closed:
return net.ErrClosed
default:
return errors.New("recv queue is full")
}
}
+115
View File
@@ -0,0 +1,115 @@
package udp
import (
"sync"
"time"
"github.com/go-gost/core/logger"
)
type connPool struct {
m sync.Map
ttl time.Duration
closed chan struct{}
logger logger.Logger
}
func newConnPool(ttl time.Duration) *connPool {
p := &connPool{
ttl: ttl,
closed: make(chan struct{}),
}
go p.idleCheck()
return p
}
func (p *connPool) WithLogger(logger logger.Logger) *connPool {
p.logger = logger
return p
}
func (p *connPool) Get(key any) (c *conn, ok bool) {
if p == nil {
return
}
v, ok := p.m.Load(key)
if ok {
c, ok = v.(*conn)
}
return
}
func (p *connPool) Set(key any, c *conn) {
if p == nil {
return
}
p.m.Store(key, c)
}
func (p *connPool) Delete(key any) {
if p == nil {
return
}
p.m.Delete(key)
}
func (p *connPool) Close() {
if p == nil {
return
}
select {
case <-p.closed:
return
default:
}
close(p.closed)
p.m.Range(func(k, v any) bool {
if c, ok := v.(*conn); ok && c != nil {
c.Close()
}
return true
})
}
func (p *connPool) idleCheck() {
ticker := time.NewTicker(p.ttl)
defer ticker.Stop()
for {
select {
case <-ticker.C:
size := 0
idles := 0
p.m.Range(func(key, value any) bool {
c, ok := value.(*conn)
if !ok || c == nil {
p.Delete(key)
return true
}
size++
if c.IsIdle() {
idles++
p.Delete(key)
c.Close()
return true
}
c.SetIdle(true)
return true
})
if idles > 0 {
p.logger.Debugf("connection pool: size=%d, idle=%d", size, idles)
}
case <-p.closed:
return
}
}
}
+117
View File
@@ -0,0 +1,117 @@
package udp
import (
"context"
"math"
"net"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/logger"
)
const (
MaxMessageSize = math.MaxUint16
)
type Relay struct {
pc1 net.PacketConn
pc2 net.PacketConn
bypass bypass.Bypass
logger logger.Logger
}
func NewRelay(pc1, pc2 net.PacketConn) *Relay {
return &Relay{
pc1: pc1,
pc2: pc2,
}
}
func (r *Relay) WithBypass(bp bypass.Bypass) *Relay {
r.bypass = bp
return r
}
func (r *Relay) WithLogger(logger logger.Logger) *Relay {
r.logger = logger
return r
}
func (r *Relay) Run(ctx context.Context) (err error) {
errc := make(chan error, 2)
go func() {
var b [MaxMessageSize]byte
for {
err := func() error {
n, raddr, err := r.pc1.ReadFrom(b[:])
if err != nil {
return err
}
if r.bypass != nil && r.bypass.Contains(ctx, "udp", raddr.String()) {
if r.logger != nil {
r.logger.Warn("bypass: ", raddr)
}
return nil
}
if _, err := r.pc2.WriteTo(b[:n], raddr); err != nil {
return err
}
if r.logger != nil {
r.logger.Tracef("%s >>> %s data: %d",
r.pc2.LocalAddr(), raddr, n)
}
return nil
}()
if err != nil {
errc <- err
return
}
}
}()
go func() {
var b [MaxMessageSize]byte
for {
err := func() error {
n, raddr, err := r.pc2.ReadFrom(b[:])
if err != nil {
return err
}
if r.bypass != nil && r.bypass.Contains(ctx, "udp", raddr.String()) {
if r.logger != nil {
r.logger.Warn("bypass: ", raddr)
}
return nil
}
if _, err := r.pc1.WriteTo(b[:n], raddr); err != nil {
return err
}
if r.logger != nil {
r.logger.Tracef("%s <<< %s data: %d",
r.pc2.LocalAddr(), raddr, n)
}
return nil
}()
if err != nil {
errc <- err
return
}
}
}()
return <-errc
}
+95
View File
@@ -0,0 +1,95 @@
package plugin
import (
"context"
"crypto/tls"
"net/http"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/backoff"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
const (
GRPC string = "grpc"
HTTP string = "http"
)
type Options struct {
Token string
TLSConfig *tls.Config
Header http.Header
Timeout time.Duration
}
type Option func(opts *Options)
func TokenOption(token string) Option {
return func(opts *Options) {
opts.Token = token
}
}
func TLSConfigOption(cfg *tls.Config) Option {
return func(opts *Options) {
opts.TLSConfig = cfg
}
}
func HeaderOption(header http.Header) Option {
return func(opts *Options) {
opts.Header = header
}
}
func TimeoutOption(timeout time.Duration) Option {
return func(opts *Options) {
opts.Timeout = timeout
}
}
func NewGRPCConn(addr string, opts *Options) (*grpc.ClientConn, error) {
grpcOpts := []grpc.DialOption{
// grpc.WithBlock(),
grpc.WithConnectParams(grpc.ConnectParams{
Backoff: backoff.DefaultConfig,
}),
}
if opts.TLSConfig != nil {
grpcOpts = append(grpcOpts,
grpc.WithAuthority(opts.TLSConfig.ServerName),
grpc.WithTransportCredentials(credentials.NewTLS(opts.TLSConfig)),
)
} else {
grpcOpts = append(grpcOpts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
if opts.Token != "" {
grpcOpts = append(grpcOpts, grpc.WithPerRPCCredentials(&rpcCredentials{token: opts.Token}))
}
return grpc.NewClient(addr, grpcOpts...)
}
type rpcCredentials struct {
token string
}
func (c *rpcCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"token": c.token,
}, nil
}
func (c *rpcCredentials) RequireTransportSecurity() bool {
return false
}
func NewHTTPClient(opts *Options) *http.Client {
return &http.Client{
Timeout: opts.Timeout,
Transport: &http.Transport{
TLSClientConfig: opts.TLSConfig,
},
}
}
+64
View File
@@ -0,0 +1,64 @@
package cache
import (
"sync"
"time"
)
type Item struct {
v interface{}
expiration int64
}
func NewItem(v interface{}, d time.Duration) *Item {
var expiration int64
if d > 0 {
expiration = time.Now().Add(d).UnixNano()
}
return &Item{
v: v,
expiration: expiration,
}
}
func (p *Item) Expired() bool {
if p == nil {
return true
}
return p.expiration > 0 && time.Now().UnixNano() > p.expiration
}
func (p *Item) Value() interface{} {
if p == nil {
return nil
}
return p.v
}
type Cache struct {
items map[string]*Item
cleanupInterval time.Duration
mu sync.RWMutex
}
func NewCache(cleanupInterval time.Duration) *Cache {
return &Cache{
cleanupInterval: cleanupInterval,
items: make(map[string]*Item),
}
}
func (c *Cache) Set(key string, item *Item) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = item
}
func (c *Cache) Get(key string) *Item {
c.mu.RLock()
defer c.mu.RUnlock()
return c.items[key]
}
+73
View File
@@ -0,0 +1,73 @@
package dtls
import (
"bytes"
"net"
"github.com/go-gost/core/common/bufpool"
)
type dtlsConn struct {
net.Conn
rbuf bytes.Buffer
bufferSize int
}
func Conn(c net.Conn, bufferSize int) net.Conn {
return &dtlsConn{
Conn: c,
bufferSize: bufferSize,
}
}
func (c *dtlsConn) Read(p []byte) (n int, err error) {
/*
defer func() {
logger.Default().Debugf("dtls: read data %d/%d, %v", n, len(p), err)
}()
*/
if c.rbuf.Len() > 0 {
return c.rbuf.Read(p)
}
bufferSize := c.bufferSize
if len(p) >= bufferSize {
return c.Conn.Read(p)
}
buf := bufpool.Get(bufferSize)
defer bufpool.Put(buf)
nn, err := c.Conn.Read(buf)
if err != nil {
return 0, err
}
n = copy(p, buf[:nn])
c.rbuf.Write(buf[n:nn])
return
}
func (c *dtlsConn) Write(p []byte) (n int, err error) {
/*
defer func() {
logger.Default().Debugf("dtls: write data %d, %v", n, err)
}()
*/
for len(p) > 0 {
nn := c.bufferSize
if nn > len(p) {
nn = len(p)
}
nn, err = c.Conn.Write(p[:nn])
n += nn
if err != nil {
return
}
p = p[nn:]
}
return
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,148 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v3.12.4
// source: gost.proto
package proto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Chunk struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}
func (x *Chunk) Reset() {
*x = Chunk{}
if protoimpl.UnsafeEnabled {
mi := &file_gost_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Chunk) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Chunk) ProtoMessage() {}
func (x *Chunk) ProtoReflect() protoreflect.Message {
mi := &file_gost_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Chunk.ProtoReflect.Descriptor instead.
func (*Chunk) Descriptor() ([]byte, []int) {
return file_gost_proto_rawDescGZIP(), []int{0}
}
func (x *Chunk) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
var File_gost_proto protoreflect.FileDescriptor
var file_gost_proto_rawDesc = []byte{
0x0a, 0x0a, 0x67, 0x6f, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x1b, 0x0a, 0x05,
0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20,
0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0x29, 0x0a, 0x09, 0x47, 0x6f, 0x73,
0x74, 0x54, 0x75, 0x6e, 0x65, 0x6c, 0x12, 0x1c, 0x0a, 0x06, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c,
0x12, 0x06, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x1a, 0x06, 0x2e, 0x43, 0x68, 0x75, 0x6e, 0x6b,
0x28, 0x01, 0x30, 0x01, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x2d, 0x67, 0x6f, 0x73, 0x74, 0x2f, 0x67, 0x6f, 0x73, 0x74, 0x2f,
0x70, 0x6b, 0x67, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x75, 0x74, 0x69, 0x6c, 0x2f,
0x67, 0x72, 0x70, 0x63, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_gost_proto_rawDescOnce sync.Once
file_gost_proto_rawDescData = file_gost_proto_rawDesc
)
func file_gost_proto_rawDescGZIP() []byte {
file_gost_proto_rawDescOnce.Do(func() {
file_gost_proto_rawDescData = protoimpl.X.CompressGZIP(file_gost_proto_rawDescData)
})
return file_gost_proto_rawDescData
}
var file_gost_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_gost_proto_goTypes = []any{
(*Chunk)(nil), // 0: Chunk
}
var file_gost_proto_depIdxs = []int32{
0, // 0: GostTunel.Tunnel:input_type -> Chunk
0, // 1: GostTunel.Tunnel:output_type -> Chunk
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_gost_proto_init() }
func file_gost_proto_init() {
if File_gost_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_gost_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*Chunk); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_gost_proto_rawDesc,
NumEnums: 0,
NumMessages: 1,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_gost_proto_goTypes,
DependencyIndexes: file_gost_proto_depIdxs,
MessageInfos: file_gost_proto_msgTypes,
}.Build()
File_gost_proto = out.File
file_gost_proto_rawDesc = nil
file_gost_proto_goTypes = nil
file_gost_proto_depIdxs = nil
}
@@ -0,0 +1,10 @@
syntax = "proto3";
option go_package = "github.com/go-gost/core/common/util/grpc/proto";
message Chunk {
bytes data = 1;
}
service GostTunel {
rpc Tunnel (stream Chunk) returns (stream Chunk);
}
@@ -0,0 +1,133 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
package proto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// GostTunelClient is the client API for GostTunel service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type GostTunelClient interface {
Tunnel(ctx context.Context, opts ...grpc.CallOption) (GostTunel_TunnelClient, error)
}
type gostTunelClient struct {
cc grpc.ClientConnInterface
}
func NewGostTunelClient(cc grpc.ClientConnInterface) GostTunelClient {
return &gostTunelClient{cc}
}
func (c *gostTunelClient) Tunnel(ctx context.Context, opts ...grpc.CallOption) (GostTunel_TunnelClient, error) {
stream, err := c.cc.NewStream(ctx, &GostTunel_ServiceDesc.Streams[0], "/GostTunel/Tunnel", opts...)
if err != nil {
return nil, err
}
x := &gostTunelTunnelClient{stream}
return x, nil
}
type GostTunel_TunnelClient interface {
Send(*Chunk) error
Recv() (*Chunk, error)
grpc.ClientStream
}
type gostTunelTunnelClient struct {
grpc.ClientStream
}
func (x *gostTunelTunnelClient) Send(m *Chunk) error {
return x.ClientStream.SendMsg(m)
}
func (x *gostTunelTunnelClient) Recv() (*Chunk, error) {
m := new(Chunk)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// GostTunelServer is the server API for GostTunel service.
// All implementations must embed UnimplementedGostTunelServer
// for forward compatibility
type GostTunelServer interface {
Tunnel(GostTunel_TunnelServer) error
mustEmbedUnimplementedGostTunelServer()
}
// UnimplementedGostTunelServer must be embedded to have forward compatible implementations.
type UnimplementedGostTunelServer struct {
}
func (UnimplementedGostTunelServer) Tunnel(GostTunel_TunnelServer) error {
return status.Errorf(codes.Unimplemented, "method Tunnel not implemented")
}
func (UnimplementedGostTunelServer) mustEmbedUnimplementedGostTunelServer() {}
// UnsafeGostTunelServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to GostTunelServer will
// result in compilation errors.
type UnsafeGostTunelServer interface {
mustEmbedUnimplementedGostTunelServer()
}
func RegisterGostTunelServer(s grpc.ServiceRegistrar, srv GostTunelServer) {
s.RegisterService(&GostTunel_ServiceDesc, srv)
}
func _GostTunel_Tunnel_Handler(srv any, stream grpc.ServerStream) error {
return srv.(GostTunelServer).Tunnel(&gostTunelTunnelServer{stream})
}
type GostTunel_TunnelServer interface {
Send(*Chunk) error
Recv() (*Chunk, error)
grpc.ServerStream
}
type gostTunelTunnelServer struct {
grpc.ServerStream
}
func (x *gostTunelTunnelServer) Send(m *Chunk) error {
return x.ServerStream.SendMsg(m)
}
func (x *gostTunelTunnelServer) Recv() (*Chunk, error) {
m := new(Chunk)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// GostTunel_ServiceDesc is the grpc.ServiceDesc for GostTunel service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var GostTunel_ServiceDesc = grpc.ServiceDesc{
ServiceName: "GostTunel",
HandlerType: (*GostTunelServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "Tunnel",
Handler: _GostTunel_Tunnel_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "gost.proto",
}
@@ -0,0 +1,72 @@
package proto
import (
context "context"
"strings"
grpc "google.golang.org/grpc"
)
type GostTunelClientX interface {
TunnelX(ctx context.Context, method string, opts ...grpc.CallOption) (GostTunel_TunnelClient, error)
}
type gostTunelClientX struct {
cc grpc.ClientConnInterface
}
func NewGostTunelClientX(cc grpc.ClientConnInterface) GostTunelClientX {
return &gostTunelClientX{
cc: cc,
}
}
func (c *gostTunelClientX) TunnelX(ctx context.Context, method string, opts ...grpc.CallOption) (GostTunel_TunnelClient, error) {
sd := ServerDesc(method)
method = "/" + sd.ServiceName + "/" + sd.Streams[0].StreamName
stream, err := c.cc.NewStream(ctx, &sd.Streams[0], method, opts...)
if err != nil {
return nil, err
}
x := &gostTunelTunnelClient{stream}
return x, nil
}
func RegisterGostTunelServerX(s grpc.ServiceRegistrar, srv GostTunelServer, method string) {
sd := ServerDesc(method)
s.RegisterService(&sd, srv)
}
func ServerDesc(method string) grpc.ServiceDesc {
serviceName, streamName := parsingMethod(method)
return grpc.ServiceDesc{
ServiceName: serviceName,
HandlerType: GostTunel_ServiceDesc.HandlerType,
Methods: GostTunel_ServiceDesc.Methods,
Streams: []grpc.StreamDesc{
{
StreamName: streamName,
Handler: GostTunel_ServiceDesc.Streams[0].Handler,
ServerStreams: GostTunel_ServiceDesc.Streams[0].ServerStreams,
ClientStreams: GostTunel_ServiceDesc.Streams[0].ClientStreams,
},
},
Metadata: GostTunel_ServiceDesc.Metadata,
}
}
func parsingMethod(method string) (string, string) {
serviceName := GostTunel_ServiceDesc.ServiceName
streamName := GostTunel_ServiceDesc.Streams[0].StreamName
v := strings.SplitN(strings.Trim(method, "/"), "/", 2)
if len(v) == 1 && v[0] != "" {
serviceName = v[0]
}
if len(v) == 2 {
serviceName = v[0]
streamName = strings.Replace(v[1], "/", "-", -1)
}
return serviceName, streamName
}
+3
View File
@@ -0,0 +1,3 @@
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
gost.proto
+313
View File
@@ -0,0 +1,313 @@
package icmp
import (
"encoding/binary"
"errors"
"fmt"
"math"
"net"
"sync/atomic"
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/core/logger"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
const (
ICMPv4 = 1
ICMPv6 = 58
)
const (
readBufferSize = 4096
writeBufferSize = 4096
magicNumber = 0x474F5354
)
const (
messageHeaderLen = 10
)
const (
FlagAck = 1
)
var (
ErrInvalidPacket = errors.New("icmp: invalid packet")
ErrInvalidType = errors.New("icmp: invalid type")
ErrShortBuffer = errors.New("icmp: short buffer")
)
type message struct {
// magic uint32 // magic number
flags uint16 // flags
// rsv uint16 // reserved field
// len uint16 // length of data
data []byte
}
func (m *message) Encode(b []byte) (n int, err error) {
if len(b) < messageHeaderLen+len(m.data) {
err = ErrShortBuffer
return
}
binary.BigEndian.PutUint32(b[:4], magicNumber) // magic number
binary.BigEndian.PutUint16(b[4:6], m.flags) // flags
binary.BigEndian.PutUint16(b[6:8], 0) // reserved
binary.BigEndian.PutUint16(b[8:10], uint16(len(m.data)))
copy(b[messageHeaderLen:], m.data)
n = messageHeaderLen + len(m.data)
return
}
func (m *message) Decode(b []byte) (n int, err error) {
if len(b) < messageHeaderLen {
err = ErrShortBuffer
return
}
if binary.BigEndian.Uint32(b[:4]) != magicNumber {
err = ErrInvalidPacket
return
}
m.flags = binary.BigEndian.Uint16(b[4:6])
length := binary.BigEndian.Uint16(b[8:10])
if len(b[messageHeaderLen:]) < int(length) {
err = ErrShortBuffer
return
}
m.data = b[messageHeaderLen : messageHeaderLen+length]
n = messageHeaderLen + int(length)
return
}
type clientConn struct {
ip6 bool
net.PacketConn
id int
seq uint32
}
func ClientConn(ip6 bool, conn net.PacketConn, id int) net.PacketConn {
return &clientConn{
ip6: ip6,
PacketConn: conn,
id: id,
}
}
func (c *clientConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
buf := bufpool.Get(readBufferSize)
defer bufpool.Put(buf)
for {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil {
return
}
proto := ICMPv4
if c.ip6 {
proto = ICMPv6
}
m, err := icmp.ParseMessage(proto, buf[:n])
if err != nil {
// logger.Default().Error("icmp: parse message %v", err)
return 0, addr, err
}
echo, ok := m.Body.(*icmp.Echo)
if !ok || m.Type != ipv4.ICMPTypeEchoReply && m.Type != ipv6.ICMPTypeEchoReply {
// logger.Default().Warnf("icmp: invalid type %s (discarded)", m.Type)
continue // discard
}
if echo.ID != c.id {
// logger.Default().Warnf("icmp: id mismatch got %d, should be %d (discarded)", echo.ID, c.id)
continue
}
msg := message{}
if _, err := msg.Decode(echo.Data); err != nil {
logger.Default().Warn(err)
continue
}
if msg.flags&FlagAck == 0 {
// logger.Default().Warn("icmp: invalid message (discarded)")
continue
}
n = copy(b, msg.data)
break
}
if v, ok := addr.(*net.IPAddr); ok {
addr = &net.UDPAddr{
IP: v.IP,
Port: c.id,
Zone: v.Zone,
}
}
// logger.Default().Infof("icmp: read from: %v %d", addr, n)
return
}
func (c *clientConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
// logger.Default().Infof("icmp: write to: %v %d", addr, len(b))
switch v := addr.(type) {
case *net.UDPAddr:
addr = &net.IPAddr{IP: v.IP, Zone: v.Zone}
}
buf := bufpool.Get(writeBufferSize)
defer bufpool.Put(buf)
msg := message{
data: b,
}
nn, err := msg.Encode(buf)
if err != nil {
return
}
echo := icmp.Echo{
ID: c.id,
Seq: int(atomic.AddUint32(&c.seq, 1)),
Data: buf[:nn],
}
m := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &echo,
}
if c.ip6 {
m.Type = ipv6.ICMPTypeEchoRequest
}
wb, err := m.Marshal(nil)
if err != nil {
return 0, err
}
_, err = c.PacketConn.WriteTo(wb, addr)
n = len(b)
return
}
type serverConn struct {
ip6 bool
net.PacketConn
seqs [65535]uint32
}
func ServerConn(ip6 bool, conn net.PacketConn) net.PacketConn {
return &serverConn{
ip6: ip6,
PacketConn: conn,
}
}
func (c *serverConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
buf := bufpool.Get(readBufferSize)
defer bufpool.Put(buf)
for {
n, addr, err = c.PacketConn.ReadFrom(buf)
if err != nil {
return
}
proto := ICMPv4
if c.ip6 {
proto = ICMPv6
}
m, err := icmp.ParseMessage(proto, buf[:n])
if err != nil {
// logger.Default().Error("icmp: parse message %v", err)
return 0, addr, err
}
echo, ok := m.Body.(*icmp.Echo)
if !ok || echo.ID <= 0 || m.Type != ipv4.ICMPTypeEcho && m.Type != ipv6.ICMPTypeEchoRequest {
// logger.Default().Warnf("icmp: invalid type %s (discarded)", m.Type)
continue
}
atomic.StoreUint32(&c.seqs[uint16(echo.ID-1)], uint32(echo.Seq))
msg := message{}
if _, err := msg.Decode(echo.Data); err != nil {
continue
}
if msg.flags&FlagAck > 0 {
continue
}
n = copy(b, msg.data)
if v, ok := addr.(*net.IPAddr); ok {
addr = &net.UDPAddr{
IP: v.IP,
Port: echo.ID,
Zone: v.Zone,
}
}
break
}
// logger.Default().Infof("icmp: read from: %v %d", addr, n)
return
}
func (c *serverConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
// logger.Default().Infof("icmp: write to: %v %d", addr, len(b))
var id int
switch v := addr.(type) {
case *net.UDPAddr:
addr = &net.IPAddr{IP: v.IP, Zone: v.Zone}
id = v.Port
}
if id <= 0 || id > math.MaxUint16 {
err = fmt.Errorf("icmp: invalid message id %v", addr)
return
}
buf := bufpool.Get(writeBufferSize)
defer bufpool.Put(buf)
msg := message{
flags: FlagAck,
data: b,
}
nn, err := msg.Encode(buf)
if err != nil {
return
}
echo := icmp.Echo{
ID: id,
Seq: int(atomic.LoadUint32(&c.seqs[id-1])),
Data: buf[:nn],
}
m := icmp.Message{
Type: ipv4.ICMPTypeEchoReply,
Code: 0,
Body: &echo,
}
if c.ip6 {
m.Type = ipv6.ICMPTypeEchoReply
}
wb, err := m.Marshal(nil)
if err != nil {
return 0, err
}
_, err = c.PacketConn.WriteTo(wb, addr)
n = len(b)
return
}
+149
View File
@@ -0,0 +1,149 @@
package kcp
import (
"crypto/sha1"
"encoding/json"
"os"
"github.com/xtaci/kcp-go/v5"
"golang.org/x/crypto/pbkdf2"
)
var (
// DefaultSalt is the default salt for KCP cipher.
DefaultSalt = "kcp-go"
)
var (
// DefaultKCPConfig is the default KCP config.
DefaultConfig = &Config{
Key: "it's a secrect",
Crypt: "aes",
Mode: "fast",
MTU: 1350,
SndWnd: 1024,
RcvWnd: 1024,
DataShard: 10,
ParityShard: 3,
DSCP: 0,
NoComp: false,
AckNodelay: false,
NoDelay: 0,
Interval: 50,
Resend: 0,
NoCongestion: 0,
SockBuf: 4194304,
SmuxVer: 1,
SmuxBuf: 4194304,
StreamBuf: 2097152,
KeepAlive: 10,
SnmpLog: "",
SnmpPeriod: 60,
Signal: false,
TCP: false,
}
)
// KCPConfig describes the config for KCP.
type Config struct {
Key string `json:"key"`
Crypt string `json:"crypt"`
Mode string `json:"mode"`
MTU int `json:"mtu"`
SndWnd int `json:"sndwnd"`
RcvWnd int `json:"rcvwnd"`
DataShard int `json:"datashard"`
ParityShard int `json:"parityshard"`
DSCP int `json:"dscp"`
NoComp bool `json:"nocomp"`
AckNodelay bool `json:"acknodelay"`
NoDelay int `json:"nodelay"`
Interval int `json:"interval"`
Resend int `json:"resend"`
NoCongestion int `json:"nc"`
SockBuf int `json:"sockbuf"`
SmuxBuf int `json:"smuxbuf"`
StreamBuf int `json:"streambuf"`
SmuxVer int `json:"smuxver"`
KeepAlive int `json:"keepalive"`
SnmpLog string `json:"snmplog"`
SnmpPeriod int `json:"snmpperiod"`
Signal bool `json:"signal"` // Signal enables the signal SIGUSR1 feature.
TCP bool `json:"tcp"`
}
func ParseFromFile(filename string) (*Config, error) {
if filename == "" {
return nil, nil
}
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
config := &Config{}
if err = json.NewDecoder(file).Decode(config); err != nil {
return nil, err
}
return config, nil
}
// Init initializes the KCP config.
func (c *Config) Init() {
switch c.Mode {
case "normal":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 40, 2, 1
case "fast":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 30, 2, 1
case "fast2":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1
case "fast3":
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 10, 2, 1
}
if c.SmuxVer <= 0 {
c.SmuxVer = 1
}
if c.SmuxBuf <= 0 {
c.SmuxBuf = c.SockBuf
}
if c.StreamBuf <= 0 {
c.StreamBuf = c.SockBuf / 2
}
}
func BlockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) {
pass := pbkdf2.Key([]byte(key), []byte(salt), 4096, 32, sha1.New)
switch crypt {
case "sm4":
block, _ = kcp.NewSM4BlockCrypt(pass[:16])
case "tea":
block, _ = kcp.NewTEABlockCrypt(pass[:16])
case "xor":
block, _ = kcp.NewSimpleXORBlockCrypt(pass)
case "none":
block, _ = kcp.NewNoneBlockCrypt(pass)
case "aes-128":
block, _ = kcp.NewAESBlockCrypt(pass[:16])
case "aes-192":
block, _ = kcp.NewAESBlockCrypt(pass[:24])
case "blowfish":
block, _ = kcp.NewBlowfishBlockCrypt(pass)
case "twofish":
block, _ = kcp.NewTwofishBlockCrypt(pass)
case "cast5":
block, _ = kcp.NewCast5BlockCrypt(pass[:16])
case "3des":
block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
case "xtea":
block, _ = kcp.NewXTEABlockCrypt(pass[:16])
case "salsa20":
block, _ = kcp.NewSalsa20BlockCrypt(pass)
case "aes":
fallthrough
default: // aes
block, _ = kcp.NewAESBlockCrypt(pass)
}
return
}
+34
View File
@@ -0,0 +1,34 @@
package kcp
import (
"net"
"github.com/golang/snappy"
)
type kcpCompStreamConn struct {
net.Conn
w *snappy.Writer
r *snappy.Reader
}
func CompStreamConn(conn net.Conn) net.Conn {
return &kcpCompStreamConn{
Conn: conn,
w: snappy.NewBufferedWriter(conn),
r: snappy.NewReader(conn),
}
}
func (c *kcpCompStreamConn) Read(b []byte) (n int, err error) {
return c.r.Read(b)
}
func (c *kcpCompStreamConn) Write(b []byte) (n int, err error) {
n, err = c.w.Write(b)
if err != nil {
return
}
err = c.w.Flush()
return n, err
}
+148
View File
@@ -0,0 +1,148 @@
package mux
import (
"net"
"time"
smux "github.com/xtaci/smux"
)
const (
defaultVersion = 1
)
type Config struct {
// SMUX Protocol version, support 1,2
Version int
// Disabled keepalive
KeepAliveDisabled bool
// KeepAliveInterval is how often to send a NOP command to the remote
KeepAliveInterval time.Duration
// KeepAliveTimeout is how long the session
// will be closed if no data has arrived
KeepAliveTimeout time.Duration
// MaxFrameSize is used to control the maximum
// frame size to sent to the remote
MaxFrameSize int
// MaxReceiveBuffer is used to control the maximum
// number of data in the buffer pool
MaxReceiveBuffer int
// MaxStreamBuffer is used to control the maximum
// number of data per stream
MaxStreamBuffer int
}
func convertConfig(cfg *Config) *smux.Config {
smuxCfg := smux.DefaultConfig()
smuxCfg.Version = defaultVersion
if cfg == nil {
return smuxCfg
}
if cfg.Version > 0 {
smuxCfg.Version = cfg.Version
}
smuxCfg.KeepAliveDisabled = cfg.KeepAliveDisabled
if cfg.KeepAliveInterval > 0 {
smuxCfg.KeepAliveInterval = cfg.KeepAliveInterval
}
if cfg.KeepAliveTimeout > 0 {
smuxCfg.KeepAliveTimeout = cfg.KeepAliveTimeout
}
if cfg.MaxFrameSize > 0 {
smuxCfg.MaxFrameSize = cfg.MaxFrameSize
}
if cfg.MaxReceiveBuffer > 0 {
smuxCfg.MaxReceiveBuffer = cfg.MaxReceiveBuffer
}
if cfg.MaxStreamBuffer > 0 {
smuxCfg.MaxStreamBuffer = cfg.MaxStreamBuffer
}
return smuxCfg
}
type Session struct {
conn net.Conn
session *smux.Session
}
func ClientSession(conn net.Conn, cfg *Config) (*Session, error) {
s, err := smux.Client(conn, convertConfig(cfg))
if err != nil {
return nil, err
}
return &Session{
conn: conn,
session: s,
}, nil
}
func ServerSession(conn net.Conn, cfg *Config) (*Session, error) {
s, err := smux.Server(conn, convertConfig(cfg))
if err != nil {
return nil, err
}
return &Session{
conn: conn,
session: s,
}, nil
}
func (session *Session) GetConn() (net.Conn, error) {
stream, err := session.session.OpenStream()
if err != nil {
return nil, err
}
return &streamConn{Conn: session.conn, stream: stream}, nil
}
func (session *Session) Accept() (net.Conn, error) {
stream, err := session.session.AcceptStream()
if err != nil {
return nil, err
}
return &streamConn{Conn: session.conn, stream: stream}, nil
}
func (session *Session) Close() error {
if session.session == nil {
return nil
}
return session.session.Close()
}
func (session *Session) IsClosed() bool {
if session.session == nil {
return true
}
return session.session.IsClosed()
}
func (session *Session) NumStreams() int {
return session.session.NumStreams()
}
type streamConn struct {
net.Conn
stream *smux.Stream
}
func (c *streamConn) Read(b []byte) (n int, err error) {
return c.stream.Read(b)
}
func (c *streamConn) Write(b []byte) (n int, err error) {
return c.stream.Write(b)
}
func (c *streamConn) Close() error {
return c.stream.Close()
}
+107
View File
@@ -0,0 +1,107 @@
package pht
import (
"context"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"strconv"
"strings"
"github.com/go-gost/core/logger"
)
type Client struct {
Host string
Client *http.Client
AuthorizePath string
PushPath string
PullPath string
TLSEnabled bool
Logger logger.Logger
}
func (c *Client) Dial(ctx context.Context, addr string) (net.Conn, error) {
raddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
c.Logger.Error(err)
return nil, err
}
if c.Host != "" {
addr = net.JoinHostPort(c.Host, strconv.Itoa(raddr.Port))
}
token, err := c.authorize(ctx, addr)
if err != nil {
c.Logger.Error(err)
return nil, err
}
cn := &clientConn{
client: c.Client,
rxc: make(chan []byte, 128),
closed: make(chan struct{}),
localAddr: &net.TCPAddr{},
remoteAddr: raddr,
logger: c.Logger,
}
scheme := "http"
if c.TLSEnabled {
scheme = "https"
}
cn.pushURL = fmt.Sprintf("%s://%s%s?token=%s", scheme, addr, c.PushPath, token)
cn.pullURL = fmt.Sprintf("%s://%s%s?token=%s", scheme, addr, c.PullPath, token)
go cn.readLoop()
return cn, nil
}
func (c *Client) authorize(ctx context.Context, addr string) (token string, err error) {
var url string
if c.TLSEnabled {
url = fmt.Sprintf("https://%s%s", addr, c.AuthorizePath)
} else {
url = fmt.Sprintf("http://%s%s", addr, c.AuthorizePath)
}
r, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return
}
if c.Logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(r, false)
c.Logger.Trace(string(dump))
} else if c.Logger.IsLevelEnabled(logger.DebugLevel) {
c.Logger.Debugf("%s %s", r.Method, r.URL)
}
resp, err := c.Client.Do(r)
if err != nil {
return
}
defer resp.Body.Close()
if c.Logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
c.Logger.Trace(string(dump))
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return
}
if strings.HasPrefix(string(data), "token=") {
token = strings.TrimPrefix(string(data), "token=")
}
if token == "" {
err = errors.New("authorize failed")
}
return
}
+226
View File
@@ -0,0 +1,226 @@
package pht
import (
"bufio"
"bytes"
"encoding/base64"
"errors"
"io"
"net"
"net/http"
"net/http/httputil"
"sync"
"time"
"github.com/go-gost/core/logger"
)
type clientConn struct {
client *http.Client
pushURL string
pullURL string
buf []byte
rxc chan []byte
closed chan struct{}
mu sync.Mutex
localAddr net.Addr
remoteAddr net.Addr
logger logger.Logger
}
func (c *clientConn) Read(b []byte) (n int, err error) {
if len(c.buf) == 0 {
select {
case c.buf = <-c.rxc:
case <-c.closed:
err = io.ErrClosedPipe
return
}
}
n = copy(b, c.buf)
c.buf = c.buf[n:]
return
}
func (c *clientConn) Write(b []byte) (n int, err error) {
if len(b) == 0 {
return
}
return c.write(b)
}
func (c *clientConn) write(b []byte) (n int, err error) {
if c.isClosed() {
err = io.ErrClosedPipe
return
}
var r io.Reader
if len(b) > 0 {
buf := bytes.NewBufferString(base64.StdEncoding.EncodeToString(b))
buf.WriteByte('\n')
r = buf
}
req, err := http.NewRequest(http.MethodPost, c.pushURL, r)
if err != nil {
return
}
if c.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(req, false)
c.logger.Trace(string(dump))
} else if c.logger.IsLevelEnabled(logger.DebugLevel) {
c.logger.Debugf("%s %s", req.Method, req.URL)
}
resp, err := c.client.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
if c.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
c.logger.Trace(string(dump))
}
if resp.StatusCode != http.StatusOK {
err = errors.New(resp.Status)
return
}
n = len(b)
return
}
func (c *clientConn) readLoop() {
for {
if c.isClosed() {
return
}
done := true
err := func() error {
r, err := http.NewRequest(http.MethodGet, c.pullURL, nil)
if err != nil {
return err
}
if c.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(r, false)
c.logger.Trace(string(dump))
} else if c.logger.IsLevelEnabled(logger.DebugLevel) {
c.logger.Debugf("%s %s", r.Method, r.URL)
}
resp, err := c.client.Do(r)
if err != nil {
return err
}
defer resp.Body.Close()
if c.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
c.logger.Trace(string(dump))
}
if resp.StatusCode != http.StatusOK {
return errors.New(resp.Status)
}
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
done = false
if scanner.Text() == "" {
continue
}
b, err := base64.StdEncoding.DecodeString(scanner.Text())
if err != nil {
return err
}
select {
case c.rxc <- b:
case <-c.closed:
return net.ErrClosed
}
}
return scanner.Err()
}()
if err != nil {
c.Close()
return
}
if done { // server connection closed
return
}
}
}
func (c *clientConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *clientConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
func (c *clientConn) Close() error {
c.mu.Lock()
select {
case <-c.closed:
c.mu.Unlock()
return nil
default:
close(c.closed)
}
c.mu.Unlock()
_, err := c.write(nil)
return err
}
func (c *clientConn) isClosed() bool {
c.mu.Lock()
defer c.mu.Unlock()
select {
case <-c.closed:
return true
default:
}
return false
}
func (c *clientConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *clientConn) SetWriteDeadline(t time.Time) error {
return nil
}
func (c *clientConn) SetDeadline(t time.Time) error {
return nil
}
type serverConn struct {
net.Conn
remoteAddr net.Addr
localAddr net.Addr
}
func (c *serverConn) LocalAddr() net.Addr {
return c.localAddr
}
func (c *serverConn) RemoteAddr() net.Addr {
return c.remoteAddr
}
+406
View File
@@ -0,0 +1,406 @@
package pht
import (
"bufio"
"context"
"crypto/tls"
"encoding/base64"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
"os"
"strings"
"sync"
"time"
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/core/logger"
xnet "github.com/go-gost/x/internal/net"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
"github.com/rs/xid"
)
const (
defaultBacklog = 128
defaultReadBufferSize = 32 * 1024
defaultReadTimeout = 10 * time.Second
)
type serverOptions struct {
authorizePath string
pushPath string
pullPath string
backlog int
tlsEnabled bool
tlsConfig *tls.Config
readBufferSize int
readTimeout time.Duration
mptcp bool
logger logger.Logger
}
type ServerOption func(opts *serverOptions)
func PathServerOption(authorizePath, pushPath, pullPath string) ServerOption {
return func(opts *serverOptions) {
opts.authorizePath = authorizePath
opts.pullPath = pullPath
opts.pushPath = pushPath
}
}
func BacklogServerOption(backlog int) ServerOption {
return func(opts *serverOptions) {
opts.backlog = backlog
}
}
func TLSConfigServerOption(tlsConfig *tls.Config) ServerOption {
return func(opts *serverOptions) {
opts.tlsConfig = tlsConfig
}
}
func EnableTLSServerOption(enable bool) ServerOption {
return func(opts *serverOptions) {
opts.tlsEnabled = enable
}
}
func ReadBufferSizeServerOption(n int) ServerOption {
return func(opts *serverOptions) {
opts.readBufferSize = n
}
}
func ReadTimeoutServerOption(timeout time.Duration) ServerOption {
return func(opts *serverOptions) {
opts.readTimeout = timeout
}
}
func MPTCPServerOption(mptcp bool) ServerOption {
return func(opts *serverOptions) {
opts.mptcp = mptcp
}
}
func LoggerServerOption(logger logger.Logger) ServerOption {
return func(opts *serverOptions) {
opts.logger = logger
}
}
// TODO: remove stale clients from conns
type Server struct {
addr net.Addr
httpServer *http.Server
http3Server *http3.Server
cqueue chan net.Conn
conns sync.Map
closed chan struct{}
options serverOptions
}
func NewServer(addr string, opts ...ServerOption) *Server {
var options serverOptions
for _, opt := range opts {
opt(&options)
}
if options.backlog <= 0 {
options.backlog = defaultBacklog
}
if options.readBufferSize <= 0 {
options.readBufferSize = defaultReadBufferSize
}
if options.readTimeout <= 0 {
options.readTimeout = defaultReadTimeout
}
s := &Server{
httpServer: &http.Server{
Addr: addr,
ReadHeaderTimeout: 30 * time.Second,
},
cqueue: make(chan net.Conn, options.backlog),
closed: make(chan struct{}),
options: options,
}
mux := http.NewServeMux()
mux.HandleFunc(options.authorizePath, s.handleAuthorize)
mux.HandleFunc(options.pushPath, s.handlePush)
mux.HandleFunc(options.pullPath, s.handlePull)
s.httpServer.Handler = mux
return s
}
func NewHTTP3Server(addr string, quicConfig *quic.Config, opts ...ServerOption) *Server {
var options serverOptions
for _, opt := range opts {
opt(&options)
}
if options.backlog <= 0 {
options.backlog = defaultBacklog
}
if options.readBufferSize <= 0 {
options.readBufferSize = defaultReadBufferSize
}
if options.readTimeout <= 0 {
options.readTimeout = defaultReadTimeout
}
s := &Server{
http3Server: &http3.Server{
Addr: addr,
TLSConfig: options.tlsConfig,
QUICConfig: quicConfig,
},
cqueue: make(chan net.Conn, options.backlog),
closed: make(chan struct{}),
options: options,
}
mux := http.NewServeMux()
mux.HandleFunc(options.authorizePath, s.handleAuthorize)
mux.HandleFunc(options.pushPath, s.handlePush)
mux.HandleFunc(options.pullPath, s.handlePull)
s.http3Server.Handler = mux
return s
}
func (s *Server) ListenAndServe() error {
if s.http3Server != nil {
network := "udp"
if xnet.IsIPv4(s.http3Server.Addr) {
network = "udp4"
}
addr, err := net.ResolveUDPAddr(network, s.http3Server.Addr)
if err != nil {
return err
}
s.addr = addr
return s.http3Server.ListenAndServe()
}
network := "tcp"
if xnet.IsIPv4(s.httpServer.Addr) {
network = "tcp4"
}
lc := net.ListenConfig{}
if s.options.mptcp {
lc.SetMultipathTCP(true)
s.options.logger.Debugf("mptcp enabled: %v", lc.MultipathTCP())
}
ln, err := lc.Listen(context.Background(), network, s.httpServer.Addr)
if err != nil {
s.options.logger.Error(err)
return err
}
s.addr = ln.Addr()
if s.options.tlsEnabled {
s.httpServer.TLSConfig = s.options.tlsConfig
ln = tls.NewListener(ln, s.options.tlsConfig)
}
return s.httpServer.Serve(ln)
}
func (s *Server) Accept() (conn net.Conn, err error) {
select {
case conn = <-s.cqueue:
case <-s.closed:
err = http.ErrServerClosed
}
return
}
func (s *Server) Close() error {
select {
case <-s.closed:
return http.ErrServerClosed
default:
close(s.closed)
if s.http3Server != nil {
return s.http3Server.Close()
}
return s.httpServer.Close()
}
}
func (s *Server) handleAuthorize(w http.ResponseWriter, r *http.Request) {
if s.options.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(r, false)
s.options.logger.Trace(string(dump))
} else if s.options.logger.IsLevelEnabled(logger.DebugLevel) {
s.options.logger.Debugf("%s %s", r.Method, r.RequestURI)
}
raddr, _ := net.ResolveTCPAddr("tcp", r.RemoteAddr)
if raddr == nil {
raddr = &net.TCPAddr{}
}
// connection id
cid := xid.New().String()
c1, c2 := net.Pipe()
c := &serverConn{
Conn: c1,
localAddr: s.addr,
remoteAddr: raddr,
}
select {
case s.cqueue <- c:
default:
c.Close()
s.options.logger.Warnf("connection queue is full, client %s discarded", r.RemoteAddr)
w.WriteHeader(http.StatusTooManyRequests)
return
}
w.Write([]byte(fmt.Sprintf("token=%s", cid)))
s.conns.Store(cid, c2)
}
func (s *Server) handlePush(w http.ResponseWriter, r *http.Request) {
if s.options.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(r, false)
s.options.logger.Trace(string(dump))
} else if s.options.logger.IsLevelEnabled(logger.DebugLevel) {
s.options.logger.Debugf("%s %s", r.Method, r.RequestURI)
}
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusBadRequest)
return
}
if err := r.ParseForm(); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
cid := r.Form.Get("token")
v, ok := s.conns.Load(cid)
if !ok {
w.WriteHeader(http.StatusForbidden)
return
}
conn := v.(net.Conn)
br := bufio.NewReader(r.Body)
data, err := br.ReadString('\n')
if err != nil {
if err != io.EOF {
s.options.logger.Error(err)
w.WriteHeader(http.StatusPartialContent)
}
conn.Close()
s.conns.Delete(cid)
return
}
data = strings.TrimSuffix(data, "\n")
if len(data) == 0 {
return
}
b, err := base64.StdEncoding.DecodeString(data)
if err != nil {
s.options.logger.Error(err)
s.conns.Delete(cid)
conn.Close()
w.WriteHeader(http.StatusBadRequest)
return
}
conn.SetWriteDeadline(time.Now().Add(30 * time.Second))
defer conn.SetWriteDeadline(time.Time{})
if _, err := conn.Write(b); err != nil {
s.options.logger.Error(err)
s.conns.Delete(cid)
conn.Close()
w.WriteHeader(http.StatusGone)
}
}
func (s *Server) handlePull(w http.ResponseWriter, r *http.Request) {
if s.options.logger.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(r, false)
s.options.logger.Trace(string(dump))
} else if s.options.logger.IsLevelEnabled(logger.DebugLevel) {
s.options.logger.Debugf("%s %s", r.Method, r.RequestURI)
}
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusBadRequest)
return
}
if err := r.ParseForm(); err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
cid := r.Form.Get("token")
v, ok := s.conns.Load(cid)
if !ok {
w.WriteHeader(http.StatusForbidden)
return
}
conn := v.(net.Conn)
w.WriteHeader(http.StatusOK)
if fw, ok := w.(http.Flusher); ok {
fw.Flush()
}
b := bufpool.Get(s.options.readBufferSize)
defer bufpool.Put(b)
for {
conn.SetReadDeadline(time.Now().Add(s.options.readTimeout))
n, err := conn.Read(b)
if n > 0 {
bw := bufio.NewWriter(w)
bw.WriteString(base64.StdEncoding.EncodeToString(b[:n]))
bw.WriteString("\n")
if err := bw.Flush(); err != nil {
return
}
if fw, ok := w.(http.Flusher); ok {
fw.Flush()
}
}
if err != nil {
if errors.Is(err, os.ErrDeadlineExceeded) {
b[0] = '\n' // no data
w.Write(b[:1])
} else if errors.Is(err, io.EOF) {
// server connection closed
} else {
if !errors.Is(err, io.ErrClosedPipe) {
s.options.logger.Error(err)
}
s.conns.Delete(cid)
conn.Close()
}
return
}
}
}
+90
View File
@@ -0,0 +1,90 @@
package quic
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"io"
"net"
)
type cipherConn struct {
net.PacketConn
key []byte
}
func CipherPacketConn(conn net.PacketConn, key []byte) net.PacketConn {
return &cipherConn{
PacketConn: conn,
key: key,
}
}
func (conn *cipherConn) ReadFrom(data []byte) (n int, addr net.Addr, err error) {
n, addr, err = conn.PacketConn.ReadFrom(data)
if err != nil {
return
}
b, err := conn.decrypt(data[:n])
if err != nil {
return
}
copy(data, b)
return len(b), addr, nil
}
func (conn *cipherConn) WriteTo(data []byte, addr net.Addr) (n int, err error) {
b, err := conn.encrypt(data)
if err != nil {
return
}
_, err = conn.PacketConn.WriteTo(b, addr)
if err != nil {
return
}
return len(b), nil
}
func (conn *cipherConn) encrypt(data []byte) ([]byte, error) {
c, err := aes.NewCipher(conn.key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return nil, err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
return gcm.Seal(nonce, nonce, data, nil), nil
}
func (conn *cipherConn) decrypt(data []byte) ([]byte, error) {
c, err := aes.NewCipher(conn.key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return nil, err
}
nonceSize := gcm.NonceSize()
if len(data) < nonceSize {
return nil, errors.New("ciphertext too short")
}
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
return gcm.Open(nil, nonce, ciphertext, nil)
}
+196
View File
@@ -0,0 +1,196 @@
package relay
import (
"bytes"
"net"
"github.com/go-gost/core/common/bufpool"
"github.com/go-gost/gosocks5"
"github.com/go-gost/relay"
)
func StatusText(code uint8) string {
switch code {
case relay.StatusBadRequest:
return "Bad Request"
case relay.StatusForbidden:
return "Forbidden"
case relay.StatusHostUnreachable:
return "Host Unreachable"
case relay.StatusInternalServerError:
return "Internal Server Error"
case relay.StatusNetworkUnreachable:
return "Network Unreachable"
case relay.StatusServiceUnavailable:
return "Service Unavailable"
case relay.StatusTimeout:
return "Timeout"
case relay.StatusUnauthorized:
return "Unauthorized"
default:
return ""
}
}
type udpTunConn struct {
net.Conn
taddr net.Addr
}
func UDPTunClientConn(c net.Conn, targetAddr net.Addr) net.Conn {
return &udpTunConn{
Conn: c,
taddr: targetAddr,
}
}
func UDPTunClientPacketConn(c net.Conn) net.PacketConn {
return &udpTunConn{
Conn: c,
}
}
func UDPTunServerConn(c net.Conn) net.PacketConn {
return &udpTunConn{
Conn: c,
}
}
func (c *udpTunConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
socksAddr := gosocks5.Addr{}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
dgram := gosocks5.UDPDatagram{
Header: &header,
Data: b,
}
_, err = dgram.ReadFrom(c.Conn)
if err != nil {
return
}
n = len(dgram.Data)
if n > len(b) {
n = copy(b, dgram.Data)
}
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
return
}
func (c *udpTunConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *udpTunConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
socksAddr := gosocks5.Addr{}
if err = socksAddr.ParseFrom(addr.String()); err != nil {
return
}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
dgram := gosocks5.UDPDatagram{
Header: &header,
Data: b,
}
dgram.Header.Rsv = uint16(len(dgram.Data))
dgram.Header.Frag = 0xff // UDP tun relay flag, used by shadowsocks
_, err = dgram.WriteTo(c.Conn)
n = len(b)
return
}
func (c *udpTunConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.taddr)
}
var (
DefaultBufferSize = 4096
)
type udpConn struct {
net.PacketConn
raddr net.Addr
taddr net.Addr
bufferSize int
}
func UDPConn(c net.PacketConn, bufferSize int) net.PacketConn {
return &udpConn{
PacketConn: c,
bufferSize: bufferSize,
}
}
// ReadFrom reads an UDP datagram.
// NOTE: for server side,
// the returned addr is the target address the client want to relay to.
func (c *udpConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
rbuf := bufpool.Get(c.bufferSize)
defer bufpool.Put(rbuf)
n, c.raddr, err = c.PacketConn.ReadFrom(rbuf)
if err != nil {
return
}
socksAddr := gosocks5.Addr{}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
hlen, err := header.ReadFrom(bytes.NewReader(rbuf[:n]))
if err != nil {
return
}
n = copy(b, rbuf[hlen:n])
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
return
}
func (c *udpConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *udpConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
wbuf := bufpool.Get(c.bufferSize)
defer bufpool.Put(wbuf)
socksAddr := gosocks5.Addr{}
if err = socksAddr.ParseFrom(addr.String()); err != nil {
return
}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
dgram := gosocks5.UDPDatagram{
Header: &header,
Data: b,
}
buf := bytes.NewBuffer(wbuf[:0])
_, err = dgram.WriteTo(buf)
if err != nil {
return
}
_, err = c.PacketConn.WriteTo(buf.Bytes(), c.raddr)
n = len(b)
return
}
func (c *udpConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.taddr)
}
func (c *udpConn) RemoteAddr() net.Addr {
return c.raddr
}
+131
View File
@@ -0,0 +1,131 @@
package resolver
import (
"context"
"fmt"
"sync"
"time"
"github.com/go-gost/core/logger"
ctxvalue "github.com/go-gost/x/ctx"
"github.com/miekg/dns"
)
const (
defaultTTL = 60 * time.Second
)
type CacheKey string
// NewCacheKey generates resolver cache key from question of dns query.
func NewCacheKey(q *dns.Question) CacheKey {
if q == nil {
return ""
}
key := fmt.Sprintf("%s%s.%s", q.Name, dns.Class(q.Qclass).String(), dns.Type(q.Qtype).String())
return CacheKey(key)
}
type cacheItem struct {
msg *dns.Msg
ts time.Time
ttl time.Duration
}
type Cache struct {
m sync.Map
logger logger.Logger
}
func NewCache() *Cache {
return &Cache{}
}
func (c *Cache) WithLogger(logger logger.Logger) *Cache {
c.logger = logger
return c
}
func (c *Cache) Load(ctx context.Context, key CacheKey) (msg *dns.Msg, ttl time.Duration) {
v, ok := c.m.Load(key)
if !ok {
return
}
item, ok := v.(*cacheItem)
if !ok {
return
}
msg = item.msg.Copy()
for i := range msg.Answer {
d := uint32(time.Since(item.ts).Seconds())
if msg.Answer[i].Header().Ttl > d {
msg.Answer[i].Header().Ttl -= d
} else {
msg.Answer[i].Header().Ttl = 1
}
}
ttl = item.ttl - time.Since(item.ts)
if log := c.logger; log.IsLevelEnabled(logger.DebugLevel) {
if sid := ctxvalue.SidFromContext(ctx); sid != "" {
log = log.WithFields(map[string]any{
"sid": sid,
})
}
log.Debugf("resolver cache hit: %s, ttl: %v", key, ttl)
}
return
}
func (c *Cache) Store(ctx context.Context, key CacheKey, mr *dns.Msg, ttl time.Duration) {
if key == "" || mr == nil || ttl < 0 {
return
}
if ttl == 0 {
for _, answer := range mr.Answer {
v := time.Duration(answer.Header().Ttl) * time.Second
if ttl == 0 || ttl > v {
ttl = v
}
}
if ttl == 0 {
ttl = defaultTTL
}
} else {
for i := range mr.Answer {
mr.Answer[i].Header().Ttl = uint32(ttl.Seconds())
}
}
c.m.Store(key, &cacheItem{
msg: mr.Copy(),
ts: time.Now(),
ttl: ttl,
})
if log := c.logger; log.IsLevelEnabled(logger.DebugLevel) {
if sid := ctxvalue.SidFromContext(ctx); sid != "" {
log = log.WithFields(map[string]any{
"sid": sid,
})
}
log.Debugf("resolver cache store: %s, ttl: %v", key, ttl)
}
}
func (c *Cache) RefreshTTL(key CacheKey) {
v, ok := c.m.Load(key)
if !ok {
return
}
item, ok := v.(*cacheItem)
if !ok {
return
}
item.ts = time.Now()
}
@@ -0,0 +1,30 @@
package resolver
import (
"net"
"github.com/miekg/dns"
)
func AddSubnetOpt(m *dns.Msg, ip net.IP) {
if m == nil || ip == nil {
return
}
opt := new(dns.OPT)
opt.Hdr.Name = "."
opt.Hdr.Rrtype = dns.TypeOPT
e := new(dns.EDNS0_SUBNET)
e.Code = dns.EDNS0SUBNET
if ip := ip.To4(); ip != nil {
e.Family = 1
e.SourceNetmask = 24
e.Address = ip
} else {
e.Family = 2
e.SourceNetmask = 128
e.Address = ip.To16()
}
opt.Option = append(opt.Option, e)
m.Extra = append(m.Extra, opt)
}
+72
View File
@@ -0,0 +1,72 @@
package serial
import (
"context"
"errors"
"io"
"net"
"time"
)
type conn struct {
port io.ReadWriteCloser
laddr net.Addr
raddr net.Addr
cancel context.CancelFunc
}
func NewConn(port io.ReadWriteCloser, addr net.Addr, cancel context.CancelFunc) net.Conn {
return &conn{
port: port,
laddr: addr,
raddr: &Addr{Port: "@"},
cancel: cancel,
}
}
func (c *conn) Read(b []byte) (n int, err error) {
return c.port.Read(b)
}
func (c *conn) Write(b []byte) (n int, err error) {
return c.port.Write(b)
}
func (c *conn) LocalAddr() net.Addr {
return c.laddr
}
func (c *conn) RemoteAddr() net.Addr {
return c.raddr
}
func (c *conn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "com", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "com", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "com", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *conn) Close() (err error) {
if c.cancel != nil {
c.cancel()
}
return c.port.Close()
}
type Addr struct {
Port string
}
func (a *Addr) Network() string {
return "serial"
}
func (a *Addr) String() string {
return a.Port
}
+169
View File
@@ -0,0 +1,169 @@
/*
Goserial is a simple go package to allow you to read and write from
the serial port as a stream of bytes.
It aims to have the same API on all platforms, including windows. As
an added bonus, the windows package does not use cgo, so you can cross
compile for windows from another platform. Unfortunately goinstall
does not currently let you cross compile so you will have to do it
manually:
GOOS=windows make clean install
Currently there is very little in the way of configurability. You can
set the baud rate. Then you can Read(), Write(), or Close() the
connection. Read() will block until at least one byte is returned.
Write is the same. There is currently no exposed way to set the
timeouts, though patches are welcome.
Currently all ports are opened with 8 data bits, 1 stop bit, no
parity, no hardware flow control, and no software flow control. This
works fine for many real devices and many faux serial devices
including usb-to-serial converters and bluetooth serial ports.
You may Read() and Write() simulantiously on the same connection (from
different goroutines).
Example usage:
package main
import (
"github.com/tarm/serial"
"log"
)
func main() {
c := &serial.Config{Name: "COM5", Baud: 115200}
s, err := serial.OpenPort(c)
if err != nil {
log.Fatal(err)
}
n, err := s.Write([]byte("test"))
if err != nil {
log.Fatal(err)
}
buf := make([]byte, 128)
n, err = s.Read(buf)
if err != nil {
log.Fatal(err)
}
log.Print("%q", buf[:n])
}
*/
package serial
import (
"errors"
"io"
"time"
)
const DefaultSize = 8 // Default value for Config.Size
type StopBits byte
type Parity byte
const (
Stop1 StopBits = 1
Stop1Half StopBits = 15
Stop2 StopBits = 2
)
const (
ParityNone Parity = 'N'
ParityOdd Parity = 'O'
ParityEven Parity = 'E'
ParityMark Parity = 'M' // parity bit is always 1
ParitySpace Parity = 'S' // parity bit is always 0
)
// Config contains the information needed to open a serial port.
//
// Currently few options are implemented, but more may be added in the
// future (patches welcome), so it is recommended that you create a
// new config addressing the fields by name rather than by order.
//
// For example:
//
// c0 := &serial.Config{Name: "COM45", Baud: 115200, ReadTimeout: time.Millisecond * 500}
//
// or
//
// c1 := new(serial.Config)
// c1.Name = "/dev/tty.usbserial"
// c1.Baud = 115200
// c1.ReadTimeout = time.Millisecond * 500
type Config struct {
Name string
Baud int
ReadTimeout time.Duration // Total timeout
// Size is the number of data bits. If 0, DefaultSize is used.
Size byte
// Parity is the bit to use and defaults to ParityNone (no parity bit).
Parity Parity
// Number of stop bits to use. Default is 1 (1 stop bit).
StopBits StopBits
// RTSFlowControl bool
// DTRFlowControl bool
// XONFlowControl bool
// CRLFTranslate bool
}
// ErrBadSize is returned if Size is not supported.
var ErrBadSize error = errors.New("unsupported serial data size")
// ErrBadStopBits is returned if the specified StopBits setting not supported.
var ErrBadStopBits error = errors.New("unsupported stop bit setting")
// ErrBadParity is returned if the parity is not supported.
var ErrBadParity error = errors.New("unsupported parity setting")
// OpenPort opens a serial port with the specified configuration
func OpenPort(c *Config) (io.ReadWriteCloser, error) {
size, par, stop := c.Size, c.Parity, c.StopBits
if size == 0 {
size = DefaultSize
}
if par == 0 {
par = ParityNone
}
if stop == 0 {
stop = Stop1
}
return openPort(c.Name, c.Baud, size, par, stop, c.ReadTimeout)
}
// Converts the timeout values for Linux / POSIX systems
func posixTimeoutValues(readTimeout time.Duration) (vmin uint8, vtime uint8) {
const MAXUINT8 = 1<<8 - 1 // 255
// set blocking / non-blocking read
var minBytesToRead uint8 = 1
var readTimeoutInDeci int64
if readTimeout > 0 {
// EOF on zero read
minBytesToRead = 0
// convert timeout to deciseconds as expected by VTIME
readTimeoutInDeci = (readTimeout.Nanoseconds() / 1e6 / 100)
// capping the timeout
if readTimeoutInDeci < 1 {
// min possible timeout 1 Deciseconds (0.1s)
readTimeoutInDeci = 1
} else if readTimeoutInDeci > MAXUINT8 {
// max possible timeout is 255 deciseconds (25.5s)
readTimeoutInDeci = MAXUINT8
}
}
return minBytesToRead, uint8(readTimeoutInDeci)
}
// func SendBreak()
// func RegisterBreakHandler(func())
@@ -0,0 +1,165 @@
//go:build linux
// +build linux
package serial
import (
"fmt"
"os"
"time"
"unsafe"
"golang.org/x/sys/unix"
)
func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
var bauds = map[int]uint32{
50: unix.B50,
75: unix.B75,
110: unix.B110,
134: unix.B134,
150: unix.B150,
200: unix.B200,
300: unix.B300,
600: unix.B600,
1200: unix.B1200,
1800: unix.B1800,
2400: unix.B2400,
4800: unix.B4800,
9600: unix.B9600,
19200: unix.B19200,
38400: unix.B38400,
57600: unix.B57600,
115200: unix.B115200,
230400: unix.B230400,
460800: unix.B460800,
500000: unix.B500000,
576000: unix.B576000,
921600: unix.B921600,
1000000: unix.B1000000,
1152000: unix.B1152000,
1500000: unix.B1500000,
2000000: unix.B2000000,
2500000: unix.B2500000,
3000000: unix.B3000000,
3500000: unix.B3500000,
4000000: unix.B4000000,
}
rate, ok := bauds[baud]
if !ok {
return nil, fmt.Errorf("Unrecognized baud rate")
}
f, err := os.OpenFile(name, unix.O_RDWR|unix.O_NOCTTY|unix.O_NONBLOCK, 0666)
if err != nil {
return nil, err
}
defer func() {
if err != nil && f != nil {
f.Close()
}
}()
// Base settings
cflagToUse := unix.CREAD | unix.CLOCAL | rate
switch databits {
case 5:
cflagToUse |= unix.CS5
case 6:
cflagToUse |= unix.CS6
case 7:
cflagToUse |= unix.CS7
case 8:
cflagToUse |= unix.CS8
default:
return nil, ErrBadSize
}
// Stop bits settings
switch stopbits {
case Stop1:
// default is 1 stop bit
case Stop2:
cflagToUse |= unix.CSTOPB
default:
// Don't know how to set 1.5
return nil, ErrBadStopBits
}
// Parity settings
switch parity {
case ParityNone:
// default is no parity
case ParityOdd:
cflagToUse |= unix.PARENB
cflagToUse |= unix.PARODD
case ParityEven:
cflagToUse |= unix.PARENB
default:
return nil, ErrBadParity
}
fd := f.Fd()
vmin, vtime := posixTimeoutValues(readTimeout)
t := unix.Termios{
Iflag: unix.IGNPAR,
Cflag: cflagToUse,
Ispeed: rate,
Ospeed: rate,
}
t.Cc[unix.VMIN] = vmin
t.Cc[unix.VTIME] = vtime
if _, _, errno := unix.Syscall6(
unix.SYS_IOCTL,
uintptr(fd),
uintptr(unix.TCSETS),
uintptr(unsafe.Pointer(&t)),
0,
0,
0,
); errno != 0 {
return nil, errno
}
if err = unix.SetNonblock(int(fd), false); err != nil {
return
}
return &Port{f: f}, nil
}
type Port struct {
// We intentionly do not use an "embedded" struct so that we
// don't export File
f *os.File
}
func (p *Port) Read(b []byte) (n int, err error) {
return p.f.Read(b)
}
func (p *Port) Write(b []byte) (n int, err error) {
return p.f.Write(b)
}
// Discards data written to the port but not transmitted,
// or data received but not read
func (p *Port) Flush() error {
const TCFLSH = 0x540B
_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(p.f.Fd()),
uintptr(TCFLSH),
uintptr(unix.TCIOFLUSH),
)
if errno == 0 {
return nil
}
return errno
}
func (p *Port) Close() (err error) {
return p.f.Close()
}
@@ -0,0 +1,28 @@
//go:build !windows && !linux
// +build !windows,!linux
package serial
import (
"errors"
"time"
)
func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
return nil, errors.New("unsupported platform")
}
type Port struct {
}
func (p *Port) Read(b []byte) (n int, err error) {
return
}
func (p *Port) Write(b []byte) (n int, err error) {
return
}
func (p *Port) Close() (err error) {
return
}
@@ -0,0 +1,328 @@
//go:build windows
// +build windows
package serial
import (
"fmt"
"os"
"sync"
"syscall"
"time"
"unsafe"
)
type Port struct {
f *os.File
fd syscall.Handle
rl sync.Mutex
wl sync.Mutex
ro *syscall.Overlapped
wo *syscall.Overlapped
}
type structDCB struct {
DCBlength, BaudRate uint32
flags [4]byte
wReserved, XonLim, XoffLim uint16
ByteSize, Parity, StopBits byte
XonChar, XoffChar, ErrorChar, EofChar, EvtChar byte
wReserved1 uint16
}
type structTimeouts struct {
ReadIntervalTimeout uint32
ReadTotalTimeoutMultiplier uint32
ReadTotalTimeoutConstant uint32
WriteTotalTimeoutMultiplier uint32
WriteTotalTimeoutConstant uint32
}
func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
if len(name) > 0 && name[0] != '\\' {
name = "\\\\.\\" + name
}
h, err := syscall.CreateFile(syscall.StringToUTF16Ptr(name),
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
0,
nil,
syscall.OPEN_EXISTING,
syscall.FILE_ATTRIBUTE_NORMAL|syscall.FILE_FLAG_OVERLAPPED,
0)
if err != nil {
return nil, err
}
f := os.NewFile(uintptr(h), name)
defer func() {
if err != nil {
f.Close()
}
}()
if err = setCommState(h, baud, databits, parity, stopbits); err != nil {
return nil, err
}
if err = setupComm(h, 64, 64); err != nil {
return nil, err
}
if err = setCommTimeouts(h, readTimeout); err != nil {
return nil, err
}
if err = setCommMask(h); err != nil {
return nil, err
}
ro, err := newOverlapped()
if err != nil {
return nil, err
}
wo, err := newOverlapped()
if err != nil {
return nil, err
}
port := new(Port)
port.f = f
port.fd = h
port.ro = ro
port.wo = wo
return port, nil
}
func (p *Port) Close() error {
return p.f.Close()
}
func (p *Port) Write(buf []byte) (int, error) {
p.wl.Lock()
defer p.wl.Unlock()
if err := resetEvent(p.wo.HEvent); err != nil {
return 0, err
}
var n uint32
err := syscall.WriteFile(p.fd, buf, &n, p.wo)
if err != nil && err != syscall.ERROR_IO_PENDING {
return int(n), err
}
return getOverlappedResult(p.fd, p.wo)
}
func (p *Port) Read(buf []byte) (int, error) {
if p == nil || p.f == nil {
return 0, fmt.Errorf("Invalid port on read")
}
p.rl.Lock()
defer p.rl.Unlock()
if err := resetEvent(p.ro.HEvent); err != nil {
return 0, err
}
var done uint32
err := syscall.ReadFile(p.fd, buf, &done, p.ro)
if err != nil && err != syscall.ERROR_IO_PENDING {
return int(done), err
}
return getOverlappedResult(p.fd, p.ro)
}
// Discards data written to the port but not transmitted,
// or data received but not read
func (p *Port) Flush() error {
return purgeComm(p.fd)
}
var (
nSetCommState,
nSetCommTimeouts,
nSetCommMask,
nSetupComm,
nGetOverlappedResult,
nCreateEvent,
nResetEvent,
nPurgeComm,
nFlushFileBuffers uintptr
)
func init() {
k32, err := syscall.LoadLibrary("kernel32.dll")
if err != nil {
panic("LoadLibrary " + err.Error())
}
defer syscall.FreeLibrary(k32)
nSetCommState = getProcAddr(k32, "SetCommState")
nSetCommTimeouts = getProcAddr(k32, "SetCommTimeouts")
nSetCommMask = getProcAddr(k32, "SetCommMask")
nSetupComm = getProcAddr(k32, "SetupComm")
nGetOverlappedResult = getProcAddr(k32, "GetOverlappedResult")
nCreateEvent = getProcAddr(k32, "CreateEventW")
nResetEvent = getProcAddr(k32, "ResetEvent")
nPurgeComm = getProcAddr(k32, "PurgeComm")
nFlushFileBuffers = getProcAddr(k32, "FlushFileBuffers")
}
func getProcAddr(lib syscall.Handle, name string) uintptr {
addr, err := syscall.GetProcAddress(lib, name)
if err != nil {
panic(name + " " + err.Error())
}
return addr
}
func setCommState(h syscall.Handle, baud int, databits byte, parity Parity, stopbits StopBits) error {
var params structDCB
params.DCBlength = uint32(unsafe.Sizeof(params))
params.flags[0] = 0x01 // fBinary
params.flags[0] |= 0x10 // Assert DSR
params.BaudRate = uint32(baud)
params.ByteSize = databits
switch parity {
case ParityNone:
params.Parity = 0
case ParityOdd:
params.Parity = 1
case ParityEven:
params.Parity = 2
case ParityMark:
params.Parity = 3
case ParitySpace:
params.Parity = 4
default:
return ErrBadParity
}
switch stopbits {
case Stop1:
params.StopBits = 0
case Stop1Half:
params.StopBits = 1
case Stop2:
params.StopBits = 2
default:
return ErrBadStopBits
}
r, _, err := syscall.Syscall(nSetCommState, 2, uintptr(h), uintptr(unsafe.Pointer(&params)), 0)
if r == 0 {
return err
}
return nil
}
func setCommTimeouts(h syscall.Handle, readTimeout time.Duration) error {
var timeouts structTimeouts
const MAXDWORD = 1<<32 - 1
// blocking read by default
var timeoutMs int64 = MAXDWORD - 1
if readTimeout > 0 {
// non-blocking read
timeoutMs = readTimeout.Nanoseconds() / 1e6
if timeoutMs < 1 {
timeoutMs = 1
} else if timeoutMs > MAXDWORD-1 {
timeoutMs = MAXDWORD - 1
}
}
/* From http://msdn.microsoft.com/en-us/library/aa363190(v=VS.85).aspx
For blocking I/O see below:
Remarks:
If an application sets ReadIntervalTimeout and
ReadTotalTimeoutMultiplier to MAXDWORD and sets
ReadTotalTimeoutConstant to a value greater than zero and
less than MAXDWORD, one of the following occurs when the
ReadFile function is called:
If there are any bytes in the input buffer, ReadFile returns
immediately with the bytes in the buffer.
If there are no bytes in the input buffer, ReadFile waits
until a byte arrives and then returns immediately.
If no bytes arrive within the time specified by
ReadTotalTimeoutConstant, ReadFile times out.
*/
timeouts.ReadIntervalTimeout = MAXDWORD
timeouts.ReadTotalTimeoutMultiplier = MAXDWORD
timeouts.ReadTotalTimeoutConstant = uint32(timeoutMs)
r, _, err := syscall.Syscall(nSetCommTimeouts, 2, uintptr(h), uintptr(unsafe.Pointer(&timeouts)), 0)
if r == 0 {
return err
}
return nil
}
func setupComm(h syscall.Handle, in, out int) error {
r, _, err := syscall.Syscall(nSetupComm, 3, uintptr(h), uintptr(in), uintptr(out))
if r == 0 {
return err
}
return nil
}
func setCommMask(h syscall.Handle) error {
const EV_RXCHAR = 0x0001
r, _, err := syscall.Syscall(nSetCommMask, 2, uintptr(h), EV_RXCHAR, 0)
if r == 0 {
return err
}
return nil
}
func resetEvent(h syscall.Handle) error {
r, _, err := syscall.Syscall(nResetEvent, 1, uintptr(h), 0, 0)
if r == 0 {
return err
}
return nil
}
func purgeComm(h syscall.Handle) error {
const PURGE_TXABORT = 0x0001
const PURGE_RXABORT = 0x0002
const PURGE_TXCLEAR = 0x0004
const PURGE_RXCLEAR = 0x0008
r, _, err := syscall.Syscall(nPurgeComm, 2, uintptr(h),
PURGE_TXABORT|PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR, 0)
if r == 0 {
return err
}
return nil
}
func newOverlapped() (*syscall.Overlapped, error) {
var overlapped syscall.Overlapped
r, _, err := syscall.Syscall6(nCreateEvent, 4, 0, 1, 0, 0, 0, 0)
if r == 0 {
return nil, err
}
overlapped.HEvent = syscall.Handle(r)
return &overlapped, nil
}
func getOverlappedResult(h syscall.Handle, overlapped *syscall.Overlapped) (int, error) {
var n int
r, _, err := syscall.Syscall6(nGetOverlappedResult, 4,
uintptr(h),
uintptr(unsafe.Pointer(overlapped)),
uintptr(unsafe.Pointer(&n)), 1, 0, 0)
if r == 0 {
return n, err
}
return n, nil
}
+66
View File
@@ -0,0 +1,66 @@
package serial
import (
"strconv"
"strings"
)
const (
DefaultPort = "COM1"
DefaultBaudRate = 9600
)
// COM1,9600,odd
func ParseConfigFromAddr(addr string) *Config {
cfg := &Config{
Name: DefaultPort,
Baud: DefaultBaudRate,
}
ss := strings.Split(addr, ",")
switch len(ss) {
case 1:
cfg.Name = ss[0]
case 2:
cfg.Name = ss[0]
cfg.Baud, _ = strconv.Atoi(ss[1])
case 3:
cfg.Name = ss[0]
cfg.Baud, _ = strconv.Atoi(ss[1])
cfg.Parity = parseParity(ss[2])
}
return cfg
}
func AddrFromConfig(cfg *Config) string {
ss := []string{
cfg.Name,
strconv.Itoa(cfg.Baud),
}
switch cfg.Parity {
case ParityEven:
ss = append(ss, "even")
case ParityOdd:
ss = append(ss, "odd")
case ParityMark:
ss = append(ss, "mark")
case ParitySpace:
ss = append(ss, "space")
}
return strings.Join(ss, ",")
}
func parseParity(s string) Parity {
switch strings.ToLower(s) {
case "o", "odd":
return ParityOdd
case "e", "even":
return ParityEven
case "m", "mark":
return ParityMark
case "s", "space":
return ParitySpace
default:
return ParityNone
}
}
+58
View File
@@ -0,0 +1,58 @@
package sniffing
import (
"bufio"
"context"
"crypto/tls"
"encoding/binary"
"net/http"
"strings"
dissector "github.com/go-gost/tls-dissector"
)
const (
ProtoHTTP = "http"
ProtoTLS = "tls"
ProtoSSH = "ssh"
)
func Sniff(ctx context.Context, r *bufio.Reader) (proto string, err error) {
hdr, err := r.Peek(dissector.RecordHeaderLen)
if err != nil {
return
}
// try to sniff TLS traffic
tlsVersion := binary.BigEndian.Uint16(hdr[1:3])
if hdr[0] == dissector.Handshake &&
(tlsVersion >= tls.VersionTLS10 && tlsVersion <= tls.VersionTLS13) {
return ProtoTLS, nil
}
// try to sniff HTTP traffic
if isHTTP(string(hdr[:])) {
return ProtoHTTP, nil
}
if string(hdr) == "SSH-2" {
return ProtoSSH, nil
}
return
}
func isHTTP(s string) bool {
return strings.HasPrefix(http.MethodGet, s[:3]) ||
strings.HasPrefix(http.MethodPost, s[:4]) ||
strings.HasPrefix(http.MethodPut, s[:3]) ||
strings.HasPrefix(http.MethodDelete, s) ||
strings.HasPrefix(http.MethodOptions, s) ||
strings.HasPrefix(http.MethodPatch, s) ||
strings.HasPrefix(http.MethodHead, s[:4]) ||
strings.HasPrefix(http.MethodConnect, s) ||
strings.HasPrefix(http.MethodTrace, s) ||
// HTTP/2 connection preface
// PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
strings.HasPrefix(s, "PRI *")
}
+882
View File
@@ -0,0 +1,882 @@
package sniffing
import (
"bufio"
"bytes"
"context"
"crypto"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"errors"
"fmt"
"io"
"math"
"net"
"net/http"
"net/http/httputil"
"strings"
"time"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/observer/stats"
"github.com/go-gost/core/recorder"
dissector "github.com/go-gost/tls-dissector"
xbypass "github.com/go-gost/x/bypass"
ctxvalue "github.com/go-gost/x/ctx"
xio "github.com/go-gost/x/internal/io"
xnet "github.com/go-gost/x/internal/net"
xhttp "github.com/go-gost/x/internal/net/http"
tls_util "github.com/go-gost/x/internal/util/tls"
ws_util "github.com/go-gost/x/internal/util/ws"
xstats "github.com/go-gost/x/observer/stats"
stats_wrapper "github.com/go-gost/x/observer/stats/wrapper"
xrecorder "github.com/go-gost/x/recorder"
"golang.org/x/net/http/httpguts"
"golang.org/x/net/http2"
"golang.org/x/time/rate"
)
const (
// DefaultBodySize is the default HTTP body or websocket frame size to record.
DefaultBodySize = 64 * 1024 // 64KB
// MaxBodySize is the maximum HTTP body or websocket frame size to record.
MaxBodySize = 1024 * 1024 // 1MB
// DeafultSampleRate is the default websocket sample rate (samples per second).
DefaultSampleRate = 10.0
)
var (
DefaultCertPool = tls_util.NewMemoryCertPool()
)
type HandleOptions struct {
Dial func(ctx context.Context, network, address string) (net.Conn, error)
DialTLS func(ctx context.Context, network, address string, cfg *tls.Config) (net.Conn, error)
Bypass bypass.Bypass
RecorderObject *xrecorder.HandlerRecorderObject
Log logger.Logger
}
type HandleOption func(opts *HandleOptions)
func WithDial(dial func(ctx context.Context, network, address string) (net.Conn, error)) HandleOption {
return func(opts *HandleOptions) {
opts.Dial = dial
}
}
func WithDialTLS(dialTLS func(ctx context.Context, network, address string, cfg *tls.Config) (net.Conn, error)) HandleOption {
return func(opts *HandleOptions) {
opts.DialTLS = dialTLS
}
}
func WithBypass(bypass bypass.Bypass) HandleOption {
return func(opts *HandleOptions) {
opts.Bypass = bypass
}
}
func WithRecorderObject(ro *xrecorder.HandlerRecorderObject) HandleOption {
return func(opts *HandleOptions) {
opts.RecorderObject = ro
}
}
func WithLog(log logger.Logger) HandleOption {
return func(opts *HandleOptions) {
opts.Log = log
}
}
type Sniffer struct {
Websocket bool
WebsocketSampleRate float64
Recorder recorder.Recorder
RecorderOptions *recorder.Options
// MITM TLS termination
Certificate *x509.Certificate
PrivateKey crypto.PrivateKey
NegotiatedProtocol string
CertPool tls_util.CertPool
MitmBypass bypass.Bypass
ReadTimeout time.Duration
}
func (h *Sniffer) HandleHTTP(ctx context.Context, conn net.Conn, opts ...HandleOption) error {
var ho HandleOptions
for _, opt := range opts {
opt(&ho)
}
pStats := xstats.Stats{}
conn = stats_wrapper.WrapConn(conn, &pStats)
br := bufio.NewReader(conn)
req, err := http.ReadRequest(br)
if err != nil {
return err
}
log := ho.Log
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(req, false)
log.Trace(string(dump))
}
ro := ho.RecorderObject
ro.HTTP = &xrecorder.HTTPRecorderObject{
Host: req.Host,
Proto: req.Proto,
Scheme: req.URL.Scheme,
Method: req.Method,
URI: req.RequestURI,
Request: xrecorder.HTTPRequestRecorderObject{
ContentLength: req.ContentLength,
Header: req.Header.Clone(),
},
}
if clientIP := xhttp.GetClientIP(req); clientIP != nil {
ro.ClientIP = clientIP.String()
}
clientAddr := ro.RemoteAddr
if ro.ClientIP != "" {
if _, port, _ := net.SplitHostPort(ro.RemoteAddr); port != "" {
clientAddr = net.JoinHostPort(ro.ClientIP, port)
}
}
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(clientAddr))
// http/2
if req.Method == "PRI" && len(req.Header) == 0 && req.URL.Path == "*" && req.Proto == "HTTP/2.0" {
return h.serveH2(ctx, xnet.NewReadWriteConn(br, conn, conn), &ho)
}
host := req.Host
if host != "" {
if _, _, err := net.SplitHostPort(host); err != nil {
host = net.JoinHostPort(strings.Trim(host, "[]"), "80")
}
ro.Host = host
log = log.WithFields(map[string]any{
"host": host,
})
if ho.Bypass != nil && ho.Bypass.Contains(ctx, "tcp", host) {
return xbypass.ErrBypass
}
}
dial := ho.Dial
if dial == nil {
dial = (&net.Dialer{}).DialContext
}
cc, err := dial(ctx, "tcp", host)
if err != nil {
return err
}
defer cc.Close()
ro.Time = time.Time{}
shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, req, ro, &pStats, log)
if err != nil || shouldClose {
return err
}
for {
pStats.Reset()
req, err := http.ReadRequest(br)
if err != nil {
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
return nil
}
return err
}
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(req, false)
log.Trace(string(dump))
}
if shouldClose, err := h.httpRoundTrip(ctx, xio.NewReadWriter(br, conn), cc, req, ro, &pStats, log); err != nil || shouldClose {
return err
}
}
}
func (h *Sniffer) serveH2(ctx context.Context, conn net.Conn, ho *HandleOptions) error {
const expectedBody = "SM\r\n\r\n"
buf := make([]byte, len(expectedBody))
n, err := io.ReadFull(conn, buf)
if err != nil {
return fmt.Errorf("h2: error reading client preface: %s", err)
}
if string(buf[:n]) != expectedBody {
return errors.New("h2: invalid client preface")
}
ro := ho.RecorderObject
log := ho.Log
ro.Time = time.Time{}
tr := &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
if dial := ho.DialTLS; dial != nil {
return dial(ctx, network, addr, cfg)
}
cc, err := (&net.Dialer{}).DialContext(ctx, network, addr)
if err != nil {
return nil, err
}
cc = tls.Client(cc, cfg)
return cc, nil
},
}
defer tr.CloseIdleConnections()
(&http2.Server{}).ServeConn(conn, &http2.ServeConnOpts{
Context: ctx,
SawClientPreface: true,
Handler: &h2Handler{
transport: tr,
recorder: h.Recorder,
recorderOptions: h.RecorderOptions,
recorderObject: ro,
log: log,
},
})
return nil
}
func (h *Sniffer) httpRoundTrip(ctx context.Context, rw, cc io.ReadWriter, req *http.Request, ro *xrecorder.HandlerRecorderObject, pStats stats.Stats, log logger.Logger) (close bool, err error) {
close = true
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro = ro2
ro.Time = time.Now()
log.Infof("%s <-> %s", ro.RemoteAddr, req.Host)
defer func() {
if err != nil {
ro.Err = err.Error()
}
ro.InputBytes = pStats.Get(stats.KindInputBytes)
ro.OutputBytes = pStats.Get(stats.KindOutputBytes)
ro.Duration = time.Since(ro.Time)
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
log.WithFields(map[string]any{
"duration": time.Since(ro.Time),
"inputBytes": ro.InputBytes,
"outputBytes": ro.OutputBytes,
}).Infof("%s >-< %s", ro.RemoteAddr, req.Host)
}()
ro.HTTP = &xrecorder.HTTPRecorderObject{
Host: req.Host,
Proto: req.Proto,
Scheme: req.URL.Scheme,
Method: req.Method,
URI: req.RequestURI,
Request: xrecorder.HTTPRequestRecorderObject{
ContentLength: req.ContentLength,
Header: req.Header.Clone(),
},
}
// HTTP/1.0
if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
if strings.ToLower(req.Header.Get("Connection")) == "keep-alive" {
req.Header.Del("Connection")
} else {
req.Header.Set("Connection", "close")
}
}
var reqBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
err = req.Write(cc)
if reqBody != nil {
ro.HTTP.Request.Body = reqBody.Content()
ro.HTTP.Request.ContentLength = reqBody.Length()
}
if err != nil {
return
}
xio.SetReadDeadline(cc, time.Now().Add(h.ReadTimeout))
resp, err := http.ReadResponse(bufio.NewReader(cc), req)
if err != nil {
err = fmt.Errorf("read response: %w", err)
return
}
defer resp.Body.Close()
xio.SetReadDeadline(cc, time.Time{})
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.Response.Header = resp.Header
ro.HTTP.Response.ContentLength = resp.ContentLength
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Trace(string(dump))
}
if resp.StatusCode == http.StatusSwitchingProtocols {
h.handleUpgradeResponse(ctx, rw, cc, req, resp, ro, log)
return
}
// HTTP/1.0
if req.ProtoMajor == 1 && req.ProtoMinor == 0 {
if !resp.Close {
resp.Header.Set("Connection", "keep-alive")
}
resp.ProtoMajor = req.ProtoMajor
resp.ProtoMinor = req.ProtoMinor
}
var respBody *xhttp.Body
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
err = resp.Write(rw)
if respBody != nil {
ro.HTTP.Response.Body = respBody.Content()
ro.HTTP.Response.ContentLength = respBody.Length()
}
if err != nil {
err = fmt.Errorf("write response: %w", err)
return
}
if resp.ContentLength >= 0 {
close = resp.Close
}
return
}
func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
return ""
}
return h.Get("Upgrade")
}
func (h *Sniffer) handleUpgradeResponse(ctx context.Context, rw io.ReadWriter, cc io.ReadWriter, req *http.Request, res *http.Response, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !strings.EqualFold(reqUpType, resUpType) {
return fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)
}
res.Body = nil
if err := res.Write(rw); err != nil {
return fmt.Errorf("response write: %v", err)
}
if reqUpType == "websocket" && h.Websocket {
return h.sniffingWebsocketFrame(ctx, rw, cc, ro, log)
}
return xnet.Transport(rw, cc)
}
func (h *Sniffer) sniffingWebsocketFrame(ctx context.Context, rw, cc io.ReadWriter, ro *xrecorder.HandlerRecorderObject, log logger.Logger) error {
errc := make(chan error, 1)
sampleRate := h.WebsocketSampleRate
if sampleRate == 0 {
sampleRate = DefaultSampleRate
}
if sampleRate < 0 {
sampleRate = math.MaxFloat64
}
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
buf := &bytes.Buffer{}
for {
start := time.Now()
if err := h.copyWebsocketFrame(cc, rw, buf, "client", ro); err != nil {
errc <- err
return
}
if limiter.Allow() {
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
}
}
}()
go func() {
ro2 := &xrecorder.HandlerRecorderObject{}
*ro2 = *ro
ro := ro2
limiter := rate.NewLimiter(rate.Limit(sampleRate), int(sampleRate))
buf := &bytes.Buffer{}
for {
start := time.Now()
if err := h.copyWebsocketFrame(rw, cc, buf, "server", ro); err != nil {
errc <- err
return
}
if limiter.Allow() {
ro.Duration = time.Since(start)
ro.Time = time.Now()
if err := ro.Record(ctx, h.Recorder); err != nil {
log.Errorf("record: %v", err)
}
}
}
}()
<-errc
return nil
}
func (h *Sniffer) copyWebsocketFrame(w io.Writer, r io.Reader, buf *bytes.Buffer, from string, ro *xrecorder.HandlerRecorderObject) (err error) {
fr := ws_util.Frame{}
if _, err = fr.ReadFrom(r); err != nil {
return err
}
ws := &xrecorder.WebsocketRecorderObject{
From: from,
Fin: fr.Header.Fin,
Rsv1: fr.Header.Rsv1,
Rsv2: fr.Header.Rsv2,
Rsv3: fr.Header.Rsv3,
OpCode: int(fr.Header.OpCode),
Masked: fr.Header.Masked,
MaskKey: fr.Header.MaskKey,
Length: fr.Header.PayloadLength,
}
if opts := h.RecorderOptions; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
buf.Reset()
if _, err := io.Copy(buf, io.LimitReader(fr.Data, int64(bodySize))); err != nil {
return err
}
ws.Payload = buf.Bytes()
}
ro.Websocket = ws
length := uint64(fr.Header.Length()) + uint64(fr.Header.PayloadLength)
if from == "client" {
ro.InputBytes = length
ro.OutputBytes = 0
} else {
ro.InputBytes = 0
ro.OutputBytes = length
}
fr.Data = io.MultiReader(bytes.NewReader(buf.Bytes()), fr.Data)
if _, err := fr.WriteTo(w); err != nil {
return err
}
return nil
}
func (h *Sniffer) HandleTLS(ctx context.Context, conn net.Conn, opts ...HandleOption) error {
var ho HandleOptions
for _, opt := range opts {
opt(&ho)
}
buf := new(bytes.Buffer)
clientHello, err := dissector.ParseClientHello(io.TeeReader(conn, buf))
if err != nil {
return err
}
log := ho.Log
ro := ho.RecorderObject
ro.TLS = &xrecorder.TLSRecorderObject{
ServerName: clientHello.ServerName,
ClientHello: hex.EncodeToString(buf.Bytes()),
}
if len(clientHello.SupportedProtos) > 0 {
ro.TLS.Proto = clientHello.SupportedProtos[0]
}
ctx = ctxvalue.ContextWithClientAddr(ctx, ctxvalue.ClientAddr(ro.RemoteAddr))
host := clientHello.ServerName
if host != "" {
if _, _, err := net.SplitHostPort(host); err != nil {
host = net.JoinHostPort(strings.Trim(host, "[]"), "443")
}
ro.Host = host
if ho.Bypass != nil && ho.Bypass.Contains(ctx, "tcp", host) {
return xbypass.ErrBypass
}
}
dial := ho.Dial
if dial == nil {
dial = (&net.Dialer{}).DialContext
}
cc, err := dial(ctx, "tcp", host)
if err != nil {
return err
}
defer cc.Close()
if h.Certificate != nil && h.PrivateKey != nil &&
len(clientHello.SupportedProtos) > 0 && (clientHello.SupportedProtos[0] == "h2" || clientHello.SupportedProtos[0] == "http/1.1") {
if host == "" {
host = ro.Host
}
if h.MitmBypass == nil || !h.MitmBypass.Contains(ctx, "tcp", host) {
return h.terminateTLS(ctx, xnet.NewReadWriteConn(io.MultiReader(buf, conn), conn, conn), cc, clientHello, &ho)
}
}
if _, err := buf.WriteTo(cc); err != nil {
return err
}
xio.SetReadDeadline(cc, time.Now().Add(h.ReadTimeout))
serverHello, err := dissector.ParseServerHello(io.TeeReader(cc, buf))
xio.SetReadDeadline(cc, time.Time{})
if serverHello != nil {
ro.TLS.CipherSuite = tls_util.CipherSuite(serverHello.CipherSuite).String()
ro.TLS.CompressionMethod = serverHello.CompressionMethod
if serverHello.Proto != "" {
ro.TLS.Proto = serverHello.Proto
}
if serverHello.Version > 0 {
ro.TLS.Version = tls_util.Version(serverHello.Version).String()
}
}
if buf.Len() > 0 {
ro.TLS.ServerHello = hex.EncodeToString(buf.Bytes())
}
if _, err := buf.WriteTo(conn); err != nil {
return err
}
log.Infof("%s <-> %s", ro.RemoteAddr, ro.Host)
xnet.Transport(conn, cc)
log.WithFields(map[string]any{
"duration": time.Since(ro.Time),
}).Infof("%s >-< %s", ro.RemoteAddr, ro.Host)
return err
}
func (h *Sniffer) terminateTLS(ctx context.Context, conn, cc net.Conn, clientHello *dissector.ClientHelloInfo, ho *HandleOptions) error {
ro := ho.RecorderObject
log := ho.Log
nextProtos := clientHello.SupportedProtos
if h.NegotiatedProtocol != "" {
nextProtos = []string{h.NegotiatedProtocol}
}
cfg := &tls.Config{
ServerName: clientHello.ServerName,
NextProtos: nextProtos,
CipherSuites: clientHello.CipherSuites,
}
if cfg.ServerName == "" {
cfg.InsecureSkipVerify = true
}
clientConn := tls.Client(cc, cfg)
if err := clientConn.HandshakeContext(ctx); err != nil {
return err
}
cs := clientConn.ConnectionState()
ro.TLS.CipherSuite = tls_util.CipherSuite(cs.CipherSuite).String()
ro.TLS.Proto = cs.NegotiatedProtocol
ro.TLS.Version = tls_util.Version(cs.Version).String()
host := cfg.ServerName
if host == "" {
if host = cs.PeerCertificates[0].Subject.CommonName; host == "" {
host = ro.Host
}
}
if h, _, _ := net.SplitHostPort(host); h != "" {
host = h
}
negotiatedProtocol := cs.NegotiatedProtocol
if h.NegotiatedProtocol != "" {
negotiatedProtocol = h.NegotiatedProtocol
}
nextProtos = nil
if negotiatedProtocol != "" {
nextProtos = []string{negotiatedProtocol}
}
// cache the tls server handshake record.
wb := &bytes.Buffer{}
conn = xnet.NewReadWriteConn(conn, io.MultiWriter(wb, conn), conn)
serverConn := tls.Server(conn, &tls.Config{
NextProtos: nextProtos,
GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) {
certPool := h.CertPool
if certPool == nil {
certPool = DefaultCertPool
}
serverName := chi.ServerName
if serverName == "" {
serverName = host
}
cert, err := certPool.Get(serverName)
if cert != nil {
pool := x509.NewCertPool()
pool.AddCert(h.Certificate)
if _, err = cert.Verify(x509.VerifyOptions{
DNSName: serverName,
Roots: pool,
}); err != nil {
log.Warnf("verify cached certificate for %s: %v", serverName, err)
cert = nil
}
}
if cert == nil {
cert, err = tls_util.GenerateCertificate(serverName, 7*24*time.Hour, h.Certificate, h.PrivateKey)
certPool.Put(serverName, cert)
}
if err != nil {
return nil, err
}
return &tls.Certificate{
Certificate: [][]byte{cert.Raw},
PrivateKey: h.PrivateKey,
}, nil
},
})
err := serverConn.HandshakeContext(ctx)
if record, _ := dissector.ReadRecord(wb); record != nil {
wb.Reset()
record.WriteTo(wb)
ro.TLS.ServerHello = hex.EncodeToString(wb.Bytes())
}
if err != nil {
return err
}
opts := []HandleOption{
WithDial(func(ctx context.Context, network, address string) (net.Conn, error) {
return clientConn, nil
}),
WithDialTLS(func(ctx context.Context, network, address string, cfg *tls.Config) (net.Conn, error) {
return clientConn, nil
}),
WithRecorderObject(ro),
WithLog(log),
}
return h.HandleHTTP(ctx, serverConn, opts...)
}
type h2Handler struct {
transport http.RoundTripper
recorder recorder.Recorder
recorderOptions *recorder.Options
recorderObject *xrecorder.HandlerRecorderObject
log logger.Logger
}
func (h *h2Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log := h.log
ro := &xrecorder.HandlerRecorderObject{}
*ro = *h.recorderObject
ro.Time = time.Now()
var err error
log.Infof("%s <-> %s", ro.RemoteAddr, r.Host)
defer func() {
ro.Duration = time.Since(ro.Time)
if err != nil {
ro.Err = err.Error()
}
if err := ro.Record(r.Context(), h.recorder); err != nil {
log.Errorf("record: %v", err)
}
log.WithFields(map[string]any{
"duration": time.Since(ro.Time),
}).Infof("%s >-< %s", ro.RemoteAddr, r.Host)
}()
if clientIP := xhttp.GetClientIP(r); clientIP != nil {
ro.ClientIP = clientIP.String()
}
ro.HTTP = &xrecorder.HTTPRecorderObject{
Host: r.Host,
Proto: r.Proto,
Scheme: "https",
Method: r.Method,
URI: r.RequestURI,
Request: xrecorder.HTTPRequestRecorderObject{
ContentLength: r.ContentLength,
Header: r.Header.Clone(),
},
}
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpRequest(r, false)
log.Trace(string(dump))
}
url := r.URL
url.Scheme = "https"
url.Host = r.Host
req := &http.Request{
Method: r.Method,
URL: url,
Host: r.Host,
Header: r.Header,
Body: r.Body,
ContentLength: r.ContentLength,
Trailer: r.Trailer,
}
var reqBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
if req.Body != nil {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
reqBody = xhttp.NewBody(req.Body, bodySize)
req.Body = reqBody
}
}
resp, err := h.transport.RoundTrip(req.WithContext(r.Context()))
if reqBody != nil {
ro.HTTP.Request.Body = reqBody.Content()
ro.HTTP.Request.ContentLength = reqBody.Length()
}
if err != nil {
log.Error(err)
w.WriteHeader(http.StatusServiceUnavailable)
return
}
defer resp.Body.Close()
ro.HTTP.StatusCode = resp.StatusCode
ro.HTTP.Response.Header = resp.Header
ro.HTTP.Response.ContentLength = resp.ContentLength
if log.IsLevelEnabled(logger.TraceLevel) {
dump, _ := httputil.DumpResponse(resp, false)
log.Trace(string(dump))
}
h.setHeader(w, resp.Header)
w.WriteHeader(resp.StatusCode)
var respBody *xhttp.Body
if opts := h.recorderOptions; opts != nil && opts.HTTPBody {
bodySize := opts.MaxBodySize
if bodySize <= 0 {
bodySize = DefaultBodySize
}
if bodySize > MaxBodySize {
bodySize = MaxBodySize
}
respBody = xhttp.NewBody(resp.Body, bodySize)
resp.Body = respBody
}
io.Copy(w, resp.Body)
if respBody != nil {
ro.HTTP.Response.Body = respBody.Content()
ro.HTTP.Response.ContentLength = respBody.Length()
}
}
func (h *h2Handler) setHeader(w http.ResponseWriter, header http.Header) {
for k, v := range header {
for i := range v {
w.Header().Add(k, v[i])
}
}
}
+168
View File
@@ -0,0 +1,168 @@
package socks
import (
"bytes"
"math"
"net"
"github.com/go-gost/gosocks5"
)
type udpTunConn struct {
net.Conn
taddr net.Addr
}
func UDPTunClientConn(c net.Conn, targetAddr net.Addr) net.Conn {
return &udpTunConn{
Conn: c,
taddr: targetAddr,
}
}
func UDPTunClientPacketConn(c net.Conn) net.PacketConn {
return &udpTunConn{
Conn: c,
}
}
func UDPTunServerConn(c net.Conn) net.PacketConn {
return &udpTunConn{
Conn: c,
}
}
func (c *udpTunConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
socksAddr := gosocks5.Addr{}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
dgram := gosocks5.UDPDatagram{
Header: &header,
Data: b,
}
_, err = dgram.ReadFrom(c.Conn)
if err != nil {
return
}
n = len(dgram.Data)
if n > len(b) {
n = copy(b, dgram.Data)
}
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
return
}
func (c *udpTunConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *udpTunConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
socksAddr := gosocks5.Addr{}
if err = socksAddr.ParseFrom(addr.String()); err != nil {
return
}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
dgram := gosocks5.UDPDatagram{
Header: &header,
Data: b,
}
dgram.Header.Rsv = uint16(len(dgram.Data))
dgram.Header.Frag = 0xff // UDP tun relay flag, used by shadowsocks
_, err = dgram.WriteTo(c.Conn)
n = len(b)
return
}
func (c *udpTunConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.taddr)
}
const (
MaxMessageSize = math.MaxUint16
)
type udpConn struct {
net.PacketConn
raddr net.Addr
taddr net.Addr
}
func UDPConn(c net.PacketConn) net.PacketConn {
return &udpConn{
PacketConn: c,
}
}
// ReadFrom reads an UDP datagram.
// NOTE: for server side,
// the returned addr is the target address the client want to relay to.
func (c *udpConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
var rbuf [MaxMessageSize]byte
n, c.raddr, err = c.PacketConn.ReadFrom(rbuf[:])
if err != nil {
return
}
socksAddr := gosocks5.Addr{}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
hlen, err := header.ReadFrom(bytes.NewReader(rbuf[:n]))
if err != nil {
return
}
n = copy(b, rbuf[hlen:n])
addr, err = net.ResolveUDPAddr("udp", socksAddr.String())
return
}
func (c *udpConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *udpConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
socksAddr := gosocks5.Addr{}
if err = socksAddr.ParseFrom(addr.String()); err != nil {
return
}
header := gosocks5.UDPHeader{
Addr: &socksAddr,
}
dgram := gosocks5.UDPDatagram{
Header: &header,
Data: b,
}
var wbuf [MaxMessageSize]byte
buf := bytes.NewBuffer(wbuf[:][:0])
_, err = dgram.WriteTo(buf)
if err != nil {
return
}
_, err = c.PacketConn.WriteTo(buf.Bytes(), c.raddr)
n = len(b)
return
}
func (c *udpConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.taddr)
}
func (c *udpConn) RemoteAddr() net.Addr {
return c.raddr
}
+18
View File
@@ -0,0 +1,18 @@
package socks
const (
// MethodTLS is an extended SOCKS5 method with tls encryption support.
MethodTLS uint8 = 0x80
// MethodTLSAuth is an extended SOCKS5 method with tls encryption and authentication support.
MethodTLSAuth uint8 = 0x82
// MethodMux is an extended SOCKS5 method for stream multiplexing.
MethodMux = 0x88
)
const (
// CmdMuxBind is an extended SOCKS5 request CMD for
// multiplexing transport with the binding server.
CmdMuxBind uint8 = 0xF2
// CmdUDPTun is an extended SOCKS5 request CMD for UDP over TCP.
CmdUDPTun uint8 = 0xF3
)
+91
View File
@@ -0,0 +1,91 @@
package ss
import (
"bytes"
"math"
"net"
"github.com/go-gost/gosocks5"
)
const (
MaxMessageSize = math.MaxUint16
)
var (
_ net.PacketConn = (*UDPConn)(nil)
_ net.Conn = (*UDPConn)(nil)
)
type UDPConn struct {
net.PacketConn
raddr net.Addr
taddr net.Addr
}
func UDPClientConn(c net.PacketConn, remoteAddr, targetAddr net.Addr) *UDPConn {
return &UDPConn{
PacketConn: c,
raddr: remoteAddr,
taddr: targetAddr,
}
}
func UDPServerConn(c net.PacketConn, remoteAddr net.Addr) *UDPConn {
return &UDPConn{
PacketConn: c,
raddr: remoteAddr,
}
}
func (c *UDPConn) ReadFrom(b []byte) (n int, addr net.Addr, err error) {
var rbuf [MaxMessageSize]byte
n, _, err = c.PacketConn.ReadFrom(rbuf[:])
if err != nil {
return
}
saddr := gosocks5.Addr{}
addrLen, err := saddr.ReadFrom(bytes.NewReader(rbuf[:n]))
if err != nil {
return
}
n = copy(b, rbuf[addrLen:n])
addr, err = net.ResolveUDPAddr("udp", saddr.String())
return
}
func (c *UDPConn) Read(b []byte) (n int, err error) {
n, _, err = c.ReadFrom(b)
return
}
func (c *UDPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) {
var wbuf [MaxMessageSize]byte
socksAddr := gosocks5.Addr{}
if err = socksAddr.ParseFrom(addr.String()); err != nil {
return
}
addrLen, err := socksAddr.Encode(wbuf[:])
if err != nil {
return
}
n = copy(wbuf[addrLen:], b)
_, err = c.PacketConn.WriteTo(wbuf[:addrLen+n], c.raddr)
return
}
func (c *UDPConn) Write(b []byte) (n int, err error) {
return c.WriteTo(b, c.taddr)
}
func (c *UDPConn) RemoteAddr() net.Addr {
return c.raddr
}
+61
View File
@@ -0,0 +1,61 @@
package ss
import (
"bytes"
"net"
"github.com/shadowsocks/go-shadowsocks2/core"
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
)
type shadowCipher struct {
cipher *ss.Cipher
}
func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn {
return ss.NewConn(conn, c.cipher.Copy())
}
func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn {
return ss.NewSecurePacketConn(conn, c.cipher.Copy())
}
func ShadowCipher(method, password string, key string) (core.Cipher, error) {
if method == "" || password == "" {
return nil, nil
}
c, _ := ss.NewCipher(method, password)
if c != nil {
return &shadowCipher{cipher: c}, nil
}
return core.PickCipher(method, []byte(key), password)
}
// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write,
// we wrap around it to make io.Copy happy.
type shadowConn struct {
net.Conn
wbuf bytes.Buffer
}
func ShadowConn(conn net.Conn, header []byte) net.Conn {
c := &shadowConn{
Conn: conn,
}
c.wbuf.Write(header)
return c
}
func (c *shadowConn) Write(b []byte) (n int, err error) {
n = len(b) // force byte length consistent
if c.wbuf.Len() > 0 {
c.wbuf.Write(b) // append the data to the cached header
_, err = c.Conn.Write(c.wbuf.Bytes())
c.wbuf.Reset()
return
}
_, err = c.Conn.Write(b)
return
}
+48
View File
@@ -0,0 +1,48 @@
package ssh
import (
"net"
"golang.org/x/crypto/ssh"
)
// a dummy ssh client conn used by client connector
type ClientConn struct {
net.Conn
client *ssh.Client
}
func NewClientConn(session *Session) net.Conn {
return &ClientConn{
Conn: session.Conn,
client: session.client,
}
}
func (c *ClientConn) Client() *ssh.Client {
return c.client
}
type sshConn struct {
channel ssh.Channel
net.Conn
}
func NewConn(conn net.Conn, channel ssh.Channel) net.Conn {
return &sshConn{
Conn: conn,
channel: channel,
}
}
func (c *sshConn) Read(b []byte) (n int, err error) {
return c.channel.Read(b)
}
func (c *sshConn) Write(b []byte) (n int, err error) {
return c.channel.Write(b)
}
func (c *sshConn) Close() error {
return c.channel.Close()
}
+126
View File
@@ -0,0 +1,126 @@
package ssh
import (
"context"
"net"
"time"
"github.com/go-gost/core/logger"
"golang.org/x/crypto/ssh"
)
const (
defaultKeepaliveInterval = 30 * time.Second
defaultKeepaliveTimeout = 15 * time.Second
defaultkeepaliveRetries = 1
)
type Session struct {
net.Conn
client *ssh.Client
closed chan struct{}
dead chan struct{}
log logger.Logger
}
func NewSession(c net.Conn, client *ssh.Client, log logger.Logger) *Session {
return &Session{
Conn: c,
client: client,
closed: make(chan struct{}),
dead: make(chan struct{}),
log: log,
}
}
func (s *Session) OpenChannel(name string) (ssh.Channel, <-chan *ssh.Request, error) {
return s.client.OpenChannel(name, nil)
}
func (s *Session) IsClosed() bool {
select {
case <-s.dead:
return true
case <-s.closed:
return true
default:
}
return false
}
func (s *Session) Wait() error {
defer close(s.closed)
return s.client.Wait()
}
func (s *Session) WaitClose() {
defer s.client.Close()
select {
case <-s.dead:
s.log.Debugf("session is dead")
case <-s.closed:
s.log.Debugf("session is closed")
}
}
func (s *Session) Keepalive(interval, timeout time.Duration, retries int) {
if interval <= 0 {
interval = defaultKeepaliveInterval
}
if timeout <= 0 {
timeout = defaultKeepaliveTimeout
}
if retries <= 0 {
retries = defaultkeepaliveRetries
}
s.log.Debugf("keepalive is enabled, interval: %v, timeout: %v, retries: %d", interval, timeout, retries)
defer close(s.dead)
t := time.NewTicker(interval)
defer t.Stop()
count := retries
for {
select {
case <-t.C:
start := time.Now()
err := func() error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
select {
case err := <-s.ping():
return err
case <-ctx.Done():
return ctx.Err()
}
}()
if err != nil {
s.log.Debugf("ssh ping: %v", err)
count--
if count == 0 {
return
}
continue
}
s.log.Debugf("ssh ping OK, RTT: %v", time.Since(start))
count = retries
case <-s.closed:
return
}
}
}
func (s *Session) ping() <-chan error {
ch := make(chan error, 1)
go func() {
defer close(ch)
if _, _, err := s.client.SendRequest("ping", true, nil); err != nil {
ch <- err
}
}()
return ch
}
+76
View File
@@ -0,0 +1,76 @@
package ssh
import (
"context"
"errors"
"fmt"
"os"
"github.com/go-gost/core/auth"
"golang.org/x/crypto/ssh"
)
const (
GostSSHTunnelRequest = "gost-tunnel" // extended request type for ssh tunnel
)
var (
ErrSessionDead = errors.New("session is dead")
)
// PasswordCallbackFunc is a callback function used by SSH server.
// It authenticates user using a password.
type PasswordCallbackFunc func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error)
func PasswordCallback(au auth.Authenticator) PasswordCallbackFunc {
if au == nil {
return nil
}
return func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
if _, ok := au.Authenticate(context.Background(), conn.User(), string(password)); ok {
return nil, nil
}
return nil, fmt.Errorf("password rejected for %s", conn.User())
}
}
// PublicKeyCallbackFunc is a callback function used by SSH server.
// It offers a public key for authentication.
type PublicKeyCallbackFunc func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error)
func PublicKeyCallback(keys map[string]bool) PublicKeyCallbackFunc {
if len(keys) == 0 {
return nil
}
return func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
if keys[string(pubKey.Marshal())] {
return &ssh.Permissions{
// Record the public key used for authentication.
Extensions: map[string]string{
"pubkey-fp": ssh.FingerprintSHA256(pubKey),
},
}, nil
}
return nil, fmt.Errorf("unknown public key for %q", c.User())
}
}
// ParseSSHAuthorizedKeysFile parses ssh authorized keys file.
func ParseAuthorizedKeysFile(name string) (map[string]bool, error) {
authorizedKeysBytes, err := os.ReadFile(name)
if err != nil {
return nil, err
}
authorizedKeysMap := make(map[string]bool)
for len(authorizedKeysBytes) > 0 {
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
if err != nil {
return nil, err
}
authorizedKeysMap[string(pubKey.Marshal())] = true
authorizedKeysBytes = rest
}
return authorizedKeysMap, nil
}
+118
View File
@@ -0,0 +1,118 @@
package sshd
import (
"context"
"errors"
"net"
"time"
"golang.org/x/crypto/ssh"
)
type DirectForwardConn struct {
conn ssh.Conn
channel ssh.Channel
dstAddr string
}
func NewDirectForwardConn(conn ssh.Conn, channel ssh.Channel, dstAddr string) net.Conn {
return &DirectForwardConn{
conn: conn,
channel: channel,
dstAddr: dstAddr,
}
}
func (c *DirectForwardConn) Read(b []byte) (n int, err error) {
return c.channel.Read(b)
}
func (c *DirectForwardConn) Write(b []byte) (n int, err error) {
return c.channel.Write(b)
}
func (c *DirectForwardConn) Close() error {
return c.channel.Close()
}
func (c *DirectForwardConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *DirectForwardConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *DirectForwardConn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *DirectForwardConn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *DirectForwardConn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *DirectForwardConn) DstAddr() string {
return c.dstAddr
}
type RemoteForwardConn struct {
ctx context.Context
conn ssh.Conn
req *ssh.Request
}
func NewRemoteForwardConn(ctx context.Context, conn ssh.Conn, req *ssh.Request) net.Conn {
return &RemoteForwardConn{
ctx: ctx,
conn: conn,
req: req,
}
}
func (c *RemoteForwardConn) Conn() ssh.Conn {
return c.conn
}
func (c *RemoteForwardConn) Request() *ssh.Request {
return c.req
}
func (c *RemoteForwardConn) Read(b []byte) (n int, err error) {
return 0, &net.OpError{Op: "read", Net: "nop", Source: nil, Addr: nil, Err: errors.New("read not supported")}
}
func (c *RemoteForwardConn) Write(b []byte) (n int, err error) {
return 0, &net.OpError{Op: "write", Net: "nop", Source: nil, Addr: nil, Err: errors.New("write not supported")}
}
func (c *RemoteForwardConn) Close() error {
return &net.OpError{Op: "close", Net: "nop", Source: nil, Addr: nil, Err: errors.New("close not supported")}
}
func (c *RemoteForwardConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *RemoteForwardConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *RemoteForwardConn) SetDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *RemoteForwardConn) SetReadDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *RemoteForwardConn) SetWriteDeadline(t time.Time) error {
return &net.OpError{Op: "set", Net: "nop", Source: nil, Addr: nil, Err: errors.New("deadline not supported")}
}
func (c *RemoteForwardConn) Done() <-chan struct{} {
return c.ctx.Done()
}
+65
View File
@@ -0,0 +1,65 @@
package stats
import (
"sync"
"github.com/go-gost/core/observer"
"github.com/go-gost/core/observer/stats"
xstats "github.com/go-gost/x/observer/stats"
)
type HandlerStats struct {
service string
stats map[string]stats.Stats
resetTraffic bool
mu sync.RWMutex
}
func NewHandlerStats(service string, resetTraffic bool) *HandlerStats {
return &HandlerStats{
service: service,
stats: make(map[string]stats.Stats),
resetTraffic: resetTraffic,
}
}
func (p *HandlerStats) Stats(client string) stats.Stats {
p.mu.RLock()
pstats := p.stats[client]
p.mu.RUnlock()
if pstats != nil {
return pstats
}
p.mu.Lock()
defer p.mu.Unlock()
pstats = p.stats[client]
if pstats == nil {
pstats = xstats.NewStats(p.resetTraffic)
}
p.stats[client] = pstats
return pstats
}
func (p *HandlerStats) Events() (events []observer.Event) {
p.mu.RLock()
defer p.mu.RUnlock()
for k, v := range p.stats {
if !v.IsUpdated() {
continue
}
events = append(events, xstats.StatsEvent{
Kind: "handler",
Service: p.service,
Client: k,
TotalConns: v.Get(stats.KindTotalConns),
CurrentConns: v.Get(stats.KindCurrentConns),
InputBytes: v.Get(stats.KindInputBytes),
OutputBytes: v.Get(stats.KindOutputBytes),
TotalErrs: v.Get(stats.KindTotalErrs),
})
}
return
}
+16
View File
@@ -0,0 +1,16 @@
package tap
import "net"
// Route is an IP routing entry
type Route struct {
Net net.IPNet
Gateway net.IP
}
type Config struct {
Name string
Net string
MTU int
Gateway string
Routes []Route
}
+461
View File
@@ -0,0 +1,461 @@
package tls
import (
"crypto"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"errors"
"fmt"
"math/big"
"net"
"os"
"strconv"
"strings"
"time"
"github.com/go-gost/core/logger"
"github.com/go-gost/x/config"
"github.com/patrickmn/go-cache"
)
const (
VersionTLS10 = "VersionTLS10"
VersionTLS11 = "VersionTLS11"
VersionTLS12 = "VersionTLS12"
VersionTLS13 = "VersionTLS13"
)
// Cipher suites from https://pkg.go.dev/crypto/tls#pkg-constants
const (
// TLS 1.0 - 1.2 cipher suites.
TLS_RSA_WITH_RC4_128_SHA = "TLS_RSA_WITH_RC4_128_SHA"
TLS_RSA_WITH_3DES_EDE_CBC_SHA = "TLS_RSA_WITH_3DES_EDE_CBC_SHA"
TLS_RSA_WITH_AES_128_CBC_SHA = "TLS_RSA_WITH_AES_128_CBC_SHA"
TLS_RSA_WITH_AES_256_CBC_SHA = "TLS_RSA_WITH_AES_256_CBC_SHA"
TLS_RSA_WITH_AES_128_CBC_SHA256 = "TLS_RSA_WITH_AES_128_CBC_SHA256"
TLS_RSA_WITH_AES_128_GCM_SHA256 = "TLS_RSA_WITH_AES_128_GCM_SHA256"
TLS_RSA_WITH_AES_256_GCM_SHA384 = "TLS_RSA_WITH_AES_256_GCM_SHA384"
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA"
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA"
TLS_ECDHE_RSA_WITH_RC4_128_SHA = "TLS_ECDHE_RSA_WITH_RC4_128_SHA"
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA"
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA"
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256"
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
// TLS 1.3 cipher suites.
TLS_AES_128_GCM_SHA256 = "TLS_AES_128_GCM_SHA256"
TLS_AES_256_GCM_SHA384 = "TLS_AES_256_GCM_SHA384"
TLS_CHACHA20_POLY1305_SHA256 = "TLS_CHACHA20_POLY1305_SHA256"
// TLS_FALLBACK_SCSV isn't a standard cipher suite but an indicator
// that the client is doing version fallback. See RFC 7507.
TLS_FALLBACK_SCSV = "TLS_FALLBACK_SCSV"
)
var (
cipherSuites = map[uint16]string{
tls.TLS_RSA_WITH_RC4_128_SHA: TLS_RSA_WITH_RC4_128_SHA,
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA: TLS_RSA_WITH_3DES_EDE_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA: TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA: TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA256: TLS_RSA_WITH_AES_128_CBC_SHA256,
tls.TLS_RSA_WITH_AES_128_GCM_SHA256: TLS_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384: TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA: TLS_ECDHE_RSA_WITH_RC4_128_SHA,
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_AES_128_GCM_SHA256: TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384: TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256: TLS_CHACHA20_POLY1305_SHA256,
}
versions = map[uint16]string{
tls.VersionSSL30: "sslv3",
tls.VersionTLS10: "tls1.0",
tls.VersionTLS11: "tls1.1",
tls.VersionTLS12: "tls1.2",
tls.VersionTLS13: "tls1.3",
}
)
type CipherSuite uint16
func (cs CipherSuite) String() string {
if v, ok := cipherSuites[uint16(cs)]; ok {
return v
}
return strconv.Itoa(int(cs))
}
type Version uint16
func (ver Version) String() string {
if v, ok := versions[uint16(ver)]; ok {
return v
}
return strconv.Itoa(int(ver))
}
// LoadDefaultConfig loads the certificate from cert & key files and optional CA file.
func LoadDefaultConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
pool, err := loadCA(caFile)
if err != nil {
logger.Default().Debugf("load default CA(%s): %v", caFile, err)
}
if pool != nil {
cfg.ClientCAs = pool
cfg.ClientAuth = tls.RequireAndVerifyClientCert
}
return cfg, nil
}
// LoadServerConfig loads the certificate from cert & key files and client CA file.
func LoadServerConfig(config *config.TLSConfig) (*tls.Config, error) {
if config.CertFile == "" && config.KeyFile == "" {
return nil, nil
}
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
if err != nil {
return nil, err
}
cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
pool, err := loadCA(config.CAFile)
if err != nil {
return nil, err
}
if pool != nil {
cfg.ClientCAs = pool
cfg.ClientAuth = tls.RequireAndVerifyClientCert
}
SetTLSOptions(cfg, config.Options)
return cfg, nil
}
// LoadClientConfig loads the certificate from cert & key files and CA file.
func LoadClientConfig(config *config.TLSConfig) (*tls.Config, error) {
var cfg *tls.Config
if config.CertFile == "" && config.KeyFile == "" {
cfg = &tls.Config{}
} else {
cert, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
if err != nil {
return nil, err
}
cfg = &tls.Config{
Certificates: []tls.Certificate{cert},
}
}
rootCAs, err := loadCA(config.CAFile)
if err != nil {
return nil, err
}
cfg.RootCAs = rootCAs
cfg.ServerName = config.ServerName
cfg.InsecureSkipVerify = !config.Secure
if config.Options != nil {
SetTLSOptions(cfg, config.Options)
}
// If the root ca is given, but skip verify, we verify the certificate manually.
if cfg.RootCAs != nil && !config.Secure {
cfg.VerifyConnection = func(state tls.ConnectionState) error {
opts := x509.VerifyOptions{
Roots: cfg.RootCAs,
CurrentTime: time.Now(),
DNSName: "",
Intermediates: x509.NewCertPool(),
}
certs := state.PeerCertificates
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
_, err := certs[0].Verify(opts)
return err
}
}
return cfg, nil
}
func SetTLSOptions(cfg *tls.Config, opts *config.TLSOptions) {
if cfg == nil || opts == nil {
return
}
switch strings.ToLower(opts.MinVersion) {
case strings.ToLower(VersionTLS10):
cfg.MinVersion = tls.VersionTLS10
case strings.ToLower(VersionTLS11):
cfg.MinVersion = tls.VersionTLS11
case strings.ToLower(VersionTLS12):
cfg.MinVersion = tls.VersionTLS12
case strings.ToLower(VersionTLS13):
cfg.MinVersion = tls.VersionTLS13
}
switch strings.ToLower(opts.MaxVersion) {
case strings.ToLower(VersionTLS10):
cfg.MaxVersion = tls.VersionTLS10
case strings.ToLower(VersionTLS11):
cfg.MaxVersion = tls.VersionTLS11
case strings.ToLower(VersionTLS12):
cfg.MaxVersion = tls.VersionTLS12
case strings.ToLower(VersionTLS13):
cfg.MaxVersion = tls.VersionTLS13
}
for _, v := range opts.CipherSuites {
switch strings.ToUpper(v) {
case TLS_RSA_WITH_RC4_128_SHA:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_RC4_128_SHA)
case TLS_RSA_WITH_3DES_EDE_CBC_SHA:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA)
case TLS_RSA_WITH_AES_128_CBC_SHA:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_CBC_SHA)
case TLS_RSA_WITH_AES_256_CBC_SHA:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_256_CBC_SHA)
case TLS_RSA_WITH_AES_128_CBC_SHA256:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_CBC_SHA256)
case TLS_RSA_WITH_AES_128_GCM_SHA256:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_128_GCM_SHA256)
case TLS_RSA_WITH_AES_256_GCM_SHA384:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_RSA_WITH_AES_256_GCM_SHA384)
case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA)
case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)
case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)
case TLS_ECDHE_RSA_WITH_RC4_128_SHA:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA)
case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA)
case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)
case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)
case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)
case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)
case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)
case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)
case TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)
case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)
case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256)
case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
cfg.CipherSuites = append(cfg.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256)
}
}
cfg.NextProtos = opts.ALPN
}
func loadCA(caFile string) (cp *x509.CertPool, err error) {
if caFile == "" {
return
}
cp = x509.NewCertPool()
data, err := os.ReadFile(caFile)
if err != nil {
return nil, err
}
if !cp.AppendCertsFromPEM(data) {
return nil, fmt.Errorf("loadCA %s: AppendCertsFromPEM failed", caFile)
}
return
}
// Wrap a net.Conn into a client tls connection, performing any
// additional verification as needed.
//
// As of go 1.3, crypto/tls only supports either doing no certificate
// verification, or doing full verification including of the peer's
// DNS name. For consul, we want to validate that the certificate is
// signed by a known CA, but because consul doesn't use DNS names for
// node names, we don't verify the certificate DNS names. Since go 1.3
// no longer supports this mode of operation, we have to do it
// manually.
//
// This code is taken from consul:
// https://github.com/hashicorp/consul/blob/master/tlsutil/config.go
func WrapTLSClient(conn net.Conn, tlsConfig *tls.Config, timeout time.Duration) (net.Conn, error) {
var err error
var tlsConn *tls.Conn
if timeout > 0 {
conn.SetDeadline(time.Now().Add(timeout))
defer conn.SetDeadline(time.Time{})
}
tlsConn = tls.Client(conn, tlsConfig)
// Otherwise perform handshake, but don't verify the domain
//
// The following is lightly-modified from the doFullHandshake
// method in https://golang.org/src/crypto/tls/handshake_client.go
if err = tlsConn.Handshake(); err != nil {
tlsConn.Close()
return nil, err
}
// We can do this in `tls.Config.VerifyConnection`, which effective for
// other TLS protocols such as WebSocket. See `route.go:parseChainNode`
/*
// If crypto/tls is doing verification, there's no need to do our own.
if tlsConfig.InsecureSkipVerify == false {
return tlsConn, nil
}
// Similarly if we use host's CA, we can do full handshake
if tlsConfig.RootCAs == nil {
return tlsConn, nil
}
opts := x509.VerifyOptions{
Roots: tlsConfig.RootCAs,
CurrentTime: time.Now(),
DNSName: "",
Intermediates: x509.NewCertPool(),
}
certs := tlsConn.ConnectionState().PeerCertificates
for i, cert := range certs {
if i == 0 {
continue
}
opts.Intermediates.AddCert(cert)
}
_, err = certs[0].Verify(opts)
if err != nil {
tlsConn.Close()
return nil, err
}
*/
return tlsConn, err
}
var (
ErrCertNotFound = errors.New("certificate not found")
)
type CertPool interface {
Get(serverName string) (*x509.Certificate, error)
Put(serverName string, cert *x509.Certificate)
}
type memoryCertPool struct {
cache *cache.Cache
}
func NewMemoryCertPool() CertPool {
return &memoryCertPool{
cache: cache.New(24*7*time.Hour, 1*time.Hour),
}
}
func (p *memoryCertPool) Get(serverName string) (*x509.Certificate, error) {
v, ok := p.cache.Get(serverName)
if !ok {
return nil, ErrCertNotFound
}
return v.(*x509.Certificate), nil
}
func (p *memoryCertPool) Put(serverName string, cert *x509.Certificate) {
p.cache.Set(serverName, cert, cache.DefaultExpiration)
}
func GenerateCertificate(serverName string, validity time.Duration, caCert *x509.Certificate, caKey crypto.PrivateKey) (*x509.Certificate, error) {
if host, _, _ := net.SplitHostPort(serverName); host != "" {
serverName = host
}
tmpl := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano() / 100000),
Subject: pkix.Name{
Organization: []string{"GOST"},
},
NotBefore: time.Now().Add(-validity),
NotAfter: time.Now().Add(validity),
SignatureAlgorithm: x509.SHA256WithRSA,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
}
if ip := net.ParseIP(serverName); ip != nil {
tmpl.IPAddresses = []net.IP{ip}
} else {
tmpl.Subject.CommonName = serverName
tmpl.DNSNames = []string{serverName}
}
pk, ok := caKey.(privateKey)
if !ok {
return nil, errors.New("invalid private key type")
}
raw, err := x509.CreateCertificate(rand.Reader, tmpl, caCert, pk.Public(), caKey)
if err != nil {
return nil, err
}
return x509.ParseCertificate(raw)
}
// https://pkg.go.dev/crypto#PrivateKey
type privateKey interface {
Public() crypto.PublicKey
Equal(x crypto.PrivateKey) bool
}
+18
View File
@@ -0,0 +1,18 @@
package tun
import (
"net"
"github.com/go-gost/core/router"
)
type Config struct {
Name string
Net []net.IPNet
// peer addr of point-to-point on MacOS
Peer string
MTU int
Gateway net.IP
Router router.Router
DNS []net.IP
}
+196
View File
@@ -0,0 +1,196 @@
package ws
import (
"encoding/binary"
"fmt"
"io"
"math"
)
// OpCode represents a WebSocket opcode.
type OpCode int
// https://tools.ietf.org/html/rfc6455#section-11.8.
const (
OpContinuation OpCode = iota
OpText
OpBinary
// 3 - 7 are reserved for further non-control frames.
_
_
_
_
_
OpClose
OpPing
OpPong
// 11-16 are reserved for further control frames.
)
// FrameHeader represents a WebSocket frame header.
// See https://tools.ietf.org/html/rfc6455#section-5.2.
type FrameHeader struct {
Fin bool
Rsv1 bool
Rsv2 bool
Rsv3 bool
OpCode OpCode
PayloadLength int64
Masked bool
MaskKey uint32
}
// ReadFrom reads a header from the reader.
// See https://tools.ietf.org/html/rfc6455#section-5.2.
func (h *FrameHeader) ReadFrom(r io.Reader) (n int64, err error) {
var buf [8]byte
// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
nn, err := io.ReadFull(r, buf[:2])
n += int64(n)
if err != nil {
return
}
b := buf[0]
h.Fin = b&(1<<7) != 0
h.Rsv1 = b&(1<<6) != 0
h.Rsv2 = b&(1<<5) != 0
h.Rsv3 = b&(1<<4) != 0
h.OpCode = OpCode(b & 0xf)
b = buf[1]
h.Masked = b&(1<<7) != 0
payloadLength := b &^ (1 << 7)
switch {
case payloadLength < 126:
h.PayloadLength = int64(payloadLength)
case payloadLength == 126:
nn, err = io.ReadFull(r, buf[:2])
h.PayloadLength = int64(binary.BigEndian.Uint16(buf[:]))
case payloadLength == 127:
nn, err = io.ReadFull(r, buf[:])
h.PayloadLength = int64(binary.BigEndian.Uint64(buf[:]))
}
n += int64(nn)
if err != nil {
return
}
if h.PayloadLength < 0 {
err = fmt.Errorf("received negative payload length: %v", h.PayloadLength)
return
}
if h.Masked {
nn, err = io.ReadFull(r, buf[:4])
n += int64(nn)
if err != nil {
return
}
h.MaskKey = binary.LittleEndian.Uint32(buf[:])
}
return
}
func (h FrameHeader) Length() int {
n := 2
switch {
case h.PayloadLength > math.MaxUint16:
n += 8
case h.PayloadLength > 125:
n += 2
}
if h.Masked {
n += 4
}
return n
}
func (h *FrameHeader) WriteTo(w io.Writer) (n int64, err error) {
var buf [14]byte
pos := 0
var b byte
if h.Fin {
b |= 1 << 7
}
if h.Rsv1 {
b |= 1 << 6
}
if h.Rsv2 {
b |= 1 << 5
}
if h.Rsv3 {
b |= 1 << 4
}
b |= byte(h.OpCode)
buf[0] = b
lengthByte := byte(0)
if h.Masked {
lengthByte |= 1 << 7
}
switch {
case h.PayloadLength > math.MaxUint16:
lengthByte |= 127
case h.PayloadLength > 125:
lengthByte |= 126
case h.PayloadLength >= 0:
lengthByte |= byte(h.PayloadLength)
}
buf[1] = lengthByte
pos = 2
switch {
case h.PayloadLength > math.MaxUint16:
binary.BigEndian.PutUint64(buf[2:], uint64(h.PayloadLength))
pos += 8
case h.PayloadLength > 125:
binary.BigEndian.PutUint16(buf[2:], uint16(h.PayloadLength))
pos += 2
}
if h.Masked {
binary.LittleEndian.PutUint32(buf[pos:], h.MaskKey)
pos += 4
}
nn, err := w.Write(buf[:pos])
n = int64(nn)
return
}
type Frame struct {
Header FrameHeader
Data io.Reader
}
func (fr *Frame) ReadFrom(r io.Reader) (n int64, err error) {
if n, err = fr.Header.ReadFrom(r); err != nil {
return
}
fr.Data = io.LimitReader(r, fr.Header.PayloadLength)
return
}
func (fr *Frame) WriteTo(w io.Writer) (n int64, err error) {
n, err = fr.Header.WriteTo(w)
if err != nil {
return
}
nn, err := io.Copy(w, fr.Data)
n += nn
return
}
+82
View File
@@ -0,0 +1,82 @@
package ws
import (
"net"
"sync"
"time"
xnet "github.com/go-gost/x/internal/net"
"github.com/gorilla/websocket"
)
type WebsocketConn interface {
net.Conn
WriteMessage(int, []byte) error
ReadMessage() (int, []byte, error)
xnet.ClientAddr
}
type websocketConn struct {
*websocket.Conn
rb []byte
clientAddr net.Addr
mux sync.Mutex
}
func Conn(conn *websocket.Conn) WebsocketConn {
return &websocketConn{
Conn: conn,
}
}
func ConnWithClientAddr(conn *websocket.Conn, clientAddr net.Addr) WebsocketConn {
return &websocketConn{
Conn: conn,
clientAddr: clientAddr,
}
}
func (c *websocketConn) Read(b []byte) (n int, err error) {
if len(c.rb) == 0 {
_, c.rb, err = c.Conn.ReadMessage()
}
n = copy(b, c.rb)
c.rb = c.rb[n:]
return
}
func (c *websocketConn) Write(b []byte) (n int, err error) {
err = c.WriteMessage(websocket.BinaryMessage, b)
n = len(b)
return
}
func (c *websocketConn) WriteMessage(messageType int, data []byte) error {
c.mux.Lock()
defer c.mux.Unlock()
return c.Conn.WriteMessage(messageType, data)
}
func (c *websocketConn) SetDeadline(t time.Time) error {
if err := c.SetReadDeadline(t); err != nil {
return err
}
return c.SetWriteDeadline(t)
}
func (c *websocketConn) SetReadDeadline(t time.Time) error {
c.mux.Lock()
defer c.mux.Unlock()
return c.Conn.SetReadDeadline(t)
}
func (c *websocketConn) SetWriteDeadline(t time.Time) error {
c.mux.Lock()
defer c.mux.Unlock()
return c.Conn.SetWriteDeadline(t)
}
func (c *websocketConn) ClientAddr() net.Addr {
return c.clientAddr
}
+52
View File
@@ -0,0 +1,52 @@
package wt
import (
"net"
"time"
wt "github.com/quic-go/webtransport-go"
)
type conn struct {
session *wt.Session
stream wt.Stream
}
func Conn(session *wt.Session, stream wt.Stream) net.Conn {
return &conn{
session: session,
stream: stream,
}
}
func (c *conn) Read(b []byte) (n int, err error) {
return c.stream.Read(b)
}
func (c *conn) Write(b []byte) (n int, err error) {
return c.stream.Write(b)
}
func (c *conn) Close() error {
return c.stream.Close()
}
func (c *conn) LocalAddr() net.Addr {
return c.session.LocalAddr()
}
func (c *conn) RemoteAddr() net.Addr {
return c.session.RemoteAddr()
}
func (c *conn) SetDeadline(t time.Time) error {
return c.stream.SetDeadline(t)
}
func (c *conn) SetReadDeadline(t time.Time) error {
return c.stream.SetReadDeadline(t)
}
func (c *conn) SetWriteDeadline(t time.Time) error {
return c.stream.SetWriteDeadline(t)
}