Implement connection retry by downscaling ssl options
parent
ed410faf0a
commit
b7be2f2bd7
87
cmd/main.go
87
cmd/main.go
|
|
@ -3,8 +3,10 @@ package main
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -48,7 +50,7 @@ func formatSize(size int64) string {
|
||||||
return FloatToString(float64(size)) + "B"
|
return FloatToString(float64(size)) + "B"
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchCommand(args []string) {
|
func execSearch(args []string) {
|
||||||
searchCmd := flag.NewFlagSet("search", flag.ExitOnError)
|
searchCmd := flag.NewFlagSet("search", flag.ExitOnError)
|
||||||
sortByFilename := searchCmd.Bool("s", false, "sort results by filename")
|
sortByFilename := searchCmd.Bool("s", false, "sort results by filename")
|
||||||
|
|
||||||
|
|
@ -76,7 +78,7 @@ func searchCommand(args []string) {
|
||||||
printer.Print()
|
printer.Print()
|
||||||
}
|
}
|
||||||
|
|
||||||
func transferLoop(transfer *xdcc.XdccTransfer) {
|
func transferLoop(transfer xdcc.Transfer) {
|
||||||
bar := pb.NewProgressBar()
|
bar := pb.NewProgressBar()
|
||||||
|
|
||||||
evts := transfer.PollEvents()
|
evts := transfer.PollEvents()
|
||||||
|
|
@ -104,7 +106,7 @@ func suggestUnknownAuthoritySwitch(err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTransfer(transfer *xdcc.XdccTransfer) {
|
func doTransfer(transfer xdcc.Transfer) {
|
||||||
err := transfer.Start()
|
err := transfer.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
|
@ -116,20 +118,21 @@ func doTransfer(transfer *xdcc.XdccTransfer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFlags(flagSet *flag.FlagSet, args []string) []string {
|
func parseFlags(flagSet *flag.FlagSet, args []string) []string {
|
||||||
findFirstFlag := func(args []string) int {
|
|
||||||
for i, arg := range args {
|
|
||||||
if strings.HasPrefix(arg, "-") || strings.HasPrefix(arg, "--") {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
flagIdx := findFirstFlag(args)
|
flagIdx := findFirstFlag(args)
|
||||||
if flagIdx >= 0 {
|
if flagIdx < 0 {
|
||||||
flagSet.Parse(args[flagIdx:])
|
return args
|
||||||
return args[:flagIdx]
|
|
||||||
}
|
}
|
||||||
return args
|
flagSet.Parse(args[flagIdx:])
|
||||||
|
return args[:flagIdx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func findFirstFlag(args []string) int {
|
||||||
|
for i, arg := range args {
|
||||||
|
if strings.HasPrefix(arg, "-") || strings.HasPrefix(arg, "--") {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadUrlListFile(filePath string) []string {
|
func loadUrlListFile(filePath string) []string {
|
||||||
|
|
@ -156,17 +159,17 @@ func loadUrlListFile(filePath string) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func printGetUsageAndExit(flagSet *flag.FlagSet) {
|
func printGetUsageAndExit(flagSet *flag.FlagSet) {
|
||||||
fmt.Printf("usage: get url1 url2 ... [-o path] [-i file] [--allow-unknown-authority]\n\nFlag set:\n")
|
fmt.Printf("usage: get url1 url2 ... [-o path] [-i file] [--ssl-only]\n\nFlag set:\n")
|
||||||
flagSet.PrintDefaults()
|
flagSet.PrintDefaults()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCommand(args []string) {
|
func execGet(args []string) {
|
||||||
getCmd := flag.NewFlagSet("get", flag.ExitOnError)
|
getCmd := flag.NewFlagSet("get", flag.ExitOnError)
|
||||||
path := getCmd.String("o", ".", "output folder of dowloaded file")
|
path := getCmd.String("o", ".", "output folder of dowloaded file")
|
||||||
inputFile := getCmd.String("i", "", "input file containing a list of urls")
|
inputFile := getCmd.String("i", "", "input file containing a list of urls")
|
||||||
skipCertificateCheck := getCmd.Bool("allow-unknown-authority", false, "skip x509 certificate check during tls connection")
|
|
||||||
noSSL := getCmd.Bool("no-ssl", false, "disable SSL.")
|
sslOnly := getCmd.Bool("ssl-only", false, "force the client to use TSL connection")
|
||||||
|
|
||||||
urlList := parseFlags(getCmd, args)
|
urlList := parseFlags(getCmd, args)
|
||||||
|
|
||||||
|
|
@ -180,41 +183,45 @@ func getCommand(args []string) {
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
for _, urlStr := range urlList {
|
for _, urlStr := range urlList {
|
||||||
if strings.HasPrefix(urlStr, "irc://") {
|
url, err := xdcc.ParseURL(urlStr)
|
||||||
url, err := xdcc.ParseURL(urlStr)
|
if errors.Is(err, xdcc.ErrInvalidURL) {
|
||||||
|
log.Printf("no valid irc url: %s\n", urlStr)
|
||||||
if err != nil {
|
continue
|
||||||
fmt.Println(err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
transfer := xdcc.NewTransfer(*url, *path, !*noSSL, *skipCertificateCheck)
|
|
||||||
go func(transfer *xdcc.XdccTransfer) {
|
|
||||||
doTransfer(transfer)
|
|
||||||
wg.Done()
|
|
||||||
}(transfer)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("no valid irc url %s\n", urlStr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
transfer := xdcc.NewTransfer(xdcc.Config{
|
||||||
|
File: *url,
|
||||||
|
OutPath: *path,
|
||||||
|
SSLOnly: *sslOnly,
|
||||||
|
})
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func(transfer xdcc.Transfer) {
|
||||||
|
doTransfer(transfer)
|
||||||
|
wg.Done()
|
||||||
|
}(transfer)
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
fmt.Println("one of the following subcommands is expected: [search, get]")
|
log.Println("one of the following subcommands is expected: [search, get]")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
case "search":
|
case "search":
|
||||||
searchCommand(os.Args[2:])
|
execSearch(os.Args[2:])
|
||||||
case "get":
|
case "get":
|
||||||
getCommand(os.Args[2:])
|
execGet(os.Args[2:])
|
||||||
default:
|
default:
|
||||||
fmt.Println("no such command: ", os.Args[1])
|
log.Println("no such command: ", os.Args[1])
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,15 +29,17 @@ func parseSlot(slotStr string) (int, error) {
|
||||||
return strconv.Atoi(slotStr)
|
return strconv.Atoi(slotStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ErrInvalidURL = errors.New("invalid IRC url")
|
||||||
|
|
||||||
// url has the following format: irc://network/channel/bot/slot
|
// url has the following format: irc://network/channel/bot/slot
|
||||||
func ParseURL(url string) (*IRCFile, error) {
|
func ParseURL(url string) (*IRCFile, error) {
|
||||||
if !strings.HasPrefix(url, "irc://") {
|
if !strings.HasPrefix(url, "irc://") {
|
||||||
return nil, errors.New("not an IRC url")
|
return nil, ErrInvalidURL
|
||||||
}
|
}
|
||||||
|
|
||||||
fields := strings.Split(strings.TrimPrefix(url, "irc://"), "/")
|
fields := strings.Split(strings.TrimPrefix(url, "irc://"), "/")
|
||||||
if len(fields) != ircFileURLFields {
|
if len(fields) != ircFileURLFields {
|
||||||
return nil, errors.New("invalid IRC url")
|
return nil, ErrInvalidURL
|
||||||
}
|
}
|
||||||
|
|
||||||
slot, err := parseSlot(fields[3])
|
slot, err := parseSlot(fields[3])
|
||||||
|
|
|
||||||
63
xdcc/xdcc.go
63
xdcc/xdcc.go
|
|
@ -127,6 +127,37 @@ type TransferAbortedEvent struct {
|
||||||
|
|
||||||
const maxConnAttempts = 5
|
const maxConnAttempts = 5
|
||||||
|
|
||||||
|
type Transfer interface {
|
||||||
|
Start() error
|
||||||
|
PollEvents() chan TransferEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
type retryTransfer struct {
|
||||||
|
*XdccTransfer
|
||||||
|
conf Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *retryTransfer) Start() error {
|
||||||
|
t1 := newXdccTransfer(t.conf, true, false)
|
||||||
|
if err := t1.conn.Connect(); err == nil {
|
||||||
|
t.XdccTransfer = t1
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t2 := newXdccTransfer(t.conf, true, true)
|
||||||
|
if err := t1.conn.Connect(); err == nil {
|
||||||
|
t.XdccTransfer = t2
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
t.XdccTransfer = newXdccTransfer(t.conf, false, false)
|
||||||
|
return t.XdccTransfer.conn.Connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *retryTransfer) PollEvents() chan TransferEvent {
|
||||||
|
return t.XdccTransfer.PollEvents()
|
||||||
|
}
|
||||||
|
|
||||||
type XdccTransfer struct {
|
type XdccTransfer struct {
|
||||||
filePath string
|
filePath string
|
||||||
url IRCFile
|
url IRCFile
|
||||||
|
|
@ -136,14 +167,32 @@ type XdccTransfer struct {
|
||||||
events chan TransferEvent
|
events chan TransferEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTransfer(url IRCFile, filePath string, enableSSL bool, skipCertificateCheck bool) *XdccTransfer {
|
type Config struct {
|
||||||
|
File IRCFile
|
||||||
|
OutPath string
|
||||||
|
SSLOnly bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTransfer(c Config) Transfer {
|
||||||
|
if c.SSLOnly {
|
||||||
|
return newXdccTransfer(c, true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &retryTransfer{
|
||||||
|
conf: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newXdccTransfer(c Config, enableSSL bool, skipCertificateCheck bool) *XdccTransfer {
|
||||||
rand.Seed(time.Now().UTC().UnixNano())
|
rand.Seed(time.Now().UTC().UnixNano())
|
||||||
nick := IRCClientUserName + strconv.Itoa(int(rand.Uint32()))
|
nick := IRCClientUserName + strconv.Itoa(int(rand.Uint32()))
|
||||||
|
|
||||||
|
file := c.File
|
||||||
|
|
||||||
config := irc.NewConfig(nick)
|
config := irc.NewConfig(nick)
|
||||||
config.SSL = enableSSL
|
config.SSL = enableSSL
|
||||||
config.SSLConfig = &tls.Config{ServerName: url.Network, InsecureSkipVerify: skipCertificateCheck}
|
config.SSLConfig = &tls.Config{ServerName: file.Network, InsecureSkipVerify: skipCertificateCheck}
|
||||||
config.Server = url.Network
|
config.Server = file.Network
|
||||||
config.NewNick = func(nick string) string {
|
config.NewNick = func(nick string) string {
|
||||||
return nick + "" + strconv.Itoa(int(rand.Uint32()))
|
return nick + "" + strconv.Itoa(int(rand.Uint32()))
|
||||||
}
|
}
|
||||||
|
|
@ -152,13 +201,13 @@ func NewTransfer(url IRCFile, filePath string, enableSSL bool, skipCertificateCh
|
||||||
|
|
||||||
t := &XdccTransfer{
|
t := &XdccTransfer{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
url: url,
|
url: file,
|
||||||
filePath: filePath,
|
filePath: c.OutPath,
|
||||||
started: false,
|
started: false,
|
||||||
connAttempts: 0,
|
connAttempts: 0,
|
||||||
events: make(chan TransferEvent, defaultEventChanSize),
|
events: make(chan TransferEvent, defaultEventChanSize),
|
||||||
}
|
}
|
||||||
t.setupHandlers(url.Channel, url.UserName, url.Slot)
|
t.setupHandlers(file.Channel, file.UserName, file.Slot)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,7 +260,7 @@ func (transfer *XdccTransfer) setupHandlers(channel string, userName string, slo
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err != nil || transfer.connAttempts >= maxConnAttempts) && !transfer.started {
|
if (err != nil || transfer.connAttempts >= maxConnAttempts) && !transfer.started {
|
||||||
transfer.notifyEvent(&TransferAbortedEvent{Error: "disconnected from server"})
|
transfer.notifyEvent(&TransferAbortedEvent{Error: err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
transfer.connAttempts++
|
transfer.connAttempts++
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue