How can I tell Emacs TRAMP to use specific arguments to ssh?

I'm trying to set up my Emacs TRAMP to access remote files via ssh, and I would like to add some additional arguments to the ssh command that tramp uses for connecting. Is there a way to do this?

I could do it by directly manipulating the variable tramp-methods, but that's icky.


I came up with my own solution, but it's a bit too complicated to post here. If anyone is interested, please comment on this answer, and I will find somewhere to post it.


Edit: Here is my solution, as requested.

Now, set up some functions for appending to the built-in argument lists stored in tramp-methods:

(defun tramp-get-method-parameter (method param)
  "Return the method parameter PARAM.
If the `tramp-methods' entry does not exist, return NIL."
  (let ((entry (assoc param (assoc method tramp-methods))))
    (when entry (cadr entry))))

(defun tramp-set-method-parameter (method param newvalue)
  "Set the method paramter PARAM to VALUE for METHOD.

If METHOD does not yet have PARAM, add it.
If METHOD does not exist, do nothing."
  (let ((method-params (assoc method tramp-methods)))
    (when method-params
          (let ((entry (assoc param method-params)))
        (if entry
            (setcar (cdr entry) newvalue)
          (setcdr (last method-params) '(param newvalue)))))))

(defun tramp-add-args (programs newargs)
  "Append NEWARGS to the argument list for any of PROGRAMS in `tramp-methods'.

PROGRAMS can be a list of strings, or a single string."
  ;; Allow a single program string or a list of matching programs.
  (when (stringp programs)
    (setq programs (list programs)))
  (message "%s" (list programs newargs))
  (loop for method in (mapcar 'car tramp-methods) do
        (let ((login-program (tramp-get-method-parameter method 'tramp-login-program))
              (copy-program (tramp-get-method-parameter method 'tramp-copy-program))
              (login-args (tramp-get-method-parameter method 'tramp-login-args))
              (copy-args (tramp-get-method-parameter method 'tramp-copy-args)))
          (message "Handling %s" method)
          (message "  Handling login program %s" login-program)
          (when (find login-program programs :test 'string=)
            (message "    Adding to login program %s" login-program)
            (tramp-set-method-parameter method 'tramp-login-args (append login-args newargs)))
          (message "  Handling copy program %s" login-program)
          (when (find copy-program programs :test 'string=)
            (message "    Adding to copy program %s" copy-program)
            (tramp-set-method-parameter method 'tramp-copy-args (append copy-args newargs))))))

Finally, use tramp-add-args to add the arguments you want to all the ssh-based methods:

(tramp-add-args
 '("scp" "scp1" "scp2" "scp1_old" "scp2_old" "sftp" "rsync" "ssh" "ssh1" "ssh2" "ssh1_old" "ssh2_old" "scpx" "sshx")
 '(("-o" "ControlPath=~/.ssh/control/emacs-master-%%r@%%h:%%p" "-o" "ControlMaster=auto")))

Bonus Round

The code that I show above sets up connection sharing for ssh. If you want this to work reliably, you need to clear the socket files when your internet connection goes down. The following can accomplish this

First, create a script called ssh-cleanup and put it in your $PATH and put the following in it:

#!/bin/sh
CONTROL_DIR="$HOME/.ssh/control"
find "$CONTROL_DIR" -type s | xargs --no-run-if-empty -- rm -f

This script cleans up stale control files, since these will get left behind when ssh dies because you unplugged your network cable or disconnected your wireless.

Now set up emacs to run this script at the appripriate time:

(defun ssh-cleanup ()
  (ignore-errors
    (call-process "ssh-cleanup")))

(defadvice tramp-cleanup-all-connections (after cleanup-control-files activate)
  (ssh-cleanup))

Now, when you run tramp-cleanup-all-connections, you'll also clean up the ssh socket directory. You should run this whenever you disconnect and reconnect to the internet.

Or, you could set up your computer to do it for you automatically.

Super Bonus Round

If you use Network Manager on Linux, you can have emacs do things for you when you connect or disconnect.

(defcustom network-manager-connect-hook nil
  "List of functions to execute upon successful establishment of
  a network connection."
  :type 'hook
  :group 'network-manager)

(defcustom network-manager-disconnect-hook nil
  "List of functions to execute upon disconnection from the
  network."
  :type 'hook
  :group 'network-manager)

;; Only enable all of this if dbus is found
(when (require 'dbus nil nil)

  (defun network-manager-dbus-signal-handler (nmstate)
    "Execute the appropriate hook when Network Manager connects or
  disconnects."
    (case nmstate
      ((4 1) (run-hooks 'network-manager-disconnect-hook))
      (3 (run-hooks 'network-manager-connect-hook))))

  (defvar network-manager-dbus-registration nil)

  (defun network-manager-integration-enable ()
    (interactive)
    (network-manager-integration-disable) ; Make sure to clean up first
    (setq network-manager-dbus-registration
          (dbus-register-signal :system
                                "org.freedesktop.NetworkManager" "/org/freedesktop/NetworkManager"
                                "org.freedesktop.NetworkManager" "StateChanged"
                                'network-manager-dbus-signal-handler)))

  (defun network-manager-integration-disable ()
    (interactive)
    (when network-manager-dbus-registration
      (dbus-unregister-object network-manager-dbus-registration)
      (setq network-manager-dbus-registration nil)))

  ;; Finally, enable it
  (network-manager-integration-enable))

Now add set tramp-cleanup-all-connections to run when you disconnect.

(add-hook 'network-manager-disconnect-hook 'tramp-cleanup-all-connections)

For a one-time use, I can't think of a convenient way.

For a more permanent setup, do your configuration at the ssh level (which has the side benefit of working outside Emacs as well). In your ~/.ssh/config, define a host alias with additional options, and connect to the alias with Tramp. E.g. in ~/.ssh/config:

Host foo
HostName foo.example.com
User rt
ProxyCommand /usr/bin/nc -X connect -x proxy:3128 %h %p

Then open /ssh:foo:/path/to/file in Emacs.