Bind our SSH daemon with SO_REUSEADDR

This permits the daemon to come up even if there are connections stuck
in TIME_WAIT or FIN_WAIT2 status within the kernel, which may happen on
a busy server if the daemon is brought down and back up again quickly.

Its potentially risky that you could run two Gerrit daemons on the
same host on the same port and not realize it, so we'll just have to
trust the administrator to not do this.

Bug: GERRIT-164
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2009-05-14 10:26:47 -07:00
parent 5856433149
commit 9410f2cf90
3 changed files with 184 additions and 53 deletions

View File

@@ -181,6 +181,17 @@ is per-user, so 1024 items translates to 1024 unique user accounts.
As each individual user account may configure multiple SSH keys,
the total number of keys may be larger than the item count.
Section sshd
~~~~~~~~~~~~
sshd.reuseAddress::
+
If true, permits the daemon to bind to the port even if the port
is already in use. If false, the daemon ensures the port is not
in use before starting. Busy sites may need to set this to true
to permit fast restarts.
+
By default, true.
File `replication.config`
-------------------------

View File

@@ -771,6 +771,11 @@ public class GerritServer {
return u;
}
/** Get the parsed <code>$site_path/gerrit.config</code> file. */
public RepositoryConfig getGerritConfig() {
return gerritConfigFile;
}
/** Get the repositories maintained by this server. */
public RepositoryCache getRepositoryCache() {
return repositories;

View File

@@ -19,24 +19,50 @@ import com.google.gerrit.server.GerritServer;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.gwtorm.client.OrmException;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.sshd.SshServer;
import org.apache.sshd.common.Cipher;
import org.apache.sshd.common.Compression;
import org.apache.sshd.common.KeyExchange;
import org.apache.sshd.common.KeyPairProvider;
import org.apache.sshd.common.Mac;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.Signature;
import org.apache.sshd.common.cipher.AES128CBC;
import org.apache.sshd.common.cipher.AES192CBC;
import org.apache.sshd.common.cipher.AES256CBC;
import org.apache.sshd.common.cipher.BlowfishCBC;
import org.apache.sshd.common.cipher.TripleDESCBC;
import org.apache.sshd.common.compression.CompressionNone;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.mac.HMACMD5;
import org.apache.sshd.common.mac.HMACMD596;
import org.apache.sshd.common.mac.HMACSHA1;
import org.apache.sshd.common.mac.HMACSHA196;
import org.apache.sshd.common.random.BouncyCastleRandom;
import org.apache.sshd.common.random.JceRandom;
import org.apache.sshd.common.random.SingletonRandomFactory;
import org.apache.sshd.common.session.AbstractSession;
import org.apache.sshd.common.signature.SignatureDSA;
import org.apache.sshd.common.signature.SignatureRSA;
import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.server.ServerChannel;
import org.apache.sshd.server.SessionFactory;
import org.apache.sshd.server.UserAuth;
import org.apache.sshd.server.auth.UserAuthPublicKey;
import org.apache.sshd.server.channel.ChannelSession;
import org.apache.sshd.server.kex.DHG1;
import org.apache.sshd.server.kex.DHG14;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spearce.jgit.lib.RepositoryConfig;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.security.KeyPair;
import java.security.PublicKey;
@@ -63,80 +89,33 @@ import java.util.List;
* Port 8010
* </pre>
*/
public class GerritSshDaemon {
public class GerritSshDaemon extends SshServer {
private static final Logger log =
LoggerFactory.getLogger(GerritSshDaemon.class);
private static SshServer sshd;
private static GerritSshDaemon sshd;
private static Collection<PublicKey> hostKeys = Collections.emptyList();
public static synchronized void startSshd() throws OrmException,
XsrfException, SocketException {
final GerritServer srv = GerritServer.getInstance();
final int myPort = Common.getGerritConfig().getSshdPort();
final SshServer daemon = SshServer.setUpDefaultServer();
daemon.setPort(myPort);
daemon.setSessionFactory(new SessionFactory() {
@Override
protected AbstractSession createSession(final IoSession ioSession)
throws Exception {
final AbstractSession s = super.createSession(ioSession);
s.setAttribute(SshUtil.REMOTE_PEER, ioSession.getRemoteAddress());
return s;
}
});
final File sitePath = srv.getSitePath();
if (SecurityUtils.isBouncyCastleRegistered()) {
daemon.setKeyPairProvider(new FileKeyPairProvider(new String[] {
new File(sitePath, "ssh_host_rsa_key").getAbsolutePath(),
new File(sitePath, "ssh_host_dsa_key").getAbsolutePath()}));
} else {
final SimpleGeneratorHostKeyProvider keyp;
keyp = new SimpleGeneratorHostKeyProvider();
keyp.setPath(new File(sitePath, "ssh_host_key").getAbsolutePath());
daemon.setKeyPairProvider(keyp);
}
setCompression(daemon);
setUserAuth(daemon);
daemon.setPublickeyAuthenticator(new DatabasePubKeyAuth(srv));
daemon.setCommandFactory(new GerritCommandFactory());
daemon.setShellFactory(new NoShell());
final GerritSshDaemon daemon = new GerritSshDaemon(srv);
try {
sshd = daemon;
daemon.start();
hostKeys = computeHostKeys();
log.info("Started Gerrit SSHD on 0.0.0.0:" + myPort);
log.info("Started Gerrit SSHD on 0.0.0.0:" + daemon.getPort());
} catch (IOException e) {
log.error("Cannot start Gerrit SSHD on 0.0.0.0:" + myPort, e);
log.error("Cannot start Gerrit SSHD on 0.0.0.0:" + daemon.getPort(), e);
sshd = null;
hostKeys = Collections.emptyList();
final SocketException e2;
e2 = new SocketException("Cannot start sshd on " + myPort);
e2 = new SocketException("Cannot start sshd on " + daemon.getPort());
e2.initCause(e);
throw e2;
}
}
@SuppressWarnings("unchecked")
private static void setCompression(final SshServer daemon) {
// Always disable transparent compression. The majority of our data
// transfer is highly compressed Git pack files. We cannot make them
// any smaller than they already are.
//
daemon.setCompressionFactories(Arrays
.<NamedFactory<Compression>> asList(new CompressionNone.Factory()));
}
@SuppressWarnings("unchecked")
private static void setUserAuth(final SshServer daemon) {
daemon.setUserAuthFactories(Arrays
.<NamedFactory<UserAuth>> asList(new UserAuthPublicKey.Factory()));
}
private static Collection<PublicKey> computeHostKeys() {
final KeyPairProvider p = sshd.getKeyPairProvider();
final List<PublicKey> keys = new ArrayList<PublicKey>(2);
@@ -172,4 +151,140 @@ public class GerritSshDaemon {
public static synchronized Collection<PublicKey> getHostKeys() {
return hostKeys;
}
private IoAcceptor acceptor;
private boolean reuseAddress;
private GerritSshDaemon(final GerritServer srv) {
setPort(Common.getGerritConfig().getSshdPort());
final RepositoryConfig cfg = srv.getGerritConfig();
reuseAddress = cfg.getBoolean("sshd", "reuseaddress", true);
if (SecurityUtils.isBouncyCastleRegistered()) {
initProviderBouncyCastle();
} else {
initProviderJce();
}
initCipers();
initMac();
initSignatures();
initChannels();
initCompression();
initUserAuth(srv);
initHostKey(srv);
setCommandFactory(new GerritCommandFactory());
setShellFactory(new NoShell());
setSessionFactory(new SessionFactory() {
@Override
protected AbstractSession createSession(final IoSession ioSession)
throws Exception {
final AbstractSession s = super.createSession(ioSession);
s.setAttribute(SshUtil.REMOTE_PEER, ioSession.getRemoteAddress());
return s;
}
});
}
@Override
public void start() throws IOException {
if (acceptor == null) {
checkConfig();
final NioSocketAcceptor ain = new NioSocketAcceptor();
final SessionFactory handler = getSessionFactory();
handler.setServer(this);
ain.setHandler(handler);
ain.setReuseAddress(reuseAddress);
ain.bind(new InetSocketAddress(getPort()));
acceptor = ain;
}
}
@Override
public void stop() {
if (acceptor != null) {
try {
acceptor.dispose();
} finally {
acceptor = null;
}
}
}
@SuppressWarnings("unchecked")
private void initProviderBouncyCastle() {
setKeyExchangeFactories(Arrays.<NamedFactory<KeyExchange>> asList(
new DHG14.Factory(), new DHG1.Factory()));
setRandomFactory(new SingletonRandomFactory(
new BouncyCastleRandom.Factory()));
}
@SuppressWarnings("unchecked")
private void initProviderJce() {
setKeyExchangeFactories(Arrays
.<NamedFactory<KeyExchange>> asList(new DHG1.Factory()));
setRandomFactory(new SingletonRandomFactory(new JceRandom.Factory()));
}
@SuppressWarnings("unchecked")
private void initCipers() {
setCipherFactories(Arrays.<NamedFactory<Cipher>> asList(
new AES128CBC.Factory(), new TripleDESCBC.Factory(),
new BlowfishCBC.Factory(), new AES192CBC.Factory(),
new AES256CBC.Factory()));
}
@SuppressWarnings("unchecked")
private void initMac() {
setMacFactories(Arrays.<NamedFactory<Mac>> asList(new HMACMD5.Factory(),
new HMACSHA1.Factory(), new HMACMD596.Factory(),
new HMACSHA196.Factory()));
}
@SuppressWarnings("unchecked")
private void initSignatures() {
setSignatureFactories(Arrays.<NamedFactory<Signature>> asList(
new SignatureDSA.Factory(), new SignatureRSA.Factory()));
}
@SuppressWarnings("unchecked")
private void initCompression() {
// Always disable transparent compression. The majority of our data
// transfer is highly compressed Git pack files. We cannot make them
// any smaller than they already are.
//
setCompressionFactories(Arrays
.<NamedFactory<Compression>> asList(new CompressionNone.Factory()));
}
@SuppressWarnings("unchecked")
private void initChannels() {
setChannelFactories(Arrays
.<NamedFactory<ServerChannel>> asList(new ChannelSession.Factory()));
}
@SuppressWarnings("unchecked")
private void initUserAuth(final GerritServer srv) {
setUserAuthFactories(Arrays
.<NamedFactory<UserAuth>> asList(new UserAuthPublicKey.Factory()));
setPublickeyAuthenticator(new DatabasePubKeyAuth(srv));
}
private void initHostKey(final GerritServer srv) {
final File sitePath = srv.getSitePath();
if (SecurityUtils.isBouncyCastleRegistered()) {
setKeyPairProvider(new FileKeyPairProvider(new String[] {
new File(sitePath, "ssh_host_rsa_key").getAbsolutePath(),
new File(sitePath, "ssh_host_dsa_key").getAbsolutePath()}));
} else {
final SimpleGeneratorHostKeyProvider keyp;
keyp = new SimpleGeneratorHostKeyProvider();
keyp.setPath(new File(sitePath, "ssh_host_key").getAbsolutePath());
setKeyPairProvider(keyp);
}
}
}