159 lines
3.8 KiB
Go
159 lines
3.8 KiB
Go
/* SPDX-License-Identifier: GPL-2.0
|
|
*
|
|
* Copyright (C) 2021 Jason A. Donenfeld. All Rights Reserved.
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"math/rand"
|
|
"net"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"golang.zx2c4.com/irc/hbot"
|
|
)
|
|
|
|
func raiseFileLimit() error {
|
|
var lim unix.Rlimit
|
|
err := unix.Getrlimit(syscall.RLIMIT_NOFILE, &lim)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if lim.Cur == lim.Max {
|
|
return nil
|
|
}
|
|
log.Printf("Raising file limit from %d to %d\n", lim.Cur, lim.Max)
|
|
lim.Cur = lim.Max
|
|
return unix.Setrlimit(syscall.RLIMIT_NOFILE, &lim)
|
|
}
|
|
|
|
func main() {
|
|
accountIdArg := flag.String("account-id", "", "account ID number")
|
|
srcChannelArg := flag.String("src-channel", "", "source channel")
|
|
dstChannelArg := flag.String("dst-channel", "", "destination channel")
|
|
srcServerArg := flag.String("src-server", "", "source server")
|
|
dstServerArg := flag.String("dst-server", "", "destination server")
|
|
flag.Parse()
|
|
if matched, _ := regexp.MatchString(`^[0-9]+$`, *accountIdArg); !matched {
|
|
fmt.Fprintln(os.Stderr, "Invalid account ID")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
if matched, _ := regexp.MatchString(`^#[a-zA-Z0-9_-]+$`, *srcChannelArg); !matched {
|
|
fmt.Fprintln(os.Stderr, "Invalid source channel")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
if matched, _ := regexp.MatchString(`^#[a-zA-Z0-9_-]+$`, *dstChannelArg); !matched {
|
|
fmt.Fprintln(os.Stderr, "Invalid destination channel")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
if _, _, err := net.SplitHostPort(*srcServerArg); err != nil {
|
|
fmt.Fprintln(os.Stderr, "Invalid source server")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
if _, _, err := net.SplitHostPort(*dstServerArg); err != nil {
|
|
fmt.Fprintln(os.Stderr, "Invalid destination server")
|
|
flag.Usage()
|
|
os.Exit(1)
|
|
}
|
|
|
|
privateKey, err := loadOrGeneratePrivateKey()
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
endpoints, err := getVpnEndpoints()
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
vpnConf, err := registerVpnConf(privateKey, *accountIdArg)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
rand.Seed(time.Now().UnixNano())
|
|
err = raiseFileLimit()
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
|
|
dialers, err := makeDialers(vpnConf, endpoints)
|
|
if err != nil {
|
|
log.Fatalln(err)
|
|
}
|
|
if len(dialers) < 2 {
|
|
log.Fatalln("Not enough dialers returned")
|
|
}
|
|
writers := newIrcWriterGroup(*dstServerArg, *dstChannelArg)
|
|
const writersPerIP = 10
|
|
type nextDialer struct {
|
|
sync.Mutex
|
|
*dialer
|
|
}
|
|
dialNext := func(nd *nextDialer, v4 bool) {
|
|
var last *dialer
|
|
for {
|
|
nd.Lock()
|
|
if nd.dialer == last {
|
|
nd.dialer = &dialers[rand.Intn(len(dialers))]
|
|
}
|
|
last = nd.dialer
|
|
nd.Unlock()
|
|
name := last.name
|
|
dial := last.dial
|
|
if v4 {
|
|
name += "-v4"
|
|
dial, _ = last.splitByAf()
|
|
} else {
|
|
name += "-v6"
|
|
_, dial = last.splitByAf()
|
|
}
|
|
writers.runWriter(name, dial)
|
|
}
|
|
}
|
|
for i := 0; i < vpnProviderMaxEndpointsInParallel/2; i++ {
|
|
nd := new(nextDialer)
|
|
for i := 0; i < writersPerIP; i++ {
|
|
go dialNext(nd, true)
|
|
go dialNext(nd, false)
|
|
}
|
|
}
|
|
|
|
srcDial := dialers[rand.Intn(len(dialers))]
|
|
logf := func(format string, args ...interface{}) {
|
|
log.Printf("[SRC %s] "+format, append([]interface{}{srcDial.name}, args...)...)
|
|
}
|
|
bot := hbot.NewBot(&hbot.Config{
|
|
Host: *srcServerArg,
|
|
Nick: randomNick(),
|
|
Channels: []string{*srcChannelArg},
|
|
Dial: srcDial.dial,
|
|
Logger: hbot.Logger{Verbosef: logf, Errorf: logf},
|
|
})
|
|
bot.AddTrigger(hbot.Trigger{
|
|
Condition: func(b *hbot.Bot, m *hbot.Message) bool {
|
|
return m.Param(0) == *srcChannelArg && m.Command == "PRIVMSG" &&
|
|
!strings.HasPrefix(m.Prefix.User, hbot.CommonBotUserPrefix) &&
|
|
!strings.HasPrefix(m.Prefix.User, "~"+hbot.CommonBotUserPrefix)
|
|
},
|
|
Action: func(b *hbot.Bot, m *hbot.Message) {
|
|
writers.queueMessage(m.Prefix.Name, m.Trailing())
|
|
},
|
|
})
|
|
for {
|
|
bot.Run()
|
|
time.Sleep(time.Second * 5)
|
|
}
|
|
}
|