diff --git a/gitso/trunk/ConnectionWindow.py b/gitso/trunk/ConnectionWindow.py index 9b9349f..49bbdf9 100644 --- a/gitso/trunk/ConnectionWindow.py +++ b/gitso/trunk/ConnectionWindow.py @@ -83,7 +83,10 @@ class ConnectionWindow(wx.Frame): self.Bind(wx.EVT_RADIOBUTTON, self.RadioToggle, id=self.rb1.GetId()) self.Bind(wx.EVT_RADIOBUTTON, self.RadioToggle, id=self.rb2.GetId()) - + # checkbox for natpmp + self.cb1 = wx.CheckBox(self, -1, 'Use NAT-PMP', (130, 48)) + + # the combobox Control self.sampleList = self.paths['list'] @@ -131,6 +134,7 @@ class ConnectionWindow(wx.Frame): self.SetDefaultItem(self.hostField) self.hostField.SetFocus() + self.cb1.Enable(False) self.SetThemeEnabled(True) self.Centre() @@ -145,7 +149,8 @@ class ConnectionWindow(wx.Frame): self.RadioToggle(None) self.hostField.Value = self.paths['connect'] self.ConnectSupport(None) - + + def RadioToggle(self, event): """ Toggles Radio Buttons @@ -156,9 +161,11 @@ class ConnectionWindow(wx.Frame): if self.rb1.GetValue(): self.ToggleValue = 0 self.hostField.Enable(True) + self.cb1.Enable(False) else: self.ToggleValue = 1 self.hostField.Enable(False) + self.cb1.Enable(True) def ConnectSupport(self, event): diff --git a/gitso/trunk/GitsoThread.py b/gitso/trunk/GitsoThread.py index f4e1443..a6cd781 100755 --- a/gitso/trunk/GitsoThread.py +++ b/gitso/trunk/GitsoThread.py @@ -26,6 +26,7 @@ along with Gitso. If not, see . import threading, time import os, sys, signal, os.path import Processes +import NATPMP class GitsoThread(threading.Thread): def __init__(self, window, paths): @@ -40,7 +41,13 @@ class GitsoThread(threading.Thread): def run(self): + """ + This is where the beef is. Start the processes and check on them. + + @author: Aaron Gerber + """ if self.host <> "": + # Get Help self.pid = self.process.getSupport(self.host) time.sleep(.5) if self.checkStatus(): @@ -49,6 +56,11 @@ class GitsoThread(threading.Thread): self.window.setMessage("Could not connect.", False) self.error = True else: + # Give Support + self.window.cb1.Enable(False) + if self.window.cb1.GetValue() == True: + self.NATPMP('request') + self.pid = self.process.giveSupport() time.sleep(.5) if self.checkStatus(): @@ -69,15 +81,36 @@ class GitsoThread(threading.Thread): def setHost(self, host=""): + """ + Set the object variable. + + @author: Aaron Gerber + """ self.host = host def kill(self): + """ + Kill the process and general clean-up. + + @author: Aaron Gerber + """ + if self.window.rb1.GetValue() == False: #give support + if self.window.cb1.GetValue() == True: + self.NATPMP('giveup') + self.window.cb1.Enable(True) + self.process.KillPID() self.pid = 0 self.running = False - + + def checkStatus(self): + """ + Check the status of the underlying process. + + @author: Aaron Gerber + """ if self.pid == 0: return False @@ -103,3 +136,25 @@ class GitsoThread(threading.Thread): else: return True + + def NATPMP(self, action): + """ + Call NAT-PMP on router to get port 5500 forwarded. + + @author: Dennis Koot + """ + if action == 'request': + lifetime = 3600 + print "Request port 5500 (NAT-PMP)." + else: + lifetime = 0 + print "Give up port 5500 (NAT-PMP)." + + pubpriv_port = int(5500) + protocol = NATPMP.NATPMP_PROTOCOL_TCP + gateway = NATPMP.get_gateway_addr() + try: + print NATPMP.map_port(protocol, pubpriv_port, pubpriv_port, lifetime, gateway_ip=gateway) + except: + print "Warning: Unable to automap port." + diff --git a/gitso/trunk/NATPMP.py b/gitso/trunk/NATPMP.py new file mode 100644 index 0000000..7012987 --- /dev/null +++ b/gitso/trunk/NATPMP.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python + +"""NAT-PMP client library + +Provides functions to interact with NAT-PMP gateways implementing version 0 +of the NAT-PMP draft specification. + +This version does not completely implement the draft standard. +* It does not provide functionality to listen for address change packets. +* It does not have a proper request queuing system, meaning that +multiple requests may be issued in parallel, against spec recommendations. + +For more information on NAT-PMP, see the NAT-PMP draft specification: + +http://files.dns-sd.org/draft-cheshire-nat-pmp.txt + +Requires Python 2.3 or later. +Tested on Python 2.3, 2.4, 2.5 against Apple AirPort Express. + +0.0.1.2 - NT autodetection code. Thanks to roee shlomo for the gateway detection regex! +0.0.1.1 - Removed broken mutex code +0.0.1 - Initial release + +""" + +__version__ = "0.0.1.2" +__license__ = """Copyright (c) 2008, Yiming Liu, All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* The names of the author and contributors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE.""" + +__author__ = "Yiming Liu " + +import struct, socket, select, time +import sys, os, re + +NATPMP_PORT = 5351 + +NATPMP_RESERVED_VAL = 0 + +NATPMP_PROTOCOL_UDP = 1 +NATPMP_PROTOCOL_TCP = 2 + +NATPMP_GATEWAY_NO_VALID_GATEWAY = -10 +NATPMP_GATEWAY_NO_SUPPORT = -11 +NATPMP_GATEWAY_CANNOT_FIND = -12 + +NATPMP_RESULT_SUCCESS = 0 # Success +NATPMP_RESULT_UNSUPPORTED_VERSION = 1 # Unsupported Version +NATPMP_RESULT_NOT_AUTHORIZED = 2 # Not Authorized/Refused/NATPMP turned off +NATPMP_RESULT_NETWORK_FAILURE = 3 # Network Failure +NATPMP_RESULT_OUT_OF_RESOURCES = 4 # can not create more mappings +NATPMP_RESULT_UNSUPPORTED_OPERATION = 5 # not a supported opcode +# all remaining results are fatal errors + +NATPMP_ERROR_DICT = { + NATPMP_RESULT_SUCCESS:"No error.", + NATPMP_RESULT_UNSUPPORTED_VERSION:"The protocol version specified is unsupported.", + NATPMP_RESULT_NOT_AUTHORIZED:"The operation was refused. NAT-PMP may be turned off on gateway.", + NATPMP_RESULT_NETWORK_FAILURE:"There was a network failure. The gateway may not have an IP address.",# Network Failure + NATPMP_RESULT_OUT_OF_RESOURCES:"The NAT-PMP gateway is out of resources and cannot create more mappings.", # can not create more mappings + NATPMP_RESULT_UNSUPPORTED_OPERATION:"The NAT-PMP gateway does not support this operation", # not a supported opcode + NATPMP_GATEWAY_NO_SUPPORT:'The gateway does not support NAT-PMP', + NATPMP_GATEWAY_NO_VALID_GATEWAY:'No valid gateway address was specified.', + NATPMP_GATEWAY_CANNOT_FIND:'Cannot automatically determine gateway address. Must specify manually.' + } + + +class NATPMPRequest(object): + """Represents a basic NAT-PMP request. This currently consists of the + 1-byte fields version and opcode. + + Other requests are derived from NATPMPRequest. + """ + retry_increment = 0.250 # seconds + + def __init__(self, version, opcode): + self.version = version + self.opcode = opcode + + def toBytes(self): + """Converts the request object to a byte string.""" + return struct.pack('!BB', self.version, self.opcode) + +class PublicAddressRequest(NATPMPRequest): + """Represents a NAT-PMP request to the local gateway for a public address. + As per the specification, this is a generic request with the opcode = 0. + """ + def __init__(self, version=0): + NATPMPRequest.__init__(self, version, 0) + +class PortMapRequest(NATPMPRequest): + """Represents a NAT-PMP request to the local gateway for a port mapping. + As per the specification, this request extends NATPMPRequest with + the fields private_port, public_port, and lifetime. The first two + are 2-byte unsigned shorts, and the last is a 4-byte unsigned integer. + """ + def __init__(self, protocol, private_port, public_port, lifetime=3600, version=0): + NATPMPRequest.__init__(self, version, protocol) + self.private_port = private_port + self.public_port = public_port + self.lifetime = lifetime + + def toBytes(self): + s= NATPMPRequest.toBytes(self) + struct.pack('!HHHI', NATPMP_RESERVED_VAL, self.private_port, self.public_port, self.lifetime) + return s + +class NATPMPResponse(object): + """Represents a generic NAT-PMP response from the local gateway. The + generic response has fields for version, opcode, result, and secs + since last epoch (last boot of the NAT gateway). As per the + specification, the opcode is offset by 128 from the opcode of + the original request. + """ + def __init__(self, version, opcode, result, sec_since_epoch): + self.version = version + self.opcode = opcode + self.result = result + self.sec_since_epoch = sec_since_epoch + + def __str__(self): + return "NATPMPResponse(%d, %d, %d, $d)" % (self.version, self.opcode, self.result, self.sec_since_epoch) + +class PublicAddressResponse(NATPMPResponse): + """Represents a NAT-PMP response from the local gateway to a + public-address request. It has one additional 4-byte field + containing the IP returned. + + The member variable ip contains the Python-friendly string form, while + ip_int contains the same in the original 4-byte unsigned int. + """ + def __init__(self, bytes): + version, opcode, result, sec_since_epoch, self.ip_int = struct.unpack("!BBHII", bytes) + NATPMPResponse.__init__(self, version, opcode, result, sec_since_epoch) + self.ip = socket.inet_ntoa(bytes[8:8+4]) + #self.ip = socket.inet_ntoa(self.ip_bytes) + + def __str__(self): + return "PublicAddressResponse: version %d, opcode %d (%d), result %d, ssec %d, ip %s" % (self.version, self.opcode, self.result, self.sec_since_epoch, self.ip) + +class PortMapResponse(NATPMPResponse): + """Represents a NAT-PMP response from the local gateway to a + public-address request. The response contains the private port, + public port, and the lifetime of the mapping in addition to typical + NAT-PMP headers. Note that the port mapping assigned is + NOT NECESSARILY the port requested (see the specification + for details). + """ + def __init__(self, bytes): + version, opcode, result, sec_since_epoch, self.private_port, self.public_port, self.lifetime = struct.unpack('!BBHIHHI', bytes) + NATPMPResponse.__init__(self, version, opcode, result, sec_since_epoch) + + def __str__(self): + return "PortMapResponse: version %d, opcode %d (%d), result %d, ssec %d, private_port %d, public port %d, lifetime %d" % (self.version, self.opcode, self.opcode, self.result, self.sec_since_epoch, self.private_port, self.public_port, self.lifetime) + +class NATPMPError(Exception): + """Generic exception state. May be used to represent unknown errors.""" + pass + +class NATPMPResultError(NATPMPError): + """Used when a NAT gateway responds with an error-state response.""" + pass + +class NATPMPNetworkError(NATPMPError): + """Used when a network error occurred while communicating + with the NAT gateway.""" + pass + +class NATPMPUnsupportedError(NATPMPError): + """Used when a NAT gateway does not support NAT-PMP.""" + pass + + +def get_gateway_addr(): + """A hack to obtain the current gateway automatically, since + Python has no interface to sysctl(). + + This may or may not be the gateway we should be contacting. + It does not guarantee correct results. + + This function requires the presence of + netstat on the path on POSIX and NT. It requires ip on + Linux. + """ + addr = "" + shell_command = 'netstat -rn' + if os.name == "posix": + pattern = re.compile('default\s+([\w.:]+)\s+\w') + if "linux" in sys.platform: + shell_command = "ip route show" + pattern = re.compile('default via\s+([\w.:]+)\s+\w') + elif os.name == "nt": + pattern = re.compile(".*?Default Gateway:[ ]+(.*?)\n") + system_out = os.popen(shell_command, 'r').read() # TODO: this could be a security issue + if not system_out: + raise NATPMPNetworkError(NATPMP_GATEWAY_CANNOT_FIND, error_str(NATPMP_GATEWAY_CANNOT_FIND)) + match = pattern.search(system_out) + if not match: + raise NATPMPNetworkError(NATPMP_GATEWAY_CANNOT_FIND, error_str(NATPMP_GATEWAY_CANNOT_FIND)) + addr = match.groups()[0].strip() + return addr # TODO: use real auto-detection + +def error_str(result_code): + """Takes a numerical error code and returns a human-readable + error string. + """ + result = NATPMP_ERROR_DICT.get(result_code) + if not result: + result = "Unknown fatal error." + return result + +def get_gateway_socket(gateway): + """Takes a gateway address string and returns a non-blocking UDP + socket to communicate with its NAT-PMP implementation on + NATPMP_PORT. + + e.g. addr = get_gateway_socket('10.0.1.1') + """ + if not gateway: + raise NATPMPNetworkError(NATPMP_GATEWAY_NO_VALID_GATEWAY, error_str(NATPMP_GATEWAY_NO_VALID_GATEWAY)) + response_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + response_socket.setblocking(0) + response_socket.connect((gateway, NATPMP_PORT)) + return response_socket + +def get_public_address(gateway_ip=get_gateway_addr(), retry=9): + """A high-level function that returns the public interface IP of + the current host by querying the NAT-PMP gateway. IP is + returned as string. + + Takes two possible keyword arguments: + gateway_ip - the IP to the NAT-PMP compatible gateway. + Defaults to using auto-detection function + get_gateway_addr() + retry - the number of times to retry the request if unsuccessful. + Defaults to 9 as per specification. + """ + addr = None + addr_request = PublicAddressRequest() + addr_response = send_request_with_retry(gateway_ip, addr_request, responseDataClass=PublicAddressResponse, retry=retry) + if addr_response.result != 0: + #sys.stderr.write("NAT-PMP error %d: %s\n" % (addr_response.result, error_str(addr_response.result))) + #sys.stderr.flush() + raise NATPMPResultError(addr_response.result, error_str(addr_response.result), addr_response) + addr = addr_response.ip + return addr + +def map_tcp_port(public_port, private_port, lifetime=3600, gateway_ip=get_gateway_addr(), retry=9, useException=True): + """A high-level wrapper to map_port() that requests a mapping + for a public TCP port on the NAT to a private TCP port on this host. + Returns the complete response on success. + + public_port - the public port of the mapping requested + private_port - the private port of the mapping requested + lifetime - the duration of the mapping in seconds. + Defaults to 3600, per specification. + gateway_ip - the IP to the NAT-PMP compatible gateway. + Defaults to using auto-detection function + get_gateway_addr() + retry - the number of times to retry the request if unsuccessful. + Defaults to 9 as per specification. + useException - throw an exception if an error result is + received from the gateway. Defaults to True. + """ + return map_port(NATPMP_PROTOCOL_TCP, public_port, private_port, lifetime, gateway_ip=gateway_ip, retry=retry, useException=useException) + +def map_udp_port(public_port, private_port, lifetime=3600, gateway_ip=get_gateway_addr(), retry=9, useException=True): + """A high-level wrapper to map_port() that requests a mapping for + a public UDP port on the NAT to a private UDP port on this host. + Returns the complete response on success. + + public_port - the public port of the mapping requested + private_port - the private port of the mapping requested + lifetime - the duration of the mapping in seconds. + Defaults to 3600, per specification. + gateway_ip - the IP to the NAT-PMP compatible gateway. + Defaults to using auto-detection function + get_gateway_addr() + retry - the number of times to retry the request if unsuccessful. + Defaults to 9 as per specification. + useException - throw an exception if an error result is + received from the gateway. Defaults to True. + """ + return map_port(NATPMP_PROTOCOL_UDP, public_port, private_port, lifetime, gateway_ip=gateway_ip, retry=retry, useException=useException) + +def map_port(protocol, public_port, private_port, lifetime=3600, gateway_ip=get_gateway_addr(), retry=9, useException=True): + """A function to map public_port to private_port of protocol. + Returns the complete response on success. + + protocol - NATPMP_PROTOCOL_UDP or NATPMP_PROTOCOL_TCP + public_port - the public port of the mapping requested + private_port - the private port of the mapping requested + lifetime - the duration of the mapping in seconds. + Defaults to 3600, per specification. + gateway_ip - the IP to the NAT-PMP compatible gateway. + Defaults to using auto-detection function + get_gateway_addr() + retry - the number of times to retry the request if unsuccessful. + Defaults to 9 as per specification. + useException - throw an exception if an error result + is received from the gateway. Defaults to True. + """ + if protocol not in [NATPMP_PROTOCOL_UDP, NATPMP_PROTOCOL_TCP]: + raise ValueError("Must be either NATPMP_PROTOCOL_UDP or NATPMP_PROTOCOL_TCP") + response = None + port_mapping_request = PortMapRequest(protocol, private_port, public_port, lifetime) + port_mapping_response = send_request_with_retry(gateway_ip, port_mapping_request, responseDataClass=PortMapResponse, retry=retry) + if port_mapping_response.result != 0 and useException: + raise NATPMPResultError(port_mapping_response.result, error_str(port_mapping_response.result), port_mapping_response) + return port_mapping_response + + +def send_request(gateway_socket, request): + gateway_socket.sendall(request.toBytes()) + +def read_response(gateway_socket, timeout, responseSize=16): + data = "" + source_addr = ("", "") + rlist, wlist, xlist = select.select([gateway_socket], [], [], timeout) + if rlist: + resp_socket = rlist[0] + data,source_addr = resp_socket.recvfrom(responseSize) + return data,source_addr + +def send_request_with_retry(gateway_ip, request, responseDataClass=None, retry=9): + gateway_socket = get_gateway_socket(gateway_ip) + n = 1 + data = "" + while n <= retry and not data: + send_request(gateway_socket, request) + data,source_addr = read_response(gateway_socket, n * request.retry_increment) + if source_addr[0] != gateway_ip or source_addr[1] != NATPMP_PORT: + data = "" # discard data if source mismatch, as per specification + n += 1 + if n >= retry and not data: + raise NATPMPUnsupportedError(NATPMP_GATEWAY_NO_SUPPORT, error_str(NATPMP_GATEWAY_NO_SUPPORT)) + if data and responseDataClass: + data = responseDataClass(data) + return data + + +if __name__ == "__main__": + addr = get_public_address() + map_resp = map_tcp_port(62001, 62001) + print addr + print map_resp.__dict__ diff --git a/gitso/trunk/arch/osx/Info.plist b/gitso/trunk/arch/osx/Info.plist deleted file mode 100644 index 4e56b7c..0000000 --- a/gitso/trunk/arch/osx/Info.plist +++ /dev/null @@ -1,98 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleDisplayName - Gitso - CFBundleDocumentTypes - - - CFBundleTypeOSTypes - - **** - fold - disk - - CFBundleTypeRole - Viewer - - - CFBundleExecutable - Gitso - CFBundleIconFile - PythonApplet.icns - CFBundleIdentifier - org.pythonmac.unspecified.Gitso - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - Gitso - CFBundlePackageType - APPL - CFBundleShortVersionString - 0.6 - CFBundleSignature - ???? - CFBundleVersion - 0.6 - LSHasLocalizedDisplayName - - NSAppleScriptEnabled - - NSHumanReadableCopyright - Aaron Gerber and Derek Buranen 2010 - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - PyMainFileNames - - __boot__ - - PyOptions - - alias - - argv_emulation - - no_chdir - - optimize - 0 - prefer_ppc - - site_packages - - use_pythonpath - - - PyResourcePackages - - PyRuntimeLocations - - @executable_path/../Frameworks/Python.framework/Versions/2.6/Python - /System/Library/Frameworks/Python.framework/Versions/2.6/Python - - PythonInfoDict - - PythonExecutable - /System/Library/Frameworks/Python.framework/Versions/2.6/Resources/Python.app/Contents/MacOS/Python - PythonLongVersion - Python 2.6.1 (r261:67515, Jul 7 2009, 23:51:51) -[GCC 4.2.1 (Apple Inc. build 5646)] on darwin - PythonShortVersion - 2.6 - py2app - - alias - - template - app - version - 0.4.2 - - - - diff --git a/gitso/trunk/makegitso.sh b/gitso/trunk/makegitso.sh index eb6cb5f..7529bd0 100755 --- a/gitso/trunk/makegitso.sh +++ b/gitso/trunk/makegitso.sh @@ -218,10 +218,6 @@ USESRC="no" ## # Get Comman line arguments ############################ -if test "$1" = ""; then - helpMenu -fi - for param in "$@" do if test "${param}" = "--no-clean"; then @@ -268,8 +264,8 @@ elif [ "`uname -a | grep Darwin`" != "" ]; then # # Patch was made with: diff -aurr . ../cotvnc-gitso/ > cotvnc-gitso.diff # - snowLeopardDMG LeopardDMG + snowLeopardDMG else echo -e "Error, you need py2applet to be installed." fi