Skip to content
Permalink
Browse files
Update parser and add unittests
  • Loading branch information
hellais committed Dec 2, 2012
1 parent 72475c3 commit fbd0a68be25acc0d6b891f558737320fe22ff375
Showing with 271 additions and 104 deletions.
  1. +21 −0 scripts/codegen.py
  2. +15 −10 txsocksx/auth.py
  3. +56 −14 txsocksx/errors.py
  4. +73 −53 txsocksx/parser.py
  5. +106 −27 txsocksx/test/test_parser.py
@@ -0,0 +1,21 @@


def makeTestFailure():
l = ['ServerFailure',
'ConnectionNotAllowed',
'NetworkUnreachable',
'HostUnreachable',
'ConnectionRefused',
'TTLExpired',
'CommandNotSupported',
'AddressNotSupported']

base = """
def test_ServerReply%(error_name)s(self):
p = SOCKSGrammar(dummyServerReplyFail%(idx)sIPV4)
failure, addr, port = p.serverReply()
self.assertIs(failure, e.%(error_name)s)
"""
for i, v in enumerate(l):
print base % {'idx': i+1, 'error_name': v}
makeTestFailure()
@@ -1,28 +1,33 @@
from twisted.internet import defer
from txsocksx.errors import SocksError
from txsocksx.errors import SOCKSError

class Anonymous(object):
method = '\x00'
"""
( 0 )
"""
def negotiate(self, proto):
pass

class GSSAPI(object):
"""
( 1 )
"""
def negotiate(self, proto):
return defer.succeed(None)
raise NotImplemented

class UsernamePasswordAuthFailed(SocksError):
class UsernamePasswordAuthFailed(SOCKSError):
pass

class UsernamePassword(object):
method = '\x02'

"""
( 2 )
"""
def __init__(self, uname, passwd):
self.uname = uname
self.passwd = passwd

@defer.inlineCallbacks
def negotiate(self, proto):
proto.transport.write(
'\x01'
+ chr(len(self.uname)) + self.uname
+ chr(len(self.passwd)) + self.passwd)
resp, = yield proto.unpack('!xB')
if resp != 0:
raise UsernamePasswordAuthFailed(resp)
@@ -1,25 +1,67 @@
from twisted.internet import error
import txsocksx.constants as c

class SocksError(Exception):
class SOCKSError(Exception):
pass

class MethodsNotAcceptedError(SocksError):
class MethodsNotAcceptedError(SOCKSError):
pass

class ConnectionError(SocksError):
class ConnectionError(SOCKSError):
pass

class ConnectionLostEarly(SocksError, error.ConnectionLost):
class ConnectionLostEarly(SOCKSError, error.ConnectionLost):
pass

socks5ErrorMap = {
c.SOCKS5_GENERAL_FAILURE: "general SOCKS server failure",
c.SOCKS5_REJECTED: "connection not allowed by ruleset",
c.SOCKS5_NETWORK_UNREACHABLE: "network unreachable",
c.SOCKS5_HOST_UNREACHABLE: "host unreachable",
c.SOCKS5_CONNECTION_REFUSED: "connection refused",
c.SOCKS5_TTL_EXPIRED: "TTL expired",
c.SOCKS5_COMMAND_NOT_SUPPORTED: "command not supported",
c.SOCKS5_ADDRESS_NOT_SUPPORTED: "address type not supported",
}
class StateError(Exception):
"""
There was a problem with the State.
"""
pass

class NoAcceptableMethods(SOCKSError):
"""
No Acceptable Methods ( FF )
"""

class ServerFailure(SOCKSError):
"""
General SOCKS server failure ( 1 )
"""

class ConnectionNotAllowed(SOCKSError):
"""
Connection not allowed ( 2 )
"""

class NetworkUnreachable(SOCKSError):
"""
Network unreachable ( 3 )
"""

class HostUnreachable(SOCKSError):
"""
Host unreachable ( 4 )
"""

class ConnectionRefused(SOCKSError):
"""
Connection refused ( 5 )
"""

class TTLExpired(SOCKSError):
"""
TTL expired ( 6 )
"""

class CommandNotSupported(SOCKSError):
"""
Command Not Supported ( 7 )
"""

class AddressNotSupported(SOCKSError):
"""
Address type not supported ( 8 )
"""


@@ -1,24 +1,26 @@
import parsley
import struct

socks_grammar = """
# XXX Is this correct?
octet = anything
from txsocksx import errors, auth

byteToInt = octet:b
-> ord(b)
socks_grammar = r"""
# XXX probably move these to another grammar and inherit from it
byte = anything:byte -> ord(byte)
short = byte:high byte:low -> (high << 8) | low
byteToIntStr = octet:b
byteToIntStr = anything:b
-> str(ord(b))
ver = '\x05' | '\x04'
# IPV4, IPV6 Address in binary form
IPV4AddrBytes = byte{4}:quads
-> '.'.join(str(q) for q in quads)
# XXX properly parse IPV6
IPV6AddrBytes = <byte{16}>
rsv = '\x00'
IPV4Addr = anything{4}:quads
-> '.'.join(str(ord(q)) for q in quads)
IPV6Addr = <anything{16}>
# IPV6 Address in the form 'X:X:X::X::X'
# IPV4 Address in the form '0.0.0.0'
IPV4AddrStr = <(digit{1, 3} '.'){4}>
IPV6AddrStr = <(hexdigit{0, 4} ':'){7} hexdigit{1, 4}>
# XXX notes
# letterOrDigitOrHyphen = letterOrDigit | '-'
@@ -27,56 +29,74 @@
# < (domainLabel '.'?)* >
# XXX make this stricter
SOCKSDomainName =
byteToInt:len <anything*>
DomainName =
byte:len <anything{len}>
# Below are SOCKS specific messages
ver = ('\x05' -> 5)
| ('\x04' -> 4)
rsv = <'\x00'>
SOCKSAddress = ('\x01' IPV4AddrBytes:addr
-> addr)
| ('\x03' IPV6AddrBytes:addr
-> addr)
| ('\x04' DomainName:domain
-> domain)
SOCKSAddress = (token('\x01') IPV4Addr:addr
-> addr
hostToSOCKSAddress =
( IPV4AddrBytes:addr
-> '\x01' + addr )
| token('\x03') IPV6Addr:addr
-> addr
| ( IPV6AddrBytes:addr
-> '\x03' + addr )
| token('\x04') SOCKSDomainName:domain
-> domain
)
| ( DomainName:addr
-> '\x04' + addr )
port = anything{2}
port = short:p -> int(p)
# The Client version identified/method selection message
clientVersionMethodMessage =
ver octet:nmethods octet{1, 255}:methods
-> (ver, nmethods, methods)
clientVersionMethod =
ver:v anything:nmethods anything{1, 255}:methods
-> (v, nmethods, methods)
methods = tokenize('\x00') -> 'No Authentication Required'
| tokenize('\x01') -> 'GSSAPI'
| tokenize('\x02') -> 'Username/Password'
| tokenize('\xFF') -> 'No Acceptable Methods'
methods = ('\x00' -> a.Anonymous)
| ('\x01' -> a.GSSAPI)
| ('\x02' -> a.UsernamePassword)
| ('\xFF' -> e.NoAcceptableMethods)
# The Server version identified/method selection message
serverVersionMethodMessage =
ver methods -> (ver, method)
cmd = token('\x01') -> 'Connect'
| token('\x02') -> 'Bind'
| token('\x03') -> 'UDP Associate'
clientRequestMessage =
ver cmd rsv SOCKSAddress port
rep = token('\x00') -> 'Suceeded'
| token('\x01') -> 'General SOCKS server failure'
| token('\x02') -> 'Connection not allowed'
| token('\x03') -> 'Network unreachable'
| token('\x04') -> 'Host unreachable'
| token('\x05') -> 'Connection refused'
| token('\x06') -> 'TTL expired'
| token('\x07') -> 'Command not supported'
| token('\x08') -> 'Address type not supported'
serverReplyMessage =
ver rep:reply rsv SOCKSAddress:address port:port
serverVersionMethod =
ver:v methods:m -> (v, m)
cmd = ('\x01' -> 1)
| ('\x02' -> 2)
| ('\x03' -> 3)
clientRequest =
ver cmd:command byte SOCKSAddress:address port:port
-> (command, address, port)
rep = ('\x00' -> 0)
| ('\x01' -> e.ServerFailure)
| ('\x02' -> e.ConnectionNotAllowed)
| ('\x03' -> e.NetworkUnreachable)
| ('\x04' -> e.HostUnreachable)
| ('\x05' -> e.ConnectionRefused)
| ('\x06' -> e.TTLExpired)
| ('\x07' -> e.CommandNotSupported)
| ('\x08' -> e.AddressNotSupported)
serverReply =
ver rep:reply byte SOCKSAddress:address port:port
-> (reply, address, port)
"""

SOCKSGrammar = parsley.makeGrammar(socks_grammar, {})
SOCKSGrammar = parsley.makeGrammar(socks_grammar,
{"e": errors, "a": auth}
)

0 comments on commit fbd0a68

Please sign in to comment.