Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
*.pyc
*.swp
build
dist
horde.egg-info
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ much more bueno. One would simply need to:
* [Nate House](https://github.com/naterh)
* [Russ Garrett](http://github.com/russss)
* [Laurie Denness](http://github.com/lozzd)
* [Sam Gleske](https://github.com/samrocketman)
77 changes: 72 additions & 5 deletions horde/horde.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#!/usr/bin/env python
import argparse
import logging
import os
import random
import re
import socket
import subprocess
import sys
import tempfile
import threading
import time

import argparse
import BitTornado.BT1.track as bttrack
import BitTornado.BT1.makemetafile as makemetafile
import murder_client as murder_client
Expand All @@ -31,6 +31,68 @@
horde_py = os.path.join(horde_root, 'horde.py')


def get_random_open_port(port, interface=""):
"""
Arguments:
port - can be a single number or a number range with a hyphen as a string
interface - a string representing either a hostname in Internet domain notation or IP
address to test binding. By default a blank value means test a binding for all
interfaces.
Returns:
returns an int between 1 and 65535 based on input port

If a single port is given as input then it simply returns the port

If a port range is input then a random port will be returned if there is no
Listening socket for that port.
"""
regex=re.compile(r'[0-9]+-{0,1}[0-9]*')
#if port is an integer then convert to string
if isinstance(port, int):
port = str(port)
#Do some error checking on the input.
if not isinstance(port, str):
raise TypeError("port not of type str.")
if not regex.match(port):
raise ValueError("port str must be a number or a range of numbers between 1 and 65535.")
ports = sorted(map(lambda x: int(x), port.split('-')))
#Do range checking on the input.
if len(ports) == 2 and ( ports[1] > 65535 ):
raise ValueError("port range is between 1 and 65535.")
elif ports[0] < 1 or ports[0] > 65535:
raise ValueError("port range is between 1 and 65535.")
#Return a port or a random range.
if len(ports) == 1:
#Since we're given a single port don't even try to test it. Just return the value.
return ports[0]
else:
port = random.randrange(ports[0],ports[1]+1)
count = 0
found = False
while not found:
try:
#test the socket for a listening service
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#The SO_REUSEADDR flag tells the kernel to reuse a local socket in TIME_WAIT
#state, without waiting for its natural timeout to expire. This avoids
#an exception "OSError: [Errno 98] Address already in use" because we are
#only testing the binding of a socket and not using it for communication.
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((interface, port))
found = True
except socket.error,e:
#Generate another port to be tested because the last one had a listening socket.
port = random.randrange(ports[0], ports[1]+1)
s.close()
count += 1
#avoid an infinite loop
if count > 10000:
break
if not found:
raise socket.error("could not obtain socket from port or port range.")
return port


def run(local_file, remote_file, hosts):
start = time.time()
log.info("Spawning tracker...")
Expand Down Expand Up @@ -144,7 +206,8 @@ def mktorrent(file_name, tracker):


def track():
bttrack.track(["--dfile", opts['data_file'], "--port", opts['port']])
bttrack.track(["--dfile", opts['data_file'], "--port",
opts['port']])


def seed(torrent, local_file):
Expand Down Expand Up @@ -187,7 +250,7 @@ def run_with_opts(local_file, remote_file, hosts='', retry=0, port=8998,
opts['remote-file'] = remote_file
opts['hosts'] = hosts
opts['retry'] = retry
opts['port'] = port
opts['port'] = get_random_open_port(port)
opts['remote_path'] = remote_path
opts['data_file'] = data_file
opts['log_dir'] = log_dir
Expand Down Expand Up @@ -217,7 +280,8 @@ def entry_point():

parser.add_argument('--port',
default=8998,
help="Port number to run the tracker on")
help="Port number to run the tracker on. Port range " +
"for random port selection also allowed (e.g. 8000-9000).")

parser.add_argument('--remote-path',
default='/tmp/horde',
Expand All @@ -241,6 +305,9 @@ def entry_point():

opts = vars(parser.parse_args())

#potentially select a random port
opts['port'] = get_random_open_port(opts['port'])

if opts['seed']:
seed(opts['local-file'], opts['remote-file'])
else:
Expand Down