Initial commit of utilities
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
158
cmd/ircmirror/main.go
Normal file
158
cmd/ircmirror/main.go
Normal file
@@ -0,0 +1,158 @@
|
||||
/* 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user