Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SOCKS5 proxy support #195

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
39 changes: 38 additions & 1 deletion quicklisp/http.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ information."
(defun make-request-buffer (host port path &key (method "GET"))
"Return an octet vector suitable for sending as an HTTP 1.1 request."
(setf method (string method))
(when *proxy-url*
(when (and *proxy-url* (not (string= (scheme (url *proxy-url*)) "socks5")))
(setf path (full-proxy-path host port path)))
(let ((sink (make-instance 'octet-sink)))
(flet ((add-line (&rest strings)
Expand Down Expand Up @@ -804,6 +804,40 @@ the indexes in the header accordingly."
(apply call (urlstring url) file rest)
(error "Unknown scheme ~S" url))))

(defun socks5-read-reply (connection length)
"Ready a reply packet from a SOCKS5 server. Check the protocol version
and length."
(let* ((buffer (make-octet-vector length))
(n-octets (read-octets buffer connection)))
(unless (and (= n-octets length) (= 5 (aref buffer 0)))
(error "SOCKS5 protocol error"))
buffer))

(defun socks5-handshake (connection hostname port)
;; Send hello. Version 5, no authentication.
(write-octets #(5 1 0) connection)
(socks5-read-reply connection 2)

;; Send CONNECT with address type DOMAINNAME.
(let ((sink (make-instance 'octet-sink)))
(add-octets #(5 1 0 3) sink)
(add-octet (length hostname) sink)
(loop for ch across hostname do (add-octet (char-int ch) sink))
(flet ((htons (num)
(vector (logand (ash num -8) #xff) (logand num #xff))))
(add-octets (htons port) sink))
(write-octets (sink-buffer sink) connection))

;; Check response. If successful then subsequent data on CONNECTION
;; will be tunneled to remote server.
(let ((reply (socks5-read-reply connection 10)))
(case (aref reply 1)
(0 t)
(3 (error "SOCKS5 network unreachable"))
(4 (error "SOCKS5 host unreachable ~S" hostname))
(5 (error "SOCKS5 connection refused ~S" hostname))
(otherwise (error "SOCKS5 unknown error ~S" hostname)))))

(defun http-fetch (url file &key (follow-redirects t) quietly
(if-exists :rename-and-delete)
(maximum-redirects *maximum-redirects*))
Expand All @@ -822,6 +856,9 @@ the indexes in the header accordingly."
:url original-url
:redirect-count redirect-count))
(with-connection (connection (hostname connect-url) (or (port connect-url) 80))
(when (string= (scheme connect-url) "socks5")
(socks5-handshake connection (hostname original-url) (or (port original-url) 80))
(format stream "~&; Connected via SOCKS5 proxy at ~A~%" connect-url))
(let ((cbuf (make-instance 'cbuf :connection connection))
(request (request-buffer "GET" url)))
(write-octets request connection)
Expand Down