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:
@@ -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`
|
||||
-------------------------
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user