From 9bb35a78b8298930adf978046e9f6c53c6853e27 Mon Sep 17 00:00:00 2001 From: Akshay Dayal Date: Fri, 17 Apr 2015 08:11:43 -0700 Subject: [PATCH 01/14] [JENKINS-26580] Initial implementation of JNLP3-connect protocol --- .../slaves/JnlpSlaveAgentProtocol3.java | 221 ++++++++++++++++++ .../jenkins/slaves/JnlpSlaveHandshake.java | 2 +- pom.xml | 2 +- 3 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java new file mode 100644 index 000000000000..5c4d348b00d0 --- /dev/null +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -0,0 +1,221 @@ +package jenkins.slaves; + +import hudson.AbortException; +import hudson.Extension; +import hudson.TcpSlaveAgentListener; +import hudson.Util; +import hudson.remoting.Channel; +import hudson.remoting.ChannelBuilder; +import hudson.remoting.SocketChannelStream; +import hudson.slaves.SlaveComputer; +import jenkins.AgentProtocol; +import jenkins.model.Jenkins; +import org.jenkinsci.remoting.engine.JnlpProtocol; +import org.jenkinsci.remoting.engine.JnlpProtocol3; +import org.jenkinsci.remoting.engine.jnlp3.ChannelCiphers; +import org.jenkinsci.remoting.engine.jnlp3.CipherUtils; +import org.jenkinsci.remoting.engine.jnlp3.HandshakeCiphers; +import org.jenkinsci.remoting.nio.NioChannelHub; + +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.inject.Inject; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.Socket; +import java.security.SecureRandom; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; + +/** + * Master-side implementation for JNLP3-connect protocol. + * + *

@see {@link JnlpProtocol3} for more details. + */ +@Extension +public class JnlpSlaveAgentProtocol3 extends AgentProtocol { + @Inject + NioChannelSelector hub; + + @Override + public String getName() { + return "JNLP3-connect"; + } + + @Override + public void handle(Socket socket) throws IOException, InterruptedException { + new Handler(hub.getHub(), socket).run(); + } + + static class Handler extends JnlpSlaveHandshake { + + public Handler(NioChannelHub hub, Socket socket) throws IOException { + super(hub,socket, + new DataInputStream(socket.getInputStream()), + new PrintWriter(new BufferedWriter( + new OutputStreamWriter(socket.getOutputStream(), "UTF-8")), true)); + } + + protected void run() throws IOException, InterruptedException { + request.load(new ByteArrayInputStream(in.readUTF().getBytes("UTF-8"))); + String nodeName = request.getProperty(JnlpProtocol3.SLAVE_NAME_KEY); + String encryptedChallenge = request.getProperty(JnlpProtocol3.CHALLENGE_KEY); + byte[] handshakeSpecKey = CipherUtils.keyFromString( + request.getProperty(JnlpProtocol3.HANDSHAKE_SPEC_KEY)); + String cookie = request.getProperty(JnlpProtocol3.COOKIE_KEY); + + SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); + if(computer == null) { + error("Slave trying to register for invalid node: " + nodeName); + return; + } + String slaveSecret = computer.getJnlpMac(); + + HandshakeCiphers handshakeCiphers = null; + try { + handshakeCiphers = HandshakeCiphers.create(nodeName, slaveSecret, handshakeSpecKey); + } catch (Exception e) { + error("Failed to create handshake ciphers for node: " + nodeName); + return; + } + + String challenge = null; + try { + challenge = handshakeCiphers.decrypt(encryptedChallenge); + } catch (Exception e) { + throw new IOException("Unable to decrypt challenge", e); + } + if (!challenge.startsWith(JnlpProtocol3.CHALLENGE_PREFIX)) { + error("Received invalid challenge"); + return; + } + + // At this point the slave looks legit, check if we think they are already connected. + Channel oldChannel = computer.getChannel(); + if(oldChannel != null) { + if (cookie != null && cookie.equals(oldChannel.getProperty(COOKIE_NAME))) { + // We think we are currently connected, but this request proves that it's from + // the party we are supposed to be communicating to. so let the current one get + // disconnected + LOGGER.info("Disconnecting " + nodeName + + " as we are reconnected from the current peer"); + try { + computer.disconnect(new TcpSlaveAgentListener.ConnectionFromCurrentPeer()) + .get(15, TimeUnit.SECONDS); + } catch (ExecutionException e) { + throw new IOException("Failed to disconnect the current client",e); + } catch (TimeoutException e) { + throw new IOException("Failed to disconnect the current client",e); + } + } else { + error(nodeName + + " is already connected to this master. Rejecting this connection."); + return; + } + } + + // Send challenge response. + String challengeReverse = new StringBuilder( + challenge.substring(JnlpProtocol3.CHALLENGE_PREFIX.length())) + .reverse().toString(); + String challengeResponse = JnlpProtocol3.CHALLENGE_PREFIX + challengeReverse; + String encryptedChallengeResponse = null; + try { + encryptedChallengeResponse = handshakeCiphers.encrypt(challengeResponse); + } catch (Exception e) { + throw new IOException("Error encrypting challenge response", e); + } + out.println(encryptedChallengeResponse.getBytes("UTF-8").length); + out.print(encryptedChallengeResponse); + out.flush(); + + // If the slave accepted our challenge response it will send channel cipher keys. + String challengeVerificationMessage = in.readUTF(); + if (!challengeVerificationMessage.equals(JnlpProtocol.GREETING_SUCCESS)) { + error("Slave did not accept our challenge response"); + return; + } + String encryptedAesKeyString = in.readUTF(); + String encryptedSpecKeyString = in.readUTF(); + ChannelCiphers channelCiphers = null; + try { + String aesKeyString = handshakeCiphers.decrypt(encryptedAesKeyString); + String specKeyString = handshakeCiphers.decrypt(encryptedSpecKeyString); + channelCiphers = ChannelCiphers.create( + CipherUtils.keyFromString(aesKeyString), + CipherUtils.keyFromString(specKeyString)); + } catch (Exception e) { + error("Failed to decrypt channel cipher keys"); + return; + } + + String newCookie = generateCookie(); + try { + out.println(handshakeCiphers.encrypt(newCookie)); + } catch (Exception e) { + throw new IOException("Error encrypting cookie", e); + } + + Channel establishedChannel = jnlpConnect(computer, channelCiphers); + establishedChannel.setProperty(COOKIE_NAME, newCookie); + } + + protected Channel jnlpConnect( + SlaveComputer computer, ChannelCiphers channelCiphers) + throws InterruptedException, IOException { + final String nodeName = computer.getName(); + final OutputStream log = computer.openLogFile(); + PrintWriter logw = new PrintWriter(log,true); + logw.println("JNLP agent connected from "+ socket.getInetAddress()); + + try { + ChannelBuilder cb = createChannelBuilder(nodeName); + Channel channel = cb.withHeaderStream(log) + .build(new CipherInputStream(SocketChannelStream.in(socket), + channelCiphers.getDecryptCipher()), + new CipherOutputStream(SocketChannelStream.out(socket), + channelCiphers.getEncryptCipher())); + + computer.setChannel(channel, log, + new Channel.Listener() { + @Override + public void onClosed(Channel channel, IOException cause) { + if(cause != null) + LOGGER.log(Level.WARNING, + Thread.currentThread().getName() + " for + " + + nodeName + " terminated", cause); + try { + socket.close(); + } catch (IOException e) { + // Do nothing. + } + } + }); + return computer.getChannel(); + } catch (AbortException e) { + logw.println(e.getMessage()); + logw.println("Failed to establish the connection with the slave"); + throw e; + } catch (IOException e) { + logw.println("Failed to establish the connection with the slave " + nodeName); + e.printStackTrace(logw); + throw e; + } + } + + private String generateCookie() { + byte[] cookie = new byte[32]; + new SecureRandom().nextBytes(cookie); + return Util.toHexString(cookie); + } + } + + static final String COOKIE_NAME = JnlpSlaveAgentProtocol3.class.getName() + ".cookie"; +} diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java index b7c01a239e1a..eb9499e1c34b 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java @@ -114,5 +114,5 @@ public ChannelBuilder createChannelBuilder(String nodeName) { } - private static final Logger LOGGER = Logger.getLogger(JnlpSlaveHandshake.class.getName()); + static final Logger LOGGER = Logger.getLogger(JnlpSlaveHandshake.class.getName()); } diff --git a/pom.xml b/pom.xml index bb2e89b46afc..4a525155a5b0 100644 --- a/pom.xml +++ b/pom.xml @@ -176,7 +176,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 2.50 + 2.51-SNAPSHOT From e9f5caa13fa1a3d1bf602ec9d67dac75f3310889 Mon Sep 17 00:00:00 2001 From: Akshay Dayal Date: Thu, 23 Apr 2015 02:24:40 -0700 Subject: [PATCH 02/14] [JENKINS-26580] Updated implementation of Jnlp3 protocol --- .../slaves/JnlpSlaveAgentProtocol3.java | 119 ++++++++++-------- 1 file changed, 64 insertions(+), 55 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 5c4d348b00d0..ce0a59beef68 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -13,8 +13,8 @@ import org.jenkinsci.remoting.engine.JnlpProtocol; import org.jenkinsci.remoting.engine.JnlpProtocol3; import org.jenkinsci.remoting.engine.jnlp3.ChannelCiphers; -import org.jenkinsci.remoting.engine.jnlp3.CipherUtils; import org.jenkinsci.remoting.engine.jnlp3.HandshakeCiphers; +import org.jenkinsci.remoting.engine.jnlp3.Jnlp3Util; import org.jenkinsci.remoting.nio.NioChannelHub; import javax.crypto.CipherInputStream; @@ -28,6 +28,7 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; +import java.nio.charset.Charset; import java.security.SecureRandom; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -38,6 +39,8 @@ * Master-side implementation for JNLP3-connect protocol. * *

@see {@link JnlpProtocol3} for more details. + * + * @author Akshay Dayal */ @Extension public class JnlpSlaveAgentProtocol3 extends AgentProtocol { @@ -59,45 +62,41 @@ static class Handler extends JnlpSlaveHandshake { public Handler(NioChannelHub hub, Socket socket) throws IOException { super(hub,socket, new DataInputStream(socket.getInputStream()), - new PrintWriter(new BufferedWriter( - new OutputStreamWriter(socket.getOutputStream(), "UTF-8")), true)); + new PrintWriter(new BufferedWriter(new OutputStreamWriter( + socket.getOutputStream(), Charset.forName("UTF-8"))), true)); } protected void run() throws IOException, InterruptedException { - request.load(new ByteArrayInputStream(in.readUTF().getBytes("UTF-8"))); + // Get initiation information from slave. + request.load(new ByteArrayInputStream(in.readUTF().getBytes(Charset.forName("UTF-8")))); String nodeName = request.getProperty(JnlpProtocol3.SLAVE_NAME_KEY); - String encryptedChallenge = request.getProperty(JnlpProtocol3.CHALLENGE_KEY); - byte[] handshakeSpecKey = CipherUtils.keyFromString( - request.getProperty(JnlpProtocol3.HANDSHAKE_SPEC_KEY)); - String cookie = request.getProperty(JnlpProtocol3.COOKIE_KEY); + // Create handshake ciphers. SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); if(computer == null) { error("Slave trying to register for invalid node: " + nodeName); return; } String slaveSecret = computer.getJnlpMac(); + HandshakeCiphers handshakeCiphers = HandshakeCiphers.create(nodeName, slaveSecret); - HandshakeCiphers handshakeCiphers = null; - try { - handshakeCiphers = HandshakeCiphers.create(nodeName, slaveSecret, handshakeSpecKey); - } catch (Exception e) { - error("Failed to create handshake ciphers for node: " + nodeName); + // Authenticate to the slave. + if (!authenticateToSlave(handshakeCiphers)) { return; } - String challenge = null; - try { - challenge = handshakeCiphers.decrypt(encryptedChallenge); - } catch (Exception e) { - throw new IOException("Unable to decrypt challenge", e); + // If there is a cookie decrypt it. + String cookie = null; + if (request.getProperty(JnlpProtocol3.COOKIE_KEY) != null) { + cookie = handshakeCiphers.decrypt(request.getProperty(JnlpProtocol3.COOKIE_KEY)); } - if (!challenge.startsWith(JnlpProtocol3.CHALLENGE_PREFIX)) { - error("Received invalid challenge"); + + // Validate the slave. + if (!validateSlave(handshakeCiphers)) { return; } - // At this point the slave looks legit, check if we think they are already connected. + // The slave is authenticated, see if its already connected. Channel oldChannel = computer.getChannel(); if(oldChannel != null) { if (cookie != null && cookie.equals(oldChannel.getProperty(COOKIE_NAME))) { @@ -121,50 +120,60 @@ protected void run() throws IOException, InterruptedException { } } - // Send challenge response. - String challengeReverse = new StringBuilder( - challenge.substring(JnlpProtocol3.CHALLENGE_PREFIX.length())) - .reverse().toString(); - String challengeResponse = JnlpProtocol3.CHALLENGE_PREFIX + challengeReverse; - String encryptedChallengeResponse = null; - try { - encryptedChallengeResponse = handshakeCiphers.encrypt(challengeResponse); - } catch (Exception e) { - throw new IOException("Error encrypting challenge response", e); - } - out.println(encryptedChallengeResponse.getBytes("UTF-8").length); + // Send greeting and new cookie. + out.println(JnlpProtocol.GREETING_SUCCESS); + String newCookie = generateCookie(); + out.println(handshakeCiphers.encrypt(newCookie)); + + // Now get the channel cipher information. + String aesKeyString = handshakeCiphers.decrypt(in.readUTF()); + String specKeyString = handshakeCiphers.decrypt(in.readUTF()); + ChannelCiphers channelCiphers = ChannelCiphers.create( + Jnlp3Util.keyFromString(aesKeyString), + Jnlp3Util.keyFromString(specKeyString)); + + Channel establishedChannel = jnlpConnect(computer, channelCiphers); + establishedChannel.setProperty(COOKIE_NAME, newCookie); + } + + private boolean authenticateToSlave(HandshakeCiphers handshakeCiphers) throws IOException { + String challenge = handshakeCiphers.decrypt( + request.getProperty(JnlpProtocol3.CHALLENGE_KEY)); + + // Send slave challenge response. + String challengeResponse = Jnlp3Util.createChallengeResponse(challenge); + String encryptedChallengeResponse = handshakeCiphers.encrypt(challengeResponse); + out.println(encryptedChallengeResponse.getBytes(Charset.forName("UTF-8")).length); out.print(encryptedChallengeResponse); out.flush(); - // If the slave accepted our challenge response it will send channel cipher keys. + // If the slave accepted our challenge response send our challenge. String challengeVerificationMessage = in.readUTF(); if (!challengeVerificationMessage.equals(JnlpProtocol.GREETING_SUCCESS)) { error("Slave did not accept our challenge response"); - return; - } - String encryptedAesKeyString = in.readUTF(); - String encryptedSpecKeyString = in.readUTF(); - ChannelCiphers channelCiphers = null; - try { - String aesKeyString = handshakeCiphers.decrypt(encryptedAesKeyString); - String specKeyString = handshakeCiphers.decrypt(encryptedSpecKeyString); - channelCiphers = ChannelCiphers.create( - CipherUtils.keyFromString(aesKeyString), - CipherUtils.keyFromString(specKeyString)); - } catch (Exception e) { - error("Failed to decrypt channel cipher keys"); - return; + return false; } - String newCookie = generateCookie(); - try { - out.println(handshakeCiphers.encrypt(newCookie)); - } catch (Exception e) { - throw new IOException("Error encrypting cookie", e); + return true; + } + + private boolean validateSlave(HandshakeCiphers handshakeCiphers) throws IOException { + String masterChallenge = Jnlp3Util.generateChallenge(); + String encryptedMasterChallenge = handshakeCiphers.encrypt(masterChallenge); + out.println(encryptedMasterChallenge.getBytes(Charset.forName("UTF-8")).length); + out.print(encryptedMasterChallenge); + out.flush(); + + // Verify the challenge response from the slave. + String encryptedMasterChallengeResponse = in.readUTF(); + String masterChallengeResponse = handshakeCiphers.decrypt( + encryptedMasterChallengeResponse); + if (!Jnlp3Util.validateChallengeResponse(masterChallenge, masterChallengeResponse)) { + error("Incorrect master challenge response from slave"); + return false; } - Channel establishedChannel = jnlpConnect(computer, channelCiphers); - establishedChannel.setProperty(COOKIE_NAME, newCookie); + return true; } protected Channel jnlpConnect( From 4915c31629afbc092345e600855279ce891e0dd4 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 29 Jan 2016 08:28:22 +0000 Subject: [PATCH 03/14] Moved some of the server-side logic to remoting This way, the inner-working of the protocol can be kept private within the remoting module, which increases a modularity. --- .../slaves/JnlpSlaveAgentProtocol.java | 8 +- .../slaves/JnlpSlaveAgentProtocol3.java | 173 +++--------------- .../jenkins/slaves/JnlpSlaveHandshake.java | 3 + pom.xml | 2 +- 4 files changed, 30 insertions(+), 156 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java index d82a42e51a74..f0f51d924463 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol.java @@ -2,6 +2,7 @@ import hudson.AbortException; import hudson.Extension; +import hudson.model.Computer; import hudson.remoting.Channel; import hudson.remoting.Channel.Listener; import hudson.remoting.ChannelBuilder; @@ -10,6 +11,7 @@ import jenkins.AgentProtocol; import jenkins.model.Jenkins; import jenkins.security.HMACConfidentialKey; +import org.jenkinsci.remoting.engine.JnlpServerHandshake; import org.jenkinsci.remoting.nio.NioChannelHub; import javax.inject.Inject; @@ -66,7 +68,7 @@ public void handle(Socket socket) throws IOException, InterruptedException { new Handler(hub.getHub(),socket).run(); } - protected static class Handler extends JnlpSlaveHandshake { + protected static class Handler extends JnlpServerHandshake { /** * @deprecated as of 1.559 @@ -77,9 +79,7 @@ public Handler(Socket socket) throws IOException { } public Handler(NioChannelHub hub, Socket socket) throws IOException { - super(hub,socket, - new DataInputStream(socket.getInputStream()), - new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8")),true)); + super(hub, Computer.threadPoolForRemoting, socket); } protected void run() throws IOException, InterruptedException { diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index ce0a59beef68..38ea98535808 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -2,43 +2,27 @@ import hudson.AbortException; import hudson.Extension; -import hudson.TcpSlaveAgentListener; -import hudson.Util; +import hudson.model.Computer; import hudson.remoting.Channel; import hudson.remoting.ChannelBuilder; -import hudson.remoting.SocketChannelStream; import hudson.slaves.SlaveComputer; import jenkins.AgentProtocol; import jenkins.model.Jenkins; -import org.jenkinsci.remoting.engine.JnlpProtocol; -import org.jenkinsci.remoting.engine.JnlpProtocol3; -import org.jenkinsci.remoting.engine.jnlp3.ChannelCiphers; -import org.jenkinsci.remoting.engine.jnlp3.HandshakeCiphers; -import org.jenkinsci.remoting.engine.jnlp3.Jnlp3Util; +import org.jenkinsci.remoting.engine.JnlpServer3Handshake; import org.jenkinsci.remoting.nio.NioChannelHub; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; import javax.inject.Inject; -import java.io.BufferedWriter; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; import java.io.IOException; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; -import java.nio.charset.Charset; -import java.security.SecureRandom; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.logging.Level; +import java.util.logging.Logger; /** * Master-side implementation for JNLP3-connect protocol. * - *

@see {@link JnlpProtocol3} for more details. + *

@see {@link org.jenkinsci.remoting.engine.JnlpProtocol3} for more details. * * @author Akshay Dayal */ @@ -57,146 +41,30 @@ public void handle(Socket socket) throws IOException, InterruptedException { new Handler(hub.getHub(), socket).run(); } - static class Handler extends JnlpSlaveHandshake { + static class Handler extends JnlpServer3Handshake { + + private SlaveComputer computer; public Handler(NioChannelHub hub, Socket socket) throws IOException { - super(hub,socket, - new DataInputStream(socket.getInputStream()), - new PrintWriter(new BufferedWriter(new OutputStreamWriter( - socket.getOutputStream(), Charset.forName("UTF-8"))), true)); + super(hub, Computer.threadPoolForRemoting, socket); } protected void run() throws IOException, InterruptedException { - // Get initiation information from slave. - request.load(new ByteArrayInputStream(in.readUTF().getBytes(Charset.forName("UTF-8")))); - String nodeName = request.getProperty(JnlpProtocol3.SLAVE_NAME_KEY); - - // Create handshake ciphers. - SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); - if(computer == null) { - error("Slave trying to register for invalid node: " + nodeName); - return; - } - String slaveSecret = computer.getJnlpMac(); - HandshakeCiphers handshakeCiphers = HandshakeCiphers.create(nodeName, slaveSecret); - - // Authenticate to the slave. - if (!authenticateToSlave(handshakeCiphers)) { - return; - } - - // If there is a cookie decrypt it. - String cookie = null; - if (request.getProperty(JnlpProtocol3.COOKIE_KEY) != null) { - cookie = handshakeCiphers.decrypt(request.getProperty(JnlpProtocol3.COOKIE_KEY)); - } - - // Validate the slave. - if (!validateSlave(handshakeCiphers)) { - return; - } - - // The slave is authenticated, see if its already connected. - Channel oldChannel = computer.getChannel(); - if(oldChannel != null) { - if (cookie != null && cookie.equals(oldChannel.getProperty(COOKIE_NAME))) { - // We think we are currently connected, but this request proves that it's from - // the party we are supposed to be communicating to. so let the current one get - // disconnected - LOGGER.info("Disconnecting " + nodeName + - " as we are reconnected from the current peer"); - try { - computer.disconnect(new TcpSlaveAgentListener.ConnectionFromCurrentPeer()) - .get(15, TimeUnit.SECONDS); - } catch (ExecutionException e) { - throw new IOException("Failed to disconnect the current client",e); - } catch (TimeoutException e) { - throw new IOException("Failed to disconnect the current client",e); - } - } else { - error(nodeName + - " is already connected to this master. Rejecting this connection."); - return; - } - } - - // Send greeting and new cookie. - out.println(JnlpProtocol.GREETING_SUCCESS); - String newCookie = generateCookie(); - out.println(handshakeCiphers.encrypt(newCookie)); - - // Now get the channel cipher information. - String aesKeyString = handshakeCiphers.decrypt(in.readUTF()); - String specKeyString = handshakeCiphers.decrypt(in.readUTF()); - ChannelCiphers channelCiphers = ChannelCiphers.create( - Jnlp3Util.keyFromString(aesKeyString), - Jnlp3Util.keyFromString(specKeyString)); - - Channel establishedChannel = jnlpConnect(computer, channelCiphers); - establishedChannel.setProperty(COOKIE_NAME, newCookie); - } - - private boolean authenticateToSlave(HandshakeCiphers handshakeCiphers) throws IOException { - String challenge = handshakeCiphers.decrypt( - request.getProperty(JnlpProtocol3.CHALLENGE_KEY)); - - // Send slave challenge response. - String challengeResponse = Jnlp3Util.createChallengeResponse(challenge); - String encryptedChallengeResponse = handshakeCiphers.encrypt(challengeResponse); - out.println(encryptedChallengeResponse.getBytes(Charset.forName("UTF-8")).length); - out.print(encryptedChallengeResponse); - out.flush(); - - // If the slave accepted our challenge response send our challenge. - String challengeVerificationMessage = in.readUTF(); - if (!challengeVerificationMessage.equals(JnlpProtocol.GREETING_SUCCESS)) { - error("Slave did not accept our challenge response"); - return false; - } - - return true; - } - - private boolean validateSlave(HandshakeCiphers handshakeCiphers) throws IOException { - String masterChallenge = Jnlp3Util.generateChallenge(); - String encryptedMasterChallenge = handshakeCiphers.encrypt(masterChallenge); - out.println(encryptedMasterChallenge.getBytes(Charset.forName("UTF-8")).length); - out.print(encryptedMasterChallenge); - out.flush(); - - // Verify the challenge response from the slave. - String encryptedMasterChallengeResponse = in.readUTF(); - String masterChallengeResponse = handshakeCiphers.decrypt( - encryptedMasterChallengeResponse); - if (!Jnlp3Util.validateChallengeResponse(masterChallenge, masterChallengeResponse)) { - error("Incorrect master challenge response from slave"); - return false; - } - - return true; - } - - protected Channel jnlpConnect( - SlaveComputer computer, ChannelCiphers channelCiphers) - throws InterruptedException, IOException { final String nodeName = computer.getName(); final OutputStream log = computer.openLogFile(); PrintWriter logw = new PrintWriter(log,true); - logw.println("JNLP agent connected from "+ socket.getInetAddress()); + logw.println("JNLP agent connected from " + socket.getInetAddress()); + + ChannelBuilder cb = createChannelBuilder(nodeName).withHeaderStream(log); try { - ChannelBuilder cb = createChannelBuilder(nodeName); - Channel channel = cb.withHeaderStream(log) - .build(new CipherInputStream(SocketChannelStream.in(socket), - channelCiphers.getDecryptCipher()), - new CipherOutputStream(SocketChannelStream.out(socket), - channelCiphers.getEncryptCipher())); + Channel channel = connect(cb); computer.setChannel(channel, log, new Channel.Listener() { @Override public void onClosed(Channel channel, IOException cause) { - if(cause != null) + if (cause != null) LOGGER.log(Level.WARNING, Thread.currentThread().getName() + " for + " + nodeName + " terminated", cause); @@ -207,7 +75,6 @@ public void onClosed(Channel channel, IOException cause) { } } }); - return computer.getChannel(); } catch (AbortException e) { logw.println(e.getMessage()); logw.println("Failed to establish the connection with the slave"); @@ -219,12 +86,16 @@ public void onClosed(Channel channel, IOException cause) { } } - private String generateCookie() { - byte[] cookie = new byte[32]; - new SecureRandom().nextBytes(cookie); - return Util.toHexString(cookie); + @Override + protected String getNodeSecret(String nodeName) throws Failure { + computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); + if (computer == null) { + throw new Failure("Slave trying to register for invalid node: " + nodeName); + } + return computer.getJnlpMac(); } + } - static final String COOKIE_NAME = JnlpSlaveAgentProtocol3.class.getName() + ".cookie"; + private static final Logger LOGGER = Logger.getLogger(JnlpSlaveAgentProtocol3.class.getName()); } diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java index eb9499e1c34b..389066825150 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java @@ -4,6 +4,7 @@ import hudson.remoting.Channel; import hudson.remoting.ChannelBuilder; import hudson.remoting.Engine; +import org.jenkinsci.remoting.engine.JnlpServerHandshake; import org.jenkinsci.remoting.nio.NioChannelHub; import java.io.DataInputStream; @@ -21,6 +22,8 @@ * * @author Kohsuke Kawaguchi * @since 1.561 + * @deprecated as of 1.609 + * Use {@link JnlpServerHandshake} */ public class JnlpSlaveHandshake { /** diff --git a/pom.xml b/pom.xml index 4a525155a5b0..af244f801a04 100644 --- a/pom.xml +++ b/pom.xml @@ -176,7 +176,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 2.51-SNAPSHOT + 2.54-SNAPSHOT From a1d3634b0b1b6b20c075caf3b1ab73e5556acd62 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 2 Feb 2016 11:04:04 +0100 Subject: [PATCH 04/14] JnlpAgentReceiver needs to be adjusted for the new type Given that there's only one caller, I think handling AbstractMethodError is better than doing the compatibility method ping pong --- .../slaves/DefaultJnlpSlaveReceiver.java | 3 +- .../jenkins/slaves/JnlpAgentReceiver.java | 12 +- .../slaves/JnlpSlaveAgentProtocol2.java | 10 +- .../jenkins/slaves/JnlpSlaveHandshake.java | 107 +----------------- 4 files changed, 25 insertions(+), 107 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java index 1011db9334d7..1a2cdfc1b838 100644 --- a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java +++ b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java @@ -7,6 +7,7 @@ import hudson.remoting.Channel; import hudson.slaves.SlaveComputer; import jenkins.model.Jenkins; +import org.jenkinsci.remoting.engine.JnlpServerHandshake; import java.io.IOException; import java.security.SecureRandom; @@ -25,7 +26,7 @@ @Extension public class DefaultJnlpSlaveReceiver extends JnlpAgentReceiver { @Override - public boolean handle(String nodeName, JnlpSlaveHandshake handshake) throws IOException, InterruptedException { + public boolean handle(String nodeName, JnlpServerHandshake handshake) throws IOException, InterruptedException { SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); if(computer==null) { diff --git a/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java b/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java index 914447332f66..3a382fd6c4f5 100644 --- a/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java +++ b/core/src/main/java/jenkins/slaves/JnlpAgentReceiver.java @@ -4,6 +4,7 @@ import hudson.ExtensionPoint; import hudson.model.Slave; import jenkins.model.Jenkins; +import org.jenkinsci.remoting.engine.JnlpServerHandshake; import java.io.IOException; import java.util.Properties; @@ -56,7 +57,16 @@ public abstract class JnlpAgentReceiver implements ExtensionPoint { * @throws Exception * Any exception thrown from this method will fatally terminate the connection. */ - public abstract boolean handle(String name, JnlpSlaveHandshake handshake) throws IOException, InterruptedException; + public abstract boolean handle(String name, JnlpServerHandshake handshake) throws IOException, InterruptedException; + + /** + * @deprecated + * Use {@link #handle(String, JnlpServerHandshake)} + */ + public boolean handle(String name, JnlpSlaveHandshake handshake) throws IOException, InterruptedException { + return handle(name,(JnlpServerHandshake)handshake); + } + public static ExtensionList all() { return ExtensionList.lookup(JnlpAgentReceiver.class); diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java index 30993bc08b1e..157811c948bf 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol2.java @@ -1,6 +1,7 @@ package jenkins.slaves; import hudson.Extension; +import org.jenkinsci.remoting.engine.JnlpServerHandshake; import org.jenkinsci.remoting.nio.NioChannelHub; import java.io.ByteArrayInputStream; @@ -54,8 +55,13 @@ protected void run() throws IOException, InterruptedException { final String nodeName = request.getProperty("Node-Name"); for (JnlpAgentReceiver recv : JnlpAgentReceiver.all()) { - if (recv.handle(nodeName,this)) - return; + try { + if (recv.handle(nodeName,this)) + return; + } catch (AbstractMethodError e) { + if (recv.handle(nodeName,new JnlpSlaveHandshake(this))) + return; + } } error("Unrecognized name: "+nodeName); diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java index 389066825150..4c9dcfa87370 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveHandshake.java @@ -1,21 +1,11 @@ package jenkins.slaves; -import hudson.model.Computer; -import hudson.remoting.Channel; -import hudson.remoting.ChannelBuilder; -import hudson.remoting.Engine; import org.jenkinsci.remoting.engine.JnlpServerHandshake; import org.jenkinsci.remoting.nio.NioChannelHub; -import java.io.DataInputStream; -import java.io.DataOutputStream; import java.io.IOException; -import java.io.PrintWriter; import java.net.Socket; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.concurrent.ExecutorService; /** * Palette of objects to talk to the incoming JNLP slave connection. @@ -25,97 +15,8 @@ * @deprecated as of 1.609 * Use {@link JnlpServerHandshake} */ -public class JnlpSlaveHandshake { - /** - * Useful for creating a {@link Channel} with NIO as the underlying transport. - */ - /*package*/ final NioChannelHub hub; - - /** - * Socket connection to the slave. - */ - /*package*/ final Socket socket; - - /** - * Wrapping Socket input stream. - */ - /*package*/ final DataInputStream in; - - /** - * For writing handshaking response. - * - * This is a poor design choice that we just carry forward for compatibility. - * For better protocol design, {@link DataOutputStream} is preferred for newer - * protocols. - */ - /*package*/ final PrintWriter out; - - /** - * Bag of properties the JNLP agent have sent us during the hand-shake. - */ - /*package*/ final Properties request = new Properties(); - - - /*package*/ JnlpSlaveHandshake(NioChannelHub hub, Socket socket, DataInputStream in, PrintWriter out) { - this.hub = hub; - this.socket = socket; - this.in = in; - this.out = out; - } - - public NioChannelHub getHub() { - return hub; - } - - public Socket getSocket() { - return socket; - } - - public DataInputStream getIn() { - return in; - } - - public PrintWriter getOut() { - return out; +public class JnlpSlaveHandshake extends JnlpServerHandshake { + /*package*/ JnlpSlaveHandshake(JnlpServerHandshake rhs) { + super(rhs); } - - public Properties getRequestProperties() { - return request; - } - - public String getRequestProperty(String name) { - return request.getProperty(name); - } - - - /** - * Sends the error output and bail out. - */ - public void error(String msg) throws IOException { - out.println(msg); - LOGGER.log(Level.WARNING,Thread.currentThread().getName()+" is aborted: "+msg); - socket.close(); - } - - /** - * {@link JnlpAgentReceiver} calls this method to tell the client that the server - * is happy with the handshaking and is ready to move on to build a channel. - */ - public void success(Properties response) { - out.println(Engine.GREETING_SUCCESS); - for (Entry e : response.entrySet()) { - out.println(e.getKey()+": "+e.getValue()); - } - out.println(); // empty line to conclude the response header - } - - public ChannelBuilder createChannelBuilder(String nodeName) { - if (hub==null) - return new ChannelBuilder(nodeName, Computer.threadPoolForRemoting); - else - return hub.newChannelBuilder(nodeName, Computer.threadPoolForRemoting); - } - - - static final Logger LOGGER = Logger.getLogger(JnlpSlaveHandshake.class.getName()); } From 3e17176fb4e6fc3783276e419e1739f74ec0785a Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 2 Feb 2016 11:32:53 +0100 Subject: [PATCH 05/14] Fixing a compilation problem --- core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java index 31aefa71e3c8..179ae52a0e8a 100644 --- a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java +++ b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java @@ -88,7 +88,7 @@ public boolean handle(String nodeName, JnlpServerHandshake handshake) throws IOE * @return * true if the slave secret matches the handshake secret, false otherwise. */ - private boolean matchesSecret(String nodeName, JnlpSlaveHandshake handshake){ + private boolean matchesSecret(String nodeName, JnlpServerHandshake handshake){ SlaveComputer computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); String handshakeSecret = handshake.getRequestProperty("Secret-Key"); // Verify that the slave secret matches the handshake secret. From 7b6a6c6316862c05d79cc6cd1194e9e8be4f6525 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 2 Feb 2016 11:33:02 +0100 Subject: [PATCH 06/14] Java7 code transformation --- .../main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java index 179ae52a0e8a..3de0ab274ad1 100644 --- a/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java +++ b/core/src/main/java/jenkins/slaves/DefaultJnlpSlaveReceiver.java @@ -44,9 +44,7 @@ public boolean handle(String nodeName, JnlpServerHandshake handshake) throws IOE LOGGER.info("Disconnecting "+nodeName+" as we are reconnected from the current peer"); try { computer.disconnect(new ConnectionFromCurrentPeer()).get(15, TimeUnit.SECONDS); - } catch (ExecutionException e) { - throw new IOException("Failed to disconnect the current client",e); - } catch (TimeoutException e) { + } catch (ExecutionException | TimeoutException e) { throw new IOException("Failed to disconnect the current client",e); } } else { From 19ad0e5d68d3c133e2fd3e61cda3d23492433b15 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 2 Feb 2016 12:03:33 +0100 Subject: [PATCH 07/14] Log cannot be opened twice so it needs to be kept around. --- .../slaves/JnlpSlaveAgentProtocol3.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 38ea98535808..3c5b8e9fa3f2 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -42,23 +42,17 @@ public void handle(Socket socket) throws IOException, InterruptedException { } static class Handler extends JnlpServer3Handshake { - private SlaveComputer computer; + private PrintWriter logw; + private OutputStream log; public Handler(NioChannelHub hub, Socket socket) throws IOException { super(hub, Computer.threadPoolForRemoting, socket); } protected void run() throws IOException, InterruptedException { - final String nodeName = computer.getName(); - final OutputStream log = computer.openLogFile(); - PrintWriter logw = new PrintWriter(log,true); - logw.println("JNLP agent connected from " + socket.getInetAddress()); - - ChannelBuilder cb = createChannelBuilder(nodeName).withHeaderStream(log); - try { - Channel channel = connect(cb); + Channel channel = connect(); computer.setChannel(channel, log, new Channel.Listener() { @@ -67,7 +61,7 @@ public void onClosed(Channel channel, IOException cause) { if (cause != null) LOGGER.log(Level.WARNING, Thread.currentThread().getName() + " for + " + - nodeName + " terminated", cause); + getNodeName() + " terminated", cause); try { socket.close(); } catch (IOException e) { @@ -80,12 +74,21 @@ public void onClosed(Channel channel, IOException cause) { logw.println("Failed to establish the connection with the slave"); throw e; } catch (IOException e) { - logw.println("Failed to establish the connection with the slave " + nodeName); + logw.println("Failed to establish the connection with the slave " + getNodeName()); e.printStackTrace(logw); throw e; } } + @Override + public ChannelBuilder createChannelBuilder(String nodeName) { + log = computer.openLogFile(); + logw = new PrintWriter(log,true); + logw.println("JNLP agent connected from " + socket.getInetAddress()); + + return super.createChannelBuilder(nodeName).withHeaderStream(log); + } + @Override protected String getNodeSecret(String nodeName) throws Failure { computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); From 1ba9c3065fb6786b153cd61ae91f3207999e653c Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 2 Feb 2016 16:04:04 -0500 Subject: [PATCH 08/14] Terminology fix --- .../main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 3c5b8e9fa3f2..c9648242371d 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -71,10 +71,10 @@ public void onClosed(Channel channel, IOException cause) { }); } catch (AbortException e) { logw.println(e.getMessage()); - logw.println("Failed to establish the connection with the slave"); + logw.println("Failed to establish the connection with the agent"); throw e; } catch (IOException e) { - logw.println("Failed to establish the connection with the slave " + getNodeName()); + logw.println("Failed to establish the connection with the agent " + getNodeName()); e.printStackTrace(logw); throw e; } @@ -93,7 +93,7 @@ public ChannelBuilder createChannelBuilder(String nodeName) { protected String getNodeSecret(String nodeName) throws Failure { computer = (SlaveComputer) Jenkins.getInstance().getComputer(nodeName); if (computer == null) { - throw new Failure("Slave trying to register for invalid node: " + nodeName); + throw new Failure("Agent trying to register for invalid node: " + nodeName); } return computer.getJnlpMac(); } From 451186d97008470a29aaa2b026ee1e021756edd4 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 19 Feb 2016 10:42:05 -0800 Subject: [PATCH 09/14] 2.54 contains JNLP3 change --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8eb258229d31..08871b195288 100644 --- a/pom.xml +++ b/pom.xml @@ -180,7 +180,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 2.54-SNAPSHOT + 2.54 From a6cd33f3a644a8b170306659f566ae0e532abe29 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 22 Feb 2016 11:10:06 -0800 Subject: [PATCH 10/14] Need to call ChannelConfigurator --- .../java/jenkins/slaves/JnlpSlaveAgentProtocol3.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index c9648242371d..42eb80632453 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -8,6 +8,7 @@ import hudson.slaves.SlaveComputer; import jenkins.AgentProtocol; import jenkins.model.Jenkins; +import jenkins.security.ChannelConfigurator; import org.jenkinsci.remoting.engine.JnlpServer3Handshake; import org.jenkinsci.remoting.nio.NioChannelHub; @@ -86,7 +87,13 @@ public ChannelBuilder createChannelBuilder(String nodeName) { logw = new PrintWriter(log,true); logw.println("JNLP agent connected from " + socket.getInetAddress()); - return super.createChannelBuilder(nodeName).withHeaderStream(log); + ChannelBuilder cb = super.createChannelBuilder(nodeName).withHeaderStream(log); + + for (ChannelConfigurator cc : ChannelConfigurator.all()) { + cc.onChannelBuilding(cb, computer); + } + + return cb; } @Override From e6065062809a3ae1a204c3d30db60354a11a2070 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 22 Feb 2016 11:11:21 -0800 Subject: [PATCH 11/14] When this gets merged let's record the version number --- core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 42eb80632453..0187a8b06673 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -26,6 +26,7 @@ *

@see {@link org.jenkinsci.remoting.engine.JnlpProtocol3} for more details. * * @author Akshay Dayal + * @since 1.XXX */ @Extension public class JnlpSlaveAgentProtocol3 extends AgentProtocol { From b1e3f6fdd2095d17293ce2c9b26e40ea98380d73 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 4 Mar 2016 13:45:54 -0800 Subject: [PATCH 12/14] A/B test this feature in production JNLP3 is activated now for 10% of users. Let's keep it like this for a while and if no major issue occurs we should expose it to everyone. --- .../slaves/JnlpSlaveAgentProtocol3.java | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java index 0187a8b06673..8b6b3df1297b 100644 --- a/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java +++ b/core/src/main/java/jenkins/slaves/JnlpSlaveAgentProtocol3.java @@ -2,6 +2,7 @@ import hudson.AbortException; import hudson.Extension; +import hudson.Util; import hudson.model.Computer; import hudson.remoting.Channel; import hudson.remoting.ChannelBuilder; @@ -11,6 +12,8 @@ import jenkins.security.ChannelConfigurator; import org.jenkinsci.remoting.engine.JnlpServer3Handshake; import org.jenkinsci.remoting.nio.NioChannelHub; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; import javax.inject.Inject; import java.io.IOException; @@ -35,7 +38,8 @@ public class JnlpSlaveAgentProtocol3 extends AgentProtocol { @Override public String getName() { - return "JNLP3-connect"; + if (ENABLED) return "JNLP3-connect"; + else return "JNLP3-disabled"; } @Override @@ -109,4 +113,25 @@ protected String getNodeSecret(String nodeName) throws Failure { } private static final Logger LOGGER = Logger.getLogger(JnlpSlaveAgentProtocol3.class.getName()); + + /** + * Flag to control the activation of JNLP3 protocol. + * This feature is being A/B tested right now. + * + *

+ * Once this will be on by default, the flag and this field will disappear. The system property is + * an escape hatch for those who hit any issues and those who are trying this out. + */ + @Restricted(NoExternalUse.class) + public static boolean ENABLED; + + static { + String propName = JnlpSlaveAgentProtocol3.class.getName() + ".enabled"; + if (System.getProperties().containsKey(propName)) + ENABLED = Boolean.getBoolean(propName); + else { + byte hash = Util.fromHexString(Jenkins.getActiveInstance().getLegacyInstanceId())[0]; + ENABLED = (hash%10)==0; + } + } } From d0d39705105179f7d3b8cdc7a73ccc2056fbf2e5 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Fri, 4 Mar 2016 14:31:29 -0800 Subject: [PATCH 13/14] Pick up a new version of remoting See https://github.com/jenkinsci/remoting/pull/78 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b4bcc0f5d07b..bf317f7b61db 100644 --- a/pom.xml +++ b/pom.xml @@ -179,7 +179,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 2.55 + 2.56-20160304.223037-1 From 04651f24d9615ac06b81ffe7aa6b1ddda3e3b921 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 7 Mar 2016 21:24:44 -0800 Subject: [PATCH 14/14] Pick up a released version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bf317f7b61db..adf876d5d3f9 100644 --- a/pom.xml +++ b/pom.xml @@ -179,7 +179,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 2.56-20160304.223037-1 + 2.56